hslayers-ng
Version:
HSLayers-NG mapping library
448 lines (438 loc) • 24.8 kB
JavaScript
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