// https://github.com/52North/helgoland-toolbox/blob/develop/libs/core/src/lib/dataset-api/dataset-impl-api-interface.service.ts // https://github.com/52North/helgoland-toolbox/blob/495f75cadcc3e7232206db1cd5dd8bbf3a172c4b/libs/core/src/lib/abstract-services/api-interface.ts#L6 // https://github.com/52North/helgoland-toolbox/blob/495f75cadcc3e7232206db1cd5dd8bbf3a172c4b/libs/core/src/lib/api-communication/connectors/dataset-api-v3-connector/api-v3-interface.ts#L321 import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable, Observer, forkJoin } from 'rxjs'; import { Phenomenon } from '../../shared/models/phenomenon'; import { Station } from '../../shared/models/station'; import { HttpService } from './http.service'; import { HttpRequestOptions } from '../../shared/models/http-requests'; import { HttpParams } from '@angular/common/http'; import { MessageService } from './message.service'; import { GeomonPlatform } from '../../shared/models/platform'; import { Dataset, GeomonTimeseries } from '../../shared/models/dataset'; import { deserialize } from 'class-transformer'; import { map } from 'rxjs/operators'; import { InternalIdHandler } from '../../common/components/services/internal-id-handler.service'; import moment from "moment"; import { Timespan } from '../../shared/models/timespan'; import { GeomonDataset } from '../../shared/models/dataset'; import { GeomonTimeseriesData, Data, HelgolandDataFilter } from '../../shared/models/dataset'; // @Injectable({ // providedIn: 'root' // }) export interface ParameterFilter { service?: string; category?: string; offering?: string; phenomenon?: string; procedure?: string; feature?: string; valueTypes?: string; platformTypes?: string; expanded?: boolean; lang?: string; [key: string]: any; } export interface ApiV3DatasetDataFilter { timespan?: string; generalize?: boolean; format?: string; unixTime?: boolean; expanded?: boolean; } type TimeValueTuple = [number, number]; class UriParameterCoder { public encodeKey(key: string): string { return encodeURIComponent(key); } public encodeValue(value: string): string { return encodeURIComponent(value); } public decodeKey(key: string): string { return key; } public decodeValue(value: string): string { return value; } } @Injectable() export class DatasetApiService { // constructor() { } constructor( private httpClient: HttpClient, protected httpService: HttpService, private messageService: MessageService, protected internalDatasetId: InternalIdHandler,) { } public getPhenomena(apiUrl: string, params?: any, options?: any) { const url = this.createRequestUrl(apiUrl, 'phenomena'); return this.requestApi(url, params, options); } public getStations(apiUrl: string, params?: any, options?: HttpRequestOptions): Observable { const url = this.createRequestUrl(apiUrl, 'features'); //https://geomon.geologie.ac.at/52n-sos-webapp/api/features let stations = this.requestApi(url, params, options); this.messageService.add('StationService: fetched stations'); return stations; } public getFeature( id: string, apiUrl: string, params?: any, options?: HttpRequestOptions ): Observable { const url = this.createRequestUrl(apiUrl, 'features', id); return this.requestApi(url, params, options); } public getPlatforms(apiUrl: string, params?: any, options?: HttpRequestOptions): Observable { // const url = this.createRequestUrl(apiUrl, 'platforms'); // return this.requestApi(url, params, options); return this.getStations(apiUrl, params, options).pipe(map(res => res.map(f => this.createGeomonPlatform(f)))); } public getPlatform( id: string, apiUrl: string, params?: any, options?: HttpRequestOptions ): Observable { // const url = this.createRequestUrl(apiUrl, 'platforms', id); // return this.requestApi(url, params, options); return this.getFeature(id, apiUrl, params, options).pipe(map(res => this.createGeomonPlatform(res))); } public getDataset(id: string, apiUrl: string, params?: any, options?: HttpRequestOptions): Observable { const url = this.createRequestUrl(apiUrl, 'datasets', id); return this.requestApi(url, params, options) .pipe( map((res) => this.prepareDataset(res, apiUrl)) ); } getDatasetData(dataset: GeomonDataset, timespan: Timespan, filter: HelgolandDataFilter): Observable { const maxTimeExtent = moment.duration(1, 'year').asMilliseconds();//31536000000 const params: ApiV3DatasetDataFilter = { format: 'flot' }; if (filter.expanded !== undefined) { params.expanded = filter.expanded }; if (filter.generalize !== undefined) { params.generalize = filter.generalize }; // if greater than one year if ((timespan.to - timespan.from) > maxTimeExtent) { const requests: Array> = []; let start = moment(timespan.from).startOf('year'); let end = moment(timespan.from).endOf('year'); while (start.isBefore(moment(timespan.to))) { const chunkSpan = new Timespan(start.unix() * 1000, end.unix() * 1000); params.timespan = this.createRequestTimespan(chunkSpan); requests.push( this.getApiData(dataset.id, dataset.url, params) .pipe(map(res => { return this.createTimeseriesData(res); // return res; })) ); start = end.add(1, 'millisecond'); end = moment(start).endOf('year'); } return forkJoin(requests).pipe(map((e) => { const mergedResult = e.reduce((previous, current) => { const next: GeomonTimeseriesData = new GeomonTimeseriesData(previous.values.concat(current.values)); if (previous.valueBeforeTimespan) { next.valueBeforeTimespan = previous.valueBeforeTimespan; } if (current.valueAfterTimespan) { next.valueAfterTimespan = current.valueAfterTimespan; } for (const key in previous.referenceValues) { if (previous.referenceValues.hasOwnProperty(key)) { next.referenceValues[key] = { values: previous.referenceValues[key].values.concat(current.referenceValues[key].values) }; if (previous.referenceValues[key].valueBeforeTimespan) { next.referenceValues[key].valueBeforeTimespan = previous.referenceValues[key].valueBeforeTimespan; } if (current.referenceValues[key].valueAfterTimespan) { next.referenceValues[key].valueAfterTimespan = current.referenceValues[key].valueAfterTimespan; } } } return next; }); if (mergedResult.values && mergedResult.values.length > 0) { // cut first const fromIdx = mergedResult.values.findIndex(el => el[0] >= timespan.from); mergedResult.values = mergedResult.values.slice(fromIdx); // cut last const toIdx = mergedResult.values.findIndex(el => el[0] >= timespan.to); if (toIdx >= 0) { mergedResult.values = mergedResult.values.slice(0, toIdx + 1); } } return mergedResult; })); } else { params.timespan = this.createRequestTimespan(timespan); if (filter.expanded !== undefined) { params.expanded = filter.expanded }; return this.getApiData(dataset.id, dataset.url, params) .pipe(map(res => this.createTimeseriesData(res))); } } protected createTimeseriesData(res: Data): GeomonTimeseriesData { const data = new GeomonTimeseriesData(res.values); data.referenceValues = res.referenceValues ? res.referenceValues : {}; if (res.valueBeforeTimespan) { data.valueBeforeTimespan = res.valueBeforeTimespan; } if (res.valueAfterTimespan) { data.valueAfterTimespan = res.valueAfterTimespan; } return data; } public getApiData(id: string, apiUrl: string, params?: ApiV3DatasetDataFilter): Observable> { const url = this.createRequestUrl(apiUrl, 'datasets', `${id}/observations`); return this.requestApi>(url, this.prepareParams(params)).pipe( map(res => { // if (params.expanded) { res = res[id]; } return res; }) ); } protected prepareParams(params: any): HttpParams { let httpParams = new HttpParams({ encoder: new UriParameterCoder() }); if (params) { Object.getOwnPropertyNames(params).forEach((key) => { if (params[key] instanceof Array) { httpParams = httpParams.set(key, params[key].join(',')); } else { httpParams = httpParams.set(key, params[key]); } }); } return httpParams; } // getDatasetData(dataset: GeomonDataset, timespan: Timespan, filter: HelgolandDataFilter): Observable { // const dataFilter = this.createDataFilter(filter); // dataFilter.format = 'flot'; // return this.api.getTsData(dataset.id, dataset.url, timespan, dataFilter).pipe(map(res => { // const data = new HelgolandTimeseriesData(res.values); // data.referenceValues = res.referenceValues ? res.referenceValues : {}; // if (res.valueBeforeTimespan) { data.valueBeforeTimespan = res.valueBeforeTimespan; } // if (res.valueAfterTimespan) { data.valueAfterTimespan = res.valueAfterTimespan; } // return data; // })); // } // public getTsData( // id: string, // apiUrl: string, // timespan: Timespan, // params: DataParameterFilter = {}, // options: HttpRequestOptions // ): Observable> { // const url = this.createRequestUrl(apiUrl, 'timeseries', id) + '/getData'; // params.timespan = this.createRequestTimespan(timespan); // return this.requestApi>(url, params, options).pipe( // map((res: any) => { // if (params.expanded) { res = res[id]; } // return res; // })); // } //#region Helper method private prepareDataset(datasetObj: GeomonTimeseries, apiUrl: string) { let dataset = deserialize(GeomonTimeseries, JSON.stringify(datasetObj)); dataset.url = apiUrl; this.internalDatasetId.generateInternalId(dataset); // if (dataset.seriesParameters) { // dataset.parameters = dataset.seriesParameters; // delete dataset.seriesParameters; // } return dataset; } protected createGeomonPlatform(feature: Station): GeomonPlatform { const datasetIds = []; for (const key in feature.properties.datasets) { if (feature.properties.datasets.hasOwnProperty(key)) { datasetIds.push(key); } } return new GeomonPlatform(feature.id, feature.properties.label, datasetIds, feature.geometry); } protected createRequestUrl(apiUrl: string, endpoint: string, id?: string) { // TODO Check whether apiUrl ends with slash let requestUrl = apiUrl + endpoint; if (id) { requestUrl += '/' + id; } return requestUrl; } private requestApiTextedPost(url: string, params: ParameterFilter = {}, options: HttpRequestOptions = {}): Observable { return this.httpService.client().post(url, params, { responseType: 'json' }); } protected requestApi(url: string, params: HttpParams = new HttpParams(), options: HttpRequestOptions = {} ): Observable { return this.httpService.client(options).get(url, { params, headers: this.createBasicAuthHeader(options.basicAuthToken as string) } ); } protected createRequestTimespan(timespan: Timespan): string { return encodeURI(moment(timespan.from).format() + '/' + moment(timespan.to).format()); } protected createBasicAuthHeader(token: string): HttpHeaders { const headers = new HttpHeaders(); if (token) { return headers.set('Authorization', token); } return headers; } //#endregion }