- add "moment": "^2.29.1" and "chartjs-adapter-moment": "^1.0.0",

- add core module for time.service.ts
- add interfaces for timespan.ts an dataset.ts
This commit is contained in:
Arno Kaimbacher 2021-09-28 16:26:53 +02:00
parent 5f657dc9e4
commit 4241bd2cb9
15 changed files with 706 additions and 57 deletions

View file

@ -3,7 +3,7 @@
// 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 } from 'rxjs';
import { Observable, Observer, forkJoin } from 'rxjs';
import { Phenomenon } from '../../shared/models/phenomenon';
import { Station } from '../../shared/models/station';
@ -15,12 +15,64 @@ import { GeomonPlatform } from '../../shared/models/platform';
import { Dataset, GeomonTimeseries } from '../../shared/models/dataset';
import { deserialize } from 'class-transformer';
import { map } from 'rxjs/operators';
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 {
@ -60,7 +112,7 @@ export class DatasetApiService {
return this.getStations(apiUrl, params, options).pipe(map(res => res.map(f => this.createGeomonPlatform(f))));
}
public getPlatform(
id: string,
@ -70,20 +122,157 @@ export class DatasetApiService {
): 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)));
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))
.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;
})
);
}
//#region Helper method
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;
}
private prepareDataset(datasetObj:GeomonTimeseries, apiUrl: string) {
// 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);
@ -97,12 +286,12 @@ export class DatasetApiService {
protected createGeomonPlatform(feature: Station): GeomonPlatform {
const datasetIds = [];
for (const key in feature.properties.datasets) {
if (feature.properties.datasets.hasOwnProperty(key)) {
datasetIds.push(key);
}
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
@ -111,6 +300,12 @@ export class DatasetApiService {
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,
@ -121,6 +316,12 @@ export class DatasetApiService {
);
}
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); }