Statistics
| Revision:

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

History | View | Annotate | Download (8.95 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.BufferedImage;
6
import java.util.Map;
7
import org.cresques.cts.ICoordTrans;
8
import org.gvsig.fmap.dal.exception.DataException;
9
import org.gvsig.fmap.dal.feature.Feature;
10
import org.gvsig.fmap.dal.feature.FeatureQuery;
11
import org.gvsig.fmap.dal.feature.FeatureSelection;
12
import org.gvsig.fmap.dal.feature.FeatureSet;
13
import org.gvsig.fmap.dal.feature.FeatureStore;
14
import org.gvsig.fmap.geom.Geometry;
15
import org.gvsig.fmap.geom.primitive.Point;
16
import org.gvsig.fmap.mapcontext.MapContextException;
17
import org.gvsig.fmap.mapcontext.ViewPort;
18
import org.gvsig.fmap.mapcontext.rendering.legend.LegendException;
19
import org.gvsig.fmap.mapcontext.rendering.symbols.ISymbol;
20
import org.gvsig.legend.heatmap.lib.api.HeatmapLegend;
21
import org.gvsig.symbology.fmap.mapcontext.rendering.legend.impl.AbstractVectorialLegend;
22
import org.gvsig.symbology.fmap.mapcontext.rendering.legend.impl.DefaultFeatureDrawnNotification;
23
import org.gvsig.symbology.fmap.mapcontext.rendering.symbol.text.impl.SimpleTextSymbol;
24
import org.gvsig.tools.exception.BaseException;
25
import org.gvsig.tools.task.Cancellable;
26
import org.gvsig.tools.visitor.VisitCanceledException;
27
import org.gvsig.tools.visitor.Visitor;
28
import org.slf4j.Logger;
29
import org.slf4j.LoggerFactory;
30

    
31
public class DefaultHeatmapLegend extends AbstractVectorialLegend implements HeatmapLegend {
32
    
33
    protected static final Logger LOG = LoggerFactory.getLogger(DefaultHeatmapLegend.class);
34
    
35
    private class DensityAlgorithm {
36

    
37
        private double[][] grid;
38
        private double[][] kernel;
39
        private int distance;
40
        private int height;
41
        private int with;
42
        private double maxValue;
43
        private double minValue;
44

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

    
64
        public int getDistance() {
65
            return this.distance;
66
        }
67

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

    
76
        public void add(int px, int py) {
77
            add(px, py, 1);
78
        }
79

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

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

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

    
127
    private final ISymbol defaultSymbol;
128
    private final DensityAlgorithm algorithm;
129
//    private int distance; // Pixels
130
    private Color[] colorTable;
131
    private int opacity;
132

    
133
    public DefaultHeatmapLegend() {
134
        this.defaultSymbol = new SimpleTextSymbol();
135
        this.algorithm = new DensityAlgorithm(30);
136
        this.opacity = 100;
137
        this.colorTable = createColorTable(255, Color.RED, Color.WHITE);
138
    }
139

    
140
    @Override
141
    protected String[] getRequiredFeatureAttributeNames(FeatureStore featureStore) throws DataException {
142
        return new String[]{
143
            featureStore.getDefaultFeatureType().getDefaultGeometryAttributeName()};
144
    }
145

    
146
    @Override
147
    public ISymbol getDefaultSymbol() {
148
        return this.defaultSymbol;
149
    }
150

    
151
    @Override
152
    public void setDefaultSymbol(ISymbol is) {
153
    }
154

    
155
    @Override
156
    public ISymbol getSymbolByFeature(Feature ftr) throws MapContextException {
157
        return this.defaultSymbol;
158
    }
159

    
160
    @Override
161
    public int getShapeType() {
162
        return Geometry.TYPES.GEOMETRY;
163
    }
164

    
165
    @Override
166
    public void setShapeType(int i) {
167
    }
168

    
169
    @Override
170
    public boolean isUseDefaultSymbol() {
171
        return true;
172
    }
173

    
174
    @Override
175
    public void useDefaultSymbol(boolean bln) {
176
    }
177

    
178
    @Override
179
    public boolean isSuitableForShapeType(int shapeType) {
180
        return true;
181
    }
182

    
183
    @Override
184
    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 {
185
        this.algorithm.init(image.getWidth(), image.getHeight());
186
        super.draw(image, g, viewPort, cancel, scale, queryParameters, coordTrans, featureStore, featureQuery, dpi);
187
        if( !cancel.isCanceled() ) {
188
            this.algorithm.draw(image, this.colorTable, cancel);
189
        }
190
    }
191

    
192
    @Override
193
    protected void drawFeatures(
194
        BufferedImage image,
195
        Graphics2D g,
196
        final ViewPort viewPort,
197
        final Cancellable cancel,
198
        final ICoordTrans coordTrans,
199
        double dpi,
200
        DefaultFeatureDrawnNotification drawnNotification,
201
        FeatureSet featureSet,
202
        FeatureSelection selection
203
    ) throws BaseException {
204
        featureSet.accept(new Visitor() {
205
            @Override
206
            public void visit(Object o) throws VisitCanceledException, BaseException {
207
                if( cancel.isCanceled() ) {
208
                    throw new VisitCanceledException();
209
                }
210
                Feature feature = (Feature) o;
211
                Geometry geom = feature.getDefaultGeometry();
212
                if( geom != null ) {
213
                    Point pointGeo = geom.centroid();
214
                    if( coordTrans != null ) {
215
                        pointGeo.reProject(coordTrans);
216
                    }
217
                    Point pointPixels = (Point) pointGeo.cloneGeometry();
218
                    pointPixels.transform(viewPort.getAffineTransform());
219
                    algorithm.add((int) pointPixels.getX(), (int) pointPixels.getY());
220
                }
221
            }
222
        });
223
    }
224

    
225
    private Color[] createColorTable(int elements, Color first, Color last) {
226
        Color[] table = new Color[elements];
227

    
228
        for( int i = 0; i < elements; i++ ) {
229
            table[i] = new Color(i, 0, 0, i); //, this.opacity);
230
//            double p = (i * 255.0 ) / elements;
231
//            int r = (int) (first.getRed() * p + last.getRed() * (1 - p));
232
//            int g = (int) (first.getGreen()* p + last.getGreen() * (1 - p));
233
//            int b = (int) (first.getBlue()* p + last.getBlue()* (1 - p));
234
//            
235
//            r = r>255?255:r<0?0:r;
236
//            g = g>255?255:g<0?0:g;
237
//            b = b>255?255:b<0?0:0;
238
//            table[i] = new Color(r,g,b, this.opacity);
239
        }
240
        return table;
241
    }
242

    
243
    /**
244
     * @return the distance
245
     */
246
    @Override
247
    public int getDistance() {
248
        return this.algorithm.getDistance();
249
    }
250

    
251
    /**
252
     * @param distance the distance to set
253
     */
254
    @Override
255
    public void setDistance(int distance) {
256
        this.algorithm.setDistance(distance);
257
    }
258

    
259
}