Skip to content

Support Google Photorealistic 3D Tiles in iTwin.js #8104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 77 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
ea82b02
Investigation on supporting GP3DT
matmarchand May 12, 2025
e1a716c
Store all URL params test
eringram May 13, 2025
69e695e
merge
markschlosseratbentley May 14, 2025
fc368df
handle base url better in case of a preceding slash
markschlosseratbentley May 14, 2025
690f580
split G3DT tile handling into separate reality data source implementa…
markschlosseratbentley May 15, 2025
81577d7
add file
markschlosseratbentley May 15, 2025
445fd1f
cleanup impl, add unit tests
andremig-bentley May 15, 2025
50ccc96
Clean up search parameters & add tests
eringram May 15, 2025
8fd1ed6
Clean up query param handling
eringram May 19, 2025
62e4e8c
accumulate glTF attribution information + store on tiles.
markschlosseratbentley May 19, 2025
a431d82
Add attributions from tiles
eringram May 19, 2025
55885c7
Merge branch 'master' into google-3d-tiles
eringram May 19, 2025
f539c7f
handle gp3dt auth more cleanly
markschlosseratbentley May 20, 2025
885dc92
rename G3DT to GP3DT
markschlosseratbentley May 20, 2025
ce3173d
gp3dt rename for test file
markschlosseratbentley May 20, 2025
850b444
Merge branch 'master' into google-3d-tiles
markschlosseratbentley May 20, 2025
92f5ee5
copyright internal
markschlosseratbentley May 20, 2025
36827e3
docs
markschlosseratbentley May 20, 2025
18f7130
docs + extract-api
markschlosseratbentley May 20, 2025
b0703bc
WIP google tiles decorator
eringram May 20, 2025
6bdab4e
Merge branch 'master' into google-3d-tiles
eringram May 20, 2025
9f7f54c
add RealityDataFormatRegistry on IModelApp. Move gp3dtKey from TileAd…
markschlosseratbentley May 21, 2025
7c5c126
Merge branch 'google-3d-tiles' of https://github.com/iTwin/itwinjs-co…
markschlosseratbentley May 21, 2025
d91ba63
Merge branch 'master' into google-3d-tiles
markschlosseratbentley May 21, 2025
da7893d
rush change
markschlosseratbentley May 21, 2025
5d4a738
rush extract-api
markschlosseratbentley May 21, 2025
fb3aa4f
remove gp3dtKey from TileAdmin
markschlosseratbentley May 21, 2025
ff601cd
extract-api
markschlosseratbentley May 21, 2025
163771e
Remove collectGltfAttributions option, always collect
eringram May 21, 2025
07ea254
extract-api
markschlosseratbentley May 21, 2025
f24009f
add getGooglePhotorealistic3DTilesURL function
markschlosseratbentley May 21, 2025
b76a766
alpha in imodelapp
markschlosseratbentley May 21, 2025
0da3381
extract-api
markschlosseratbentley May 21, 2025
3d4298d
Rm GoogleMapDecorator from map-layers-formats, rm copyright from Tile
eringram May 21, 2025
24a24a2
extract-api
eringram May 21, 2025
eaf2611
Merge branch 'master' into google-3d-tiles
eringram May 21, 2025
76c6097
Remove unnecessary export
eringram May 21, 2025
9a3778b
Merge branch 'google-3d-tiles' of https://github.com/iTwin/itwinjs-co…
eringram May 21, 2025
91231e0
Clean up comments
eringram May 21, 2025
66e52cb
Rush change
eringram May 21, 2025
7a47ad4
Merge branch 'master' into google-3d-tiles
markschlosseratbentley May 22, 2025
0d0b6b0
Merge branch 'master' into google-3d-tiles
eringram May 22, 2025
32ba8d4
rework registry
markschlosseratbentley May 22, 2025
a135b7f
RealityTileTreeReference test WIP
eringram May 22, 2025
30766b5
Merge branch 'master' into google-3d-tiles
eringram May 22, 2025
2abf4ed
Merge branch 'master' into google-3d-tiles
eringram May 23, 2025
bd0e5d7
Merge branch 'master' into google-3d-tiles
eringram May 23, 2025
d5ee6bd
Unit test progress
eringram May 23, 2025
2d76949
Merge branch 'master' into google-3d-tiles
eringram May 27, 2025
42e8436
RealityDataSourceGP3DTProvider WIP
eringram May 27, 2025
10bce2d
Merge branch 'master' into google-3d-tiles
eringram May 28, 2025
536ac63
Fix build errors
eringram May 28, 2025
86508b2
Add assets, remove whitespace change
eringram May 28, 2025
48dbc7e
remove dead code/api
markschlosseratbentley May 29, 2025
b8cb0d1
remove old API
markschlosseratbentley May 29, 2025
78f0596
export changes; extract-api
markschlosseratbentley May 29, 2025
8170f58
remove common changelog
markschlosseratbentley May 29, 2025
7b9d785
rush change
markschlosseratbentley May 29, 2025
dda78df
rush update
markschlosseratbentley May 29, 2025
b2648f1
Merge branch 'master' into google-3d-tiles
eringram May 29, 2025
25950bd
Add option for user to pass getAuthToken function to provider
eringram May 29, 2025
f5d39bf
Merge branch 'master' into google-3d-tiles
eringram May 30, 2025
54806b0
Make API key optional, docs
eringram May 30, 2025
8f4cd4f
Move decorator from tileTreeRef to rdSourceProvider
eringram May 30, 2025
d187797
Merge branch 'master' into google-3d-tiles
eringram May 30, 2025
da4c404
Merge branch 'master' into google-3d-tiles
eringram Jun 2, 2025
1721498
Fix RealityModelTileTree tests
eringram Jun 2, 2025
af3baf0
Merge branch 'master' into google-3d-tiles
eringram Jun 2, 2025
844a26f
Merge branch 'master' into google-3d-tiles
eringram Jun 3, 2025
1c46382
Fix unit tests
eringram Jun 3, 2025
c66ca83
Merge branch 'master' into google-3d-tiles
eringram Jun 3, 2025
73ad054
Test cleanup
eringram Jun 3, 2025
0fb4e2c
Merge branch 'master' into google-3d-tiles
eringram Jun 3, 2025
99ab26a
Show copyrights on screen WIP
eringram Jun 3, 2025
d9505b3
Merge branch 'master' into google-3d-tiles
eringram Jun 4, 2025
ca0f014
Use getCopyrights function
eringram Jun 4, 2025
0da8a87
Improve tests & extract-api
eringram Jun 4, 2025
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
48 changes: 48 additions & 0 deletions common/api/core-frontend.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3764,9 +3764,15 @@ export function getCesiumAssetUrl(osmAssetId: number, requestKey: string): strin
// @public
export function getCompressedJpegFromCanvas(canvas: HTMLCanvasElement, maxBytes?: number, minCompressionQuality?: number): string | undefined;

// @internal
export function getCopyrights(vp: ScreenViewport): Map<string, number>;

// @internal (undocumented)
export function getFrustumPlaneIntersectionDepthRange(frustum: Frustum, plane: Plane3dByOriginAndUnitNormal): Range1d;

// @alpha
export function getGooglePhotorealistic3DTilesURL(): string;

// @public
export function getImageSourceFormatForMimeType(mimeType: string): ImageSourceFormat | undefined;

Expand Down Expand Up @@ -4075,6 +4081,8 @@ export interface GltfReaderArgs {

// @internal
export interface GltfReaderResult extends TileContent {
// (undocumented)
copyright?: string;
// (undocumented)
range?: AxisAlignedBox3d;
// (undocumented)
Expand All @@ -4095,6 +4103,15 @@ export interface GLTimerResult {
nanoseconds: number;
}

// @internal
export class GoogleMapsDecorator implements Decorator {
constructor(showCreditsOnScreen?: boolean);
activate(mapType: GoogleMapsMapTypes): Promise<boolean>;
decorate: (context: DecorateContext) => void;
// (undocumented)
readonly logo: LogoDecoration;
}

// @public
export type GpuMemoryLimit = "none" | "default" | "aggressive" | "relaxed" | number;

Expand Down Expand Up @@ -5620,6 +5637,20 @@ export enum LockedStates {
Y_BM = 2
}

// @internal
export class LogoDecoration implements CanvasDecoration {
// (undocumented)
activate(sprite: Sprite): Promise<boolean>;
// (undocumented)
decorate(context: DecorateContext): void;
drawDecoration(ctx: CanvasRenderingContext2D): void;
get isLoaded(): boolean;
moveToLowerLeftCorner(context: DecorateContext): boolean;
set offset(offset: Point3d | undefined);
get offset(): Point3d | undefined;
readonly position: Point3d;
}

// @public
export class LookAndMoveTool extends ViewManip {
constructor(vp: ScreenViewport, oneShot?: boolean, isDraggingRequired?: boolean);
Expand Down Expand Up @@ -8115,11 +8146,24 @@ export namespace RealityDataSource {
export function createOrbitGtBlobPropsFromKey(rdSourceKey: RealityDataSourceKey): OrbitGtBlobProps | undefined;
// @alpha
export function fromKey(key: RealityDataSourceKey, iTwinId: GuidString | undefined): Promise<RealityDataSource | undefined>;
// @alpha
export function setBaseUrl(id: string): void;
}

// @alpha
export class RealityDataSourceGP3DTProvider implements RealityDataSourceProvider {
constructor(options: RealityDataSourceGP3DTProviderOptions);
// (undocumented)
createRealityDataSource(key: RealityDataSourceKey, iTwinId: GuidString | undefined): Promise<RealityDataSource | undefined>;
// (undocumented)
decorate(_context: DecorateContext): void;
initialize(): Promise<boolean>;
}

// @alpha
export interface RealityDataSourceProvider {
createRealityDataSource(key: RealityDataSourceKey, iTwinId: GuidString | undefined): Promise<RealityDataSource | undefined>;
decorate?(_context: DecorateContext): void;
}

// @alpha
Expand Down Expand Up @@ -8217,6 +8261,10 @@ export class RealityTile extends Tile {
// @internal (undocumented)
computeVisibilityFactor(args: TileDrawArgs): number;
// @internal (undocumented)
get copyright(): string | undefined;
// @internal (undocumented)
protected _copyright?: string;
// @internal (undocumented)
disposeContents(): void;
// @internal (undocumented)
protected forceSelectRealityTile(): boolean;
Expand Down
6 changes: 6 additions & 0 deletions common/api/summary/core-frontend.exports.csv
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ internal;interface;GetAttachmentViewportArgs
public;function;getCenteredViewRect
public;function;getCesiumAssetUrl
public;function;getCompressedJpegFromCanvas
internal;function;getCopyrights
internal;function;getFrustumPlaneIntersectionDepthRange
alpha;function;getGooglePhotorealistic3DTilesURL
public;function;getImageSourceFormatForMimeType
public;function;getImageSourceMimeType
public;interface;GetPixelDataWorldPointArgs
Expand All @@ -241,6 +243,7 @@ internal;interface;GltfReaderArgs
internal;interface;GltfReaderResult
beta;interface;GltfTemplate
internal;interface;GLTimerResult
internal;class;GoogleMapsDecorator
public;type;GpuMemoryLimit
public;interface;GpuMemoryLimits
public;class;GraphicalEditingScope
Expand Down Expand Up @@ -346,6 +349,7 @@ public;enum;LocateFilterStatus
public;class;LocateOptions
public;class;LocateResponse
internal;enum;LockedStates
internal;class;LogoDecoration
public;class;LookAndMoveTool
public;interface;LookAtArgs
public;interface;LookAtOrthoArgs
Expand Down Expand Up @@ -507,6 +511,8 @@ alpha;function;createKeyFromBlobUrl
alpha;function;createKeyFromOrbitGtBlobProps
alpha;function;createOrbitGtBlobPropsFromKey
alpha;function;fromKey
alpha;function;setBaseUrl
alpha;class;RealityDataSourceGP3DTProvider
alpha;interface;RealityDataSourceProvider
alpha;class;RealityDataSourceProviderRegistry
public;interface;RealityMeshParams
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-common",
"comment": "",
"type": "none"
}
],
"packageName": "@itwin/core-common"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-frontend",
"comment": "Support Google Photorealistic 3D Tiles.",
"type": "none"
}
],
"packageName": "@itwin/core-frontend"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/map-layers-formats",
"comment": "",
"type": "none"
}
],
"packageName": "@itwin/map-layers-formats"
}
6 changes: 6 additions & 0 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions core/common/src/ContextRealityModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,22 @@ export interface OrbitGtBlobProps {
*/
export enum RealityDataProvider {
/**
* This is the legacy mode where the access to the 3d tiles is harcoded in ContextRealityModelProps.tilesetUrl property.
* It was use to support RealityMesh3DTiles, Terrain3DTiles, Cesium3DTiles
* You should use other mode when possible
* This is the legacy mode where the access to the 3d tiles is hardcoded in ContextRealityModelProps.tilesetUrl property.
* It was used to support RealityMesh3DTiles, Terrain3DTiles, Cesium3DTiles
* You should use other modes when possible
* @see [[RealityDataSource.createKeyFromUrl]] that will try to detect provider from an URL
*/
TilesetUrl = "TilesetUrl",
/**
* This is the legacy mode where the access to the 3d tiles is harcoded in ContextRealityModelProps.OrbitGtBlob property.
* It was use to support OrbitPointCloud (OPC) from other server than ContextShare
* You should use other mode when possible
* This is the legacy mode where the access to the 3d tiles is hardcoded in ContextRealityModelProps.OrbitGtBlob property.
* It was used to support OrbitPointCloud (OPC) from other server than ContextShare
* You should use other modes when possible
* @see [[RealityDataSource.createKeyFromOrbitGtBlobProps]] that will try to detect provider from an URL
*/
OrbitGtBlob = "OrbitGtBlob",
/**
* Will provide access url from realityDataId and iTwinId on contextShare for 3dTile storage format or OPC storage format
* This provider support all type of 3dTile storage fomat and OrbitPointCloud: RealityMesh3DTiles, Terrain3DTiles, Cesium3DTiles, OPC
* Will provide access url from realityDataId and iTwinId on contextShare for 3dTile storage format or OPC storage format
* This provider supports all types of 3dTile storage format and OrbitPointCloud: RealityMesh3DTiles, Terrain3DTiles, Cesium3DTiles, OPC
* @see [[RealityDataFormat]].
*/
ContextShare = "ContextShare",
Expand Down
4 changes: 3 additions & 1 deletion core/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@
"vitest": "^3.0.6",
"vite-multiple-assets": "^1.3.1",
"vite-plugin-static-copy": "2.2.0",
"webpack": "^5.97.1"
"webpack": "^5.97.1",
"sinon": "^17.0.2",
"@types/sinon": "^17.0.2"
},
"//dependencies": [
"NOTE: these dependencies should be only for things that DO NOT APPEAR IN THE API",
Expand Down
158 changes: 158 additions & 0 deletions core/frontend/src/GoogleMapsDecorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { Point3d } from "@itwin/core-geometry";
import { CanvasDecoration } from "./render/CanvasDecoration";
import { DecorateContext } from "./ViewContext";
import { IModelApp } from "./IModelApp";
import { Decorator } from "./ViewManager";
import { IconSprites, Sprite } from "./Sprites";
import { RealityTile } from "./tile/internal";
import { ScreenViewport } from "./Viewport";

/** Layer types that can be added to the map.
* @internal
*/
type GoogleMapsMapTypes = "roadmap" | "satellite" | "terrain";

/** A simple decorator that shows the logo at a given screen position.
* @internal
*/
export class LogoDecoration implements CanvasDecoration {
private _sprite?: Sprite;

/** The current position of the logo in view coordinates. */
public readonly position = new Point3d();

private _offset: Point3d | undefined;

public set offset(offset: Point3d | undefined) {
this._offset = offset;
}

/** The logo offset in view coordinates.*/
public get offset() {
return this._offset;
}

/** Move the logo to the lower left corner of the screen. */
public moveToLowerLeftCorner(context: DecorateContext): boolean {
if (!this._sprite || !this._sprite.isLoaded)
return false;

this.position.x = this._offset?.x ?? 0;
this.position.y = context.viewport.parentDiv.clientHeight - this._sprite.size.y;
if (this._offset?.y)
this.position.y -= this._offset.y;
return true;
}

/* TODO: Add other move methods as needed */

/** Indicate if the logo is loaded and ready to be drawn. */
public get isLoaded() { return this._sprite?.isLoaded ?? false; }

public async activate(sprite: Sprite): Promise<boolean> {
this._sprite = sprite;
return new Promise<boolean>((resolve, _reject) => {
sprite.loadPromise.then(() => {
resolve(true);
}).catch(() => {
resolve (false);
});
});
}

/** Draw this sprite onto the supplied canvas.
* @see [[CanvasDecoration.drawDecoration]]
*/
public drawDecoration(ctx: CanvasRenderingContext2D): void {
if (this.isLoaded) {
// Draw image with an origin at the top left corner
ctx.drawImage(this._sprite!.image!, 0, 0);
}
}

public decorate(context: DecorateContext) {
context.addCanvasDecoration(this);
}
}

/** A decorator that adds the Google Maps logo to the lower left corner of the screen.
* @internal
*/
export class GoogleMapsDecorator implements Decorator {
public readonly logo = new LogoDecoration();
private _showCreditsOnScreen?: boolean;

/** Create a new GoogleMapsDecorator.
* @param showCreditsOnScreen If true, the data attributions/copyrights from the Google Photorealistic 3D Tiles will be displayed on screen. The Google Maps logo will always be displayed.
*/
constructor(showCreditsOnScreen?: boolean) {
this._showCreditsOnScreen = showCreditsOnScreen;
}

/** Activate the logo based on the given map type. */
public async activate(mapType: GoogleMapsMapTypes): Promise<boolean> {
// Pick the logo that is the most visible on the background map
const imageName = mapType === "roadmap" ?
"google_on_white" :
"google_on_non_white";

// We need to move the logo right after the 'i.js' button
this.logo.offset = new Point3d(45, 10);

return this.logo.activate(IconSprites.getSpriteFromUrl(`${IModelApp.publicPath}images/${imageName}.png`));
};

/** Decorate implementation */
public decorate = (context: DecorateContext) => {
if (!this.logo.isLoaded)
return;
this.logo.moveToLowerLeftCorner(context);
this.logo.decorate(context);

if (!this._showCreditsOnScreen)
return;

// Get data attribution (copyright) text
const copyrightMap = getCopyrights(context.viewport);
// Order by most occurances to least
// See https://developers.google.com/maps/documentation/tile/create-renderer#display-attributions
const sortedCopyrights = [...copyrightMap.entries()].sort((a, b) => b[1] - a[1]);
const copyrightText = sortedCopyrights.map(([key]) => ` • ${key}`).join("");

// Create and add element, offset to leave space for i.js and Google logos
const elem = document.createElement("div");
elem.innerHTML = copyrightText;
elem.style.color = "white";
elem.style.fontSize = "10px";
elem.style.textWrap = "wrap";
elem.style.position = "absolute";
elem.style.bottom = "10px";
elem.style.left = "107px";

context.addHtmlDecoration(elem);
};
}

/** Get copyrights from tiles currently in the viewport.
* @internal
*/
export function getCopyrights(vp: ScreenViewport): Map<string, number> {
const tiles = IModelApp.tileAdmin.getTilesForUser(vp)?.selected;
const copyrightMap = new Map<string, number>();
if (tiles) {
for (const tile of tiles as Set<RealityTile>) {
if (tile.copyright) {
for (const copyright of tile.copyright?.split(";")) {
const currentCount = copyrightMap.get(copyright);
copyrightMap.set(copyright, currentCount ? currentCount + 1 : 1);
}
}
}
}
return copyrightMap;
}
Loading