Source AdvancedSearch
Source AdvancedSearch
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/advanced-search/advanced-search.declaration.tspackages/common/src/lib/widgets/advanced-search/advanced-search.config.tspackages/common/src/lib/widgets/advanced-search/advanced-address-search/advanced-address-search.config.tspackages/common/src/lib/widgets/advanced-search/advanced-address-search/advanced-address-search.declaration.tspackages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressCommuneCombobox.sveltepackages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressNumeroCombobox.sveltepackages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressStreetCombobox.sveltepackages/common/src/lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte.tspackages/common/src/lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search.config.tspackages/common/src/lib/widgets/advanced-search/advanced-cad-search/AdvancedCadSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-cad-search/BisCadSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-cad-search/CapakeyCadSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-cad-search/CommuneCadSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-cad-search/DivisionCadSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-cad-search/ExposantCadSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-cad-search/PuissanceCadSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-cad-search/RadicalCadSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-cad-search/SectionCadSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-coordinate-search/advanced-coordinate-search.config.tspackages/common/src/lib/widgets/advanced-search/advanced-coordinate-search/AdvancedCoordinateSearch.sveltepackages/common/src/lib/widgets/advanced-search/advanced-segmentation-search/advanced-segmentation-search.config.tspackages/common/src/lib/widgets/advanced-search/advanced-segmentation-search/AdvancedSegmentationSearch.sveltepackages/common/src/lib/widgets/advanced-search/AdvancedSearch.svelte
packages/common/src/lib/widgets/advanced-search/advanced-search.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';import { type AdvancedSearchFullConfig, advancedSearchFullConfigSchema } from './advanced-search.config';import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = { factory: () => import('./AdvancedSearch.svelte').then((AdvancedSearch) => widgetFactorySvelte(AdvancedSearch)), schema: () => advancedSearchFullConfigSchema,} satisfies WidgetDeclaration;
export type AdvancedSearchProps = WidgetProps<AdvancedSearchFullConfig>;packages/common/src/lib/widgets/advanced-search/advanced-search.config.ts
import { defineWidgetConfig } from '$lib/api/managers/configuration';import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';import { i18nSchemaFrom } from '$lib/api/managers/i18n';import { z } from 'zod';import { advancedCadSearchConfigSchema } from './advanced-cad-search/advanced-cad-search.config';import { coordinateSearchConfigLight } from './advanced-coordinate-search/advanced-coordinate-search.config';import { advancedSegmentationSearchConfigSchema } from './advanced-segmentation-search/advanced-segmentation-search.config';import { iconSchema } from '$lib/api/icons';import { advancedSearchAddressConfigSchema, searchAddressTranslations,} from '$lib/widgets/advanced-search/advanced-address-search/advanced-address-search.config';
const searchConfigSchema = z.object({ type: z.enum(['CAD', 'SEG', 'COOR', 'ADR']), icon: iconSchema,});
export type SearchOptionConfig = z.infer<typeof searchConfigSchema>;
export const advancedSearchConfigSchema = z.object({ availableSearches: z.array(searchConfigSchema).default([ { type: 'ADR', icon: { lucide: 'House' } }, { type: 'CAD', icon: { geoviewer: 'parcel' } }, { type: 'SEG', icon: { geoviewer: 'road' } }, { type: 'COOR', icon: { lucide: 'LocateFixed' } }, ]), cadSearchConfig: advancedCadSearchConfigSchema.prefault({}), coordinateSearchConfig: coordinateSearchConfigLight.prefault({}), segmentationSearchConfig: advancedSegmentationSearchConfigSchema.prefault({}), addressSearchConfig: advancedSearchAddressConfigSchema.prefault({}),});
export const advancedSearchFullConfigSchema = defineWidgetConfig({ title: { fr: 'Recherche avancée', nl: 'NL - Recherche avancée', }, icon: { geoviewer: 'advanced-search', }, inToolbar: inToolbarSchemaFrom(false), i18n: i18nSchemaFrom({ CAD: { fr: 'Parcelle cadastrale', nl: 'NL - Parcelle cadastrale', }, COOR: { fr: 'Coordonnées', nl: 'NL - Coordonnées', }, SEG: { fr: 'Route régionale', nl: 'NL - Route régionale', }, ADR: { fr: 'Adresse', nl: 'NL - Adresse', }, ...searchAddressTranslations, }), onActivate: { deactivate: { classes: ['Export', 'Report', 'Identify', 'AddData', 'Draw', 'MeasureDistance', 'MeasureSurface'], }, }, config: advancedSearchConfigSchema.prefault({}),});
export type AdvancedSearchFullConfig = z.infer<typeof advancedSearchFullConfigSchema>;packages/common/src/lib/widgets/advanced-search/advanced-address-search/advanced-address-search.config.ts
import { defineWidgetConfig } from '$lib/api/managers/configuration';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 { z } from 'zod';
export const searchAddressTranslations = { 'search-municipality-label': { fr: 'Commune / Code postal', nl: 'NL - Commune / Code postal', }, 'search-street-label': { fr: 'Rue', nl: 'NL - Rue', }, 'search-number-label': { fr: 'Numéro', nl: 'NL - Numéro', }, 'i-go': { fr: "J'y vais", nl: 'Ik ga', },};
export const advancedsearchAddressConfigSchema = z .object({ symbolConfig: apiFeatureSymbolsSchema.prefault({}), }) .prefault({});export const advancedSearchAddressConfigSchema = z.object({ config: advancedsearchAddressConfigSchema.prefault({}), i18n: i18nSchemaFrom(searchAddressTranslations),});export const advancedSearchAddressFullConfigSchema = defineWidgetConfig({ title: { fr: 'Rechercher une adresse', nl: 'NL - Rechercher une adresse', }, icon: { lucide: 'MapPinned', }, inToolbar: inToolbarSchemaFrom({ type: 'button', }), i18n: i18nSchemaFrom(searchAddressTranslations), config: advancedsearchAddressConfigSchema,});
export type AdvancedSearchAddressFullConfig = z.infer<typeof advancedSearchAddressFullConfigSchema>;packages/common/src/lib/widgets/advanced-search/advanced-address-search/advanced-address-search.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';import { type AdvancedSearchAddressFullConfig, advancedSearchAddressFullConfigSchema,} from './advanced-address-search.config';import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = { factory: () => import('./AdvancedAddressSearch.svelte').then((AdvancedAddressSearch) => widgetFactorySvelte(AdvancedAddressSearch), ), schema: () => advancedSearchAddressFullConfigSchema,} satisfies WidgetDeclaration;
export type AdvancedAddressSearchProps = WidgetProps<AdvancedSearchAddressFullConfig>;packages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressCommuneCombobox.svelte
<script lang="ts"> import { type GeolisterResponsePosition, IcarGeolocalisationService } from '$lib/api/clients/geocodews'; import { debounceState, derivedWithDestroy, queryState } from '$lib/api/utils'; import { ApiCombobox, type ComboboxSelectedItem } from '$lib/components/api-combobox';
interface CommuneValue { cp?: string; ins?: string; }
interface Props { selectedCommune?: string; }
let { selectedCommune = $bindable() }: Props = $props();
let filterText = $state(''); const debouncedFilter = debounceState(() => filterText, 500); const filter = $derived(debouncedFilter.debounced?.length > 2 ? debouncedFilter.debounced : null);
const restQueryClient = derivedWithDestroy( () => searchCommune(filter), (query) => query?.cancel(), );
const searchQuery = queryState({ queryFn: ({ query }) => query!.then(mapToSelectItem), inputFn: () => ({ query: restQueryClient.value }), disabled: () => !restQueryClient.value, });
$effect(() => { if (!selectedCommune) { filterText = ''; searchQuery.data = []; } });
// We need to be able to search commune based on the INS or the CP. Thus, we need an internal // object to store both those params and filter on them. // As the second endpoint of the AdvancedAddressSearch needs the INS, we set the "external" value to // the INS. let internalSelectedCommune = $state<CommuneValue | undefined>(); $effect(() => { if (!internalSelectedCommune) { selectedCommune = undefined; } if (internalSelectedCommune && internalSelectedCommune.ins) { selectedCommune = internalSelectedCommune.ins; } });
function searchCommune(filter: string | null) { if (!filter) { return null; } // Si l'utilisateur a encodé un code postal, le paramètre à envoyer au service est différent const cp = parseInt(filter); if (isNaN(cp)) { return IcarGeolocalisationService.geolistAsJson({ city: filter }); } else { return IcarGeolocalisationService.geolistAsJson({ zone: filter }); } }
function filterCommunes(filterText: string, options: ComboboxSelectedItem<CommuneValue | undefined>[]) { const searchText = filterText.toLowerCase(); return options.filter((x) => { return ( (x.label && x.label.toLowerCase().indexOf(searchText) > -1) || (x.value && x.value.ins && x.value.ins.indexOf(searchText) > -1) || (x.value && x.value.cp && x.value.cp.indexOf(searchText) > -1) ); }); }
function mapToSelectItem(res: GeolisterResponsePosition) { if (res.candidates) { return res.candidates .filter((x) => x.city != undefined) .reduce( (acc, candidate) => { const zone = candidate.zone; const city = candidate.city;
// As the second endpoint of the AdvancedAddressSearch needs an INS, we filter out all // values that do not provide an ins if (!city || !city.name || !city.ident) return acc;
let name = city.name; const value: CommuneValue = { cp: undefined, ins: undefined, };
// If the candidate as a zone (object where the CP is), we add it to the value if (zone) { value.cp = zone.ident; } value.ins = city.ident;
if (!acc.set.has(value.ins)) { acc.set.add(value.ins); acc.list.push({ label: name!, value, }); } return acc; }, { list: [] as ComboboxSelectedItem<CommuneValue>[], set: new Set<string>() }, ).list; }
return []; }</script>
<ApiCombobox bind:value={internalSelectedCommune} bind:filterText filterFunction={filterCommunes} loading={searchQuery.loading} lazy options={searchQuery.data}/>packages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressNumeroCombobox.svelte
<script lang="ts"> import { type GeolisterResponsePosition, IcarGeolocalisationService } from '$lib/api/clients/geocodews'; import { debounceState, derivedWithDestroy, queryState } from '$lib/api/utils'; import { ApiCombobox, type ComboboxSelectedItem } from '$lib/components/api-combobox';
interface Props { selectedStreet?: string; selectedNumber?: string; }
let { selectedStreet, selectedNumber = $bindable() }: Props = $props();
let filterText = $state(''); const debouncedFilter = debounceState(() => filterText, 500); const filter = $derived.by(() => { const value = debouncedFilter.debounced; return value && value.length > 0 ? value : '*'; }); const disabled = $derived.by(() => !selectedStreet || !searchQuery || !searchQuery.data);
$effect(() => { if (!selectedNumber) { filterText = ''; searchQuery.data = []; } });
$effect(() => { if (selectedStreet || !selectedStreet) { selectedNumber = undefined; } });
const restQueryClient = derivedWithDestroy( () => filter && selectedStreet ? IcarGeolocalisationService.geolistAsJson({ street: selectedStreet, house: filter }) : null, (query) => query?.cancel(), );
const searchQuery = queryState({ queryFn: ({ query }) => query!.then(mapToSelectItem), inputFn: () => ({ query: restQueryClient.value }), disabled: () => !restQueryClient.value, });
function mapToSelectItem(res: GeolisterResponsePosition) { if (res.candidates) { return res.candidates .filter((x) => x.house !== undefined) .reduce( (acc, candidate) => { if (candidate.house) { const { ident, name } = candidate.house; if (ident && !acc.set.has(ident)) { acc.set.add(ident); acc.list.push({ label: name!, value: ident?.toString(), }); } } return acc; }, { list: [] as ComboboxSelectedItem<string>[], set: new Set<number>() }, ).list; } return []; }</script>
<ApiCombobox bind:value={selectedNumber} bind:filterText lazy loading={searchQuery.loading} options={searchQuery.data} {disabled}/>packages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressSearch.svelte
<script lang="ts"> import { IcarGeolocalisationService } from '$lib/api/clients/geocodews'; import type { ApiGeoJSON } from '$lib/api/domain/api-geojson.model'; import { getI18n } from '$lib/api/managers/i18n'; import { getMapManager } from '$lib/api/map'; import { Button } from '$lib/components/shadcn/ui/button'; import { Label } from '$lib/components/shadcn/ui/label'; import { onDestroy } from 'svelte'; import Loader from '$lib/components/common/Loader.svelte'; import CommuneCombobox from './AdvancedAddressCommuneCombobox.svelte'; import NumeroCombobox from './AdvancedAddressNumeroCombobox.svelte'; import StreetCombobox from './AdvancedAddressStreetCombobox.svelte'; import type { ApiFeature } from '$lib/api/feature'; import type { AdvancedAddressSearchProps } from './advanced-address-search.declaration'; import StringUtils from '$lib/api/utils/string.utils';
let { fullConfig }: AdvancedAddressSearchProps = $props();
const i18n = getI18n(fullConfig.i18n); const mapManager = getMapManager();
let loading = $state(false); let selectedCommune = $state<string | undefined>(undefined); let selectedStreet = $state<string | undefined>(undefined); let selectedNumber = $state<string | undefined>(undefined); let currentFeature: ApiFeature | undefined; let oldFeature: ApiFeature | undefined;
onDestroy(() => { if (oldFeature) { mapManager.tools.highlight.unhighlightFeature(oldFeature); } if (currentFeature) { mapManager.tools.highlight.unhighlightFeature(currentFeature); } });
function search() { loading = true; const number = StringUtils.isNullOrEmpty(selectedNumber) ? undefined : selectedNumber; const street = number != undefined || StringUtils.isNullOrEmpty(selectedStreet) ? undefined : selectedStreet; const commune = number != undefined || street != undefined || StringUtils.isNullOrEmpty(selectedCommune) ? undefined : selectedCommune; IcarGeolocalisationService.geolistAsJson({ city: commune, street: street, house: number, geom: true, }).then( (res) => { oldFeature = currentFeature; if (res.candidates && res.candidates.length > 0) { const bestResult = res.candidates.sort((a, b) => (a.score && b.score ? b.score - a.score : 0))[0]; const bestResultGeometry = bestResult.house?.geometry ?? bestResult.street?.geometry ?? bestResult.city?.geometry; handleHighlight(bestResultGeometry as ApiGeoJSON); } loading = false; }, (err) => { console.error(err); loading = false; }, ); }
function handleHighlight(geoJSONGeometry: ApiGeoJSON | undefined) { oldFeature = currentFeature; if (geoJSONGeometry) { currentFeature = mapManager.tools.featureConverter.geoJSON.fromGeoJSON(geoJSONGeometry)[0]; mapManager.tools.highlight.highlightFeature(currentFeature); } else { currentFeature = undefined; } if (oldFeature) { mapManager.tools.highlight.unhighlightFeature(oldFeature); } if (currentFeature) { mapManager.tools.zoom.zoomToFeature(currentFeature); } }
function reset() { selectedCommune = undefined; selectedStreet = undefined; selectedNumber = undefined; if (currentFeature) { mapManager.tools.highlight.unhighlightFeature(currentFeature); } }</script>
<div class="gv-space-y-3"> <div> <Label class="gv-font-bold">{i18n('search-municipality-label')}</Label> <CommuneCombobox bind:selectedCommune /> </div> <div> <Label class="gv-font-bold">{i18n('search-street-label')}</Label> <StreetCombobox {selectedCommune} bind:selectedStreet /> </div> <div> <Label class="gv-font-bold">{i18n('search-number-label')}</Label> <NumeroCombobox {selectedStreet} bind:selectedNumber /> </div> {#if loading} <div class="gv-p-4 gv-flex gv-justify-center gv-items-center"> <Loader /> </div> {/if} <div class="gv-pt-5"> <Button disabled={!selectedCommune} onclick={() => search()} size="sm">{i18n('i-go')}</Button> <Button variant="secondary" onclick={() => reset()} size="sm">{i18n('common.reset')}</Button> </div></div>packages/common/src/lib/widgets/advanced-search/advanced-address-search/AdvancedAddressStreetCombobox.svelte
<script lang="ts"> import { type GeolisterResponsePosition, IcarGeolocalisationService } from '$lib/api/clients/geocodews'; import { debounceState, derivedWithDestroy, queryState } from '$lib/api/utils'; import { ApiCombobox, type ComboboxSelectedItem } from '$lib/components/api-combobox';
interface Props { selectedStreet?: string; selectedCommune?: string; }
let { selectedStreet = $bindable(), selectedCommune }: Props = $props();
let filterText = $state(''); const debouncedFilter = debounceState(() => filterText, 500); const filter = $derived(debouncedFilter.debounced?.length > 2 ? debouncedFilter.debounced : null); const disabled = $derived.by(() => !selectedCommune || !searchQuery || !searchQuery.data);
const restQueryClient = derivedWithDestroy( () => (filter ? IcarGeolocalisationService.geolistAsJson({ city: selectedCommune, street: filter }) : null), (query) => query?.cancel(), );
$effect(() => { if (!selectedStreet) { filterText = ''; searchQuery.data = []; } });
// If the commune changes, selected street has to be reset $effect(() => { if (selectedCommune) { selectedStreet = undefined; } });
const searchQuery = queryState({ queryFn: ({ query }) => query!.then(mapToSelectItem), inputFn: () => ({ query: restQueryClient.value }), disabled: () => !restQueryClient.value, });
function mapToSelectItem(res: GeolisterResponsePosition) { if (res.candidates) { return res.candidates .filter((x) => x.street != undefined) .reduce( (acc, candidate) => { if (candidate.street) { const { ident, name } = candidate.street; if (ident && !acc.set.has(ident)) { acc.set.add(ident); acc.list.push({ label: name!, value: ident?.toString(), }); } } return acc; }, { list: [] as ComboboxSelectedItem<string>[], set: new Set<number>() }, ).list; }
return []; }</script>
<ApiCombobox bind:value={selectedStreet} bind:filterText lazy loading={searchQuery.loading} options={searchQuery.data} {disabled}/>packages/common/src/lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte.ts
import { ApiParcelWsClient, CadmapControllerService } from '$lib/api/clients';import type { I18nRegistry } from '$lib/api/managers/i18n';import { extractCapakeyComponents, formatCapakey } from '$lib/api/utils';import { getContext, setContext } from 'svelte';import { showToast } from '$lib/components/toast/toast.utils';import type { ApiFeature } from '$lib/api/feature';import type { MapManager } from '$lib/api/map';import type { ApiGeoJSONFeatureCollection } from '$lib/api/domain';
export class AdvancedCadSearchContext { private _selectedCommune = $state<string | undefined>(); private _selectedDivision = $state<string | undefined>(); private _selectedSection = $state<string | undefined>(); private _selectedRadical = $state<string | undefined>(); private _selectedExposant = $state<string | undefined>(); private _selectedPuissance = $state<string | undefined>(); private _selectedBis = $state<string | undefined>(); private _capakey = $state<string>('');
private onMapFeature: ApiFeature | undefined;
private readonly COMPLETE_CAPAKEY_LENGTH = 17;
constructor( public i18nStore: I18nRegistry, private mapManager: MapManager, private capakeyKeyNotCompleteMessage: string, ) { $effect.root(() => { $effect(() => { const anyChange = `${this._selectedCommune}${this._selectedDivision}${this._selectedSection}${this._selectedRadical}${this._selectedExposant}${this._selectedPuissance}${this._selectedBis}`; this.buildCapakey(); }); }); }
public removeOnMapFeature() { if (this.onMapFeature) { this.mapManager.tools.highlight.unhighlightFeature(this.onMapFeature); this.onMapFeature = undefined; } }
public search() { if (!this.capakeyIsComplete && !this.selectedCommune) { showToast({ level: 'info', message: this.capakeyKeyNotCompleteMessage }); return; } this.removeOnMapFeature(); this.getSearchResult().then((geoJson) => { if (!geoJson) { return; } const resultFeatures = this.mapManager.tools.featureConverter.geoJSON.fromGeoJSON(geoJson) ?? []; this.onMapFeature = this.mapManager.tools.geometryEngine.union(resultFeatures); this.mapManager.tools.highlight.highlightFeature(this.onMapFeature); this.mapManager.tools.zoom.zoomToFeature(this.onMapFeature); }); }
public async getSearchResult(): Promise<ApiGeoJSONFeatureCollection | undefined> { if (!this._selectedCommune && this._capakey && this._capakey.length !== 0) { return ApiParcelWsClient.getShapeFromCapakey(this._capakey); }
const { _selectedCommune, _selectedDivision, _selectedSection, _selectedRadical, _selectedExposant, _selectedPuissance, _selectedBis, } = this;
if (_selectedDivision) { if (_selectedSection) { if (_selectedRadical) { if (_selectedExposant) { if (_selectedPuissance) { if (_selectedBis) { return ApiParcelWsClient.getParcelleShapesByBis( _selectedDivision, _selectedSection, _selectedRadical, _selectedExposant, _selectedPuissance, _selectedBis, ); } return ApiParcelWsClient.getParcelleShapesByPuissance( _selectedDivision, _selectedSection, _selectedRadical, _selectedExposant, _selectedPuissance, ); } return ApiParcelWsClient.getParcelleShapesByExposant( _selectedDivision, _selectedSection, _selectedRadical, _selectedExposant, ); } return ApiParcelWsClient.getParcelleShapesFromRadical( _selectedDivision, _selectedSection, _selectedRadical, ); } return ApiParcelWsClient.getShapeSection(_selectedDivision, _selectedSection); } return ApiParcelWsClient.getShapeDivision(_selectedDivision); }
if (_selectedCommune) { return ApiParcelWsClient.getShapeCommune(_selectedCommune); } }
public get capakeyIsComplete(): boolean { return this._capakey?.length === this.COMPLETE_CAPAKEY_LENGTH; }
public reset(withCapakey = true): void { this.selectedCommune = undefined; if (withCapakey) { this._capakey = ''; } this.removeOnMapFeature(); }
private buildCapakey(): void { if (!this._selectedDivision) { return; } this._capakey = formatCapakey({ division: this._selectedDivision, section: this._selectedSection, radical: this._selectedRadical, exposant: this._selectedExposant, puissance: this._selectedPuissance, bis: this._selectedBis, }); }
set capakey(value: string) { this._capakey = value; if (this._capakey.length === this.COMPLETE_CAPAKEY_LENGTH) { const capakeyComponents = extractCapakeyComponents(this._capakey); if (!capakeyComponents) { return; } const { division, radical, exposant, puissance, bis, section } = capakeyComponents; CadmapControllerService.getShapeParcellesByBis({ codeDiv: division, sect: section, radical: radical, exposant: exposant, puissance: puissance, bis: bis, }).then((res) => { const selectedIns = res[0]?.commune; if (selectedIns) { this.selectedCommune = selectedIns; this.selectedDivision = division; this.selectedSection = section; this.selectedRadical = radical; this.selectedExposant = exposant; this.selectedPuissance = puissance; this.selectedBis = bis; } }); } }
set selectedCommune(value: string | undefined) { if (this._selectedCommune != value) { this.selectedDivision = undefined; } this._selectedCommune = value; }
set selectedDivision(value: string | undefined) { if (this._selectedDivision != value) { this.selectedSection = undefined; } this._selectedDivision = value; }
set selectedSection(value: string | undefined) { if (this._selectedSection != value) { this.selectedRadical = undefined; } this._selectedSection = value; }
set selectedRadical(value: string | undefined) { if (this._selectedRadical != value) { this.selectedExposant = undefined; } this._selectedRadical = value; }
set selectedExposant(value: string | undefined) { if (this._selectedExposant != value) { this.selectedPuissance = undefined; } this._selectedExposant = value; }
set selectedPuissance(value: string | undefined) { if (this._selectedPuissance != value) { this.selectedBis = undefined; } this._selectedPuissance = value; }
set selectedBis(value: string | undefined) { this._selectedBis = value; }
get selectedCommune(): string | undefined { return this._selectedCommune; }
get selectedDivision(): string | undefined { return this._selectedDivision; }
get selectedSection(): string | undefined { return this._selectedSection; }
get selectedRadical(): string | undefined { return this._selectedRadical; }
get selectedExposant(): string | undefined { return this._selectedExposant; }
get selectedPuissance(): string | undefined { return this._selectedPuissance; }
get selectedBis(): string | undefined { return this._selectedBis; }
get capakey(): string { return this._capakey; }}
const ADVANCED_CAD_CONTEXT_KEY = 'ADVANCED_CAD_CONTEXT_KEY';
export function setAdvancedCadContext(legendData: AdvancedCadSearchContext) { setContext(ADVANCED_CAD_CONTEXT_KEY, legendData); return getAdvancedCadContext();}
export function getAdvancedCadContext(): AdvancedCadSearchContext { const advancedCadContext = getContext<AdvancedCadSearchContext>(ADVANCED_CAD_CONTEXT_KEY); if (!advancedCadContext) { throw new Error('AdvancedCadContext not found in context.'); } return advancedCadContext;}packages/common/src/lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search.config.ts
import { defineWidgetConfig } from '$lib/api/managers/configuration';import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';import { i18nSchemaFrom } from '$lib/api/managers/i18n';import { z } from 'zod';
export const advancedCadSearchTranslations = { commune: { fr: 'Commune', nl: 'NL - Commune', }, division: { fr: 'Division', nl: 'NL - Division', }, section: { fr: 'Section', nl: 'NL - Section', }, radical: { fr: 'Radical', nl: 'NL - Radical', }, exposant: { fr: 'Exposant', nl: 'NL - Exposant', }, puissance: { fr: 'Puissance', nl: 'NL - Puissance', }, bis: { fr: 'Bis', nl: 'NL - Bis', }, capakey: { fr: 'Capakey', nl: 'NL - Capakey', }, 'capakey-not-complete': { fr: 'Veuillez indiquer une capakey complète', nl: 'NL - Veuillez indiquer une capakey complète', },};export const advancedCadSearchConfigSchema = z.object({ i18n: i18nSchemaFrom(advancedCadSearchTranslations),});
export const advancedSearchFullConfigSchema = defineWidgetConfig({ title: { fr: 'Recherche avancée de parcelles cadastrales', nl: 'NL - Recherche avancée de parcelles cadastrales', }, inToolbar: inToolbarSchemaFrom(false), i18n: i18nSchemaFrom({}), config: advancedCadSearchConfigSchema.optional().prefault({}),});
export type AdvancedCadSearchFullConfig = z.infer<typeof advancedSearchFullConfigSchema>;export type AdvancedCadSearchConfig = z.infer<typeof advancedCadSearchConfigSchema>;packages/common/src/lib/widgets/advanced-search/advanced-cad-search/AdvancedCadSearch.svelte
<script lang="ts"> import { Separator } from '$lib/components/shadcn/ui/separator'; import type { AdvancedCadSearchConfig } from '$lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search.config'; import { AdvancedCadSearchContext, setAdvancedCadContext, } from '$lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte'; import CommuneCadSearch from '$lib/widgets/advanced-search/advanced-cad-search/CommuneCadSearch.svelte'; import DivisionCadSearch from '$lib/widgets/advanced-search/advanced-cad-search/DivisionCadSearch.svelte'; import SectionCadSearch from '$lib/widgets/advanced-search/advanced-cad-search/SectionCadSearch.svelte'; import RadicalCadSearch from '$lib/widgets/advanced-search/advanced-cad-search/RadicalCadSearch.svelte'; import ExposantCadSearch from '$lib/widgets/advanced-search/advanced-cad-search/ExposantCadSearch.svelte'; import PuissanceCadSearch from '$lib/widgets/advanced-search/advanced-cad-search/PuissanceCadSearch.svelte'; import BisCadSearch from '$lib/widgets/advanced-search/advanced-cad-search/BisCadSearch.svelte'; import { Button } from '$lib/components/shadcn/ui/button'; import { getMapManager } from '$lib/api/map'; import RotateCcw from 'lucide-svelte/icons/rotate-ccw'; import CapakeyCadSearch from '$lib/widgets/advanced-search/advanced-cad-search/CapakeyCadSearch.svelte'; import { getI18n } from '$lib/api/managers/i18n'; import { onDestroy } from 'svelte';
type Props = { config: AdvancedCadSearchConfig; }; let { config }: Props = $props();
const i18n = getI18n(config.i18n); const mapManager = getMapManager(); const advancedCadContext = setAdvancedCadContext( new AdvancedCadSearchContext(config.i18n, mapManager, i18n('capakey-not-complete')), );
onDestroy(() => { advancedCadContext.removeOnMapFeature(); });</script>
<div class="gv-space-y-3"> <CapakeyCadSearch />
<Separator />
<CommuneCadSearch /> <DivisionCadSearch /> <SectionCadSearch /> <RadicalCadSearch /> <ExposantCadSearch /> <PuissanceCadSearch /> <BisCadSearch />
<div class="gv-flex gv-justify-end gv-gap-2"> <Button data-test-id="AdvancedSearch-Parcel-Reset" variant="secondary" onclick={() => advancedCadContext.reset()} size="sm" > <RotateCcw class="gv-size-4" /> {i18n('common.reset')} </Button>
<Button data-test-id="AdvancedSearch-Parcel-Search" onclick={() => advancedCadContext.search()} size="sm"> {i18n('common.search')} </Button> </div></div>packages/common/src/lib/widgets/advanced-search/advanced-cad-search/BisCadSearch.svelte
<script lang="ts"> import { Label } from '$lib/components/shadcn/ui/label'; import { getAdvancedCadContext } from '$lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte'; import { CadmapControllerService } from '$lib/api/clients'; import { getI18n } from '$lib/api/managers/i18n'; import { ApiCombobox } from '$lib/components/api-combobox'; import { queryState } from '$lib/api/utils';
const advancedCadContext = getAdvancedCadContext(); const i18n = getI18n(advancedCadContext.i18nStore);
const requiredParamsAreFullfilled = $derived( !!advancedCadContext.selectedDivision && !!advancedCadContext.selectedSection && !!advancedCadContext.selectedRadical && !!advancedCadContext.selectedExposant && !!advancedCadContext.selectedPuissance, );
const bisQueryState = queryState({ queryFn: (input) => CadmapControllerService.getListeBis(input), inputFn: () => ({ codeDiv: advancedCadContext.selectedDivision!, sect: advancedCadContext.selectedSection!, radical: advancedCadContext.selectedRadical!, exposant: advancedCadContext.selectedExposant!, puissance: advancedCadContext.selectedPuissance!, }), disabled: () => !requiredParamsAreFullfilled, });
const options = $derived.by(() => { return ( bisQueryState?.data?.map((x) => { return { label: x.bis, value: x.bis, }; }) ?? [] ); }); const disabled = $derived(!requiredParamsAreFullfilled || bisQueryState?.loading || options.length === 0);</script>
<div> <Label class="gv-font-bold">{i18n('bis')}</Label> <ApiCombobox avoidCollisions={false} dataTestId="AdvancedSearch-Parcel-Bis" loading={bisQueryState?.loading} bind:value={advancedCadContext.selectedBis} {options} {disabled} /></div>packages/common/src/lib/widgets/advanced-search/advanced-cad-search/CapakeyCadSearch.svelte
<script lang="ts"> import { Label } from '$lib/components/shadcn/ui/label'; import { Input } from '$lib/components/shadcn/ui/input'; import { getAdvancedCadContext } from '$lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte'; import { getI18n } from '$lib/api/managers/i18n'; import { onEnterPressed } from '$lib/api/utils';
const advancedCadContext = $state(getAdvancedCadContext()); const i18n = getI18n(advancedCadContext.i18nStore);</script>
<div> <Label class="gv-font-bold">{i18n('capakey')}</Label> <Input data-test-id="AdvancedSearch-Parcel-Capakey" onkeypress={(evt) => onEnterPressed(evt, () => advancedCadContext.search())} bind:value={advancedCadContext.capakey} type="text" /></div>packages/common/src/lib/widgets/advanced-search/advanced-cad-search/CommuneCadSearch.svelte
<script lang="ts"> import { Label } from '$lib/components/shadcn/ui/label'; import { getAdvancedCadContext } from '$lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte'; import { CadmapControllerService } from '$lib/api/clients'; import { getI18n } from '$lib/api/managers/i18n'; import { ApiCombobox, type ComboboxSelectedItem } from '$lib/components/api-combobox'; import { queryState } from '$lib/api/utils';
const advancedCadContext = getAdvancedCadContext(); const i18n = getI18n(advancedCadContext.i18nStore);
const communeQueryState = queryState({ queryKey: 'CommuneCadSearch-getListeCommunes', queryFn: () => CadmapControllerService.getListeCommunes({}), });
const options = $derived.by(() => { return ( communeQueryState.data?.map((x) => { return { label: x.nom, value: x.ins, }; }) ?? [] ); });
function filterCommunes(filterText: string, options: ComboboxSelectedItem<string | undefined>[]) { const searchText = filterText.toLowerCase(); return options.filter((x) => { return ( (x.label && x.label.toLowerCase().indexOf(searchText) > -1) || (x.value && x.value.indexOf(searchText) > -1) ); }); }
const disabled = $derived(communeQueryState.loading || options.length === 0);</script>
<div> <Label class="gv-font-bold">{i18n('commune')}</Label> <ApiCombobox avoidCollisions={false} dataTestId="AdvancedSearch-Parcel-Commune" loading={communeQueryState.loading} bind:value={advancedCadContext.selectedCommune} filterFunction={filterCommunes} {options} {disabled} /></div>packages/common/src/lib/widgets/advanced-search/advanced-cad-search/DivisionCadSearch.svelte
<script lang="ts"> import { Label } from '$lib/components/shadcn/ui/label'; import { getAdvancedCadContext } from '$lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte'; import { CadmapControllerService } from '$lib/api/clients'; import { getI18n } from '$lib/api/managers/i18n'; import { ApiCombobox } from '$lib/components/api-combobox'; import { queryState } from '$lib/api/utils';
const advancedCadContext = getAdvancedCadContext(); const i18n = getI18n(advancedCadContext.i18nStore);
const requiredParamsAreFullfilled = $derived(!!advancedCadContext.selectedCommune);
const divisionQueryState = queryState({ queryFn: (input) => CadmapControllerService.getListeDivisions(input), inputFn: () => ({ ins: advancedCadContext.selectedCommune! }), disabled: () => !requiredParamsAreFullfilled, });
const options = $derived.by(() => { return ( divisionQueryState?.data?.map((x) => ({ label: x.divNom, value: x.codeDiv, })) ?? [] ); });
const disabled = $derived(!requiredParamsAreFullfilled || divisionQueryState?.loading || options.length === 0);</script>
<div> <Label class="gv-font-bold">{i18n('division')}</Label> <ApiCombobox avoidCollisions={false} dataTestId="AdvancedSearch-Parcel-Division" loading={divisionQueryState?.loading} bind:value={advancedCadContext.selectedDivision} {options} {disabled} /></div>packages/common/src/lib/widgets/advanced-search/advanced-cad-search/ExposantCadSearch.svelte
<script lang="ts"> import { Label } from '$lib/components/shadcn/ui/label'; import { getAdvancedCadContext } from '$lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte'; import { CadmapControllerService } from '$lib/api/clients'; import { getI18n } from '$lib/api/managers/i18n'; import { ApiCombobox } from '$lib/components/api-combobox'; import { queryState } from '$lib/api/utils';
const advancedCadContext = getAdvancedCadContext(); const i18n = getI18n(advancedCadContext.i18nStore);
const requiredParamsAreFullfilled = $derived( !!advancedCadContext.selectedDivision && !!advancedCadContext.selectedSection && !!advancedCadContext.selectedRadical, );
const exposantQueryState = queryState({ queryFn: (input) => CadmapControllerService.getListeExposants(input), inputFn: () => ({ codeDiv: advancedCadContext.selectedDivision!, sect: advancedCadContext.selectedSection!, radical: advancedCadContext.selectedRadical!, }), disabled: () => !requiredParamsAreFullfilled, });
const options = $derived.by(() => { return ( exposantQueryState?.data?.map((x) => { return { label: x.exposant, value: x.exposant, }; }) ?? [] ); });
const disabled = $derived(!requiredParamsAreFullfilled || exposantQueryState?.loading || options.length === 0);</script>
<div> <Label class="gv-font-bold">{i18n('exposant')}</Label> <ApiCombobox avoidCollisions={false} dataTestId="AdvancedSearch-Parcel-Exposant" loading={exposantQueryState?.loading} bind:value={advancedCadContext.selectedExposant} {options} {disabled} /></div>packages/common/src/lib/widgets/advanced-search/advanced-cad-search/PuissanceCadSearch.svelte
<script lang="ts"> import { Label } from '$lib/components/shadcn/ui/label'; import { getAdvancedCadContext } from '$lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte'; import { CadmapControllerService } from '$lib/api/clients'; import { getI18n } from '$lib/api/managers/i18n'; import { ApiCombobox } from '$lib/components/api-combobox'; import { queryState } from '$lib/api/utils';
const advancedCadContext = getAdvancedCadContext(); const i18n = getI18n(advancedCadContext.i18nStore);
const requiredParamsAreFullfilled = $derived( !!advancedCadContext.selectedDivision && !!advancedCadContext.selectedSection && !!advancedCadContext.selectedRadical && !!advancedCadContext.selectedExposant, );
const puissanceQueryState = queryState({ queryFn: (input) => CadmapControllerService.getListePuissances(input), inputFn: () => ({ codeDiv: advancedCadContext.selectedDivision!, sect: advancedCadContext.selectedSection!, radical: advancedCadContext.selectedRadical!, exposant: advancedCadContext.selectedExposant!, }), disabled: () => !requiredParamsAreFullfilled, });
const options = $derived.by(() => { return ( puissanceQueryState?.data?.map((x) => { return { label: x.puissance, value: x.puissance, }; }) ?? [] ); });
const disabled = $derived(!requiredParamsAreFullfilled || puissanceQueryState?.loading || options.length === 0);</script>
<div> <Label class="gv-font-bold">{i18n('puissance')}</Label> <ApiCombobox avoidCollisions={false} dataTestId="AdvancedSearch-Parcel-Puissance" loading={puissanceQueryState?.loading} bind:value={advancedCadContext.selectedPuissance} {options} {disabled} /></div>packages/common/src/lib/widgets/advanced-search/advanced-cad-search/RadicalCadSearch.svelte
<script lang="ts"> import { Label } from '$lib/components/shadcn/ui/label'; import { getAdvancedCadContext } from '$lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte'; import { CadmapControllerService } from '$lib/api/clients'; import { getI18n } from '$lib/api/managers/i18n'; import { ApiCombobox } from '$lib/components/api-combobox'; import { queryState } from '$lib/api/utils';
const advancedCadContext = getAdvancedCadContext(); const i18n = getI18n(advancedCadContext.i18nStore);
const requiredParamsAreFullfilled = $derived( !!advancedCadContext.selectedDivision && !!advancedCadContext.selectedSection, );
const radicalQueryState = queryState({ queryFn: (input) => CadmapControllerService.getListeRadicaux(input), inputFn: () => ({ codeDiv: advancedCadContext.selectedDivision!, sect: advancedCadContext.selectedSection! }), disabled: () => !requiredParamsAreFullfilled, });
const options = $derived.by(() => { return ( radicalQueryState?.data?.map((x) => { return { label: x.radical, value: x.radical, }; }) ?? [] ); });
const disabled = $derived(!requiredParamsAreFullfilled || radicalQueryState?.loading || options.length === 0);</script>
<div> <Label class="gv-font-bold">{i18n('radical')}</Label> <ApiCombobox avoidCollisions={false} dataTestId="AdvancedSearch-Parcel-Radical" loading={radicalQueryState?.loading} bind:value={advancedCadContext.selectedRadical} {options} {disabled} /></div>packages/common/src/lib/widgets/advanced-search/advanced-cad-search/SectionCadSearch.svelte
<script lang="ts"> import { Label } from '$lib/components/shadcn/ui/label'; import { getAdvancedCadContext } from '$lib/widgets/advanced-search/advanced-cad-search/advanced-cad-search-context.svelte'; import { CadmapControllerService } from '$lib/api/clients'; import { getI18n } from '$lib/api/managers/i18n'; import { ApiCombobox } from '$lib/components/api-combobox'; import { queryState } from '$lib/api/utils';
const advancedCadContext = getAdvancedCadContext(); const i18n = getI18n(advancedCadContext.i18nStore);
const requiredParamsAreFullfilled = $derived(!!advancedCadContext.selectedDivision);
const sectionQueryState = queryState({ queryFn: (input) => CadmapControllerService.getListeSections(input), inputFn: () => ({ codeDiv: advancedCadContext.selectedDivision! }), disabled: () => !requiredParamsAreFullfilled, });
const options = $derived.by(() => { return ( sectionQueryState?.data?.map((x) => { return { label: x.sect, value: x.sect, }; }) ?? [] ); });
const disabled = $derived(!requiredParamsAreFullfilled || sectionQueryState?.loading || options.length === 0);</script>
<div> <Label class="gv-font-bold">{i18n('section')}</Label> <ApiCombobox avoidCollisions={false} dataTestId="AdvancedSearch-Parcel-Section" loading={sectionQueryState?.loading} bind:value={advancedCadContext.selectedSection} {options} {disabled} /></div>packages/common/src/lib/widgets/advanced-search/advanced-coordinate-search/advanced-coordinate-search.config.ts
import { defineWidgetConfig, inToolbarSchemaFrom } from '$lib/api/managers/configuration';import { i18nDataSchema, i18nSchemaFrom } from '$lib/api/managers/i18n';import { z } from 'zod';import { extentSchema } from '$lib/api/domain/api-extent.schema';
const coordinateSearchProjectionOption = z.object({ wkid: z.number(), label: i18nDataSchema, dms: z.boolean().optional(), xLabel: z.string().optional(), yLabel: z.string().optional(), xPlaceholder: z.string().optional(), yPlaceholder: z.string().optional(),});export type CoordinateSearchProjectionOption = z.infer<typeof coordinateSearchProjectionOption>;
export const advancedCoordinateSearchFullConfigSchema = defineWidgetConfig({ title: { fr: 'Recherche avancée de coordonnées', nl: 'NL - Recherche avancée de coordonnées', }, inToolbar: inToolbarSchemaFrom(false), i18n: i18nSchemaFrom({ 'coordinate-label': { fr: 'Système de coordonnées', nl: 'NL - Système de coordonnées', }, 'parsing-error-message': { fr: 'Le format de la coordonnée est invalide. Le format attendu est un nombre entier ou décimal.', nl: 'NL - Le format de la coordonnée est invalide. Le format attendu est un nombre entier ou décimal.', }, 'parsing-dms-error-message': { fr: 'Le format de la coordonnée est invalide.', nl: 'NL - Le format de la coordonnée est invalide.', }, 'layer-label': { fr: 'Coordonnées', nl: 'NL - Coordonnées', }, 'keep-coordinate': { fr: 'Conserver le résultat sur la carte', nl: 'NL - Conserver le résultat sur la carte', }, 'outside-of-warning-extent': { fr: 'Les coordonnées recherchées sont en dehors de la Wallonie', nl: 'NL - Les coordonnées recherchées sont en dehors de la Wallonie', }, }), config: z .object({ options: coordinateSearchProjectionOption.array().default([ { label: 'Lambert Belge 2008', wkid: 3812, xPlaceholder: '763875,1', yPlaceholder: '643212,2', }, { label: 'Lambert Belge 1972', wkid: 31370, xPlaceholder: '263873,6', yPlaceholder: '149848,8', }, { label: 'WGS84 (DD)', wkid: 4326, xLabel: 'Longitude', yLabel: 'Latitude', xPlaceholder: '5,977', yPlaceholder: '50,588', }, { label: 'WGS84 (DMS)', wkid: 4326, dms: true, xLabel: 'Longitude Est', yLabel: 'Latitude Nord', xPlaceholder: '5° 58\' 36,839" E', yPlaceholder: '50° 35\' 17,998" N', }, ]), warningMessageExtent: extentSchema.default({ xmin: 5700, ymin: 7600, xmax: 315000, ymax: 195000, wkid: 31370, }), showWarningMessage: z.boolean().default(true), }) .prefault({}),});
export const coordinateSearchConfigLight = advancedCoordinateSearchFullConfigSchema.pick({ i18n: true, config: true,});export type CoordinateSearchConfigLight = z.infer<typeof coordinateSearchConfigLight>;
export type AdvancedCoordinateSearchFullConfig = z.infer<typeof advancedCoordinateSearchFullConfigSchema>;packages/common/src/lib/widgets/advanced-search/advanced-coordinate-search/AdvancedCoordinateSearch.svelte
<script lang="ts"> import { initGraphicMapServiceConfiguration, MapServiceTypes } from '$lib/api/managers/configuration'; 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 { ApiGraphicsMapService } from '$lib/api/mapservices'; import { onEnterPressed, parseDms } from '$lib/api/utils'; import { ApiSelect, type ApiSelectItem } from '$lib/components/api-select'; import { Button } from '$lib/components/shadcn/ui/button'; import { Checkbox } from '$lib/components/shadcn/ui/checkbox'; import { Input } from '$lib/components/shadcn/ui/input'; import { Label } from '$lib/components/shadcn/ui/label'; import RotateCcw from 'lucide-svelte/icons/rotate-ccw'; import type { CoordinateSearchConfigLight, CoordinateSearchProjectionOption, } from './advanced-coordinate-search.config'; import { onDestroy } from 'svelte'; import type { ApiPoint } from '$lib/api/geometry'; import { projectionManager } from '$lib/api/managers/projection';
interface Props { fullConfig: CoordinateSearchConfigLight; }
let { fullConfig }: Props = $props(); const { config } = fullConfig; const i18n = getI18n(fullConfig.i18n); const mapManager = getMapManager(); const transform = mapManager.tools.transform; const coordinateLayerId = 'COORDINATE_LAYER_ID'; const layer = getCoordinateLayer();
let keepInLayer = $state(layer.toc.visible);
$effect(() => { if (keepInLayer) { layer.toc.visible = true; } else { layer.removeAll(); layer.toc.visible = false; } });
let projectionOption = $state<CoordinateSearchProjectionOption>(fullConfig.config.options[0]); const options: ApiSelectItem<CoordinateSearchProjectionOption>[] = $derived( fullConfig.config.options.map((o) => ({ value: o, label: i18n.translate(o.label), })), );
let xValue = $state(''); let yValue = $state('');
let parsingError = $state(false); let outsideOfWarningExtent = $state(false);
function getCoordinateLayer(): ApiGraphicsMapService { let layer = mapManager.layerList.findById(coordinateLayerId);
if (layer && layer.type !== MapServiceTypes.GRAPHICS) { layer = undefined; }
if (!layer) { const layerConfig = initGraphicMapServiceConfiguration({ id: coordinateLayerId, label: i18n('layer-label'), removable: false, toc: { visible: false, }, }); layer = mapManager.addGraphicMapService(layerConfig); }
return layer as ApiGraphicsMapService; }
function reset() { xValue = ''; yValue = ''; parsingError = false; outsideOfWarningExtent = false; }
function submit() { const { x, y } = projectionOption.dms ? parseDms(xValue, yValue) : { x: parseFloat(xValue.replace(',', '.')), y: parseFloat(yValue.replace(',', '.')), }; if (Number.isNaN(x)) { parsingError = true; return; } if (Number.isNaN(y)) { parsingError = true; return; }
const projection = projectionManager.getByWkid(projectionOption.wkid); if (!projection) { throw new GeoviewerError(`No projection found for wkid: ${projectionOption.wkid}`); } const point = mapManager.tools.featureFactory.createPoint({ x, y, wkid: projection.wkid });
outsideOfWarningExtent = config.showWarningMessage && pointIsOutsideWarningBbox(point);
if (!keepInLayer) { layer.removeAll(); }
layer.addFeature(point); mapManager.tools.zoom.zoomToFeature(point); }
function pointIsOutsideWarningBbox(point: ApiPoint): boolean { const projectedWarningExtent = transform.transformExtent(config.warningMessageExtent, point.wkid); return ( point.x < projectedWarningExtent.xmin || point.x > projectedWarningExtent.xmax || point.y < projectedWarningExtent.ymin || point.y > projectedWarningExtent.ymax ); }
onDestroy(() => { if (!keepInLayer) { mapManager.removeMapService(coordinateLayerId); } });</script>
<div class="gv-space-y-3"> <div> <Label class="gv-font-bold">{i18n('coordinate-label')}</Label> <ApiSelect dataTestId="AdvancedSearch-Coord-Projections" getKey={(p: CoordinateSearchProjectionOption) => `${p.wkid}-${p.label}`} {options} bind:value={projectionOption} valueChanged={() => (parsingError = false)} /> </div>
<div class="gv-grid gv-grid-cols-2 gv-gap-3"> <div> <Label class="gv-font-bold">{projectionOption.xLabel ?? 'X (m)'}</Label> <Input data-test-id="AdvancedSearch-Coord-X" onkeypress={(evt) => onEnterPressed(evt, () => submit())} placeholder={projectionOption.xPlaceholder} bind:value={xValue} /> </div> <div> <Label class="gv-font-bold">{projectionOption.yLabel ?? 'Y (m)'}</Label> <Input data-test-id="AdvancedSearch-Coord-Y" onkeypress={(evt) => onEnterPressed(evt, () => submit())} placeholder={projectionOption.yPlaceholder} bind:value={yValue} /> </div> </div>
{#if parsingError} <span class="gv-text-destructive gv-text-sm"> {projectionOption.dms ? i18n('parsing-dms-error-message') : i18n('parsing-error-message')} </span> {/if}
{#if outsideOfWarningExtent} <span class="gv-text-destructive gv-text-sm"> {i18n('outside-of-warning-extent')} </span> {/if}
<div class="gv-flex gv-items-center gv-gap-2"> <Checkbox data-test-id="AdvancedSearch-Coord-KeepCoordinates" bind:checked={keepInLayer} id="keep-coordinates" /> <Label for="keep-coordinates">{i18n('keep-coordinate')}</Label> </div>
<div class="gv-flex gv-justify-end gv-gap-2"> <Button data-test-id="AdvancedSearch-Coord-Reset" variant="secondary" onclick={reset} size="sm"> <RotateCcw class="gv-size-4" /> {i18n('common.reset')} </Button>
<Button data-test-id="AdvancedSearch-Coord-Search" onclick={submit} size="sm"> {i18n('common.search')} </Button> </div></div>packages/common/src/lib/widgets/advanced-search/advanced-segmentation-search/advanced-segmentation-search.config.ts
import { defineWidgetConfig } from '$lib/api/managers/configuration/index.js';import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';import { i18nSchemaFrom } from '$lib/api/managers/i18n';import { z } from 'zod';
export const advancedSegmentationSearchTranslations = { road: { fr: 'Route', nl: 'NL - Route', }, 'unable-to-fetch-segments': { fr: 'Une erreur est survenue au chargement des segments', nl: 'NL - Une erreur est survenue au chargement des segments', }, 'bk-start': { fr: 'Borne de début (km)', nl: 'NL - Borne de début (km)', }, 'bk-end': { fr: 'Borne de fin (km)', nl: 'NL - Borne de fin (km)', },};export const advancedSegmentationSearchConfigSchema = z.object({ i18n: i18nSchemaFrom(advancedSegmentationSearchTranslations),});
export const advancedSegmentationSearchFullConfigSchema = defineWidgetConfig({ title: { fr: 'Recherche avancée de route régionale', nl: 'NL - Recherche avancée de route régionale', }, inToolbar: inToolbarSchemaFrom(false), i18n: i18nSchemaFrom(advancedSegmentationSearchTranslations), config: advancedSegmentationSearchConfigSchema.optional().prefault({}),});
export type AdvancedSegmentationSearchFullConfig = z.infer<typeof advancedSegmentationSearchFullConfigSchema>;packages/common/src/lib/widgets/advanced-search/advanced-segmentation-search/AdvancedSegmentationSearch.svelte
<script lang="ts"> import { getI18n } from '$lib/api/managers/i18n'; import { getMapManager } from '$lib/api/map'; import { ApiCombobox, type ComboboxSelectedItem } from '$lib/components/api-combobox'; import { Button } from '$lib/components/shadcn/ui/button'; import { Input } from '$lib/components/shadcn/ui/input'; import { Label } from '$lib/components/shadcn/ui/label'; import type { AdvancedSegmentationSearchFullConfig } from '$lib/widgets/advanced-search/advanced-segmentation-search/advanced-segmentation-search.config'; import RotateCcw from 'lucide-svelte/icons/rotate-ccw'; import { onDestroy } from 'svelte'; import type { ApiFeature } from '$lib/api/feature'; import type { Segment } from '$lib/api/clients/lrsws/models/Segment'; import { onEnterPressed, queryState } from '$lib/api/utils'; import { showToast } from '$lib/components/toast/toast.utils';
interface Props { fullConfig: AdvancedSegmentationSearchFullConfig; }
let { fullConfig }: Props = $props();
const mapManager = getMapManager(); const segmentation = mapManager.services.segmentation; const i18n = getI18n(fullConfig.i18n);
let bkStart = $state<number>(0); let bkEnd = $state<number>(0); let currentFeature: ApiFeature | undefined;
let selectedRoad = $state<Segment | null>(null); let roadsQueryState = queryState({ queryKey: `AdvancedSegmentation-getSegmentList-1`, queryFn: () => segmentation.getSegmentList('1'), });
const inError = $derived(roadsQueryState?.error); $effect(() => { if (inError) { showToast({ level: 'error', message: i18n('unable-to-fetch-segments') }); } });
const roadsOptions = $derived<ComboboxSelectedItem<Segment>[]>( roadsQueryState.data?.filter((x) => !!x).map((x) => ({ label: x.simplifiedCode ?? '', value: x })) ?? [], ); const disableSearch = $derived.by(() => !selectedRoad || (bkEnd != undefined && bkStart > bkEnd));
$effect(() => { if (selectedRoad) { bkStart = selectedRoad.cumulee_start ? selectedRoad.cumulee_start / 1000 : 0; bkEnd = selectedRoad.cumulee_end ? selectedRoad.cumulee_end / 1000 : 0; } });
$effect(() => { if (bkStart < 0) { bkStart = 0; } if (bkEnd < 0) { bkEnd = 0; } });
function reset() { selectedRoad = null; bkStart = 0; bkEnd = 0; if (currentFeature) { mapManager.tools.highlight.unhighlightFeature(currentFeature); } }
function submit() { mapManager.services.segmentation .getSegmentWithShape({ code: selectedRoad!.code!, cumuleeEnd: bkEnd != undefined ? bkEnd : bkStart, cumuleeStart: bkStart != undefined ? bkStart : 0, systemType: '1', }) .then((segment) => { if (currentFeature) { mapManager.tools.highlight.unhighlightFeature(currentFeature); } if (segment) { currentFeature = segment; mapManager.tools.zoom.zoomToFeature(currentFeature); mapManager.tools.highlight.highlightFeature(currentFeature); } }); }
function filterSegments(filterText: string, options: ComboboxSelectedItem<Segment | undefined>[]) { const filter = filterText.toLowerCase(); return options.filter((x) => { return x.label && x.label.toLowerCase().includes(filter); }); }
onDestroy(() => { reset(); });</script>
<div class="gv-space-y-3"> <div> <Label class="gv-font-bold">{i18n('road')}</Label> <ApiCombobox avoidCollisions={false} dataTestId="AdvancedSearch-Segmentation-Roads" filterFunction={filterSegments} bind:value={selectedRoad} options={roadsOptions} lazy={true} disabled={!!inError} loading={roadsQueryState.loading} /> </div>
<div> <Label class="gv-font-bold">{i18n('bk-start')}</Label> <Input class="gv-input-number-without-arrows" min="0" on:keypress={(evt) => onEnterPressed(evt, () => submit())} data-test-id="AdvancedSearch-Segmentation-BkStart" type="number" bind:value={bkStart} /> </div>
<div> <Label class="gv-font-bold">{i18n('bk-end')}</Label> <Input class="gv-input-number-without-arrows" min="0" on:keypress={(evt) => onEnterPressed(evt, () => submit())} data-test-id="AdvancedSearch-Segmentation-BkEnd" type="number" bind:value={bkEnd} /> </div>
<div class="gv-flex gv-justify-end gv-gap-2"> <Button data-test-id="AdvancedSearch-Segmentation-Reset" variant="secondary" onclick={reset} size="sm"> <RotateCcw class="gv-size-4" /> {i18n('common.reset')} </Button>
<Button data-test-id="AdvancedSearch-Segmentation-Search" disabled={disableSearch} onclick={submit} size="sm"> {i18n('common.search')} </Button> </div></div>packages/common/src/lib/widgets/advanced-search/AdvancedSearch.svelte
<script lang="ts"> import { getI18n } from '$lib/api/managers/i18n'; import { CollapsibleContent, CollapsibleTrigger, Root } from '$lib/components/shadcn/ui/collapsible'; import { Icon } from '$lib/components/icon'; import { cn } from '$lib/components/shadcn/utils'; import AdvancedCadSearch from './advanced-cad-search/AdvancedCadSearch.svelte'; import AdvancedCoordinateSearch from './advanced-coordinate-search/AdvancedCoordinateSearch.svelte'; import AdvancedSegmentationSearch from './advanced-segmentation-search/AdvancedSegmentationSearch.svelte'; import type { SearchOptionConfig } from './advanced-search.config'; import type { AdvancedSearchProps } from './advanced-search.declaration'; import AdvancedSearchAddress from '$lib/widgets/advanced-search/advanced-address-search/AdvancedAddressSearch.svelte';
let { fullConfig }: AdvancedSearchProps = $props();
const { config } = fullConfig; const i18n = getI18n(fullConfig.i18n);
let openedSearch = $state<SearchOptionConfig['type']>();</script>
{#each config.availableSearches as currentSearch} <Root open={openedSearch === currentSearch.type} class="gv-border-b gv-px-5 gv-py-2.5 gv-space-y-2.5"> <CollapsibleTrigger data-test-id="AdvancedSearch-Tab-{currentSearch.type}" onclick={() => (openedSearch = openedSearch === currentSearch.type ? undefined : currentSearch.type)} class="gv-flex gv-w-full gv-items-center gv-space-x-2 gv-font-bold" > <Icon icon={currentSearch.icon} class="gv-size-6 gv-text-primary" /> <span class="gv-flex-1 gv-text-left">{i18n(currentSearch.type)}</span> <Icon icon={{ lucide: 'ChevronDown' }} class={cn(openedSearch === currentSearch.type && 'gv-rotate-180', 'gv-duration-150')} /> </CollapsibleTrigger>
<CollapsibleContent class="gv-pl-8"> {#if currentSearch.type === 'ADR'} <AdvancedSearchAddress fullConfig={config.addressSearchConfig} /> {:else if currentSearch.type === 'CAD'} <AdvancedCadSearch config={config.cadSearchConfig} /> {:else if currentSearch.type === 'COOR'} <AdvancedCoordinateSearch fullConfig={config.coordinateSearchConfig} /> {:else if currentSearch.type === 'SEG'} <AdvancedSegmentationSearch fullConfig={config.segmentationSearchConfig} /> {:else} Not implemented yet ... {/if} </CollapsibleContent> </Root>{/each}