import { C3DTTemporalBatchTable } from './model/C3DTTemporalBatchTable';
import { C3DTTemporalBoundingVolume } from './model/C3DTTemporalBoundingVolume';
import { C3DTTemporalTileset } from './model/C3DTTemporalTileset';
import * as itowns from 'itowns';
import { arrayPushOnce } from '@ud-viz/utils_shared';
export {
C3DTTemporalBatchTable,
C3DTTemporalBoundingVolume,
C3DTTemporalTileset,
};
export { STLayer } from './STLayer';
export { DISPLAY_MODE as STS_DISPLAY_MODE } from './STShape';
export { STSCircle } from './STSCircle';
export { STSHelix } from './STSHelix';
export { STSVector } from './STSVector';
export { STSParabola } from './STSParabola';
export const ID = '3DTILES_temporal';
const TEMPORAL_COLOR_OPACITY = {
noTransaction: {
color: 'white',
opacity: 1,
},
invisible: {
color: 'blue',
opacity: 0,
},
debug: {
color: 'brown',
opacity: 0.2,
},
creation: {
color: 'green',
opacity: 0.6,
},
demolition: {
color: 'red',
opacity: 0.6,
},
modification: {
color: 'yellow',
opacity: 0.6,
},
};
/* The `Temporal3DTilesLayerWrapper` class is a wrapper for a temporal 3D Tiles layer
* using the `3DTILES_temporal` extension, providing methods to compute and update the
* style of the layer based on temporal data. */
export class Temporal3DTilesLayerWrapper {
/**
* A constructor that initializes a temporal style for a C3DTilesLayer by
* computing tile maps based on the `3DTILES_temporal` batch table hierarchy content found in
* the tile content.
*
* @param {itowns.C3DTilesLayer} temporalC3DTilesLayer - An instance of the `itowns.C3DTilesLayer` class. It
* represents a layer that displays 3D tiles with temporal data.
*/
constructor(temporalC3DTilesLayer) {
/**
* the layer wrapped
*
@type {itowns.C3DTilesLayer} */
this.temporalC3DTilesLayer = temporalC3DTilesLayer;
/**
* all date possible to update style with (ascending order)
*
@type {Array<number>} */
this.knownDatesForAllTiles = [];
/**
* date selected TODO: use a Date Object
*
@type {number} */
this._styleDate = null;
const computedTileIds = [];
const tileMaps = new Map();
// compute tileMaps base on the batchTable found in tileContent
temporalC3DTilesLayer.addEventListener(
itowns.C3DTILES_LAYER_EVENTS.ON_TILE_CONTENT_LOADED,
({ tileContent }) => {
// avoid to recompute map for a tile already loaded
if (!arrayPushOnce(computedTileIds, tileContent.tileId)) return;
console.debug(
'compute init data temporal style ',
tileContent.tileId,
temporalC3DTilesLayer.id
);
const possibleDates = [];
/** @type {Map<string,object>} */
tileMaps.set(tileContent.tileId, new Map());
temporalC3DTilesLayer.tileset.extensions[
'3DTILES_temporal'
].transactions.forEach((transaction) => {
// add possibleDate
const transactionDuration =
transaction.endDate - transaction.startDate;
const firstHalfDate = transaction.startDate + transactionDuration / 3;
const secondHalfDate = transaction.endDate - transactionDuration / 3;
arrayPushOnce(possibleDates, firstHalfDate);
arrayPushOnce(possibleDates, secondHalfDate);
arrayPushOnce(possibleDates, transaction.startDate);
arrayPushOnce(possibleDates, transaction.endDate);
transaction.source.forEach((fId) => {
if (transaction.type == 'modification') {
tileMaps
.get(tileContent.tileId)
.set(fId + firstHalfDate, TEMPORAL_COLOR_OPACITY.modification);
tileMaps
.get(tileContent.tileId)
.set(fId + secondHalfDate, TEMPORAL_COLOR_OPACITY.invisible);
} else {
// all other transaction
tileMaps
.get(tileContent.tileId)
.set(fId + firstHalfDate, TEMPORAL_COLOR_OPACITY.noTransaction);
tileMaps
.get(tileContent.tileId)
.set(
fId + secondHalfDate,
TEMPORAL_COLOR_OPACITY.noTransaction
);
}
});
transaction.destination.forEach((fId) => {
if (transaction.type == 'modification') {
tileMaps
.get(tileContent.tileId)
.set(fId + firstHalfDate, TEMPORAL_COLOR_OPACITY.invisible);
tileMaps
.get(tileContent.tileId)
.set(fId + secondHalfDate, TEMPORAL_COLOR_OPACITY.modification);
} else {
// all other transaction
tileMaps
.get(tileContent.tileId)
.set(fId + firstHalfDate, TEMPORAL_COLOR_OPACITY.noTransaction);
tileMaps
.get(tileContent.tileId)
.set(
fId + secondHalfDate,
TEMPORAL_COLOR_OPACITY.noTransaction
);
}
});
});
// handle demolition/creation which are not in batchTable/extension
possibleDates.sort((a, b) => a - b);
for (const [
// eslint-disable-next-line no-unused-vars
tileId,
tileC3DTileFeatures,
] of temporalC3DTilesLayer.tilesC3DTileFeatures) {
// eslint-disable-next-line no-unused-vars
for (const [batchId, c3DTileFeature] of tileC3DTileFeatures) {
const temporalExtension =
c3DTileFeature.getInfo().extensions['3DTILES_temporal'];
for (let index = 0; index < possibleDates.length - 1; index++) {
const date = possibleDates[index];
const nextDate = possibleDates[index + 1];
if (temporalExtension.endDate == date) {
// if no transaction next index should demolition (no modification)
const featureDateID = temporalExtension.featureId + nextDate;
if (!tileMaps.get(tileContent.tileId).has(featureDateID)) {
tileMaps
.get(tileContent.tileId)
.set(featureDateID, TEMPORAL_COLOR_OPACITY.demolition);
}
}
if (temporalExtension.startDate == nextDate) {
// if no transaction previous index should creation (no modification)
const featureDateID = temporalExtension.featureId + date;
if (!tileMaps.get(tileContent.tileId).has(featureDateID)) {
tileMaps
.get(tileContent.tileId)
.set(featureDateID, TEMPORAL_COLOR_OPACITY.creation);
}
}
}
}
}
possibleDates.forEach((date) =>
arrayPushOnce(this.knownDatesForAllTiles, date)
);
this.knownDatesForAllTiles.sort((a, b) => a - b); // sort
if (this._styleDate == null)
this.styleDate = this.knownDatesForAllTiles[0]; // init with a default value
}
);
const computeColorOpacity = (c3DTileFeature) => {
const temporalExtension =
c3DTileFeature.getInfo().extensions['3DTILES_temporal'];
if (
temporalExtension.startDate <= this._styleDate &&
temporalExtension.endDate >= this._styleDate
) {
// no transaction
return TEMPORAL_COLOR_OPACITY.noTransaction;
}
// check if color opacity associated to featureDateID
const featureDateID = temporalExtension.featureId + this._styleDate;
if (
tileMaps.has(c3DTileFeature.tileId) &&
tileMaps.get(c3DTileFeature.tileId).has(featureDateID)
) {
return tileMaps.get(c3DTileFeature.tileId).get(featureDateID);
}
return TEMPORAL_COLOR_OPACITY.invisible;
};
temporalC3DTilesLayer.style = new itowns.Style({
fill: {
color: (feature) => {
const colorOpacity = computeColorOpacity(feature);
return colorOpacity.color;
},
opacity: (feature) => {
const colorOpacity = computeColorOpacity(feature);
return colorOpacity.opacity;
},
},
});
}
magnetizeDate(date) {
if (!this.knownDatesForAllTiles.includes(date)) {
// take the date the more closer
let lastDiff = Infinity;
for (let index = 0; index < this.knownDatesForAllTiles.length; index++) {
const knownDate = this.knownDatesForAllTiles[index];
const diff = Math.abs(date - knownDate);
if (diff < lastDiff) {
lastDiff = diff;
continue;
} else {
date = this.knownDatesForAllTiles[index - 1];
break;
}
}
}
return date;
}
/**
* Update temporal 3DTiles layer style with a date
*
* @param {number} date - year to update style with
*/
set styleDate(date) {
this._styleDate = this.magnetizeDate(date);
this.temporalC3DTilesLayer.updateStyle();
}
}