in src/component/marker/MarkerComponent.ts [347:783]
protected _activate(): void {
const groundAltitude$ = this._navigator.stateService.currentState$.pipe(
map(
(frame: AnimationFrame): number => {
return frame.state.camera.position.z + this._relativeGroundAltitude;
}),
distinctUntilChanged(
(a1: number, a2: number): boolean => {
return Math.abs(a1 - a2) < 0.01;
}),
publishReplay(1),
refCount());
const geoInitiated$ = observableCombineLatest(
groundAltitude$,
this._navigator.stateService.reference$).pipe(
first(),
map((): void => { /* noop */ }),
publishReplay(1),
refCount());
const clampedConfiguration$ = this._configuration$.pipe(
map(
(configuration: MarkerConfiguration): MarkerConfiguration => {
return { visibleBBoxSize: Math.max(1, Math.min(200, configuration.visibleBBoxSize)) };
}));
const currentLngLat$ = this._navigator.stateService.currentImage$.pipe(
map((image: Image): LngLat => { return image.lngLat; }),
publishReplay(1),
refCount());
const visibleBBox$ = observableCombineLatest(
clampedConfiguration$,
currentLngLat$).pipe(
map(
([configuration, lngLat]: [MarkerConfiguration, LngLat]): [LngLat, LngLat] => {
return this._graphCalculator
.boundingBoxCorners(lngLat, configuration.visibleBBoxSize / 2);
}),
publishReplay(1),
refCount());
const visibleMarkers$ = observableCombineLatest(
observableConcat(
observableOf<MarkerSet>(this._markerSet),
this._markerSet.changed$),
visibleBBox$).pipe(
map(
([set, bbox]: [MarkerSet, [LngLat, LngLat]]): Marker[] => {
return set.search(bbox);
}));
const subs = this._subscriptions;
subs.push(geoInitiated$.pipe(
switchMap(
(): Observable<[Marker[], LngLatAlt, number]> => {
return visibleMarkers$.pipe(
withLatestFrom(
this._navigator.stateService.reference$,
groundAltitude$));
}))
.subscribe(
(
[markers, reference, alt]
: [Marker[], LngLatAlt, number])
: void => {
const markerScene: MarkerScene = this._markerScene;
const sceneMarkers: { [id: string]: Marker } =
markerScene.markers;
const markersToRemove: { [id: string]: Marker } =
Object.assign({}, sceneMarkers);
for (const marker of markers) {
if (marker.id in sceneMarkers) {
delete markersToRemove[marker.id];
} else {
const point3d =
geodeticToEnu(
marker.lngLat.lng,
marker.lngLat.lat,
reference.alt + alt,
reference.lng,
reference.lat,
reference.alt);
markerScene.add(marker, point3d);
}
}
for (const id in markersToRemove) {
if (!markersToRemove.hasOwnProperty(id)) {
continue;
}
markerScene.remove(id);
}
}));
subs.push(geoInitiated$.pipe(
switchMap(
(): Observable<[Marker[], [LngLat, LngLat], LngLatAlt, number]> => {
return this._markerSet.updated$.pipe(
withLatestFrom(
visibleBBox$,
this._navigator.stateService.reference$,
groundAltitude$));
}))
.subscribe(
(
[markers, [sw, ne], reference, alt]
: [Marker[], [LngLat, LngLat], LngLatAlt, number])
: void => {
const markerScene: MarkerScene = this._markerScene;
for (const marker of markers) {
const exists = markerScene.has(marker.id);
const visible = marker.lngLat.lat > sw.lat &&
marker.lngLat.lat < ne.lat &&
marker.lngLat.lng > sw.lng &&
marker.lngLat.lng < ne.lng;
if (visible) {
const point3d =
geodeticToEnu(
marker.lngLat.lng,
marker.lngLat.lat,
reference.alt + alt,
reference.lng,
reference.lat,
reference.alt);
markerScene.add(marker, point3d);
} else if (!visible && exists) {
markerScene.remove(marker.id);
}
}
}));
subs.push(this._navigator.stateService.reference$.pipe(
skip(1),
withLatestFrom(groundAltitude$))
.subscribe(
([reference, alt]: [LngLatAlt, number]): void => {
const markerScene: MarkerScene = this._markerScene;
for (const marker of markerScene.getAll()) {
const point3d =
geodeticToEnu(
marker.lngLat.lng,
marker.lngLat.lat,
reference.alt + alt,
reference.lng,
reference.lat,
reference.alt);
markerScene.update(marker.id, point3d);
}
}));
subs.push(groundAltitude$.pipe(
skip(1),
withLatestFrom(
this._navigator.stateService.reference$,
currentLngLat$))
.subscribe(
(
[alt, reference, lngLat]
: [number, LngLatAlt, LngLat])
: void => {
const markerScene = this._markerScene;
const position =
geodeticToEnu(
lngLat.lng,
lngLat.lat,
reference.alt + alt,
reference.lng,
reference.lat,
reference.alt);
for (const marker of markerScene.getAll()) {
const point3d =
geodeticToEnu(
marker.lngLat.lng,
marker.lngLat.lat,
reference.alt + alt,
reference.lng,
reference.lat,
reference.alt);
const distanceX = point3d[0] - position[0];
const distanceY = point3d[1] - position[1];
const groundDistance = Math
.sqrt(distanceX * distanceX + distanceY * distanceY);
if (groundDistance > 50) {
continue;
}
markerScene.lerpAltitude(marker.id, alt, Math.min(1, Math.max(0, 1.2 - 1.2 * groundDistance / 50)));
}
}));
subs.push(this._navigator.stateService.currentState$
.pipe(
map(
(frame: AnimationFrame): GLRenderHash => {
const scene = this._markerScene;
return {
name: this._name,
renderer: {
frameId: frame.id,
needsRender: scene.needsRender,
render: scene.render.bind(scene),
pass: RenderPass.Opaque,
},
};
}))
.subscribe(this._container.glRenderer.render$));
const hoveredMarkerId$: Observable<string> =
observableCombineLatest(
this._container.renderService.renderCamera$,
this._container.mouseService.mouseMove$)
.pipe(
map(
([render, event]: [RenderCamera, MouseEvent]): string => {
const element = this._container.container;
const [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
const viewport = this._viewportCoords
.canvasToViewport(
canvasX,
canvasY,
element);
const markerId: string = this._markerScene.intersectObjects(viewport, render.perspective);
return markerId;
}),
publishReplay(1),
refCount());
const draggingStarted$: Observable<boolean> =
this._container.mouseService
.filtered$(this._name, this._container.mouseService.mouseDragStart$).pipe(
map(
(): boolean => {
return true;
}));
const draggingStopped$: Observable<boolean> =
this._container.mouseService
.filtered$(this._name, this._container.mouseService.mouseDragEnd$).pipe(
map(
(): boolean => {
return false;
}));
const filteredDragging$: Observable<boolean> =
observableMerge(
draggingStarted$,
draggingStopped$)
.pipe(
startWith(false));
subs.push(observableMerge(
draggingStarted$.pipe(
withLatestFrom(hoveredMarkerId$)),
observableCombineLatest(
draggingStopped$,
observableOf<string>(null))).pipe(
startWith<[boolean, string]>([false, null]),
pairwise())
.subscribe(
([previous, current]: [boolean, string][]): void => {
const dragging = current[0];
const type: ComponentEventType =
dragging ?
"markerdragstart" :
"markerdragend";
const id = dragging ? current[1] : previous[1];
const marker = this._markerScene.get(id);
const event: ComponentMarkerEvent = {
marker,
target: this,
type,
};
this.fire(type, event);
}));
const mouseDown$: Observable<boolean> = observableMerge(
this._container.mouseService.mouseDown$.pipe(
map((): boolean => { return true; })),
this._container.mouseService.documentMouseUp$.pipe(
map((): boolean => { return false; }))).pipe(
startWith(false));
subs.push(
observableCombineLatest(
this._container.mouseService.active$,
hoveredMarkerId$.pipe(distinctUntilChanged()),
mouseDown$,
filteredDragging$)
.pipe(
map(
(
[active, markerId, mouseDown, filteredDragging]
: [boolean, string, boolean, boolean])
: boolean => {
return (!active && markerId != null && mouseDown) ||
filteredDragging;
}),
distinctUntilChanged())
.subscribe(
(claim: boolean): void => {
if (claim) {
this._container.mouseService.claimMouse(this._name, 1);
this._container.mouseService.claimWheel(this._name, 1);
} else {
this._container.mouseService.unclaimMouse(this._name);
this._container.mouseService.unclaimWheel(this._name);
}
}));
const offset$: Observable<[Marker, number[], RenderCamera]> = this._container.mouseService
.filtered$(this._name, this._container.mouseService.mouseDragStart$).pipe(
withLatestFrom(
hoveredMarkerId$,
this._container.renderService.renderCamera$),
map(
(
[e, id, r]:
[MouseEvent, string, RenderCamera])
: [Marker, number[], RenderCamera] => {
const marker: Marker = this._markerScene.get(id);
const element = this._container.container;
const [groundCanvasX, groundCanvasY]: number[] =
this._viewportCoords
.projectToCanvas(
marker.geometry.position
.toArray(),
element,
r.perspective);
const [canvasX, canvasY] = this._viewportCoords
.canvasPosition(e, element);
const offset = [canvasX - groundCanvasX, canvasY - groundCanvasY];
return [marker, offset, r];
}),
publishReplay(1),
refCount());
subs.push(this._container.mouseService
.filtered$(
this._name,
this._container.mouseService.mouseDrag$)
.pipe(
withLatestFrom(
offset$,
this._navigator.stateService.reference$,
clampedConfiguration$))
.subscribe(
([event, [marker, offset, render], reference, configuration]:
[MouseEvent, [Marker, number[], RenderCamera], LngLatAlt, MarkerConfiguration]): void => {
if (!this._markerScene.has(marker.id)) {
return;
}
const element = this._container.container;
const [canvasX, canvasY] = this._viewportCoords
.canvasPosition(event, element);
const groundX = canvasX - offset[0];
const groundY = canvasY - offset[1];
const [viewportX, viewportY] = this._viewportCoords
.canvasToViewport(
groundX,
groundY,
element);
const direction =
new THREE.Vector3(viewportX, viewportY, 1)
.unproject(render.perspective)
.sub(render.perspective.position)
.normalize();
const distance = Math.min(
this._relativeGroundAltitude / direction.z,
configuration.visibleBBoxSize / 2 - 0.1);
if (distance < 0) {
return;
}
const intersection = direction
.clone()
.multiplyScalar(distance)
.add(render.perspective.position);
intersection.z =
render.perspective.position.z
+ this._relativeGroundAltitude;
const [lng, lat] =
enuToGeodetic(
intersection.x,
intersection.y,
intersection.z,
reference.lng,
reference.lat,
reference.alt);
this._markerScene
.update(
marker.id,
intersection.toArray(),
{ lat, lng });
this._markerSet.update(marker);
const type: ComponentEventType = "markerposition";
const markerEvent: ComponentMarkerEvent = {
marker,
target: this,
type,
};
this.fire(type, markerEvent);
}));
}