Statistics
| Revision:

root / org.gvsig.legend.heatmap / trunk / org.gvsig.legend.heatmap / org.gvsig.legend.heatmap.lib / org.gvsig.legend.heatmap.lib.impl / src / main / java / org / gvsig / legend / heatmap / lib / impl / DefaultHeatmapLegend.java @ 1847

History | View | Annotate | Download (17.7 KB)

1
package org.gvsig.legend.heatmap.lib.impl;
2

    
3
import java.awt.Color;
4
import java.awt.Graphics2D;
5
import java.awt.Image;
6
import java.awt.image.BufferedImage;
7
import java.util.Map;
8
import org.apache.commons.lang3.StringUtils;
9
import org.cresques.cts.ICoordTrans;
10
import org.gvsig.fmap.dal.exception.DataException;
11
import org.gvsig.fmap.dal.feature.Feature;
12
import org.gvsig.fmap.dal.feature.FeatureQuery;
13
import org.gvsig.fmap.dal.feature.FeatureSelection;
14
import org.gvsig.fmap.dal.feature.FeatureSet;
15
import org.gvsig.fmap.dal.feature.FeatureStore;
16
import org.gvsig.fmap.dal.feature.FeatureType;
17
import org.gvsig.fmap.geom.Geometry;
18
import org.gvsig.fmap.geom.primitive.Point;
19
import org.gvsig.fmap.mapcontext.MapContextException;
20
import org.gvsig.fmap.mapcontext.ViewPort;
21
import org.gvsig.fmap.mapcontext.layers.operations.IHasImageLegend;
22
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
23
import org.gvsig.fmap.mapcontext.rendering.legend.events.SymbolLegendEvent;
24
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
25
import org.gvsig.gui.ColorTablePainter;
26
import org.gvsig.gui.DefaultColorTablePainter;
27
import org.gvsig.legend.heatmap.lib.api.HeatmapLegend;
28
import org.gvsig.symbology.fmap.mapcontext.rendering.legend.impl.AbstractVectorialLegend;
29
import org.gvsig.symbology.fmap.mapcontext.rendering.legend.impl.DefaultFeatureDrawnNotification;
30
import org.gvsig.symbology.fmap.mapcontext.rendering.symbol.text.impl.SimpleTextSymbol;
31
import org.gvsig.tools.exception.BaseException;
32
import org.gvsig.tools.task.Cancellable;
33
import org.gvsig.tools.visitor.VisitCanceledException;
34
import org.gvsig.tools.visitor.Visitor;
35

    
36
public class DefaultHeatmapLegend extends AbstractVectorialLegend implements HeatmapLegend, IHasImageLegend {
37

    
38
    private class DensityAlgorithm {
39

    
40
        private double[][] grid;
41
        private double[][] kernel;
42
        private int distance;
43
        private int height;
44
        private int with;
45
        private double maxValue;
46
        private double minValue;
47

    
48
        public DensityAlgorithm(int distance) {
49
            this.setDistance(distance);
50
        }
51

    
52
        public void setDistance(int distance) {
53
            this.distance = distance;
54
            this.kernel = new double[2 * this.distance + 1][2 * this.distance + 1];
55
            for( int y = -this.distance; y < this.distance + 1; y++ ) {
56
                for( int x = -this.distance; x < this.distance + 1; x++ ) {
57
                    final double dDist = Math.sqrt(x * x + y * y);
58
                    if( dDist < this.distance ) {
59
                        this.kernel[x + this.distance][y + this.distance] = Math.pow(1 - (dDist * dDist) / (this.distance * this.distance), 2);
60
                    } else {
61
                        this.kernel[x + this.distance][y + this.distance] = 0;
62
                    }
63
                }
64
            }
65
        }
66

    
67
        public int getDistance() {
68
            return this.distance;
69
        }
70

    
71
        public void init(int with, int height) {
72
            this.with = with;
73
            this.height = height;
74
            this.grid = new double[with][height];
75
            this.maxValue = 0;
76
            this.minValue = 0;
77
        }
78

    
79
        public void add(int px, int py) {
80
            add(px, py, 1);
81
        }
82

    
83
        public void add(int px, int py, double value) {
84
            for( int y = -this.distance; y < this.distance + 1; y++ ) {
85
                for( int x = -this.distance; x < this.distance + 1; x++ ) {
86
                    if( this.kernel[x + this.distance][y + this.distance] != 0 ) {
87
                        addValue(px + x, py + y, value * this.kernel[x + this.distance][y + this.distance]);
88
                    }
89
                }
90
            }
91
        }
92

    
93
        private void addValue(int px, int py, double value) {
94
            if( px < 0 || py < 0 || px >= with || py >= height ) {
95
                return;
96
            }
97
            value += this.grid[px][py];
98
            this.grid[px][py] = value;
99
            if( value > this.maxValue ) {
100
                this.maxValue = value;
101
            }
102
            if( value < this.minValue ) {
103
                this.minValue = value;
104
            }
105
        }
106

    
107
        public void draw(BufferedImage img, Graphics2D g, HeatmapColorTable hmColorTable, Cancellable cancel) {
108
            try {
109
                Color c;
110
                int maxIndexColor = hmColorTable.length-1;
111
                for( int x = 0; x < with; x++ ) {
112
                    for( int y = 0; y < height; y++ ) {
113
                        if( cancel.isCanceled() ) {
114
                            return;
115
                        }
116
                        double value = this.grid[x][y];
117
                        if( value > 0 ) {
118
                            int icolor = (int) (value * maxIndexColor / maxValue);
119
//                            icolor = icolor < 0 ? 0 : icolor >= maxIndexColor ? maxIndexColor - 1 : icolor;
120
                            c = blend(new Color(img.getRGB(x, y)),hmColorTable.getColorTable()[icolor]);
121
                            img.setRGB(x, y, c.getRGB());
122
                        }
123
                    }
124
                }
125
            } catch (Exception ex) {
126
                LOG.warn("Problems drawing heatmap", ex);
127
            }
128
        }
129

    
130
        private Color blend(Color dst, Color src) {
131
            //
132
            // https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
133
            //
134
            // when the destination background is opaque
135
            //
136
            // double outa = 1;
137
            double srca = src.getAlpha() / 255.0;
138
            double srca_1 = (1 - srca);
139

    
140
            Color color = new Color(
141
                (int)(src.getRed()  * srca + dst.getRed()  * srca_1) & 0xff,
142
                (int)(src.getGreen()* srca + dst.getGreen()* srca_1) & 0xff,
143
                (int)(src.getBlue() * srca + dst.getBlue() * srca_1) & 0xff
144
            );
145
            return color;
146
        }
147

    
148
    }
149

    
150
    private final ISymbol defaultSymbol;
151
    private final DensityAlgorithm algorithm;
152
    private boolean isRamp;
153
    private Color[] sourceColorTable;
154
    private int colorTableHotColorAlpha;
155
    private int colorTableColdColorAlpha;
156
    private String fieldName;
157
    private boolean useAlphaInColorTable;
158
    private Image imageLegend;
159
    private HeatmapColorTable hmColorTable;
160

    
161
    public DefaultHeatmapLegend() {
162
        this.defaultSymbol = new SimpleTextSymbol();
163
        this.algorithm = new DensityAlgorithm(30);
164
        this.colorTableHotColorAlpha = 255;
165
        this.colorTableColdColorAlpha = 0;
166
        this.useAlphaInColorTable = false;
167
        this.setColorTable(100, new Color(0, 0, 255, 0), new Color(255, 0, 0, 255));
168
        this.imageLegend = null;
169
    }
170

    
171
    @Override
172
    protected String[] getRequiredFeatureAttributeNames(FeatureStore featureStore) throws DataException {
173
        FeatureType ftype = featureStore.getDefaultFeatureType();
174
        if( StringUtils.isEmpty(this.fieldName) ) {
175
            return new String[]{
176
                ftype.getDefaultGeometryAttributeName()
177
            };
178
        }
179
        return new String[]{
180
            ftype.getDefaultGeometryAttributeName(),
181
            this.fieldName
182
        };
183
    }
184

    
185
    @Override
186
    public ISymbol getDefaultSymbol() {
187
        return this.defaultSymbol;
188
    }
189

    
190
    @Override
191
    public void setDefaultSymbol(ISymbol is) {
192
    }
193

    
194
    @Override
195
    public ISymbol getSymbolByFeature(Feature ftr) throws MapContextException {
196
        return this.defaultSymbol;
197
    }
198

    
199
    @Override
200
    public int getShapeType() {
201
        return Geometry.TYPES.GEOMETRY;
202
    }
203

    
204
    @Override
205
    public void setShapeType(int i) {
206
    }
207

    
208
    @Override
209
    public boolean isUseDefaultSymbol() {
210
        return true;
211
    }
212

    
213
    @Override
214
    public void useDefaultSymbol(boolean bln) {
215
    }
216

    
217
    @Override
218
    public boolean isSuitableForShapeType(int shapeType) {
219
        return true;
220
    }
221

    
222
    @Override
223
    protected void draw(BufferedImage image, Graphics2D g, ViewPort viewPort, Cancellable cancel, double scale, Map queryParameters, ICoordTrans coordTrans, FeatureStore featureStore, FeatureQuery featureQuery, double dpi) throws LegendException {
224
        this.algorithm.init(image.getWidth(), image.getHeight());
225
        super.draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans, featureStore, featureQuery, dpi);
226
        if( !cancel.isCanceled() ) {
227
            hmColorTable = null;
228
            if( this.useRamp() || !useAlphaInColorTable ) {
229
                hmColorTable = new HeatmapColorTable(this.getSourceColorTable());
230
            } else {
231
                hmColorTable = new HeatmapColorTable(this.getSourceColorTable(),colorTableColdColorAlpha, colorTableHotColorAlpha);
232
            }
233
            this.algorithm.draw(image, g, hmColorTable, cancel);
234
        }
235
    }
236

    
237
    @Override
238
    protected void drawFeatures(
239
        BufferedImage image,
240
        Graphics2D g,
241
        final ViewPort viewPort,
242
        final Cancellable cancel,
243
        final ICoordTrans coordTrans,
244
        double dpi,
245
        DefaultFeatureDrawnNotification drawnNotification,
246
        FeatureSet featureSet,
247
        FeatureSelection selection
248
    ) throws BaseException {
249
        int x = -1;
250
        if( !StringUtils.isEmpty(this.fieldName) ) {
251
            x = featureSet.getDefaultFeatureType().getIndex(this.fieldName);
252
        }
253
        final int n = x;
254
        featureSet.accept(new Visitor() {
255
            @Override
256
            public void visit(Object o) throws VisitCanceledException, BaseException {
257
                if( cancel.isCanceled() ) {
258
                    throw new VisitCanceledException();
259
                }
260
                Feature feature = (Feature) o;
261
                Geometry geom = feature.getDefaultGeometry();
262
                if( geom != null ) {
263
                    Point pointGeo = geom.centroid();
264
                    if( coordTrans != null ) {
265
                        pointGeo.reProject(coordTrans);
266
                    }
267
                    Point pointPixels = (Point) pointGeo.cloneGeometry();
268
                    pointPixels.transform(viewPort.getAffineTransform());
269
                    if( n >= 0 ) {
270
                        double value = 0;
271
                        try {
272
                            value = feature.getDouble(n);
273
                        } catch(Exception ex) {
274
                        }
275
                        if( value >0 ) {
276
                            algorithm.add((int) pointPixels.getX(), (int) pointPixels.getY(), value);
277
                        }
278
                    } else {
279
                        algorithm.add((int) pointPixels.getX(), (int) pointPixels.getY());
280
                    }
281
                }
282
            }
283
        });
284
    }
285

    
286
    /**
287
     * @return the distance
288
     */
289
    @Override
290
    public int getDistance() {
291
        return this.algorithm.getDistance();
292
    }
293

    
294
    /**
295
     * @param distance the distance to set
296
     */
297
    @Override
298
    public void setDistance(int distance) {
299
        this.algorithm.setDistance(distance);
300
    }
301

    
302
    @Override
303
    public void setColorTable(Color[] colorTable) {
304
        this.isRamp = false;
305
        this.sourceColorTable = colorTable;
306
        this.imageLegend = null;
307
        this.fireDefaultSymbolChangedEvent(new SymbolLegendEvent(null,null));
308
    }
309

    
310
    @Override
311
    public void setColorTable(int numColor, Color first, Color last) {
312
        Color[] table = new Color[numColor];
313

    
314
        double deltaRed = (last.getRed() - first.getRed()) / numColor;
315
        double deltaGreen = (last.getGreen() - first.getGreen()) / numColor;
316
        double deltaBlue = (last.getBlue() - first.getBlue()) / numColor;
317
        double deltaAlpha = (last.getAlpha() - first.getAlpha()) / numColor;
318
        for( int i = 0; i < numColor; i++ ) {
319
            table[i] = new Color(
320
                (int) (first.getRed() + i * deltaRed),
321
                (int) (first.getGreen() + i * deltaGreen),
322
                (int) (first.getBlue() + i * deltaBlue),
323
                (int) (first.getAlpha() + i * deltaAlpha)
324
            );
325
        }
326
        this.isRamp = true;
327
        this.sourceColorTable = table;
328
        this.imageLegend = null;
329
        this.fireDefaultSymbolChangedEvent(new SymbolLegendEvent(null,null));
330
    }
331

    
332
    @Override
333
    public Color[] getSourceColorTable() {
334
        return this.sourceColorTable;
335
    }
336

    
337
    @Override
338
    public Color[] getTargetColorTable() {
339
        return this.getHetMapColorTable().getColorTable();
340
    }
341

    
342
    private HeatmapColorTable getHetMapColorTable() {
343
        if (this.hmColorTable == null) {
344
            if (this.useRamp() || !useAlphaInColorTable) {
345
                this.hmColorTable = new HeatmapColorTable(this.getSourceColorTable());
346
            } else {
347
                this.hmColorTable =
348
                    new HeatmapColorTable(this.getSourceColorTable(), colorTableColdColorAlpha, colorTableHotColorAlpha);
349
            }
350
        }
351
        return this.hmColorTable;
352
    }
353

    
354

    
355
    @Override
356
    public boolean useRamp() {
357
        return this.isRamp;
358
    }
359

    
360
    @Override
361
    public String getFieldName() {
362
        return this.fieldName;
363
    }
364

    
365
    @Override
366
    public void setFieldName(String fieldName) {
367
        this.fieldName = fieldName;
368
    }
369

    
370
    @Override
371
    public int getColorTableHotColorAlpha() {
372
        return colorTableHotColorAlpha;
373
    }
374

    
375
    @Override
376
    public void setColorTableHotColorAlpha(int colorTableHotColorAlpha) {
377
        this.colorTableHotColorAlpha = colorTableHotColorAlpha;
378
        this.imageLegend = null;
379
    }
380

    
381
    @Override
382
    public int getColorTableColdColorAlpha() {
383
        return colorTableColdColorAlpha;
384
    }
385

    
386
    @Override
387
    public void setColorTableColdColorAlpha(int colorTableColdColorAlpha) {
388
        this.colorTableColdColorAlpha = colorTableColdColorAlpha;
389
        this.imageLegend = null;
390
    }
391

    
392
    @Override
393
    public boolean useAlphaInColorTable() {
394
        return this.useAlphaInColorTable;
395
    }
396

    
397
    @Override
398
    public boolean setUseAlphaInColorTable(boolean use) {
399
        boolean x = this.useAlphaInColorTable;
400
        this.useAlphaInColorTable = use;
401
        return x;
402
    }
403

    
404
    @Override
405
    public Image getImageLegend() {
406
        if( this.imageLegend==null ) {
407
            BufferedImage img = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
408
            ColorTablePainter painter = new DefaultColorTablePainter(this.getTargetColorTable(),"");
409
            Graphics2D g = img.createGraphics();
410
            g.setClip(0, 0, 80, 20);
411
            g.setBackground(Color.WHITE);
412
            g.fillRect(0, 0, 80, 20);
413
            painter.paint(g, false);
414
            this.imageLegend = img;
415
        }
416
        return this.imageLegend;
417
    }
418

    
419
    @Override
420
    public String getPathImage() {
421
        return null;
422
    }
423

    
424
    private static class HeatmapColorTable {
425

    
426
        private Color[] sourceColorTable = null;
427
        private int coldAlpha = -1;
428
        private int hotAlpha = -1;
429
        private Color coldColor = null;
430
        private Color hotColor = null;
431
        private Color[] targetColorTable = null;
432
        private int length = -1;
433

    
434
        public HeatmapColorTable(Color[] sourceColorTable){
435
            this.sourceColorTable  = sourceColorTable;
436
            this.length = sourceColorTable.length;
437
        }
438

    
439
        public HeatmapColorTable(Color[] sourceColorTable, int coldAlpha, int hotAlpha){
440
            this.sourceColorTable  = sourceColorTable;
441
            this.coldAlpha = coldAlpha;
442
            this.hotAlpha = hotAlpha;
443
            this.length = sourceColorTable.length;
444
        }
445

    
446
        public HeatmapColorTable(Color coldColor, Color hotColor, int coldAlpha, int hotAlpha, int length){
447
            this.coldColor = coldColor;
448
            this.hotColor = hotColor;
449
            this.coldAlpha = coldAlpha;
450
            this.hotAlpha = hotAlpha;
451
            this.length = length;
452
        }
453

    
454
        public Color[] getColorTable(){
455
            if(targetColorTable==null){
456
                if(sourceColorTable!=null){ //Tenemos tabla de color
457
                    double alphaDelta = getDelta(coldAlpha, hotAlpha, length);
458
                    targetColorTable = new Color[sourceColorTable.length];
459
                    for (int i = 0; i < sourceColorTable.length; i++) {
460
                        Color sourceColor = sourceColorTable[i];
461
                        if (coldAlpha >= 0 && hotAlpha >= 0) {
462
                            targetColorTable[i] =
463
                                new Color(sourceColor.getRed(),
464
                                    sourceColor.getGreen(),
465
                                    sourceColor.getBlue(),
466
                                    coldAlpha + (int) (i * alphaDelta));
467
                        } else {
468
                            targetColorTable[i] = sourceColor;
469
                        }
470
                    }
471
                } else {  //Tenemos gradiente
472
                    int coldRed = coldColor.getRed();
473
                    int coldGreen = coldColor.getGreen();
474
                    int hotGreen = hotColor.getGreen();
475

    
476
                    int hotRed = hotColor.getRed();
477
                    int coldBlue = coldColor.getBlue();
478
                    int hotBlue = hotColor.getBlue();
479

    
480
                    double redDelta = getDelta(coldRed, hotRed, length);
481
                    double greenDelta = getDelta(coldGreen, hotGreen, length);
482
                    double blueDelta = getDelta(coldBlue, hotBlue, length);
483
                    double alphaDelta = getDelta(coldAlpha, hotAlpha, length);
484
                    targetColorTable = new Color[length];
485
                    for (int i = 0; i < sourceColorTable.length; i++) {
486
                        targetColorTable[i] =
487
                            new Color(coldRed + (int) (i * redDelta),
488
                                coldGreen + (int) (i * greenDelta),
489
                                hotGreen + (int) (i * blueDelta),
490
                                coldAlpha + (int) (i * alphaDelta));
491
                    }
492
                }
493
            }
494
            return targetColorTable;
495
        }
496

    
497
        private double getDelta(int x1, int x2, int lenght){
498
            return (x2-x1)/((double)lenght-1);
499
        }
500
    }
501

    
502
}