Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,24 @@ export class ExerciseMapComponent implements AfterViewInit, OnDestroy {
) {}

ngAfterViewInit(): void {
this.popupManager = new PopupManager(
this.popoverContent,
this.popoverContainer.nativeElement
);
// run outside angular zone for better performance
this.ngZone.runOutsideAngular(() => {
this.olMapManager = new OlMapManager(
this.store,
this.exerciseService,
this.openLayersContainer.nativeElement,
this.popoverContainer.nativeElement,
this.ngZone,
this.transferLinesService
this.transferLinesService,
this.popupManager!
);
this.dragElementService.registerMap(this.olMapManager.olMap);
});
this.popupManager = new PopupManager(
this.olMapManager!.popupOverlay,
this.popoverContent
);
this.olMapManager!.changePopup$.pipe(

this.popupManager!.changePopup$.pipe(
takeUntil(this.destroy$)
).subscribe((options) => {
// Because changePopup$ is coming from outside the angular zone, we need to wrap it in a zone
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import type { Type, NgZone } from '@angular/core';
import type { Store } from '@ngrx/store';
import type { MapBrowserEvent } from 'ol';
import { Feature } from 'ol';
import LineString from 'ol/geom/LineString';
import type { TranslateEvent } from 'ol/interaction/Translate';
import type VectorLayer from 'ol/layer/Vector';
import type VectorSource from 'ol/source/Vector';
import type { Subject } from 'rxjs';
import { rgbColorPalette } from 'src/app/shared/functions/colors';
import type { CateringLine } from 'src/app/shared/types/catering-line';
import type { AppState } from 'src/app/state/app.state';
import { selectVisibleCateringLines } from 'src/app/state/application/selectors/shared.selectors';
import type OlMap from 'ol/Map';
import type { FeatureManager } from '../utility/feature-manager';
import type { OlMapInteractionsManager } from '../utility/ol-map-interactions-manager';
import type { OpenPopupOptions } from '../utility/popup-manager';
import { LineStyleHelper } from '../utility/style-helper/line-style-helper';
import { ElementManager } from './element-manager';

Expand All @@ -20,13 +28,39 @@ export class CateringLinesFeatureManager
}),
0.05
);
public readonly layer: VectorLayer<VectorSource<LineString>>;

constructor(public readonly layer: VectorLayer<VectorSource<LineString>>) {
constructor(
private readonly store: Store<AppState>,
private readonly olMap: OlMap
) {
super();
layer.setStyle((feature, currentZoom) =>
this.layer = super.createElementLayer<LineString>();
this.layer.setStyle((feature, currentZoom) =>
this.lineStyleHelper.getStyle(feature as Feature, currentZoom)
);
}
togglePopup$?: Subject<OpenPopupOptions<any, Type<any>>> | undefined;
register(
changePopup$: Subject<OpenPopupOptions<any, Type<any>> | undefined>,
destroy$: Subject<void>,
ngZone: NgZone,
mapInteractionsManager: OlMapInteractionsManager
) {
this.olMap.addLayer(this.layer);
mapInteractionsManager.addFeatureLayer(this.layer);
this.togglePopup$?.subscribe(changePopup$);
// Propagate the changes on an element to the featureManager
this.registerChangeHandlers(
this.store.select(selectVisibleCateringLines),
destroy$,
ngZone,
(element) => this.onElementCreated(element),
(element) => this.onElementDeleted(element),
(oldElement, newElement) =>
this.onElementChanged(oldElement, newElement)
);
}

public isFeatureTranslatable(feature: Feature<LineString>) {
return false;
Expand Down Expand Up @@ -76,9 +110,9 @@ export class CateringLinesFeatureManager
) {}

onFeatureDrop(
dropEvent: TranslateEvent,
droppedFeature: Feature<any>,
droppedOnFeature: Feature<LineString>
droppedOnFeature: Feature<LineString>,
dropEvent?: TranslateEvent
) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,60 @@
import type { Type, NgZone } from '@angular/core';
import type { Store } from '@ngrx/store';
import type { UUID } from 'digital-fuesim-manv-shared';
import type { MapBrowserEvent, View } from 'ol';
import { Feature } from 'ol';
import { getTopRight } from 'ol/extent';
import { Point } from 'ol/geom';
import type { TranslateEvent } from 'ol/interaction/Translate';
import type VectorLayer from 'ol/layer/Vector';
import VectorLayer from 'ol/layer/Vector';
import type OlMap from 'ol/Map';
import type VectorSource from 'ol/source/Vector';
import VectorSource from 'ol/source/Vector';
import Icon from 'ol/style/Icon';
import Style from 'ol/style/Style';
import type { Subject } from 'rxjs';
import type { ExerciseService } from 'src/app/core/exercise.service';
import type { AppState } from 'src/app/state/app.state';
import { selectExerciseState } from 'src/app/state/application/selectors/exercise.selectors';
import { selectCurrentRole } from 'src/app/state/application/selectors/shared.selectors';
import { selectStateSnapshot } from 'src/app/state/get-state-snapshot';
import type { FeatureManager } from '../utility/feature-manager';
import type { OlMapInteractionsManager } from '../utility/ol-map-interactions-manager';
import type { OpenPopupOptions } from '../utility/popup-manager';

function calculateTopRightViewPoint(view: View) {
const extent = getTopRight(view.calculateExtent());
return new Point([extent[0]!, extent[1]!]);
}

export class DeleteFeatureManager implements FeatureManager<Point> {
readonly layer: VectorLayer<VectorSource<Point>>;
constructor(
private readonly store: Store<AppState>,
public readonly layer: VectorLayer<VectorSource<Point>>,
private readonly olMap: OlMap,
private readonly exerciseService: ExerciseService
) {
this.layer = new VectorLayer({
// These two settings prevent clipping during animation/interaction but cause a performance hit -> disable if needed
updateWhileAnimating: true,
updateWhileInteracting: true,
renderBuffer: 250,
source: new VectorSource<Point>(),
});
}
togglePopup$?: Subject<OpenPopupOptions<any, Type<any>>> | undefined;
register(
changePopup$: Subject<OpenPopupOptions<any, Type<any>> | undefined>,
destroy$: Subject<void>,
ngZone: NgZone,
mapInteractionsManager: OlMapInteractionsManager
) {
this.olMap.addLayer(this.layer);
mapInteractionsManager.addFeatureLayer(this.layer);
if (selectStateSnapshot(selectCurrentRole, this.store) === 'trainer') {
this.makeVisible();
}
}
public makeVisible() {
this.layer.setStyle(
new Style({
image: new Icon({
Expand All @@ -49,6 +76,7 @@ export class DeleteFeatureManager implements FeatureManager<Point> {
);
});
}

public onFeatureClicked(
event: MapBrowserEvent<any>,
feature: Feature<any>
Expand All @@ -60,9 +88,9 @@ export class DeleteFeatureManager implements FeatureManager<Point> {
}

public onFeatureDrop(
dropEvent: TranslateEvent,
droppedFeature: Feature<any>,
droppedOnFeature: Feature<Point>
droppedOnFeature: Feature<Point>,
dropEvent?: TranslateEvent
) {
const id = droppedFeature.getId() as UUID;
const exerciseState = selectStateSnapshot(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import type { NgZone } from '@angular/core';
import type { ImmutableJsonObject } from 'digital-fuesim-manv-shared';
import type { Feature } from 'ol';
import type { Geometry } from 'ol/geom';
import type { Geometry, Point } from 'ol/geom';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import type { Observable, Subject } from 'rxjs';
import { pairwise, startWith, takeUntil } from 'rxjs';
import { handleChanges } from 'src/app/shared/functions/handle-changes';
import { generateChangedProperties } from '../utility/generate-changed-properties';

/**
Expand Down Expand Up @@ -87,6 +93,42 @@ export abstract class ElementManager<
public getElementFromFeature(feature: Feature<any>) {
return feature.get(featureElementKey);
}
/**
* @param renderBuffer The size of the largest symbol, line width or label on the highest zoom level.
*/
protected createElementLayer<LayerGeometry extends Geometry = Point>(
renderBuffer = 250
) {
return new VectorLayer({
// These two settings prevent clipping during animation/interaction but cause a performance hit -> disable if needed
updateWhileAnimating: true,
updateWhileInteracting: true,
renderBuffer,
source: new VectorSource<LayerGeometry>(),
});
}

protected registerChangeHandlers(
elementDictionary$: Observable<{ [id: string]: Element }>,
destroy$: Subject<void>,
ngZone: NgZone,
createHandler?: (newElement: Element) => void,
deleteHandler?: (deletedElement: Element) => void,
changeHandler?: (oldElement: Element, newElement: Element) => void
) {
elementDictionary$
.pipe(startWith({}), pairwise(), takeUntil(destroy$))
.subscribe(([oldElementDictionary, newElementDictionary]) => {
// run outside angular zone for better performance
ngZone.runOutsideAngular(() => {
handleChanges(oldElementDictionary, newElementDictionary, {
createHandler,
deleteHandler,
changeHandler,
});
});
});
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,61 @@
import type { Type, NgZone } from '@angular/core';
import type { Store } from '@ngrx/store';
import type { MapImage, UUID } from 'digital-fuesim-manv-shared';
import type { Feature, MapBrowserEvent } from 'ol';
import type Point from 'ol/geom/Point';
import type VectorLayer from 'ol/layer/Vector';
import type OlMap from 'ol/Map';
import type VectorSource from 'ol/source/Vector';
import type { Subject } from 'rxjs';
import type { ExerciseService } from 'src/app/core/exercise.service';
import type { AppState } from 'src/app/state/app.state';
import { selectCurrentRole } from 'src/app/state/application/selectors/shared.selectors';
import {
selectCurrentRole,
selectVisibleMapImages,
} from 'src/app/state/application/selectors/shared.selectors';
import { selectStateSnapshot } from 'src/app/state/get-state-snapshot';
import { MapImagePopupComponent } from '../shared/map-image-popup/map-image-popup.component';
import type { OlMapInteractionsManager } from '../utility/ol-map-interactions-manager';
import { PointGeometryHelper } from '../utility/point-geometry-helper';
import { ImagePopupHelper } from '../utility/popup-helper';
import type { OpenPopupOptions } from '../utility/popup-manager';
import { ImageStyleHelper } from '../utility/style-helper/image-style-helper';
import { MoveableFeatureManager } from './moveable-feature-manager';

export class MapImageFeatureManager extends MoveableFeatureManager<MapImage> {
public register(
changePopup$: Subject<OpenPopupOptions<any, Type<any>> | undefined>,
destroy$: Subject<void>,
ngZone: NgZone,
mapInteractionsManager: OlMapInteractionsManager
): void {
super.registerFeatureElementManager(
this.store.select(selectVisibleMapImages),
changePopup$,
destroy$,
ngZone,
mapInteractionsManager
);
}
private readonly imageStyleHelper = new ImageStyleHelper(
(feature) => (this.getElementFromFeature(feature) as MapImage).image
);
private readonly popupHelper = new ImagePopupHelper(this.olMap, this.layer);

constructor(
olMap: OlMap,
layer: VectorLayer<VectorSource<Point>>,
exerciseService: ExerciseService,
private readonly store: Store<AppState>
) {
super(
olMap,
layer,
(targetPosition, mapImage) => {
exerciseService.proposeAction({
type: '[MapImage] Move MapImage',
mapImageId: mapImage.id,
targetPosition,
});
},
new PointGeometryHelper()
new PointGeometryHelper(),
10_000
);
this.layer.setStyle((feature, resolution) => {
const style = this.imageStyleHelper.getStyle(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
import type { Type, NgZone } from '@angular/core';
import type { Store } from '@ngrx/store';
import type { Material, UUID } from 'digital-fuesim-manv-shared';
import { normalZoom } from 'digital-fuesim-manv-shared';
import type { Feature, MapBrowserEvent } from 'ol';
import type Point from 'ol/geom/Point';
import type VectorLayer from 'ol/layer/Vector';
import type OlMap from 'ol/Map';
import type VectorSource from 'ol/source/Vector';
import type { Subject } from 'rxjs';
import type { ExerciseService } from 'src/app/core/exercise.service';
import type { AppState } from 'src/app/state/app.state';
import { selectVisibleMaterials } from 'src/app/state/application/selectors/shared.selectors';
import { MaterialPopupComponent } from '../shared/material-popup/material-popup.component';
import type { OlMapInteractionsManager } from '../utility/ol-map-interactions-manager';
import { PointGeometryHelper } from '../utility/point-geometry-helper';
import { ImagePopupHelper } from '../utility/popup-helper';
import type { OpenPopupOptions } from '../utility/popup-manager';
import { ImageStyleHelper } from '../utility/style-helper/image-style-helper';
import { NameStyleHelper } from '../utility/style-helper/name-style-helper';
import { MoveableFeatureManager } from './moveable-feature-manager';

export class MaterialFeatureManager extends MoveableFeatureManager<Material> {
public register(
changePopup$: Subject<OpenPopupOptions<any, Type<any>> | undefined>,
destroy$: Subject<void>,
ngZone: NgZone,
mapInteractionsManager: OlMapInteractionsManager
): void {
super.registerFeatureElementManager(
this.store.select(selectVisibleMaterials),
changePopup$,
destroy$,
ngZone,
mapInteractionsManager
);
}
private readonly imageStyleHelper = new ImageStyleHelper(
(feature) => (this.getElementFromFeature(feature) as Material).image
);
Expand All @@ -33,12 +51,11 @@ export class MaterialFeatureManager extends MoveableFeatureManager<Material> {

constructor(
olMap: OlMap,
layer: VectorLayer<VectorSource<Point>>,
private readonly store: Store<AppState>,
exerciseService: ExerciseService
) {
super(
olMap,
layer,
(targetPosition, material) => {
exerciseService.proposeAction({
type: '[Material] Move material',
Expand Down
Loading