- package.json에 rxjs 패키지를 추가한다.
"rxjs": "^7.4.0"
- 중심 좌표에 따라 json POI 데이터를 필터링+클러스터링하고 그 결과를 반환하는 함수를 작성해야한다.
그 전에 함수에 필요한 type을 먼저 선언한다.
export type Coordinates = [number, number];
export type Lv = 1 | 2 | 3;
interface _POI {
name: string;
lv: Lv;
coordinates: Coordinates;
}
export interface ParentPOI extends _POI {
children: POI[];
}
export type POI = _POI | ParentPOI;
export interface POISets {
lv1POIs: POI[];
lv2POIs: POI[];
lv3POIs: POI[];
}
그 후 메인함수 작성을 시작한다.
// 중심 좌표를 인자로 받아 좌표 주변의 POI만 filtering + clustering 한 후, 그 결과인 POISets를 반환하는 함수
export const generatePOISetsByCenter = (center: Coordinates): POISets => {
const lv1POIs: POI[] = [];
const lv2POIs: POI[] = [];
const lv3POIs: POI[] = [];
// TODO: json 데이터를 필터링 + 클러스터링하고 lv1/2/3POIs 배열을 채워넣는다.
return {
lv1POIs,
lv2POIs,
lv3POIs,
};
}
- 먼저 json을 import한 후, rxjs from을 이용하여 Observable 로 만든다. 그리고 pipe 함수를 시작한다.
import POIData from '@D/test.json';
import { from } from 'rxjs';
from(POIData as POI[])
.pipe(
- 현재 Observable에 해당하는 lv1 POI(json 배열이 lv1 객체로 시작하므로) 중 가까운 지역만 filtering한 후, lv1POIs에 push하고자 한다.
import { MonoTypeOperatorFunction } from 'rxjs';
// NOTE: 좌표값 2개를 인자로 받아 두 좌표 사이 거리를 km 단위로 계산하는 함수
export function calculateDistanceBetweenCoordinates(coordinates1, coordinates2) {
...
}
export const filterNearPOIs = (
referenceCoordinates: Coordinates,
allowedDistanceInKm: number,
): MonoTypeOperatorFunction<POI> =>
filter(
(poi: POI) => calculateDistanceBetweenCoordinates(poi.coordinates, referenceCoordinates) < allowedDistanceInKm,
);
from(POIData as POI[])
.pipe(
filterNearPOIs(center, 100),
tap((poi) => lv1POIs.push(poi)),
filterNearPOIs에서 RxJS filter operator를 사용하여 100km 이내의 POI만 필터링한다. 그 후, tap operator로 side effect를 일으켜 lv1POIs에 해당 POI들을 push한다.
- lv2 POI도 탐색하기 위해 lv1 -> lv2로 한 단계 내려간다.
export const extractChildren = (poi: POI): POI[] => ('children' in poi ? poi.children : []);
export const goDownOneLevel = mergeMap(extractChildren);
from(POIData as POI[])
.pipe(
filterNearPOIs(center, 100),
tap((poi) => lv1POIs.push(poi)),
goDownOneLevel,
goDownOneLevel에서 mergeMap을 이용하여 lv1의 children으로부터 lv2 Observable을 만든다.
- lv2 Observable도 마찬가지로 20km이내의 POI만 필터링하고, lv2POIs에 push한다.
from(POIData as POI[])
.pipe(
filterNearPOIs(center, 100),
tap((poi) => lv1POIs.push(poi)),
goDownOneLevel,
filterNearPOIs(center, 20),
tap((poi) => lv2POIs.push(poi)),
- lv2의 children인 lv3로 한 단계 내려간 후, 필터링없이 lv3POIs에 push한다.
from(POIData as POI[])
.pipe(
filterNearPOIs(center, 100),
tap((poi) => lv1POIs.push(poi)),
goDownOneLevel,
filterNearPOIs(center, 20),
tap((poi) => lv2POIs.push(poi)),
goDownOneLevel,
tap((poi) => lv3POIs.push(poi)),
)
.subscribe();
그 후 subscribe를 통해 실제로 pipeline을 실행한다!
아래는 완성된 cluster.ts 전체 코드이다.
import POIData from '...';
import { Coordinates, POI, POISets } from '...';
import { calculateDistanceBetweenCoordinates } from '...';
import { from, MonoTypeOperatorFunction } from 'rxjs';
import { filter, map, mergeMap, tap } from 'rxjs/operators';
export const extractChildren = (poi: POI): POI[] => ('children' in poi ? poi.children : []);
export const goDownOneLevel = mergeMap(extractChildren);
export const filterNearPOIs = (
referenceCoordinates: Coordinates,
allowedDistanceInKm: number,
): MonoTypeOperatorFunction<POI> =>
filter(
(poi: POI) => calculateDistanceBetweenCoordinates(poi.coordinates, referenceCoordinates) < allowedDistanceInKm,
);
export const generatePOISetsByCenter = (center: Coordinates): POISets => {
const lv1POIs: POI[] = [];
const lv2POIs: POI[] = [];
const lv3POIs: POI[] = [];
from(POIData as POI[])
.pipe(
filterNearPOIs(center, 100),
tap((poi) => lv1POIs.push(poi)),
goDownOneLevel,
filterNearPOIs(center, 20),
tap((poi) => lv2POIs.push(poi)),
goDownOneLevel,
tap((poi) => lv3POIs.push(poi)),
)
.subscribe();
return {
lv1POIs,
lv2POIs,
lv3POIs,
};
};