Source Code Cross Referenced for ImageMosaicReader.java in  » GIS » GeoTools-2.4.1 » org » geotools » gce » imagemosaic » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.gce.imagemosaic 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * Geotools 2 - OpenSource mapping toolkit
0003:         * (C) 2006, Geotools Project Managment Committee (PMC)
0004:         *
0005:         *    This library is free software; you can redistribute it and/or
0006:         *    modify it under the terms of the GNU Lesser General Public
0007:         *    License as published by the Free Software Foundation; either
0008:         *    version 2.1 of the License, or (at your option) any later version.
0009:         *
0010:         *    This library is distributed in the hope that it will be useful,
0011:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013:         *    Lesser General Public License for more details.
0014:         *
0015:         *    You should have received a copy of the GNU Lesser General Public
0016:         *    License along with this library; if not, write to the Free Software
0017:         *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
0018:         */
0019:        package org.geotools.gce.imagemosaic;
0020:
0021:        import java.awt.Color;
0022:        import java.awt.Rectangle;
0023:        import java.awt.RenderingHints;
0024:        import java.awt.Transparency;
0025:        import java.awt.geom.AffineTransform;
0026:        import java.awt.geom.Area;
0027:        import java.awt.geom.Point2D;
0028:        import java.awt.image.BufferedImage;
0029:        import java.awt.image.ColorModel;
0030:        import java.awt.image.DataBuffer;
0031:        import java.awt.image.IndexColorModel;
0032:        import java.awt.image.renderable.ParameterBlock;
0033:        import java.io.BufferedInputStream;
0034:        import java.io.File;
0035:        import java.io.FileInputStream;
0036:        import java.io.FileNotFoundException;
0037:        import java.io.IOException;
0038:        import java.io.UnsupportedEncodingException;
0039:        import java.lang.ref.SoftReference;
0040:        import java.net.MalformedURLException;
0041:        import java.net.URL;
0042:        import java.net.URLDecoder;
0043:        import java.util.Iterator;
0044:        import java.util.List;
0045:        import java.util.Properties;
0046:        import java.util.logging.Level;
0047:        import java.util.logging.Logger;
0048:
0049:        import javax.imageio.ImageIO;
0050:        import javax.imageio.ImageReadParam;
0051:        import javax.media.jai.ImageLayout;
0052:        import javax.media.jai.JAI;
0053:        import javax.media.jai.ParameterBlockJAI;
0054:        import javax.media.jai.PlanarImage;
0055:        import javax.media.jai.ROI;
0056:        import javax.media.jai.operator.MosaicDescriptor;
0057:        import javax.media.jai.operator.PatternDescriptor;
0058:
0059:        import org.geotools.coverage.grid.GeneralGridRange;
0060:        import org.geotools.coverage.grid.GridCoverage2D;
0061:        import org.geotools.coverage.grid.GridGeometry2D;
0062:        import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
0063:        import org.geotools.coverage.grid.io.AbstractGridFormat;
0064:        import org.geotools.data.AbstractDataStore;
0065:        import org.geotools.data.DataSourceException;
0066:        import org.geotools.data.FeatureSource;
0067:        import org.geotools.data.shapefile.ShapefileDataStore;
0068:        import org.geotools.factory.FactoryRegistryException;
0069:        import org.geotools.factory.Hints;
0070:        import org.geotools.feature.Feature;
0071:        import org.geotools.geometry.GeneralEnvelope;
0072:        import org.geotools.geometry.jts.ReferencedEnvelope;
0073:        import org.geotools.image.ImageWorker;
0074:        import org.geotools.parameter.Parameter;
0075:        import org.geotools.referencing.CRS;
0076:        import org.geotools.referencing.operation.builder.GridToEnvelopeMapper;
0077:        import org.geotools.resources.CRSUtilities;
0078:        import org.geotools.resources.image.ImageUtilities;
0079:        import org.opengis.coverage.grid.Format;
0080:        import org.opengis.coverage.grid.GridCoverage;
0081:        import org.opengis.coverage.grid.GridCoverageReader;
0082:        import org.opengis.parameter.GeneralParameterValue;
0083:        import org.opengis.referencing.FactoryException;
0084:        import org.opengis.referencing.crs.CoordinateReferenceSystem;
0085:        import org.opengis.referencing.datum.PixelInCell;
0086:        import org.opengis.referencing.operation.MathTransform;
0087:        import org.opengis.referencing.operation.NoninvertibleTransformException;
0088:        import org.opengis.referencing.operation.TransformException;
0089:        import org.opengis.geometry.MismatchedDimensionException;
0090:
0091:        import com.vividsolutions.jts.geom.Envelope;
0092:
0093:        /**
0094:         * This reader is repsonsible for providing access to mosaic of georeferenced
0095:         * images. Citing JAI documentation:
0096:         * 
0097:         * The "Mosaic" operation creates a mosaic of two or more source images. This
0098:         * operation could be used for example to assemble a set of overlapping
0099:         * geospatially rectified images into a contiguous image. It could also be used
0100:         * to create a montage of photographs such as a panorama.
0101:         * 
0102:         * All source images are assumed to have been geometrically mapped into a common
0103:         * coordinate space. The origin (minX, minY) of each image is therefore taken to
0104:         * represent the location of the respective image in the common coordinate
0105:         * system of the sour ce images. This coordinate space will also be that of the
0106:         * destination image.
0107:         * 
0108:         * All source images must have the same data type and sample size for all bands
0109:         * and have the same number of bands as color components. The destination will
0110:         * have the same data type, sample size, and number of bands and color
0111:         * components as the sources.
0112:         * 
0113:         * 
0114:         * @author Simone Giannecchini
0115:         * @since 2.3
0116:         * 
0117:         */
0118:        public final class ImageMosaicReader extends
0119:                AbstractGridCoverage2DReader implements  GridCoverageReader {
0120:
0121:            /** Logger. */
0122:            private final static Logger LOGGER = org.geotools.util.logging.Logging
0123:                    .getLogger("org.geotools.gce.imagemosaic");
0124:
0125:            /**
0126:             * The source {@link URL} pointing to the index shapefile for this
0127:             * {@link ImageMosaicReader}.
0128:             */
0129:            private final URL sourceURL;
0130:
0131:            /** {@link AbstractDataStore} pointd to the index shapefile. */
0132:            private final AbstractDataStore tileIndexStore;
0133:
0134:            /** {@link SoftReference} to the index holding the tiles' envelopes. */
0135:            private SoftReference index;
0136:
0137:            /**
0138:             * The typename of the chems inside the {@link ShapefileDataStore} that
0139:             * contains the index for this {@link ImageMosaicReader}.
0140:             */
0141:            private final String typeName;
0142:
0143:            /** {@link FeatureSource} for the shape index. */
0144:            private final FeatureSource featureSource;
0145:
0146:            /**
0147:             * This {@link BufferedImage} is cached in memory since it is used whenever
0148:             * I need to build up a fake mosaic.
0149:             */
0150:            private final BufferedImage unavailableImage;
0151:
0152:            private boolean expandMe;
0153:
0154:            /**
0155:             * Max number of tiles that this plugin will load.
0156:             * 
0157:             * If this number is exceeded, i.e. we request an area which is too large
0158:             * instead of getting stuck ith opening thousands of files I give you back a
0159:             * fake coverage.
0160:             */
0161:            public static final int MAX_TILES = 1000000;
0162:
0163:            /**
0164:             * COnstructor.
0165:             * 
0166:             * @param source
0167:             *            The source object.
0168:             * @throws IOException
0169:             * @throws UnsupportedEncodingException
0170:             * 
0171:             */
0172:            public ImageMosaicReader(Object source, Hints uHints)
0173:                    throws IOException {
0174:                // /////////////////////////////////////////////////////////////////////
0175:                // 
0176:                // Forcing longitude first since the geotiff specification seems to
0177:                // assume that we have first longitude the latitude.
0178:                //
0179:                // /////////////////////////////////////////////////////////////////////
0180:                if (uHints != null) {
0181:                    // prevent the use from reordering axes
0182:                    this .hints.add(uHints);
0183:                }
0184:                if (source == null) {
0185:
0186:                    final IOException ex = new IOException(
0187:                            "ImageMosaicReader:No source set to read this coverage.");
0188:                    if (LOGGER.isLoggable(Level.WARNING))
0189:                        LOGGER.log(Level.WARNING, ex.getLocalizedMessage(), ex);
0190:                    throw new DataSourceException(ex);
0191:                }
0192:                this .source = source;
0193:
0194:                // /////////////////////////////////////////////////////////////////////
0195:                //
0196:                // Check source
0197:                //
0198:                // /////////////////////////////////////////////////////////////////////
0199:                if (source instanceof  File)
0200:                    this .sourceURL = ((File) source).toURL();
0201:                else if (source instanceof  URL)
0202:                    this .sourceURL = (URL) source;
0203:                else if (source instanceof  String) {
0204:                    final File tempFile = new File((String) source);
0205:                    if (tempFile.exists()) {
0206:                        this .sourceURL = tempFile.toURL();
0207:                    } else
0208:                        try {
0209:                            this .sourceURL = new URL(URLDecoder.decode(
0210:                                    (String) source, "UTF8"));
0211:                            if (this .sourceURL.getProtocol() != "file") {
0212:                                throw new IllegalArgumentException(
0213:                                        "This plugin accepts only File,  URL and String pointing to a file");
0214:
0215:                            }
0216:                        } catch (MalformedURLException e) {
0217:                            if (LOGGER.isLoggable(Level.WARNING))
0218:                                LOGGER.log(Level.WARNING, e
0219:                                        .getLocalizedMessage(), e);
0220:                            throw new IllegalArgumentException(
0221:                                    "This plugin accepts only File,  URL and String pointing to a file");
0222:
0223:                        } catch (UnsupportedEncodingException e) {
0224:                            if (LOGGER.isLoggable(Level.WARNING))
0225:                                LOGGER.log(Level.WARNING, e
0226:                                        .getLocalizedMessage(), e);
0227:                            throw new IllegalArgumentException(
0228:                                    "This plugin accepts only File,  URL and String pointing to a file");
0229:
0230:                        }
0231:
0232:                } else
0233:                    throw new IllegalArgumentException(
0234:                            "This plugin accepts only File, URL and String pointing to a file");
0235:
0236:                // /////////////////////////////////////////////////////////////////////
0237:                //
0238:                // Load tiles informations, especially the bounds, which will be
0239:                // reused
0240:                //
0241:                // /////////////////////////////////////////////////////////////////////
0242:                tileIndexStore = new ShapefileDataStore(this .sourceURL);
0243:                if (LOGGER.isLoggable(Level.FINE))
0244:                    LOGGER.fine("Connected mosaic reader to its data store "
0245:                            + sourceURL.toString());
0246:                final String[] typeNames = tileIndexStore.getTypeNames();
0247:                if (typeNames.length <= 0)
0248:                    throw new IllegalArgumentException(
0249:                            "Problems when opening the index, no typenames for the schema are defined");
0250:
0251:                typeName = typeNames[0];
0252:                featureSource = tileIndexStore.getFeatureSource(typeName);
0253:
0254:                // //
0255:                //
0256:                // Load all the features inside the index
0257:                //
0258:                // //
0259:                if (LOGGER.isLoggable(Level.FINE))
0260:                    LOGGER.fine("About to create index");
0261:                index = new SoftReference(new MemorySpatialIndex(featureSource
0262:                        .getFeatures()));
0263:                if (LOGGER.isLoggable(Level.FINE))
0264:                    LOGGER.fine("Created index");
0265:                // //
0266:                //
0267:                // get the crs if able to
0268:                //
0269:                // //
0270:                final Object tempCRS = this .hints
0271:                        .get(Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM);
0272:                if (tempCRS != null) {
0273:                    this .crs = (CoordinateReferenceSystem) tempCRS;
0274:                    LOGGER.log(Level.WARNING, new StringBuffer(
0275:                            "Using forced coordinate reference system ")
0276:                            .append(crs.toWKT()).toString());
0277:                } else {
0278:                    final CoordinateReferenceSystem tempcrs = featureSource
0279:                            .getSchema().getDefaultGeometry()
0280:                            .getCoordinateSystem();
0281:                    if (tempcrs == null) {
0282:                        // use the default crs
0283:                        crs = AbstractGridFormat.getDefaultCRS();
0284:                        LOGGER
0285:                                .log(
0286:                                        Level.WARNING,
0287:                                        new StringBuffer(
0288:                                                "Unable to find a CRS for this coverage, using a default one: ")
0289:                                                .append(crs.toWKT()).toString());
0290:                    } else
0291:                        crs = tempcrs;
0292:                }
0293:
0294:                // /////////////////////////////////////////////////////////////////////
0295:                //
0296:                // Load properties file with information about levels and envelope
0297:                //
0298:                // /////////////////////////////////////////////////////////////////////
0299:                // property file
0300:                loadProperties();
0301:
0302:                // load the unavailaible pattern
0303:                unavailableImage = ImageIO.read(this .getClass().getResource(
0304:                        "unav.png"));
0305:            }
0306:
0307:            /**
0308:             * Loads the properties file that contains useful information about this
0309:             * coverage.
0310:             * 
0311:             * @throws UnsupportedEncodingException
0312:             * @throws IOException
0313:             * @throws FileNotFoundException
0314:             */
0315:            private void loadProperties() throws UnsupportedEncodingException,
0316:                    IOException, FileNotFoundException {
0317:                String temp = URLDecoder.decode(sourceURL.getFile(), "UTF8");
0318:                final int index = temp.lastIndexOf(".");
0319:                if (index != -1)
0320:                    temp = temp.substring(0, index);
0321:                final File propertiesFile = new File(new StringBuffer(temp)
0322:                        .append(".properties").toString());
0323:                assert propertiesFile.exists() && propertiesFile.isFile();
0324:                final Properties properties = new Properties();
0325:                properties.load(new BufferedInputStream(new FileInputStream(
0326:                        propertiesFile)));
0327:
0328:                // load the envelope
0329:                final String envelope = properties.getProperty("Envelope2D");
0330:                String[] pairs = envelope.split(" ");
0331:                final double cornersV[][] = new double[2][2];
0332:                String pair[];
0333:                for (int i = 0; i < 2; i++) {
0334:                    pair = pairs[i].split(",");
0335:                    cornersV[i][0] = Double.parseDouble(pair[0]);
0336:                    cornersV[i][1] = Double.parseDouble(pair[1]);
0337:                }
0338:                this .originalEnvelope = new GeneralEnvelope(cornersV[0],
0339:                        cornersV[1]);
0340:                this .originalEnvelope.setCoordinateReferenceSystem(crs);
0341:
0342:                // resolutions levels
0343:                numOverviews = Integer.parseInt(properties
0344:                        .getProperty("LevelsNum")) - 1;
0345:                final String levels = properties.getProperty("Levels");
0346:                pairs = levels.split(" ");
0347:                overViewResolutions = numOverviews > 1 ? new double[numOverviews][2]
0348:                        : null;
0349:                pair = pairs[0].split(",");
0350:                highestRes = new double[2];
0351:                highestRes[0] = Double.parseDouble(pair[0]);
0352:                highestRes[1] = Double.parseDouble(pair[1]);
0353:
0354:                if (LOGGER.isLoggable(Level.FINE))
0355:                    LOGGER.fine(new StringBuffer("Highest res ").append(
0356:                            highestRes[0]).append(" ").append(highestRes[1])
0357:                            .toString());
0358:
0359:                for (int i = 1; i < numOverviews + 1; i++) {
0360:                    pair = pairs[i].split(",");
0361:                    overViewResolutions[i - 1][0] = Double.parseDouble(pair[0]);
0362:                    overViewResolutions[i - 1][1] = Double.parseDouble(pair[1]);
0363:                }
0364:
0365:                // name
0366:                coverageName = properties.getProperty("Name");
0367:
0368:                // need a color expansion?
0369:                // this is a newly added property we have to be ready to the case where
0370:                // we do not find it.
0371:                try {
0372:                    expandMe = properties.getProperty("ExpandToRGB")
0373:                            .equalsIgnoreCase("true");
0374:                } catch (Exception e) {
0375:                    expandMe = false;
0376:                }
0377:
0378:                // original gridrange (estimated)
0379:                originalGridRange = new GeneralGridRange(new Rectangle(
0380:                        (int) Math.round(originalEnvelope.getLength(0)
0381:                                / highestRes[0]), (int) Math
0382:                                .round(originalEnvelope.getLength(1)
0383:                                        / highestRes[1])));
0384:            }
0385:
0386:            /**
0387:             * Constructor.
0388:             * 
0389:             * @param source
0390:             *            The source object.
0391:             * @throws IOException
0392:             * @throws UnsupportedEncodingException
0393:             * 
0394:             */
0395:            public ImageMosaicReader(Object source) throws IOException {
0396:                this (source, null);
0397:
0398:            }
0399:
0400:            /*
0401:             * (non-Javadoc)
0402:             * 
0403:             * @see org.opengis.coverage.grid.GridCoverageReader#getFormat()
0404:             */
0405:            public Format getFormat() {
0406:                return new ImageMosaicFormat();
0407:            }
0408:
0409:            /*
0410:             * (non-Javadoc)
0411:             * 
0412:             * @see org.opengis.coverage.grid.GridCoverageReader#read(org.opengis.parameter.GeneralParameterValue[])
0413:             */
0414:            public GridCoverage read(GeneralParameterValue[] params)
0415:                    throws IOException {
0416:
0417:                if (LOGGER.isLoggable(Level.FINE)) {
0418:                    LOGGER.fine("Reading mosaic from " + sourceURL.toString());
0419:                    LOGGER.fine(new StringBuffer("Highest res ").append(
0420:                            highestRes[0]).append(" ").append(highestRes[1])
0421:                            .toString());
0422:                }
0423:
0424:                // /////////////////////////////////////////////////////////////////////
0425:                //
0426:                // Checking params
0427:                //
0428:                // /////////////////////////////////////////////////////////////////////
0429:                Color inputTransparentColor = (Color) ImageMosaicFormat.INPUT_TRANSPARENT_COLOR
0430:                        .getDefaultValue();
0431:                Color outputTransparentColor = (Color) ImageMosaicFormat.OUTPUT_TRANSPARENT_COLOR
0432:                        .getDefaultValue();
0433:                double inputImageThreshold = ((Double) ImageMosaicFormat.INPUT_IMAGE_THRESHOLD_VALUE
0434:                        .getDefaultValue()).doubleValue();
0435:                Parameter param = null;
0436:                GeneralEnvelope requestedEnvelope = null;
0437:                Rectangle dim = null;
0438:                boolean blend = false;
0439:                if (params != null) {
0440:                    final int length = params.length;
0441:                    for (int i = 0; i < length; i++) {
0442:                        param = (Parameter) params[i];
0443:
0444:                        if (param.getDescriptor().getName().getCode().equals(
0445:                                ImageMosaicFormat.READ_GRIDGEOMETRY2D.getName()
0446:                                        .toString())) {
0447:                            final GridGeometry2D gg = (GridGeometry2D) param
0448:                                    .getValue();
0449:                            requestedEnvelope = (GeneralEnvelope) gg
0450:                                    .getEnvelope();
0451:                            dim = gg.getGridRange2D().getBounds();
0452:                        } else if (param
0453:                                .getDescriptor()
0454:                                .getName()
0455:                                .getCode()
0456:                                .equals(
0457:                                        ImageMosaicFormat.INPUT_TRANSPARENT_COLOR
0458:                                                .getName().toString())) {
0459:                            inputTransparentColor = (Color) param.getValue();
0460:
0461:                        } else if (param
0462:                                .getDescriptor()
0463:                                .getName()
0464:                                .getCode()
0465:                                .equals(
0466:                                        ImageMosaicFormat.INPUT_IMAGE_THRESHOLD_VALUE
0467:                                                .getName().toString())) {
0468:                            inputImageThreshold = ((Double) param.getValue())
0469:                                    .doubleValue();
0470:
0471:                        } else if (param.getDescriptor().getName().getCode()
0472:                                .equals(
0473:                                        ImageMosaicFormat.FADING.getName()
0474:                                                .toString())) {
0475:                            blend = ((Boolean) param.getValue()).booleanValue();
0476:
0477:                        } else if (param
0478:                                .getDescriptor()
0479:                                .getName()
0480:                                .getCode()
0481:                                .equals(
0482:                                        ImageMosaicFormat.OUTPUT_TRANSPARENT_COLOR
0483:                                                .getName().toString())) {
0484:                            outputTransparentColor = (Color) param.getValue();
0485:
0486:                        }
0487:                    }
0488:                }
0489:                // /////////////////////////////////////////////////////////////////////
0490:                //
0491:                // Loading tiles trying to optimize as much as possible
0492:                //
0493:                // /////////////////////////////////////////////////////////////////////
0494:                return loadTiles(requestedEnvelope, inputTransparentColor,
0495:                        outputTransparentColor, inputImageThreshold, dim, blend);
0496:            }
0497:
0498:            /**
0499:             * Loading the tiles which overlap with the requested envelope with control
0500:             * over the <code>inputImageThresholdValue</code>, the fading effect
0501:             * between different images, abd the <code>transparentColor</code> for the
0502:             * input images.
0503:             * 
0504:             * @param requestedOriginalEnvelope
0505:             *            bounds the tiles that we will load. Tile outside ths
0506:             *            {@link GeneralEnvelope} won't even be considered.
0507:             * 
0508:             * 
0509:             * @param transparentColor
0510:             *            should be used to control transparency on input images.
0511:             * @param outputTransparentColor
0512:             * @param inputImageThresholdValue
0513:             *            should be used to create ROIs on the input images
0514:             * @param pixelDimension
0515:             *            is the dimension in pixels of the requested coverage.
0516:             * @param fading
0517:             *            tells to ask for {@link MosaicDescriptor#MOSAIC_TYPE_BLEND}
0518:             *            instead of the classic
0519:             *            {@link MosaicDescriptor#MOSAIC_TYPE_OVERLAY}.
0520:             * @return a {@link GridCoverage2D} matching as close as possible the
0521:             *         requested {@link GeneralEnvelope} and <code>pixelDimension</code>,
0522:             *         or null in case nothing existed in the requested area.
0523:             * @throws IOException
0524:             */
0525:            private GridCoverage loadTiles(
0526:                    GeneralEnvelope requestedOriginalEnvelope,
0527:                    Color transparentColor, Color outputTransparentColor,
0528:                    double inputImageThresholdValue, Rectangle pixelDimension,
0529:                    boolean fading) throws IOException {
0530:
0531:                if (LOGGER.isLoggable(Level.FINE))
0532:                    LOGGER
0533:                            .fine(new StringBuffer(
0534:                                    "Creating mosaic to comply with envelope ")
0535:                                    .append(
0536:                                            requestedOriginalEnvelope != null ? requestedOriginalEnvelope
0537:                                                    .toString()
0538:                                                    : null)
0539:                                    .append(" crs ")
0540:                                    .append(crs.toWKT())
0541:                                    .append(" dim ")
0542:                                    .append(
0543:                                            pixelDimension == null ? " null"
0544:                                                    : pixelDimension.toString())
0545:                                    .toString());
0546:                // /////////////////////////////////////////////////////////////////////
0547:                //
0548:                // Check if we have something to load by intersecting the requested
0549:                // envelope with the bounds of the data set.
0550:                //
0551:                // If the requested envelope is not in the same crs of the data set crs
0552:                // we have to perform a conversion towards the latter crs before
0553:                // intersecting anything.
0554:                //
0555:                // /////////////////////////////////////////////////////////////////////
0556:                GeneralEnvelope intersectionEnvelope = null;
0557:                if (requestedOriginalEnvelope != null) {
0558:                    if (!CRS.equalsIgnoreMetadata(requestedOriginalEnvelope
0559:                            .getCoordinateReferenceSystem(), this .crs)) {
0560:                        try {
0561:                            // transforming the envelope back to the dataset crs in
0562:                            // order to interact with the original envelope for this
0563:                            // mosaic.
0564:                            final MathTransform transform = operationFactory
0565:                                    .createOperation(
0566:                                            requestedOriginalEnvelope
0567:                                                    .getCoordinateReferenceSystem(),
0568:                                            crs).getMathTransform();
0569:                            if (!transform.isIdentity()) {
0570:                                requestedOriginalEnvelope = CRSUtilities
0571:                                        .transform(transform,
0572:                                                requestedOriginalEnvelope);
0573:                                requestedOriginalEnvelope
0574:                                        .setCoordinateReferenceSystem(this .crs);
0575:
0576:                                if (LOGGER.isLoggable(Level.FINE))
0577:                                    LOGGER.fine(new StringBuffer(
0578:                                            "Reprojected envelope ").append(
0579:                                            requestedOriginalEnvelope
0580:                                                    .toString())
0581:                                            .append(" crs ")
0582:                                            .append(crs.toWKT()).toString());
0583:                            }
0584:                        } catch (TransformException e) {
0585:                            throw new DataSourceException(
0586:                                    "Unable to create a coverage for this source",
0587:                                    e);
0588:                        } catch (FactoryException e) {
0589:                            throw new DataSourceException(
0590:                                    "Unable to create a coverage for this source",
0591:                                    e);
0592:                        }
0593:                    }
0594:                    if (!requestedOriginalEnvelope.intersects(
0595:                            this .originalEnvelope, true)) {
0596:                        if (LOGGER.isLoggable(Level.WARNING))
0597:                            LOGGER
0598:                                    .warning("The requested envelope does not intersect the envelope of this mosaic, we will return a null coverage.");
0599:                        return null;
0600:                    }
0601:                    intersectionEnvelope = new GeneralEnvelope(
0602:                            requestedOriginalEnvelope);
0603:                    // intersect the requested area with the bounds of this layer
0604:                    intersectionEnvelope.intersect(originalEnvelope);
0605:
0606:                } else {
0607:                    requestedOriginalEnvelope = new GeneralEnvelope(
0608:                            originalEnvelope);
0609:                    intersectionEnvelope = requestedOriginalEnvelope;
0610:
0611:                }
0612:                requestedOriginalEnvelope
0613:                        .setCoordinateReferenceSystem(this .crs);
0614:                intersectionEnvelope.setCoordinateReferenceSystem(this .crs);
0615:                // ok we got something to return, let's load records from the index
0616:                // /////////////////////////////////////////////////////////////////////
0617:                //
0618:                // Prepare the filter for loading th needed layers
0619:                //
0620:                // /////////////////////////////////////////////////////////////////////
0621:                final ReferencedEnvelope intersectionJTSEnvelope = new ReferencedEnvelope(
0622:                        intersectionEnvelope.getMinimum(0),
0623:                        intersectionEnvelope.getMaximum(0),
0624:                        intersectionEnvelope.getMinimum(1),
0625:                        intersectionEnvelope.getMaximum(1), crs);
0626:
0627:                // /////////////////////////////////////////////////////////////////////
0628:                //
0629:                // Load feaures from the index
0630:                // In case there are no features under the requested bbox which is legal
0631:                // in case the mosaic is not a real sqare, we return a fake mosaic.
0632:                //
0633:                // /////////////////////////////////////////////////////////////////////
0634:                if (LOGGER.isLoggable(Level.FINE))
0635:                    LOGGER.fine("loading tile for envelope "
0636:                            + intersectionJTSEnvelope.toString());
0637:                final List features = getFeaturesFromIndex(intersectionJTSEnvelope);
0638:                if (features == null) {
0639:                    return fakeMosaic(requestedOriginalEnvelope, pixelDimension);
0640:                }
0641:                // do we have any feature to load
0642:                final Iterator it = features.iterator();
0643:                if (!it.hasNext())
0644:                    return fakeMosaic(requestedOriginalEnvelope, pixelDimension);
0645:                final int size = features.size();
0646:                if (size > MAX_TILES) {
0647:                    LOGGER
0648:                            .warning(new StringBuffer("We can load at most ")
0649:                                    .append(MAX_TILES)
0650:                                    .append(
0651:                                            " tiles while there were requested ")
0652:                                    .append(features)
0653:                                    .append(
0654:                                            "\nI am going to print out a fake coverage, sorry about it!")
0655:                                    .toString());
0656:                    return fakeMosaic(requestedOriginalEnvelope, pixelDimension);
0657:                }
0658:                if (LOGGER.isLoggable(Level.FINE))
0659:                    LOGGER.fine("We have " + size + " tiles to load");
0660:                try {
0661:                    return loadRequestedTiles(requestedOriginalEnvelope,
0662:                            intersectionEnvelope, transparentColor,
0663:                            outputTransparentColor, intersectionJTSEnvelope,
0664:                            features, it, inputImageThresholdValue,
0665:                            pixelDimension, size, fading);
0666:                } catch (DataSourceException e) {
0667:                    LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
0668:                } catch (TransformException e) {
0669:                    LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
0670:                }
0671:                return null;
0672:
0673:            }
0674:
0675:            /**
0676:             * Builds up a fake mosaic that consists of a transparent image with a red
0677:             * cross.
0678:             * 
0679:             * <p>
0680:             * The purpose of the fake mosaic
0681:             * 
0682:             * @param requestedEnvelope
0683:             *            is the envelope requested by the user.
0684:             * @param dim
0685:             *            indicates the requested dimension for this
0686:             *            {@link GridCoverage2D} in terms of pixels.
0687:             * @param features
0688:             *            is the number of features touching the requsted envelope.
0689:             * @return a {@link GridCoverage2D}.
0690:             */
0691:            private GridCoverage fakeMosaic(GeneralEnvelope requestedEnvelope,
0692:                    Rectangle dim) {
0693:
0694:                return coverageFactory
0695:                        .create(coverageName, PatternDescriptor.create(
0696:                                unavailableImage, new Integer(dim.width),
0697:                                new Integer(dim.height),
0698:                                ImageUtilities.NOCACHE_HINT), requestedEnvelope);
0699:
0700:            }
0701:
0702:            /**
0703:             * This method loads the tiles which overlap the requested
0704:             * {@link GeneralEnvelope} using the provided values for alpha and input
0705:             * ROI.
0706:             * 
0707:             * @param requestedOriginalEnvelope
0708:             * @param intersectionEnvelope
0709:             * @param transparentColor
0710:             * @param outputTransparentColor
0711:             * @param requestedJTSEnvelope
0712:             * @param features
0713:             * @param it
0714:             * @param inputImageThresholdValue
0715:             * @param dim
0716:             * @param numImages
0717:             * @param blend
0718:             * @return
0719:             * @throws DataSourceException
0720:             * @throws TransformException
0721:             */
0722:            private GridCoverage loadRequestedTiles(
0723:                    GeneralEnvelope requestedOriginalEnvelope,
0724:                    GeneralEnvelope intersectionEnvelope,
0725:                    Color transparentColor, Color outputTransparentColor,
0726:                    final Envelope requestedJTSEnvelope, final List features,
0727:                    final Iterator it, double inputImageThresholdValue,
0728:                    Rectangle dim, int numImages, boolean blend)
0729:                    throws DataSourceException, TransformException {
0730:
0731:                try {
0732:                    // if we get here we have something to load
0733:                    // /////////////////////////////////////////////////////////////////////
0734:                    //
0735:                    // prepare the params for executing a mosaic operation.
0736:                    //
0737:                    // /////////////////////////////////////////////////////////////////////
0738:                    final ParameterBlockJAI pbjMosaic = new ParameterBlockJAI(
0739:                            "Mosaic");
0740:                    pbjMosaic.setParameter("mosaicType",
0741:                            MosaicDescriptor.MOSAIC_TYPE_OVERLAY);
0742:
0743:                    // /////////////////////////////////////////////////////////////////////
0744:                    //
0745:                    // compute the requested resolution given the requested envelope and
0746:                    // dimension.
0747:                    //
0748:                    // /////////////////////////////////////////////////////////////////////
0749:                    final ImageReadParam readP = new ImageReadParam();
0750:                    final Integer imageChoice;
0751:                    if (dim != null)
0752:                        imageChoice = setReadParams(readP,
0753:                                requestedOriginalEnvelope, dim);
0754:                    else
0755:                        imageChoice = new Integer(0);
0756:                    if (LOGGER.isLoggable(Level.FINE))
0757:                        LOGGER.fine(new StringBuffer("Loading level ").append(
0758:                                imageChoice.toString()).append(
0759:                                " with subsampling factors ").append(
0760:                                readP.getSourceXSubsampling()).append(" ")
0761:                                .append(readP.getSourceYSubsampling())
0762:                                .toString());
0763:                    // /////////////////////////////////////////////////////////////////////
0764:                    //
0765:                    // Resolution.
0766:                    //
0767:                    // I am implicitly assuming that all the images have the same
0768:                    // resolution. In principle this is not required but in practice
0769:                    // having different resolution would surely bring to having small
0770:                    // displacements in the final mosaic which we do not wnat to happen.
0771:                    //
0772:                    // /////////////////////////////////////////////////////////////////////
0773:                    final double[] res;
0774:                    if (imageChoice.intValue() == 0) {
0775:                        res = new double[highestRes.length];
0776:                        res[0] = highestRes[0];
0777:                        res[1] = highestRes[1];
0778:                    } else {
0779:                        final double temp[] = overViewResolutions[imageChoice
0780:                                .intValue() - 1];
0781:                        res = new double[temp.length];
0782:                        res[0] = temp[0];
0783:                        res[1] = temp[1];
0784:
0785:                    }
0786:                    // adjusting the resolution for the source subsampling
0787:                    res[0] *= readP.getSourceXSubsampling();
0788:                    res[1] *= readP.getSourceYSubsampling();
0789:                    // /////////////////////////////////////////////////////////////////////
0790:                    //
0791:                    // Envelope of the loaded dataset and upper left corner of this
0792:                    // envelope.
0793:                    //
0794:                    // Ths envelope corresponds to the union of the envelopes of all the
0795:                    // tiles that intersect the area that was request by the user. It is
0796:                    // crucial to understand that this geographic area can be, and it
0797:                    // usually is, bigger then the requested one. This involves doing a
0798:                    // crop operation at the end of the mosaic creation.
0799:                    //
0800:                    // /////////////////////////////////////////////////////////////////////
0801:                    final Envelope loadedDataSetBound = getLoadedDataSetBoud(features);
0802:                    final Point2D ULC = new Point2D.Double(loadedDataSetBound
0803:                            .getMinX(), loadedDataSetBound.getMaxY());
0804:
0805:                    // /////////////////////////////////////////////////////////////////////
0806:                    //
0807:                    // CORE LOOP
0808:                    //
0809:                    // Loop over the single features and load the images which
0810:                    // intersect the requested envelope. Once all of them have been
0811:                    // loaded, next step is to create the mosaic and then
0812:                    // crop it as requested.
0813:                    //
0814:                    // /////////////////////////////////////////////////////////////////////
0815:                    final File tempFile = new File(this .sourceURL.getFile());
0816:                    final String parentLocation = tempFile.getParent();
0817:                    Feature feature;
0818:                    String location;
0819:                    Envelope bound;
0820:                    PlanarImage loadedImage;
0821:                    File imageFile;
0822:                    final ROI[] rois = new ROI[numImages];
0823:                    final PlanarImage[] alphaChannels = new PlanarImage[numImages];
0824:                    final Area finalLayout = new Area();
0825:
0826:                    // reusable parameters
0827:                    boolean alphaIn = false;
0828:                    boolean doTransparentColor = false;
0829:                    boolean doInputImageThreshold = false;
0830:                    int[] alphaIndex = null;
0831:                    int i = 0;
0832:                    Boolean readMetadata = Boolean.FALSE;
0833:                    Boolean readThumbnails = Boolean.FALSE;
0834:                    Boolean verifyInput = Boolean.FALSE;
0835:                    ParameterBlock pbjImageRead;
0836:                    ColorModel model;
0837:
0838:                    do {
0839:                        // /////////////////////////////////////////////////////////////////////
0840:                        //
0841:                        // Get location and envelope of the image to load.
0842:                        //
0843:                        // /////////////////////////////////////////////////////////////////////
0844:                        feature = (Feature) it.next();
0845:                        location = (String) feature.getAttribute("location");
0846:                        bound = feature.getBounds();
0847:
0848:                        // /////////////////////////////////////////////////////////////////////
0849:                        //
0850:                        // lLad a tile from disk as requested.
0851:                        //
0852:                        // /////////////////////////////////////////////////////////////////////
0853:                        if (LOGGER.isLoggable(Level.FINE))
0854:                            LOGGER.fine("About to read image number " + i);
0855:                        imageFile = new File(new StringBuffer(parentLocation)
0856:                                .append(File.separatorChar).append(location)
0857:                                .toString());
0858:                        pbjImageRead = new ParameterBlock();
0859:                        pbjImageRead.add(ImageIO
0860:                                .createImageInputStream(imageFile));
0861:                        pbjImageRead.add(imageChoice);
0862:                        pbjImageRead.add(readMetadata);
0863:                        pbjImageRead.add(readThumbnails);
0864:                        pbjImageRead.add(verifyInput);
0865:                        pbjImageRead.add(null);
0866:                        pbjImageRead.add(null);
0867:                        pbjImageRead.add(readP);
0868:                        pbjImageRead.add(null);
0869:                        loadedImage = JAI.create("ImageRead", pbjImageRead);
0870:                        if (LOGGER.isLoggable(Level.FINE))
0871:                            LOGGER.fine("Just read image number " + i);
0872:
0873:                        // /////////////////////////////////////////////////////////////
0874:                        //
0875:                        // Input alpha, ROI and transparent color management.
0876:                        //
0877:                        // Once I get the first image Ican acquire all the information I
0878:                        // need in order to decide which actions to while and after
0879:                        // loading the images.
0880:                        //
0881:                        // Specifically, I have to check if the loaded image have
0882:                        // transparency, because if we do a ROI and/or we have a
0883:                        // transparent color to set we have to remove it.
0884:                        //
0885:                        // /////////////////////////////////////////////////////////////
0886:                        if (i == 0) {
0887:                            // //
0888:                            //
0889:                            // We check here if the images have an alpha channel or some
0890:                            // other sort of transparency. In case we have transparency
0891:                            // I also save the index of the transparent channel.
0892:                            //
0893:                            // //
0894:                            model = loadedImage.getColorModel();
0895:                            alphaIn = model.hasAlpha();
0896:                            if (alphaIn)
0897:                                alphaIndex = new int[] { model
0898:                                        .getNumComponents() - 1 };
0899:
0900:                            // //
0901:                            //
0902:                            // ROI has to be computed depending on the value of the
0903:                            // input threshold and on the data type of the images.
0904:                            //
0905:                            // If I request a threshod of 0 on a byte image, I can skip
0906:                            // doing the ROI!
0907:                            //
0908:                            // //
0909:                            doInputImageThreshold = checkIfThresholdIsNeeded(
0910:                                    loadedImage, inputImageThresholdValue);
0911:
0912:                            // //
0913:                            //
0914:                            // Checking if we have to do something against the final
0915:                            // transparent color.
0916:                            //
0917:                            // If we have a valid transparent color we have to remove
0918:                            // the input alpha information.
0919:                            //
0920:                            // However a possible optimization is to check for index
0921:                            // color model images with transparency where the
0922:                            // transparent color is the same requested here and no ROIs
0923:                            // requested.
0924:                            //
0925:                            // //
0926:                            if (transparentColor != null) {
0927:                                // paranoiac check on the provided transparent color
0928:                                transparentColor = new Color(transparentColor
0929:                                        .getRed(), transparentColor.getGreen(),
0930:                                        transparentColor.getBlue());
0931:                                doTransparentColor = true;
0932:                                //
0933:                                // If the images use an IndexColorModel Bitamsk where
0934:                                // the transparent color is the same that was requested,
0935:                                // the optimization is to avoid removing the alpha
0936:                                // information just to readd it at the end. We can
0937:                                // simply go with what we have from the input.
0938:                                //
0939:                                // However, we have to take into account that no action
0940:                                // has to be take if a ROI is requested on the input
0941:                                // images since that would imply doing an RGB
0942:                                // conversion.
0943:                                //
0944:                                //
0945:                                if (model instanceof  IndexColorModel
0946:                                        && alphaIn
0947:                                        && model.getTransparency() == Transparency.BITMASK) {
0948:                                    final IndexColorModel icm = (IndexColorModel) model;
0949:                                    final int transparentPixel = icm
0950:                                            .getTransparentPixel();
0951:                                    if (transparentPixel != -1) {
0952:                                        final int oldTransparentColor = icm
0953:                                                .getRGB(transparentPixel);
0954:                                        if (oldTransparentColor == transparentColor
0955:                                                .getRGB()) {
0956:                                            doTransparentColor = false;
0957:                                        }
0958:
0959:                                    }
0960:
0961:                                }
0962:
0963:                            }
0964:
0965:                        }
0966:
0967:                        // /////////////////////////////////////////////////////////////////////
0968:                        //
0969:                        // add to the mosaic collection
0970:                        //
0971:                        // /////////////////////////////////////////////////////////////////////
0972:                        if (LOGGER.isLoggable(Level.FINE))
0973:                            LOGGER.fine("Adding to mosaic image number " + i);
0974:                        addToMosaic(pbjMosaic, bound, ULC, res, loadedImage,
0975:                                doInputImageThreshold, rois, i,
0976:                                inputImageThresholdValue, alphaIn, alphaIndex,
0977:                                alphaChannels, finalLayout, imageFile,
0978:                                doTransparentColor, transparentColor);
0979:
0980:                        i++;
0981:                    } while (i < numImages);
0982:
0983:                    // /////////////////////////////////////////////////////////////////////
0984:                    //
0985:                    // Prepare the last parameters for the mosaic.
0986:                    //
0987:                    // First of all we set the input threshold accordingly to the input
0988:                    // image data type. I find the default value (which is 0) very bad
0989:                    // for data type other than byte and ushort. With float and double
0990:                    // it can cut off a large par of fthe dynamic.
0991:                    //
0992:                    // Second step is the the management of the input threshold that is
0993:                    // converted into a roi because the way we want to manage such
0994:                    // threshold is by applying it on the intensitiy of the input image.
0995:                    // Note that this ROI has to be mutually exclusive with the alpha
0996:                    // management due to the rules of the JAI Mosaic Operation which
0997:                    // ignore the ROIs in case an alpha information is provided for the
0998:                    // input images.
0999:                    //
1000:                    // Third step is the management of the alpha information which can
1001:                    // be the result of a masking operation upong the request for a
1002:                    // transparent color or the result of input images with internal
1003:                    // transparency.
1004:                    //
1005:                    // Fourth step is the blending for having nice Fading effect at
1006:                    // overlapping regions.
1007:                    //
1008:                    // /////////////////////////////////////////////////////////////////////
1009:                    double th = getThreshold(loadedImage.getSampleModel()
1010:                            .getDataType());
1011:                    pbjMosaic.setParameter("sourceThreshold",
1012:                            new double[][] { { th } });
1013:                    if (doInputImageThreshold) {
1014:                        // //
1015:                        //
1016:                        // Set the ROI parameter in case it was requested by setting a
1017:                        // threshold.
1018:                        // 
1019:                        // //
1020:                        pbjMosaic.setParameter("sourceROI", rois);
1021:
1022:                    } else if (alphaIn || doTransparentColor) {
1023:                        // //
1024:                        //
1025:                        // In case the input images have transparency information this
1026:                        // way we can handle it.
1027:                        //
1028:                        // //
1029:                        pbjMosaic.setParameter("sourceAlpha", alphaChannels);
1030:
1031:                    }
1032:                    // //
1033:                    //
1034:                    // It might important to set the mosaic tpe to blend otherwise
1035:                    // sometimes strange results jump in.
1036:                    // 
1037:                    // //
1038:                    if (blend) {
1039:                        pbjMosaic.setParameter("mosaicType",
1040:                                MosaicDescriptor.MOSAIC_TYPE_BLEND);
1041:
1042:                    }
1043:
1044:                    // /////////////////////////////////////////////////////////////////////
1045:                    //
1046:                    // Create the mosaic image by doing a crop if necessary and also
1047:                    // managing the transparent color if applicablw. Be aware that
1048:                    // management of the transparent color involves removing
1049:                    // transparency information from the input images.
1050:                    // 
1051:                    // /////////////////////////////////////////////////////////////////////
1052:                    return prepareMosaic(location, requestedOriginalEnvelope,
1053:                            intersectionEnvelope, res, loadedDataSetBound,
1054:                            pbjMosaic, finalLayout, outputTransparentColor);
1055:                } catch (IOException e) {
1056:                    throw new DataSourceException(
1057:                            "Unable to create this mosaic", e);
1058:                }
1059:            }
1060:
1061:            /**
1062:             * ROI has to be computed depending on the value of the input threshold and
1063:             * on the data type of the images.
1064:             * 
1065:             * If I request a threshod of 0 on a byte image, I can skip doing the ROI!
1066:             * 
1067:             * @param loadedImage
1068:             *            to check before applying a threshold.
1069:             * @param thresholdValue
1070:             *            is the value that is suggested to be used for the threshold.
1071:             * @return true in case the threshold is to be performed, false otherwise.
1072:             */
1073:            private boolean checkIfThresholdIsNeeded(PlanarImage loadedImage,
1074:                    double thresholdValue) {
1075:                if (Double.isNaN(thresholdValue)
1076:                        || Double.isInfinite(thresholdValue))
1077:                    return false;
1078:                switch (loadedImage.getSampleModel().getDataType()) {
1079:                case DataBuffer.TYPE_BYTE:
1080:                    int bTh = (int) thresholdValue;
1081:                    if (bTh <= 0 || bTh >= 255)
1082:                        return false;
1083:                case DataBuffer.TYPE_USHORT:
1084:                    int usTh = (int) thresholdValue;
1085:                    if (usTh <= 0 || usTh >= 65535)
1086:                        return false;
1087:                case DataBuffer.TYPE_SHORT:
1088:                    int sTh = (int) thresholdValue;
1089:                    if (sTh <= Short.MIN_VALUE || sTh >= Short.MAX_VALUE)
1090:                        return false;
1091:                case DataBuffer.TYPE_INT:
1092:                    int iTh = (int) thresholdValue;
1093:                    if (iTh <= Integer.MIN_VALUE || iTh >= Integer.MAX_VALUE)
1094:                        return false;
1095:                case DataBuffer.TYPE_FLOAT:
1096:                    float fTh = (float) thresholdValue;
1097:                    if (fTh <= -Float.MAX_VALUE || fTh >= Float.MAX_VALUE
1098:                            || Float.isInfinite(fTh) || Float.isNaN(fTh))
1099:                        return false;
1100:                case DataBuffer.TYPE_DOUBLE:
1101:                    double dTh = (double) thresholdValue;
1102:                    if (dTh <= -Double.MAX_VALUE || dTh >= Double.MAX_VALUE
1103:                            || Double.isInfinite(dTh) || Double.isNaN(dTh))
1104:                        return false;
1105:
1106:                }
1107:                return true;
1108:            }
1109:
1110:            /**
1111:             * Returns a suitable threshold depending on the {@link DataBuffer} type.
1112:             * 
1113:             * <p>
1114:             * Remember that the threshold works with >=.
1115:             * 
1116:             * @param dataType
1117:             *            to create a low threshold for.
1118:             * @return a minimum threshold value suitable for this data type.
1119:             */
1120:            private double getThreshold(int dataType) {
1121:                switch (dataType) {
1122:                case DataBuffer.TYPE_BYTE:
1123:                case DataBuffer.TYPE_USHORT:
1124:                    // XXX change to zero when bug fixed
1125:                    return 1.0;
1126:                case DataBuffer.TYPE_INT:
1127:                    return Integer.MIN_VALUE;
1128:                case DataBuffer.TYPE_SHORT:
1129:                    return Short.MIN_VALUE;
1130:                case DataBuffer.TYPE_DOUBLE:
1131:                    return -Double.MAX_VALUE;
1132:                case DataBuffer.TYPE_FLOAT:
1133:                    return -Float.MAX_VALUE;
1134:                }
1135:                return 0;
1136:            }
1137:
1138:            /**
1139:             * Retrieves the ULC of the BBOX composed by all the tiles we need to load.
1140:             * 
1141:             * @param double
1142:             * @return A {@link Point2D} pointing to the ULC of the smalles area made by
1143:             *         mosaicking all the tile that actually intersect the passed
1144:             *         envelope.
1145:             * @throws IOException
1146:             */
1147:            private Envelope getLoadedDataSetBoud(List features)
1148:                    throws IOException {
1149:                // /////////////////////////////////////////////////////////////////////
1150:                //
1151:                // Load feaures and evaluate envelope
1152:                //
1153:                // /////////////////////////////////////////////////////////////////////
1154:                final Envelope loadedULC = new Envelope();
1155:                Iterator it = features.iterator();
1156:                while (it.hasNext()) {
1157:                    loadedULC.expandToInclude(((Feature) it.next())
1158:                            .getDefaultGeometry().getEnvelopeInternal());
1159:                }
1160:                return loadedULC;
1161:
1162:            }
1163:
1164:            /**
1165:             * Retrieves the list of features that intersect the provided evelope
1166:             * loadinf them inside an index in memory where beeded.
1167:             * 
1168:             * @param envelope
1169:             *            Envelope for selectig features that intersect.
1170:             * @return A list of fetaures.
1171:             * @throws IOException
1172:             *             In case loading the needed features failes.
1173:             */
1174:            private List getFeaturesFromIndex(final Envelope envelope)
1175:                    throws IOException {
1176:                List features = null;
1177:                Object o;
1178:                synchronized (index) {
1179:                    if (LOGGER.isLoggable(Level.FINE))
1180:                        LOGGER.fine("Trying to  use the index...");
1181:                    o = index.get();
1182:                    if (o != null) {
1183:                        if (LOGGER.isLoggable(Level.FINE))
1184:                            LOGGER.fine("Index does not need to be created...");
1185:
1186:                    } else {
1187:                        if (LOGGER.isLoggable(Level.FINE))
1188:                            LOGGER.fine("Index needa to be recreated...");
1189:                        o = new MemorySpatialIndex(featureSource.getFeatures());
1190:                    }
1191:                    if (LOGGER.isLoggable(Level.FINE))
1192:                        LOGGER.fine("Index Loaded");
1193:                }
1194:                features = ((MemorySpatialIndex) o).findFeatures(envelope);
1195:                return features;
1196:            }
1197:
1198:            /**
1199:             * Once we reach this method it means that we have loaded all the images
1200:             * which were intersecting the requested nevelope. Next step is to create
1201:             * the final mosaic image and cropping it to the exact requested envelope.
1202:             * 
1203:             * @param location
1204:             * 
1205:             * @param envelope
1206:             * @param requestedEnvelope
1207:             * @param intersectionEnvelope
1208:             * @param res
1209:             * @param loadedTilesEnvelope
1210:             * @param pbjMosaic
1211:             * @param transparentColor
1212:             * @param doAlpha
1213:             * @param doTransparentColor
1214:             * @param finalLayout
1215:             * @param outputTransparentColor
1216:             * @param singleImageROI
1217:             * @return A {@link GridCoverage}, wewll actually a {@link GridCoverage2D}.
1218:             * @throws IllegalArgumentException
1219:             * @throws FactoryRegistryException
1220:             * @throws DataSourceException
1221:             */
1222:            private GridCoverage prepareMosaic(String location,
1223:                    GeneralEnvelope requestedOriginalEnvelope,
1224:                    GeneralEnvelope intersectionEnvelope, double[] res,
1225:                    final Envelope loadedTilesEnvelope,
1226:                    ParameterBlockJAI pbjMosaic, Area finalLayout,
1227:                    Color outputTransparentColor) throws DataSourceException {
1228:                GeneralEnvelope finalenvelope = null;
1229:                PlanarImage preparationImage;
1230:                Rectangle loadedTilePixelsBound = finalLayout.getBounds();
1231:                if (LOGGER.isLoggable(Level.FINE))
1232:                    LOGGER.fine(new StringBuffer("Loaded bbox ").append(
1233:                            loadedTilesEnvelope.toString()).append(
1234:                            " while requested bbox ").append(
1235:                            requestedOriginalEnvelope.toString()).toString());
1236:
1237:                // /////////////////////////////////////////////////////////////////////
1238:                //
1239:                // Check if we need to do a crop on the loaded tiles or not. Keep into
1240:                // account that most part of the time the loaded tiles will be go
1241:                // beyoind the requested area, hence there is a need for cropping them
1242:                // while mosaicking them.
1243:                //
1244:                // /////////////////////////////////////////////////////////////////////
1245:                final GeneralEnvelope loadedTilesBoundEnv = new GeneralEnvelope(
1246:                        new double[] { loadedTilesEnvelope.getMinX(),
1247:                                loadedTilesEnvelope.getMinY() }, new double[] {
1248:                                loadedTilesEnvelope.getMaxX(),
1249:                                loadedTilesEnvelope.getMaxY() });
1250:                loadedTilesBoundEnv.setCoordinateReferenceSystem(crs);
1251:                final double loadedTilesEnvelopeDim0 = loadedTilesBoundEnv
1252:                        .getLength(0);
1253:                final double loadedTilesEnvelopeDim1 = loadedTilesBoundEnv
1254:                        .getLength(1);
1255:                if (!intersectionEnvelope.equals(loadedTilesBoundEnv, Math.min(
1256:                        (loadedTilesEnvelopeDim0 / loadedTilePixelsBound
1257:                                .getWidth()) / 2.0,
1258:                        (loadedTilesEnvelopeDim1 / loadedTilePixelsBound
1259:                                .getHeight()) / 2.0), false)) {
1260:
1261:                    // /////////////////////////////////////////////////////////////////////
1262:                    //
1263:                    // CROP the mosaic image to the requested BBOX
1264:                    //
1265:                    // /////////////////////////////////////////////////////////////////////
1266:                    // intersect them
1267:                    final GeneralEnvelope intersection = new GeneralEnvelope(
1268:                            intersectionEnvelope);
1269:                    intersection.intersect(loadedTilesBoundEnv);
1270:
1271:                    // get the transform for going from world to grid
1272:                    try {
1273:                        final GridToEnvelopeMapper gridToEnvelopeMapper = new GridToEnvelopeMapper(
1274:                                new GeneralGridRange(loadedTilePixelsBound),
1275:                                loadedTilesBoundEnv);
1276:                        gridToEnvelopeMapper
1277:                                .setGridType(PixelInCell.CELL_CORNER);
1278:                        final MathTransform transform = gridToEnvelopeMapper
1279:                                .createTransform().inverse();
1280:                        final GeneralGridRange finalRange = new GeneralGridRange(
1281:                                CRSUtilities.transform(transform, intersection));
1282:                        // CROP
1283:                        finalLayout
1284:                                .intersect(new Area(finalRange.toRectangle()));
1285:                        Rectangle tempRect = finalLayout.getBounds();
1286:
1287:                        preparationImage = JAI
1288:                                .create("Mosaic", pbjMosaic,
1289:                                        new RenderingHints(
1290:                                                JAI.KEY_IMAGE_LAYOUT,
1291:                                                new ImageLayout(tempRect.x,
1292:                                                        tempRect.y,
1293:                                                        tempRect.width,
1294:                                                        tempRect.height)));
1295:
1296:                        finalenvelope = intersection;
1297:
1298:                    } catch (MismatchedDimensionException e) {
1299:                        throw new DataSourceException(
1300:                                "Problem when creating this mosaic.", e);
1301:                    } catch (NoninvertibleTransformException e) {
1302:                        throw new DataSourceException(
1303:                                "Problem when creating this mosaic.", e);
1304:                    } catch (TransformException e) {
1305:                        throw new DataSourceException(
1306:                                "Problem when creating this mosaic.", e);
1307:                    }
1308:
1309:                } else {
1310:                    preparationImage = JAI.create("Mosaic", pbjMosaic);
1311:                    finalenvelope = new GeneralEnvelope(intersectionEnvelope);
1312:                }
1313:                if (LOGGER.isLoggable(Level.FINE))
1314:                    LOGGER.fine(new StringBuffer("Mosaic created ").toString());
1315:
1316:                //
1317:                // ///////////////////////////////////////////////////////////////////
1318:                //
1319:                // FINAL ALPHA
1320:                //
1321:                //
1322:                // ///////////////////////////////////////////////////////////////////
1323:                if (outputTransparentColor != null) {
1324:                    if (LOGGER.isLoggable(Level.FINE))
1325:                        LOGGER.fine(new StringBuffer("Support for alpha")
1326:                                .toString());
1327:                    //
1328:                    // ///////////////////////////////////////////////////////////////////
1329:                    //
1330:                    // If requested I can perform the ROI operation on the prepared ROI
1331:                    // image for building up the alpha band
1332:                    //
1333:                    //
1334:                    // ///////////////////////////////////////////////////////////////////
1335:                    ImageWorker w = new ImageWorker(preparationImage);
1336:                    if (preparationImage.getColorModel() instanceof  IndexColorModel) {
1337:                        preparationImage = w.maskIndexColorModelByte(
1338:                                outputTransparentColor).getPlanarImage();
1339:                    } else
1340:                        preparationImage = w.maskComponentColorModelByte(
1341:                                outputTransparentColor).getPlanarImage();
1342:
1343:                    // ///////////////////////////////////////////////////////////////////
1344:                    //
1345:                    // create the coverage
1346:                    //
1347:                    //
1348:                    // ///////////////////////////////////////////////////////////////////
1349:                    return coverageFactory.create(coverageName,
1350:                            preparationImage, finalenvelope);
1351:                }
1352:                // ///////////////////////////////////////////////////////////////////
1353:                //		
1354:                // create the coverage
1355:                //		
1356:                // ///////////////////////////////////////////////////////////////////
1357:                return coverageFactory.create(coverageName, preparationImage,
1358:                        finalenvelope);
1359:
1360:            }
1361:
1362:            /**
1363:             * Adding an image which intersect the requested envelope to the final
1364:             * moisaic. This operation means computing the translation factor keeping
1365:             * into account the resolution of the actual image, the envelope of the
1366:             * loaded dataset and the envelope of this image.
1367:             * 
1368:             * @param pbjMosaic
1369:             * @param bound
1370:             *            Lon-Lat bounds of the loaded image
1371:             * @param ulc
1372:             * @param res
1373:             * @param loadedImage
1374:             * @param removeAlpha
1375:             * @param rois
1376:             * @param i
1377:             * @param inputImageThresholdValue
1378:             * @param alphaChannels
1379:             * @param alphaIndex
1380:             * @param alphaIn
1381:             * @param finalLayout
1382:             * @param imageFile
1383:             * @param transparentColor
1384:             * @param doTransparentColor
1385:             * @throws FileNotFoundException
1386:             * @throws IOException
1387:             */
1388:            private void addToMosaic(ParameterBlockJAI pbjMosaic,
1389:                    Envelope bound, Point2D ulc, double[] res,
1390:                    PlanarImage loadedImage, boolean doInputImageThreshold,
1391:                    ROI[] rois, int i, double inputImageThresholdValue,
1392:                    boolean alphaIn, int[] alphaIndex,
1393:                    PlanarImage[] alphaChannels, Area finalLayout,
1394:                    File imageFile, boolean doTransparentColor,
1395:                    Color transparentColor) {
1396:                // /////////////////////////////////////////////////////////////////////
1397:                //
1398:                // Computing TRANSLATION AND SCALING FACTORS
1399:                //
1400:                // Using the spatial resolution we compute the translation factors for
1401:                // positioning the actual image correctly in final mosaic.
1402:                //
1403:                // /////////////////////////////////////////////////////////////////////
1404:                PlanarImage readyToMosaicImage = scaleAndTranslate(bound, ulc,
1405:                        res, loadedImage);
1406:
1407:                // ///////////////////////////////////////////////////////////////////
1408:                //
1409:                // INDEX COLOR MODEL EXPANSION
1410:                //
1411:                // Take into account the need for an expansions of the original color
1412:                // model.
1413:                //
1414:                // If the original color model is an index color model an expansion
1415:                // might be requested in case the differemt palettes are not all the
1416:                // same. In this case the mosaic operator from JAI would provide wrong
1417:                // results since it would take the first palette and use that one for
1418:                // all the other images.
1419:                //
1420:                // There is a special case to take into account here. In case the input
1421:                // images use an IndexColorModel t might happen that the transparent
1422:                // color is present in some of them while it is not present in some
1423:                // others. This case is the case where for sure a color expansion is
1424:                // needed. However we have to take into account that during the masking
1425:                // phase the images where the requested transparent color was present
1426:                // willl have 4 bands, the other 3. If we want the mosaic to work we
1427:                // have to add na extra band to the latter type of images for providing
1428:                // alpha information to them.
1429:                //
1430:                //
1431:                // ///////////////////////////////////////////////////////////////////
1432:                if (expandMe
1433:                        && readyToMosaicImage.getColorModel() instanceof  IndexColorModel) {
1434:                    readyToMosaicImage = new ImageWorker(readyToMosaicImage)
1435:                            .forceComponentColorModel().getPlanarImage();
1436:                }
1437:
1438:                // ///////////////////////////////////////////////////////////////////
1439:                //
1440:                // TRANSPARENT COLOR MANAGEMENT
1441:                //
1442:                //
1443:                // ///////////////////////////////////////////////////////////////////
1444:                if (doTransparentColor) {
1445:                    if (LOGGER.isLoggable(Level.FINE))
1446:                        LOGGER.fine(new StringBuffer(
1447:                                "Support for alpha on input image number " + i)
1448:                                .toString());
1449:                    // /////////////////////////////////////////////////////////////////////
1450:                    //
1451:                    // If requested I can perform the ROI operation on the prepared ROI
1452:                    // image for building up the alpha band
1453:                    //
1454:                    // /////////////////////////////////////////////////////////////////////
1455:                    ImageWorker w = new ImageWorker(readyToMosaicImage);
1456:                    if (readyToMosaicImage.getColorModel() instanceof  IndexColorModel) {
1457:                        readyToMosaicImage = w.maskIndexColorModelByte(
1458:                                transparentColor).getPlanarImage();
1459:                    } else
1460:                        readyToMosaicImage = w.maskComponentColorModelByte(
1461:                                transparentColor).getPlanarImage();
1462:                    alphaIndex = new int[] { readyToMosaicImage.getColorModel()
1463:                            .getNumComponents() - 1 };
1464:
1465:                }
1466:                // ///////////////////////////////////////////////////////////////////
1467:                //
1468:                // ROI
1469:                //
1470:                // ///////////////////////////////////////////////////////////////////
1471:                if (doInputImageThreshold) {
1472:                    ImageWorker w = new ImageWorker(readyToMosaicImage);
1473:                    w.tileCacheEnabled(false).intensity().binarize(
1474:                            inputImageThresholdValue);
1475:                    rois[i] = w.getImageAsROI();
1476:
1477:                } else if (alphaIn || doTransparentColor) {
1478:                    ImageWorker w = new ImageWorker(readyToMosaicImage);
1479:                    // /////////////////////////////////////////////////////////////////////
1480:                    //
1481:                    // ALPHA in INPUT
1482:                    //
1483:                    // I have to select the alpha band and provide it to the final
1484:                    // mosaic operator. I have to force going to ComponentColorModel in
1485:                    // case the image is indexed.
1486:                    //
1487:                    // /////////////////////////////////////////////////////////////////////
1488:                    if (readyToMosaicImage.getColorModel() instanceof  IndexColorModel) {
1489:                        alphaChannels[i] = w.forceComponentColorModel()
1490:                                .retainLastBand().getPlanarImage();
1491:                    }
1492:
1493:                    else
1494:                        alphaChannels[i] = w.retainBands(alphaIndex)
1495:                                .getPlanarImage();
1496:
1497:                }
1498:
1499:                // /////////////////////////////////////////////////////////////////////
1500:                //
1501:                // ADD TO MOSAIC
1502:                //
1503:                // /////////////////////////////////////////////////////////////////////
1504:                pbjMosaic.addSource(readyToMosaicImage);
1505:                finalLayout.add(new Area(readyToMosaicImage.getBounds()));
1506:
1507:            }
1508:
1509:            /**
1510:             * Computing TRANSLATION AND SCALING FACTORS
1511:             * 
1512:             * Using the spatial resolution we compute the translation factors for
1513:             * positioning the actual image correctly in final mosaic.
1514:             * 
1515:             * @param bound
1516:             * @param ulc
1517:             * @param res
1518:             * @param image
1519:             * @return
1520:             */
1521:            private PlanarImage scaleAndTranslate(Envelope bound, Point2D ulc,
1522:                    double[] res, PlanarImage image) {
1523:                // evaluate translation and scaling factors.
1524:                double resX = (bound.getMaxX() - bound.getMinX())
1525:                        / image.getWidth();
1526:                double resY = (bound.getMaxY() - bound.getMinY())
1527:                        / image.getHeight();
1528:                double scaleX = 1.0, scaleY = 1.0;
1529:                double xTrans = 0.0, yTrans = 0.0;
1530:                if (Math.abs((resX - res[0]) / resX) > EPS
1531:                        || Math.abs(resY - res[1]) > EPS) {
1532:                    scaleX = res[0] / resX;
1533:                    scaleY = res[1] / resY;
1534:
1535:                }
1536:                xTrans = (bound.getMinX() - ulc.getX()) / res[0];
1537:                yTrans = (ulc.getY() - bound.getMaxY()) / res[1];
1538:                //
1539:                // Optimising scale and translate.
1540:                //
1541:                // In case the scale factors are very close to 1 we have two
1542:                // optimizarions: if fthe translation factors are close to zero we do
1543:                // thing, otherwise if thery are integers we do a simple translate.
1544:                //
1545:                // In the general case when wew have translation and scaling we do a
1546:                // warp affine which is the most precise operation we can perform.
1547:                //
1548:                // //
1549:                final ParameterBlock pbjAffine = new ParameterBlock();
1550:                if (Math.abs(xTrans - (int) xTrans) < Math.pow(10, -3)
1551:                        && Math.abs(yTrans - (int) yTrans) < Math.pow(10, -3)
1552:                        && Math.abs(scaleX - 1) < Math.pow(10, -6)
1553:                        && Math.abs(scaleY - 1) < Math.pow(10, -6)) {
1554:
1555:                    // return the original image
1556:                    if (Math.abs(xTrans) < Math.pow(10, -3)
1557:                            && Math.abs(yTrans) < Math.pow(10, -3)) {
1558:                        return image;
1559:
1560:                    }
1561:
1562:                    // translation
1563:                    pbjAffine.addSource(image).add(new Float(xTrans)).add(
1564:                            new Float(yTrans)).add(
1565:                            ImageUtilities.NN_INTERPOLATION_HINT
1566:                                    .get(JAI.KEY_INTERPOLATION));
1567:                    // avoid doing the color expansion now since it might not be needed
1568:                    return JAI.create("Translate", pbjAffine,
1569:                            ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
1570:
1571:                }
1572:                // translation and scaling
1573:                pbjAffine.addSource(image).add(
1574:                        new AffineTransform(scaleX, 0, 0, scaleY, xTrans,
1575:                                yTrans)).add(
1576:                        ImageUtilities.NN_INTERPOLATION_HINT
1577:                                .get(JAI.KEY_INTERPOLATION));
1578:                // avoid doing the color expansion now since it might not be needed
1579:                final RenderingHints hints = (RenderingHints) ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL
1580:                        .clone();
1581:                // adding the capability to do a border extension which is great when
1582:                // doing
1583:                hints.add(ImageUtilities.EXTEND_BORDER_BY_COPYING);
1584:                return JAI.create("Affine", pbjAffine, hints);
1585:
1586:            }
1587:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.