Browse Source

✨ merge with index app

jmacura 4 years ago
parent
commit
5da06d11cf

+ 1 - 1
src/adjuster/adjuster-event.service.ts

@@ -3,7 +3,7 @@ import {Subject} from 'rxjs';
 
 @Injectable({providedIn: 'root'})
 export class AdjusterEventService {
-  clustersLoaded: Subject<{success: boolean; err?}> = new Subject();
+  loaded: Subject<{success: boolean; type: string; err?}> = new Subject();
   methodChanged: Subject<string> = new Subject();
   constructor() {}
 }

+ 6 - 4
src/adjuster/adjuster-loader.component.ts

@@ -1,8 +1,10 @@
-import {AdjusterEventService} from './adjuster-event.service';
 import {Component, Input, ViewRef} from '@angular/core';
+
 import {HsDialogComponent} from 'hslayers-ng/components/layout/dialogs/dialog-component.interface';
 import {HsDialogContainerService} from 'hslayers-ng/components/layout/dialogs/dialog-container.service';
 
+import {AdjusterService} from './adjuster.service';
+
 @Component({
   selector: 'pra-adjuster-loader',
   template: require('./adjuster-loader.directive.html'),
@@ -12,10 +14,10 @@ export class AdjusterLoaderComponent implements HsDialogComponent {
   viewRef: ViewRef;
   constructor(
     public hsDialogContainerService: HsDialogContainerService,
-    public adjusterEventService: AdjusterEventService
+    public adjusterService: AdjusterService
   ) {
-    this.adjusterEventService.clustersLoaded.subscribe(() => {
+    /*this.adjusterEventService.loaded.subscribe(() => {
       this.hsDialogContainerService.destroy(this);
-    });
+    });*/
   }
 }

+ 5 - 3
src/adjuster/adjuster-loader.directive.html

@@ -1,8 +1,10 @@
-<div class="loader-splash">
+<div class="loader-splash" *ngIf="adjusterService.isInProcess()">
   <div class="center-block">
-    <h1>Calculating clusters</h1>
+    <h1 [hidden]="!adjusterService.isLoading()">{{'ADJUSTER.loadingData' | translate}}</h1>
+    <h1 [hidden]="!adjusterService.isCalculatingRAI()">{{'ADJUSTER.calcAttractivity' | translate}}</h1>
+    <h1 [hidden]="!adjusterService.isClustering()">{{'ADJUSTER.calcClusters' | translate}}</h1>
     <div class="spinner-border spinner" role="status">
-      <span class="sr-only">Loading...</span>
+      <span class="sr-only">{{'ADJUSTER.loading' | translate}}...</span>
     </div>
   </div>
 </div>

+ 11 - 1
src/adjuster/adjuster.component.ts

@@ -1,9 +1,11 @@
 import {Component, ViewRef} from '@angular/core';
 
+import {HsDialogContainerService} from 'hslayers-ng/components/layout/dialogs/dialog-container.service';
 import {HsLayoutService} from 'hslayers-ng/components/layout/layout.service';
 import {HsPanelComponent} from 'hslayers-ng/components/layout/panels/panel-component.interface';
 
 import {AdjusterEventService} from './adjuster-event.service';
+import {AdjusterLoaderComponent} from './adjuster-loader.component';
 import {AdjusterService} from './adjuster.service';
 
 @Component({
@@ -20,11 +22,12 @@ export class AdjusterComponent implements HsPanelComponent {
   constructor(
     public adjusterService: AdjusterService,
     public adjusterEventService: AdjusterEventService,
+    public hsDialogContainerService: HsDialogContainerService,
     public hsLayoutService: HsLayoutService
   ) {
     this.descriptionVisible = false;
     this.method = this.adjusterService.method;
-    this.adjusterEventService.clustersLoaded.subscribe(({success, err}) => {
+    this.adjusterEventService.loaded.subscribe(({success, err}) => {
       if (!success) {
         this.errorMsg = err.message;
       }
@@ -32,6 +35,7 @@ export class AdjusterComponent implements HsPanelComponent {
   }
 
   ngOnInit(): void {
+    this.hsDialogContainerService.create(AdjusterLoaderComponent, {});
     this.adjusterService.init();
   }
 
@@ -56,6 +60,12 @@ export class AdjusterComponent implements HsPanelComponent {
     return datasetsEffectivelyTurnedOn.length === 0;
   }
 
+  noOperationSelected(): boolean {
+    return (
+      !this.adjusterService.allowIndex && !this.adjusterService.allowClusters
+    );
+  }
+
   selectMethod(): void {
     this.adjusterService.method = this.method;
     this.adjusterEventService.methodChanged.next(this.method);

+ 27 - 12
src/adjuster/adjuster.directive.html

@@ -1,15 +1,30 @@
 <div [hidden]="!isVisible()" class="card mainpanel">
-  <hs-panel-header name="adjuster" [title]="'Adjust factors'"></hs-panel-header>
+  <hs-panel-header name="adjuster" [title]="'ADJUSTER.adjustFactors' | translate"></hs-panel-header>
   <div class="card-body">
     <div class="p-2 center-block">
-      <button type="button" class="btn btn-primary" (click)="adjusterService.apply()"
-        [disabled]="adjusterService.isClusteringInProcess() || noDataSelected()">Calculate clusters</button>
-      <div class="text-warning pt-2" [hidden]="!noDataSelected() || errorMsg">Select at least one dataset and set at least one
-        factor's weight to a non-zero value.</div>
-      <div class="text-danger pt-2" [hidden]="!errorMsg">Server error: {{errorMsg}}</div>
+      <div class="d-flex flex-row">
+        <div>
+          <button type="button" class="btn btn-primary btn-lg" (click)="adjusterService.apply()"
+            [disabled]="adjusterService.isInProcess() || noDataSelected() || noOperationSelected() ">{{'ADJUSTER.calculate' | translate}}</button>
+        </div>
+        <div>
+          <button type="button" class="btn btn-sm btn-light hs-lm-item-visibility"
+            [ngClass]="adjusterService.allowIndex ? 'hs-checkmark' : 'hs-uncheckmark'"
+            (click)="adjusterService.allowIndex = !adjusterService.allowIndex; $event.stopPropagation()"></button>
+          <label class="pl-2 text-secondary">{{'ADJUSTER.index' | translate}}</label>
+        </div>
+        <div>
+          <button type="button" class="btn btn-sm btn-light hs-lm-item-visibility"
+            [ngClass]="adjusterService.allowClusters ? 'hs-checkmark' : 'hs-uncheckmark'"
+            (click)="adjusterService.allowClusters = !adjusterService.allowClusters; $event.stopPropagation()"></button>
+            <label class="pl-2 text-secondary">{{'ADJUSTER.clusters' | translate}}</label>
+        </div>
+      </div>
+      <div class="text-warning pt-2" [hidden]="!noDataSelected() || errorMsg">{{'ADJUSTER.noDataSelectedMsg' | translate}}</div>
+      <div class="text-danger pt-2" [hidden]="!errorMsg">{{'ADJUSTER.serverError' | translate}}: {{errorMsg}}</div>
     </div>
-    <div class="p-2 center-block">
-      <div class="pt-2 text-secondary">Number of clusters to create: {{adjusterService.numberOfClusters}}</div>
+    <div class="p-2 center-block" [hidden]="!adjusterService.allowClusters">
+      <div class="pt-2 text-secondary">{{'ADJUSTER.numberOfClusters' | translate}}: {{adjusterService.numberOfClusters}}</div>
       <input type="range" class="custom-range" [(ngModel)]="adjusterService.numberOfClusters" min="5" max="15" step="1">
     </div>
     <div *ngFor="let factor of adjusterService.factors; let datasetlistVisible = false">
@@ -40,7 +55,7 @@
       </div>
     </div>
     <hr>
-    <form role="form" class="pt-3 form" [hidden]="adjusterService.clusters.length == 0">
+    <form role="form" class="pt-3 form" [hidden]="!adjusterService.clustersLoaded()">
       <div class="form-group">
         <div class="input-group">
           <label class="text-center">Display clusters calculated by method</label>
@@ -57,9 +72,9 @@
       </div>
     </form>
     <hr>
-    <div class="pt-3 center-block" [hidden]="adjusterService.clusters.length == 0">
-      You can investigate the layers<br>
-      in the <a href="" (click)="hsLayoutService.setMainPanel('layermanager');$event.preventDefault();">LayerManager</a>
+    <div class="pt-3 center-block" [hidden]="adjusterService.clustersLoaded()">
+      {{'ADJUSTER.layerManagerLink1' | translate}}<br>
+      {{'ADJUSTER.layerManagerLink2' | translate}} <a href="" (click)="hsLayoutService.setMainPanel('layermanager');$event.preventDefault();">{{'ADJUSTER.layerManagerLinkLMName' | translate}}</a>
     </div>
   </div>
 </div>

+ 8 - 1
src/adjuster/adjuster.module.ts

@@ -2,6 +2,7 @@ import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
 import {CommonModule} from '@angular/common';
 import {FormsModule} from '@angular/forms';
 import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
+import {TranslateModule} from '@ngx-translate/core';
 
 import {HsPanelHelpersModule} from 'hslayers-ng/components/layout/panels/panel-helpers.module';
 
@@ -12,7 +13,13 @@ import {AdjusterService} from './adjuster.service';
 
 @NgModule({
   schemas: [CUSTOM_ELEMENTS_SCHEMA],
-  imports: [CommonModule, FormsModule, HsPanelHelpersModule, NgbModule],
+  imports: [
+    CommonModule,
+    FormsModule,
+    HsPanelHelpersModule,
+    NgbModule,
+    TranslateModule,
+  ],
   exports: [AdjusterComponent],
   declarations: [AdjusterComponent, AdjusterLoaderComponent],
   entryComponents: [AdjusterComponent, AdjusterLoaderComponent],

+ 252 - 77
src/adjuster/adjuster.service.ts

@@ -1,10 +1,11 @@
 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 attractivity from '../Attractivity.json';
+import attractivenessConfig from '../attractiveness.config.json';
 import clusteringMethods from '../data/clustering_methods.json';
 import {AdjusterEventService} from './adjuster-event.service';
 import {AdjusterLoaderComponent} from './adjuster-loader.component';
@@ -13,17 +14,25 @@ import {nuts} from '../nuts';
 
 @Injectable({providedIn: 'root'})
 export class AdjusterService {
+  /** To be read from a config file */
+  allowedClusteringMethods: Array<string>;
+  /** 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 = [];
   clusters = [];
-  numberOfClusters;
+  numberOfClusters: number;
   method: string;
-  methods: Array<{
-    codename: string;
-    name: string;
-    type: string;
-  }>;
+  methods: Array<MethodDescription>;
   private _clusteringInProcess: boolean;
+  private _clustersLoaded: boolean;
+  private _loadInProcess: boolean;
+  private _raiInProcess: boolean;
 
   constructor(
     public adjusterEventService: AdjusterEventService,
@@ -31,12 +40,18 @@ export class AdjusterService {
     public hsUtilsService: HsUtilsService,
     public httpClient: HttpClient
   ) {
-    this.serviceBaseUrl = 'https://jmacura.ml/ws/';
-      //window.location.hostname === 'localhost'
-        //? 'https://jmacura.ml/ws/' // 'http://localhost:3000/'
-        //: 'https://publish.lesprojekt.cz/nodejs/';
-    this.methods = clusteringMethods;
-    this.method = 'haclustwd2';
+    // 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;
   }
 
@@ -45,26 +60,109 @@ export class AdjusterService {
    * and applies the returned values
    */
   apply(): void {
-    this.hsDialogContainerService.create(AdjusterLoaderComponent, {});
-    const f = () => {
-      this._clusteringInProcess = true;
-      this.httpClient
-        .post(this.serviceBaseUrl + '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;
-          /*let max = 0;
+    if (this.allowIndex) {
+      this.calculateIndex();
+    }
+    if (this.allowClusters) {
+      this.calculateClusters();
+    }
+  }
+
+  calculateIndex(): void {
+    this._raiInProcess = true;
+    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()
+      .then((attractivenessData: any[]) => {
+        // 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;
+        });
+        console.time('forEach-Index');
+        this.processIndex(codeRecordRelations);
+        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.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']] = 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.processClusters(codeRecordRelations);
+        //TODO: method.layer.getSource().legend_categories = this.adjusterLegendService.createClusterLegend(
+        //  this.numberOfClusters
+        //);
+        //}
+        console.timeEnd('forEach-Cluster');
+        /*let max = 0;
         this.clusters.forEach((a) => {
           if (a.aggregate > max) {
             max = a.aggregate;
@@ -77,53 +175,37 @@ export class AdjusterService {
         this.attractivity.forEach((a) => {
           this.nutsCodeRecordRelations[a.code] = a;
         });*/
-          nuts.nuts3Source.forEachFeature((feature) => {
-            // Pair each feature with its clustering data
-            const featureData = clusterData.find(
-              (item) => item['nuts_id'] === feature.get('NUTS_ID')
-            );
-            if (!featureData) {
-              console.warn(`No data for feature ${feature.get('NUTS_ID')}`);
-              console.log(feature);
-              return;
-            }
-            Object.keys(featureData).forEach(function (key, index) {
-              if (key !== 'nuts_id') {
-                feature.set(key, featureData[key]);
-              }
-            });
-          });
-          const clusters = [];
-          for (const region of clusterData) {
-            if (!clusters.includes(region[this.method])) {
-              clusters.push(region[this.method]);
-            }
-          }
-          this.clusters = clusters;
-          this._clusteringInProcess = false;
-          this.adjusterEventService.clustersLoaded.next({success: true});
-        })
-        .catch((error) => {
-          console.warn(`Error obtaining data from ${this.serviceBaseUrl}.`);
-          console.log(error);
-          this._clusteringInProcess = false;
-          this.adjusterEventService.clustersLoaded.next({
-            success: false,
-            err: error,
-          });
+        this._clustersLoaded = true;
+        this._clusteringInProcess = false;
+        this.adjusterEventService.loaded.next({
+          success: true,
+          type: 'clusters',
         });
-    };
-    this.hsUtilsService.debounce(f, 300, false, this)();
+      })
+      .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._clusteringInProcess = true;
+    this._loadInProcess = true;
     this.httpClient
-      .get(this.serviceBaseUrl + 'datasets')
+      .get(this.serviceBaseUrl + 'eu/datasets/')
       .toPromise()
       .then((data: any) => {
         this.factors = data.map((dataset) => {
-          return {name: dataset.Factor, weight: 1, datasets: []};
+          return {
+            name: dataset.Factor,
+            weight: this.initialWeights[dataset.Factor] ?? 1,
+            datasets: [],
+          };
         });
         this.factors = this.hsUtilsService.removeDuplicates(
           this.factors,
@@ -140,23 +222,116 @@ export class AdjusterService {
               };
             });
         });
+        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._clusteringInProcess = false;
-        this.adjusterEventService.clustersLoaded.next({
+        this._loadInProcess = false;
+        /*this.adjusterEventService.loaded.next({
           success: false,
           err: error,
-        });
+        });*/
+      });
+  }
+
+  processIndex(codeRecordRelations: Record<string, unknown>): void {
+    /*if (obce.getFeatures()?.length < 1) {
+      obce.once('changefeature', () => this.processIndex(codeRecordRelations));
+      return;
+    }*/
+    let errs = 0;
+    //let logs = 0;
+    nuts.nuts3Source.forEachFeature((feature) => {
+      // Pair each feature with its attractivity data
+      const featureData = codeRecordRelations[feature.get('NUTS_ID')];
+      if (!featureData) {
+        if (errs < 20) {
+          errs++;
+          console.warn(`No data for feature ${feature.get('NUTS_ID')}`);
+          console.log(feature);
+        }
+        return;
+      }
+      /*logs++;
+      if (logs % 100 == 0) {
+        console.log(`processed ${logs} items`);
+      }*/
+      Object.keys(featureData).forEach((key, index) => {
+        if (key !== 'nuts_id') {
+          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
+    nuts.nuts3Source.getFeatures()[0].dispatchEvent('change');
+  }
+
+  processClusters(codeRecordRelations: Record<string, unknown>): void {
+    let errs = 0;
+    //let logs = 0;
+    nuts.nuts3Source.forEachFeature((feature) => {
+      // Pair each feature with its clustering data
+      const featureData = codeRecordRelations[feature.get('NUTS_ID')];
+      /*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('NUTS_ID')}`);
+          console.log(feature);
+        }
+        return;
+      }
+      /*logs++;
+      if (logs % 100 == 0) {
+        console.log(`processed ${logs} items`);
+      }*/
+      //feature.set(method.codename, featureData[method.codename], true);
+      Object.keys(featureData).forEach(function (key, index) {
+        if (key !== 'nuts_id') {
+          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
+    nuts.nuts3Source.getFeatures()[0].dispatchEvent('change');
+  }
+
+  clustersLoaded(): boolean {
+    return this._clustersLoaded;
   }
 
   /**
-   * @returns {boolean} true if clustering is in process, false otherwise
+   * @returns {boolean} true if clustering or index processing is in process or loading data, false otherwise
    */
-  isClusteringInProcess(): boolean {
+  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;
+};

+ 4 - 4
src/adjuster/index.ts

@@ -3,7 +3,7 @@ import {downgrade} from 'hslayers-ng/common/downgrader';
 import {downgradeComponent, downgradeInjectable} from '@angular/upgrade/static';
 
 import {AdjusterComponent} from './adjuster.component';
-import {AdjusterLoaderComponent} from './adjuster-loader.component';
+//import {AdjusterLoaderComponent} from './adjuster-loader.component';
 import {AdjusterModule} from './adjuster.module';
 import {AdjusterService} from './adjuster.service';
 
@@ -12,11 +12,11 @@ export const downgradedModule = downgrade(AdjusterModule);
 angular
   .module(downgradedModule, ['hs.core', 'hs.map'])
   .service('AdjusterService', downgradeInjectable(AdjusterService))
-  .directive('praAdjuster', downgradeComponent({component: AdjusterComponent}))
-  .directive(
+  .directive('praAdjuster', downgradeComponent({component: AdjusterComponent}));
+/*.directive(
     'praAdjusterLoader',
     downgradeComponent({component: AdjusterLoaderComponent})
-  );
+  );*/
 
 angular.module('pra.adjuster', [downgradedModule]);
 

+ 8 - 5
src/app.service.ts

@@ -7,6 +7,8 @@ import {Vector as VectorLayer} from 'ol/layer';
 import {Vector as VectorSource} from 'ol/source';
 
 import {HsConfig} from 'hslayers-ng/config.service';
+import {HsEventBusService} from 'hslayers-ng/components/core/event-bus.service';
+import {HsLanguageService} from 'hslayers-ng/components/language/language.service';
 import {HsLayoutService} from 'hslayers-ng/components/layout/layout.service';
 //import {HsMapService} from 'hslayers-ng/components/map/map.service';
 import {HsPanelContainerService} from 'hslayers-ng/components/layout/panels/panel-container.service';
@@ -15,7 +17,6 @@ import {HsSidebarService} from 'hslayers-ng/components/sidebar/sidebar.service';
 import {AdjusterComponent} from './adjuster/adjuster.component';
 import {AdjusterEventService} from './adjuster/adjuster-event.service';
 import {AdjusterService} from './adjuster/adjuster.service';
-import {HsEventBusService} from 'hslayers-ng/components/core/event-bus.service';
 import {nuts} from './nuts';
 
 @Injectable({providedIn: 'root'})
@@ -80,10 +81,11 @@ export class AppService {
     public adjusterEventService: AdjusterEventService,
     public hsConfig: HsConfig,
     public hsEventBus: HsEventBusService,
+    public hsLanguageService: HsLanguageService,
     public hsLayoutService: HsLayoutService,
     //public hsMapService: HsMapService,
-    public hsSidebarService: HsSidebarService,
-    public hsPanelContainerService: HsPanelContainerService
+    public hsPanelContainerService: HsPanelContainerService,
+    public hsSidebarService: HsSidebarService
   ) {
     this.serviceUrl = this.adjusterService.serviceBaseUrl + 'georeport/';
     this.nuts3Layer.set('popUp', {
@@ -116,7 +118,7 @@ export class AppService {
       ],
     });
     this.pilotRegions.set('queryable', false);
-    this.adjusterEventService.clustersLoaded.subscribe(({success}) => {
+    this.adjusterEventService.loaded.subscribe(({success}) => {
       if (success) {
         this.colorPalette = this.generateRandomColorPalette(
           adjusterService.clusters.length
@@ -144,7 +146,8 @@ export class AppService {
       panel: 'adjuster',
       module: 'pra.adjuster',
       order: 0,
-      title: 'Adjust factors',
+      title: () =>
+        this.hsLanguageService.getTranslation('ADJUSTER.adjustFactors'),
       description: 'Adjust factors for computation',
       icon: 'icon-analytics-piechart',
     });

+ 8 - 0
src/attractiveness.config.json

@@ -0,0 +1,8 @@
+{
+  "allowedClusteringMethods": [
+    "haclustwd2",
+    "km50l.cluster"
+  ],
+  "initialWeights": {  },
+  "serviceBaseUrl": "https://jmacura.ml/ws/"
+}