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

History | View | Annotate | Download (22.5 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.GeometryUtils;
22
import org.gvsig.fmap.geom.primitive.Point;
23
import org.gvsig.fmap.mapcontext.MapContextException;
24
import org.gvsig.fmap.mapcontext.ViewPort;
25
import org.gvsig.fmap.mapcontext.layers.operations.IHasImageLegend;
26
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
27
import org.gvsig.fmap.mapcontext.rendering.legend.events.SymbolLegendEvent;
28
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
29
import org.gvsig.gui.ColorTablePainter;
30
import org.gvsig.gui.DefaultColorTablePainter;
31
import org.gvsig.legend.heatmap.lib.api.HeatmapLegend;
32
import org.gvsig.symbology.fmap.mapcontext.rendering.legend.impl.AbstractVectorialLegend;
33
import org.gvsig.symbology.fmap.mapcontext.rendering.legend.impl.DefaultFeatureDrawnNotification;
34
import org.gvsig.symbology.fmap.mapcontext.rendering.symbol.text.impl.SimpleTextSymbol;
35
import org.gvsig.tools.exception.BaseException;
36
import org.gvsig.tools.persistence.PersistentState;
37
import org.gvsig.tools.persistence.exception.PersistenceException;
38
import org.gvsig.tools.swing.api.ToolsSwingLocator;
39
import org.gvsig.tools.swing.api.ToolsSwingManager;
40
import org.gvsig.tools.task.Cancellable;
41
import org.gvsig.tools.visitor.VisitCanceledException;
42
import org.gvsig.tools.visitor.Visitor;
43

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

    
46
    private class DensityAlgorithm {
47

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

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

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

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

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

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

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

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

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

    
150
        public void drawWithAlphaColors(BufferedImage img, Graphics2D g, Color[] colorTable, Cancellable cancel, Geometry roi) {
151
            try {
152
                Color c;
153
                int maxIndexColor = colorTable.length-1;
154
                for( int x = 0; x < with; x++ ) {
155
                    for( int y = 0; y < height; y++ ) {
156
                        if( cancel.isCanceled() ) {
157
                            return;
158
                        }
159
                        if (roi!=null) {
160
                            Point point = GeometryUtils.createPoint(x, y);
161
                            if (!roi.intersects(point)) {
162
                                continue;
163
                            }
164
                        }
165
                        double value = this.grid[x][y];
166
                        if( value > 0 ) {
167
                            int icolor = (int) (value * maxIndexColor / maxValue);
168
                            c = colorTable[icolor];
169
                            img.setRGB(x, y, c.getRGB());
170
                        }
171
                    }
172
                }
173
            } catch (Exception ex) {
174
                LOG.warn("Problems drawing heatmap", ex);
175
            }
176
        }
177
    }
178

    
179
    private ISymbol defaultSymbol;
180
    private DensityAlgorithm algorithm;
181
    private boolean isRamp;
182
    private Color[] sourceColorTable;
183
    private int colorTableHotColorAlpha;
184
    private int colorTableColdColorAlpha;
185
    private boolean useAlphaInColorTable;
186
    private String fieldName;
187
    private Image imageLegend;
188
    private HeatmapColorTable hmColorTable;
189
    private Color rampColdColor;
190
    private Color rampHotColor;
191
    private int rampNumColors;
192
    private Geometry roi;
193
    
194
    public DefaultHeatmapLegend() {
195
        
196
        this.defaultSymbol = new SimpleTextSymbol();
197
        this.algorithm = new DensityAlgorithm(30);
198
        this.colorTableHotColorAlpha = 255;
199
        this.colorTableColdColorAlpha = 0;
200
        this.useAlphaInColorTable = false;
201
        this.setColorTable(100, new Color(0, 0, 255, 0), new Color(255, 0, 0, 255));
202
        this.imageLegend = null;
203
        this.hmColorTable = null;
204
        this.fieldName = null;
205
    }
206

    
207
    @Override
208
    protected String[] getRequiredFeatureAttributeNames(FeatureStore featureStore) throws DataException {
209
        FeatureType ftype = featureStore.getDefaultFeatureType();
210
        if( StringUtils.isEmpty(this.fieldName) ) {
211
            return new String[]{
212
                ftype.getDefaultGeometryAttributeName()
213
            };
214
        }
215
        return new String[]{
216
            ftype.getDefaultGeometryAttributeName(),
217
            this.fieldName
218
        };
219
    }
220

    
221
    @Override
222
    public ISymbol getDefaultSymbol() {
223
        return this.defaultSymbol;
224
    }
225

    
226
    @Override
227
    public void setDefaultSymbol(ISymbol is) {
228
    }
229

    
230
    @Override
231
    public ISymbol getSymbolByFeature(Feature ftr) throws MapContextException {
232
        return this.defaultSymbol;
233
    }
234

    
235
    @Override
236
    public int getShapeType() {
237
        return Geometry.TYPES.GEOMETRY;
238
    }
239

    
240
    @Override
241
    public void setShapeType(int i) {
242
    }
243

    
244
    @Override
245
    public boolean isUseDefaultSymbol() {
246
        return true;
247
    }
248

    
249
    @Override
250
    public void useDefaultSymbol(boolean bln) {
251
    }
252

    
253
    @Override
254
    public boolean isSuitableForShapeType(int shapeType) {
255
        return true;
256
    }
257

    
258
    @Override
259
    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 {
260
        int saved_distance = this.algorithm.getDistance();
261
        try {
262
            int distance = (int) (this.algorithm.getDistance() * (dpi / 72));
263
            Geometry theROI = null;
264
            if (this.roi!=null) {
265
                theROI = this.roi.cloneGeometry();
266
                theROI.transform(viewPort.getAffineTransform());
267
            }
268
            this.algorithm.setDistance(distance);
269
            this.algorithm.init(image.getWidth(), image.getHeight());
270
            super.draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans, featureStore, featureQuery, dpi);
271
            if( !cancel.isCanceled() ) {
272
                this.algorithm.drawWithOpaqueColors(image, g, this.getHeatMapColorTable().getColorTable(), cancel, theROI);
273
            }
274
        } finally {
275
            this.algorithm.setDistance(saved_distance);
276

    
277
        }
278
    }
279
    
280
    @Override
281
    public void print(Graphics2D g, ViewPort viewPort, Cancellable cancel,
282
            double scale, Map queryParameters, ICoordTrans coordTrans,
283
            FeatureStore featureStore, FeatureQuery featureQuery, PrintAttributes properties)
284
            throws LegendException {
285
        int saved_distance = this.algorithm.getDistance();
286
        try {
287
            double dpi = viewPort.getDPI();
288
            // Ver CartographicSupportToolkit.getCartographicLength
289
            int distance = (int) (this.algorithm.getDistance() * (dpi / 72));
290
            Geometry theROI = null;
291
            if (this.roi!=null) {
292
                theROI = this.roi.cloneGeometry();
293
                theROI.transform(viewPort.getAffineTransform());
294
            }
295
            this.algorithm.setDistance(distance);
296
            this.algorithm.init(viewPort.getImageWidth(), viewPort.getImageHeight());
297
            BufferedImage image = new BufferedImage(viewPort.getImageWidth(), viewPort.getImageHeight(), BufferedImage.TYPE_INT_ARGB);
298
            super.draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans, featureStore, featureQuery, dpi);
299
            if (!cancel.isCanceled()) {
300
                this.algorithm.drawWithAlphaColors(image, g, this.getHeatMapColorTable().getColorTable(), cancel, theROI);
301
                g.drawImage(image, 0, 0, null);
302
            }
303
        } finally {
304
            this.algorithm.setDistance(saved_distance);
305

    
306
        }
307
    }
308

    
309
    @Override
310
    protected void drawFeatures(
311
        BufferedImage image,
312
        Graphics2D g,
313
        final ViewPort viewPort,
314
        final Cancellable cancel,
315
        final ICoordTrans coordTrans,
316
        double dpi,
317
        DefaultFeatureDrawnNotification drawnNotification,
318
        FeatureSet featureSet,
319
        FeatureSelection selection
320
    ) throws BaseException {
321
        int x = -1;
322
        if( !StringUtils.isEmpty(this.fieldName) ) {
323
            x = featureSet.getDefaultFeatureType().getIndex(this.fieldName);
324
        }
325
        final int n = x;
326
        featureSet.accept(new Visitor() {
327
            @Override
328
            public void visit(Object o) throws VisitCanceledException, BaseException {
329
                if( cancel.isCanceled() ) {
330
                    throw new VisitCanceledException();
331
                }
332
                Feature feature = (Feature) o;
333
                Geometry geom = feature.getDefaultGeometry();
334
                if( geom != null ) {
335
                    Point pointGeo = geom.centroid();
336
                    if( coordTrans != null ) {
337
                        pointGeo.reProject(coordTrans);
338
                    }
339
                    Point pointPixels = (Point) pointGeo.cloneGeometry();
340
                    pointPixels.transform(viewPort.getAffineTransform());
341
                    if( n >= 0 ) {
342
                        double value = 0;
343
                        try {
344
                            value = feature.getDouble(n);
345
                        } catch(Exception ex) {
346
                        }
347
                        if( value >0 ) {
348
                            algorithm.add((int) pointPixels.getX(), (int) pointPixels.getY(), value);
349
                        }
350
                    } else {
351
                        algorithm.add((int) pointPixels.getX(), (int) pointPixels.getY());
352
                    }
353
                }
354
            }
355
        });
356
    }
357

    
358
    /**
359
     * @return the distance
360
     */
361
    @Override
362
    public int getDistance() {
363
        return this.algorithm.getDistance();
364
    }
365

    
366
    /**
367
     * @param distance the distance to set
368
     */
369
    @Override
370
    public void setDistance(int distance) {
371
        this.algorithm.setDistance(distance);
372
    }
373

    
374
    @Override
375
    public void setColorTable(Color[] colorTable) {
376
        this.isRamp = false;
377
        this.sourceColorTable = colorTable;
378
        this.imageLegend = null;
379
        this.hmColorTable = null;
380
        this.fireDefaultSymbolChangedEvent(new SymbolLegendEvent(null,null));
381
    }
382

    
383
    @Override
384
    public void setColorTable(int numColors, Color coldColor, Color hotColor) {
385
        this.isRamp = true;
386
        this.rampColdColor = coldColor;
387
        this.rampHotColor = hotColor;
388
        this.rampNumColors = numColors;
389
        this.imageLegend = null;
390
        this.hmColorTable = null;
391
        this.fireDefaultSymbolChangedEvent(new SymbolLegendEvent(null,null));
392
    }
393

    
394
    @Override
395
    public Color[] getSourceColorTable() {
396
        return this.sourceColorTable;
397
    }
398

    
399
    @Override
400
    public Color[] getTargetColorTable() {
401
        return this.getHeatMapColorTable().getColorTable();
402
    }
403

    
404
    private HeatmapColorTable getHeatMapColorTable() {
405
        if (this.hmColorTable == null) {
406
            if (this.useRamp()) {
407
                this.hmColorTable = new HeatmapColorTable(this.rampColdColor, this.rampHotColor, this.rampNumColors);
408
            } else {
409
                if(useAlphaInColorTable) {
410
                    this.hmColorTable = new HeatmapColorTable(this.getSourceColorTable(), colorTableColdColorAlpha, colorTableHotColorAlpha);
411
                } else {
412
                this.hmColorTable =
413
                    this.hmColorTable = new HeatmapColorTable(this.getSourceColorTable());
414
                }
415
            }
416
        }
417
        return this.hmColorTable;
418
    }
419

    
420

    
421
    @Override
422
    public boolean useRamp() {
423
        return this.isRamp;
424
    }
425

    
426
    @Override
427
    public String getFieldName() {
428
        return this.fieldName;
429
    }
430

    
431
    @Override
432
    public void setFieldName(String fieldName) {
433
        this.fieldName = fieldName;
434
    }
435

    
436
    @Override
437
    public int getColorTableHotColorAlpha() {
438
        return colorTableHotColorAlpha;
439
    }
440

    
441
    @Override
442
    public void setColorTableHotColorAlpha(int colorTableHotColorAlpha) {
443
        this.colorTableHotColorAlpha = colorTableHotColorAlpha;
444
        this.imageLegend = null;
445
        this.hmColorTable = null;
446
    }
447

    
448
    @Override
449
    public int getColorTableColdColorAlpha() {
450
        return colorTableColdColorAlpha;
451
    }
452

    
453
    @Override
454
    public void setColorTableColdColorAlpha(int colorTableColdColorAlpha) {
455
        this.colorTableColdColorAlpha = colorTableColdColorAlpha;
456
        this.imageLegend = null;
457
        this.hmColorTable = null;
458
    }
459

    
460
    @Override
461
    public boolean useAlphaInColorTable() {
462
        return this.useAlphaInColorTable;
463
    }
464

    
465
    @Override
466
    public boolean setUseAlphaInColorTable(boolean use) {
467
        boolean x = this.useAlphaInColorTable;
468
        this.useAlphaInColorTable = use;
469
        this.hmColorTable = null;
470

    
471
        return x;
472
    }
473
    @Override
474
    public void setROI(Geometry roi) {
475
        this.roi = roi;
476
    }
477

    
478
    @Override
479
    public Geometry getROI() {
480
        return this.roi;
481
    }
482
    
483
    @Override
484
    public Image getImageLegend() {
485
        if( this.imageLegend==null ) {
486
            BufferedImage img = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
487
            ColorTablePainter painter = new DefaultColorTablePainter(this.getTargetColorTable(),"");
488
            Graphics2D g = img.createGraphics();
489
            g.setClip(0, 0, 80, 20);
490
            g.setBackground(Color.WHITE);
491
            g.fillRect(0, 0, 80, 20);
492
            painter.paint(g, false);
493
            this.imageLegend = img;
494
        }
495
        return this.imageLegend;
496
    }
497

    
498
    @Override
499
    public String getPathImage() {
500
        return null;
501
    }
502

    
503
    @Override
504
    public void loadFromState(PersistentState state) throws PersistenceException {
505
        this.defaultSymbol = new SimpleTextSymbol();
506
        this.imageLegend = null;
507
        this.hmColorTable = null;
508

    
509
        super.loadFromState(state);
510
        this.isRamp = state.getBoolean("isRamp");
511
        this.sourceColorTable = (Color[]) state.getArray("sourceColorTable",Color.class);
512
        this.colorTableHotColorAlpha = state.getInt("colorTableHotColorAlpha");
513
        this.colorTableColdColorAlpha = state.getInt("colorTableColdColorAlpha");
514
        this.useAlphaInColorTable = state.getBoolean("useAlphaInColorTable");
515
        this.fieldName = state.getString("fieldName");
516
        this.rampNumColors = state.getInt("rampNumColors");
517
        this.rampColdColor = (Color)state.get("rampColdColor");
518
        this.rampHotColor = (Color)state.get("rampHotColor");
519

    
520
        this.algorithm = new DensityAlgorithm(state.getInt("distance"));
521
    }
522

    
523
    @Override
524
    public int getRampNumColors() {
525
        return this.rampNumColors;
526
    }
527

    
528
    @Override
529
    public Color getRampColdColor() {
530
        return this.rampColdColor;
531
    }
532

    
533
    @Override
534
    public Color getRampHotColor() {
535
        return this.rampHotColor;
536
    }
537

    
538
    @Override
539
    public void saveToState(PersistentState state) throws PersistenceException {
540
        super.saveToState(state);
541
        state.set("isRamp", isRamp);
542
        state.set("sourceColorTable", sourceColorTable);
543
        state.set("colorTableHotColorAlpha", colorTableHotColorAlpha);
544
        state.set("colorTableColdColorAlpha", colorTableColdColorAlpha);
545
        state.set("useAlphaInColorTable", useAlphaInColorTable);
546
        state.set("fieldName", fieldName);
547
        state.set("distance", algorithm.distance);
548

    
549
        state.set("rampNumColors", rampNumColors);
550
        state.set("rampColdColor", rampColdColor);
551
        state.set("rampHotColor", rampHotColor);
552
    }
553

    
554

    
555
    private static class HeatmapColorTable {
556

    
557
        private Color[] sourceColorTable = null;
558
        private int coldAlpha = -1;
559
        private int hotAlpha = -1;
560
        private Color coldColor = null;
561
        private Color hotColor = null;
562
        private Color[] targetColorTable = null;
563
        private int length = -1;
564

    
565
        public HeatmapColorTable(Color[] sourceColorTable){
566
            this.sourceColorTable  = sourceColorTable;
567
        }
568

    
569
        public HeatmapColorTable(Color[] sourceColorTable, int coldAlpha, int hotAlpha){
570
            this.sourceColorTable  = sourceColorTable;
571
            this.coldAlpha = coldAlpha;
572
            this.hotAlpha = hotAlpha;
573
        }
574

    
575
        public HeatmapColorTable(Color coldColor, Color hotColor, int length){
576
            this.coldColor = coldColor;
577
            this.hotColor = hotColor;
578
            this.length = length;
579
        }
580

    
581
        public Color[] getColorTable(){
582
            if(targetColorTable==null){
583
                if(sourceColorTable!=null){ //Tenemos tabla de color
584
                    if (coldAlpha >= 0 || hotAlpha >= 0) { //Se usa alpha para la tabla de color
585
                        double alphaDelta = getDelta(coldAlpha, hotAlpha, sourceColorTable.length);
586
                        targetColorTable = new Color[sourceColorTable.length];
587
                        for (int i = 0; i < sourceColorTable.length; i++) {
588
                            Color sourceColor = sourceColorTable[i];
589
                            if (coldAlpha >= 0 && hotAlpha >= 0) {
590
                                targetColorTable[i] =
591
                                    new Color(sourceColor.getRed(), sourceColor.getGreen(), sourceColor.getBlue(),
592
                                        coldAlpha + (int) (i * alphaDelta));
593
                            } else {
594
                                targetColorTable[i] = sourceColor;
595
                            }
596
                        }
597
                    } else { //No se usa alpha para la tabla de color
598
                        targetColorTable = sourceColorTable;
599
                    }
600
                } else { // Tenemos gradiente
601
                    targetColorTable = new Color[length];
602

    
603
                    double deltaRed = (hotColor.getRed() - coldColor.getRed()) / length;
604
                    double deltaGreen = (hotColor.getGreen() - coldColor.getGreen()) / length;
605
                    double deltaBlue = (hotColor.getBlue() - coldColor.getBlue()) / length;
606
                    double deltaAlpha = (hotColor.getAlpha() - coldColor.getAlpha()) / length;
607
                    for (int i = 0; i < length; i++) {
608
                        targetColorTable[i] =
609
                            new Color(
610
                                (int) (coldColor.getRed() + i * deltaRed),
611
                                (int) (coldColor.getGreen() + i * deltaGreen),
612
                                (int) (coldColor.getBlue() + i * deltaBlue),
613
                                (int) (coldColor.getAlpha() + i * deltaAlpha));
614
                    }
615
                }
616
            }
617
            return targetColorTable;
618
        }
619

    
620
        private double getDelta(int x1, int x2, int lenght){
621
            return (x2-x1)/((double)lenght-1);
622
        }
623
    }
624
}