mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-24 14:38:45 +00:00
bug4134_2: A working version of colored windstreamlets according to minmax speed.
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
package com.sap.sailing.gwt.ui.shared.racemap;
|
||||
|
||||
public interface SwarmMinMaxSpeedChangedListener {
|
||||
void onSwarmMinMaxSpeedChanged();
|
||||
}
|
||||
+71
-42
@@ -11,6 +11,7 @@ import com.google.gwt.canvas.dom.client.Context2d;
|
||||
import com.google.gwt.canvas.dom.client.TextMetrics;
|
||||
import com.google.gwt.core.client.Scheduler;
|
||||
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
|
||||
import com.google.gwt.i18n.client.NumberFormat;
|
||||
import com.google.gwt.maps.client.MapWidget;
|
||||
import com.google.gwt.maps.client.controls.ControlPosition;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
@@ -18,6 +19,7 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.gwt.ui.actions.GetWindInfoAction;
|
||||
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.client.shared.racemap.CoordinateSystem;
|
||||
@@ -44,7 +46,7 @@ import com.sap.sse.gwt.client.player.Timer;
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay {
|
||||
public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay implements SwarmMinMaxSpeedChangedListener{
|
||||
public static final String LOAD_WIND_STREAMLET_DATA_CATEGORY = "loadWindStreamletData";
|
||||
private static final int animationIntervalMillis = 40;
|
||||
private static final long RESOLUTION_IN_MILLIS = 5000;
|
||||
@@ -66,6 +68,8 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay {
|
||||
|
||||
private long latitudeCount;
|
||||
private double latitudeSum;
|
||||
|
||||
private final NumberFormat numberFormatOneDecimal = NumberFormatterFactory.getDecimalFormat(1);
|
||||
|
||||
public WindStreamletsRaceboardOverlay(MapWidget map, int zIndex, final Timer timer,
|
||||
RegattaAndRaceIdentifier raceIdentifier, SailingServiceAsync sailingService,
|
||||
@@ -130,45 +134,62 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay {
|
||||
streamletLegend.setCoordinateSpaceWidth(canvasWidth);
|
||||
streamletLegend.setCoordinateSpaceHeight(canvasHeight); }
|
||||
|
||||
private void drawLegend() {
|
||||
double x, y;
|
||||
x=100;
|
||||
y=16;
|
||||
double w = 1;
|
||||
double h = 20;
|
||||
int maxIdx = 300;
|
||||
Context2d context2d = streamletLegend.getContext2d();
|
||||
context2d.setFillStyle("rgba(0,0,0,.3)");
|
||||
context2d.setLineWidth(1.0);
|
||||
context2d.beginPath();
|
||||
context2d.fillRect(x-7.0, y-16.0, w*maxIdx+15.0, 56.0);
|
||||
context2d.closePath();
|
||||
context2d.stroke();
|
||||
context2d.setFillStyle("white");
|
||||
String label = stringMessages.windSpeedInKnots();
|
||||
TextMetrics txtmet;
|
||||
txtmet = context2d.measureText(label);
|
||||
context2d.fillText(label, x + (w*maxIdx - txtmet.getWidth())/2.0, y - 5.0);
|
||||
for(int idx=0; idx <= maxIdx; idx++) {
|
||||
//double speed = idx * 24.0 / maxIdx;
|
||||
double speed = 4.0 + idx * 16.0 / maxIdx;
|
||||
context2d.setFillStyle(windField.getColor(speed));
|
||||
context2d.beginPath();
|
||||
context2d.fillRect(x + idx*w, y, w, h);
|
||||
context2d.closePath();
|
||||
context2d.stroke();
|
||||
if (Math.abs(speed % 2.0) < (12.0/maxIdx)) {
|
||||
context2d.setStrokeStyle("white");
|
||||
public void drawLegend() {
|
||||
if (streamletLegend.isVisible()) {
|
||||
streamletLegend.getContext2d().clearRect(0, 0, streamletLegend.getCoordinateSpaceWidth(),
|
||||
streamletLegend.getCoordinateSpaceHeight());
|
||||
if (swarm != null && swarm.isColored()) {
|
||||
final double x = 100;
|
||||
final double y = 16;
|
||||
final double w = 1;
|
||||
final double h = 20;
|
||||
final double speed_max = swarm.getMaxParticleSpeedInKnots();
|
||||
final double speed_min = swarm.getMinParticleSpeedInKnots();
|
||||
final double speed_spread = speed_max - speed_min;
|
||||
final int scale_spread;
|
||||
if (speed_spread < 0.5) {
|
||||
scale_spread = 300;
|
||||
}
|
||||
else if (speed_spread < 1) {
|
||||
scale_spread = 100;
|
||||
}
|
||||
else {
|
||||
scale_spread = 50;
|
||||
}
|
||||
final int maxIdx = 300;
|
||||
Context2d context2d = streamletLegend.getContext2d();
|
||||
context2d.setFillStyle("rgba(0,0,0,.3)");
|
||||
context2d.setLineWidth(1.0);
|
||||
context2d.beginPath();
|
||||
context2d.moveTo(x + idx*w, y + h);
|
||||
context2d.lineTo(x + idx*w, y + h + 7.0);
|
||||
context2d.fillRect(x - 10.0, y - 16.0, w * maxIdx + 20.0, 56.0);
|
||||
context2d.closePath();
|
||||
context2d.stroke();
|
||||
context2d.setFillStyle("white");
|
||||
label = ""+Math.round(speed);
|
||||
String label = stringMessages.windSpeedInKnots();
|
||||
TextMetrics txtmet;
|
||||
txtmet = context2d.measureText(label);
|
||||
context2d.fillText(label, x + idx*w - txtmet.getWidth()/2.0, y + h + 8.0 + 8.0);
|
||||
context2d.fillText(label, x + (w * maxIdx - txtmet.getWidth()) / 2.0, y - 5.0);
|
||||
for (int idx = 0; idx <= maxIdx; idx++) {
|
||||
final double speedSteps = speed_min + idx * (speed_max - speed_min) / maxIdx;
|
||||
context2d.setFillStyle(swarm.getColorWithSpectrumBoundaries(speedSteps));
|
||||
context2d.beginPath();
|
||||
context2d.fillRect(x + idx * w, y, w, h);
|
||||
context2d.closePath();
|
||||
context2d.stroke();
|
||||
if (idx % scale_spread == 0) {
|
||||
context2d.setStrokeStyle("white");
|
||||
context2d.setLineWidth(1.0);
|
||||
context2d.beginPath();
|
||||
context2d.moveTo(x + idx * w, y + h);
|
||||
context2d.lineTo(x + idx * w, y + h + 7.0);
|
||||
context2d.closePath();
|
||||
context2d.stroke();
|
||||
context2d.setFillStyle("white");
|
||||
label = numberFormatOneDecimal.format(speedSteps);
|
||||
txtmet = context2d.measureText(label);
|
||||
context2d.fillText(label, x + idx * w - txtmet.getWidth() / 2.0, y + h + 8.0 + 8.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,7 +201,9 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay {
|
||||
this.swarm = new Swarm(this, map, timer, windField, new StreamletParameters());
|
||||
}
|
||||
initCanvasOrigin();
|
||||
this.swarm.setColors(colored);
|
||||
this.swarm.start(animationIntervalMillis);
|
||||
this.swarm.addSwarmMinMaxSpeedChangedListener(this);
|
||||
}
|
||||
|
||||
private void scheduleWindDataRefresh() {
|
||||
@@ -203,8 +226,9 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay {
|
||||
}
|
||||
|
||||
private void stopStreamlets() {
|
||||
if (swarm != null) {
|
||||
if (this.swarm != null) {
|
||||
this.swarm.stop();
|
||||
this.swarm.removeSwarmMinMaxSpeedChangedListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,13 +322,13 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay {
|
||||
public void setVisible(boolean isVisible) {
|
||||
if (getCanvas() != null) {
|
||||
if (isVisible) {
|
||||
if (this.windField.getColors()) {
|
||||
this.streamletLegend.setVisible(true);
|
||||
}
|
||||
this.startStreamlets();
|
||||
this.visible = isVisible;
|
||||
if (this.swarm.isColored()) {
|
||||
this.streamletLegend.setVisible(true);
|
||||
}
|
||||
} else {
|
||||
if (this.windField.getColors()) {
|
||||
if (this.swarm.isColored()) {
|
||||
this.streamletLegend.setVisible(false);
|
||||
}
|
||||
this.stopStreamlets();
|
||||
@@ -312,14 +336,15 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean colored;
|
||||
public void setColors(boolean isColored) {
|
||||
this.windField.setColors(isColored);
|
||||
this.colored = isColored;
|
||||
if ((isColored) && (firstColoring)) {
|
||||
firstColoring = false;
|
||||
this.drawLegend();
|
||||
}
|
||||
this.streamletLegend.setVisible(isColored);
|
||||
if (swarm != null) {
|
||||
swarm.setColors(isColored);}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -337,4 +362,8 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay {
|
||||
swarm.onBoundsChanged(zoomChanged, swarmPause);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSwarmMinMaxSpeedChanged() {
|
||||
drawLegend();
|
||||
}
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.sap.sailing.gwt.ui.simulator.streamlets;
|
||||
|
||||
public class SchmittTrigger {
|
||||
private double percentage;
|
||||
|
||||
public SchmittTrigger() {}
|
||||
public SchmittTrigger(double percentageTriggerThreeshold) {
|
||||
this.percentage = percentageTriggerThreeshold;
|
||||
}
|
||||
|
||||
public boolean isGreaterThanThreeshold(double input,double threesholdValue) {
|
||||
if (input * (1 + percentage/100) > threesholdValue) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public boolean isLesserThanThreeshold(double input,double threesholdValue) {
|
||||
if (input * (1 - percentage/100) < threesholdValue) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public boolean isValueChangeTriggered(double input, double threesholdValue) {
|
||||
if (isGreaterThanThreeshold(input, threesholdValue) || isLesserThanThreeshold(input, threesholdValue)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public double getTriggerPercentage() {
|
||||
return percentage;
|
||||
}
|
||||
}
|
||||
+86
-4
@@ -1,6 +1,8 @@
|
||||
package com.sap.sailing.gwt.ui.simulator.streamlets;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gwt.canvas.client.Canvas;
|
||||
import com.google.gwt.canvas.dom.client.Context2d;
|
||||
@@ -13,6 +15,7 @@ import com.google.gwt.maps.client.events.bounds.BoundsChangeMapHandler;
|
||||
import com.google.gwt.user.client.Timer;
|
||||
import com.sap.sailing.gwt.ui.client.shared.racemap.BoundsUtil;
|
||||
import com.sap.sailing.gwt.ui.client.shared.racemap.CoordinateSystem;
|
||||
import com.sap.sailing.gwt.ui.shared.racemap.SwarmMinMaxSpeedChangedListener;
|
||||
import com.sap.sailing.gwt.ui.simulator.StreamletParameters;
|
||||
import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay;
|
||||
import com.sap.sse.gwt.client.player.TimeListener;
|
||||
@@ -74,6 +77,8 @@ public class Swarm implements TimeListener {
|
||||
timePoint = timer.getTime();
|
||||
cosineOfAverageLatitude = 1.0; // default to equator
|
||||
diffPx = new Vector(0, 0);
|
||||
swarmMinMaxSpeedChangedListeners = new HashSet<>();
|
||||
schmittTrigger = new SchmittTrigger(/*percent*/ 25);
|
||||
}
|
||||
|
||||
public void start(final int animationIntervalMillis) {
|
||||
@@ -113,7 +118,9 @@ public class Swarm implements TimeListener {
|
||||
while (!done && attempts-- > 0) {
|
||||
particle = new Particle();
|
||||
particle.currentPosition = getRandomPosition();
|
||||
if (field.inBounds(particle.currentPosition)) {
|
||||
// Is the if check really needed? getRandomPosition() returns always a position that is within the
|
||||
// visibleBoundsOfField. visibleBoundsOfField is always within fieldBounds. See updateBounds().
|
||||
if(field.inBounds(particle.currentPosition)) {
|
||||
Vector v = field.getVector(particle.currentPosition, timePoint);
|
||||
double weight = field.getParticleWeight(particle.currentPosition, v);
|
||||
if (weight >= Math.random()) {
|
||||
@@ -145,7 +152,6 @@ public class Swarm implements TimeListener {
|
||||
result = LatLng.newInstance(latDeg, lngDeg);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Particle[] createParticles() {
|
||||
Particle[] newParticles = new Particle[nParticles];
|
||||
for (int idx = 0; idx < newParticles.length; idx++) {
|
||||
@@ -234,7 +240,7 @@ public class Swarm implements TimeListener {
|
||||
}
|
||||
double particleSpeed = particle.v == null ? 0 : particle.v.length();
|
||||
ctxt.setLineWidth(field.getLineWidth(particleSpeed));
|
||||
ctxt.setStrokeStyle(field.getColor(particleSpeed));
|
||||
ctxt.setStrokeStyle(getColorWithSpectrumBoundaries(particleSpeed));
|
||||
ctxt.beginPath();
|
||||
ctxt.moveTo(particle.previousPixelCoordinate.x, particle.previousPixelCoordinate.y);
|
||||
ctxt.lineTo(particle.currentPixelCoordinate.x, particle.currentPixelCoordinate.y);
|
||||
@@ -242,11 +248,61 @@ public class Swarm implements TimeListener {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean colored = false;
|
||||
public String getColorWithSpectrumBoundaries(double speed) {
|
||||
if (colored) {
|
||||
double h = (1 - (speed - minParticleSpeedInKnots)/(maxParticleSpeedInKnots - minParticleSpeedInKnots)) * 240;
|
||||
return "hsl(" + Math.round(h) + ", 100%, 50%)";
|
||||
} else {
|
||||
return "rgba(255,255,255," + Math.min(1.0, speed / 40) + ")";
|
||||
}
|
||||
}
|
||||
public void setColors(boolean isColored) {
|
||||
this.colored = isColored;
|
||||
}
|
||||
|
||||
public boolean isColored() {
|
||||
return colored;
|
||||
}
|
||||
|
||||
private double maxParticleSpeedInKnots = 0.0;
|
||||
/**
|
||||
* @return the maxParticleSpeedInKnots
|
||||
*/
|
||||
public double getMaxParticleSpeedInKnots() {
|
||||
return maxParticleSpeedInKnots;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the minParticleSpeedInKnots
|
||||
*/
|
||||
public double getMinParticleSpeedInKnots() {
|
||||
return minParticleSpeedInKnots;
|
||||
}
|
||||
|
||||
private double minParticleSpeedInKnots = 120.0;
|
||||
|
||||
private boolean isNotifyListeners = true;
|
||||
|
||||
private void notifyListeners() {
|
||||
if (isNotifyListeners) {
|
||||
for (SwarmMinMaxSpeedChangedListener listener : swarmMinMaxSpeedChangedListeners) {
|
||||
listener.onSwarmMinMaxSpeedChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final SchmittTrigger schmittTrigger;
|
||||
|
||||
/**
|
||||
* Moves each particle by its vector {@link Particle#v} multiplied by the speed which is 0.01 times the
|
||||
* {@link VectorField#getMotionScale(int)} at the map's current zoom level.
|
||||
* {@link VectorField#getMotionScale(int)} at the map's current zoom level. Also the max and min speed fields of the
|
||||
* swarm get updated and the listeners get notified.
|
||||
*/
|
||||
private boolean execute(Vector diffPx) {
|
||||
boolean isUpdateListeners = false;
|
||||
double minSpeed = 120.0;
|
||||
double maxSpeed = 0.0;
|
||||
double speed = field.getMotionScale(map.getZoom());
|
||||
for (int idx = 0; idx < particles.length; idx++) {
|
||||
Particle particle = particles[idx];
|
||||
@@ -268,6 +324,12 @@ public class Swarm implements TimeListener {
|
||||
particle.stepsToLive--;
|
||||
if ((particle.stepsToLive > 0) && (this.field.inBounds(particle.currentPosition))) {
|
||||
particle.v = field.getVector(particle.currentPosition, timePoint);
|
||||
if (particle.v.length() > maxSpeed) {
|
||||
maxSpeed = particle.v.length();
|
||||
}
|
||||
if (particle.v.length() < minSpeed) {
|
||||
minSpeed = particle.v.length();
|
||||
}
|
||||
} else {
|
||||
particle.v = null;
|
||||
}
|
||||
@@ -276,7 +338,17 @@ public class Swarm implements TimeListener {
|
||||
particles[idx] = this.createParticle();
|
||||
}
|
||||
}
|
||||
if (schmittTrigger.isValueChangeTriggered(maxSpeed, maxParticleSpeedInKnots)) {
|
||||
maxParticleSpeedInKnots = maxSpeed;
|
||||
isUpdateListeners = true;
|
||||
}
|
||||
if (schmittTrigger.isValueChangeTriggered(minSpeed, minParticleSpeedInKnots)) {
|
||||
minParticleSpeedInKnots = minSpeed;
|
||||
isUpdateListeners = true;
|
||||
}
|
||||
this.isNotifyListeners = isUpdateListeners;
|
||||
drawSwarm();
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -288,4 +360,14 @@ public class Swarm implements TimeListener {
|
||||
public void timeChanged(Date newTime, Date oldTime) {
|
||||
timePoint = newTime;
|
||||
}
|
||||
|
||||
private final Set<SwarmMinMaxSpeedChangedListener> swarmMinMaxSpeedChangedListeners;
|
||||
public void addSwarmMinMaxSpeedChangedListener(SwarmMinMaxSpeedChangedListener listener) {
|
||||
swarmMinMaxSpeedChangedListeners.add(listener);
|
||||
}
|
||||
public void removeSwarmMinMaxSpeedChangedListener(SwarmMinMaxSpeedChangedListener listener) {
|
||||
swarmMinMaxSpeedChangedListeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user