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 | 1717 | jjdelcerro | 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 | } |