Skip to content

Commit

Permalink
Merge pull request #6950 from TerriaJS/attribute-google-reality
Browse files Browse the repository at this point in the history
Google Photorealistic 3D Tiles attribution
  • Loading branch information
staffordsmith83 authored Oct 26, 2023
2 parents 2f0cec5 + d92ae3d commit ada05f2
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 70 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Fix bug in mismatched GeoJSON Feature `_id_` and TableMixin `rowId` - this was causing incorrect styling when using `filterByProperties` or features had `null` geometry
- Fix splitter for `GeoJsonMixin` (lines and polygon features only)
- Fix share links with picked features from `ProtomapsImageryProvider`
- Added on screen attribution and Google logo for Google Photorealistic 3D Tiles.
- Add `hideDefaultDescription` to `CatalogMemberTraits` - if true, then no generic default description will be shown when `description` is empty.
- [The next improvement]

Expand Down
110 changes: 65 additions & 45 deletions lib/Models/Cesium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ import { setViewerMode } from "./ViewerMode";

//import Cesium3DTilesInspector from "terriajs-cesium/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector";

type CreditDisplayElement = {
credit: Credit;
count: number;
};

// Intermediary
var cartesian3Scratch = new Cartesian3();
var enuToFixedScratch = new Matrix4();
Expand Down Expand Up @@ -129,7 +134,10 @@ export default class Cesium extends GlobeOrMap {
| MappableMixin.Instance
| /*TODO Cesium.Cesium3DTileset*/ any;

// Lightbox and on screen attributions from CreditDisplay
private cesiumDataAttributions: IObservableArray<string> = observable([]);
// Public because this is accessed from BottomLeftBar.tsx
cesiumScreenDataAttributions: IObservableArray<string> = observable([]);

// When true, feature picking is paused. This is useful for temporarily
// disabling feature picking when some other interaction mode wants to take
Expand Down Expand Up @@ -490,6 +498,7 @@ export default class Cesium extends GlobeOrMap {
const creditDisplay: CreditDisplay & {
_currentFrameCredits?: {
lightboxCredits: AssociativeArray;
screenCredits: AssociativeArray;
};
} = this.scene.frameState.creditDisplay;
const creditDisplayOldDestroy = creditDisplay.destroy;
Expand All @@ -505,48 +514,16 @@ export default class Cesium extends GlobeOrMap {
creditDisplayOldEndFrame.bind(creditDisplay)();

runInAction(() => {
const creditDisplayElements: {
credit: Credit;
count: number;
}[] = creditDisplay._currentFrameCredits!.lightboxCredits.values;

// sort credits by count (number of times they are added to map)
const credits = creditDisplayElements
.sort((credit1, credit2) => {
return credit2.count - credit1.count;
})
.map(({ credit }) => credit.html);

if (isEqual(credits, toJS(this.cesiumDataAttributions))) return;

// first remove ones that are not on the map anymore
// Iterate backwards because we're removing items.
for (let i = this.cesiumDataAttributions.length - 1; i >= 0; i--) {
const attribution = this.cesiumDataAttributions[i];
if (!credits.includes(attribution)) {
this.cesiumDataAttributions.remove(attribution);
}
}

// then go through all credits and add them or update their position
for (const [index, credit] of credits.entries()) {
const attributionIndex = this.cesiumDataAttributions.indexOf(credit);

if (attributionIndex === index) {
// it is already on correct position in the list
continue;
} else if (attributionIndex === -1) {
// it is not on the list yet so we add it to the list
this.cesiumDataAttributions.splice(index, 0, credit);
} else {
// it is on the list but not in the right place so we move it
this.cesiumDataAttributions.splice(
index,
0,
this.cesiumDataAttributions.splice(attributionIndex, 1)[0]
);
}
}
syncCesiumCreditsToAttributions(
creditDisplay._currentFrameCredits!.lightboxCredits
.values as CreditDisplayElement[],
this.cesiumDataAttributions
);
syncCesiumCreditsToAttributions(
creditDisplay._currentFrameCredits!.screenCredits
.values as CreditDisplayElement[],
this.cesiumScreenDataAttributions
);
});
};
}
Expand Down Expand Up @@ -1033,11 +1010,11 @@ export default class Cesium extends GlobeOrMap {
: undefined;

if (!center) {
/** In cases where the horizon is not visible, we cannot calculate a center using a pick ray,
/** In cases where the horizon is not visible, we cannot calculate a center using a pick ray,
but we need to return a useful CameraView that works in 3D mode and 2D mode.
In this case we can return the correct definition for the cesium camera, with position, direction, and up,
In this case we can return the correct definition for the cesium camera, with position, direction, and up,
but we need to calculate a bounding box on the ellipsoid too to be used in 2D mode.
To do this we clone the camera, rotate it to point straight down, and project the camera view from that position onto the ellipsoid.
**/

Expand Down Expand Up @@ -1851,3 +1828,46 @@ function flyToBoundingSpherePromise(
});
});
}

function syncCesiumCreditsToAttributions(
creditsElements: CreditDisplayElement[],
dataAttributionsObservable: IObservableArray<string>
) {
// sort credits by count (number of times they are added to map)
const credits = creditsElements
.sort((credit1, credit2) => {
return credit2.count - credit1.count;
})
.map(({ credit }) => credit.html);

if (isEqual(credits, toJS(dataAttributionsObservable))) return;

// first remove ones that are not on the map anymore
// Iterate backwards because we're removing items.
for (let i = dataAttributionsObservable.length - 1; i >= 0; i--) {
const attribution = dataAttributionsObservable[i];
if (!credits.includes(attribution)) {
dataAttributionsObservable.remove(attribution);
}
}

// then go through all credits and add them or update their position
for (const [index, credit] of credits.entries()) {
const attributionIndex = dataAttributionsObservable.indexOf(credit);

if (attributionIndex === index) {
// it is already on correct position in the list
continue;
} else if (attributionIndex === -1) {
// it is not on the list yet so we add it to the list
dataAttributionsObservable.splice(index, 0, credit);
} else {
// it is on the list but not in the right place so we move it
dataAttributionsObservable.splice(
index,
0,
dataAttributionsObservable.splice(attributionIndex, 1)[0]
);
}
}
}
5 changes: 1 addition & 4 deletions lib/ReactViews/BottomDock/MapDataCount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ const MapDataCount = observer(function (props: Props) {
: t("countDatasets.noMapDataEnabled");

return (
// Should we even provide a wrapper Box? makes sense not to, but most of the
// components as they stand come with their own "wrapper" via scss
// <Box styledMinHeight="72px">
<Box>
<Box css={"flex-shrink 0.5;"}>
<ButtonAsLabel light={hasMapData}>
<Spacing right={1} />
<StyledIcon
Expand Down
71 changes: 62 additions & 9 deletions lib/ReactViews/Map/BottomLeftBar/BottomLeftBar.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,67 @@
import { observer } from "mobx-react";
import React, { FC } from "react";
import { useTranslation } from "react-i18next";

import ViewState from "../../../ReactViewModels/ViewState";
import Icon from "../../../Styled/Icon";

import styled, { useTheme } from "styled-components";
import defined from "terriajs-cesium/Source/Core/defined";
import type Cesium3DTilesCatalogItem from "../../../Models/Catalog/CatalogItems/Cesium3DTilesCatalogItem";
import ViewerMode from "../../../Models/ViewerMode";
import ViewState from "../../../ReactViewModels/ViewState";
import Box from "../../../Styled/Box";
import Icon from "../../../Styled/Icon";
import Text from "../../../Styled/Text";
import MapDataCount from "../../BottomDock/MapDataCount";
import Terria from "../../../Models/Terria";
import MapIconButton from "../../MapIconButton/MapIconButton";
import defined from "terriajs-cesium/Source/Core/defined";
import { useViewState } from "../../Context";
import parseCustomHtmlToReact from "../../Custom/parseCustomHtmlToReact";
import MapIconButton from "../../MapIconButton/MapIconButton";

const BottomLeftContainer = styled(Box)`
position: absolute;
bottom: 40px;
@media (max-width: ${(props) => props.theme.mobile}px) {
bottom: 35px;
}
display: flex;
`;

// Use padding to avoid other UI elements
const AttributionsContainer = styled(Text)`
text-shadow: 0 0 2px #000000;
padding-left: 8px;
padding-right: 56px;
@media (max-width: ${(props) => props.theme.mobile}px) {
padding-right: 8px;
padding-bottom: 32px;
}
`;

const shouldShowPlayStoryButton = (viewState: ViewState) =>
viewState.terria.configParameters.storyEnabled &&
defined(viewState.terria.stories) &&
viewState.terria.stories.length > 0 &&
viewState.useSmallScreenInterface;

const BottomLeftBar: FC = () => {
const BottomLeftBar: FC = observer(() => {
const { t } = useTranslation();
const theme = useTheme();
const viewState = useViewState();

const screenDataAttributions =
viewState.terria.cesium?.cesiumScreenDataAttributions;

const isNotificationActive =
viewState.terria.notificationState.currentNotification;

const isUsingGooglePhotorealistic3dTiles =
viewState.terria.mainViewer.viewerMode === ViewerMode.Cesium &&
viewState.terria.workbench.items
.filter((i): i is Cesium3DTilesCatalogItem => i.type === "3d-tiles")
.some(
(i) =>
i.url?.startsWith(
"https://tile.googleapis.com/v1/3dtiles/root.json"
) && i.show
);

return (
<BottomLeftContainer theme={theme}>
<MapDataCount
Expand All @@ -53,8 +82,32 @@ const BottomLeftBar: FC = () => {
</MapIconButton>
</Box>
) : null}
{/* Google Logo. Needed for Google Photorealistic 3D Tiles
*/}
{isUsingGooglePhotorealistic3dTiles && (
<img
height="18px"
style={{ paddingLeft: "8px" }}
src="build/TerriaJS/images/google_on_non_white_hdpi.png"
></img>
)}
{/* On screen data attributions. At the moment, this supports only Cesium viewer.
Needed for Google Photorealistic 3D Tiles
*/}
{!!screenDataAttributions?.length && (
<AttributionsContainer textLight mini>
{screenDataAttributions
.flatMap((attributionHtml, i) => [
<span key={attributionHtml}>
{parseCustomHtmlToReact(attributionHtml)}
</span>,
<span key={`delimiter-${i}`}></span>
])
.slice(0, -1)}
</AttributionsContainer>
)}
</BottomLeftContainer>
);
};
});

export default BottomLeftBar;
16 changes: 6 additions & 10 deletions lib/ReactViews/Map/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { VFC, useCallback, useEffect, useMemo, useState } from "react";
import { observer } from "mobx-react";
import React, { VFC, useCallback, useEffect, useState } from "react";
import styled, { css, keyframes, useTheme } from "styled-components";
import EventHelper from "terriajs-cesium/Source/Core/EventHelper";
import { useViewState } from "../Context";

export const ProgressBar: VFC = () => {
export const ProgressBar: VFC = observer(() => {
const [loadPercentage, setLoadPercentage] = useState(0);
const [indeterminateLoading, setIndeterminateLoading] = useState<any>();

Expand Down Expand Up @@ -32,13 +33,8 @@ export const ProgressBar: VFC = () => {
};
}, []);

const backgroundColor = useMemo(
() =>
terria.baseMapContrastColor === "#ffffff"
? "#ffffff"
: theme.colorPrimary,
[terria.baseMapContrastColor]
);
const backgroundColor =
terria.baseMapContrastColor === "#ffffff" ? "#ffffff" : theme.colorPrimary;

const allComplete = loadPercentage === 100 && !indeterminateLoading;

Expand All @@ -50,7 +46,7 @@ export const ProgressBar: VFC = () => {
loadPercentage={`${loadPercentage}%`}
/>
);
};
});

interface IStyledProgressBarProps {
loadPercentage: string;
Expand Down
7 changes: 7 additions & 0 deletions lib/Traits/TraitsClasses/Cesium3dTilesTraits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ export class OptionsTraits extends ModelTraits {
description: "Point cloud shading parameters"
})
pointCloudShading?: PointCloudShadingTraits;

@primitiveTrait({
type: "boolean",
name: "Show credits on screen",
description: "Whether to display the credits of this tileset on screen."
})
showCreditsOnScreen: boolean = false;
}

export default class Cesium3DTilesTraits extends mixTraits(
Expand Down
2 changes: 0 additions & 2 deletions lib/ViewModels/TerriaViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ export default class TerriaViewer {
newViewer = untracked(() => new NoViewer(this));
}

console.log(`Creating a viewer: ${newViewer.type}`);
this._lastViewer = newViewer;
newViewer.zoomTo(currentView || untracked(() => this.homeCamera), 0.0);

Expand Down Expand Up @@ -248,7 +247,6 @@ export default class TerriaViewer {
let currentView: CameraView | undefined;
if (this._lastViewer !== undefined) {
this.beforeViewerChanged.raiseEvent();
console.log(`Destroying viewer: ${this._lastViewer.type}`);
currentView = this._lastViewer.getCurrentCameraView();
this._lastViewer.destroy();
this._lastViewer = undefined;
Expand Down
Binary file added wwwroot/images/google_on_non_white_hdpi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit ada05f2

Please sign in to comment.