UNPKG

hslayers-ng

Version:
448 lines (438 loc) 24.8 kB
import * as i0 from '@angular/core'; import { Pipe, inject, Injectable, Input, Component, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { HsConfig } from 'hslayers-ng/config'; import { HsQueryVectorService } from 'hslayers-ng/services/query'; import { getBase, getShowInLayerManager, getTitle } from 'hslayers-ng/common/extensions'; import { isLayerVectorLayer, isLayerClustered } from 'hslayers-ng/services/utils'; import { HsMapService } from 'hslayers-ng/services/map'; import * as i1$1 from 'hslayers-ng/common/panels'; import { HsPanelBaseComponent, HsPanelHelpersModule, HsPanelHeaderComponent } from 'hslayers-ng/common/panels'; import { HsSidebarService } from 'hslayers-ng/services/sidebar'; import { HsLanguageService } from 'hslayers-ng/services/language'; import * as i1 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import * as i2 from '@ngx-translate/core'; import { TranslatePipe } from '@ngx-translate/core'; import * as i3 from '@angular/common'; import { CommonModule } from '@angular/common'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; const featureLimit = 20; class HsFeatureFilterPipe { /** * Transform * * @returns Filtered features */ transform(features, searchText) { if (!features) { return []; } if (!searchText) { return features; } searchText = searchText.toLocaleLowerCase(); return features .filter((feature) => { return feature.name.toLocaleLowerCase().includes(searchText); }) .slice(0, featureLimit); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureFilterPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureFilterPipe, isStandalone: false, name: "featureFilter" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureFilterPipe, decorators: [{ type: Pipe, args: [{ name: 'featureFilter', standalone: false, }] }] }); class HsFeatureTableService { constructor() { this.hsQueryVectorService = inject(HsQueryVectorService); /** * Trigger for reverse sorting */ this.sortReverse = false; /** * Last sorting value selected */ this.lastSortValue = ''; /** * all feature attributes for HTML table */ this.features = []; } /** * Checks if layer is vectorLayer and is visible in layer_manager, to exclude layers, such as, point Clicked * * @param layer - Layer from HsConfig.layersInFeatureTable * @returns Returns layer */ addLayer(layer) { if (!getBase(layer) && isLayerVectorLayer(layer, false) && (getShowInLayerManager(layer) === undefined || getShowInLayerManager(layer) == true)) { return this.wrapLayer(layer); } return; } /** * Wrap layer object * * @param layer - Layer from HsConfig.layersInFeatureTable * @returns Returns wrapped layer object */ wrapLayer(layer) { return { title: getTitle(layer), lyr: layer, type: 'vector', }; } /** * Search all layers feature attributes and map them into new objects for html table * * @param layer - Layer from HsConfig.layersInFeatureTable */ fillFeatureList(layer) { const source = isLayerClustered(layer) ? layer.getSource().getSource() : layer.getSource(); this.features = source .getFeatures() .map((f) => this.describeFeature(f)) .filter((f) => f?.attributes?.length > 0); } /** * Update feature description * * @param feature - Feature selected */ updateFeatureDescription(feature) { const newDescriptor = this.describeFeature(feature); const currentIx = this.features.findIndex((f) => f.feature == feature); if (newDescriptor && currentIx > -1) { this.features[currentIx] = newDescriptor; } } /** * Add feature description * * @param feature - Feature selected */ addFeatureDescription(feature) { const newDescriptor = this.describeFeature(feature); if (newDescriptor) { this.features.push(newDescriptor); } } /** * Remove feature description * * @param feature - Feature selected */ removeFeatureDescription(feature) { const currentIx = this.features.findIndex((f) => f.feature == feature); if (currentIx > -1) { this.features.splice(currentIx, 1); } } /** * Describe feature * * @param feature - Feature selected */ describeFeature(feature) { const attribWrapper = this.hsQueryVectorService .getFeatureAttributes(feature) .pop(); if (!attribWrapper) { return null; } return { name: this.setFeatureName(attribWrapper.attributes), feature, attributes: this.attributesWithoutFeatureName(attribWrapper.attributes), stats: attribWrapper.stats, }; } /** * Find feature name attribute and separate it from other attributes for html table purposes * * @param attributes - layers feature attributes * @returns feature name */ setFeatureName(attributes) { if (attributes.length > 0) { let name = 'Feature'; for (const attribute of attributes) { if (attribute.name === 'name') { name = attribute.value; } } return name; } return 'Feature'; } /** * Remove feature name attribute from feature attributes array * * @param attributes - layers feature attributes * @returns feature attributes */ attributesWithoutFeatureName(attributes) { return attributes.filter((attr) => attr.name !== 'name'); } /** * Sort features by requested value * * @param valueName - Requested value to sort the feature table list */ sortFeaturesBy(valueName) { if (this.features !== undefined && this.features.length > 1) { // If last sort by value is the same as current, reverse the sort order if (this.lastSortValue === valueName) { this.sortReverse = !this.sortReverse; } else { this.sortReverse = false; } this.features = this.features.sort((a, b) => this.sortFeatures(a, b, valueName)); } } /** * Sorting algorithm * * @param a - First input feature * @param b - second input feature * @param valueName - Sorting value * @returns Returns each features relative position in the table */ sortFeatures(a, b, valueName) { let aFeature, bFeature; let position; if (valueName === 'name') { //check if table is being sorted by name aFeature = a[valueName]; bFeature = b[valueName]; } else { aFeature = this.getValue(a.attributes, valueName); //get requested attribute value bFeature = this.getValue(b.attributes, valueName); } if (aFeature === null) { position = 1; } if (bFeature === null) { position = -1; } if (typeof aFeature == 'string' && typeof bFeature == 'string') { position = aFeature.charAt(0) > bFeature.charAt(0) ? 1 : aFeature.charAt(0) < bFeature.charAt(0) ? -1 : 0; } if (typeof aFeature == 'number' && typeof bFeature == 'number') { position = aFeature - bFeature; } if (this.sortReverse) { position = position * -1; } this.lastSortValue = valueName; return position; } /** * Get requested features attribute value, which will be used in the sorting algorithm * * @param attributes - features attributes * @param valueName - Sorting value * @returns Returns attributes value */ getValue(attributes, valueName) { let value = attributes //get requested attribute value .filter((attr) => attr.name == valueName) .map((attr) => attr.value); if (value.length == 0 || value === undefined) { value = null; } else { value = value[0]; } return value; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureTableService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureTableService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureTableService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); class HsLayerFeaturesComponent { constructor() { this.hsFeatureTableService = inject(HsFeatureTableService); this.hsMapService = inject(HsMapService); this.hsLanguageService = inject(HsLanguageService); /** * Toggle for showing feature statistics */ this.showFeatureStats = false; this.searchedFeatures = ''; } /** * Activate listeners for any layer source changes to update the html table */ ngOnInit() { const olLayer = this.layer.lyr; const source = isLayerClustered(olLayer) ? olLayer.getSource().getSource() : olLayer.getSource(); if (source) { this.hsFeatureTableService.fillFeatureList(olLayer); source.on('changefeature', (e) => { this.hsFeatureTableService.updateFeatureDescription(e.feature); }); source.on('addfeature', (e) => { this.hsFeatureTableService.addFeatureDescription(e.feature); }); source.on('removefeature', (e) => { this.hsFeatureTableService.removeFeatureDescription(e.feature); }); } } /** * Zoom to feature from HTML table after triggering zoom action * @param operation - Action for HTML table */ executeOperation(operation) { switch (operation.action) { case 'zoom to': const extent = operation.feature.getGeometry().getExtent(); this.hsMapService.fitExtent(extent); break; case 'custom action': operation.customAction(operation.feature); break; default: } } /** * Translate provided text to selected locale language * @param text - Text to translate to locale * @returns Returns translation */ translate(text) { const translation = this.hsLanguageService.getTranslationIgnoreNonExisting('FEATURE_TABLE', text, undefined); return translation; } /** * Sort features by value * @param name - Sort value */ sortFeaturesBy(name) { this.hsFeatureTableService.sortFeaturesBy(name); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsLayerFeaturesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.3", type: HsLayerFeaturesComponent, isStandalone: false, selector: "hs-layer-features", inputs: { layer: "layer" }, ngImport: i0, template: "<h3 class=\"mt-1\" style=\"text-align: center\">{{'LAYERS.' + layer.title | translate : {fallbackValue: layer.title} }}</h3>\n<div class=\"form-group mt-1 mb-1\">\n <div class=\"input-group\" style=\"margin-bottom: 4px\">\n <input type=\"text\" class=\"form-control hs-filter\" [placeholder]=\"'COMMON.filter' | translate \"\n [(ngModel)]=\"searchedFeatures\" name=\"FeatureName\">\n </div>\n</div>\n@for (feature of hsFeatureTableService.features | featureFilter: searchedFeatures; track feature) {\n <table style=\"width:100%;\">\n <thead>\n <tr style=\"text-align: center; background-color: rgba(0,0,0,0.05);\">\n <th colspan=\"2\">{{translate(feature.name)}}<!-- TODO: Remove function call from template -->\n <a style=\"float: center;\" (click)=\"sortFeaturesBy(feature.name)\"\n [title]=\"'FEATURE_TABLE.sortFeaturesByValue' | translate \">^</a>\n </th>\n </tr>\n </thead>\n <tbody>\n @for (attr of feature.attributes; track attr) {\n <tr rowspan=\"3\">\n <th class=\"tdbreak\" style=\"background-color: rgba(0,0,0,0.03);\">\n {{translate(attr.name)}}<a style=\"float: right;\" (click)=\"sortFeaturesBy(attr.name)\"\n [title]=\"'FEATURE_TABLE.sortFeaturesByValue' | translate \">^</a><!-- TODO: Remove function call from template -->\n </th>\n <td class=\"tdbreak\" style=\"min-width: 200px; max-width: 200px;\">\n @if (!attr.sanitizedValue && !attr.value?.operations) {\n <span>\n <i>{{attr.value}}</i>\n </span>\n }\n <i>@if (!!attr.sanitizedValue) {\n <span [innerHTML]=\"attr.sanitizedValue\"></span>\n }</i>\n @if (attr.value?.operations) {\n <div>\n @for (operation of attr.value.operations; track operation) {\n <a (click)=\"executeOperation(operation)\"><span>\n <i> {{translate(operation.customActionName) ||\n translate(operation.action)}};</i></span></a>\n }<!-- TODO: Remove function call from template -->\n </div>\n }\n </td>\n </tr>\n }\n @for (stat of feature.stats; track stat) {\n <tr [hidden]=\"!showFeatureStats\" class=\"tdbreak\">\n <th class=\"tdbreak\" style=\"background-color: rgba(0,0,0,0.03);\">\n {{translate(stat.name)}}</th><!-- TODO: Remove function call from template -->\n <td class=\"tdbreak\" style=\"min-width: 200px; max-width: 200px;\"><span><i>{{stat.value}}</i></span></td>\n </tr>\n }\n </tbody>\n </table>\n }\n <p class=\"p-2\">\n <small><a (click)=\"showFeatureStats = !showFeatureStats\">{{'FEATURE_TABLE.showFeatureStats' | translate\n }}</a></small>\n </p>\n <hr />\n", styles: ["td,th{border:1px solid #dddddd;text-align:left;padding:6px;font-size:.875rem}.tdbreak{overflow-wrap:break-word;word-break:break-word}\n"], dependencies: [{ kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "pipe", type: HsFeatureFilterPipe, name: "featureFilter" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsLayerFeaturesComponent, decorators: [{ type: Component, args: [{ selector: 'hs-layer-features', standalone: false, template: "<h3 class=\"mt-1\" style=\"text-align: center\">{{'LAYERS.' + layer.title | translate : {fallbackValue: layer.title} }}</h3>\n<div class=\"form-group mt-1 mb-1\">\n <div class=\"input-group\" style=\"margin-bottom: 4px\">\n <input type=\"text\" class=\"form-control hs-filter\" [placeholder]=\"'COMMON.filter' | translate \"\n [(ngModel)]=\"searchedFeatures\" name=\"FeatureName\">\n </div>\n</div>\n@for (feature of hsFeatureTableService.features | featureFilter: searchedFeatures; track feature) {\n <table style=\"width:100%;\">\n <thead>\n <tr style=\"text-align: center; background-color: rgba(0,0,0,0.05);\">\n <th colspan=\"2\">{{translate(feature.name)}}<!-- TODO: Remove function call from template -->\n <a style=\"float: center;\" (click)=\"sortFeaturesBy(feature.name)\"\n [title]=\"'FEATURE_TABLE.sortFeaturesByValue' | translate \">^</a>\n </th>\n </tr>\n </thead>\n <tbody>\n @for (attr of feature.attributes; track attr) {\n <tr rowspan=\"3\">\n <th class=\"tdbreak\" style=\"background-color: rgba(0,0,0,0.03);\">\n {{translate(attr.name)}}<a style=\"float: right;\" (click)=\"sortFeaturesBy(attr.name)\"\n [title]=\"'FEATURE_TABLE.sortFeaturesByValue' | translate \">^</a><!-- TODO: Remove function call from template -->\n </th>\n <td class=\"tdbreak\" style=\"min-width: 200px; max-width: 200px;\">\n @if (!attr.sanitizedValue && !attr.value?.operations) {\n <span>\n <i>{{attr.value}}</i>\n </span>\n }\n <i>@if (!!attr.sanitizedValue) {\n <span [innerHTML]=\"attr.sanitizedValue\"></span>\n }</i>\n @if (attr.value?.operations) {\n <div>\n @for (operation of attr.value.operations; track operation) {\n <a (click)=\"executeOperation(operation)\"><span>\n <i> {{translate(operation.customActionName) ||\n translate(operation.action)}};</i></span></a>\n }<!-- TODO: Remove function call from template -->\n </div>\n }\n </td>\n </tr>\n }\n @for (stat of feature.stats; track stat) {\n <tr [hidden]=\"!showFeatureStats\" class=\"tdbreak\">\n <th class=\"tdbreak\" style=\"background-color: rgba(0,0,0,0.03);\">\n {{translate(stat.name)}}</th><!-- TODO: Remove function call from template -->\n <td class=\"tdbreak\" style=\"min-width: 200px; max-width: 200px;\"><span><i>{{stat.value}}</i></span></td>\n </tr>\n }\n </tbody>\n </table>\n }\n <p class=\"p-2\">\n <small><a (click)=\"showFeatureStats = !showFeatureStats\">{{'FEATURE_TABLE.showFeatureStats' | translate\n }}</a></small>\n </p>\n <hr />\n", styles: ["td,th{border:1px solid #dddddd;text-align:left;padding:6px;font-size:.875rem}.tdbreak{overflow-wrap:break-word;word-break:break-word}\n"] }] }], propDecorators: { layer: [{ type: Input }] } }); class HsFeatureTableComponent extends HsPanelBaseComponent { constructor() { super(...arguments); this.hsFeatureTableService = inject(HsFeatureTableService); this.hsConfig = inject(HsConfig); this.hsMapService = inject(HsMapService); this.hsSidebarService = inject(HsSidebarService); this.layers = []; this.name = 'feature-table'; } ngOnInit() { this.hsSidebarService.addButton({ panel: 'featureTable', module: 'hs.feature-table', order: 14, fits: true, title: 'PANEL_HEADER.FEATURE_TABLE', description: 'SIDEBAR.descriptions.FEATURE_TABLE', icon: 'fa-solid fa-table-list', }); this.hsMapService.loaded().then(() => { for (const layer of this.hsConfig.layersInFeatureTable || []) { this.addLayerToTable(layer); } }); } /** * Add layer to feature description table * @param layer - Layer to add */ addLayerToTable(layer) { const layerDescriptor = this.hsFeatureTableService.addLayer(layer); if (layerDescriptor) { this.layers.push(layerDescriptor); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureTableComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.3", type: HsFeatureTableComponent, isStandalone: false, selector: "hs-feature-table", usesInheritance: true, ngImport: i0, template: "@if (isVisible$ | async) {\n <div class=\"card hs-main-panel\">\n <hs-panel-header name=\"feature-table\" [panelTabs]=\"'FEATURE_TABLE'\"></hs-panel-header>\n <div class=\"card-body\">\n @for (layer of layers; track layer) {\n <div>\n <hs-layer-features [layer]=\"layer\"></hs-layer-features>\n </div>\n }\n </div>\n </div>\n}", dependencies: [{ kind: "component", type: i1$1.HsPanelHeaderComponent, selector: "hs-panel-header", inputs: ["name", "panelTabs", "translationModule", "selectedTab$"], outputs: ["tabSelected"] }, { kind: "component", type: HsLayerFeaturesComponent, selector: "hs-layer-features", inputs: ["layer"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureTableComponent, decorators: [{ type: Component, args: [{ selector: 'hs-feature-table', standalone: false, template: "@if (isVisible$ | async) {\n <div class=\"card hs-main-panel\">\n <hs-panel-header name=\"feature-table\" [panelTabs]=\"'FEATURE_TABLE'\"></hs-panel-header>\n <div class=\"card-body\">\n @for (layer of layers; track layer) {\n <div>\n <hs-layer-features [layer]=\"layer\"></hs-layer-features>\n </div>\n }\n </div>\n </div>\n}" }] }] }); class HsFeatureTableModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureTableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureTableModule, declarations: [HsFeatureTableComponent, HsLayerFeaturesComponent, HsFeatureFilterPipe], imports: [CommonModule, FormsModule, HsPanelHelpersModule, TranslatePipe, NgbDropdownModule, HsPanelHeaderComponent], exports: [HsFeatureTableComponent, HsLayerFeaturesComponent, HsFeatureFilterPipe] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureTableModule, imports: [CommonModule, FormsModule, HsPanelHelpersModule, NgbDropdownModule, HsPanelHeaderComponent] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.3", ngImport: i0, type: HsFeatureTableModule, decorators: [{ type: NgModule, args: [{ schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [ HsFeatureTableComponent, HsLayerFeaturesComponent, HsFeatureFilterPipe, ], imports: [ CommonModule, FormsModule, HsPanelHelpersModule, TranslatePipe, NgbDropdownModule, HsPanelHeaderComponent, ], exports: [ HsFeatureTableComponent, HsLayerFeaturesComponent, HsFeatureFilterPipe, ], }] }] }); /** * Generated bundle index. Do not edit. */ export { HsFeatureFilterPipe, HsFeatureTableComponent, HsFeatureTableModule, HsFeatureTableService, HsLayerFeaturesComponent }; //# sourceMappingURL=hslayers-ng-components-feature-table.mjs.map