Source AddDataFromFile
Source AddDataFromFile
Cette page est générée automatiquement à partir du dépôt local au moment de la génération de la documentation.
Fichiers inclus
packages/common/src/lib/widgets/add-data/add-data-from-file/add-data-from-file.declaration.tspackages/common/src/lib/widgets/add-data/add-data-from-file/add-data-from-file.config.tspackages/common/src/lib/widgets/add-data/add-data-from-file/add-data-from-file.i18n.tspackages/common/src/lib/widgets/add-data/add-data-from-file/add-data-from-file.model.tspackages/common/src/lib/widgets/add-data/add-data-from-file/AddDataFromFile.sveltepackages/common/src/lib/widgets/add-data/add-data-from-file/AddDataFromFileWidget.sveltepackages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/CSVFileInput.sveltepackages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/GeoJSONFileInput.sveltepackages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/GMLFileInput.sveltepackages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/GPXFileInput.sveltepackages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/KMLFileInput.sveltepackages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/KMZFileInput.sveltepackages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/ShapeFileInput.sveltepackages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte.tspackages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/SpatialFileInput.svelte
packages/common/src/lib/widgets/add-data/add-data-from-file/add-data-from-file.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';import { addDataFromFileWidgetConfigSchema, type AddDataConfigFromFileWidget } from './add-data-from-file.config';import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = { factory: () => import('./AddDataFromFileWidget.svelte').then((AddDataFromFileWidget) => widgetFactorySvelte(AddDataFromFileWidget), ), schema: () => addDataFromFileWidgetConfigSchema,} satisfies WidgetDeclaration;
export type AddDataFromFileProps = WidgetProps<AddDataConfigFromFileWidget>;packages/common/src/lib/widgets/add-data/add-data-from-file/add-data-from-file.config.ts
import { defineWidgetConfig } from '$lib/api/managers/configuration/models/widget/widget-configuration.schema';import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';import { i18nSchemaFrom } from '$lib/api/managers/i18n/i18n.schema';import { apiFeatureSymbolsSchema } from '$lib/api/symbol';import { type infer as zInfer, z } from 'zod';import { SpatialFileType } from './add-data-from-file.model';import { Projections } from '$lib/api/managers/projection';import { addDataFromFileI18n } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.i18n';
export const addDataFromFileConfigSchema = z .object({ fileTypes: z .enum(SpatialFileType) .array() .default([ SpatialFileType.GeoJSON, SpatialFileType.SHP, SpatialFileType.CSV, SpatialFileType.GPX, SpatialFileType.KML, SpatialFileType.KMZ, SpatialFileType.GML, ]), defaultFileType: z.enum(SpatialFileType).default(SpatialFileType.GeoJSON), defaultShapeFileWkid: z.number().default(Projections.LAMBERT_2008.wkid), defaultSymbolConfig: apiFeatureSymbolsSchema.prefault({}), defaultForceStyle: z.boolean().default(true), resetFormAfterFileAdded: z.boolean().default(false), showResetButton: z.boolean().default(false), closeOnFileAdded: z.boolean().default(true), csvTemplateExamplesUrl: z .string() .optional() .default('https://geoservices.wallonie.be/geoviewer/viewer/poi.zip'), }) .prefault({});export type AddDataFromFileConfig = z.infer<typeof addDataFromFileConfigSchema>;
export const addDataFromFileWidgetConfigSchema = defineWidgetConfig({ i18n: i18nSchemaFrom(addDataFromFileI18n), title: addDataFromFileI18n['add-data-from-file-title'], inToolbar: inToolbarSchemaFrom({ type: 'button', icon: { lucide: 'Plus', }, label: addDataFromFileI18n['add-data-from-file-title'], }), config: addDataFromFileConfigSchema,});
export type AddDataConfigFromFileWidget = zInfer<typeof addDataFromFileWidgetConfigSchema>;packages/common/src/lib/widgets/add-data/add-data-from-file/add-data-from-file.i18n.ts
import type { I18nRegistry } from '$lib/api/managers/i18n';
export const addDataFromFileI18n = { 'add-data-from-file-title': { fr: 'Ajouter des données', nl: 'NL - Ajouter des données', }, 'add-data-from-file-sub-title': { fr: 'Depuis un fichier', nl: 'NL - Depuis un fichier', }, 'add-data': { fr: 'Ajouter les données', nl: 'NL - Ajouter les données', }, 'csv-tips-button': { fr: "Quelques points d'attention à respecter avec un fichier CSV. Plus d'infos", nl: "NL - Quelques points d'attention à respecter avec un fichier CSV. Plus d'infos", }, 'csv-tips-text': { fr: `Pour fonctionner, cet outil a besoin d'un fichier CSV. Attention, l'ajout de fichiers .csv d'une taille supérieure à 0.5 Mb peut dégrader les performances de votre navigateur. Le fichier CSV doit respecter le format suivant: <ul class="gv-list-disc gv-ml-6"> <li>La première ligne est constituée des titres de champs (noms des colonnes).</li> <li>En fonction du type d’information que vous souhaitez ajouter, renseignez les colonnes suivantes :</li> <ul class="gv-list-disc gv-ml-6"> <li>soit un champ nommé "X" et un champ nommé "Y" reprenant les coordonnées du point exprimées en Lambert Belge 1972 (exemple : 183635)</li> <li>soit un champ nommé "latitude" et un champ nommé "longitude" reprenant les coordonnées en degrés décimales exprimées en wgs84 (exemple : 4.84250)</li> <li>soit un champ nommé "capakey" reprenant la clé cadastrale (exemple : 92074A0010/00B000 – ce champ résulte de la concaténation des valeurs : CODE_DIVISION + SECTION + RADICAL + / + BIS + EXPOSANT + PUISSANCE)</li> <li>soit des champs nommés "Commune","Rue","Numéro" et éventuellement "Code Postal" reprenant une adresse (exemple : "Namur" "St Nicolas" "127")</li> </ul> </ul> `, nl: "NL - Quelques points d'attention à respecter avec un fichier CSV. Plus d'infos", }, 'data-type': { fr: 'Type de données', nl: 'NL - Type de données', }, 'download-csv-examples': { fr: 'Télécharger les examples CSV', nl: 'NL - Télécharger les examples CSV', }, 'file-url': { fr: 'Url du fichier', nl: 'NL - Url du fichier', }, 'file-upload': { fr: 'Upload du fichier', nl: 'NL - Upload du fichier', }, 'service-name': { fr: 'Nom de la couche', nl: 'Naam van de layer', }, 'loaded-layers': { fr: "Couches chargées depuis l'URL", nl: "NL - Couches chargées depuis l'URL", }, 'shape-file-wkid-message': { fr: "Si le fichier .prj n'est pas présent, les données seront considérées comment étant en EPSG:{{wkid}}", nl: "NL - Si le fichier .prj n'est pas présent, les données seront considérées comment étant en EPSG:{{wkid}}", }, 'style-config': { fr: 'Configuration du style', nl: 'NL - Configuration du style', }, 'force-style': { fr: 'Forcer le style', nl: 'NL Forcer le style', }, 'DASHSTYLE-DASH': { fr: 'Pointillés', nl: 'NL - Pointillés', }, 'DASHSTYLE-DASHDOT': { fr: 'Pointillés - points', nl: 'NL - Pointillés - points', }, 'DASHSTYLE-DOT': { fr: 'Points', nl: 'NL - Points', },} satisfies I18nRegistry;
export type AddDataFromFileI18n = I18nRegistry<keyof typeof addDataFromFileI18n>;packages/common/src/lib/widgets/add-data/add-data-from-file/add-data-from-file.model.ts
import type { ApiFeature } from '$lib/api/feature';
export enum SpatialFileType { GeoJSON = 'GeoJSON', SHP = 'SHP', CSV = 'CSV', KML = 'KML', XLSX = 'XLSX', KMZ = 'KMZ', GPX = 'GPX', GML = 'GML',}
export interface ParsingResult { features: ApiFeature[]; label: string; errorMessages?: string[];}
export type SpatialFileTypeConfig = { spatialFileType: SpatialFileType; allowedExtensions: string[]; fileExtension: string; contentType: string; exportAvailable: boolean;};
export const APPLICATION_JSON = 'application/json';export const APPLICATION_GPX = 'application/gpx+xml';export const APPLICATION_CSV = 'application/csv';export const APPLICATION_XML = 'application/xml';export const APPLICATION_ZIP = 'application/zip';export const APPLICATION_XLXS = 'application/xlxs';export const APPLICATION_GML = 'application/gml';
export const SpatialFileInfo: Record<SpatialFileType, SpatialFileTypeConfig> = { [SpatialFileType.GeoJSON]: { spatialFileType: SpatialFileType.GeoJSON, allowedExtensions: ['.json'], fileExtension: '.json', contentType: APPLICATION_JSON, exportAvailable: true, }, [SpatialFileType.SHP]: { spatialFileType: SpatialFileType.SHP, allowedExtensions: ['.zip'], fileExtension: '.zip', contentType: APPLICATION_ZIP, exportAvailable: true, }, [SpatialFileType.CSV]: { spatialFileType: SpatialFileType.CSV, allowedExtensions: ['.csv'], fileExtension: '.csv', contentType: APPLICATION_CSV, exportAvailable: true, }, [SpatialFileType.GPX]: { spatialFileType: SpatialFileType.GPX, allowedExtensions: ['.gpx'], fileExtension: '.gpx', contentType: APPLICATION_GPX, exportAvailable: true, }, [SpatialFileType.KML]: { spatialFileType: SpatialFileType.KML, allowedExtensions: ['.kml'], fileExtension: '.kml', contentType: APPLICATION_XML, exportAvailable: true, }, [SpatialFileType.KMZ]: { spatialFileType: SpatialFileType.KMZ, allowedExtensions: ['.kmz'], fileExtension: '.kmz', contentType: APPLICATION_ZIP, exportAvailable: false, }, [SpatialFileType.XLSX]: { spatialFileType: SpatialFileType.XLSX, allowedExtensions: ['.xlxs'], fileExtension: '.xlxs', contentType: APPLICATION_XLXS, exportAvailable: true, }, [SpatialFileType.GML]: { spatialFileType: SpatialFileType.GML, allowedExtensions: ['.gml'], fileExtension: '.gml', contentType: APPLICATION_GML, exportAvailable: false, },};packages/common/src/lib/widgets/add-data/add-data-from-file/AddDataFromFile.svelte
<script lang="ts"> import { GeoviewerError } from '$lib/api/managers/error/geoviewer-error.model'; import { getI18n } from '$lib/api/managers/i18n'; import { getMapManager } from '$lib/api/map'; import { type ApiFeatureSymbols } from '$lib/api/symbol'; import { deepClone, getErrorMessage, nonEmpty } from '$lib/api/utils'; import StringUtils from '$lib/api/utils/string.utils'; import { ApiSelect } from '$lib/components/api-select'; import ApiSymbolEditor from '$lib/components/api-symbol-editor/ApiSymbolEditor.svelte'; import { Button } from '$lib/components/shadcn/ui/button'; import { CollapsibleContent, Root as CollapsibleRoot } from '$lib/components/shadcn/ui/collapsible'; import { Input } from '$lib/components/shadcn/ui/input'; import { Label } from '$lib/components/shadcn/ui/label'; import { type ParsingResult, SpatialFileType } from './add-data-from-file.model'; import Info from 'lucide-svelte/icons/info'; import SpatialFileInput from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/SpatialFileInput.svelte'; import { setSpatialInputFileState, SpatialFileInputState, } from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte'; import { getAddData } from '$lib/widgets/add-data/add-data.svelte'; import type { AddDataFromFileI18n } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.i18n'; import type { AddDataFromFileConfig } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.config'; import Loader from '$lib/components/common/Loader.svelte'; import { getTopicManager } from '$lib/api/managers/topic'; import { initGraphicMapServiceConfiguration } from '$lib/api/managers/configuration'; import { highlightServiceInToc } from '$lib/widgets/toc/toc.utils'; import { getWidgetManager } from '$lib/api/managers/widget';
interface Props { config: AddDataFromFileConfig; i18nRegistry: AddDataFromFileI18n; }
const { config, i18nRegistry }: Props = $props();
const i18n = getI18n(i18nRegistry ?? {}); const topic = getTopicManager(); const mapManager = getMapManager(); const widgetManager = getWidgetManager(); const geometryEngine = mapManager.tools.geometryEngine; const featureConverter = mapManager.tools.featureConverter; const zoomTool = mapManager.tools.zoom; const spatialInputFileState = setSpatialInputFileState(new SpatialFileInputState(topic)); const addDataContext = getAddData();
let fileInputElement = $state.raw<HTMLInputElement>(); let currentFileType = $state(SpatialFileType.SHP); let parsingResult = $state<ParsingResult[]>([]); let currentSymbolConfig = $state<ApiFeatureSymbols>(config.defaultSymbolConfig); let fileUrl = $state<string>(''); let csvTipShown = $state<boolean>(false);
let uploadType: 'file' | 'url' = 'file'; let loadedUrl = '';
$effect(() => { currentSymbolConfig = deepClone($state.snapshot(config.defaultSymbolConfig)); currentFileType = config.defaultFileType; });
const fileTypesOptions = $derived(config.fileTypes.map((fileType) => ({ label: fileType, value: fileType })));
let fetchingFile = $state(false);
function addMapService(): void { if (!parsingResult) { throw new GeoviewerError(`Unable to parse file`); } const addedMapServices = parsingResult.map((parsingResult) => { const mapServiceConfiguration = initGraphicMapServiceConfiguration({ id: StringUtils.uuid(), label: getNewLabel(parsingResult.label), symbols: $state.snapshot(currentSymbolConfig), }); const graphicMapService = mapManager.addGraphicMapService(mapServiceConfiguration); graphicMapService.addFeatures(parsingResult.features); return graphicMapService; }); addedMapServices.forEach((mapService) => { topic.publish({ type: 'AddData-add-mapService-from-file', layer: mapService, uploadType, fileType: currentFileType, sourceUrl: uploadType === 'url' ? loadedUrl : null, features: featureConverter.geoJSON.toGeoJSON(mapService.features), }); highlightServiceInToc(mapService.id, widgetManager, mapManager); });
const extents = addedMapServices.map((mapService) => mapService.extent).filter(nonEmpty); // TODO - handle projs (instead of assuming we want the 1st element wkid) const fullExtent = geometryEngine.unionExtent(extents); zoomTool.zoomToExtent(fullExtent); if (config.closeOnFileAdded) { addDataContext.closeWidget(); } if (config.resetFormAfterFileAdded) { reset(); } }
function getNewLabel(label: string): string { const labelAlreadyTaken = mapManager.layerList.list.find((x) => x.label === label); if (!labelAlreadyTaken) return label; const numberOfSuffixed = mapManager.layerList.list.filter((x) => x.label.match(/^(.+)_([0-9]+)$/)).length; return `${label}_${numberOfSuffixed + 1}`; }
function reset() { currentFileType = config.defaultFileType; currentSymbolConfig = deepClone(config.defaultSymbolConfig); if (fileInputElement) { fileInputElement.value = ''; } parsingResult = []; }
async function fetchFile() { fetchingFile = true; parsingResult = []; try { const response = await fetch(fileUrl); if (!response.ok) { throw new GeoviewerError(`Error while fetching file ${fileUrl}`); } const fileBlob = await response.blob(); spatialInputFileState.fileFromUrl = new File([fileBlob], 'downloadedFile'); uploadType = 'url'; loadedUrl = $state.snapshot(fileUrl); } catch (error) { spatialInputFileState.onParseError(getErrorMessage(error)); throw new GeoviewerError('Failed to load the url', { cause: error }); } finally { fetchingFile = false; } }
function setParsingResultFromFile(results: ParsingResult[]) { parsingResult = results; uploadType = 'file'; }</script>
<div class="gv-flex gv-flex-col gv-gap-3"> <span class="gv-font-bold gv-text-lg gv-my-2">{i18n('add-data-from-file-sub-title')}</span>
<div class="gv-flex gv-flex-col gv-gap-1"> <Label class="gv-font-bold">{i18n('common.file-format')}</Label> <ApiSelect bind:value={currentFileType} options={fileTypesOptions} dataTestId="AddDataFile-FileTypeSelect" size="sm" /> {#if currentFileType === SpatialFileType.CSV} <button data-test-id="AddDataFile-CSVInfoButton" class="gv-flex gv-items-center gv-text-sm gv-text-start" onclick={() => (csvTipShown = !csvTipShown)} > <Info class="gv-h-4" /> {i18n('csv-tips-button')} </button>
<div class="gv-mt-2"> <CollapsibleRoot bind:open={csvTipShown} class="w-full space-y-2"> <CollapsibleContent class="space-y-2"> <div data-test-id="AddDataFile-CSVInfoText" class="gv-text-xs"> {@html i18n('csv-tips-text')} </div> <a data-test-id="AddDataFile-DownloadCSVTemplates" class="gv-mt-1 gv-text-primary gv-text-sm" href={config.csvTemplateExamplesUrl}>{i18n('download-csv-examples')}</a > </CollapsibleContent> </CollapsibleRoot> </div> {/if} {#if currentFileType === SpatialFileType.SHP} <div class="gv-flex gv-text-sm gv-justify-between gv-w-full"> <div class="gv-flex gv-items-center"> <Info class="gv-h-4 gv-mt-[-2px]" /> {i18n('shape-file-wkid-message', { wkid: config.defaultShapeFileWkid })} </div> </div> {/if} </div>
<SpatialFileInput bind:parsingResult={() => parsingResult, setParsingResultFromFile} {i18nRegistry} {spatialInputFileState} {currentFileType} currentShapeFileWkid={config.defaultShapeFileWkid} />
<div class="gv-flex gv-flex-col gv-gap-1"> <Label class="gv-font-bold" for="add-data-file-input">{i18n('file-url')}</Label> <div class="gv-flex gv-gap-1"> <Input bind:value={fileUrl} data-test-id="AddDataFile-FileUrl" class="gv-flex-1" size="sm" /> <Button class="gv-w-1/5" disabled={!fileUrl || fetchingFile || fileUrl.length === 0} onclick={fetchFile} data-test-id="AddDataFile-LoadFileUrl" size="sm" > {#if fetchingFile} <Loader class="gv-size-4" /> {/if} {i18n('common.load')} </Button> </div> </div>
<div class="gv-flex gv-flex-col gv-gap-1"> <Label class="gv-font-bold">{i18n('style-config')}</Label> <div class="gv-w-1/2 gv-h-10"> <ApiSymbolEditor bind:value={currentSymbolConfig} /> </div> </div>
<div class="gv-flex gv-justify-end gv-gap-2"> {#if config.showResetButton} <Button onclick={() => reset()} data-test-id="AddDataFile-ResetButton" size="sm"> {i18n('common.reset')} </Button> {/if} <Button disabled={spatialInputFileState.loading || !parsingResult || parsingResult.length === 0} onclick={addMapService} data-test-id="AddDataFile-AddButton" size="sm" > {i18n('add-data')} </Button> </div></div>packages/common/src/lib/widgets/add-data/add-data-from-file/AddDataFromFileWidget.svelte
<script lang="ts"> import AddDataFromFile from './AddDataFromFile.svelte'; import type { AddDataFromFileProps } from './add-data-from-file.declaration';
let { fullConfig }: AddDataFromFileProps = $props();</script>
<AddDataFromFile config={fullConfig.config} i18nRegistry={fullConfig.i18n} />packages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/CSVFileInput.svelte
<script lang="ts"> import { type ParsingResult, SpatialFileInfo, SpatialFileType, } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.model'; import { FilesUtils, getErrorMessage } from '$lib/api/utils'; import { GeoviewerError } from '$lib/api/managers/error/geoviewer-error.model';
import type { SpatialFileInputState } from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte'; import { ApiFileInput } from '$lib/components/api-file-input'; import type { ApiFeatureConverter } from '$lib/api/tools/converter/api-feature.converter'; import { showToast } from '$lib/components/toast/toast.utils';
interface Props { parsingResult: ParsingResult[]; spatialInputFileState: SpatialFileInputState; featureConverter: ApiFeatureConverter; }
let { parsingResult = $bindable([]), spatialInputFileState, featureConverter }: Props = $props();
const allowedExtensions = SpatialFileInfo.CSV.allowedExtensions.join(',');
$effect(() => { if (spatialInputFileState.fileFromUrl) { readFileContent(spatialInputFileState.fileFromUrl); } });
async function handleFileChange(event: Event) { const file = FilesUtils.readFileFromEvent(event); if (file) { await readFileContent(file); } }
async function readFileContent(file: File) { try { spatialInputFileState.loading = true; const { features, errorMessages } = await featureConverter.csv.fromCSVFile(file); parsingResult = [ { features: features, label: file.name, errorMessages, }, ]; errorMessages.forEach((error) => { showToast({ level: 'error', message: error }); }); } catch (error) { spatialInputFileState.onParseError(getErrorMessage(error), SpatialFileType.CSV); throw new GeoviewerError('Failed to convert csv', { cause: error }); } finally { spatialInputFileState.loading = false; } }</script>
<ApiFileInput dataTestId="AddDataFile-InputFile" multiple={false} loading={spatialInputFileState.loading} accept={allowedExtensions} onchange={handleFileChange}/>packages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/GeoJSONFileInput.svelte
<script lang="ts"> import { type ParsingResult, SpatialFileInfo, SpatialFileType, } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.model'; import { FilesUtils, getErrorMessage } from '$lib/api/utils'; import { GeoviewerError } from '$lib/api/managers/error/geoviewer-error.model'; import { type SpatialFileInputState } from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte'; import { ApiFileInput } from '$lib/components/api-file-input'; import type { ApiFeatureConverter } from '$lib/api/tools/converter/api-feature.converter';
interface Props { parsingResult: ParsingResult[]; spatialInputFileState: SpatialFileInputState; featureConverter: ApiFeatureConverter; }
let { parsingResult = $bindable([]), spatialInputFileState, featureConverter }: Props = $props();
const allowedExtensions = SpatialFileInfo.GeoJSON.allowedExtensions.join(',');
$effect(() => { if (spatialInputFileState.fileFromUrl) { readFileContent(spatialInputFileState.fileFromUrl); } });
async function handleFileChange(event: Event) { const file = FilesUtils.readFileFromEvent(event); if (file) { await readFileContent(file); } }
async function readFileContent(file: File) { try { spatialInputFileState.loading = true; const defaultMapServiceName = file.name; parsingResult = [ { features: await featureConverter.geoJSON.fromGeoJSONFile(file), label: defaultMapServiceName, }, ]; } catch (error) { spatialInputFileState.onParseError(getErrorMessage(error), SpatialFileType.GeoJSON); throw new GeoviewerError('Failed to convert geojson', { cause: error }); } finally { spatialInputFileState.loading = false; } }</script>
<ApiFileInput dataTestId="AddDataFile-InputFile" multiple={false} loading={spatialInputFileState.loading} accept={allowedExtensions} onchange={handleFileChange}/>packages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/GMLFileInput.svelte
<script lang="ts"> import { type ParsingResult, SpatialFileInfo, SpatialFileType, } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.model'; import { FilesUtils, getErrorMessage } from '$lib/api/utils'; import { GeoviewerError } from '$lib/api/managers/error/geoviewer-error.model'; import type { SpatialFileInputState } from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte'; import { ApiFileInput } from '$lib/components/api-file-input'; import type { ApiFeatureConverter } from '$lib/api/tools/converter/api-feature.converter';
interface Props { parsingResult: ParsingResult[]; spatialInputFileState: SpatialFileInputState; featureConverter: ApiFeatureConverter; }
let { parsingResult = $bindable([]), spatialInputFileState, featureConverter }: Props = $props();
const allowedExtensions = SpatialFileInfo.GML.allowedExtensions.join(',');
$effect(() => { if (spatialInputFileState.fileFromUrl) { readFileContent(spatialInputFileState.fileFromUrl); } });
async function handleFileChange(event: Event) { spatialInputFileState.loading = true; const file = FilesUtils.readFileFromEvent(event); await readFileContent(file); }
async function readFileContent(file: File) { try { const features = await featureConverter.gml.fromGMLFile(file); if (features && features.length > 0) { parsingResult = [ { features, label: file.name, }, ]; } } catch (error) { spatialInputFileState.onParseError(SpatialFileType.GeoJSON, SpatialFileType.GML); throw new GeoviewerError(`${error}`); } finally { spatialInputFileState.loading = false; } }</script>
<ApiFileInput dataTestId="AddDataFile-InputFile" multiple={false} loading={spatialInputFileState.loading} accept={allowedExtensions} onchange={handleFileChange}></ApiFileInput>packages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/GPXFileInput.svelte
<script lang="ts"> import { type ParsingResult, SpatialFileInfo, SpatialFileType, } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.model'; import { FilesUtils, getErrorMessage } from '$lib/api/utils'; import { GeoviewerError } from '$lib/api/managers/error/geoviewer-error.model'; import type { SpatialFileInputState } from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte'; import { ApiFileInput } from '$lib/components/api-file-input'; import type { ApiFeatureConverter } from '$lib/api/tools/converter/api-feature.converter';
interface Props { parsingResult: ParsingResult[]; spatialInputFileState: SpatialFileInputState; featureConverter: ApiFeatureConverter; }
let { parsingResult = $bindable([]), spatialInputFileState, featureConverter }: Props = $props();
const allowedExtensions = SpatialFileInfo.GPX.allowedExtensions.join(',');
$effect(() => { if (spatialInputFileState.fileFromUrl) { readFileContent(spatialInputFileState.fileFromUrl); } });
async function handleFileChange(event: Event) { const file = FilesUtils.readFileFromEvent(event); if (file) { await readFileContent(file); } }
async function readFileContent(file: File) { try { spatialInputFileState.loading = true; const features = await featureConverter.gpx.fromGPXFile(file); if (features && features.length > 0) { parsingResult = [ { features, label: file.name, }, ]; } } catch (error) { spatialInputFileState.onParseError(getErrorMessage(error), SpatialFileType.GPX); throw new GeoviewerError('Failed to convert gpx', { cause: error }); } finally { spatialInputFileState.loading = false; } }</script>
<ApiFileInput dataTestId="AddDataFile-InputFile" multiple={false} loading={spatialInputFileState.loading} accept={allowedExtensions} onchange={handleFileChange}/>packages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/KMLFileInput.svelte
<script lang="ts"> import { type ParsingResult, SpatialFileInfo, SpatialFileType, } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.model'; import { FilesUtils, getErrorMessage } from '$lib/api/utils'; import { GeoviewerError } from '$lib/api/managers/error/geoviewer-error.model'; import type { SpatialFileInputState } from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte'; import { ApiFileInput } from '$lib/components/api-file-input'; import type { ApiFeatureConverter } from '$lib/api/tools/converter/api-feature.converter'; import { getFilenameFromFeaturesAttributes } from '$lib/api/tools/converter/api-feature-kml.converter';
interface Props { parsingResult: ParsingResult[]; spatialInputFileState: SpatialFileInputState; featureConverter: ApiFeatureConverter; }
let { parsingResult = $bindable([]), spatialInputFileState, featureConverter }: Props = $props();
const allowedExtensions = SpatialFileInfo.KML.allowedExtensions.join(',');
$effect(() => { if (spatialInputFileState.fileFromUrl) { readFileContent(spatialInputFileState.fileFromUrl); } });
async function handleFileChange(event: Event) { const file = FilesUtils.readFileFromEvent(event); if (file) { await readFileContent(file); } }
async function readFileContent(file: File) { try { spatialInputFileState.loading = true; const features = await featureConverter.kml.fromKMLFile(file); if (features.length > 0) { parsingResult = [{ features, label: getFilenameFromFeaturesAttributes(features) || file.name }]; } } catch (error) { spatialInputFileState.onParseError(getErrorMessage(error), SpatialFileType.KML); throw new GeoviewerError('Failed to convert kml', { cause: error }); } finally { spatialInputFileState.loading = false; } }</script>
<ApiFileInput dataTestId="AddDataFile-InputFile" multiple={false} loading={spatialInputFileState.loading} accept={allowedExtensions} onchange={handleFileChange}/>packages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/KMZFileInput.svelte
<script lang="ts"> import { type ParsingResult, SpatialFileInfo, SpatialFileType, } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.model'; import { FilesUtils, getErrorMessage } from '$lib/api/utils'; import { GeoviewerError } from '$lib/api/managers/error/geoviewer-error.model'; import type { SpatialFileInputState } from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte'; import { ApiFileInput } from '$lib/components/api-file-input'; import type { ApiFeatureConverter } from '$lib/api/tools/converter/api-feature.converter';
interface Props { parsingResult: ParsingResult[]; spatialInputFileState: SpatialFileInputState; featureConverter: ApiFeatureConverter; }
let { parsingResult = $bindable([]), spatialInputFileState, featureConverter }: Props = $props();
const allowedExtensions = SpatialFileInfo.KMZ.allowedExtensions.join(',');
$effect(() => { if (spatialInputFileState.fileFromUrl) { readFileContent(spatialInputFileState.fileFromUrl); } });
async function handleFileChange(event: Event) { const file = FilesUtils.readFileFromEvent(event); if (file) { await readFileContent(file); } }
async function readFileContent(file: File) { try { spatialInputFileState.loading = true; const items = await featureConverter.kml.fromKMZFile(file); parsingResult.push(...items); } catch (error) { spatialInputFileState.onParseError(getErrorMessage(error), SpatialFileType.KMZ); throw new GeoviewerError('Failed to convert kmz', { cause: error }); } finally { spatialInputFileState.loading = false; } }</script>
<ApiFileInput dataTestId="AddDataFile-InputFile" multiple={false} loading={spatialInputFileState.loading} accept={allowedExtensions} onchange={handleFileChange}/>packages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/ShapeFileInput.svelte
<script lang="ts"> import { type ParsingResult, SpatialFileInfo, SpatialFileType, } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.model'; import { FilesUtils, getErrorMessage } from '$lib/api/utils'; import { GeoviewerError } from '$lib/api/managers/error/geoviewer-error.model'; import { Projections } from '$lib/api/managers/projection'; import type { SpatialFileInputState } from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte'; import { ApiFileInput } from '$lib/components/api-file-input'; import type { ApiFeatureConverter } from '$lib/api/tools/converter/api-feature.converter';
interface Props { parsingResult: ParsingResult[]; currentShapeFileWkid: number; spatialInputFileState: SpatialFileInputState; featureConverter: ApiFeatureConverter; }
let { parsingResult = $bindable([]), currentShapeFileWkid = Projections.LAMBERT_72.wkid, spatialInputFileState, featureConverter, }: Props = $props();
const allowedExtensions = SpatialFileInfo.SHP.allowedExtensions.join(',');
$effect(() => { if (spatialInputFileState.fileFromUrl) { readFileContent(spatialInputFileState.fileFromUrl); } });
async function handleFileChange(event: Event) { const file = FilesUtils.readFileFromEvent(event); if (file) { await readFileContent(file); } }
async function readFileContent(file: File) { try { spatialInputFileState.loading = true; parsingResult = await featureConverter.shapeFile.fromShapeFile(file, currentShapeFileWkid); } catch (error) { spatialInputFileState.onParseError(getErrorMessage(error), SpatialFileType.SHP); throw new GeoviewerError('Failed to convert shapefile', { cause: error }); } finally { spatialInputFileState.loading = false; } }</script>
<ApiFileInput dataTestId="AddDataFile-InputFile" multiple={false} loading={spatialInputFileState.loading} accept={allowedExtensions} onchange={handleFileChange}/>packages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte.ts
import { getContext, setContext } from 'svelte';import { TopicManager } from '$lib/api/managers/topic';import type { SpatialFileType } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.model';
export class SpatialFileInputState { public loading = $state<boolean>(false); public fileFromUrl = $state<File | undefined>(); private topicManager: TopicManager;
constructor(topicManager: TopicManager) { this.topicManager = topicManager; }
public onParseError(errorMessage: string, fileType?: SpatialFileType): void { this.topicManager.publish({ type: 'AddData-parsing-file-error', fileType, errorMessage, }); }}
const SPATIAL_FILE_INPUT_CONTEXT_KEY = 'SPATIAL_FILE_INPUT_CONTEXT_KEY';
export function setSpatialInputFileState(spatialFileInputState: SpatialFileInputState) { setContext(SPATIAL_FILE_INPUT_CONTEXT_KEY, spatialFileInputState); return getSpatialFileInputState();}
export function getSpatialFileInputState(): SpatialFileInputState { const spatialFileInputState = getContext<SpatialFileInputState>(SPATIAL_FILE_INPUT_CONTEXT_KEY); if (!spatialFileInputState) { throw new Error('SpatialFileInputState not found in context.'); } return spatialFileInputState;}packages/common/src/lib/widgets/add-data/add-data-from-file/spatial-file-input/SpatialFileInput.svelte
<script lang="ts"> import { type ParsingResult, SpatialFileType, } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.model'; import GeoJONFileInput from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/GeoJSONFileInput.svelte'; import ShapeFileInput from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/ShapeFileInput.svelte'; import CSVFileInput from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/CSVFileInput.svelte'; import KMLFileInput from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/KMLFileInput.svelte'; import KMZFileInput from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/KMZFileInput.svelte'; import GPXFileInput from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/GPXFileInput.svelte'; import GMLFileInput from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/GMLFileInput.svelte'; import type { SpatialFileInputState } from '$lib/widgets/add-data/add-data-from-file/spatial-file-input/spatial-file-input.state.svelte'; import { Label } from '$lib/components/shadcn/ui/label'; import { getI18n } from '$lib/api/managers/i18n'; import { getMapManager } from '$lib/api/map'; import type { AddDataFromFileI18n } from '$lib/widgets/add-data/add-data-from-file/add-data-from-file.i18n';
interface Props { parsingResult: ParsingResult[]; currentFileType: SpatialFileType; currentShapeFileWkid: number; spatialInputFileState: SpatialFileInputState; i18nRegistry: AddDataFromFileI18n; }
let { parsingResult = $bindable([]), currentFileType, currentShapeFileWkid, spatialInputFileState, i18nRegistry, }: Props = $props();
const i18n = getI18n(i18nRegistry); const mapManager = getMapManager(); const featureConverter = mapManager.tools.featureConverter;</script>
<div class="gv-flex gv-flex-col gv-gap-1"> <Label class="gv-font-bold" for="add-data-file-input">{i18n('file-upload')}</Label> <div class="gv-flex gv-w-full"> {#if currentFileType === SpatialFileType.GeoJSON} <GeoJONFileInput {featureConverter} {spatialInputFileState} bind:parsingResult /> {:else if currentFileType === SpatialFileType.SHP} <ShapeFileInput {featureConverter} {spatialInputFileState} bind:parsingResult {currentShapeFileWkid} /> {:else if currentFileType === SpatialFileType.CSV} <CSVFileInput {featureConverter} {spatialInputFileState} bind:parsingResult /> {:else if currentFileType === SpatialFileType.KML} <KMLFileInput {featureConverter} {spatialInputFileState} bind:parsingResult /> {:else if currentFileType === SpatialFileType.KMZ} <KMZFileInput {featureConverter} {spatialInputFileState} bind:parsingResult /> {:else if currentFileType === SpatialFileType.GPX} <GPXFileInput {featureConverter} {spatialInputFileState} bind:parsingResult /> {:else if currentFileType === SpatialFileType.GML} <GMLFileInput {featureConverter} {spatialInputFileState} bind:parsingResult /> {:else} <div>Unsupported file type {currentFileType}</div> {/if} </div></div>