in src/component/bearing/BearingComponent.ts [88:294]
protected _activate(): void {
const subs = this._subscriptions;
const cameraBearingFov$ =
this._container.renderService.renderCamera$.pipe(
map(
(rc: RenderCamera): [number, number] => {
let vFov: number = this._spatial.degToRad(rc.perspective.fov);
let hFov: number = rc.perspective.aspect === Number.POSITIVE_INFINITY ?
Math.PI :
Math.atan(rc.perspective.aspect * Math.tan(0.5 * vFov)) * 2;
return [this._spatial.azimuthalToBearing(rc.rotation.phi), hFov];
}),
distinctUntilChanged(
(a1: [number, number], a2: [number, number]): boolean => {
return Math.abs(a2[0] - a1[0]) < this._distinctThreshold &&
Math.abs(a2[1] - a1[1]) < this._distinctThreshold;
}));
const imageFov$ = observableCombineLatest(
this._navigator.stateService.currentState$.pipe(
distinctUntilChanged(
undefined,
(frame: AnimationFrame): string => {
return frame.state.currentImage.id;
})),
this._navigator.panService.panImages$).pipe(
map(
([frame, panImages]:
[
AnimationFrame,
[
Image,
Transform,
number][],
])
: ImageFov => {
const image: Image = frame.state.currentImage;
const transform: Transform = frame.state.currentTransform;
if (isSpherical(image.cameraType)) {
return [Math.PI, Math.PI];
}
const currentProjectedPoints =
this._computeProjectedPoints(transform);
const hFov = this._spatial
.degToRad(
this._computeHorizontalFov(
currentProjectedPoints));
let hFovLeft: number = hFov / 2;
let hFovRight: number = hFov / 2;
for (const [n, , f] of panImages) {
const diff: number = this._spatial.wrap(n.compassAngle - image.compassAngle, -180, 180);
if (diff < 0) {
hFovLeft = this._spatial.degToRad(Math.abs(diff)) + f / 2;
} else {
hFovRight = this._spatial.degToRad(Math.abs(diff)) + f / 2;
}
}
return [hFovLeft, hFovRight];
}),
distinctUntilChanged(
(
[hFovLeft1, hFovRight1]: ImageFov,
[hFovLeft2, hFovRight2]: ImageFov): boolean => {
return Math.abs(hFovLeft2 - hFovLeft1) < this._distinctThreshold &&
Math.abs(hFovRight2 - hFovRight1) < this._distinctThreshold;
}));
const offset$ = observableCombineLatest(
this._navigator.stateService.currentState$.pipe(
distinctUntilChanged(
undefined,
(frame: AnimationFrame): string => {
return frame.state.currentImage.id;
})),
this._container.renderService.bearing$).pipe(
map(
([frame, bearing]: [AnimationFrame, number]): number => {
const offset: number = this._spatial.degToRad(frame.state.currentImage.compassAngle - bearing);
return offset;
}));
const imageFovOperation$ = new Subject<ImageFovOperation>();
const smoothImageFov$ = imageFovOperation$.pipe(
scan(
(state: ImageFovState, operation: ImageFovOperation): ImageFovState => {
return operation(state);
},
{ alpha: 0, curr: [0, 0, 0], prev: [0, 0, 0] }),
map(
(state: ImageFovState): ImageFov => {
const alpha = MathUtils.smootherstep(state.alpha, 0, 1);
const curr = state.curr;
const prev = state.prev;
return [
this._interpolate(prev[0], curr[0], alpha),
this._interpolate(prev[1], curr[1], alpha),
];
}));
subs.push(imageFov$.pipe(
map(
(nbf: ImageFov): ImageFovOperation => {
return (state: ImageFovState): ImageFovState => {
const a = MathUtils.smootherstep(state.alpha, 0, 1);
const c = state.curr;
const p = state.prev;
const prev: ImageFov = [
this._interpolate(p[0], c[0], a),
this._interpolate(p[1], c[1], a),
];
const curr: ImageFov = <ImageFov>nbf.slice();
return {
alpha: 0,
curr: curr,
prev: prev,
};
};
}))
.subscribe(imageFovOperation$));
subs.push(imageFov$.pipe(
switchMap(
(): Observable<number> => {
return this._container.renderService.renderCameraFrame$.pipe(
skip(1),
scan<RenderCamera, number>(
(alpha: number): number => {
return alpha + this._animationSpeed;
},
0),
takeWhile(
(alpha: number): boolean => {
return alpha <= 1 + this._animationSpeed;
}),
map(
(alpha: number): number => {
return Math.min(alpha, 1);
}));
}),
map(
(alpha: number): ImageFovOperation => {
return (nbfState: ImageFovState): ImageFovState => {
return {
alpha: alpha,
curr: <ImageFov>nbfState.curr.slice(),
prev: <ImageFov>nbfState.prev.slice(),
};
};
}))
.subscribe(imageFovOperation$));
const imageBearingFov$ = observableCombineLatest(
offset$,
smoothImageFov$).pipe(
map(
([offset, fov]: [number, ImageFov]): ImageBearingFov => {
return [offset, fov[0], fov[1]];
}));
subs.push(observableCombineLatest(
cameraBearingFov$,
imageBearingFov$,
this._configuration$,
this._container.renderService.size$).pipe(
map(
([[cb, cf], [no, nfl, nfr], configuration, size]:
[[number, number], [number, number, number], BearingConfiguration, ViewportSize]): VirtualNodeHash => {
const background: vd.VNode = this._createBackground(cb);
const fovIndicator: vd.VNode = this._createFovIndicator(nfl, nfr, no);
const north: vd.VNode = this._createNorth(cb);
const cameraSector: vd.VNode = this._createCircleSectorCompass(
this._createCircleSector(Math.max(Math.PI / 20, cf), "#FFF"));
const compact: string = configuration.size === ComponentSize.Small ||
configuration.size === ComponentSize.Automatic && size.width < 640 ?
".mapillary-bearing-compact" : "";
return {
name: this._name,
vNode: vd.h(
"div.mapillary-bearing-indicator-container" + compact,
{ oncontextmenu: (event: MouseEvent): void => { event.preventDefault(); } },
[
background,
fovIndicator,
north,
cameraSector,
]),
};
}))
.subscribe(this._container.domRenderer.render$));
}