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 @ 2267

History | View | Annotate | Download (21.3 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

    
9
import org.apache.commons.lang3.StringUtils;
10
import org.cresques.cts.ICoordTrans;
11
import org.gvsig.compat.print.PrintAttributes;
12

    
13
import org.gvsig.fmap.dal.exception.DataException;
14
import org.gvsig.fmap.dal.feature.Feature;
15
import org.gvsig.fmap.dal.feature.FeatureQuery;
16
import org.gvsig.fmap.dal.feature.FeatureSelection;
17
import org.gvsig.fmap.dal.feature.FeatureSet;
18
import org.gvsig.fmap.dal.feature.FeatureStore;
19
import org.gvsig.fmap.dal.feature.FeatureType;
20
import org.gvsig.fmap.geom.Geometry;
21
import org.gvsig.fmap.geom.primitive.Point;
22
import org.gvsig.fmap.mapcontext.MapContextException;
23
import org.gvsig.fmap.mapcontext.ViewPort;
24
import org.gvsig.fmap.mapcontext.layers.operations.IHasImageLegend;
25
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
26
import org.gvsig.fmap.mapcontext.rendering.legend.events.SymbolLegendEvent;
27
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
28
import org.gvsig.gui.ColorTablePainter;
29
import org.gvsig.gui.DefaultColorTablePainter;
30
import org.gvsig.legend.heatmap.lib.api.HeatmapLegend;
31
import org.gvsig.symbology.fmap.mapcontext.rendering.legend.impl.AbstractVectorialLegend;
32
import org.gvsig.symbology.fmap.mapcontext.rendering.legend.impl.DefaultFeatureDrawnNotification;
33
import org.gvsig.symbology.fmap.mapcontext.rendering.symbol.text.impl.SimpleTextSymbol;
34
import org.gvsig.tools.exception.BaseException;
35
import org.gvsig.tools.persistence.PersistentState;
36
import org.gvsig.tools.persistence.exception.PersistenceException;
37
import org.gvsig.tools.swing.api.ToolsSwingLocator;
38
import org.gvsig.tools.swing.api.ToolsSwingManager;
39
import org.gvsig.tools.task.Cancellable;
40
import org.gvsig.tools.visitor.VisitCanceledException;
41
import org.gvsig.tools.visitor.Visitor;
42

    
43
public class DefaultHeatmapLegend extends AbstractVectorialLegend implements HeatmapLegend, IHasImageLegend {
44

    
45
    private class DensityAlgorithm {
46

    
47
        private double[][] grid;
48
        private double[][] kernel;
49
        private int distance;
50
        private int height;
51
        private int with;
52
        private double maxValue;
53
        private double minValue;
54

    
55
        public DensityAlgorithm(int distance) {
56
            this.setDistance(distance);
57
        }
58

    
59
        public void setDistance(int distance) {
60
            if( this.distance == distance ) {
61
                return;
62
            }
63
            this.distance = distance;
64
            this.kernel = new double[2 * this.distance + 1][2 * this.distance + 1];
65
            for( int y = -this.distance; y < this.distance + 1; y++ ) {
66
                for( int x = -this.distance; x < this.distance + 1; x++ ) {
67
                    final double dDist = Math.sqrt(x * x + y * y);
68
                    if( dDist < this.distance ) {
69
                        this.kernel[x + this.distance][y + this.distance] = Math.pow(1 - (dDist * dDist) / (this.distance * this.distance), 2);
70
                    } else {
71
                        this.kernel[x + this.distance][y + this.distance] = 0;
72
                    }
73
                }
74
            }
75
        }
76

    
77
        public int getDistance() {
78
            return this.distance;
79
        }
80

    
81
        public void init(int with, int height) {
82
            this.with = with;
83
            this.height = height;
84
            this.grid = new double[with][height];
85
            this.maxValue = 0;
86
            this.minValue = 0;
87
        }
88

    
89
        public void add(int px, int py) {
90
            add(px, py, 1);
91
        }
92

    
93
        public void add(int px, int py, double value) {
94
            for( int y = -this.distance; y < this.distance + 1; y++ ) {
95
                for( int x = -this.distance; x < this.distance + 1; x++ ) {
96
                    if( this.kernel[x + this.distance][y + this.distance] != 0 ) {
97
                        addValue(px + x, py + y, value * this.kernel[x + this.distance][y + this.distance]);
98
                    }
99
                }
100
            }
101
        }
102

    
103
        private void addValue(int px, int py, double value) {
104
            if( px < 0 || py < 0 || px >= with || py >= height ) {
105
                return;
106
            }
107
            value += this.grid[px][py];
108
            this.grid[px][py] = value;
109
            if( value > this.maxValue ) {
110
                this.maxValue = value;
111
            }
112
            if( value < this.minValue ) {
113
                this.minValue = value;
114
            }
115
        }
116

    
117
        public void drawWithOpaqueColors(BufferedImage img, Graphics2D g, Color[] colorTable, Cancellable cancel) {
118
            try {
119
                ToolsSwingManager toolsSwingManager = ToolsSwingLocator.getToolsSwingManager();
120
                Color c;
121
                int maxIndexColor = colorTable.length-1;
122
                for( int x = 0; x < with; x++ ) {
123
                    for( int y = 0; y < height; y++ ) {
124
                        if( cancel.isCanceled() ) {
125
                            return;
126
                        }
127
                        double value = this.grid[x][y];
128
                        if( value > 0 ) {
129
                            int icolor = (int) (value * maxIndexColor / maxValue);
130
                            c = toolsSwingManager.alphaBlendingWithOpaqueBackground(
131
                                    new Color(img.getRGB(x, y)),
132
                                    colorTable[icolor]
133
                            );
134
                            img.setRGB(x, y, c.getRGB());
135
                        }
136
                    }
137
                }
138
            } catch (Exception ex) {
139
                LOG.warn("Problems drawing heatmap", ex);
140
            }
141
        }
142

    
143
        public void drawWithAlphaColors(BufferedImage img, Graphics2D g, Color[] colorTable, Cancellable cancel) {
144
            try {
145
                Color c;
146
                int maxIndexColor = colorTable.length-1;
147
                for( int x = 0; x < with; x++ ) {
148
                    for( int y = 0; y < height; y++ ) {
149
                        if( cancel.isCanceled() ) {
150
                            return;
151
                        }
152
                        double value = this.grid[x][y];
153
                        if( value > 0 ) {
154
                            int icolor = (int) (value * maxIndexColor / maxValue);
155
                            c = colorTable[icolor];
156
                            img.setRGB(x, y, c.getRGB());
157
                        }
158
                    }
159
                }
160
            } catch (Exception ex) {
161
                LOG.warn("Problems drawing heatmap", ex);
162
            }
163
        }
164
    }
165

    
166
    private ISymbol defaultSymbol;
167
    private DensityAlgorithm algorithm;
168
    private boolean isRamp;
169
    private Color[] sourceColorTable;
170
    private int colorTableHotColorAlpha;
171
    private int colorTableColdColorAlpha;
172
    private boolean useAlphaInColorTable;
173
    private String fieldName;
174
    private Image imageLegend;
175
    private HeatmapColorTable hmColorTable;
176
    private Color rampColdColor;
177
    private Color rampHotColor;
178
    private int rampNumColors;
179

    
180
    public DefaultHeatmapLegend() {
181
        this.defaultSymbol = new SimpleTextSymbol();
182
        this.algorithm = new DensityAlgorithm(30);
183
        this.colorTableHotColorAlpha = 255;
184
        this.colorTableColdColorAlpha = 0;
185
        this.useAlphaInColorTable = false;
186
        this.setColorTable(100, new Color(0, 0, 255, 0), new Color(255, 0, 0, 255));
187
        this.imageLegend = null;
188
        this.hmColorTable = null;
189
        this.fieldName = null;
190
    }
191

    
192
    @Override
193
    protected String[] getRequiredFeatureAttributeNames(FeatureStore featureStore) throws DataException {
194
        FeatureType ftype = featureStore.getDefaultFeatureType();
195
        if( StringUtils.isEmpty(this.fieldName) ) {
196
            return new String[]{
197
                ftype.getDefaultGeometryAttributeName()
198
            };
199
        }
200
        return new String[]{
201
            ftype.getDefaultGeometryAttributeName(),
202
            this.fieldName
203
        };
204
    }
205

    
206
    @Override
207
    public ISymbol getDefaultSymbol() {
208
        return this.defaultSymbol;
209
    }
210

    
211
    @Override
212
    public void setDefaultSymbol(ISymbol is) {
213
    }
214

    
215
    @Override
216
    public ISymbol getSymbolByFeature(Feature ftr) throws MapContextException {
217
        return this.defaultSymbol;
218
    }
219

    
220
    @Override
221
    public int getShapeType() {
222
        return Geometry.TYPES.GEOMETRY;
223
    }
224

    
225
    @Override
226
    public void setShapeType(int i) {
227
    }
228

    
229
    @Override
230
    public boolean isUseDefaultSymbol() {
231
        return true;
232
    }
233

    
234
    @Override
235
    public void useDefaultSymbol(boolean bln) {
236
    }
237

    
238
    @Override
239
    public boolean isSuitableForShapeType(int shapeType) {
240
        return true;
241
    }
242

    
243
    @Override
244
    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 {
245
        int saved_distance = this.algorithm.getDistance();
246
        try {
247
            int distance = (int) (this.algorithm.getDistance() * (dpi / 72));
248
            this.algorithm.setDistance(distance);
249
            this.algorithm.init(image.getWidth(), image.getHeight());
250
            super.draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans, featureStore, featureQuery, dpi);
251
            if( !cancel.isCanceled() ) {
252
                this.algorithm.drawWithOpaqueColors(image, g, this.getHeatMapColorTable().getColorTable(), cancel);
253
            }
254
        } finally {
255
            this.algorithm.setDistance(saved_distance);
256

    
257
        }
258
    }
259
    
260
    @Override
261
    public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel,
262
            double scale, Map queryParameters, ICoordTrans coordTrans,
263
            FeatureStore featureStore, FeatureQuery featureQuery, PrintAttributes properties)
264
            throws LegendException {
265
        int saved_distance = this.algorithm.getDistance();
266
        try {
267
            double dpi = viewPort.getDPI();
268
            // Ver CartographicSupportToolkit.getCartographicLength
269
            int distance = (int) (this.algorithm.getDistance() * (dpi / 72));
270
            
271
            this.algorithm.setDistance(distance);
272
            this.algorithm.init(viewPort.getImageWidth(), viewPort.getImageHeight());
273
            BufferedImage image = new BufferedImage(viewPort.getImageWidth(), viewPort.getImageHeight(), BufferedImage.TYPE_INT_ARGB);
274
            super.draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans, featureStore, featureQuery, dpi);
275
            if (!cancel.isCanceled()) {
276
                this.algorithm.drawWithAlphaColors(image, g, this.getHeatMapColorTable().getColorTable(), cancel);
277
                g.drawImage(image, 0, 0, null);
278
            }
279
        } finally {
280
            this.algorithm.setDistance(saved_distance);
281

    
282
        }
283
    }
284

    
285
    @Override
286
    protected void drawFeatures(
287
        BufferedImage image,
288
        Graphics2D g,
289
        final ViewPort viewPort,
290
        final Cancellable cancel,
291
        final ICoordTrans coordTrans,
292
        double dpi,
293
        DefaultFeatureDrawnNotification drawnNotification,
294
        FeatureSet featureSet,
295
        FeatureSelection selection
296
    ) throws BaseException {
297
        int x = -1;
298
        if( !StringUtils.isEmpty(this.fieldName) ) {
299
            x = featureSet.getDefaultFeatureType().getIndex(this.fieldName);
300
        }
301
        final int n = x;
302
        featureSet.accept(new Visitor() {
303
            @Override
304
            public void visit(Object o) throws VisitCanceledException, BaseException {
305
                if( cancel.isCanceled() ) {
306
                    throw new VisitCanceledException();
307
                }
308
                Feature feature = (Feature) o;
309
                Geometry geom = feature.getDefaultGeometry();
310
                if( geom != null ) {
311
                    Point pointGeo = geom.centroid();
312
                    if( coordTrans != null ) {
313
                        pointGeo.reProject(coordTrans);
314
                    }
315
                    Point pointPixels = (Point) pointGeo.cloneGeometry();
316
                    pointPixels.transform(viewPort.getAffineTransform());
317
                    if( n >= 0 ) {
318
                        double value = 0;
319
                        try {
320
                            value = feature.getDouble(n);
321
                        } catch(Exception ex) {
322
                        }
323
                        if( value >0 ) {
324
                            algorithm.add((int) pointPixels.getX(), (int) pointPixels.getY(), value);
325
                        }
326
                    } else {
327
                        algorithm.add((int) pointPixels.getX(), (int) pointPixels.getY());
328
                    }
329
                }
330
            }
331
        });
332
    }
333

    
334
    /**
335
     * @return the distance
336
     */
337
    @Override
338
    public int getDistance() {
339
        return this.algorithm.getDistance();
340
    }
341

    
342
    /**
343
     * @param distance the distance to set
344
     */
345
    @Override
346
    public void setDistance(int distance) {
347
        this.algorithm.setDistance(distance);
348
    }
349

    
350
    @Override
351
    public void setColorTable(Color[] colorTable) {
352
        this.isRamp = false;
353
        this.sourceColorTable = colorTable;
354
        this.imageLegend = null;
355
        this.hmColorTable = null;
356
        this.fireDefaultSymbolChangedEvent(new SymbolLegendEvent(null,null));
357
    }
358

    
359
    @Override
360
    public void setColorTable(int numColors, Color coldColor, Color hotColor) {
361
        this.isRamp = true;
362
        this.rampColdColor = coldColor;
363
        this.rampHotColor = hotColor;
364
        this.rampNumColors = numColors;
365
        this.imageLegend = null;
366
        this.hmColorTable = null;
367
        this.fireDefaultSymbolChangedEvent(new SymbolLegendEvent(null,null));
368
    }
369

    
370
    @Override
371
    public Color[] getSourceColorTable() {
372
        return this.sourceColorTable;
373
    }
374

    
375
    @Override
376
    public Color[] getTargetColorTable() {
377
        return this.getHeatMapColorTable().getColorTable();
378
    }
379

    
380
    private HeatmapColorTable getHeatMapColorTable() {
381
        if (this.hmColorTable == null) {
382
            if (this.useRamp()) {
383
                this.hmColorTable = new HeatmapColorTable(this.rampColdColor, this.rampHotColor, this.rampNumColors);
384
            } else {
385
                if(useAlphaInColorTable) {
386
                    this.hmColorTable = new HeatmapColorTable(this.getSourceColorTable(), colorTableColdColorAlpha, colorTableHotColorAlpha);
387
                } else {
388
                this.hmColorTable =
389
                    this.hmColorTable = new HeatmapColorTable(this.getSourceColorTable());
390
                }
391
            }
392
        }
393
        return this.hmColorTable;
394
    }
395

    
396

    
397
    @Override
398
    public boolean useRamp() {
399
        return this.isRamp;
400
    }
401

    
402
    @Override
403
    public String getFieldName() {
404
        return this.fieldName;
405
    }
406

    
407
    @Override
408
    public void setFieldName(String fieldName) {
409
        this.fieldName = fieldName;
410
    }
411

    
412
    @Override
413
    public int getColorTableHotColorAlpha() {
414
        return colorTableHotColorAlpha;
415
    }
416

    
417
    @Override
418
    public void setColorTableHotColorAlpha(int colorTableHotColorAlpha) {
419
        this.colorTableHotColorAlpha = colorTableHotColorAlpha;
420
        this.imageLegend = null;
421
        this.hmColorTable = null;
422
    }
423

    
424
    @Override
425
    public int getColorTableColdColorAlpha() {
426
        return colorTableColdColorAlpha;
427
    }
428

    
429
    @Override
430
    public void setColorTableColdColorAlpha(int colorTableColdColorAlpha) {
431
        this.colorTableColdColorAlpha = colorTableColdColorAlpha;
432
        this.imageLegend = null;
433
        this.hmColorTable = null;
434
    }
435

    
436
    @Override
437
    public boolean useAlphaInColorTable() {
438
        return this.useAlphaInColorTable;
439
    }
440

    
441
    @Override
442
    public boolean setUseAlphaInColorTable(boolean use) {
443
        boolean x = this.useAlphaInColorTable;
444
        this.useAlphaInColorTable = use;
445
        this.hmColorTable = null;
446

    
447
        return x;
448
    }
449

    
450
    @Override
451
    public Image getImageLegend() {
452
        if( this.imageLegend==null ) {
453
            BufferedImage img = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
454
            ColorTablePainter painter = new DefaultColorTablePainter(this.getTargetColorTable(),"");
455
            Graphics2D g = img.createGraphics();
456
            g.setClip(0, 0, 80, 20);
457
            g.setBackground(Color.WHITE);
458
            g.fillRect(0, 0, 80, 20);
459
            painter.paint(g, false);
460
            this.imageLegend = img;
461
        }
462
        return this.imageLegend;
463
    }
464

    
465
    @Override
466
    public String getPathImage() {
467
        return null;
468
    }
469

    
470
    @Override
471
    public void loadFromState(PersistentState state) throws PersistenceException {
472
        this.defaultSymbol = new SimpleTextSymbol();
473
        this.imageLegend = null;
474
        this.hmColorTable = null;
475

    
476
        super.loadFromState(state);
477
        this.isRamp = state.getBoolean("isRamp");
478
        this.sourceColorTable = (Color[]) state.getArray("sourceColorTable",Color.class);
479
        this.colorTableHotColorAlpha = state.getInt("colorTableHotColorAlpha");
480
        this.colorTableColdColorAlpha = state.getInt("colorTableColdColorAlpha");
481
        this.useAlphaInColorTable = state.getBoolean("useAlphaInColorTable");
482
        this.fieldName = state.getString("fieldName");
483
        this.rampNumColors = state.getInt("rampNumColors");
484
        this.rampColdColor = (Color)state.get("rampColdColor");
485
        this.rampHotColor = (Color)state.get("rampHotColor");
486

    
487
        this.algorithm = new DensityAlgorithm(state.getInt("distance"));
488
    }
489

    
490
    @Override
491
    public int getRampNumColors() {
492
        return this.rampNumColors;
493
    }
494

    
495
    @Override
496
    public Color getRampColdColor() {
497
        return this.rampColdColor;
498
    }
499

    
500
    @Override
501
    public Color getRampHotColor() {
502
        return this.rampHotColor;
503
    }
504

    
505
    @Override
506
    public void saveToState(PersistentState state) throws PersistenceException {
507
        super.saveToState(state);
508
        state.set("isRamp", isRamp);
509
        state.set("sourceColorTable", sourceColorTable);
510
        state.set("colorTableHotColorAlpha", colorTableHotColorAlpha);
511
        state.set("colorTableColdColorAlpha", colorTableColdColorAlpha);
512
        state.set("useAlphaInColorTable", useAlphaInColorTable);
513
        state.set("fieldName", fieldName);
514
        state.set("distance", algorithm.distance);
515

    
516
        state.set("rampNumColors", rampNumColors);
517
        state.set("rampColdColor", rampColdColor);
518
        state.set("rampHotColor", rampHotColor);
519
    }
520

    
521

    
522
    private static class HeatmapColorTable {
523

    
524
        private Color[] sourceColorTable = null;
525
        private int coldAlpha = -1;
526
        private int hotAlpha = -1;
527
        private Color coldColor = null;
528
        private Color hotColor = null;
529
        private Color[] targetColorTable = null;
530
        private int length = -1;
531

    
532
        public HeatmapColorTable(Color[] sourceColorTable){
533
            this.sourceColorTable  = sourceColorTable;
534
        }
535

    
536
        public HeatmapColorTable(Color[] sourceColorTable, int coldAlpha, int hotAlpha){
537
            this.sourceColorTable  = sourceColorTable;
538
            this.coldAlpha = coldAlpha;
539
            this.hotAlpha = hotAlpha;
540
        }
541

    
542
        public HeatmapColorTable(Color coldColor, Color hotColor, int length){
543
            this.coldColor = coldColor;
544
            this.hotColor = hotColor;
545
            this.length = length;
546
        }
547

    
548
        public Color[] getColorTable(){
549
            if(targetColorTable==null){
550
                if(sourceColorTable!=null){ //Tenemos tabla de color
551
                    if (coldAlpha >= 0 || hotAlpha >= 0) { //Se usa alpha para la tabla de color
552
                        double alphaDelta = getDelta(coldAlpha, hotAlpha, sourceColorTable.length);
553
                        targetColorTable = new Color[sourceColorTable.length];
554
                        for (int i = 0; i < sourceColorTable.length; i++) {
555
                            Color sourceColor = sourceColorTable[i];
556
                            if (coldAlpha >= 0 && hotAlpha >= 0) {
557
                                targetColorTable[i] =
558
                                    new Color(sourceColor.getRed(), sourceColor.getGreen(), sourceColor.getBlue(),
559
                                        coldAlpha + (int) (i * alphaDelta));
560
                            } else {
561
                                targetColorTable[i] = sourceColor;
562
                            }
563
                        }
564
                    } else { //No se usa alpha para la tabla de color
565
                        targetColorTable = sourceColorTable;
566
                    }
567
                } else { // Tenemos gradiente
568
                    targetColorTable = new Color[length];
569

    
570
                    double deltaRed = (hotColor.getRed() - coldColor.getRed()) / length;
571
                    double deltaGreen = (hotColor.getGreen() - coldColor.getGreen()) / length;
572
                    double deltaBlue = (hotColor.getBlue() - coldColor.getBlue()) / length;
573
                    double deltaAlpha = (hotColor.getAlpha() - coldColor.getAlpha()) / length;
574
                    for (int i = 0; i < length; i++) {
575
                        targetColorTable[i] =
576
                            new Color(
577
                                (int) (coldColor.getRed() + i * deltaRed),
578
                                (int) (coldColor.getGreen() + i * deltaGreen),
579
                                (int) (coldColor.getBlue() + i * deltaBlue),
580
                                (int) (coldColor.getAlpha() + i * deltaAlpha));
581
                    }
582
                }
583
            }
584
            return targetColorTable;
585
        }
586

    
587
        private double getDelta(int x1, int x2, int lenght){
588
            return (x2-x1)/((double)lenght-1);
589
        }
590
    }
591
}