1
1
package gov.nasa.ammos.aerie.procedural.timeline.collections
2
2
3
3
import gov.nasa.ammos.aerie.procedural.timeline.BaseTimeline
4
+ import gov.nasa.ammos.aerie.procedural.timeline.BoundsTransformer
4
5
import gov.nasa.ammos.aerie.procedural.timeline.Interval
5
6
import gov.nasa.ammos.aerie.procedural.timeline.Timeline
6
7
import gov.nasa.ammos.aerie.procedural.timeline.ops.NonZeroDurationOps
7
8
import gov.nasa.ammos.aerie.procedural.timeline.ops.SerialOps
8
9
import gov.nasa.ammos.aerie.procedural.timeline.ops.coalesce.CoalesceIntervalsOp
9
10
import gov.nasa.ammos.aerie.procedural.timeline.util.preprocessList
10
11
import gov.nasa.ammos.aerie.procedural.timeline.util.sorted
12
+ import gov.nasa.jpl.aerie.merlin.protocol.types.Duration
11
13
12
14
/* * A coalescing timeline of [Intervals][Interval] with no extra data. */
13
15
data class Windows (private val timeline : Timeline <Interval , Windows >):
@@ -25,10 +27,16 @@ data class Windows(private val timeline: Timeline<Interval, Windows>):
25
27
combined.sorted()
26
28
}
27
29
30
+ /* * Calculates the union of this and a single [Interval]. */
31
+ infix fun union (other : Interval ) = union(Windows (other))
32
+
28
33
/* * Calculates the intersection of this and another [Windows]. */
29
34
infix fun intersection (other : Windows ) =
30
35
unsafeMap2(::Windows , other) { _, _, i -> i }
31
36
37
+ /* * Calculates the intersection of this and a single [Interval]. */
38
+ infix fun intersection (other : Interval ) = select(other)
39
+
32
40
/* * Calculates the complement; i.e. highlights everything that is not highlighted in this timeline. */
33
41
fun complement () = unsafeOperate { opts ->
34
42
val result = mutableListOf (opts.bounds)
@@ -37,4 +45,69 @@ data class Windows(private val timeline: Timeline<Interval, Windows>):
37
45
}
38
46
result
39
47
}
48
+
49
+ /* * Subtracts the intersection with another [Windows] from this. */
50
+ fun minus (other : Windows ) = intersection(other.complement())
51
+
52
+ /* *
53
+ * Subtracts the intersection with a single [Interval] from this.
54
+ *
55
+ * Essentially a rename of [gov.nasa.ammos.aerie.procedural.timeline.ops.GeneralOps.unset].
56
+ */
57
+ fun minus (other : Interval ) = unset(other)
58
+
59
+ /* *
60
+ * Returns a new [Windows] where each interval is replaced by just the point at its start time.
61
+ *
62
+ * Doesn't care about inclusivity.
63
+ * If an input interval doesn't contain its start point, the output will still be at the same time.
64
+ */
65
+ fun starts () = unsafeMapIntervals(BoundsTransformer .IDENTITY , false ) {
66
+ Interval .at(it.start)
67
+ }
68
+
69
+ /* *
70
+ * Returns a new [Windows] where each interval is replaced by just the point at its end time.
71
+ *
72
+ * Doesn't care about inclusivity.
73
+ * If an input interval doesn't contain its end point, the output will still be at the same time.
74
+ */
75
+ fun ends () = unsafeMapIntervals(BoundsTransformer .IDENTITY , false ) {
76
+ Interval .at(it.end)
77
+ }
78
+
79
+ /* *
80
+ * Independently shift the start and end points of each interval.
81
+ *
82
+ * The start and end can be shifted by different amounts, stretching or squishing the interval.
83
+ * If the interval is empty after the shift, it is removed.
84
+ *
85
+ * Unlike [gov.nasa.ammos.aerie.procedural.timeline.ops.ParallelOps.shiftEndpoints], this function
86
+ * DOES coalesce the output. If you stretch the intervals such that they start to
87
+ * overlap, those overlapping intervals will be combined into one. This means that applying
88
+ * the reverse operation (i.e. `windows.shiftEndpoints(Duration.ZERO, Duration.MINUTE).shiftEndpoints(Duration.ZERO, Duration.MINUTE.negate())`
89
+ * does NOT necessarily result in the same timeline.
90
+ *
91
+ * To turn off coalescing behavior, convert it into a [Universal] timeline first with `.isolate($ -> true)`
92
+ * or `.unsafeCast(Universal::new)`. You can undo this with `.highlight($ -> true)`.
93
+ */
94
+ fun shiftEndpoints (shiftStart : Duration , shiftEnd : Duration = shiftStart) =
95
+ unsafeMapIntervals(
96
+ { i ->
97
+ Interval .between(
98
+ Duration .min(i.start.minus(shiftStart), i.start.minus(shiftEnd)),
99
+ Duration .max(i.end.minus(shiftStart), i.end.minus(shiftEnd)),
100
+ i.startInclusivity,
101
+ i.endInclusivity
102
+ )
103
+ },
104
+ true
105
+ ) { t -> t.interval.shiftBy(shiftStart, shiftEnd) }
106
+
107
+ /* *
108
+ * Extends the end of each interval by a duration. The duration can be negative.
109
+ *
110
+ * See [shiftEndpoints] for a warning about coalescing behavior.
111
+ */
112
+ fun extend (shiftEnd : Duration ) = shiftEndpoints(Duration .ZERO , shiftEnd)
40
113
}
0 commit comments