Spaces:
Configuration error
Configuration error
File size: 9,051 Bytes
9f21d0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | import { Cartesian3, Entity, JulianDate, ReferenceFrame, VelocityOrientationProperty } from "@cesium/engine";
import { describe, expect, test } from "vitest";
import { GridPositionProperty } from "./GridPositionProperty";
const ANCHOR_MS = Date.UTC(2018, 11, 8);
const STEP = 45;
const anchor = () => JulianDate.fromDate(new Date(ANCHOR_MS));
const at = (seconds: number) => JulianDate.addSeconds(anchor(), seconds, new JulianDate());
/** A straight line through the grid, so an exact answer is known at every instant. */
const ramp = (fromIndex: number, count: number): Float64Array => {
const xyz = new Float64Array(count * 3);
for (let index = 0; index < count; index += 1) {
const gridIndex = fromIndex + index;
xyz[index * 3] = gridIndex * 100;
xyz[index * 3 + 1] = gridIndex * -200;
xyz[index * 3 + 2] = 7000;
}
return xyz;
};
const filled = (fromIndex = 0, count = 10): GridPositionProperty => {
const property = new GridPositionProperty();
property.reset(ANCHOR_MS, STEP);
property.add(fromIndex, ramp(fromIndex, count));
return property;
};
describe("GridPositionProperty", () => {
test("is constant, and reads as nothing, until it holds samples", () => {
const property = new GridPositionProperty();
expect(property.isConstant).toBe(true);
expect(property.getValue(at(0))).toBeUndefined();
});
test("reproduces the samples it was given, exactly", () => {
const property = filled();
for (let index = 0; index < 10; index += 1) {
const value = property.getValue(at(index * STEP)) as Cartesian3;
expect(value.x).toBeCloseTo(index * 100, 6);
expect(value.y).toBeCloseTo(index * -200, 6);
expect(value.z).toBeCloseTo(7000, 6);
}
});
test("interpolates a straight line without bending it", () => {
// A cubic through collinear points is that line, so any error here is the
// basis functions rather than the accuracy of the fit.
const property = filled();
const value = property.getValue(at(3.25 * STEP)) as Cartesian3;
expect(value.x).toBeCloseTo(325, 6);
expect(value.y).toBeCloseTo(-650, 6);
});
test("holds the end value rather than extrapolating past it", () => {
// A clock scrubbed clear of the window reads here for a frame or two before the
// refill lands. A free-running cubic would answer with a position half an orbit
// of divergence away; holding answers with a stale one, which is bounded.
const property = filled(0, 6);
expect((property.getValue(at(5 * STEP)) as Cartesian3).x).toBeCloseTo(500, 6);
expect((property.getValue(at(20 * STEP)) as Cartesian3).x).toBeCloseTo(500, 6);
expect((property.getValue(at(-40 * STEP)) as Cartesian3).x).toBeCloseTo(0, 6);
});
test("holds the nearest sample when there are too few for a cubic", () => {
const property = new GridPositionProperty();
property.reset(ANCHOR_MS, STEP);
property.add(4, ramp(4, 2));
expect((property.getValue(at(4 * STEP)) as Cartesian3).x).toBeCloseTo(400, 6);
expect((property.getValue(at(5 * STEP)) as Cartesian3).x).toBeCloseTo(500, 6);
});
test("reads the same instant from either end of a seam", () => {
// Two batches, appended in either order, describing one grid. What makes this
// safe is that an index means an instant, so neither batch has to know the
// other's times.
const forwards = filled(0, 6);
forwards.add(6, ramp(6, 6));
const backwards = filled(6, 6);
backwards.add(0, ramp(0, 6));
expect(forwards.length).toBe(12);
expect(backwards.length).toBe(12);
for (const seconds of [5.5 * STEP, 6 * STEP, 6.5 * STEP]) {
expect((forwards.getValue(at(seconds)) as Cartesian3).x).toBeCloseTo((backwards.getValue(at(seconds)) as Cartesian3).x, 6);
}
});
test("overwrites an interval it already holds instead of duplicating it", () => {
const property = filled(0, 10);
const rewritten = new Float64Array(9).fill(42);
expect(property.add(3, rewritten)).toBe(true);
expect(property.length).toBe(10);
expect((property.getValue(at(4 * STEP)) as Cartesian3).x).toBeCloseTo(42, 6);
});
test("a batch that extends the front and overlaps is taken whole", () => {
const property = filled(6, 4);
expect(property.add(2, ramp(2, 8))).toBe(true);
expect(property.firstIndex).toBe(2);
expect(property.length).toBe(8);
expect((property.getValue(at(3 * STEP)) as Cartesian3).x).toBeCloseTo(300, 6);
expect((property.getValue(at(9 * STEP)) as Cartesian3).x).toBeCloseTo(900, 6);
});
test("a batch that swallows the window whole keeps all of it", () => {
const property = filled(6, 3);
expect(property.add(4, ramp(4, 10))).toBe(true);
expect(property.firstIndex).toBe(4);
expect(property.length).toBe(10);
expect((property.getValue(at(13 * STEP)) as Cartesian3).x).toBeCloseTo(1300, 6);
});
test("refuses a batch that would leave a hole", () => {
const property = filled(0, 5);
// Grid indices 5..7 are missing, so this cannot be spliced on without the
// samples between. The caller treats a refusal as a failed fill.
expect(property.add(8, ramp(8, 3))).toBe(false);
expect(property.length).toBe(5);
});
test("drops from either end without moving the samples that remain", () => {
const property = filled(0, 12);
property.dropBefore(4);
expect(property.firstIndex).toBe(4);
expect(property.length).toBe(8);
property.dropAfter(9);
expect(property.length).toBe(6);
expect((property.getValue(at(7 * STEP)) as Cartesian3).x).toBeCloseTo(700, 6);
});
test("keeps reading correctly after a window slides and refills", () => {
// The whole lifecycle in one: evict the tail of the window, append the new
// head, and check an instant in the part that was never touched.
const property = filled(0, 12);
property.dropBefore(6);
expect(property.add(12, ramp(12, 6))).toBe(true);
expect(property.firstIndex).toBe(6);
expect(property.length).toBe(12);
expect((property.getValue(at(13.5 * STEP)) as Cartesian3).x).toBeCloseTo(1350, 6);
expect((property.getValue(at(8 * STEP)) as Cartesian3).x).toBeCloseTo(800, 6);
});
test("a different anchor or step starts the grid over", () => {
const property = filled();
expect(property.isOnGrid(ANCHOR_MS, STEP)).toBe(true);
expect(property.isOnGrid(ANCHOR_MS + 1, STEP)).toBe(false);
expect(property.isOnGrid(ANCHOR_MS, STEP + 1)).toBe(false);
property.reset(ANCHOR_MS, 60);
expect(property.length).toBe(0);
});
test("turns a grid index back into the instant it stands for", () => {
const property = filled();
expect(JulianDate.secondsDifference(property.timeAt(4), anchor())).toBeCloseTo(4 * STEP, 6);
expect(property.indexAtOrAfter(at(4 * STEP))).toBe(4);
expect(property.indexAtOrAfter(at(4.1 * STEP))).toBe(5);
});
test("converts out of its own reference frame on request", () => {
const property = filled();
const own = property.getValueInReferenceFrame(at(2 * STEP), ReferenceFrame.FIXED) as Cartesian3;
const other = property.getValueInReferenceFrame(at(2 * STEP), ReferenceFrame.INERTIAL);
expect(own.x).toBeCloseTo(200, 6);
// Cesium's transform data is not loaded in a unit test, so the conversion may
// legitimately decline — what matters is that it is attempted, not assumed.
if (other) {
expect(other.x).not.toBeCloseTo(200, 6);
}
});
test("drives an entity the way Cesium's own position properties do", () => {
// The interface is not enough to go on: VelocityVectorProperty subscribes via
// `value._definitionChanged` rather than the getter, so a property satisfying
// only the documented shape throws here. Constructing what the app constructs
// is the only check that catches it — every assertion above passed while a
// scene of 5,000 satellites built nothing at all.
const property = filled();
const entity = new Entity({ name: "grid", position: property as never });
entity.orientation = new VelocityOrientationProperty(property as never);
expect(entity.position).toBe(property);
expect((entity.position!.getValue(at(2 * STEP)) as Cartesian3).x).toBeCloseTo(200, 6);
// A ramp is straight, so the orientation is well defined and constant along it.
expect(entity.orientation.getValue(at(2 * STEP))).toBeDefined();
});
test("raises definitionChanged when what it holds changes", () => {
const property = filled();
let raised = 0;
property.definitionChanged.addEventListener(() => {
raised += 1;
});
property.add(10, ramp(10, 4));
expect(raised).toBe(1);
});
test("hands back the raw samples for a range", () => {
const property = filled(0, 10);
const positions = property.rawPositions(2, 4);
expect(positions).toHaveLength(3);
expect(positions[0]?.x).toBeCloseTo(200, 6);
// Clamped to what is held rather than padded with zeroes.
expect(property.rawPositions(-5, 100)).toHaveLength(10);
});
});
|