terriajs
Version:
Geospatial data visualization platform.
338 lines • 16 kB
JavaScript
import Cartesian2 from "terriajs-cesium/Source/Core/Cartesian2";
import Cartesian3 from "terriajs-cesium/Source/Core/Cartesian3";
import Color from "terriajs-cesium/Source/Core/Color";
import Iso8601 from "terriajs-cesium/Source/Core/Iso8601";
import TimeInterval from "terriajs-cesium/Source/Core/TimeInterval";
import TimeIntervalCollection from "terriajs-cesium/Source/Core/TimeIntervalCollection";
import BillboardGraphics from "terriajs-cesium/Source/DataSources/BillboardGraphics";
import ColorMaterialProperty from "terriajs-cesium/Source/DataSources/ColorMaterialProperty";
import ConstantPositionProperty from "terriajs-cesium/Source/DataSources/ConstantPositionProperty";
import ConstantProperty from "terriajs-cesium/Source/DataSources/ConstantProperty";
import LabelGraphics from "terriajs-cesium/Source/DataSources/LabelGraphics";
import PathGraphics from "terriajs-cesium/Source/DataSources/PathGraphics";
import PointGraphics from "terriajs-cesium/Source/DataSources/PointGraphics";
import PolylineGlowMaterialProperty from "terriajs-cesium/Source/DataSources/PolylineGlowMaterialProperty";
import SampledPositionProperty from "terriajs-cesium/Source/DataSources/SampledPositionProperty";
import SampledProperty from "terriajs-cesium/Source/DataSources/SampledProperty";
import TimeIntervalCollectionPositionProperty from "terriajs-cesium/Source/DataSources/TimeIntervalCollectionPositionProperty";
import TimeIntervalCollectionProperty from "terriajs-cesium/Source/DataSources/TimeIntervalCollectionProperty";
import HeightReference from "terriajs-cesium/Source/Scene/HeightReference";
import TerriaFeature from "../Models/Feature/Feature";
import { getRowValues } from "./createLongitudeLatitudeFeaturePerRow";
import { getFeatureStyle } from "./getFeatureStyle";
import TableColumnType from "./TableColumnType";
/** For a given TimeProperties object, convert all PreSampledProperty to SampledProperty */
function convertPreSampledProperties(timeProperties) {
if (!timeProperties)
return {};
return Object.entries(timeProperties).reduce((current, [key, value]) => {
if (value instanceof PreSampledProperty) {
const sampledProperty = value.getProperty();
if (sampledProperty) {
current[key] = sampledProperty;
}
}
else if (value instanceof TimeIntervalCollectionProperty) {
current[key] = value;
}
return current;
}, {});
}
/** This class can be used in-place for Cesium's SampledProperty.
*
* It provides better performance as instead of calling `SampledProperty.addSample` for every sample, it will call `SampledProperty.addSamples` once with all samples.
* This occurs when `PreSampledProperty.toSampledProperty()` is called
**/
class PreSampledProperty {
type;
times = [];
values = [];
allValuesAreTheSame = true;
constructor(type) {
this.type = type;
}
getProperty() {
if (this.allValuesAreTheSame) {
return new ConstantProperty(this.values[0]);
}
const property = new SampledProperty(this.type);
property.addSamples(this.times, this.values);
return property;
}
addSample(time, value) {
this.times.push(time);
if (this.allValuesAreTheSame &&
this.values.length > 1 &&
value.toString() !== this.values[this.values.length - 2].toString()) {
this.allValuesAreTheSame = false;
}
this.values.push(value);
}
}
/** This class can be used in-place for Cesium's SampledPositionProperty.
*
* It behaves exactly the same as PreSampledProperty
**/
class PreSampledPositionProperty {
times = [];
values = [];
allValuesAreTheSame = true;
getProperty() {
if (this.allValuesAreTheSame) {
return new ConstantPositionProperty(this.values[0]);
}
const property = new SampledPositionProperty();
property.addSamples(this.times, this.values);
return property;
}
addSample(time, value) {
this.times.push(time);
if (this.allValuesAreTheSame &&
this.values.length > 1 &&
!value.equals(this.values[this.values.length - 2])) {
this.allValuesAreTheSame = false;
}
this.values.push(value);
}
}
/**
* Create lat/lon features, one for each id group in the table
*/
export default function createLongitudeLatitudeFeaturePerId(style) {
const features = [];
for (let i = 0; i < style.rowGroups.length; i++) {
const [featureId, rowIds] = style.rowGroups[i];
features.push(createFeature(featureId, rowIds, style));
}
return features;
}
function createProperty(type, interpolate) {
return interpolate
? new PreSampledProperty(type)
: new TimeIntervalCollectionProperty();
}
function createFeature(_featureId, rowIds, style) {
const isSampled = !!style.isSampled;
const tableHasScalarColumn = !!style.tableModel.tableColumns.find((col) => col.type === TableColumnType.scalar);
const interpolate = isSampled && tableHasScalarColumn;
const positionProperty = isSampled
? new PreSampledPositionProperty()
: new TimeIntervalCollectionPositionProperty();
// The following "TimeProperties<T>" objects are used to transform feature styling properties into time-enabled properties (eg SampledProperty or TimeIntervalCollectionProperty)
// Required<T> is used as we need to make sure that all styling properties have a time-enabled property defined
// See `getFeatureStyle` for "raw" feature styling properties
const pointGraphicsTimeProperties = style.pointStyleMap.traits.enabled
? {
color: createProperty(Color, interpolate),
outlineColor: createProperty(Color, interpolate),
pixelSize: createProperty(Number, interpolate),
outlineWidth: createProperty(Number, interpolate),
scaleByDistance: new TimeIntervalCollectionProperty(),
disableDepthTestDistance: new TimeIntervalCollectionProperty()
}
: undefined;
const billboardGraphicsTimeProperties = style.pointStyleMap.traits.enabled
? {
image: new TimeIntervalCollectionProperty(),
height: createProperty(Number, interpolate),
width: createProperty(Number, interpolate),
color: createProperty(Color, interpolate),
rotation: createProperty(Number, interpolate),
pixelOffset: createProperty(Cartesian2, interpolate),
scaleByDistance: new TimeIntervalCollectionProperty(),
disableDepthTestDistance: new TimeIntervalCollectionProperty()
}
: undefined;
const pathGraphicsTimeProperties = style.trailStyleMap.traits.enabled
? {
leadTime: createProperty(Number, interpolate),
trailTime: createProperty(Number, interpolate),
width: createProperty(Number, interpolate),
resolution: createProperty(Number, interpolate)
}
: undefined;
const pathGraphicsSolidColorTimeProperties = style.trailStyleMap.traits.enabled &&
style.trailStyleMap.traits.materialType === "solidColor"
? {
color: createProperty(Color, interpolate)
}
: undefined;
const pathGraphicsPolylineGlowTimeProperties = style.trailStyleMap.traits.enabled &&
style.trailStyleMap.traits.materialType === "polylineGlow"
? {
color: createProperty(Color, interpolate),
glowPower: createProperty(Number, interpolate),
taperPower: createProperty(Number, interpolate)
}
: undefined;
const labelGraphicsTimeProperties = style.labelStyleMap.traits.enabled
? {
font: new TimeIntervalCollectionProperty(),
text: new TimeIntervalCollectionProperty(),
style: new TimeIntervalCollectionProperty(),
scale: createProperty(Number, interpolate),
fillColor: createProperty(Color, interpolate),
outlineColor: createProperty(Color, interpolate),
outlineWidth: createProperty(Number, interpolate),
pixelOffset: createProperty(Cartesian2, interpolate),
verticalOrigin: new TimeIntervalCollectionProperty(),
horizontalOrigin: new TimeIntervalCollectionProperty(),
scaleByDistance: new TimeIntervalCollectionProperty(),
disableDepthTestDistance: new TimeIntervalCollectionProperty()
}
: undefined;
const properties = new TimeIntervalCollectionProperty();
const description = new TimeIntervalCollectionProperty();
const longitudes = style.longitudeColumn.valuesAsNumbers.values;
const latitudes = style.latitudeColumn.valuesAsNumbers.values;
const timeIntervals = style.timeIntervals;
const availability = new TimeIntervalCollection();
const tableColumns = style.tableModel.tableColumns;
/** use `PointGraphics` or `BillboardGraphics`. This wil be false if any pointTraits.marker !== "point", as then we use images as billboards */
let usePointGraphicsForId = true;
for (let i = 0; i < rowIds.length; i++) {
const rowId = rowIds[i];
const longitude = longitudes[rowId];
const latitude = latitudes[rowId];
const interval = timeIntervals[rowId];
if (longitude === null || latitude === null || !interval) {
continue;
}
addSampleOrInterval(positionProperty, Cartesian3.fromDegrees(longitude, latitude, 0.0), interval);
const { pointGraphicsOptions, usePointGraphics, pathGraphicsOptions, pathGraphicsPolylineGlowOptions, pathGraphicsSolidColorOptions, labelGraphicsOptions, billboardGraphicsOptions } = getFeatureStyle(style, rowId);
if (!usePointGraphics) {
usePointGraphicsForId = false;
}
if (pointGraphicsTimeProperties && pointGraphicsOptions)
// Copy all style object values across to time-enabled properties
Object.entries(pointGraphicsOptions).forEach(([key, value]) => {
if (key in pointGraphicsTimeProperties)
addSampleOrInterval(pointGraphicsTimeProperties[key], value, interval);
});
if (billboardGraphicsTimeProperties && billboardGraphicsOptions)
Object.entries(billboardGraphicsOptions).forEach(([key, value]) => {
if (key in billboardGraphicsTimeProperties)
addSampleOrInterval(billboardGraphicsTimeProperties[key], value, interval);
});
if (labelGraphicsTimeProperties && labelGraphicsOptions)
Object.entries(labelGraphicsOptions).forEach(([key, value]) => {
if (key in labelGraphicsTimeProperties)
addSampleOrInterval(labelGraphicsTimeProperties[key], value, interval);
});
if (pathGraphicsTimeProperties && pathGraphicsOptions)
Object.entries(pathGraphicsOptions).forEach(([key, value]) => {
if (key in pathGraphicsTimeProperties)
addSampleOrInterval(pathGraphicsTimeProperties[key], value, interval);
});
if (pathGraphicsSolidColorTimeProperties && pathGraphicsSolidColorOptions)
Object.entries(pathGraphicsSolidColorOptions).forEach(([key, value]) => {
if (key in pathGraphicsSolidColorTimeProperties)
addSampleOrInterval(pathGraphicsSolidColorTimeProperties[key], value, interval);
});
if (pathGraphicsPolylineGlowTimeProperties &&
pathGraphicsPolylineGlowOptions)
Object.entries(pathGraphicsPolylineGlowOptions).forEach(([key, value]) => {
if (key in pathGraphicsPolylineGlowTimeProperties)
addSampleOrInterval(pathGraphicsPolylineGlowTimeProperties[key], value, interval);
});
// Feature properties/description
addSampleOrInterval(properties, {
...getRowValues(rowId, tableColumns)
}, interval);
addSampleOrInterval(description, getRowDescription(rowId, tableColumns), interval);
availability.addInterval(interval);
}
const show = calculateShow(availability);
const feature = new TerriaFeature({
position:
// positionProperty is either SampledPositionProperty or PreSampledPositionProperty
// If it's PreSampledPositionProperty - we need to transform it to SampledPositionProperty by calling `getProperty()`
positionProperty instanceof PreSampledPositionProperty
? positionProperty.getProperty()
: positionProperty,
point: usePointGraphicsForId
? new PointGraphics({
...convertPreSampledProperties(pointGraphicsTimeProperties),
show,
heightReference: HeightReference.CLAMP_TO_GROUND
})
: undefined,
billboard: !usePointGraphicsForId
? new BillboardGraphics({
...convertPreSampledProperties(billboardGraphicsTimeProperties),
heightReference: HeightReference.CLAMP_TO_GROUND,
show
})
: undefined,
path: pathGraphicsTimeProperties
? new PathGraphics({
show,
...convertPreSampledProperties(pathGraphicsTimeProperties),
// Material has to be handled separately from pathGraphicsTimeProperties
material: pathGraphicsPolylineGlowTimeProperties
? new PolylineGlowMaterialProperty(convertPreSampledProperties(pathGraphicsPolylineGlowTimeProperties))
: pathGraphicsSolidColorTimeProperties
? new ColorMaterialProperty(convertPreSampledProperties(pathGraphicsSolidColorTimeProperties).color)
: undefined
})
: undefined,
label: labelGraphicsTimeProperties
? new LabelGraphics({
show,
...convertPreSampledProperties(labelGraphicsTimeProperties)
})
: undefined,
availability
});
// Add properties to feature.data so we have access to TimeIntervalCollectionProperty outside of the PropertyBag.
feature.data = {
timeIntervalCollection: properties,
rowIds,
type: "terriaFeatureData"
};
feature.description = description;
return feature;
}
function addSampleOrInterval(property, data, interval) {
if (property instanceof PreSampledProperty ||
property instanceof PreSampledPositionProperty) {
property.addSample(interval.start, data);
}
else {
const thisInterval = interval.clone();
thisInterval.data = data;
property?.intervals.addInterval(thisInterval);
}
}
function calculateShow(availability) {
const show = new TimeIntervalCollectionProperty();
if (availability.start) {
const start = availability.start;
const stop = availability.stop;
show.intervals.addInterval(new TimeInterval({
start: Iso8601.MINIMUM_VALUE,
stop: Iso8601.MAXIMUM_VALUE,
data: false
}));
show.intervals.addInterval(new TimeInterval({ start, stop, data: true }));
}
else {
show.intervals.addInterval(new TimeInterval({
start: Iso8601.MINIMUM_VALUE,
stop: Iso8601.MAXIMUM_VALUE,
data: true
}));
}
return show;
}
function getRowDescription(index, tableColumns) {
const rows = tableColumns
.map((column) => {
const title = column.title;
const value = column.valueFunctionForType(index);
return `<tr><td>${title}</td><td>${value}</td></tr>`;
})
.join("\n");
return `<table class="cesium-infoBox-defaultTable">${rows}</table>`;
}
//# sourceMappingURL=createLongitudeLatitudeFeaturePerId.js.map