2020-02-04 12:54:53 +00:00
|
|
|
import {Injectable} from '@angular/core';
|
|
|
|
import {HttpClient} from '@angular/common/http';
|
|
|
|
import {AppConfig} from '../shared/app.config';
|
|
|
|
import {Observable} from 'rxjs';
|
|
|
|
import {WeatherCurrentObservation} from '../models/weatherCurrentObservation';
|
2020-07-21 14:57:11 +00:00
|
|
|
import {DatePipe} from '@angular/common';
|
|
|
|
import {WeatherData} from '../models/WeatherData';
|
|
|
|
import {GeoJSON} from 'ol/format';
|
|
|
|
import {map, switchMap} from 'rxjs/operators';
|
|
|
|
import {getCenter} from 'ol/extent';
|
2020-07-21 15:06:58 +00:00
|
|
|
import {IItem} from '../models/item';
|
2020-02-04 12:54:53 +00:00
|
|
|
|
|
|
|
@Injectable({
|
|
|
|
providedIn: 'root',
|
|
|
|
})
|
|
|
|
export class WeatherService {
|
2020-07-21 14:57:11 +00:00
|
|
|
private apiCurrentObservationUrl = 'currentobservation';
|
|
|
|
private apiObservation = 'observation';
|
|
|
|
private apiForecast = 'forecast';
|
2020-02-04 12:54:53 +00:00
|
|
|
|
2020-07-21 14:57:11 +00:00
|
|
|
private format: GeoJSON;
|
|
|
|
|
|
|
|
constructor(public httpClient: HttpClient, public appConfig: AppConfig, private datePipe: DatePipe) {
|
|
|
|
this.format = new GeoJSON();
|
2020-02-04 12:54:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public GetCurrentObservation(centroid: number[]): Observable<WeatherCurrentObservation> {
|
2020-02-04 16:38:04 +00:00
|
|
|
const endpoint = this.appConfig.getConfig('weatherApiEndPoint');
|
|
|
|
const apiKey = this.appConfig.getConfig('weatherApiKey');
|
2020-07-21 14:57:11 +00:00
|
|
|
const observationUrl = `${endpoint}${this.apiCurrentObservationUrl}/?c=${centroid[0]},${centroid[1]}&key=${apiKey}`;
|
2020-02-04 12:54:53 +00:00
|
|
|
|
|
|
|
return this.httpClient.get<WeatherCurrentObservation>(observationUrl);
|
|
|
|
}
|
2020-07-21 14:57:11 +00:00
|
|
|
|
|
|
|
public getWeatherRangeForItem(item: IItem, daysBefore = 10, daysAfter = 10): Observable<WeatherData[]> {
|
|
|
|
const geometry = this.format.readGeometry(item.geometry);
|
|
|
|
const centroid = getCenter(geometry.getExtent());
|
|
|
|
|
|
|
|
const currentDate = new Date(Date.now());
|
|
|
|
const sd = new Date(Date.now());
|
|
|
|
const ed = new Date(Date.now());
|
|
|
|
sd.setDate((currentDate.getDate() - daysBefore));
|
|
|
|
ed.setDate((currentDate.getDate() + daysAfter));
|
|
|
|
|
|
|
|
return this.getWeatherRange(centroid, this.datePipe.transform(sd, 'yyyy-MM-ddThh:mm:ss'),
|
|
|
|
this.datePipe.transform(ed, 'yyyy-MM-ddThh:mm:ss'));
|
|
|
|
}
|
|
|
|
|
|
|
|
public getWeatherRange(centroid: number[], startDate: string, endDate: string): Observable<WeatherData[]> {
|
|
|
|
const endpoint = this.appConfig.getConfig('weatherApiEndPoint');
|
|
|
|
const apiKey = this.appConfig.getConfig('weatherApiKey');
|
|
|
|
|
|
|
|
// weather does not support UTC format, also remove Z
|
|
|
|
const sd = encodeURIComponent(this.removeUTCZ(startDate));
|
|
|
|
const ed = encodeURIComponent(this.removeUTCZ(endDate));
|
|
|
|
|
2020-07-22 21:04:06 +00:00
|
|
|
const historical = `${endpoint}${this.apiObservation}/?c=${centroid[0]},${centroid[1]}&sd=${sd}&ed=${ed}&t=observation&interval=hourly&key=${apiKey}`;
|
|
|
|
const forecast = `${endpoint}${this.apiForecast}/?c=${centroid[0]},${centroid[1]}&interval=hourly&key=${apiKey}`;
|
2020-07-21 14:57:11 +00:00
|
|
|
|
|
|
|
return this.httpClient.get<any[]>(historical).pipe(
|
2020-07-22 21:04:06 +00:00
|
|
|
map(h => h.map(d => ({...d, rain: d.rainPastHour}))),
|
2020-07-21 14:57:11 +00:00
|
|
|
switchMap(h => {
|
|
|
|
return this.httpClient.get<any[]>(forecast)
|
|
|
|
.pipe(
|
2020-07-22 21:04:06 +00:00
|
|
|
map(f => this.hourlyToDaily([...h, ...f.filter(fd => fd.time <= endDate)])
|
|
|
|
));
|
2020-07-21 14:57:11 +00:00
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-07-22 21:04:06 +00:00
|
|
|
private hourlyToDaily(hourlyWeatherData: any[]): WeatherData[] {
|
|
|
|
const days = this.groupBy(hourlyWeatherData, hi => hi.time.split('T')[0]);
|
|
|
|
return Object.entries(days)
|
|
|
|
.reduce ( (result, entry) => {
|
|
|
|
const wData = entry[1];
|
|
|
|
|
|
|
|
const sortData = wData.sort(d => new Date(d.time).getHours() - 12);
|
|
|
|
const closestToTwelveOClockData = sortData[0];
|
|
|
|
if (closestToTwelveOClockData.length === 0) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
const bestCardinal = closestToTwelveOClockData.windDirectionCardinal;
|
|
|
|
const bestWindDirection = closestToTwelveOClockData.windDirection;
|
|
|
|
const bestIconCode = closestToTwelveOClockData.iconCode;
|
|
|
|
return [
|
|
|
|
...result,
|
|
|
|
{
|
|
|
|
date: entry[0],
|
|
|
|
iconCode: bestIconCode,
|
|
|
|
minTemperature: Math.min(...wData.map(d => d.temperature)),
|
|
|
|
maxTemperature: Math.max(...wData.map(d => d.temperature)),
|
|
|
|
relHumidity: wData.reduce( (r, e) => r + e.relativeHumidity, 0) / wData.length,
|
|
|
|
precipitation: wData.reduce( (r, e) => r + e.rain, 0) * 10,
|
|
|
|
wSpeed: wData.reduce( (r, e) => r + e.windSpeed, 0) / wData.length,
|
|
|
|
wDir: bestWindDirection,
|
|
|
|
wCardinal: bestCardinal
|
|
|
|
}
|
|
|
|
];
|
|
|
|
}, []);
|
2020-07-21 14:57:11 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 21:04:06 +00:00
|
|
|
private groupBy(items: any[], selector: (item) => any): any[] {
|
|
|
|
return items.reduce(
|
|
|
|
(result, item) => {
|
|
|
|
const group = selector(item);
|
|
|
|
return ({
|
|
|
|
...result,
|
|
|
|
[group]: [
|
|
|
|
...(result[group] || []),
|
|
|
|
item,
|
|
|
|
],
|
|
|
|
});},
|
|
|
|
{},
|
|
|
|
);
|
2020-07-21 14:57:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private removeUTCZ(dateFormat: string): string {
|
|
|
|
if (dateFormat[dateFormat.length - 1] === 'Z') {
|
|
|
|
return dateFormat.substring(0, dateFormat.length - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return dateFormat;
|
|
|
|
}
|
2020-02-04 12:54:53 +00:00
|
|
|
}
|