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

History | View | Annotate | Download (13.9 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, Color[] colorTable, int alphaHot, int alphaCold, Cancellable cancel) {
108
            try {
109
                Color c;
110
                int maxColors = colorTable.length;
111
                double deltaAlpha = (alphaHot - alphaCold) / maxColors;
112
                for( int x = 0; x < with; x++ ) {
113
                    for( int y = 0; y < height; y++ ) {
114
                        if( cancel.isCanceled() ) {
115
                            return;
116
                        }
117
                        double value = this.grid[x][y];
118
                        if( value > 0 ) {
119
                            int icolor = (int) (value * maxColors / maxValue);
120
                            icolor = icolor < 0 ? 0 : icolor >= maxColors ? maxColors - 1 : icolor;
121
                            if( alphaHot > 0 ) {
122
                                c = colorTable[icolor];
123
                                c = new Color(c.getRed(), c.getGreen(), c.getBlue(), (int) (icolor * deltaAlpha) & 0xff);
124
                            } else {
125
                                c = colorTable[icolor];
126
                            }
127
                            c = blend(new Color(img.getRGB(x, y)),c);
128
                            img.setRGB(x, y, c.getRGB());
129
                        }
130
                    }
131
                }
132
            } catch (Exception ex) {
133
                LOG.warn("Problems drawing heatmap", ex);
134
            }
135
        }
136

    
137
        private Color blend(Color dst, Color src) {
138
            //
139
            // https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
140
            //
141
            // when the destination background is opaque
142
            //
143
            // double outa = 1;
144
            double srca = src.getAlpha() / 255.0;
145
            double srca_1 = (1 - srca);
146
            
147
            Color color = new Color(
148
                (int)(src.getRed()  * srca + dst.getRed()  * srca_1) & 0xff,
149
                (int)(src.getGreen()* srca + dst.getGreen()* srca_1) & 0xff,
150
                (int)(src.getBlue() * srca + dst.getBlue() * srca_1) & 0xff
151
            );
152
            return color;
153
        }
154

    
155
    }
156

    
157
    private final ISymbol defaultSymbol;
158
    private final DensityAlgorithm algorithm;
159
    private boolean isRamp;
160
    private Color[] colorTable;
161
    private int colorTableHotColorAlpha;
162
    private int colorTableColdColorAlpha;
163
    private String fieldName;
164
    private boolean useAlphaInColorTable;
165
    private Image imageLegend;
166
    
167
    public DefaultHeatmapLegend() {
168
        this.defaultSymbol = new SimpleTextSymbol();
169
        this.algorithm = new DensityAlgorithm(30);
170
        this.colorTableHotColorAlpha = 255;
171
        this.colorTableColdColorAlpha = 0;
172
        this.useAlphaInColorTable = false;
173
        this.setColorTable(100, new Color(0, 0, 255, 1), new Color(255, 0, 0, 255));
174
        this.imageLegend = null;
175
    }
176

    
177
    @Override
178
    protected String[] getRequiredFeatureAttributeNames(FeatureStore featureStore) throws DataException {
179
        FeatureType ftype = featureStore.getDefaultFeatureType();
180
        if( StringUtils.isEmpty(this.fieldName) ) {
181
            return new String[]{
182
                ftype.getDefaultGeometryAttributeName()
183
            };
184
        }
185
        return new String[]{
186
            ftype.getDefaultGeometryAttributeName(),
187
            this.fieldName
188
        };
189
    }
190

    
191
    @Override
192
    public ISymbol getDefaultSymbol() {
193
        return this.defaultSymbol;
194
    }
195

    
196
    @Override
197
    public void setDefaultSymbol(ISymbol is) {
198
    }
199

    
200
    @Override
201
    public ISymbol getSymbolByFeature(Feature ftr) throws MapContextException {
202
        return this.defaultSymbol;
203
    }
204

    
205
    @Override
206
    public int getShapeType() {
207
        return Geometry.TYPES.GEOMETRY;
208
    }
209

    
210
    @Override
211
    public void setShapeType(int i) {
212
    }
213

    
214
    @Override
215
    public boolean isUseDefaultSymbol() {
216
        return true;
217
    }
218

    
219
    @Override
220
    public void useDefaultSymbol(boolean bln) {
221
    }
222

    
223
    @Override
224
    public boolean isSuitableForShapeType(int shapeType) {
225
        return true;
226
    }
227

    
228
    @Override
229
    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 {
230
        this.algorithm.init(image.getWidth(), image.getHeight());
231
        super.draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans, featureStore, featureQuery, dpi);
232
        if( !cancel.isCanceled() ) {
233
            if( this.useRamp() || !useAlphaInColorTable ) {
234
                this.algorithm.draw(image, g, this.getColorTable(), -1, -1, cancel);
235
            } else {
236
                this.algorithm.draw(image, g, this.getColorTable(), colorTableHotColorAlpha, colorTableColdColorAlpha, cancel);
237
            }
238
        }
239
    }
240

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

    
290
    /**
291
     * @return the distance
292
     */
293
    @Override
294
    public int getDistance() {
295
        return this.algorithm.getDistance();
296
    }
297

    
298
    /**
299
     * @param distance the distance to set
300
     */
301
    @Override
302
    public void setDistance(int distance) {
303
        this.algorithm.setDistance(distance);
304
    }
305

    
306
    @Override
307
    public void setColorTable(Color[] colorTable) {
308
        this.isRamp = false;
309
        this.colorTable = colorTable;
310
        this.imageLegend = null;
311
        this.fireDefaultSymbolChangedEvent(new SymbolLegendEvent(null,null));
312
    }
313

    
314
    @Override
315
    public void setColorTable(int numColor, Color first, Color last) {
316
        Color[] table = new Color[numColor];
317

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

    
336
    @Override
337
    public Color[] getColorTable() {
338
        return this.colorTable;
339
    }
340

    
341
    @Override
342
    public boolean useRamp() {
343
        return this.isRamp;
344
    }
345

    
346
    @Override
347
    public String getFieldName() {
348
        return this.fieldName;
349
    }
350

    
351
    @Override
352
    public void setFieldName(String fieldName) {
353
        this.fieldName = fieldName;
354
    }
355

    
356
    @Override
357
    public int getColorTableHotColorAlpha() {
358
        return colorTableHotColorAlpha;
359
    }
360

    
361
    @Override
362
    public void setColorTableHotColorAlpha(int colorTableHotColorAlpha) {
363
        this.colorTableHotColorAlpha = colorTableHotColorAlpha;
364
    }
365

    
366
    @Override
367
    public int getColorTableColdColorAlpha() {
368
        return colorTableColdColorAlpha;
369
    }
370

    
371
    @Override
372
    public void setColorTableColdColorAlpha(int colorTableColdColorAlpha) {
373
        this.colorTableColdColorAlpha = colorTableColdColorAlpha;
374
    }
375

    
376
    @Override
377
    public boolean useAlphaInColorTable() {
378
        return this.useAlphaInColorTable;
379
    }
380

    
381
    @Override
382
    public boolean setUseAlphaInColorTable(boolean use) {
383
        boolean x = this.useAlphaInColorTable;
384
        this.useAlphaInColorTable = use;
385
        return x;
386
    }
387

    
388
    @Override
389
    public Image getImageLegend() {
390
        if( this.imageLegend==null ) {
391
            BufferedImage img = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
392
            ColorTablePainter painter = new DefaultColorTablePainter(this.colorTable,"");
393
            Graphics2D g = img.createGraphics();
394
            g.setClip(0, 0, 150, 20);
395
            painter.paint(g, false);
396
            this.imageLegend = img;
397
        }
398
        return this.imageLegend;
399
    }
400

    
401
    @Override
402
    public String getPathImage() {
403
        return null;
404
    }
405

    
406
}