Suppose I have an aobservable, that emits 2D points, denoting turn points of some route
public static double[][] points = new double[][]{
{0, 0},
{0, 10},
{1, 10},
{1, 0},
{0, 0}
};
public static Observable<double[]> routePoints() {
return Observable
.from(points);
}
This means robot starts from origin (0,0)
, then goes to lower left corner of the rectangle, then lower right corner, then back up and finally back to the origin.
Suppose now I want an observable, that feeds that points as if robot is moving at a constant speed, say 1 unit per second.
So, first operator receives (0,0)
and retransmits it as is.
Then it receives (0, 10)
and sees, that distance to this point is 10
units and it should take 10
seconds to reach that point. So, new observable should emit
(0, 1)
(0, 2)
(0, 3)
(0, 4)
(0, 5)
(0, 6)
(0, 7)
(0, 8)
(0, 9)
(0, 10)
one pair per second until last received point reached and re-transmitted. Then operator should take next point and do the same with it.
How to accomplish this with ReactiveX?
I suspect we should implement "bakpressure" here to slow source observable until consequent one re-emits everythong in time?
UPDATE
I wrote the code below and it works. It uses two flatMap
s. One is providing additional points and also accompany each point with timestamp (in seconds) and another flatMap
introduce delays appropriate to timestamps.
The code works but it looks complex. The question persists: whether ReactiveX library contains ready-made tools for these sort of things:
import rx.Observable;
import rx.functions.Action1;
import rx.functions.Func1;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
public class EmitByTime {
public static double speed = 1;
public static double[][] points = new double[][]{
{0, 0},
{0, 10},
{1, 10},
{1, 0},
{0, 0}
};
public static Observable<double[]> routePoints() {
return Observable
.from(points);
}
public static Observable<double[]> routeFeeder() {
return routePoints()
.flatMap(new Func1<double[], Observable<double[]>>() {
double[] previous = null;
@Override
public Observable<double[]> call(double[] doubles) {
if( previous == null) {
previous = new double[] {doubles[0], doubles[1], 0};
return Observable.just(previous);
}
else {
double dx = doubles[0] - previous[0];
double dy = doubles[1] - previous[1];
double dist = Math.sqrt( dx*dx + dy*dy );
dx /= dist;
dy /= dist;
double vx = dx * speed;
double vy = dy * speed;
double period = dist / speed;
double end = previous[2] + period;
double start = Math.ceil(previous[2] + 1);
ArrayList<double[]> sequence = new ArrayList<>();
for(double t = start; t < end; t+=1) {
double tt = (t - previous[2]);
double x = previous[0] + tt * vx;
double y = previous[1] + tt * vy;
sequence.add(new double[] {x, y, t});
}
previous = new double[] {doubles[0], doubles[1], end};
sequence.add(previous);
return Observable.from(sequence);
}
}
})
.flatMap(new Func1<double[], Observable<double[]>>() {
long origin = System.currentTimeMillis();
@Override
public Observable<double[]> call(double[] doubles) {
long now = System.currentTimeMillis();
long due = Math.round(doubles[2]*1000) + origin;
if( due <= now ) {
return Observable.just(doubles);
}
else {
return Observable.just(doubles).delay(due-now, TimeUnit.MILLISECONDS);
}
}
})
;
}
public static void main(String[] args) throws InterruptedException {
routeFeeder().subscribe(new Action1<double[]>() {
@Override
public void call(double[] doubles) {
System.out.println(Arrays.toString(doubles));
}
});
Thread.sleep(20000);
}
}