Skip to content

Commit

Permalink
refactor: regions service (#1875)
Browse files Browse the repository at this point in the history
Co-authored-by: Julien Schneider <[email protected]>
  • Loading branch information
domsteinbach and derschnee68 authored Nov 5, 2024
1 parent 6346a08 commit a2afd80
Show file tree
Hide file tree
Showing 13 changed files with 349 additions and 301 deletions.
65 changes: 32 additions & 33 deletions libs/vre/shared/app-representations/src/lib/region.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { Injectable } from '@angular/core';
import { ReadResourceSequence } from '@dasch-swiss/dsp-js';
import { DspResource, GenerateProperty } from '@dasch-swiss/vre/shared/app-common';
import { IncomingService } from '@dasch-swiss/vre/shared/app-common-to-move';
import { BehaviorSubject, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { BehaviorSubject, Subject } from 'rxjs';
import { map, takeUntil, tap } from 'rxjs/operators';

/**
* Regions, also called annotations, are used to mark specific areas on an image.
* This service handles the loading, display and selection of a resource's regions.
*/
@Injectable()
export class RegionService {
private _resource!: DspResource;
private _resourceId!: string;

private _regionsSubject = new BehaviorSubject<DspResource[]>([]);
regions$ = this._regionsSubject.asObservable();
Expand All @@ -22,47 +23,45 @@ export class RegionService {
private _showRegions = new BehaviorSubject(false);
showRegions$ = this._showRegions.asObservable();

private _highlightRegion = new BehaviorSubject<string | null>(null);
highlightRegion$ = this.showRegions$.pipe(
switchMap(value => (value ? this._highlightRegion.asObservable() : of(null)))
);
private _selectedRegion = new BehaviorSubject<string | null>(null);
selectedRegion$ = this._selectedRegion.asObservable();

private _imageIsLoadedSubject = new BehaviorSubject(false);
imageIsLoaded$ = this._imageIsLoadedSubject.asObservable();
private _ngUnsubscribe = new Subject<void>();

constructor(private _incomingService: IncomingService) {}

onInit(resource: DspResource) {
this._resource = resource;
this.updateRegions();
/** This method acts as a constructor. */
initialize(resourceId: string) {
this._resourceId = resourceId;
this.updateRegions$().pipe(takeUntil(this._ngUnsubscribe)).subscribe();
}

showRegions(value: boolean) {
this._showRegions.next(value);
updateRegions$() {
return this._getIncomingRegions(this._resourceId).pipe(
tap(res => {
this._regionsSubject.next(res);
})
);
}

updateRegions() {
this._incomingService
.getIncomingRegions(this._resource.res.id, 0)
.pipe(
map(regions =>
(regions as ReadResourceSequence).resources.map(_resource => {
const z = new DspResource(_resource);
z.resProps = GenerateProperty.regionProperty(_resource);
return z;
})
)
)
.subscribe(res => {
this._regionsSubject.next(res);
});
showRegions(value: boolean) {
this._showRegions.next(value);
}

highlightRegion(regionIri: string) {
this._highlightRegion.next(regionIri);
selectRegion(regionIri: string) {
this._selectedRegion.next(regionIri);
}

imageIsLoaded() {
this._imageIsLoadedSubject.next(true);
private _getIncomingRegions(resourceId: string) {
const offset = 0;
return this._incomingService.getIncomingRegions(resourceId, offset).pipe(
map(regions =>
(regions as ReadResourceSequence).resources.map(_resource => {
const z = new DspResource(_resource);
z.resProps = GenerateProperty.regionProperty(_resource);
return z;
})
)
);
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,126 @@
import { Injectable } from '@angular/core';
import { Point2D } from '@dasch-swiss/dsp-js';
import { AppError } from '@dasch-swiss/vre/shared/app-error-handler';
import { AccessTokenService } from '@dasch-swiss/vre/shared/app-session';
import * as OpenSeadragon from 'openseadragon';
import OpenSeadragon from 'openseadragon';
import { Subject } from 'rxjs';
import { osdViewerConfig } from './osd-viewer.config';

@Injectable()
export class OpenSeaDragonService {
private _viewer!: OpenSeadragon.Viewer;
interface Overlay {
startPoint: Point2D;
endPoint: Point2D;
imageSize: Point2D;
overlay: Element;
}

get viewer(): OpenSeadragon.Viewer {
@Injectable({ providedIn: 'root' })
export class OpenSeaDragonService {
get viewer() {
return this._viewer;
}

set viewer(htmlElement: HTMLElement) {
const accessToken = this._accessToken.getAccessToken();
private readonly _OVERLAY_COLOR = 'rgba(255,0,0,0.3)';
private readonly ZOOM_FACTOR = 0.2;

private _viewer!: OpenSeadragon.Viewer;

private _rectangleInDrawing: {
overlayElement: HTMLElement;
startPos: OpenSeadragon.Point;
endPos?: OpenSeadragon.Point;
} | null = null;

private _createdRectangleSubject = new Subject<Overlay>();
createdRectangle$ = this._createdRectangleSubject.asObservable();

constructor(private _accessToken: AccessTokenService) {}

onInit(htmlElement: HTMLElement) {
const viewerConfig: OpenSeadragon.Options = {
...osdViewerConfig,
element: htmlElement,
loadTilesWithAjax: true,
};

const accessToken = this._accessToken.getAccessToken();
if (accessToken) {
viewerConfig.ajaxHeaders = {
Authorization: `Bearer ${accessToken}`,
};
}

this._viewer = new OpenSeadragon.Viewer(viewerConfig);
this._trackClickEvents(this._viewer);
}

constructor(private _accessToken: AccessTokenService) {}
zoom(direction: 1 | -1) {
this._viewer.viewport.zoomBy(1 + direction * this.ZOOM_FACTOR);
}

private _trackClickEvents(viewer: OpenSeadragon.Viewer) {
return new OpenSeadragon.MouseTracker({
element: viewer.canvas,
pressHandler: event => {
if (viewer.isMouseNavEnabled()) {
return;
}

const overlayElement: HTMLElement = document.createElement('div');
overlayElement.style.background = this._OVERLAY_COLOR;
const viewportPos = viewer.viewport.pointFromPixel((event as OpenSeadragon.ViewerEvent).position!);
viewer.addOverlay(overlayElement, new OpenSeadragon.Rect(viewportPos.x, viewportPos.y, 0, 0));
this._rectangleInDrawing = {
overlayElement,
startPos: viewportPos,
};
},
dragHandler: event => {
if (viewer.isMouseNavEnabled()) {
return;
}

if (!this._rectangleInDrawing) {
throw new AppError('Rectangle is not set');
}

const viewPortPos = viewer.viewport.pointFromPixel((event as OpenSeadragon.ViewerEvent).position!);
const diffX = viewPortPos.x - this._rectangleInDrawing.startPos.x;
const diffY = viewPortPos.y - this._rectangleInDrawing.startPos.y;
const location = new OpenSeadragon.Rect(
Math.min(this._rectangleInDrawing.startPos.x, this._rectangleInDrawing.startPos.x + diffX),
Math.min(this._rectangleInDrawing.startPos.y, this._rectangleInDrawing.startPos.y + diffY),
Math.abs(diffX),
Math.abs(diffY)
);

viewer.updateOverlay(this._rectangleInDrawing.overlayElement, location);
this._rectangleInDrawing.endPos = viewPortPos;
},
releaseHandler: () => {
if (viewer.isMouseNavEnabled()) {
return;
}

if (!this._rectangleInDrawing) {
throw new AppError('Rectangle is not set');
}

if (!this._rectangleInDrawing.endPos) {
return;
}

const imageSize = viewer.world.getItemAt(0).getContentSize();
const startPoint = viewer.viewport.viewportToImageCoordinates(this._rectangleInDrawing.startPos);
const endPoint = viewer.viewport.viewportToImageCoordinates(this._rectangleInDrawing.endPos);
this._createdRectangleSubject.next({
startPoint,
endPoint,
imageSize,
overlay: this._rectangleInDrawing.overlayElement,
});

this._rectangleInDrawing = null;
},
});
}
}
Loading

0 comments on commit a2afd80

Please sign in to comment.