Skip to content

Commit

Permalink
Merge branch 'render-uirs'
Browse files Browse the repository at this point in the history
  • Loading branch information
garrappachc committed Apr 20, 2019
2 parents c8ec719 + 1cec7c7 commit 5c894b0
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 26 deletions.
9 changes: 9 additions & 0 deletions src/app/map/map.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class MarkerServiceStub {
marker = marker(latLng(0, 0));
aircraft(pilot: Pilot) { return this.marker; }
airport(airport: Airport) { return this.marker; }
fir(fir: Fir) { return this.marker; }
}

class MapStub {
Expand Down Expand Up @@ -89,6 +90,14 @@ describe('MapService', () => {
it('should add lines to lines layer');
});

describe('#addFir()', () => {
it('should add marker', inject([MapService], (service: MapService) => {
const spy = spyOn(TestBed.get(MarkerService), 'fir').and.callThrough();
service.addFir({ boundaries: [] } as Fir);
expect(spy).toHaveBeenCalled();
}));
});

describe('#clearLines()', () => {
it('should clear lines layer');
});
Expand Down
5 changes: 3 additions & 2 deletions src/app/map/map.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,11 @@ export class MapService {
}

addFir(fir: Fir) {
// fixme: move these color to config?
const color = fir.hasUirAtcsOnly ? '#8fbecf' : '#b02020';
polygon(fir.boundaries, {
color: '#b02020',
color, fillColor: color,
opacity: 0.2,
fillColor: '#b02020',
weight: 1,
interactive: false,
})
Expand Down
2 changes: 1 addition & 1 deletion src/app/map/marker.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class MarkerService {
fir(fir: Fir): Marker {
const label = divIcon({
html: fir.icao,
className: 'vatsim-fir-label-active',
className: fir.hasUirAtcsOnly ? 'vatsim-fir-label-uir' : 'vatsim-fir-label-active',
iconSize: [100, 16],
tooltipAnchor: [50, -8],
});
Expand Down
13 changes: 11 additions & 2 deletions src/app/map/styles/fir-label.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
.vatsim-fir-label-active {
color: #9d5656;
@mixin make-fir-label {
font-weight: bolder;
white-space: nowrap;
text-align: center;
}

.vatsim-fir-label-active {
@include make-fir-label();
color: #9d5656;
}

.vatsim-fir-label-uir {
@include make-fir-label();
color: #2191b0;
}
2 changes: 1 addition & 1 deletion src/app/map/tooltip.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class TooltipService {
forFir(fir: Fir): HTMLElement {
return this.create(FirTooltipComponent, componentRef => {
componentRef.instance.fir = fir;
componentRef.instance.atcs = this.clients.filter(c => isAtc(c) && c.fir === fir.icao) as Atc[];
componentRef.instance.atcs = this.clients.filter(c => isAtc(c) && fir.atcs.indexOf(c.callsign) >= 0) as Atc[];
});
}

Expand Down
1 change: 1 addition & 0 deletions src/app/vatsim/models/atc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface Atc extends Client {

airport?: string;
fir?: string;
uir?: string;
}

export function isAtc(client: Client): client is Atc {
Expand Down
6 changes: 3 additions & 3 deletions src/app/vatsim/models/fir.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Atc } from '@app/vatsim/models/atc';

export interface Fir {
icao: string;
name: string;
Expand All @@ -10,5 +8,7 @@ export interface Fir {
labelPosition: [number, number];
oceanic: boolean;

atcs?: Atc[];
/** List of ATC callsigns */
atcs?: string[];
hasUirAtcsOnly: boolean;
}
1 change: 1 addition & 0 deletions src/app/vatsim/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { Client } from './client';
export { Fir } from './fir';
export { Pilot, isPilot } from './pilot';
export { VatsimData } from './vatsim-data';
export { Uir } from './uir';
7 changes: 7 additions & 0 deletions src/app/vatsim/models/uir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Uir {
icao: string;
name: string;
firs: string[];

atcs?: string[];
}
4 changes: 3 additions & 1 deletion src/app/vatsim/models/vatsim-data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Client, Airport } from '.';
import { Client, Airport, Fir, Uir } from '.';

export interface VatsimData {
general: {
Expand All @@ -11,4 +11,6 @@ export interface VatsimData {

clients: Client[];
activeAirports: Airport[];
firs: Fir[];
uirs: Uir[];
}
89 changes: 87 additions & 2 deletions src/app/vatsim/vatsim.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { TestBed, inject } from '@angular/core/testing';
import { TestBed, inject, fakeAsync, tick, flush, async } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { VatsimService } from './vatsim.service';
import { API_URL } from '../api-url';
import { Fir, Uir } from './models';

describe('VatsimService', () => {
let httpController: HttpTestingController;
Expand All @@ -24,7 +25,7 @@ describe('VatsimService', () => {
expect(service).toBeTruthy();
});

it('should call api', inject([VatsimService], (service: VatsimService) => {
it('should call the api', inject([VatsimService], (service: VatsimService) => {
httpController.expectOne('FAKE_HOST/vatsim/data');
httpController.verify();
}));
Expand All @@ -36,5 +37,89 @@ describe('VatsimService', () => {
httpController.expectOne('FAKE_HOST/vatsim/data');
httpController.verify();
}));

describe('processes data', () => {
afterEach(() => {
httpController.verify();
});

it('when there\'s no FIR', inject([VatsimService], (service: VatsimService) => {
service.data.subscribe(data => {
expect(data).toBeTruthy();
expect(data.clients).toBeTruthy();
expect(data.clients.length).toEqual(0);
});

const request = httpController.expectOne('FAKE_HOST/vatsim/data');
expect(request.request.method).toBe('GET');

httpController.expectNone('FAKE_HOST/firs');
request.flush({ clients: [] });
}));

it('when there are FIRs but no UIRs', inject([VatsimService], (service: VatsimService) => {
service.data.subscribe(data => {
expect(data).toBeTruthy();
expect(data.clients).toBeTruthy();
expect(data.clients.length).toEqual(1);

expect(data.firs).toBeDefined();
expect(data.firs.length).toEqual(1);
expect(data.firs[0].hasUirAtcsOnly).toBe(false);
expect(data.firs[0].atcs).toEqual(['FAKE_CALLSIGN']);
});

httpController.expectOne('FAKE_HOST/vatsim/data').flush({ clients: [{ callsign: 'FAKE_CALLSIGN', type: 'atc', fir: 'FAKE_FIR' }] });
httpController.expectOne('FAKE_HOST/firs?icao=FAKE_FIR').flush([{ icao: 'FAKE_FIR' }]);
}));

it('when there are only UIRs', inject([VatsimService], (service: VatsimService) => {
service.data.subscribe(data => {
expect(data).toBeTruthy();
expect(data.clients).toBeTruthy();
expect(data.clients.length).toEqual(1);

expect(data.firs).toBeDefined();
expect(data.firs.length).toEqual(3);
for (const f of data.firs) {
expect(f.hasUirAtcsOnly).toBe(true);
expect(f.atcs).toEqual(['FAKE_CALLSIGN']);
}
expect(data.uirs.length).toEqual(1);
expect(data.uirs[0].atcs).toEqual(['FAKE_CALLSIGN']);
});

httpController.expectOne('FAKE_HOST/vatsim/data').flush({ clients: [{ callsign: 'FAKE_CALLSIGN', type: 'atc', uir: 'FAKE_UIR' }] });
httpController.expectOne('FAKE_HOST/firs?icao=FAKE_UIR').flush([{ icao: 'FAKE_UIR',
firs: ['FAKE_FIR_1', 'FAKE_FIR_2', 'FAKE_FIR_3'] }]);
httpController.expectOne('FAKE_HOST/firs?icao=FAKE_FIR_1,FAKE_FIR_2,FAKE_FIR_3').flush([{ icao: 'FAKE_FIR_1' },
{ icao: 'FAKE_FIR_2' }, { icao: 'FAKE_FIR_3' }]);
}));

it('when there are FIRs and UIRs', inject([VatsimService], (service: VatsimService) => {
service.data.subscribe(data => {
expect(data).toBeTruthy();
expect(data.clients).toBeTruthy();
expect(data.clients.length).toEqual(2);

expect(data.firs).toBeDefined();
expect(data.uirs).toBeDefined();

expect(data.firs).toEqual([
{ icao: 'FAKE_FIR', hasUirAtcsOnly: false, atcs: ['FAKE_ATC_1', 'FAKE_ATC_2'] },
] as Fir[]);
expect(data.uirs).toEqual([
{ icao: 'FAKE_UIR', firs: ['FAKE_FIR'], atcs: ['FAKE_ATC_2'] },
] as Uir[]);
});

httpController.expectOne('FAKE_HOST/vatsim/data').flush({ clients: [
{ callsign: 'FAKE_ATC_1', type: 'atc', fir: 'FAKE_FIR' },
{ callsign: 'FAKE_ATC_2', type: 'atc', uir: 'FAKE_UIR' },
]});
httpController.expectOne('FAKE_HOST/firs?icao=FAKE_UIR').flush([{ icao: 'FAKE_UIR', firs: ['FAKE_FIR'] }]);
httpController.expectOne('FAKE_HOST/firs?icao=FAKE_FIR').flush([{ icao: 'FAKE_FIR' }]);
}));
});
});
});
68 changes: 54 additions & 14 deletions src/app/vatsim/vatsim.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Injectable, Inject } from '@angular/core';
import { API_URL } from '../api-url';
import { ReplaySubject, zip } from 'rxjs';
import { VatsimData, Fir } from './models';
import { HttpClient } from '@angular/common/http';
import { switchMap, defaultIfEmpty, map } from 'rxjs/operators';
import { ReplaySubject, of } from 'rxjs';
import { VatsimData, Fir, isAtc, Uir, Atc } from './models';
import { HttpClient, HttpParams } from '@angular/common/http';
import { switchMap, map, tap } from 'rxjs/operators';

type VatsimDataResponse = Pick<VatsimData, Exclude<keyof VatsimData, 'firs'|'uirs'>>;

@Injectable({
providedIn: 'root'
})
export class VatsimService {

private dataSource = new ReplaySubject<VatsimData & { firs: Fir[] }>(1);
private dataSource = new ReplaySubject<VatsimData>(1);
readonly data = this.dataSource.asObservable();

constructor(
Expand All @@ -22,20 +24,58 @@ export class VatsimService {
}

refresh() {
this.http.get<VatsimData>(`${this.apiUrl}/vatsim/data`).pipe(
this.http.get<VatsimDataResponse>(`${this.apiUrl}/vatsim/data`).pipe(
switchMap(response => this.resolveFirs(response)),
).subscribe(data => this.dataSource.next(data));
}

private resolveFirs(response: VatsimData) {
const firs = [...new Set(response.clients
.filter(client => client.type === 'atc')
.map((atc: any) => atc.fir))].filter(fir => !!fir);
private fetchFirs(firIcaos: string[]) {
return firIcaos.length === 0 ? of([]) :
this.http.get<Fir[]>(`${this.apiUrl}/firs`, { params: new HttpParams().set('icao', firIcaos.join(',')) });
}

private resolveFirs(response: VatsimDataResponse) {
const uirsToFetch = [...new Set(response.clients
.filter(client => isAtc(client))
.map((atc: Atc) => atc.uir))].filter(uir => !!uir);

const firsToFetch = [...new Set(response.clients
.filter(client => isAtc(client))
.map((atc: Atc) => atc.fir))].filter(fir => !!fir);

const fetchUirs = uirsToFetch.length === 0 ? of([]) :
this.http.get<Uir[]>(`${this.apiUrl}/firs`, { params: new HttpParams().set('icao', uirsToFetch.join(',')) });

let uirs: Uir[];

return zip(...firs.map(icao => this.http.get<Fir>(`${this.apiUrl}/firs/${icao}`))).pipe(
defaultIfEmpty([]),
map(firsRes => ({ ...response, firs: firsRes }),
));
return fetchUirs.pipe(
tap(uirsRes => uirs = uirsRes.map(uir => (
{ ...uir,
atcs: response.clients
.filter(client => isAtc(client) && client.uir === uir.icao)
.map(c => c.callsign)
}
))),
map(uirsRes => uirsRes.reduce((acc, uir) => acc.concat(uir.firs), [])),
map(firsInUirs => [ ...new Set([ ...firsToFetch, ...firsInUirs ])]),
switchMap(firs => this.fetchFirs(firs)),
map(firs => firs.map(fir => {
const firAtcs = response.clients
.filter(client => isAtc(client) && client.fir === fir.icao)
.map(c => c.callsign);
return { ...fir,
hasUirAtcsOnly: firAtcs.length === 0,
atcs: [
...firAtcs,
...uirs
.filter(uir => uir.firs.find(f => f === fir.icao))
.map(u => u.atcs)
.reduce((acc, x) => acc.concat(x), [])
]
};
})),
map(firs => ({ ...response, firs, uirs })),
);
}

}

0 comments on commit 5c894b0

Please sign in to comment.