Skip to content

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.ts

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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>

Aller plus loin