import {HttpClient} from '@angular/common/http'; import {Injectable} from '@angular/core'; import {Vector as VectorLayer} from 'ol/layer'; import {HsConfig} from 'hslayers-ng/config.service'; import {HsLayerManagerMetadataService} from 'hslayers-ng/components/layermanager/layermanager-metadata.service'; import {HsLayerManagerService} from 'hslayers-ng/components/layermanager'; 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 {obce, obceIndexLayer, osmLayer} from '../app.config'; @Injectable({providedIn: 'root'}) export class AdjusterService { /** To be read from a config file */ allowedClusteringMethods: Array; /** To be read from a config file */ initialWeights; /** 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 = []; numberOfClusters: number; //method: string; methods: Array<{ codename: string; layer?: VectorLayer; name: string; type: string; }>; private _clusteringInProcess: boolean; private _clustersLoaded: boolean; private _loadInProcess: boolean; private _raiInProcess: boolean; constructor( public adjusterEventService: AdjusterEventService, public hsConfig: HsConfig, public hsLayerMetadataService: HsLayerManagerMetadataService, public hsLayerManagerService: HsLayerManagerService, public hsUtilsService: HsUtilsService, public $http: 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.numberOfClusters = 9; } /** * Sends a request to polirural-attractiveness-service * and applies the returned values */ apply(): void { if (this.allowIndex) { this.calculateIndex(); } if (this.allowClusters) { this.calculateClusters(); } } calculateIndex(): void { this._raiInProcess = true; this.$http .post(this.serviceBaseUrl + 'cz/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() .then((attractivenessData: any[]) => { console.log(attractivenessData); // 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] = a; }); let errs = 0; //let logs = 0; console.time('forEach-Index'); obce.forEachFeature((feature) => { // Pair each feature with its attractivity data const featureData = codeRecordRelations[feature.get('nationalCode')]; if (!featureData) { if (errs < 20) { errs++; console.warn( `No data for feature ${feature.get('nationalCode')}` ); console.log(feature); } return; } /*logs++; if (logs % 100 == 0) { console.log(`processed ${logs} items`); }*/ Object.keys(featureData).forEach((key, index) => { if (key !== 'lau2') { feature.set(key, featureData[key], true); //true stands for "silent" - important for performance! } }); }); // Since we are updating the features silently, we now have to refresh manually obce.getFeatures()[0].dispatchEvent('change'); console.timeEnd('forEach-Index'); this._raiInProcess = false; this.adjusterEventService.loaded.next({ success: true, type: 'index', }); }) .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, }); }); } calculateClusters(): void { this._clusteringInProcess = true; this.$http .post(this.serviceBaseUrl + 'cz/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) => { console.log('data received', data); let logs = 0; let errs = 0; const clusterData = data.response; console.log(obceIndexLayer); /*let sublayers = []; const oldSublayers = obceLayer.get('Layer'); if (oldSublayers !== undefined && Array.isArray(oldSublayers)) { for (const sublyr of oldSublayers) { sublyr.getSource().clear(); } if (oldSublayers.length > this.numberOfClusters) { sublayers = oldSublayers.slice(0, this.numberOfClusters); } } let i = sublayers.length; while (sublayers.length < this.numberOfClusters) { const sublyr = new VectorLayer({ title: `Cluster ${i + 1}`, name: i + 1 + '', source: new VectorSource({}), }); sublayers.push(sublyr); sublyr.Title = `Cluster ${i + 1}`; sublyr.Name = `c${i + 1}`; i++; }*/ console.time('forEach-Cluster'); obce.forEachFeature((feature) => { // Pair each feature with its clustering data const featureData = clusterData.find( // NOTE: Do NOT add triple equal sign! (item) => item['lau2'] == feature.get('nationalCode') ); if (!featureData) { if (errs < 20) { errs++; console.warn( `No data for feature ${feature.get('nationalCode')}` ); console.log(feature); } return; } logs++; if (logs % 100 == 0) { console.log(`processed ${logs} items`); } Object.keys(featureData).forEach(function (key, index) { if (key !== 'lau2') { feature.set(key, featureData[key], true); } }); }); //const clusters = []; //const obceFeatures: Array = obce.getFeatures(); /*for (const region of clusterData) { if (!clusters.includes(region[this.method])) { clusters.push(region[this.method]); } const feature = obceFeatures.find( // NOTE: Do NOT add triple equal sign! (f) => f.get('nationalCode') == region['lau2'] ); if (!feature) { if (errs < 20) { errs++; console.warn(`No feature matches region ${region['lau2']}`); console.log(region); } continue; } logs++; if (logs % 100 == 0) { console.log(`processed ${logs} items`); } Object.keys(region).forEach(function (key, index) { if (key !== 'lau2') { feature.set(key, region[key], true); } }); if (logs % 100 == 0) { //sublayers[0].getSource().addFeature(feature); } //sublayers[region[this.method] - 1].getSource().addFeature(feature); }*/ //obceLayer.set('Layer', sublayers); //this.hsLayerMetadataService.fillMetadata(obceLayer); //console.log(sublayers[0].getSource().getFeatures()); obce.getFeatures()[0].dispatchEvent('change'); console.timeEnd('forEach-Cluster'); console.log('clustering done!'); //this.clusters = clusters; 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.$http .get(this.serviceBaseUrl + 'cz/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(); this.hsLayerManagerService.setGreyscale(osmLayer); }) .catch((error) => { console.warn(`Web service at ${this.serviceBaseUrl} unavailable!`); console.log(error); this._loadInProcess = false; }); } 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; } }