diff --git a/git-hooks/commit-msg b/git-hooks/commit-msg
index 032ee3f60..ace314c05 100755
--- a/git-hooks/commit-msg
+++ b/git-hooks/commit-msg
@@ -1 +1,2 @@
+#!/bin/sh
npx commitlint --edit $1
\ No newline at end of file
diff --git a/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.html b/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.html
index ff47fa0ad..682d19be1 100644
--- a/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.html
+++ b/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.html
@@ -5,3 +5,14 @@
(catalogSelectChange)="onCatalogSelectChange($event)"
>
+
+
+
diff --git a/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.scss b/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.scss
new file mode 100644
index 000000000..5ed4ac1f1
--- /dev/null
+++ b/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.scss
@@ -0,0 +1,5 @@
+.get-catalog-list-button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
diff --git a/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.ts b/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.ts
index 53a4ef322..84b306aeb 100644
--- a/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.ts
+++ b/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.component.ts
@@ -1,19 +1,45 @@
+import { NgIf, formatDate } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
Input,
+ OnDestroy,
OnInit
} from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatTooltipModule } from '@angular/material/tooltip';
import { EntityStore } from '@igo2/common/entity';
import { ToolComponent } from '@igo2/common/tool';
+import { ContextService, DetailedContext } from '@igo2/context';
+import { LanguageService } from '@igo2/core/language';
import { StorageScope, StorageService } from '@igo2/core/storage';
-import { Catalog, CatalogLibaryComponent, CatalogService } from '@igo2/geo';
+import {
+ Catalog,
+ CatalogItem,
+ CatalogItemGroup,
+ CatalogItemLayer,
+ CatalogItemType,
+ CatalogLibaryComponent,
+ CatalogService
+} from '@igo2/geo';
+import {
+ addExcelSheetToWorkBook,
+ createExcelWorkBook,
+ writeExcelFile
+} from '@igo2/utils';
-import { take } from 'rxjs/operators';
+import { TranslateModule } from '@ngx-translate/core';
+import { Observable, Subscription, combineLatest, forkJoin } from 'rxjs';
+import { concatAll, map, switchMap, take, toArray } from 'rxjs/operators';
import { ToolState } from '../../tool/tool.state';
import { CatalogState } from '../catalog.state';
+import {
+ InfoFromSourceOptions,
+ ListExport
+} from './catalog-library-tool.interface';
+import { getInfoFromSourceOptions } from './catalog-library-tool.utils';
/**
* Tool to browse the list of available catalogs.
@@ -26,11 +52,19 @@ import { CatalogState } from '../catalog.state';
@Component({
selector: 'igo-catalog-library-tool',
templateUrl: './catalog-library-tool.component.html',
+ styleUrls: ['./catalog-library-tool.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
- imports: [CatalogLibaryComponent]
+ imports: [
+ CatalogLibaryComponent,
+ MatButtonModule,
+ MatTooltipModule,
+ NgIf,
+ TranslateModule
+ ]
})
-export class CatalogLibraryToolComponent implements OnInit {
+export class CatalogLibraryToolComponent implements OnInit, OnDestroy {
+ private generatelist$$: Subscription;
/**
* Store that contains the catalogs
* @internal
@@ -44,6 +78,11 @@ export class CatalogLibraryToolComponent implements OnInit {
*/
@Input() addCatalogAllowed = false;
+ /**
+ * Determine if the export button is shown
+ */
+ @Input() exportButton = false;
+
/**
* List of predefined catalogs
*/
@@ -62,10 +101,12 @@ export class CatalogLibraryToolComponent implements OnInit {
}
constructor(
+ private contextService: ContextService,
private catalogService: CatalogService,
private catalogState: CatalogState,
private toolState: ToolState,
- private storageService: StorageService
+ private storageService: StorageService,
+ private languageService: LanguageService
) {}
/**
@@ -81,6 +122,10 @@ export class CatalogLibraryToolComponent implements OnInit {
}
}
+ ngOnDestroy() {
+ this.generatelist$$?.unsubscribe();
+ }
+
/**
* When the selected catalog changes, toggle the the CatalogBrowser tool.
* @internal
@@ -110,4 +155,241 @@ export class CatalogLibraryToolComponent implements OnInit {
);
});
}
+
+ /**
+ * Get the item abstract for getCatalogList
+ */
+ private getMetadataAbstract(item: CatalogItemLayer): string {
+ return item.options.metadata.abstract?.replaceAll('\n', '') ?? '';
+ }
+ /**
+ * Get the item url metadata for getCatalogList
+ */
+ private getMetadataUrl(item: CatalogItemLayer): string {
+ return item.options.metadata.url;
+ }
+ /**
+ * Prepare the observale to produce the layer list extraction
+ * @returns An array of catalog and items plus detailed contexts info.
+ */
+ private getCatalogsAndItemsAndDetailedContexts(): Observable<
+ [
+ {
+ catalog: Catalog;
+ items: CatalogItem[];
+ }[],
+ DetailedContext[]
+ ]
+ > {
+ return combineLatest([
+ this.store.entities$.pipe(
+ switchMap((catalogs) => {
+ return forkJoin(
+ catalogs.map((catalog) =>
+ this.catalogService.loadCatalogItems(catalog).pipe(
+ map((items) => {
+ return { catalog, items };
+ })
+ )
+ )
+ );
+ })
+ ),
+ this.contextService
+ .getLocalContexts()
+ .pipe(
+ switchMap((contextsList) =>
+ forkJoin(
+ contextsList.ours.map((context) =>
+ this.contextService.getLocalContext(context.uri)
+ )
+ )
+ )
+ )
+ ]);
+ }
+
+ private layersInfoFromContexts(): Observable {
+ return this.contextService.getLocalContexts().pipe(
+ switchMap((contextsList) =>
+ forkJoin(
+ contextsList.ours.map((context) =>
+ this.contextService.getLocalContext(context.uri)
+ )
+ )
+ ),
+ concatAll(),
+ map((detailedContext) => {
+ return detailedContext.layers.map((layer) =>
+ getInfoFromSourceOptions(
+ layer.sourceOptions,
+ detailedContext.title ?? detailedContext.uri
+ )
+ );
+ }),
+ concatAll(),
+ toArray()
+ );
+ }
+
+ private listExportFromCatalogs(): Observable {
+ let rank = 1;
+ const finalListExportOutputs: ListExport[] = [];
+ return this.store.entities$.pipe(
+ switchMap((catalogs) =>
+ combineLatest(
+ catalogs.map((catalog) =>
+ this.catalogService.loadCatalogItems(catalog).pipe(
+ map((items) => {
+ return { catalog, items };
+ })
+ )
+ )
+ )
+ ),
+ map((catalogsWithItems) => {
+ catalogsWithItems.forEach((catalogAndItems) => {
+ const catalog = catalogAndItems.catalog;
+ const loadedCatalogItems = catalogAndItems.items;
+
+ const catalogListExports = loadedCatalogItems.reduce(
+ (catalogListExports, item) => {
+ if (item.type === CatalogItemType.Group) {
+ const group = item as CatalogItemGroup;
+ group.items.forEach((layer: CatalogItemLayer) => {
+ catalogListExports.push(
+ this.formatLayer(layer, rank, group.title, catalog.title)
+ );
+ rank++;
+ });
+ } else {
+ const layer = item as CatalogItemLayer;
+ catalogListExports.push(
+ this.formatLayer(layer, rank, '', catalog.title)
+ );
+ rank++;
+ }
+ return catalogListExports;
+ },
+ [] as ListExport[]
+ );
+ finalListExportOutputs.push(...catalogListExports);
+ });
+ return finalListExportOutputs;
+ })
+ );
+ }
+
+ async getCatalogList(): Promise {
+ this.generatelist$$ = combineLatest([
+ this.layersInfoFromContexts(),
+ this.listExportFromCatalogs()
+ ]).subscribe(([layersInfoFromContexts, listExportFromCatalogs]) => {
+ const listExport = this.matchLayersWithLayersFromContext(
+ listExportFromCatalogs,
+ layersInfoFromContexts
+ );
+
+ this.exportExcel(listExport);
+ });
+ }
+
+ private formatLayer(
+ layer: CatalogItemLayer,
+ rank: number,
+ groupTitle: string,
+ catalogTitle: string
+ ): ListExport {
+ const infos = getInfoFromSourceOptions(
+ layer.options.sourceOptions,
+ layer.id
+ );
+ const t = this.languageService.translate;
+ return {
+ id: infos.id,
+ rank: rank.toString(),
+ layerTitle: layer.title,
+ layerGroup: groupTitle,
+ catalog: catalogTitle,
+ provider: layer.externalProvider
+ ? t.instant('igo.integration.catalog.listExport.external')
+ : t.instant('igo.integration.catalog.listExport.internal'),
+ url: infos.url,
+ layerName: infos.layerName,
+ context: '',
+ metadataAbstract: this.getMetadataAbstract(layer),
+ metadataUrl: this.getMetadataUrl(layer)
+ };
+ }
+
+ /**
+ * Match a list of layer info with an other list derived from contexts
+ * @param catalogOutputs The row list to be written into a file
+ * @param layerInfosFromDetailedContexts Layers info derived from the context
+ * @returns An altered list, with layer/context association
+ */
+
+ private matchLayersWithLayersFromContext(
+ listExport: ListExport[],
+ layerInfosFromDetailedContexts: InfoFromSourceOptions[]
+ ): ListExport[] {
+ listExport.map((catalogOutput) => {
+ const matchingLayersFromContext = layerInfosFromDetailedContexts
+ .filter(
+ (l) =>
+ l.id === catalogOutput.id ||
+ (l.layerName === catalogOutput.layerName &&
+ l.url === catalogOutput.url)
+ )
+ .map((f) => f.context);
+ catalogOutput.context = matchingLayersFromContext.join(',');
+ });
+ return listExport;
+ }
+
+ /**
+ * Write a Excel file
+ * @param catalogOutputs The row list to be written into a excel file
+ */
+ async exportExcel(catalogOutputs: ListExport[]) {
+ const translateCatalogKey = (key: TemplateStringsArray) =>
+ this.languageService.translate.instant(
+ `igo.integration.catalog.listExport.${key}`
+ );
+
+ catalogOutputs.unshift({
+ id: 'catalogIdHeader',
+ rank: translateCatalogKey`rank`,
+ layerTitle: translateCatalogKey`layerTitle`,
+ layerGroup: translateCatalogKey`layerGroup`,
+ catalog: translateCatalogKey`catalog`,
+ provider: translateCatalogKey`externalProvider`,
+ url: translateCatalogKey`url`,
+ layerName: translateCatalogKey`layerName`,
+ context: translateCatalogKey`context`,
+ metadataAbstract: translateCatalogKey`metadataAbstract`,
+ metadataUrl: translateCatalogKey`metadataUrl`
+ });
+
+ const catalogOutput = catalogOutputs.map((catalogOutput) => {
+ delete catalogOutput.id;
+ return catalogOutput;
+ });
+
+ const workBook = await createExcelWorkBook();
+
+ await addExcelSheetToWorkBook('Informations', catalogOutput, workBook, {
+ json2SheetOpts: {
+ skipHeader: true
+ }
+ });
+
+ const documentName = this.languageService.translate.instant(
+ 'igo.integration.catalog.listExport.documentName',
+ {
+ value: formatDate(Date.now(), 'YYYY-MM-dd-H_mm', 'en-US')
+ }
+ );
+ writeExcelFile(workBook, documentName, { compression: true });
+ }
}
diff --git a/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.interface.ts b/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.interface.ts
new file mode 100644
index 000000000..8eb386c59
--- /dev/null
+++ b/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.interface.ts
@@ -0,0 +1,23 @@
+import { AnyDataSourceOptions } from '@igo2/geo';
+
+export interface ListExport {
+ id: string;
+ rank: string;
+ layerTitle: string;
+ layerGroup: string;
+ catalog: string;
+ provider: string;
+ url: string;
+ layerName: string;
+ context: string;
+ metadataAbstract: string;
+ metadataUrl: string;
+}
+
+export interface InfoFromSourceOptions {
+ id: string;
+ layerName: string;
+ url: string;
+ sourceOptions: AnyDataSourceOptions;
+ context: string;
+}
diff --git a/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.utils.ts b/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.utils.ts
new file mode 100644
index 000000000..ae67d1f15
--- /dev/null
+++ b/packages/integration/src/lib/catalog/catalog-library-tool/catalog-library-tool.utils.ts
@@ -0,0 +1,120 @@
+import {
+ AnyDataSourceOptions,
+ ArcGISRestDataSourceOptions,
+ CartoDataSourceOptions,
+ ClusterDataSourceOptions,
+ FeatureDataSourceOptions,
+ MVTDataSourceOptions,
+ OSMDataSourceOptions,
+ WFSDataSourceOptions,
+ WMSDataSourceOptions,
+ WMTSDataSourceOptions,
+ XYZDataSourceOptions,
+ generateIdFromSourceOptions
+} from '@igo2/geo';
+
+import { InfoFromSourceOptions } from './catalog-library-tool.interface';
+
+export function getInfoFromSourceOptions(
+ sourceOptions: AnyDataSourceOptions,
+ context: string
+): InfoFromSourceOptions {
+ const value: InfoFromSourceOptions = {
+ id: undefined,
+ layerName: undefined,
+ url: undefined,
+ sourceOptions: undefined,
+ context
+ };
+
+ switch (sourceOptions.type) {
+ case 'imagearcgisrest':
+ case 'arcgisrest':
+ case 'tilearcgisrest': {
+ const argisSo = sourceOptions as ArcGISRestDataSourceOptions;
+ value.layerName = argisSo.layer;
+ value.url = argisSo.url;
+ value.sourceOptions = argisSo;
+ break;
+ }
+ case 'wmts': {
+ const wmtsSo = sourceOptions as WMTSDataSourceOptions;
+ value.layerName = wmtsSo.layer;
+ value.url = wmtsSo.url;
+ value.sourceOptions = wmtsSo;
+ break;
+ }
+ case 'xyz': {
+ const xyzSo = sourceOptions as XYZDataSourceOptions;
+ value.layerName = '';
+ value.url = xyzSo.url;
+ value.sourceOptions = xyzSo;
+ break;
+ }
+ case 'wms': {
+ const wmsSo = sourceOptions as WMSDataSourceOptions;
+ wmsSo.params.LAYERS = wmsSo.params.LAYERS ?? (wmsSo.params as any).layers;
+ value.layerName = wmsSo.params.LAYERS;
+
+ value.url = wmsSo.url;
+ value.sourceOptions = wmsSo;
+ break;
+ }
+ case 'osm': {
+ const osmSo = sourceOptions as OSMDataSourceOptions;
+ value.layerName = '';
+ value.url = osmSo.url
+ ? osmSo.url
+ : 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
+ value.sourceOptions = osmSo;
+ break;
+ }
+ case 'wfs': {
+ const wfsSo = sourceOptions as WFSDataSourceOptions;
+ value.layerName = wfsSo.params.featureTypes;
+ value.url = wfsSo.url;
+ value.sourceOptions = wfsSo;
+ break;
+ }
+ case 'vector': {
+ const featureSo = sourceOptions as FeatureDataSourceOptions;
+ value.layerName = '';
+ value.url = featureSo.url;
+ value.sourceOptions = featureSo;
+ break;
+ }
+ case 'cluster': {
+ const clusterSo = sourceOptions as ClusterDataSourceOptions;
+ value.layerName = '';
+ value.url = clusterSo.url;
+ value.sourceOptions = clusterSo;
+ break;
+ }
+ case 'mvt': {
+ const mvtSo = sourceOptions as MVTDataSourceOptions;
+ value.layerName = '';
+ value.url = mvtSo.url;
+ value.sourceOptions = mvtSo;
+ break;
+ }
+ case 'carto': {
+ const cartoSo = sourceOptions as CartoDataSourceOptions;
+ value.layerName = cartoSo.config.layers
+ .map((layer) => layer.options.sql)
+ .join(' ');
+ value.url = `https://${cartoSo.account}.carto.com/api/v1/map`;
+ value.sourceOptions = cartoSo;
+ break;
+ }
+ default:
+ break;
+ }
+ if (value.sourceOptions) {
+ value.id = generateIdFromSourceOptions(value.sourceOptions);
+ value.url = value.url?.startsWith('/')
+ ? window.location.origin + value.url
+ : value.url;
+ }
+
+ return value;
+}
diff --git a/packages/integration/src/locale/en.integration.json b/packages/integration/src/locale/en.integration.json
index 27db49428..64b636389 100644
--- a/packages/integration/src/locale/en.integration.json
+++ b/packages/integration/src/locale/en.integration.json
@@ -22,6 +22,24 @@
"advancedMap": "Advanced map tools",
"closestFeature": "Closest feature tool"
},
+ "catalog": {
+ "library.getCatalogList": "Get the catalogs's content list",
+ "listExport": {
+ "rank": "Rank",
+ "layerTitle": "Layer Title",
+ "layerGroup": "Layer Group",
+ "catalog": "Catalog",
+ "externalProvider": "Provider",
+ "url": "URL",
+ "layerName": "Layer name",
+ "context": "Context/Thematic",
+ "metadataAbstract": "Data description",
+ "metadataUrl": "Metadata hyperlink",
+ "documentName": "LayerList_{{value}}",
+ "internal": "Internal",
+ "external": "External"
+ }
+ },
"dataIssueReporterTool": {
"submit": {
"title": "Submit a data inconsistency",
diff --git a/packages/integration/src/locale/fr.integration.json b/packages/integration/src/locale/fr.integration.json
index c869c11bd..710102d22 100644
--- a/packages/integration/src/locale/fr.integration.json
+++ b/packages/integration/src/locale/fr.integration.json
@@ -22,6 +22,25 @@
"advancedMap": "Outils avancés",
"closestFeature": "Entités à proximité"
},
+ "catalog": {
+ "library.getCatalogList": "Obtenir la liste du contenu des catalogues",
+ "listExport": {
+ "rank": "Rang",
+ "layerTitle": "Titre de la couche",
+ "layerGroup": "Nom du groupe de couches",
+ "catalog": "Catalogue",
+ "externalProvider": "Fournisseur",
+ "url": "URL",
+ "layerName": "Nom système de la couche d'informaiton",
+ "context": "Contexte/Thématique",
+ "dataDescription": "Description de la donnée",
+ "metadataAbstract": "Description de la donnée",
+ "metadataUrl": "Lien des métadonnées",
+ "documentName": "ListeCouchesInformation_{{value}}",
+ "internal": "Interne",
+ "external": "Externe"
+ }
+ },
"dataIssueReporterTool": {
"submit": {
"title": "Soumettre une incohérence de donnée",