import {BehaviorSubject} from 'rxjs'; import {HttpClient} from '@angular/common/http'; import {Injectable} from '@angular/core'; import {csv} from 'd3'; import vars2facts from '../assets/data/variables2factors.json'; @Injectable({providedIn: 'root'}) export class SdmDihService { readonly DATA_PATH = window.location.hostname.includes('localhost') ? '../assets/data/normalized_data.csv' : './assets/data/normalized_data.csv'; sdmData; //aggregatedData = {}; firstYear: string; lastYear: string; /** * TODO: fill this in auto-magically */ factors = [ 'aggregated', 'http://www.semanticweb.org/attractiveness/anthropic', 'http://www.semanticweb.org/attractiveness/economic', 'http://www.semanticweb.org/attractiveness/natural', 'http://www.semanticweb.org/attractiveness/social', ]; domains = []; regions = []; scenarios = []; years = []; yearsLoaded = false; dataLoads: BehaviorSubject = new BehaviorSubject(false); constructor(private httpClient: HttpClient) { this.loadData() .then(() => { this.years = Object.keys(this.sdmData); this.yearsLoaded = true; // console.log(this.years); this.firstYear = this.years[0]; this.lastYear = this.years[this.years.length - 1]; this.regions = [ ...new Set( this.sdmData[this.firstYear].map((yearData) => yearData['MODEL']) ), ]; // console.log(this.regions); this.scenarios = [ ...new Set( this.sdmData[this.firstYear].map((yearData) => yearData['SCENARIO']) ), ]; // console.log(this.scenarios); this.domains = [ ...new Set( this.sdmData[this.firstYear].map((yearData) => yearData['DOMAIN']) ), ]; // console.log(this.domains); this.dataLoads.next(true); }) .catch((err) => { console.warn('shit happened', err); }); } /** * For given region, year and scenario calculates its aggregated index, * i.e. the Index of Rural Attractiveness (RAI) by combining its sub-indices * @param regionData - Data for all the factors in a certain region, certain year and certain scenario * @returns Aggregated index for the region, year and scenario */ calcAggregatedIndex(regionData) { let weightedSum = 0; let sumWeight = 0; for (const key of Object.keys(regionData) as any) { if ( key === 'MODEL' || key === 'DOMAIN' || key === 'SCENARIO' || key === 'TIME_STEP' ) { continue; } const factorValues = regionData[key]; let sumValue = 0; let n = 0; for (const ds of factorValues.datasets) { sumValue += ds.value; n += 1; } weightedSum += sumValue * factorValues['weight']; sumWeight += n * factorValues['weight']; } return weightedSum / sumWeight; } /** * For given region, given year, given scenario and single factor calculates its index value (a sub-index) * @param factorData - Data for a single factor in a certain region, certain year and certain scenario * @returns Sub-index value (between 0 and 1) */ calcFactorIndex(factorData) { let sumValue = 0; let n = 0; for (const ds of factorData.datasets) { sumValue += ds.value; n += 1; } return sumValue / n; } async loadData() { const data = await csv(this.DATA_PATH); // Array of objects, each object represents a row, where key is a heading and value is the cell // console.log(data); this.sdmData = {}; for (const row of data) { const timeStep = row['TIME_STEP']; const regionData = this.splitDataByFactor(row); if (!this.sdmData[timeStep]) { this.sdmData[timeStep] = []; } this.sdmData[timeStep].push(regionData); } // console.log(this.sdmData); } perc2color(perc: number): string { perc = perc * 100; let r; let g; const b = 0; if (perc < 50) { r = 255; g = Math.round(5.1 * perc); } else { g = 255; r = Math.round(510 - 5.1 * perc); } // eslint-disable-next-line @typescript-eslint/no-unused-vars const h = r * 0x10000 + g * 0x100 + b * 0x1; return `rgba(${r}, ${g}, ${b}, 1)`; } /** * Change format of each input row into a two-level structure where datasets are classified by factors * @param inputRegionData - row as read by the CSV reader * @returns data for one region in one point in time */ splitDataByFactor(inputRegionData) { const regionData = {}; for (const dataset of Object.keys(inputRegionData)) { const datasetValue = inputRegionData[dataset]; if (['MODEL', 'DOMAIN', 'SCENARIO', 'TIME_STEP'].includes(dataset)) { // just copy the MODEL, DOMAIN, SCENARIO and TIME_STEP regionData[dataset] = datasetValue; continue; } const factor = vars2facts[dataset]; // when no factor is defined for the dataset, then skip this dataset if (!factor) { continue; } if (!regionData[factor]) { regionData[factor] = { weight: 1, //TODO: Allow to change weight of factors? datasets: [], }; } regionData[factor].datasets.push({ name: dataset, value: parseFloat(datasetValue), }); } for (const key of Object.keys(regionData)) { if ( key === 'MODEL' || key === 'DOMAIN' || key === 'SCENARIO' || key === 'TIME_STEP' ) { continue; } const values = regionData[key]; if (values === undefined || values.datasets === undefined) { console.warn('no data for', values, regionData, key); } const index = this.calcFactorIndex(values); values['index'] = index; } const aggregated = this.calcAggregatedIndex(regionData); regionData['aggregated'] = aggregated; return regionData; } }