332 lines
13 KiB
TypeScript
332 lines
13 KiB
TypeScript
// 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<Phenomenon[]>(url, params, options);
|
|
}
|
|
|
|
public getStations(apiUrl: string, params?: any, options?: HttpRequestOptions): Observable<Station[]> {
|
|
const url = this.createRequestUrl(apiUrl, 'features'); //https://geomon.geologie.ac.at/52n-sos-webapp/api/features
|
|
let stations = this.requestApi<Station[]>(url, params, options);
|
|
this.messageService.add('StationService: fetched stations');
|
|
return stations;
|
|
}
|
|
|
|
public getFeature(
|
|
id: string,
|
|
apiUrl: string,
|
|
params?: any,
|
|
options?: HttpRequestOptions
|
|
): Observable<Station> {
|
|
const url = this.createRequestUrl(apiUrl, 'features', id);
|
|
return this.requestApi<Station>(url, params, options);
|
|
}
|
|
|
|
public getPlatforms(apiUrl: string, params?: any, options?: HttpRequestOptions): Observable<GeomonPlatform[]> {
|
|
// const url = this.createRequestUrl(apiUrl, 'platforms');
|
|
// return this.requestApi<GeomonPlatform[]>(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<GeomonPlatform> {
|
|
// const url = this.createRequestUrl(apiUrl, 'platforms', id);
|
|
// return this.requestApi<GeomonPlatform>(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<GeomonTimeseries> {
|
|
const url = this.createRequestUrl(apiUrl, 'datasets', id);
|
|
return this.requestApi<GeomonTimeseries>(url, params, options)
|
|
.pipe(
|
|
map((res) => this.prepareDataset(res, apiUrl))
|
|
);
|
|
}
|
|
|
|
getDatasetData(dataset: GeomonDataset, timespan: Timespan, filter: HelgolandDataFilter): Observable<GeomonTimeseriesData> {
|
|
|
|
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<Observable<GeomonTimeseriesData>> = [];
|
|
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<TimeValueTuple>(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<TimeValueTuple>(dataset.id, dataset.url, params)
|
|
.pipe(map(res => this.createTimeseriesData(res)));
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
protected createTimeseriesData(res: Data<TimeValueTuple>): 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<T>(id: string, apiUrl: string, params?: ApiV3DatasetDataFilter): Observable<Data<T>> {
|
|
const url = this.createRequestUrl(apiUrl, 'datasets', `${id}/observations`);
|
|
return this.requestApi<Data<T>>(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<HelgolandData> {
|
|
// const dataFilter = this.createDataFilter(filter);
|
|
// dataFilter.format = 'flot';
|
|
// return this.api.getTsData<TimeValueTuple>(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<T>(
|
|
// id: string,
|
|
// apiUrl: string,
|
|
// timespan: Timespan,
|
|
// params: DataParameterFilter = {},
|
|
// options: HttpRequestOptions
|
|
// ): Observable<Data<T>> {
|
|
// const url = this.createRequestUrl(apiUrl, 'timeseries', id) + '/getData';
|
|
// params.timespan = this.createRequestTimespan(timespan);
|
|
// return this.requestApi<Data<T>>(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>(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<Object> {
|
|
return this.httpService.client().post(url, params, {
|
|
responseType: 'json'
|
|
});
|
|
}
|
|
|
|
protected requestApi<T>(url: string, params: HttpParams = new HttpParams(), options: HttpRequestOptions = {}
|
|
): Observable<T> {
|
|
return this.httpService.client(options).get<T>(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
|
|
}
|