fix performance issues by replacing Google Point and LatLng with lean classes Vector and GeoPos; add bounds check; add update logic for Map move & zoom and screen resize;

This commit is contained in:
Christopher Ronnewinkel (D036654) committed 2014-04-26 00:05:17 +02:00
1 parent e4ff29f52b
commit 2e2820aed1
7 files changed
+342 -116

No files matched your search

@@ -0,0 +1,24 @@
package com.sap.sailing.gwt.ui.simulator;
import com.google.gwt.maps.client.base.LatLng;
public class GeoPos {
public double lat;
public double lng;
public GeoPos() {
}
public GeoPos(double lat, double lng) {
this.lat = lat;
this.lng = lng;
}
public GeoPos(LatLng pos) {
this.lat = pos.getLatitude();
this.lng = pos.getLongitude();
}
}
@@ -28,33 +28,38 @@ public class Mercator {
public void calibrate() {
Point pointSW;
Point pointNE;
Vector pointSW;
Vector pointNE;
int canvasHeight = canvas.getCanvas().getOffsetHeight();
LatLng mapSW = this.map.getBounds().getSouthWest();
LatLng mapNE = this.map.getBounds().getNorthEast();
GeoPos mapSW = new GeoPos();
mapSW.lat = this.map.getBounds().getSouthWest().getLatitude();
mapSW.lng = this.map.getBounds().getSouthWest().getLongitude();
GeoPos mapNE = new GeoPos();
mapNE.lat = this.map.getBounds().getNorthEast().getLatitude();
mapNE.lng = this.map.getBounds().getNorthEast().getLongitude();
pointSW = this.sphere2plane(mapSW);
pointNE = this.sphere2plane(mapNE);
if (pointNE.getX() < pointSW.getX()) {
pointSW = Point.newInstance(pointSW.getX() - 2*Math.PI, pointSW.getY());
if (pointNE.x < pointSW.x) {
pointSW.x -= 2*Math.PI;
}
this.alpha = canvasHeight / (pointNE.getY() - pointSW.getY());
this.beta = pointSW.getX();
this.alpha = canvasHeight / (pointNE.y - pointSW.y);
this.beta = pointSW.x;
this.gamma = - this.alpha;
this.delta = pointNE.getY();
this.delta = pointNE.y;
}
public Point sphere2plane(LatLng p) {
double x = p.getLongitude()*Math.PI/180.0;
double latsin = Math.sin(p.getLatitude()*Math.PI/180.0);
double y = 0.5*Math.log((1.0+latsin)/(1.0-latsin));
return Point.newInstance(x, y);
public Vector sphere2plane(GeoPos p) {
Vector result = new Vector();
result.x = p.lng*Math.PI/180.0;
double latsin = Math.sin(p.lat*Math.PI/180.0);
result.y = 0.5*Math.log((1.0+latsin)/(1.0-latsin));
return result;
}
public LatLng plane2sphere(Point px) {
@@ -63,11 +68,11 @@ public class Mercator {
return LatLng.newInstance(lat, lng);
}
public Point latlng2pixel(LatLng p) {
Point proj = this.sphere2plane(p);
double x = this.alpha * (proj.getX() - this.beta);
double y = this.gamma * (proj.getY() - this.delta);
return Point.newInstance(x, y);
public Vector latlng2pixel(GeoPos p) {
Vector proj = this.sphere2plane(p);
proj.x = this.alpha * (proj.x - this.beta);
proj.y = this.gamma * (proj.y - this.delta);
return proj;
}
public LatLng pixel2latlng(Point px) {
@@ -1,14 +1,12 @@
package com.sap.sailing.gwt.ui.simulator;
import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.base.Point;
public class Particle {
public LatLng pos;
public Point pxOld;
public GeoPos pos;
public Vector pxOld;
public int alpha;
public int age;
public Point v;
public Vector v;
}
@@ -3,82 +3,81 @@ package com.sap.sailing.gwt.ui.simulator;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.base.Point;
public class RectField {
Point[][] field;
Vector[][] field;
private double x0;
private double x1;
private double y0;
private double y1;
public double x0;
public double x1;
public double y0;
public double y1;
private double visX0;
private double visX1;
private double visY0;
private double visY1;
public double visX0;
public double visX1;
public double visY0;
public double visY1;
private int w;
private int h;
private double maxLength;
//private double numParticleFactor;
public double numParticleFactor;
public RectField(Point[][] field, double x0, double y0, double x1, double y1) {
public RectField(Vector[][] field, double x0, double y0, double x1, double y1) {
this.x0 = x0;
this.x1 = x1;
this.y0 = y0;
this.y1 = y1;
this.visX0 = x0;
this.visY0 = y0;
this.visX1 = x1;
this.visY1 = y1;
this.visX0 = 0;
this.visY0 = 0;
this.visX1 = 0;
this.visY1 = 0;
this.field = field;
this.w = field.length;
this.h = field[0].length;
this.maxLength = 0;
//this.numParticleFactor = 4.5;
this.numParticleFactor = 4.5;
double mx = 0;
double my = 0;
for (int i = 0; i < this.w; i++) {
for (int j = 0; j < this.h; j++) {
if (length(field[i][j]) > this.maxLength) {
if (field[i][j].length() > this.maxLength) {
mx = i;
my = j;
}
this.maxLength = Math.max(this.maxLength, length(field[i][j]));
this.maxLength = Math.max(this.maxLength, field[i][j].length());
}
}
mx = (mx / this.w) * (x1 - x0) + x0;
my = (my / this.h) * (y1 - y0) + y0;
}
public static double length(Point p) {
return Math.sqrt(p.getX()*p.getX() + p.getY()*p.getY());
}
//public static double length(Vector p) {
// return Math.sqrt(p.x*p.x + p.y*p.y);
//}
public static RectField read(String jsonData, boolean correctForSphere) {
JSONObject data = JSONParser.parseLenient(jsonData).isObject();
int w = (int)data.get("gridWidth").isNumber().doubleValue();
int h = (int)data.get("gridHeight").isNumber().doubleValue();
Point[][] field = new Point[w][h];
Vector[][] field = new Vector[w][h];
int i = 0;
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
double vx = data.get("field").isArray().get(i++).isNumber().doubleValue();
double vy = data.get("field").isArray().get(i++).isNumber().doubleValue();
Point v = Point.newInstance(vx, vy);
Vector v = new Vector();
v.x = data.get("field").isArray().get(i++).isNumber().doubleValue();
v.y = data.get("field").isArray().get(i++).isNumber().doubleValue();
if (correctForSphere) {
double uy = y / (h - 1);
double lat = data.get("y0").isNumber().doubleValue() * (1 - uy) + data.get("y1").isNumber().doubleValue() * uy;
double m = Math.PI * lat / 180;
//double length = length(v);
v = Point.newInstance(v.getX() / Math.cos(m), v.getY());
v.x = v.x / Math.cos(m);
//v.setLength(length);
}
field[x][y] = v;
@@ -88,19 +87,20 @@ public class RectField {
return result;
}
public LatLng getRandomPosition() {
public GeoPos getRandomPosition() {
double rndY = Math.random();
double rndX = Math.random();
double y = rndY * this.visY0 + (1 - rndY) * this.visY1;
double x = rndX * this.visX0 + (1 - rndX) * this.visX1;
return LatLng.newInstance(y, x);
GeoPos result = new GeoPos();
result.lat = rndY * this.visY0 + (1 - rndY) * this.visY1;
result.lng = rndX * this.visX0 + (1 - rndX) * this.visX1;
return result;
}
public boolean inBounds(double x, double y) {
return x >= this.x0 && x < this.x1 && y >= this.y0 && y < this.y1;
}
public Point interpolate(double a, double b) {
public Vector interpolate(double a, double b) {
int na = (int)Math.floor(a);
int nb = (int)Math.floor(b);
int ma = (int)Math.ceil(a);
@@ -108,22 +108,23 @@ public class RectField {
double fa = a - na;
double fb = b - nb;
double avgX = this.field[na][nb].getX() * (1 - fa) * (1 - fb) + this.field[ma][nb].getX() * fa * (1 - fb) + this.field[na][mb].getX() * (1 - fa) * fb + this.field[ma][mb].getX() * fa * fb;
double avgY = this.field[na][nb].getY() * (1 - fa) * (1 - fb) + this.field[ma][nb].getY() * fa * (1 - fb) + this.field[na][mb].getY() * (1 - fa) * fb + this.field[ma][mb].getY() * fa * fb;
Vector result = new Vector();
result.x = this.field[na][nb].x * (1 - fa) * (1 - fb) + this.field[ma][nb].x * fa * (1 - fb) + this.field[na][mb].x * (1 - fa) * fb + this.field[ma][mb].x * fa * fb;
result.y = this.field[na][nb].y * (1 - fa) * (1 - fb) + this.field[ma][nb].y * fa * (1 - fb) + this.field[na][mb].y * (1 - fa) * fb + this.field[ma][mb].y * fa * fb;
return Point.newInstance(avgX, avgY);
};
return result;
}
public Point getValue(LatLng p) {
double a = (this.w - 1 - 1e-6) * (p.getLongitude() - this.x0) / (this.x1 - this.x0);
double b = (this.h - 1 - 1e-6) * (p.getLatitude() - this.y0) / (this.y1 - this.y0);
public Vector getVector(GeoPos p) {
double a = (this.w - 1 - 1e-6) * (p.lng - this.x0) / (this.x1 - this.x0);
double b = (this.h - 1 - 1e-6) * (p.lat - this.y0) / (this.y1 - this.y0);
if ((a < 0)||(a > (this.w-1))||(b < 0)||(b > (this.h-1))) {
return null;
} else {
return this.interpolate(a, b);
}
};
}
public LatLng getCenter() {
return LatLng.newInstance((y0+y1)/2.0, (x0+x1)/2.0);
@@ -133,27 +134,27 @@ public class RectField {
return maxLength;
}
/*RectField.prototype.motionScale = function(zoomLevel) {
public double motionScale(int zoomLevel) {
return 0.9 * Math.pow(1.7, Math.min(1.0, 6.0 - zoomLevel));
};*/
}
public double particleWeight(LatLng p, Point v) {
return 1.0 - length(v) / this.maxLength;
};
public double particleWeight(GeoPos p, Vector v) {
return 1.0 - v.length() / this.maxLength;
}
/*RectField.prototype.getColors = function() {
var colors = [];
var alpha = 1.0;
var greyValue = 255;
for (var i = 0; i < 256; i++) {
colors[i] = 'rgba(' + (greyValue) + ',' + (greyValue) + ',' + (greyValue) + ',' + (alpha*i/255.0) + ')';
public String[] getColors() {
String[] colors = new String[256];
double alpha = 1.0;
int greyValue = 255;
for (int i = 0; i < 256; i++) {
colors[i] = "rgba(" + (greyValue) + "," + (greyValue) + "," + (greyValue) + "," + (alpha*i/255.0) + ")";
//this.colors[i] = 'hsla(' + 360*(0.55+0.9*(0.5-i/255)) + ',' + (100) + '% ,' + (50) + '%,' + (i/255) + ')';
}
return colors;
};
}
RectField.prototype.lineWidth = function(s) {
public double lineWidth(int alpha) {
return 1.0;
};*/
}
}
@@ -7,6 +7,7 @@ import java.util.List;
import java.util.logging.Logger;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.maps.client.LoadApi;
@@ -19,6 +20,8 @@ import com.google.gwt.maps.client.controls.MapTypeStyle;
import com.google.gwt.maps.client.controls.PanControlOptions;
import com.google.gwt.maps.client.controls.ScaleControlOptions;
import com.google.gwt.maps.client.controls.ZoomControlOptions;
import com.google.gwt.maps.client.events.bounds.BoundsChangeMapEvent;
import com.google.gwt.maps.client.events.bounds.BoundsChangeMapHandler;
import com.google.gwt.maps.client.events.click.ClickMapEvent;
import com.google.gwt.maps.client.events.click.ClickMapHandler;
import com.google.gwt.maps.client.events.idle.IdleMapEvent;
@@ -369,7 +372,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ
if (windParams.isShowStreamlets2()) {
mapTypeStyles = new MapTypeStyle[7];
mapTypeStyles = new MapTypeStyle[11];
// hide all transit lines including ferry lines
mapTypeStyles[0] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.TRANSIT);
@@ -379,18 +382,45 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ
mapTypeStyles[2] = GoogleMapStyleHelper.createSimplifiedStyle(MapTypeStyleFeatureType.ROAD);
// set water color
mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), -50, -50);
mapTypeStyles[4] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.LANDSCAPE, new RGBColor(255, 255, 255), 0, -70);
mapTypeStyles[5] = GoogleMapStyleHelper.createElementStyleOnlyLightness(MapTypeStyleFeatureType.ROAD, MapTypeStyleElementType.ALL, -40);
mapTypeStyles[4] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.LANDSCAPE, new RGBColor(255, 255, 255), -100, -70);
mapTypeStyles[5] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.POI, new RGBColor(255, 255, 255), -100, -70);
mapTypeStyles[6] = GoogleMapStyleHelper.createElementStyleOnlyLightness(MapTypeStyleFeatureType.ROAD, MapTypeStyleElementType.ALL, -40);
MapTypeStyle mapStyle = MapTypeStyle.newInstance();
mapStyle.setFeatureType(MapTypeStyleFeatureType.ADMINISTRATIVE);
mapStyle.setElementType(MapTypeStyleElementType.LABELS);
mapStyle.setElementType(MapTypeStyleElementType.LABELS__TEXT__FILL);
MapTypeStyler[] typeStylers = new MapTypeStyler[1];
typeStylers[0] = MapTypeStyler.newInvertLightnessStyler(true);
mapStyle.setStylers(typeStylers);
mapTypeStyles[6] = mapStyle;
mapTypeStyles[7] = mapStyle;
mapStyle = MapTypeStyle.newInstance();
mapStyle.setFeatureType(MapTypeStyleFeatureType.ADMINISTRATIVE);
mapStyle.setElementType(MapTypeStyleElementType.LABELS__TEXT__STROKE);
typeStylers = new MapTypeStyler[1];
typeStylers[0] = MapTypeStyler.newInvertLightnessStyler(true);
mapStyle.setStylers(typeStylers);
mapTypeStyles[8] = mapStyle;
mapStyle = MapTypeStyle.newInstance();
mapStyle.setFeatureType(MapTypeStyleFeatureType.ADMINISTRATIVE);
mapStyle.setElementType(MapTypeStyleElementType.GEOMETRY__FILL);
typeStylers = new MapTypeStyler[3];
typeStylers[0] = MapTypeStyler.newHueStyler("#ffffff");
typeStylers[1] = MapTypeStyler.newLightnessStyler(-70);
typeStylers[2] = MapTypeStyler.newSaturationStyler(-100);
mapStyle.setStylers(typeStylers);
mapTypeStyles[9] = mapStyle;
} else {
mapStyle = MapTypeStyle.newInstance();
mapStyle.setFeatureType(MapTypeStyleFeatureType.ADMINISTRATIVE);
mapStyle.setElementType(MapTypeStyleElementType.GEOMETRY__STROKE);
typeStylers = new MapTypeStyler[1];
typeStylers[0] = MapTypeStyler.newLightnessStyler(-70);
mapStyle.setStylers(typeStylers);
mapTypeStyles[10] = mapStyle;
} else {
mapTypeStyles = new MapTypeStyle[4];
@@ -433,6 +463,20 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ
add(map, 0, 0);
map.setSize("100%", "100%");
if (parent.particles > 0) {
map.addBoundsChangeHandler(new BoundsChangeMapHandler() {
@Override
public void onEvent(BoundsChangeMapEvent event) {
// improve browser performance by deferred scheduling of redraws
Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
public void execute() {
windStreamletsCanvasOverlay.onBoundsChanged();
}
});
}
});
}
/*map.addZoomChangeHandler(new ZoomChangeMapHandler() {
@Override
@@ -0,0 +1,12 @@
package com.sap.sailing.gwt.ui.simulator;
public class Vector {
public double x;
public double y;
public double length() {
return Math.sqrt(x*x + y*y);
}
}
@@ -13,7 +13,6 @@ import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.base.Point;
import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO;
import com.sap.sailing.gwt.ui.shared.WindFieldDTO;
import com.sap.sailing.gwt.ui.shared.WindFieldGenParamsDTO;
@@ -37,13 +36,18 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti
private boolean visible = false;
private Timer timer;
com.google.gwt.user.client.Timer loopTimer;
private String[] color;
private Mercator projection;
private RectField field;
private int nParticles;
private Particle[] particles;
private LatLng pNE;
private LatLng pSW;
private GeoPos pNE;
private GeoPos pSW;
private boolean swarmOffScreen = false;
private int swarmPause = 0;
private GeoPos boundsNE;
private GeoPos boundsSW;
private static Logger logger = Logger.getLogger(WindStreamletsCanvasOverlay.class.getName());
@@ -279,6 +283,10 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti
}
}
public static native void console(String msg) /*-{
console.log(msg);
}-*/;
public void gwtSwarmTest() {
projection = new Mercator(this, map);
@@ -294,71 +302,206 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti
projection.calibrate();
}
color = new String[256];
for(int alpha=0; alpha<=255; alpha++) {
color[alpha] = "rgba(255,255,255,"+(((double)alpha)/255.0)+")";
}
this.updateBounds();
color = field.getColors();
Context2d ctxt = canvas.getContext2d();
ctxt.setFillStyle("white");
particles = new Particle[nParticles];
particles = this.makeNewParticles();
pNE = map.getBounds().getNorthEast();
pSW = map.getBounds().getSouthWest();
/*// Create animation-loop using scheduler for repeated method calls
Scheduler scheduler = Scheduler.get();
scheduler.scheduleFixedPeriod(this, 40);
//scheduler.scheduleFixedDelay(this, 10);*/
startLoop(40);
}
for(int idx=0; idx<particles.length; idx++) {
LatLng q = field.getRandomPosition();
public Particle[] makeNewParticles() {
Particle[] newParticles = new Particle[nParticles];
for(int idx=0; idx<newParticles.length; idx++) {
GeoPos q = field.getRandomPosition();
Particle particle = new Particle();
particle.pos = q;
particle.age = Math.max(2, (int)Math.round(Math.random()*40));
particle.pxOld = projection.latlng2pixel(particle.pos);
particle.pxOld = projection.latlng2pixel(q);
particle.alpha = 0;
particle.v = field.getValue(q);
particles[idx] = particle;
particle.v = field.getVector(q);
newParticles[idx] = particle;
}
return newParticles;
}
public void onBoundsChanged() {
projection.clearCanvas();
swarmPause = 5;
}
public void onMoveEnd() {
}
public void updateBounds() {
GeoPos mapNE = new GeoPos(map.getBounds().getNorthEast());
GeoPos mapSW = new GeoPos(map.getBounds().getSouthWest());
GeoPos fieldNE = new GeoPos(Math.max(this.field.y0, this.field.y1), Math.max(this.field.x0, this.field.x1));
GeoPos fieldSW = new GeoPos(Math.min(this.field.y0, this.field.y1), Math.min(this.field.x0, this.field.x1));
Vector visibleNE = this.isVisible(fieldNE);
Vector visibleSW = this.isVisible(fieldSW);
boolean useBoundsNorth = (visibleNE.y == 0);
boolean useBoundsEast = (visibleNE.x == 0);
boolean useBoundsSouth = (visibleSW.y == 0);
boolean useBoundsWest = (visibleSW.x == 0);
swarmOffScreen = (visibleNE.y > 0)||(visibleSW.y < 0)||(visibleNE.x < 0)||(visibleSW.x > 0);
if (swarmOffScreen) {
this.boundsNE = fieldNE;
this.boundsSW = fieldSW;
} else {
if ((!useBoundsNorth)&&(!useBoundsEast)) {
this.boundsNE = mapNE;
} else if (!useBoundsNorth) {
this.boundsNE = new GeoPos(mapNE.lat, fieldNE.lng);
} else if (!useBoundsEast) {
this.boundsNE = new GeoPos(fieldNE.lat, mapNE.lng);
} else {
this.boundsNE = fieldNE;
}
if ((!useBoundsSouth)&&(!useBoundsWest)) {
this.boundsSW = mapSW;
} else if (!useBoundsSouth) {
this.boundsSW = new GeoPos(mapSW.lat, fieldSW.lng);
} else if (!useBoundsWest) {
this.boundsSW = new GeoPos(fieldSW.lat, mapSW.lng);
} else {
this.boundsSW = fieldSW;
}
}
Scheduler scheduler = Scheduler.get();
scheduler.scheduleFixedPeriod(this, 40);
}
this.field.visX0 = this.boundsSW.lng;
this.field.visY0 = this.boundsSW.lat;
this.field.visX1 = this.boundsNE.lng;
this.field.visY1 = this.boundsNE.lat;
Vector boundsSWpx = this.projection.latlng2pixel(this.boundsSW);
Vector boundsNEpx = this.projection.latlng2pixel(this.boundsNE);
double boundsWidthpx = Math.abs(boundsNEpx.x - boundsSWpx.x);
double boundsHeightpx = Math.abs(boundsSWpx.y - boundsNEpx.y);
this.nParticles = (int)Math.round(Math.sqrt(boundsWidthpx * boundsHeightpx) * this.field.numParticleFactor);
//this.numParticles = Math.sqrt(boundsWidthpx*boundsWidthpx + boundsHeightpx*boundsHeightpx) * this.field.numParticleFactor;
//console.log("numParticles: "+this.numParticles + " at " + (boundsWidthpx) +"x" + (boundsHeightpx) + "px (" + (boundsWidthpx * boundsHeightpx) + " pixels)");
};
public Vector isVisible(GeoPos pos) {
// test for visibility of swarm
Vector proj = this.projection.latlng2pixel(pos);
Vector result = new Vector();
result.x = (proj.x < 0 ? -1 : 0) + (proj.x > this.getCanvas().getOffsetWidth() ? 1 : 0);
result.y = (proj.y < 0 ? -1 : 0) + (proj.y > this.getCanvas().getOffsetHeight() ? 1 : 0);
return result;
}
public void startLoop(final int millis) {
// Create animation-loop based on timer timeout
loopTimer = new com.google.gwt.user.client.Timer() {
public void run() {
Date time0 = new Date();
if (swarmPause > 1) {
swarmPause--;
} else if (swarmPause == 1) {
setCanvasSettings();
projection.calibrate();
updateBounds();
particles = makeNewParticles();
swarmPause = 0;
}
//console("swarmOffScreen:"+swarmOffScreen);
if ((!swarmOffScreen)&&(swarmPause == 0)) {
execute();
}
Date time1 = new Date();
//console("delta:"+(time1.getTime()-time0.getTime())+"/"+millis);
loopTimer.schedule((int)Math.max(10, millis - (time1.getTime()-time0.getTime())));
}
};
loopTimer.schedule(millis);
}
public void drawSwarm() {
Context2d ctxt = canvas.getContext2d();
ctxt.setGlobalAlpha(0.05);
ctxt.setGlobalAlpha(0.08);
ctxt.setGlobalCompositeOperation("destination-out");
ctxt.setFillStyle("black");
ctxt.fillRect(0, 0, canvas.getOffsetWidth(), canvas.getOffsetHeight());
ctxt.setGlobalAlpha(1.0);
ctxt.setGlobalCompositeOperation("source-over");
ctxt.setFillStyle("white");
//int nDrawn = 0;
for(int idx=0; idx<particles.length; idx++) {
Particle particle = particles[idx];
if (particle.age == 0) {
continue;
}
ctxt.setLineWidth(1.0);
//nDrawn++;
ctxt.setLineWidth(field.lineWidth(particle.alpha));
ctxt.setStrokeStyle(color[particle.alpha]);
ctxt.beginPath();
ctxt.moveTo(particle.pxOld.getX(), particle.pxOld.getY());
ctxt.moveTo(particle.pxOld.x, particle.pxOld.y);
particle.pxOld = projection.latlng2pixel(particle.pos);
ctxt.lineTo(particle.pxOld.getX(), particle.pxOld.getY());
ctxt.lineTo(particle.pxOld.x, particle.pxOld.y);
ctxt.stroke();
}
}
//console("drawn particles:"+nDrawn);
}
/* public void swarmStart() {
int millis = 40; //opt_millis; // || 20;
//self = this;
//function go() {
Date start = new Date();
if (!execute()) {
return;
}
Date time = new Date();
int passed = (int)(time.getTime() - start.getTime());
setTimeout(swarmStart(), Math.max(10, passed));
//}
//go();
}*/
public boolean execute() {
double off = 0.01;
double speed = 0.01*field.motionScale(map.getZoom());
for(int idx=0; idx<particles.length; idx++) {
Particle particle = particles[idx];
Point v = null;
Vector v = null;
if (particle.age <= 0) {
boolean done = false;
while (!done) {
particle.pos = field.getRandomPosition();
v = field.getValue(particle.pos);
v = field.getVector(particle.pos);
double weight = field.particleWeight(particle.pos, v);
if ((weight >= Math.random())&&(v != null)) {
particle.age = Math.max(2, (int)Math.round(Math.random()*40));
@@ -373,14 +516,13 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti
if (particle.v == null) {
particle.age = 0;
} else {
double lat = particle.pos.getLatitude() + off*particle.v.getY();
double lng = particle.pos.getLongitude() + off*particle.v.getX();
double s = RectField.length(particle.v) / field.getMaxLength();
particle.pos.lat = particle.pos.lat + speed*particle.v.y;
particle.pos.lng = particle.pos.lng + speed*particle.v.x;
double s = particle.v.length() / field.getMaxLength();
particle.alpha = (int)Math.min(255, 90 + Math.round(350 * s));
particle.pos = LatLng.newInstance(lat, lng);
particle.age--;
if (particle.age > 0) {
particle.v = field.getValue(particle.pos);
particle.v = field.getVector(particle.pos);
}
}
}