Statistics
| Revision:

gvsig-raster / org.gvsig.raster / trunk / org.gvsig.raster / org.gvsig.raster.lib / org.gvsig.raster.lib.impl / src / main / java / org / gvsig / raster / impl / provider / MemoryTileMatrixBuffer.java @ 5462

History | View | Annotate | Download (17.9 KB)

1 2443 nbrodin
/* gvSIG. Geographic Information System of the Valencian Government
2
 *
3
 * Copyright (C) 2007-2008 Infrastructures and Transports Department
4
 * of the Valencian Government (CIT)
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 2
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 */
22
package org.gvsig.raster.impl.provider;
23
24
import java.awt.geom.Rectangle2D;
25
import java.util.ArrayList;
26
import java.util.List;
27
28
import org.gvsig.fmap.dal.coverage.dataset.Buffer;
29
import org.gvsig.fmap.dal.coverage.datastruct.Extent;
30
import org.gvsig.fmap.dal.coverage.exception.ProcessInterruptedException;
31
import org.gvsig.raster.cache.tile.Tile;
32
import org.gvsig.raster.impl.DefaultRasterManager;
33
import org.gvsig.raster.impl.datastruct.ExtentImpl;
34
35
/**
36
 * Buffer composed by a list of tiles and its extents.
37
 * It will expose a method getWindow to read a window of data from the tile list.
38
 * The list of tiles have to make a square.
39
 *
40
 * @author Nacho Brodin (nachobrodin@gmail.com)
41
 */
42
public class MemoryTileMatrixBuffer {
43
        //private Logger           log          = LoggerFactory.getLogger(MemoryMatrixBuffer.class);
44
        private Buffer[]         bufferList   = null;
45
        private int              nRows        = 0;
46
        private int              nCols        = 0;
47
        @SuppressWarnings("unused")
48
        private Extent           bbox         = null;
49
        private List<Tile>       tileList     = null;
50
        private int              dataType     = Buffer.TYPE_UNDEFINED;
51
52
        public class Position {
53
                int col = 0;
54
                int row = 0;
55
        }
56
57
        /**
58
         * Constructor using an array of tiles. It will compute the number
59
         * of rows and columns and the complete bounding box
60
         * @param tileList
61
         */
62
        public MemoryTileMatrixBuffer(Tile[] tileList) {
63
                this.tileList = new ArrayList<Tile>();
64
                bufferList = new Buffer[tileList.length];
65
                for (int i = 0; i < tileList.length; i++) {
66
                        this.tileList.add(tileList[i]);
67
                        bufferList[i] = (Buffer)tileList[i].getData()[0];
68
                        if(bufferList[i] != null)
69
                                dataType = bufferList[i].getDataType();
70
                        //extentList[i] = tileList[i].getExtent();
71
                        //pxLayerList[i] = tileList[i].getCoordsPx();
72
                }
73
                int minRow = Integer.MAX_VALUE;
74
                int maxRow = Integer.MIN_VALUE;
75
                int minCol = Integer.MAX_VALUE;
76
                int maxCol = Integer.MIN_VALUE;
77
                for (int i = 0; i < tileList.length; i++) {
78
                        if(tileList[i].getRow() > maxRow)
79
                                maxRow = tileList[i].getRow();
80
                        if(tileList[i].getCol() > maxCol)
81
                                maxCol = tileList[i].getCol();
82
                        if(tileList[i].getRow() < minRow)
83
                                minRow = tileList[i].getRow();
84
                        if(tileList[i].getCol() < minCol)
85
                                minCol = tileList[i].getCol();
86
                }
87
                nRows = maxRow - minRow + 1;
88
                nCols = maxCol - minCol + 1;
89
                bbox = calculateBBox();
90
        }
91
92
        /**
93
         * Constructor using an ArrayList of tiles. It will compute the number
94
         * of rows and columns and the complete bounding box
95
         * @param tileList
96
         */
97
        public MemoryTileMatrixBuffer(List<Tile> tileList) {
98
                this.tileList = tileList;
99
                bufferList = new Buffer[tileList.size()];
100
                for (int i = 0; i < tileList.size(); i++) {
101
                        bufferList[i] = (Buffer)tileList.get(i).getData()[0];
102
                        if(bufferList[i] != null)
103
                                dataType = bufferList[i].getDataType();
104
                }
105
                int minRow = Integer.MAX_VALUE;
106
                int maxRow = Integer.MIN_VALUE;
107
                int minCol = Integer.MAX_VALUE;
108
                int maxCol = Integer.MIN_VALUE;
109
                for (int i = 0; i < tileList.size(); i++) {
110
                        if(tileList.get(i).getRow() > maxRow)
111
                                maxRow = tileList.get(i).getRow();
112
                        if(tileList.get(i).getCol() > maxCol)
113
                                maxCol = tileList.get(i).getCol();
114
                        if(tileList.get(i).getRow() < minRow)
115
                                minRow = tileList.get(i).getRow();
116
                        if(tileList.get(i).getCol() < minCol)
117
                                minCol = tileList.get(i).getCol();
118
                }
119
                nRows = maxRow - minRow + 1;
120
                nCols = maxCol - minCol + 1;
121
                bbox = calculateBBox();
122
        }
123
124
        /**
125
         * Builds the complete bounding box of the tile list
126
         * @return
127
         */
128
        private Extent calculateBBox() {
129
                double minX = Double.MAX_VALUE;
130
                double minY = Double.MAX_VALUE;
131
                double maxX = 0;
132
                double maxY = 0;
133
                for (int i = 0; i < tileList.size(); i++) {
134
                        Rectangle2D e = tileList.get(i).getExtent();
135
                        if(e.getX() < minX)
136
                                minX = e.getX();
137
                        if((e.getY() - e.getHeight()) < minY)
138
                                minY = (e.getY() - e.getHeight());
139
                        if((e.getX() + e.getWidth()) > maxX)
140
                                maxX = (e.getX() + e.getWidth());
141
                        if(e.getY() > maxY)
142
                                maxY = e.getY();
143
                }
144
                return new ExtentImpl(minX, minY, maxX, maxY);
145
        }
146
147
        /**
148
         * Gets a window from tiles
149
         * @param ext
150
         * @param buf
151
         * @return
152
         */
153
        public Buffer getWindow(Rectangle2D r, int w, int h, int bandCount) {
154
                int shiftRow = 0;
155
                int shiftCol = 0;
156
                int posRow = 0;
157
                int posCol = 0;
158
                Rectangle2D[] rTileShiftList = new Rectangle2D[bufferList.length];
159
                Rectangle2D[] rBufferShiftList = new Rectangle2D[bufferList.length];
160
                int[] colsWidth = new int[nCols];
161
                int[] rowsHeight = new int[nRows];
162
                for (int i = 0; i < bufferList.length; i++) {
163
                        Rectangle2D pxLayer = tileList.get(i).getCoordsPx();
164
                        int initPxTileX = (int)Math.max(pxLayer.getMinX(), r.getMinX()) - (tileList.get(i).getCol() * tileList.get(i).getWidthPx());
165
                        int initPxTileY = (int)Math.max(pxLayer.getMinY(), r.getMinY()) - (tileList.get(i).getRow() * tileList.get(i).getHeightPx());
166
                        int widthPxTile = (int)Math.min(pxLayer.getMaxX(), r.getMaxX()) - (tileList.get(i).getCol() * tileList.get(i).getWidthPx()) - initPxTileX;
167
                        int heightPxTile = (int)Math.min(pxLayer.getMaxY(), r.getMaxY()) - (tileList.get(i).getRow() * tileList.get(i).getHeightPx()) - initPxTileY;
168
                        rTileShiftList[i] = new Rectangle2D.Double(initPxTileX, initPxTileY, widthPxTile, heightPxTile);
169
                        if(i == 0) {
170
                                shiftCol = tileList.get(i).getCol();
171
                                shiftRow = tileList.get(i).getRow();
172
                        }
173
                        posCol = tileList.get(i).getCol() - shiftCol;
174
                        posRow = tileList.get(i).getRow() - shiftRow;
175
                        if(posCol == 0)
176
                                colsWidth[posCol] = widthPxTile;
177
                        else
178
                                colsWidth[posCol] = widthPxTile + colsWidth[posCol - 1];
179
180
                        if(posRow == 0)
181
                                rowsHeight[posRow] = heightPxTile;
182
                        else
183
                                rowsHeight[posRow] = heightPxTile + rowsHeight[posRow - 1];
184
185
186
                        //shiftX = initPxTileX < shiftX ? initPxTileX : shiftX;
187
                        //shiftY = initPxTileY < shiftY ? initPxTileY : shiftY;
188
                }
189
190
                int initPxBufferX = 0;
191
                int initPxBufferY = 0;
192
                int widthPxBuffer = 0;
193
                int heightPxBuffer = 0;
194
195
                for (int i = 0; i < bufferList.length; i++) {
196
                        posCol = tileList.get(i).getCol() - shiftCol;
197
                        posRow = tileList.get(i).getRow() - shiftRow;
198
                        if(posCol == 0) {
199
                                initPxBufferX = 0;
200
                                widthPxBuffer = colsWidth[0];
201
                        } else {
202
                                initPxBufferX = colsWidth[posCol - 1];
203
                                widthPxBuffer = colsWidth[posCol] - initPxBufferX;
204
                        }
205
                        if(posRow == 0) {
206
                                initPxBufferY = 0;
207
                                heightPxBuffer = rowsHeight[0];
208
                        } else {
209
                                initPxBufferY = rowsHeight[posRow - 1];
210
                                heightPxBuffer = rowsHeight[posRow] - initPxBufferY;
211
                        }
212
                        rBufferShiftList[i] = new Rectangle2D.Double(initPxBufferX, initPxBufferY, widthPxBuffer, heightPxBuffer);
213
                }
214
215
                Buffer buf = DefaultRasterManager.getInstance().createBuffer(dataType, w, h, bandCount, true);
216
217
                for (int i = 0; i < bufferList.length; i++) {
218
                        loadBuffer(buf,
219
                                        bufferList[i],
220
                                        rTileShiftList[i],
221
                                        (int)rBufferShiftList[i].getX(),
222
                                        (int)rBufferShiftList[i].getY(),
223
                                        false);
224
                }
225
                return buf;
226
        }
227
228
        /**
229
         * Gets a window from tiles
230
         * @param requestExtent
231
         * @param buf
232
         * @return
233
         */
234
        public Buffer getWindow(Extent requestExtent, int w, int h, int bandCount) {
235
                if(tileList.size() <= 0)
236
                        return null;
237
238
                Buffer sourceWithoutResampling = createBufferWithoutResampling(requestExtent, tileList.get(0).getExtent(), bufferList[0], w, h, bandCount);
239
                double wcX1 = -1;
240
                double wcY1 = -1;
241
                int initXPxBuf = 0;
242
                int initYPxBuf = 0;
243
244
                for (int i = 0; i < bufferList.length; i++) {
245
246
                        //1-Calcular las coordenadas pixel del tile de entrada
247
                        Rectangle2D rTile = getClipPoints(tileList.get(i).getExtent(), bufferList[i], requestExtent);
248
249
                        //2-Ajustar peti?n al extent del tile
250
                        Extent adjustedRequestExtent = getAdjustedExtent(tileList.get(i).getExtent(), requestExtent);
251
252
                        if(        (rTile.getX() == 0 && rTile.getWidth() == 0) ||
253
                                        (rTile.getY() == 0 && rTile.getHeight() == 0))
254
                                continue;
255
256
                        //3-Calcular coordenada pixel de inicio del buffer
257
                        wcX1 = Math.abs(adjustedRequestExtent.getMin().getX() - requestExtent.getMin().getX());
258
                        wcY1 = Math.abs(requestExtent.getMax().getY() - adjustedRequestExtent.getMax().getY());
259
                        initXPxBuf = (int)Math.floor((wcX1 * (sourceWithoutResampling.getWidth())) / requestExtent.width());
260
                        initYPxBuf = (int)Math.floor((wcY1 * (sourceWithoutResampling.getHeight())) / requestExtent.height());
261
262
                        //4-Copiar recorte al buffer
263
264
                        loadBuffer(sourceWithoutResampling, bufferList[i], rTile, initXPxBuf, initYPxBuf, false);
265
                }
266
                /*try {
267
                        save(sourceWithoutResampling, true, requestExtent, null);
268
                } catch (NotSupportedExtensionException e) {
269
                        log.debug("Error saving in MemoryMatrixBuffer", e);
270
                } catch (RasterDriverException e) {
271
                        log.debug("Error saving in MemoryMatrixBuffer", e);
272
                } catch (ProcessInterruptedException e) {
273
                        log.debug("Error saving in MemoryMatrixBuffer", e);
274
                } catch (IOException e) {
275
                        log.debug("Error saving in MemoryMatrixBuffer", e);
276
                }*/
277
                //Devuelve el buffer pero reescalandolo antes al tama?o en pixeles de la petici?n
278
                try {
279
                        Buffer result = null;
280
                        result = sourceWithoutResampling.getAdjustedWindow(w, h, Buffer.INTERPOLATION_NearestNeighbour);
281
                        if(result != sourceWithoutResampling)
282
                                sourceWithoutResampling.dispose();
283
                        return result;
284
                } catch (ProcessInterruptedException e) {
285
                }
286
                return null;
287
        }
288
289
        /**
290
         * Gets the point list to clip the tile
291
         * @param extTile Bounding box of the tile
292
         * @param b Buffer of the tile
293
         * @param extentRequest
294
         * @return
295
         */
296
        private Rectangle2D getClipPoints(Rectangle2D extTile, Buffer b, Extent extentRequest) {
297
                double widthWCTile = extTile.getWidth();
298
                double widthPXTile = b.getWidth();
299
                double heightWCTile = extTile.getHeight();
300
                double heightPXTile = b.getHeight();
301
302
                //1-Ajustar peti?n al extent del tile
303
                Extent adjustedRequestExtent = getAdjustedExtent(extTile, extentRequest);
304
305
                //2-Obtener el punto inicial y final del recorte del tile en pixeles
306
                double wcX1 = adjustedRequestExtent.getMin().getX() - extTile.getX();
307
                double wcX2 = adjustedRequestExtent.getMax().getX() - extTile.getX();
308
                double wcY1 = extTile.getY() - adjustedRequestExtent.getMax().getY();
309
                double wcY2 = extTile.getY() - adjustedRequestExtent.getMin().getY();
310
                int initXPxTile = (int)Math.floor((wcX1 * widthPXTile) / widthWCTile);
311
                int endXPxTile = (int)Math.ceil((wcX2 * widthPXTile) / widthWCTile);
312
                int initYPxTile = (int)Math.floor((wcY1 * heightPXTile) / heightWCTile);
313
                int endYPxTile = (int)Math.ceil((wcY2 * heightPXTile) / heightWCTile);
314
                endXPxTile = endXPxTile >= widthPXTile ? (int)(widthPXTile - 1) : endXPxTile;
315
                endYPxTile = endYPxTile >= heightPXTile ? (int)(heightPXTile - 1) : endYPxTile;
316
317
                return new Rectangle2D.Double(initXPxTile,
318
                                initYPxTile,
319
                                Math.abs((endXPxTile - initXPxTile) + 1),
320
                                Math.abs((endYPxTile - initYPxTile) + 1));
321
        }
322
323
        /**
324
         * Write data in the source buffer taking into account the view shift
325
         * @param dstBuf
326
         * @param tileBuf
327
         * @param rTile
328
         * @param initXPxBuf
329
         * @param initYPxBuf
330
         */
331
        private void loadBuffer(Buffer dstBuf, Buffer tileBuf, Rectangle2D rTile, int initXPxBuf, int initYPxBuf, boolean alpha) {
332
                int r = initXPxBuf;
333
                int c = initYPxBuf;
334
                //if(!alpha) {
335
                        if(tileBuf.getDataType() == Buffer.TYPE_BYTE) {
336
                                for (int band = 0; band < tileBuf.getBandCount(); band++) {
337
                                        r = initYPxBuf;
338
                                        for (int row = (int)rTile.getMinY(); (row < (int)rTile.getMaxY() && r < dstBuf.getHeight()); row++) {
339
                                                c = initXPxBuf;
340
                                                for (int col = (int)rTile.getMinX(); (col < (int)rTile.getMaxX() && c < dstBuf.getWidth()); col++) {
341
                                                        dstBuf.setElem(r, c, band, tileBuf.getElemByte(row, col, band));
342
                                                        c++;
343
                                                }
344
                                                r++;
345
                                        }
346
                                }
347
                        }
348
                /*} else {
349
                        if(tileBuf.getDataType() == Buffer.TYPE_BYTE) {
350
                                r = initYPxBuf;
351
                                for (int row = (int)rTile.getMinY(); row < (int)rTile.getMaxY(); row++) {
352
                                        c = initXPxBuf;
353
                                        for (int col = (int)rTile.getMinX(); col < (int)rTile.getMaxX(); col++) {
354
                                                sourceBuf.setElem(r, c, 0, tileBuf.getElemByte(row, col, 0));
355
                                                c++;
356
                                        }
357
                                        r++;
358
                                }
359
                        }
360
                }*/
361
                if(tileBuf.getDataType() == Buffer.TYPE_SHORT) {
362
                        for (int band = 0; band < tileBuf.getBandCount(); band++) {
363
                                r = initYPxBuf;
364
                                for (int row = (int)rTile.getMinY(); (row < (int)rTile.getMaxY() && r < dstBuf.getHeight()); row++) {
365
                                        c = initXPxBuf;
366
                                        for (int col = (int)rTile.getMinX(); (col < (int)rTile.getMaxX() && c < dstBuf.getWidth()); col++) {
367
                                                dstBuf.setElem(r, c, band, tileBuf.getElemShort(row, col, band));
368
                                                c++;
369
                                        }
370
                                        r++;
371
                                }
372
                        }
373
                }
374
                if(tileBuf.getDataType() == Buffer.TYPE_INT) {
375
                        for (int band = 0; band < tileBuf.getBandCount(); band++) {
376
                                r = initYPxBuf;
377
                                for (int row = (int)rTile.getMinY(); (row < (int)rTile.getMaxY() && r < dstBuf.getHeight()); row++) {
378
                                        c = initXPxBuf;
379
                                        for (int col = (int)rTile.getMinX(); (col < (int)rTile.getMaxX() && c < dstBuf.getWidth()); col++) {
380
                                                dstBuf.setElem(r, c, band, tileBuf.getElemInt(row, col, band));
381
                                                c++;
382
                                        }
383
                                        r++;
384
                                }
385
                        }
386
                }
387
                if(tileBuf.getDataType() == Buffer.TYPE_FLOAT) {
388
                        for (int band = 0; band < tileBuf.getBandCount(); band++) {
389
                                r = initYPxBuf;
390
                                for (int row = (int)rTile.getMinY(); (row < (int)rTile.getMaxY() && r < dstBuf.getHeight()); row++) {
391
                                        c = initXPxBuf;
392
                                        for (int col = (int)rTile.getMinX(); (col < (int)rTile.getMaxX() && c < dstBuf.getWidth()); col++) {
393
                                                dstBuf.setElem(r, c, band, tileBuf.getElemFloat(row, col, band));
394
                                                c++;
395
                                        }
396
                                        r++;
397
                                }
398
                        }
399
                }
400
                if(tileBuf.getDataType() == Buffer.TYPE_DOUBLE) {
401
                        for (int band = 0; band < tileBuf.getBandCount(); band++) {
402
                                r = initYPxBuf;
403
                                for (int row = (int)rTile.getMinY(); (row < (int)rTile.getMaxY() && r < dstBuf.getHeight()); row++) {
404
                                        c = initXPxBuf;
405
                                        for (int col = (int)rTile.getMinX(); (col < (int)rTile.getMaxX() && c < dstBuf.getWidth()); col++) {
406
                                                dstBuf.setElem(r, c, band, tileBuf.getElemDouble(row, col, band));
407
                                                c++;
408
                                        }
409
                                        r++;
410
                                }
411
                        }
412
                }
413
        }
414
415
//        private void save(Buffer bufResult,
416
//                        boolean alphaBand,
417
//                        Extent tileExtent,
418
//                        ColorInterpretation colorInterpretation) throws NotSupportedExtensionException, RasterDriverException, ProcessInterruptedException, IOException {
419
//                //Escritura en disco del tile
420
//                RasterManager rManager = RasterLocator.getManager();
421
//                DataServerWriter dataWriter = RasterLocator.getManager().createDataServerWriter();
422
//                dataWriter.setBuffer(bufResult, -1);
423
//                Params params = rManager.createWriter("_.tif").getParams();
424
//                double pixelSize = (tileExtent.width() / bufResult.getWidth());
425
//                AffineTransform affineTransform = new AffineTransform(pixelSize, 0, 0, -pixelSize,
426
//                                tileExtent.getULX(),
427
//                                tileExtent.getULY());
428
//                RasterWriter rw = rManager.createWriter(dataWriter, "/tmp/prueba.tif",
429
//                                bufResult.getBandCount(), affineTransform, bufResult.getWidth(),
430
//                                bufResult.getHeight(), bufResult.getDataType(), params, null);
431
//
432
//                if(colorInterpretation != null) {
433
//                        String[] values = colorInterpretation.getValues();
434
//                        if(alphaBand) {
435
//                                String[] newValues = values;
436
//                                boolean exists = false;
437
//                                for (int i = 0; i < values.length; i++) {
438
//                                        if(values[i] != null && values[i].compareTo("Alpha") == 0)
439
//                                                exists = true;
440
//                                }
441
//                                if(!exists) {
442
//                                        newValues = new String[values.length + 1];
443
//                                        for (int i = 0; i < values.length; i++) {
444
//                                                newValues[i] = values[i];
445
//                                        }
446
//                                        newValues[newValues.length - 1] = "Alpha";
447
//                                }
448
//                                rw.setColorBandsInterpretation(newValues);
449
//                        }
450
//
451
//                } else {
452
//                        if(alphaBand) {
453
//                                String[] ci = new String[bufResult.getBandCount()];
454
//                                if(bufResult.getBandCount() == 4) {
455
//                                        ci[0] = "Red";
456
//                                        ci[1] = "Green";
457
//                                        ci[2] = "Blue";
458
//                                        ci[3] = "Alpha";
459
//                                } else {
460
//                                        for (int i = 0; i < bufResult.getBandCount(); i++) {
461
//                                                ci[i] = "Gray";
462
//                                        }
463
//                                }
464
//                                rw.setColorBandsInterpretation(ci);
465
//                        }
466
//                }
467
//                rw.dataWrite();
468
//                rw.writeClose();
469
//        }
470
471
        /**
472
         * Builds a buffer in the same resolution as the list of tiles. When the operation
473
         * ends this buffer should be resampled.
474
         * @return
475
         */
476
        private Buffer createBufferWithoutResampling(Extent extOrigin,
477
                        Rectangle2D extTile,
478
                        Buffer bufTile,
479
                        int originWidth,
480
                        int originHeight,
481
                        int bandCount) {
482
                double psOrigin = extOrigin.width() / originWidth;
483
                double psTile = extTile.getWidth() / bufTile.getWidth();
484
                double rel = psTile / psOrigin;
485
                int w = (int)Math.floor(originWidth / rel);
486
                int h = (int)Math.floor(originHeight / rel);
487
                return DefaultRasterManager.getInstance().createBuffer(dataType, w, h, bandCount, true);
488
        }
489
490
491
        /**
492
         * Adjust the request to the tile bounding box
493
         * @param tileExtent
494
         * @param extentRequest
495
         * @return
496
         */
497
        private Extent getAdjustedExtent(Rectangle2D tileExtent, Extent extentRequest) {
498
                double x1 = Math.max(extentRequest.getMin().getX(), tileExtent.getX());
499
                double y1 = Math.min(extentRequest.getMax().getY(), tileExtent.getY());
500
                double x2 = Math.min(extentRequest.getMax().getX(), (tileExtent.getX() + tileExtent.getWidth()));
501
                double y2 = Math.max(extentRequest.getMin().getY(), (tileExtent.getY() - tileExtent.getHeight()));
502
                return new ExtentImpl(x1, y1, x2, y2);
503
        }
504
505
506
        /*private double clip(double value) {
507
                return math.clipDecimals(value, 5);
508
        }
509

510
        private double round(double value) {
511
                double a = (value - (int)value);
512
                return (a > 0.95 || a < 0.05) ? Math.round(value) : value;
513
        }*/
514
515
}