Skip to content

Commit 3ccab66

Browse files
committed
feat: integrated visualization of cropsar
1 parent 25fda9d commit 3ccab66

File tree

5 files changed

+95
-9
lines changed

5 files changed

+95
-9
lines changed

src/app/models/vineyard.model.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { Variety } from './variety.model';
22
import { Polygon } from 'ol/geom/Polygon';
33
import { Action } from './action.model';
44

5+
export interface TimeSeriesEntry {
6+
date: moment.Moment;
7+
value: number;
8+
}
59
export interface MeteoStatEntry {
610
date: moment.Moment;
711
tavg: number;
@@ -16,6 +20,9 @@ export interface MeteoStatEntry {
1620
tsun: number;
1721
}
1822

23+
export interface TimeSeriesStats {
24+
data: TimeSeriesEntry[];
25+
}
1926
export interface MeteoStats {
2027
data: MeteoStatEntry[];
2128
}

src/app/services/color.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export enum COLOR {
1313
STATION_GDD = '#00ef00',
1414
STATION_SUNHOURS = '#efbf00',
1515
STATION_HUMIDITY = '#044ce8',
16+
NDVI = '#409b00',
1617
}
1718

1819
@Injectable({

src/app/services/statistics.service.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
22
import { AngularFirestore } from '@angular/fire/firestore';
33
import { BehaviorSubject } from 'rxjs';
44
import { Action } from '../models/action.model';
5-
import { MeteoStatEntry, MeteoStats, Vineyard } from '../models/vineyard.model';
5+
import { MeteoStatEntry, MeteoStats, TimeSeriesEntry, TimeSeriesStats, Vineyard } from '../models/vineyard.model';
66
import { map } from 'rxjs/operators';
77
import * as moment from 'moment/moment';
88
import { SeasonsService } from './seasons.service';
@@ -13,23 +13,48 @@ import { SeasonsService } from './seasons.service';
1313
export class StatisticsService {
1414
private _meteoStats: BehaviorSubject<MeteoStatEntry[]>;
1515

16+
private _ndviStats: BehaviorSubject<TimeSeriesEntry[]>;
17+
1618
private BASE_TEMP = 10.0;
1719

1820
constructor(private fireStore: AngularFirestore, private seasonService: SeasonsService) {
1921
this._meteoStats = new BehaviorSubject<MeteoStatEntry[]>([]);
22+
this._ndviStats = new BehaviorSubject<TimeSeriesEntry[]>([]);
2023
}
2124

22-
private getVineyardMeteoCollectionPath(vineyard: Vineyard): string {
25+
private getVineyardStatsCollectionPath(vineyard: Vineyard): string {
2326
return `users/${vineyard.owner}/vineyards/${vineyard.id}/stats`;
2427
}
2528

2629
public getMeteoListener(): BehaviorSubject<MeteoStatEntry[]> {
2730
return this._meteoStats;
2831
}
2932

33+
public getNDVIListener(): BehaviorSubject<TimeSeriesEntry[]> {
34+
return this._ndviStats;
35+
}
36+
37+
public getNDVIStats(vineyard: Vineyard): void {
38+
this.fireStore
39+
.collection(this.getVineyardStatsCollectionPath(vineyard))
40+
.doc('cropsar')
41+
.snapshotChanges()
42+
.pipe(
43+
map((data) => (data.payload.data() as TimeSeriesStats)?.data || []),
44+
map((entries: TimeSeriesEntry[]) =>
45+
entries.map((e: TimeSeriesEntry) => ({
46+
...e,
47+
date: moment(e.date),
48+
value: Math.ceil(e.value * 100) / 100,
49+
}))
50+
)
51+
)
52+
.subscribe((entries: TimeSeriesEntry[]) => this._ndviStats.next(entries));
53+
}
54+
3055
public getMeteoStats(vineyard: Vineyard): void {
3156
this.fireStore
32-
.collection(this.getVineyardMeteoCollectionPath(vineyard))
57+
.collection(this.getVineyardStatsCollectionPath(vineyard))
3358
.doc('meteo')
3459
.snapshotChanges()
3560
.pipe(

src/app/vineyeard-view/statistics/statistics.component.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ import { LoadingController, Platform } from '@ionic/angular';
33
import { ActionType, BBCHAction, BrixAction } from './../../models/action.model';
44
import { STATS_OPTIONS } from './../../conf/statistics.config';
55
import { StatisticsService } from './../../services/statistics.service';
6-
import { VineyardService } from './../../services/vineyard.service';
7-
import { MeteoStatEntry, Vineyard } from './../../models/vineyard.model';
6+
import { MeteoStatEntry, TimeSeriesEntry, Vineyard } from './../../models/vineyard.model';
87
import { AfterViewInit, Component, ElementRef, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core';
98
import * as Highcharts from 'highcharts/highstock';
109
import { Action } from 'src/app/models/action.model';
11-
import { TitleCasePipe } from '@angular/common';
1210
import * as moment from 'moment';
1311
import { Variety } from '../../models/variety.model';
1412
import { COLOR, ColorService } from '../../services/color.service';
@@ -25,13 +23,14 @@ enum StatTypes {
2523
ACTIONS = 'Actions',
2624
BBCH = 'BBCH',
2725
METEO = 'Meteo',
26+
NDVI = 'NDVI',
2827
DGD = 'Growing Days',
2928
BRIX = 'Brix',
3029
SUNHOURS = 'Sun Hours',
3130
HUMIDITY = 'Humidity',
3231
}
3332

34-
const PREMIUM_TYPES: StatTypes[] = [StatTypes.METEO, StatTypes.DGD];
33+
const PREMIUM_TYPES: StatTypes[] = [StatTypes.METEO, StatTypes.NDVI, StatTypes.DGD];
3534

3635
@Component({
3736
selector: 'app-statistics',
@@ -76,9 +75,7 @@ export class StatisticsComponent implements AfterViewInit, OnChanges {
7675

7776
constructor(
7877
private utilService: UtilService,
79-
private vineyardService: VineyardService,
8078
private statService: StatisticsService,
81-
private titlecasePipe: TitleCasePipe,
8279
private platform: Platform,
8380
private colorService: ColorService,
8481
private integrationsService: IntegrationsService,
@@ -177,6 +174,7 @@ export class StatisticsComponent implements AfterViewInit, OnChanges {
177174
...this.getSunHoursAxis(),
178175
...this.getHumidityAxis(),
179176
...this.getBBCHAxis(),
177+
...this.getNDVIAxis(),
180178
];
181179
axes.forEach((a: any) => this._chart.addAxis(a));
182180
}
@@ -192,6 +190,10 @@ export class StatisticsComponent implements AfterViewInit, OnChanges {
192190
requests.push(this.getWeatherStationMeteoGraphs(), this.getMeteoTimelines());
193191
}
194192

193+
if (this.activeStats.includes(StatTypes.NDVI)) {
194+
requests.push(this.getNDVITimelines());
195+
}
196+
195197
if (this.activeStats.includes(StatTypes.DGD)) {
196198
requests.push(this.getWeatherStationDGDGraphs(), this.getDgdTimelines());
197199
}
@@ -252,6 +254,21 @@ export class StatisticsComponent implements AfterViewInit, OnChanges {
252254
};
253255
}
254256

257+
getNDVIAxis(): any[] {
258+
return [
259+
{
260+
id: 'ndvi',
261+
labels: {
262+
format: '{value}',
263+
},
264+
title: {
265+
text: 'NDVI',
266+
},
267+
opposite: true,
268+
},
269+
];
270+
}
271+
255272
getMeteoAxis(): any[] {
256273
return [
257274
{
@@ -448,6 +465,41 @@ export class StatisticsComponent implements AfterViewInit, OnChanges {
448465
);
449466
}
450467

468+
getNDVITimelines(): Observable<any> {
469+
const stats: TimeSeriesEntry[] = this.statService.getNDVIListener().getValue();
470+
const years = stats
471+
.map((s: TimeSeriesEntry) => s.date.year())
472+
.filter((y: number, idx: number, ys: number[]) => ys.indexOf(y) === idx);
473+
474+
return merge(
475+
[].concat(
476+
...years
477+
.filter((y: number) => this.seasons.indexOf(y) >= 0)
478+
.map((y: number, idx: number) => [
479+
{
480+
id: `NDVI ${y}`,
481+
name: `NDVI ${y}`,
482+
type: 'spline',
483+
yAxis: 'ndvi',
484+
color: this.colorService.darken(COLOR.NDVI, idx),
485+
showInNavigator: true,
486+
tooltip: {
487+
formatter(point) {
488+
return `<span style="color:${point.color}">●</span> <b>NDVI ${y}</b>: ${point.y}`;
489+
},
490+
},
491+
data: stats
492+
.filter((s: TimeSeriesEntry) => s.date.year() === y)
493+
.map((e: TimeSeriesEntry) => ({
494+
x: this.statService.getNormalizedDate(e.date),
495+
y: e.value,
496+
})),
497+
},
498+
])
499+
)
500+
);
501+
}
502+
451503
getMeteoTimelines(): Observable<any> {
452504
const stats: MeteoStatEntry[] = this.statService.getMeteoListener().getValue();
453505
const years = stats

src/app/vineyeard-view/vineyard-view-page.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export class VineyardViewPage implements OnInit, OnDestroy, AfterViewInit {
9595
this.varietyService.getVarieties(this.activeVineyard);
9696
this.noteService.getNotes(this.activeVineyard);
9797
this.statService.getMeteoStats(this.activeVineyard);
98+
this.statService.getNDVIStats(this.activeVineyard);
9899
this.integrationsService.getIntegrations(this.activeVineyard);
99100
}
100101
});

0 commit comments

Comments
 (0)