adjuster.service.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. import {HttpClient} from '@angular/common/http';
  2. import {Injectable} from '@angular/core';
  3. import {Vector as VectorLayer} from 'ol/layer';
  4. import {HsConfig} from 'hslayers-ng/config.service';
  5. import {HsLayerManagerMetadataService} from 'hslayers-ng/components/layermanager/layermanager-metadata.service';
  6. import {HsLayerManagerService} from 'hslayers-ng/components/layermanager';
  7. import {HsUtilsService} from 'hslayers-ng/components/utils/utils.service';
  8. import attractivenessConfig from '../attractiveness.config.json';
  9. import clusteringMethods from '../data/clustering_methods.json';
  10. import {AdjusterEventService} from './adjuster-event.service';
  11. import {obce, obceIndexLayer, osmLayer} from '../app.config';
  12. @Injectable({providedIn: 'root'})
  13. export class AdjusterService {
  14. /** To be read from a config file */
  15. allowedClusteringMethods: Array<string>;
  16. /** To be read from a config file */
  17. initialWeights;
  18. /** To be read from a config file */
  19. serviceBaseUrl: string;
  20. /** Used in the UI as a selector */
  21. allowClusters = true;
  22. /** Used in the UI as a selector */
  23. allowIndex = true;
  24. factors = [];
  25. numberOfClusters: number;
  26. //method: string;
  27. methods: Array<{
  28. codename: string;
  29. layer?: VectorLayer;
  30. name: string;
  31. type: string;
  32. }>;
  33. private _clusteringInProcess: boolean;
  34. private _clustersLoaded: boolean;
  35. private _loadInProcess: boolean;
  36. private _raiInProcess: boolean;
  37. constructor(
  38. public adjusterEventService: AdjusterEventService,
  39. public hsConfig: HsConfig,
  40. public hsLayerMetadataService: HsLayerManagerMetadataService,
  41. public hsLayerManagerService: HsLayerManagerService,
  42. public hsUtilsService: HsUtilsService,
  43. public $http: HttpClient
  44. ) {
  45. // First safely set configurable properties
  46. this.allowedClusteringMethods =
  47. attractivenessConfig?.allowedClusteringMethods ?? [];
  48. this.initialWeights = attractivenessConfig?.initialWeights ?? {};
  49. this.serviceBaseUrl =
  50. attractivenessConfig?.serviceBaseUrl ??
  51. 'https://publish.lesprojekt.cz/nodejs/';
  52. // 'https://jmacura.ml/ws/' // 'http://localhost:3000/'
  53. this.methods = clusteringMethods.filter((m) =>
  54. this.allowedClusteringMethods.includes(m.codename)
  55. );
  56. this.numberOfClusters = 9;
  57. }
  58. /**
  59. * Sends a request to polirural-attractiveness-service
  60. * and applies the returned values
  61. */
  62. apply(): void {
  63. if (this.allowIndex) {
  64. this.calculateIndex();
  65. }
  66. if (this.allowClusters) {
  67. this.calculateClusters();
  68. }
  69. }
  70. calculateIndex(): void {
  71. this._raiInProcess = true;
  72. this.$http
  73. .post(this.serviceBaseUrl + 'cz/scores/', {
  74. factors: this.factors.map((f) => {
  75. return {
  76. factor: f.name,
  77. weight: f.weight,
  78. datasets: f.datasets
  79. .filter((ds) => ds.included)
  80. .map((ds) => ds.name),
  81. };
  82. }),
  83. })
  84. .toPromise()
  85. .then((attractivenessData: any[]) => {
  86. console.log(attractivenessData);
  87. // Spread the 'aggregate' value between 0 and 1
  88. const min = attractivenessData.reduce((a, b) =>
  89. a.aggregate < b.aggregate ? a : b
  90. ).aggregate;
  91. const max = attractivenessData.reduce((a, b) =>
  92. a.aggregate > b.aggregate ? a : b
  93. ).aggregate;
  94. const coefficient = 1 / (max - min);
  95. const constant = -min * coefficient;
  96. attractivenessData.forEach((a) => {
  97. a.aggregate *= coefficient;
  98. a.aggregate += constant;
  99. });
  100. // Store relation between region and its data in a hash-table-like structure
  101. // more memory consuming, but faster then find()
  102. const codeRecordRelations = {};
  103. attractivenessData.forEach((a) => {
  104. codeRecordRelations[a.code] = a;
  105. });
  106. let errs = 0;
  107. //let logs = 0;
  108. console.time('forEach-Index');
  109. obce.forEachFeature((feature) => {
  110. // Pair each feature with its attractivity data
  111. const featureData = codeRecordRelations[feature.get('nationalCode')];
  112. if (!featureData) {
  113. if (errs < 20) {
  114. errs++;
  115. console.warn(
  116. `No data for feature ${feature.get('nationalCode')}`
  117. );
  118. console.log(feature);
  119. }
  120. return;
  121. }
  122. /*logs++;
  123. if (logs % 100 == 0) {
  124. console.log(`processed ${logs} items`);
  125. }*/
  126. Object.keys(featureData).forEach((key, index) => {
  127. if (key !== 'lau2') {
  128. feature.set(key, featureData[key], true); //true stands for "silent" - important for performance!
  129. }
  130. });
  131. });
  132. // Since we are updating the features silently, we now have to refresh manually
  133. obce.getFeatures()[0].dispatchEvent('change');
  134. console.timeEnd('forEach-Index');
  135. this._raiInProcess = false;
  136. this.adjusterEventService.loaded.next({
  137. success: true,
  138. type: 'index',
  139. });
  140. })
  141. .catch((error) => {
  142. console.warn(`Error obtaining data from ${this.serviceBaseUrl}.`);
  143. console.log(error);
  144. this._raiInProcess = false;
  145. this.adjusterEventService.loaded.next({
  146. success: true,
  147. type: 'index',
  148. err: error,
  149. });
  150. });
  151. }
  152. calculateClusters(): void {
  153. this._clusteringInProcess = true;
  154. this.$http
  155. .post(this.serviceBaseUrl + 'cz/clusters/', {
  156. numberOfClusters: this.numberOfClusters,
  157. factors: this.factors.map((f) => {
  158. return {
  159. factor: f.name,
  160. weight: f.weight,
  161. datasets: f.datasets
  162. .filter((ds) => ds.included)
  163. .map((ds) => ds.name),
  164. };
  165. }),
  166. })
  167. .toPromise()
  168. .then((data: any) => {
  169. console.log('data received', data);
  170. let logs = 0;
  171. let errs = 0;
  172. const clusterData = data.response;
  173. console.log(obceIndexLayer);
  174. /*let sublayers = [];
  175. const oldSublayers = obceLayer.get('Layer');
  176. if (oldSublayers !== undefined && Array.isArray(oldSublayers)) {
  177. for (const sublyr of oldSublayers) {
  178. sublyr.getSource().clear();
  179. }
  180. if (oldSublayers.length > this.numberOfClusters) {
  181. sublayers = oldSublayers.slice(0, this.numberOfClusters);
  182. }
  183. }
  184. let i = sublayers.length;
  185. while (sublayers.length < this.numberOfClusters) {
  186. const sublyr = new VectorLayer({
  187. title: `Cluster ${i + 1}`,
  188. name: i + 1 + '',
  189. source: new VectorSource({}),
  190. });
  191. sublayers.push(sublyr);
  192. sublyr.Title = `Cluster ${i + 1}`;
  193. sublyr.Name = `c${i + 1}`;
  194. i++;
  195. }*/
  196. console.time('forEach-Cluster');
  197. obce.forEachFeature((feature) => {
  198. // Pair each feature with its clustering data
  199. const featureData = clusterData.find(
  200. // NOTE: Do NOT add triple equal sign!
  201. (item) => item['lau2'] == feature.get('nationalCode')
  202. );
  203. if (!featureData) {
  204. if (errs < 20) {
  205. errs++;
  206. console.warn(
  207. `No data for feature ${feature.get('nationalCode')}`
  208. );
  209. console.log(feature);
  210. }
  211. return;
  212. }
  213. logs++;
  214. if (logs % 100 == 0) {
  215. console.log(`processed ${logs} items`);
  216. }
  217. Object.keys(featureData).forEach(function (key, index) {
  218. if (key !== 'lau2') {
  219. feature.set(key, featureData[key], true);
  220. }
  221. });
  222. });
  223. //const clusters = [];
  224. //const obceFeatures: Array<any> = obce.getFeatures();
  225. /*for (const region of clusterData) {
  226. if (!clusters.includes(region[this.method])) {
  227. clusters.push(region[this.method]);
  228. }
  229. const feature = obceFeatures.find(
  230. // NOTE: Do NOT add triple equal sign!
  231. (f) => f.get('nationalCode') == region['lau2']
  232. );
  233. if (!feature) {
  234. if (errs < 20) {
  235. errs++;
  236. console.warn(`No feature matches region ${region['lau2']}`);
  237. console.log(region);
  238. }
  239. continue;
  240. }
  241. logs++;
  242. if (logs % 100 == 0) {
  243. console.log(`processed ${logs} items`);
  244. }
  245. Object.keys(region).forEach(function (key, index) {
  246. if (key !== 'lau2') {
  247. feature.set(key, region[key], true);
  248. }
  249. });
  250. if (logs % 100 == 0) {
  251. //sublayers[0].getSource().addFeature(feature);
  252. }
  253. //sublayers[region[this.method] - 1].getSource().addFeature(feature);
  254. }*/
  255. //obceLayer.set('Layer', sublayers);
  256. //this.hsLayerMetadataService.fillMetadata(obceLayer);
  257. //console.log(sublayers[0].getSource().getFeatures());
  258. obce.getFeatures()[0].dispatchEvent('change');
  259. console.timeEnd('forEach-Cluster');
  260. console.log('clustering done!');
  261. //this.clusters = clusters;
  262. this._clustersLoaded = true;
  263. this._clusteringInProcess = false;
  264. this.adjusterEventService.loaded.next({
  265. success: true,
  266. type: 'clusters',
  267. });
  268. })
  269. .catch((error) => {
  270. console.warn(`Error obtaining data from ${this.serviceBaseUrl}.`);
  271. console.log(error);
  272. this._clusteringInProcess = false;
  273. this.adjusterEventService.loaded.next({
  274. success: false,
  275. type: 'clusters',
  276. err: error,
  277. });
  278. });
  279. }
  280. init(): void {
  281. this._loadInProcess = true;
  282. this.$http
  283. .get(this.serviceBaseUrl + 'cz/datasets/')
  284. .toPromise()
  285. .then((data: any) => {
  286. this.factors = data.map((dataset) => {
  287. return {
  288. name: dataset.Factor,
  289. weight: this.initialWeights[dataset.Factor] ?? 1,
  290. datasets: [],
  291. };
  292. });
  293. this.factors = this.hsUtilsService.removeDuplicates(
  294. this.factors,
  295. 'name'
  296. );
  297. this.factors.forEach((factor) => {
  298. factor.datasets = data
  299. .filter((ds) => ds.Factor === factor.name)
  300. .map((ds) => {
  301. return {
  302. name: ds.Name,
  303. desc: ds.Description,
  304. included: true,
  305. };
  306. });
  307. });
  308. this._loadInProcess = false;
  309. this.apply();
  310. this.hsLayerManagerService.setGreyscale(osmLayer);
  311. })
  312. .catch((error) => {
  313. console.warn(`Web service at ${this.serviceBaseUrl} unavailable!`);
  314. console.log(error);
  315. this._loadInProcess = false;
  316. });
  317. }
  318. clustersLoaded(): boolean {
  319. return this._clustersLoaded;
  320. }
  321. /**
  322. * @returns {boolean} true if clustering or index processing is in process or loading data, false otherwise
  323. */
  324. isInProcess(): boolean {
  325. return (
  326. this._loadInProcess || this._clusteringInProcess || this._raiInProcess
  327. );
  328. }
  329. isLoading(): boolean {
  330. return this._loadInProcess;
  331. }
  332. isClustering(): boolean {
  333. return this._clusteringInProcess;
  334. }
  335. isCalculatingRAI(): boolean {
  336. return this._raiInProcess;
  337. }
  338. }