import {HttpClient} from '@angular/common/http'; import {Injectable} from '@angular/core'; import {Vector as VectorLayer} from 'ol/layer'; import {HsDialogContainerService} from 'hslayers-ng/components/layout/dialogs/dialog-container.service'; import {HsUtilsService} from 'hslayers-ng/components/utils/utils.service'; import attractivenessConfig from '../attractiveness.config.json'; import clusteringMethods from '../data/clustering_methods.json'; import {AdjusterEventService} from './adjuster-event.service'; import {AdjusterLegendService} from './adjuster-legend.service'; import {AdjusterLoaderComponent} from './adjuster-loader.component'; import {AttractivenessClustersService} from './attractiveness-clusters.service'; import {AttractivenessIndexService} from './attractiveness-index.service'; import {nuts} from '../nuts'; @Injectable({providedIn: 'root'}) export class AdjusterService { /** To be read from a config file */ allowedClusteringMethods: Array; /** To be read from a config file */ initialWeights: Record; /** To be read from a config file */ serviceBaseUrl: string; /** Used in the UI as a selector */ allowClusters = true; /** Used in the UI as a selector */ allowIndex = true; factors = []; clusters = []; numberOfClusters: number; method: string; methods: Array; private _clusteringInProcess: boolean; private _clustersLoaded: boolean; private _loadInProcess: boolean; private _raiInProcess: boolean; constructor( public adjusterEventService: AdjusterEventService, public adjusterLegendService: AdjusterLegendService, public attractivenessIndexService: AttractivenessIndexService, public attractivenessClustersService: AttractivenessClustersService, public hsDialogContainerService: HsDialogContainerService, public hsUtilsService: HsUtilsService, public httpClient: HttpClient ) { // First safely set configurable properties this.allowedClusteringMethods = attractivenessConfig?.allowedClusteringMethods ?? []; this.initialWeights = attractivenessConfig?.initialWeights ?? {}; this.serviceBaseUrl = attractivenessConfig?.serviceBaseUrl ?? 'https://publish.lesprojekt.cz/nodejs/'; // 'https://jmacura.ml/ws/' // 'http://localhost:3000/' this.methods = clusteringMethods.filter((m) => this.allowedClusteringMethods.includes(m.codename) ); this.method = 'haclustwd2'; //TODO: set in config/or not use at all? this.numberOfClusters = 12; } /** * Sends a request to polirural-attractiveness-service * and applies the returned values */ apply(): void { if (this.allowIndex) { this.calculateIndex(); } if (this.allowClusters) { this.calculateClusters(); } } async calculateIndex(): Promise { this._raiInProcess = true; let attractivenessData: any; try { attractivenessData = await this.httpClient .post(this.serviceBaseUrl + 'eu/scores/', { factors: this.factors.map((f) => { return { factor: f.name, weight: f.weight, datasets: f.datasets .filter((ds) => ds.included) .map((ds) => ds.name), }; }), }) .toPromise(); } catch (error) { console.warn(`Error obtaining data from ${this.serviceBaseUrl}.`); console.log(error); this._raiInProcess = false; this.adjusterEventService.loaded.next({ success: true, type: 'index', err: error, }); } // Spread the 'aggregate' value between 0 and 1 const min = attractivenessData.reduce((a, b) => a.aggregate < b.aggregate ? a : b ).aggregate; const max = attractivenessData.reduce((a, b) => a.aggregate > b.aggregate ? a : b ).aggregate; const coefficient = 1 / (max - min); const constant = -min * coefficient; attractivenessData.forEach((a) => { a.aggregate *= coefficient; a.aggregate += constant; }); // Store relation between region and its data in a hash-table-like structure // more memory consuming, but faster then find() const codeRecordRelations = {}; attractivenessData.forEach((a) => { codeRecordRelations[a.code.toUpperCase()] = a; }); console.time('forEach-Index'); this.attractivenessIndexService.processIndex(codeRecordRelations); console.timeEnd('forEach-Index'); this._raiInProcess = false; this.adjusterEventService.loaded.next({ success: true, type: 'index', }); } calculateClusters(): void { this._clusteringInProcess = true; this.httpClient .post(this.serviceBaseUrl + 'eu/clusters/', { numberOfClusters: this.numberOfClusters, factors: this.factors.map((f) => { return { factor: f.name, weight: f.weight, datasets: f.datasets .filter((ds) => ds.included) .map((ds) => ds.name), }; }), }) .toPromise() .then((data: any) => { const clusterData = data.response; // Store relation between region and its data in a hash-table-like structure // more memory consuming, but much faster then find() const codeRecordRelations = {}; clusterData.forEach((c) => { codeRecordRelations[c['nuts_id'].toUpperCase()] = c; }); console.time('forEach-Cluster'); /*const clusters = []; for (const region of clusterData) { if (!clusters.includes(region[this.method])) { clusters.push(region[this.method]); } }*/ //for (const method of this.methods) { this.attractivenessClustersService.processClusters(codeRecordRelations); //} console.timeEnd('forEach-Cluster'); /*let max = 0; this.clusters.forEach((a) => { if (a.aggregate > max) { max = a.aggregate; } }); const normalizer = 1 / max; this.attractivity.forEach((a) => { a.aggregate *= normalizer; }); this.attractivity.forEach((a) => { this.nutsCodeRecordRelations[a.code] = a; });*/ // Fake the legend nuts.nuts3ClustersSource.legend_categories = this.adjusterLegendService.createClusterLegend( this.numberOfClusters ); this._clustersLoaded = true; this._clusteringInProcess = false; this.adjusterEventService.loaded.next({ success: true, type: 'clusters', }); }) .catch((error) => { console.warn(`Error obtaining data from ${this.serviceBaseUrl}.`); console.log(error); this._clusteringInProcess = false; this.adjusterEventService.loaded.next({ success: false, type: 'clusters', err: error, }); }); } init(): void { this._loadInProcess = true; this.httpClient .get(this.serviceBaseUrl + 'eu/datasets/') .toPromise() .then((data: any) => { this.factors = data.map((dataset) => { return { name: dataset.Factor, weight: this.initialWeights[dataset.Factor] ?? 1, datasets: [], }; }); this.factors = this.hsUtilsService.removeDuplicates( this.factors, 'name' ); this.factors.forEach((factor) => { factor.datasets = data .filter((ds) => ds.Factor === factor.name) .map((ds) => { return { name: ds.Name, desc: ds.Description, included: true, }; }); }); this._loadInProcess = false; this.apply(); // In HSL 2.5, setting layer greyscale breaks the print() functionality //this.hsLayerManagerService.setGreyscale(osmLayer); }) .catch((error) => { console.warn(`Web service at ${this.serviceBaseUrl} unavailable!`); console.log(error); this._loadInProcess = false; /*this.adjusterEventService.loaded.next({ success: false, err: error, });*/ }); } clustersLoaded(): boolean { return this._clustersLoaded; } /** * @returns {boolean} true if clustering or index processing is in process or loading data, false otherwise */ isInProcess(): boolean { return ( this._loadInProcess || this._clusteringInProcess || this._raiInProcess ); } isLoading(): boolean { return this._loadInProcess; } isClustering(): boolean { return this._clusteringInProcess; } isCalculatingRAI(): boolean { return this._raiInProcess; } } type MethodDescription = { codename: string; layer?: VectorLayer; name: string; type: string; };