calculator.service.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import {HttpClient} from '@angular/common/http';
  2. import {Injectable} from '@angular/core';
  3. import {NgbCalendar, NgbDateStruct} from '@ng-bootstrap/ng-bootstrap';
  4. import {Subject} from 'rxjs';
  5. import {catchError} from 'rxjs/operators';
  6. import {
  7. HsCompositionsParserService,
  8. HsConfig,
  9. HsEventBusService,
  10. HsLanguageService,
  11. HsMapService,
  12. HsToastService,
  13. } from 'hslayers-ng';
  14. import {FcFieldService} from './field.service';
  15. import {FcZonesService} from './zones.service';
  16. import {imageWmsTLayer, imageWmsTSource} from './image-wms-t-layer';
  17. /**
  18. * Set of precalculated indices
  19. */
  20. const INDICES_PRE_FLIGHT = ['EVI', 'NDVI', 'RVI4S1'] as const;
  21. /**
  22. * Set of indices which are calculated on the fly
  23. */
  24. const INDICES_ON_THE_FLY = ['NDWI', 'NDTI', 'NDRE'] as const;
  25. export type IndexPreFlight = typeof INDICES_PRE_FLIGHT[number];
  26. export type IndexOnTheFly = typeof INDICES_ON_THE_FLY[number];
  27. export type Index = IndexPreFlight | IndexOnTheFly;
  28. @Injectable({providedIn: 'root'})
  29. export class FcCalculatorService {
  30. AVAILABLE_PRODUCTS = [...INDICES_PRE_FLIGHT, ...INDICES_ON_THE_FLY].sort();
  31. BLUR_MIN_VALUE = 1 as const;
  32. BLUR_MAX_VALUE = 5 as const;
  33. MIN_LPIS_VISIBLE_ZOOM = 15 as const;
  34. SERVICE_BASE_URL = 'https://fieldcalc.lesprojekt.cz/' as const;
  35. availableDates: Array<string>;
  36. blurValue = 1;
  37. dateRangeSelects: Subject<{date: string}> = new Subject();
  38. dateCalendarSelects: Subject<{date: string}> = new Subject();
  39. lpisLoading = false;
  40. quantileCount = 4;
  41. selectedDate: string;
  42. selectedDateCalendar: NgbDateStruct;
  43. viewChanges: Subject<any> = new Subject();
  44. lastError = '';
  45. //selectedProduct;
  46. private _datesLoading: boolean;
  47. private _zonesLoading: boolean;
  48. constructor(
  49. private fieldService: FcFieldService,
  50. private hsCompositionsParserService: HsCompositionsParserService,
  51. private hsConfig: HsConfig,
  52. private hsEventBus: HsEventBusService,
  53. private hsLanguageService: HsLanguageService,
  54. private hsMapService: HsMapService,
  55. private hsToastService: HsToastService,
  56. private httpClient: HttpClient,
  57. private zonesService: FcZonesService,
  58. private calendar: NgbCalendar
  59. ) {
  60. this.dateRangeSelects.subscribe(({date}) => {
  61. this.selectedDate = date;
  62. });
  63. /**
  64. * When new field is selected, clear all other params
  65. */
  66. this.fieldService.fieldSelects.subscribe(() => {
  67. this.availableDates = undefined;
  68. this.selectedDate = undefined;
  69. });
  70. this.hsEventBus.olMapLoads.subscribe((map) => {
  71. map.map.getView().on('change:resolution', (evt) => {
  72. this.viewChanges.next(evt.target);
  73. });
  74. });
  75. }
  76. noDates(): boolean {
  77. return this.availableDates === undefined;
  78. }
  79. /**
  80. * Call 'get_dates' API method
  81. */
  82. async getDates({product}: {product: Index}) {
  83. this.availableDates = undefined;
  84. this.selectedDateCalendar = null;
  85. this._datesLoading = true;
  86. try {
  87. const data = await this.httpClient
  88. .get<{dates: string[]}>(
  89. (this.proxyEnabled() ? this.hsConfig.apps.default.proxyPrefix : '') +
  90. this.SERVICE_BASE_URL +
  91. 'get_dates?' +
  92. 'product=' +
  93. product +
  94. '&centroid=' +
  95. JSON.stringify(this.fieldService.getSelectedFieldCentroid())
  96. )
  97. .toPromise();
  98. this._datesLoading = false;
  99. console.log('data received!', data);
  100. this.availableDates = data.dates;
  101. /* Any previously selected date must be cleaned up
  102. * so it won't get sent to the API as a wrong param
  103. */
  104. this.selectedDate = undefined;
  105. } catch (err) {
  106. this._datesLoading = false;
  107. this.hsToastService.createToastPopupMessage(
  108. this.hsLanguageService.getTranslationIgnoreNonExisting(
  109. 'CALCULATOR',
  110. 'errorLoading'
  111. ),
  112. this.hsLanguageService.getTranslationIgnoreNonExisting(
  113. 'CALCULATOR',
  114. 'errorLoadingDates'
  115. ),
  116. {
  117. toastStyleClasses: 'bg-warning text-dark',
  118. }
  119. );
  120. console.error('Somethin fucked up!');
  121. console.log(err);
  122. }
  123. }
  124. get datesLoading() {
  125. return this._datesLoading;
  126. }
  127. get zonesLoading() {
  128. return this._zonesLoading;
  129. }
  130. /**
  131. * Call 'get_zones' or 'get_zones_exp' API method
  132. */
  133. async getZones({product}: {product: Index}) {
  134. this.lastError = '';
  135. this._zonesLoading = true;
  136. const endpoint = INDICES_PRE_FLIGHT.includes(product as IndexPreFlight)
  137. ? 'get_zones'
  138. : 'get_zones_exp';
  139. try {
  140. const data = await this.httpClient
  141. .post(
  142. (this.proxyEnabled() ? this.hsConfig.apps.default.proxyPrefix : '') +
  143. this.SERVICE_BASE_URL +
  144. endpoint,
  145. {
  146. product,
  147. number_of_quantiles: this.quantileCount,
  148. blur: this.blurValue,
  149. date: this.selectedDate,
  150. format: 'geojson',
  151. geojson: this.fieldService.getSelectedFieldGeoJSON(),
  152. },
  153. {
  154. headers: {
  155. 'Content-Type': 'application/json',
  156. 'Accept': 'application/json',
  157. },
  158. }
  159. )
  160. .toPromise();
  161. console.log('data received!', data);
  162. // in case of unreliable data the return value is {"error": "data is not reliable."}
  163. // TODO: show this error message to user
  164. if (data['error']) {
  165. throw new Error(data['error']);
  166. }
  167. this._zonesLoading = false;
  168. await this.zonesService.updateZones(data, {
  169. quantileCount: this.quantileCount,
  170. });
  171. } catch (err) {
  172. const errLoadingZones =
  173. this.hsLanguageService.getTranslationIgnoreNonExisting(
  174. 'CALCULATOR',
  175. 'errorLoadingZones'
  176. );
  177. this._zonesLoading = false;
  178. this.hsToastService.createToastPopupMessage(
  179. this.hsLanguageService.getTranslationIgnoreNonExisting(
  180. 'CALCULATOR',
  181. 'errorLoading'
  182. ),
  183. errLoadingZones,
  184. {
  185. toastStyleClasses: 'bg-warning text-dark',
  186. }
  187. );
  188. this.lastError = errLoadingZones;
  189. console.error('Somethin fucked up!');
  190. console.log(err);
  191. }
  192. }
  193. /**
  194. * Updates WMS-t layer with the source image
  195. * @param date - ISO date
  196. */
  197. updateImageBackground(date: string) {
  198. const isoTime = date.split('T')[0].replace(/-/g, '');
  199. imageWmsTSource.updateParams({time: isoTime});
  200. imageWmsTLayer.setVisible(true);
  201. }
  202. /**
  203. * Adds all layers from a provided composition to the map, optionally into a provided folder
  204. * @param compositionLink - URL of the composition record
  205. * @param path - Folder name
  206. */
  207. addLayersFromComposition(compositionLink: string, {path}) {
  208. const composition = []; //TODO: get composition object from compositionLink URL
  209. if (path) {
  210. for (const layer of composition) {
  211. //TODO: add path
  212. }
  213. }
  214. }
  215. private proxyEnabled(): boolean {
  216. return (
  217. this.hsConfig.apps.default.useProxy === undefined ||
  218. this.hsConfig.apps.default.useProxy
  219. );
  220. }
  221. }