Skip to content

Commit

Permalink
[PLAT-4879] Add controller for fetching PMI annotations (#631)
Browse files Browse the repository at this point in the history
* Use latest protos

* Add controller for accessing PMI annotations
  • Loading branch information
danschultz authored Sep 26, 2024
1 parent 0aeba56 commit ae3af3a
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 5 deletions.
2 changes: 1 addition & 1 deletion packages/viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@vertexvis/geometry": "0.22.0",
"@vertexvis/html-templates": "0.22.0",
"@vertexvis/scene-tree-protos": "^0.1.21",
"@vertexvis/scene-view-protos": "^0.4.5",
"@vertexvis/scene-view-protos": "^0.5.0",
"@vertexvis/stream-api": "0.22.0",
"@vertexvis/utils": "0.22.0",
"@vertexvis/web-workers": "^0.1.0",
Expand Down
11 changes: 11 additions & 0 deletions packages/viewer/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
AnnotationState,
} from './lib/annotations/controller';
import { ModelViewController } from './lib/model-views/controller';
import { PmiController } from './lib/pmi';
import { TapEventDetails } from './lib/interactions/tapEventDetails';
import { ConnectionStatus } from './components/viewer/viewer';
import {
Expand Down Expand Up @@ -581,6 +582,11 @@ export namespace Components {
* Specifies how phantom parts should appear. The opacity must be between 0 and 1, where 0 is completely hidden and 1 is completely visible.
*/
phantom?: PhantomOptions;
/**
* The controller for accessing and viewing PMI.
* @readonly
*/
pmi: PmiController | undefined;
/**
* Registers and initializes an interaction handler with the viewer. Returns a `Disposable` that should be used to deregister the interaction handler.
*
Expand Down Expand Up @@ -2482,6 +2488,11 @@ declare namespace LocalJSX {
* Specifies how phantom parts should appear. The opacity must be between 0 and 1, where 0 is completely hidden and 1 is completely visible.
*/
phantom?: PhantomOptions;
/**
* The controller for accessing and viewing PMI.
* @readonly
*/
pmi?: PmiController | undefined;
/**
* An optional value that will debounce frame updates when resizing this viewer element.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/viewer/src/components/viewer/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
| `modelViews` | -- | The controller for accessing model views associated with the scene view. | `ModelViewController \| undefined` | `undefined` |
| `noDefaultLights` | `no-default-lights` | Specifies whether to use the default lights for the scene. When false, default lights are used. When true, no default lights are used, and the lights must be specified separately. | `boolean` | `false` |
| `phantom` | -- | Specifies how phantom parts should appear. The opacity must be between 0 and 1, where 0 is completely hidden and 1 is completely visible. | `PhantomOptions \| undefined` | `{ opacity: 0.1 }` |
| `pmi` | -- | The controller for accessing and viewing PMI. | `PmiController \| undefined` | `undefined` |
| `resizeDebounce` | `resize-debounce` | An optional value that will debounce frame updates when resizing this viewer element. | `number` | `100` |
| `rotateAroundTapPoint` | `rotate-around-tap-point` | Enables or disables the default rotation interaction being changed to rotate around the pointer down location. | `boolean` | `true` |
| `sceneComparison` | -- | Specifies if and how to compare to another scene | `SceneComparisonOptions \| undefined` | `undefined` |
Expand Down
14 changes: 14 additions & 0 deletions packages/viewer/src/components/viewer/viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import { TouchInteractionHandler } from '../../lib/interactions/touchInteraction
import { fromPbFrameOrThrow } from '../../lib/mappers';
import { paintTime, Timing } from '../../lib/meters';
import { ModelViewController } from '../../lib/model-views/controller';
import { PmiController } from '../../lib/pmi';
import {
CanvasRenderer,
createCanvasRenderer,
Expand Down Expand Up @@ -348,6 +349,13 @@ export class Viewer {
*/
@Prop({ mutable: true }) public modelViews: ModelViewController | undefined;

/**
* The controller for accessing and viewing PMI.
*
* @readonly
*/
@Prop({ mutable: true }) public pmi: PmiController | undefined;

/**
* Emits an event whenever the user taps or clicks a location in the viewer.
* The event includes the location of the tap or click.
Expand Down Expand Up @@ -517,6 +525,12 @@ export class Viewer {
() => this.deviceId
);

this.pmi = new PmiController(
client,
() => this.token,
() => this.deviceId
);

this.updateStreamAttributes();
this.stateMap.cursorManager.onChanged.on(() => this.handleCursorChanged());
}
Expand Down
1 change: 1 addition & 0 deletions packages/viewer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export * from './lib/interactions/keyInteraction';
export * from './lib/measurement';
export * from './lib/model-views';
export * from './lib/pins';
export * from './lib/pmi';
export {
Camera,
CrossSectioner,
Expand Down
49 changes: 49 additions & 0 deletions packages/viewer/src/lib/pmi/__tests__/controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
jest.mock(
'@vertexvis/scene-view-protos/sceneview/protos/scene_view_api_pb_service'
);

import { SceneViewAPIClient } from '@vertexvis/scene-view-protos/sceneview/protos/scene_view_api_pb_service';

import { mockGrpcUnaryResult, random } from '../../../testing';
import { makeListPmiAnnotationsResponse } from '../../../testing/pmi';
import { PmiController } from '../controller';
import { mapListPmiAnnotationsResponseOrThrow } from '../mapper';

describe(PmiController, () => {
const jwt = random.string();
const deviceId = random.string();

describe(PmiController.prototype.listAnnotations, () => {
it('fetches page of annotations', async () => {
const { controller, client } = makePmiController(jwt, deviceId);
const expected = makeListPmiAnnotationsResponse();

(client.listPmiAnnotations as jest.Mock).mockImplementationOnce(
mockGrpcUnaryResult(expected)
);

const res = await controller.listAnnotations();
expect(res).toEqual(
mapListPmiAnnotationsResponseOrThrow(expected.toObject())
);
});
});

function makePmiController(
jwt: string,
deviceId: string
): {
controller: PmiController;
client: SceneViewAPIClient;
} {
const client = new SceneViewAPIClient('https://example.com');
return {
client,
controller: new PmiController(
client,
() => jwt,
() => deviceId
),
};
}
});
59 changes: 59 additions & 0 deletions packages/viewer/src/lib/pmi/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Pager } from '@vertexvis/scene-view-protos/core/protos/paging_pb';
import { Uuid2l } from '@vertexvis/scene-view-protos/core/protos/uuid_pb';
import {
ListPmiAnnotationsRequest,
ListPmiAnnotationsResponse,
} from '@vertexvis/scene-view-protos/sceneview/protos/scene_view_api_pb';
import { SceneViewAPIClient } from '@vertexvis/scene-view-protos/sceneview/protos/scene_view_api_pb_service';
import { UUID } from '@vertexvis/utils';

import { createMetadata, JwtProvider, requestUnary } from '../grpc';
import { mapListPmiAnnotationsResponseOrThrow } from './mapper';
import { PmiAnnotationListResponse } from './types';

export interface ListAnnotationsOptions {
modelViewId?: UUID.UUID;
cursor?: string;
size?: number;
}

export class PmiController {
public constructor(
private client: SceneViewAPIClient,
private jwtProvider: JwtProvider,
private deviceIdProvider: () => string | undefined
) {}

public async listAnnotations({
modelViewId,
cursor,
size = 50,
}: ListAnnotationsOptions = {}): Promise<PmiAnnotationListResponse> {
const res: ListPmiAnnotationsResponse = await requestUnary(
async (handler) => {
const deviceId = this.deviceIdProvider();
const meta = await createMetadata(this.jwtProvider, deviceId);
const req = new ListPmiAnnotationsRequest();

if (modelViewId != null) {
const { msb, lsb } = UUID.toMsbLsb(modelViewId);
const id = new Uuid2l();
id.setMsb(msb);
id.setLsb(lsb);
req.setModelViewId(id);
}

const page = new Pager();
page.setLimit(size);
if (cursor != null) {
page.setCursor(cursor);
}
req.setPage(page);

this.client.listPmiAnnotations(req, meta, handler);
}
);

return mapListPmiAnnotationsResponseOrThrow(res.toObject());
}
}
2 changes: 2 additions & 0 deletions packages/viewer/src/lib/pmi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './controller';
export * from './types';
27 changes: 27 additions & 0 deletions packages/viewer/src/lib/pmi/mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { PmiAnnotation as PBPmiAnnotation } from '@vertexvis/scene-view-protos/core/protos/model_views_pb';
import { ListPmiAnnotationsResponse } from '@vertexvis/scene-view-protos/sceneview/protos/scene_view_api_pb';
import { Mapper as M } from '@vertexvis/utils';

import { fromPbUuid2l, mapCursor } from '../mappers';
import { PmiAnnotation, PmiAnnotationListResponse } from './types';

const mapModelView: M.Func<PBPmiAnnotation.AsObject, PmiAnnotation> =
M.defineMapper(
M.read(M.mapRequiredProp('id', fromPbUuid2l), M.getProp('displayName')),
([id, displayName]) => ({ id, displayName })
);

const mapListPmiAnnotationsResponse: M.Func<
ListPmiAnnotationsResponse.AsObject,
PmiAnnotationListResponse
> = M.defineMapper(
M.read(
M.mapProp('annotationsList', M.mapArray(mapModelView)),
M.mapProp('nextPageCursor', mapCursor)
),
([annotations, next]) => ({ annotations, paging: { next } })
);

export const mapListPmiAnnotationsResponseOrThrow = M.ifInvalidThrow(
mapListPmiAnnotationsResponse
);
13 changes: 13 additions & 0 deletions packages/viewer/src/lib/pmi/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { UUID } from '@vertexvis/utils';

import { PagingLinks } from '../types/pagination';

export interface PmiAnnotation {
id: UUID.UUID;
displayName: string;
}

export interface PmiAnnotationListResponse {
annotations: PmiAnnotation[];
paging: PagingLinks;
}
24 changes: 24 additions & 0 deletions packages/viewer/src/testing/pmi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { PmiAnnotation } from '@vertexvis/scene-view-protos/core/protos/model_views_pb';
import { ListPmiAnnotationsResponse } from '@vertexvis/scene-view-protos/sceneview/protos/scene_view_api_pb';
import { UUID } from '@vertexvis/utils';

import { random } from './random';
import { makeUuid2l } from './sceneView';

export function makeListPmiAnnotationsResponse(
annotations: PmiAnnotation[] = [makeAnnotation(), makeAnnotation()]
): ListPmiAnnotationsResponse {
const res = new ListPmiAnnotationsResponse();
res.setAnnotationsList(annotations);
return res;
}

export function makeAnnotation(
id: UUID.UUID = UUID.create(),
displayName: string = random.string()
): PmiAnnotation {
const annotation = new PmiAnnotation();
annotation.setId(makeUuid2l(id));
annotation.setDisplayName(displayName);
return annotation;
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2239,10 +2239,10 @@
resolved "https://registry.yarnpkg.com/@vertexvis/scene-tree-protos/-/scene-tree-protos-0.1.25.tgz#e41223ed3a5aef9609c7e42c2687649c968c0fd1"
integrity sha512-MjeVWG41f6p4en9dtYdV852POeDMMoyqhdTIYJMuUBJvXlW9YjINWXHB+Vd5uNZ1xumU6Rahx1KqTxcOYkPxhw==

"@vertexvis/scene-view-protos@^0.4.5":
version "0.4.8"
resolved "https://registry.yarnpkg.com/@vertexvis/scene-view-protos/-/scene-view-protos-0.4.8.tgz#86921570af4415b0b6ed414af9ac10f38c7441ff"
integrity sha512-dQ954cxiAkn10ZVW9C0PcT9bjmefSX2VI4aPuDMvjk1IIJijvB5OcdIrUpnUUKUr53h92jBqyH8+rgIBB/BB3w==
"@vertexvis/scene-view-protos@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@vertexvis/scene-view-protos/-/scene-view-protos-0.5.0.tgz#2e9c7a0b7d72885559cf5ba53975aaaba3d2631e"
integrity sha512-XHmukptOgOrs4UJlk1iUVh/mhJkeQJwmUS7beutA1NQ5Q//t9SYxp1mOQJPyIp4O/I/xXQ82CKW/tQ25DsUHrg==

"@vertexvis/typescript-config-vertexvis@1.1.0":
version "1.1.0"
Expand Down

0 comments on commit ae3af3a

Please sign in to comment.