001: /*
002: * Geotools2 - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017: package org.geotools.arcsde.gce;
018:
019: import java.awt.Color;
020: import java.awt.Font;
021: import java.awt.Graphics2D;
022: import java.awt.Point;
023: import java.awt.Rectangle;
024: import java.awt.RenderingHints;
025: import java.awt.image.BufferedImage;
026: import java.awt.image.renderable.ParameterBlock;
027: import java.io.File;
028: import java.io.IOException;
029: import java.util.ArrayList;
030: import java.util.HashMap;
031: import java.util.StringTokenizer;
032: import java.util.logging.Level;
033: import java.util.logging.Logger;
034:
035: import javax.media.jai.ImageLayout;
036: import javax.media.jai.JAI;
037:
038: import org.geotools.arcsde.gce.band.ArcSDERasterBandCopier;
039: import org.geotools.arcsde.gce.imageio.ArcSDERasterImageReadParam;
040: import org.geotools.arcsde.gce.imageio.ArcSDERasterReader;
041: import org.geotools.arcsde.gce.imageio.ArcSDERasterReaderSpi;
042: import org.geotools.arcsde.pool.ArcSDEConnectionConfig;
043: import org.geotools.arcsde.pool.ArcSDEConnectionPool;
044: import org.geotools.arcsde.pool.ArcSDEConnectionPoolFactory;
045: import org.geotools.arcsde.pool.ArcSDEPooledConnection;
046: import org.geotools.arcsde.pool.UnavailableArcSDEConnectionException;
047: import org.geotools.coverage.Category;
048: import org.geotools.coverage.GridSampleDimension;
049: import org.geotools.coverage.grid.GeneralGridRange;
050: import org.geotools.coverage.grid.GridCoverage2D;
051: import org.geotools.coverage.grid.GridGeometry2D;
052: import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
053: import org.geotools.coverage.grid.io.AbstractGridFormat;
054: import org.geotools.data.DataSourceException;
055: import org.geotools.factory.Hints;
056: import org.geotools.geometry.DirectPosition2D;
057: import org.geotools.geometry.GeneralEnvelope;
058: import org.geotools.geometry.jts.ReferencedEnvelope;
059: import org.geotools.parameter.Parameter;
060: import org.geotools.referencing.CRS;
061: import org.geotools.referencing.operation.transform.LinearTransform1D;
062: import org.geotools.util.NumberRange;
063: import org.omg.CORBA._PolicyStub;
064: import org.opengis.coverage.grid.Format;
065: import org.opengis.coverage.grid.GridCoverage;
066: import org.opengis.coverage.grid.GridCoverageReader;
067: import org.opengis.parameter.GeneralParameterValue;
068: import org.opengis.referencing.FactoryException;
069: import org.opengis.referencing.crs.CoordinateReferenceSystem;
070: import org.opengis.referencing.operation.MathTransform;
071: import org.opengis.referencing.operation.TransformException;
072: import org.opengis.geometry.BoundingBox;
073: import org.opengis.geometry.DirectPosition;
074: import org.opengis.geometry.Envelope;
075:
076: import com.esri.sde.sdk.client.SeColumnDefinition;
077: import com.esri.sde.sdk.client.SeException;
078: import com.esri.sde.sdk.client.SeExtent;
079: import com.esri.sde.sdk.client.SeQuery;
080: import com.esri.sde.sdk.client.SeRaster;
081: import com.esri.sde.sdk.client.SeRasterAttr;
082: import com.esri.sde.sdk.client.SeRasterBand;
083: import com.esri.sde.sdk.client.SeRasterColumn;
084: import com.esri.sde.sdk.client.SeRow;
085: import com.esri.sde.sdk.client.SeSqlConstruct;
086: import com.esri.sde.sdk.client.SeTable;
087: import com.esri.sde.sdk.pe.PeFactory;
088: import com.esri.sde.sdk.pe.PeProjectedCS;
089: import com.esri.sde.sdk.pe.PeProjectionException;
090:
091: /**
092: * This class can read an ArcSDE Raster datasource and
093: * create a {@link GridCoverage2D} from the data.
094: *
095: * @author Saul Farber (based on ArcGridReader)
096: * @since 2.3.x
097: */
098: public final class ArcSDERasterGridCoverage2DReader extends
099: AbstractGridCoverage2DReader implements GridCoverageReader {
100:
101: private static final boolean DEBUG = false;
102: /** Logger. */
103: private final static Logger LOGGER = org.geotools.util.logging.Logging
104: .getLogger(ArcSDERasterGridCoverage2DReader.class
105: .getPackage().getName());
106:
107: /**
108: * The connectionpool we're using to fetch images from this ArcSDE raster
109: * layer
110: */
111: private ArcSDEConnectionPool connectionPool = null;
112:
113: /** The name of the raster table we're pulling images from in this reader * */
114: private String rasterTable = null;
115:
116: /**
117: * raster column names on this raster. If there's more than one raster
118: * column (is this possible?) then we just use the first one.
119: */
120: private String[] rasterColumns;
121:
122: /** An SDE API object which holds lots of metadata about the raster layer * */
123: private SeRasterAttr rasterAttributes = null;
124:
125: /** The epsg code for the native projection of this raster * */
126: private int epsgCode = -1;
127:
128: /** Array holding information on each level of the pyramid in this raster. * */
129: private ArcSDEPyramid pyramidInfo;
130:
131: /** size in pixels of each tile
132: @deprecated -- use pyramidInfo.getLevel(x).getTileWidth(),getTileHeight()**/
133: private int tileWidth, tileHeight;
134:
135: /** Local copy of the javax.imageio.ImageReader subclass for reading from this ArcSDE Raster Source **/
136: private ArcSDERasterReader imageIOReader;
137: /**
138: * hashmap storing ArcSDERasterBand data-typed objects keyed to their
139: * SeRasterBand.getId()s *
140: */
141: private HashMap bandInfo;
142:
143: private GridSampleDimension[] gridBands;
144:
145: private int bufferedImageType;
146: private Point _levelZeroPRP;
147:
148: /**
149: * Creates a new instance of an ArcSDERasterReader
150: *
151: * @param input
152: * Source object (probably a connection-type URL) for which we
153: * want to build the ArcSDERasterReader
154: * @throws DataSourceException
155: */
156: public ArcSDERasterGridCoverage2DReader(Object input)
157: throws DataSourceException {
158: this (input, null);
159: }
160:
161: /**
162: * Creates a new instance of an ArcSDERasterReader
163: *
164: * @param input
165: * Source object (probably a connection-type URL) for which we
166: * want to build the ArcSDERasterReader
167: * @param hints
168: * Hints to be used by this reader throughout his life.
169: * @throws DataSourceException
170: */
171: public ArcSDERasterGridCoverage2DReader(Object input,
172: final Hints hints) throws DataSourceException {
173:
174: if (hints != null)
175: this .hints.add(hints);
176:
177: setupConnectionPool(input);
178: calculateCoordinateReferenceSystem();
179: pyramidInfo = new ArcSDEPyramid(rasterAttributes, crs);
180: if (_levelZeroPRP != null) {
181: _levelZeroPRP = new Point(_levelZeroPRP.x
182: * pyramidInfo.tileWidth, _levelZeroPRP.y
183: * pyramidInfo.tileHeight);
184: }
185: calculateBandDependentInfo();
186: setupCoverageMetadata();
187: setupImageIOReader();
188:
189: LOGGER.info("ArcSDE raster " + coverageName
190: + " based on table " + rasterTable
191: + " has been configured.");
192: }
193:
194: /**
195: * @see org.opengis.coverage.grid.GridCoverageReader#getFormat()
196: */
197: public Format getFormat() {
198: return new ArcSDERasterFormat();
199: }
200:
201: /**
202: * Reads a {@link GridCoverage2D} possibly matching as close as possible the
203: * resolution computed by using the input params provided by using the
204: * parameters for this {@link #read(GeneralParameterValue[])}.
205: *
206: * <p>
207: * To have an idea about the possible read parameters take a look at
208: * {@link AbstractGridFormat} class and {@link ArcSDERasterFormat} class.
209: *
210: * @param params
211: * an array of {@link GeneralParameterValue} containing the
212: * parameters to control this read process.
213: *
214: * @return a {@link GridCoverage2D}.
215: *
216: * @see AbstractGridFormat
217: * @see ArcSDERasterFormat
218: * @see org.opengis.coverage.grid.GridCoverageReader#read(org.opengis.parameter.GeneralParameterValue[])
219: */
220: public GridCoverage read(GeneralParameterValue[] params)
221: throws IllegalArgumentException, IOException {
222:
223: GeneralEnvelope readEnvelope = null;
224: Rectangle requestedDim = null;
225: if (params != null) {
226: final int length = params.length;
227: Parameter param;
228: String name;
229: for (int i = 0; i < length; i++) {
230: param = (Parameter) params[i];
231: name = param.getDescriptor().getName().getCode();
232: if (name.equals(AbstractGridFormat.READ_GRIDGEOMETRY2D
233: .getName().toString())) {
234: final GridGeometry2D gg = (GridGeometry2D) param
235: .getValue();
236: readEnvelope = new GeneralEnvelope((Envelope) gg
237: .getEnvelope2D());
238: requestedDim = gg.getGridRange2D().getBounds();
239: } else {
240: LOGGER.warning("discarding parameter with name "
241: + name);
242: }
243:
244: }
245: }
246: if (requestedDim == null) {
247: throw new IllegalArgumentException(
248: "You must call ArcSDERasterReader.read() with a GPV[] including a Parameter for READ_GRIDGEOMETRY2D.");
249: }
250: if (readEnvelope == null) {
251: readEnvelope = new GeneralEnvelope(pyramidInfo
252: .getPyramidLevel(pyramidInfo.getNumLevels() - 1)
253: .getEnvelope());
254: }
255: if (LOGGER.isLoggable(Level.FINE))
256: LOGGER.fine("ArcSDE raster image requested: ["
257: + readEnvelope + ", " + requestedDim + "]");
258: return createCoverage(readEnvelope, requestedDim, null);
259: }
260:
261: /**
262: * This method creates the GridCoverage2D from the underlying SDE Raster
263: * Table.
264: *
265: * Note: Because of pyramiding, this coverage will not usually be exactly
266: * the same size/extent as the coverage requested originally by read(). This
267: * is because the ArcSDERasterReader will choose to supply you with best
268: * data available, and let the eventually renderer (generally the
269: * GridCoverageRenderer) downsample/generalize the returned coverage.
270: *
271: *
272: * @param requestedDim
273: * The requested image dimensions in pixels
274: * @param readEnvelope
275: * The request envelope, in CRS units
276: * @param forcedLevel
277: * If this parameter is non-null, it contains the level of the pyramid at which to render
278: * this request. Note that this parameter should be used with care, as forcing a rendering
279: * at too low a level could cause significant memory overload!
280: *
281: *
282: * @return a GridCoverage
283: * @throws IOException
284: *
285: * @throws java.io.IOException
286: */
287: private GridCoverage createCoverage(
288: GeneralEnvelope requestedEnvelope, Rectangle requestedDim,
289: Integer forcedLevel) throws IOException {
290:
291: ArcSDEPooledConnection scon = null;
292: try {
293:
294: if (LOGGER.isLoggable(Level.INFO))
295: LOGGER
296: .info("Creating coverage out of request: imagesize -- "
297: + requestedDim
298: + " envelope -- "
299: + requestedEnvelope);
300:
301: ReferencedEnvelope reqEnv = new ReferencedEnvelope(
302: requestedEnvelope.getMinimum(0), requestedEnvelope
303: .getMaximum(0), requestedEnvelope
304: .getMinimum(1), requestedEnvelope
305: .getMaximum(1), requestedEnvelope
306: .getCoordinateReferenceSystem());
307:
308: final CoordinateReferenceSystem nativeCRS = pyramidInfo
309: .getPyramidLevel(0).getEnvelope()
310: .getCoordinateReferenceSystem();
311: if (!CRS.equalsIgnoreMetadata(nativeCRS, reqEnv
312: .getCoordinateReferenceSystem())) {
313: //we're being reprojected. We'll need to reproject reqEnv into our native coordsys
314: try {
315: ReferencedEnvelope origReqEnv = reqEnv;
316: reqEnv = reqEnv.transform(nativeCRS, true);
317: } catch (FactoryException fe) {
318: //unable to reproject?
319: throw new DataSourceException(
320: "Unable to find a reprojection from requested coordsys to native coordsys for this request",
321: fe);
322: } catch (TransformException te) {
323: throw new DataSourceException(
324: "Unable to perform reprojection from requested coordsys to native coordsys for this request",
325: te);
326: }
327: }
328:
329: int level = 0;
330: if (forcedLevel != null) {
331: level = forcedLevel.intValue();
332: } else {
333: level = pyramidInfo.pickOptimalRasterLevel(reqEnv,
334: requestedDim);
335: }
336:
337: ArcSDEPyramidLevel optimalLevel = pyramidInfo
338: .getPyramidLevel(level);
339:
340: BufferedImage outputImage = null;
341: ReferencedEnvelope outputImageEnvelope = null;
342:
343: if (!optimalLevel.getEnvelope().intersects(
344: (BoundingBox) reqEnv)) {
345: //this is a blank raster. I guess we should create a completely blank image with the
346: //correct size and return that. Transparency?
347: outputImage = createInitialBufferedImage(
348: requestedDim.width, requestedDim.height);
349: outputImageEnvelope = new ReferencedEnvelope(reqEnv);
350:
351: } else {
352: //ok, there's actually something to render. Render it.
353: RasterQueryInfo rasterGridInfo = pyramidInfo
354: .fitExtentToRasterPixelGrid(reqEnv, level);
355:
356: scon = connectionPool.getConnection();
357:
358: ArcSDERasterImageReadParam rParam = new ArcSDERasterImageReadParam();
359: rParam.setConnection(scon);
360:
361: outputImage = createInitialBufferedImage(
362: rasterGridInfo.image.width,
363: rasterGridInfo.image.height);
364: rParam.setDestination(outputImage);
365:
366: final int minImageX = Math.max(rasterGridInfo.image.x,
367: 0);
368: final int maxImageX = Math.min(rasterGridInfo.image.x
369: + rasterGridInfo.image.width, pyramidInfo
370: .getPyramidLevel(level).size.width);
371: int minImageY = Math.max(rasterGridInfo.image.y, 0);
372: int maxImageY = Math.min(rasterGridInfo.image.y
373: + rasterGridInfo.image.height, pyramidInfo
374: .getPyramidLevel(level).size.height);
375:
376: Rectangle sourceRegion = new Rectangle(minImageX,
377: minImageY, maxImageX - minImageX, maxImageY
378: - minImageY);
379: //check for inaccessible negative-indexed level-zero tiles. Shift to level 1 if necessary.
380: if (level == 0 && _levelZeroPRP != null) {
381: if ((maxImageY > _levelZeroPRP.y && minImageY < _levelZeroPRP.y)
382: || (maxImageX > _levelZeroPRP.x && minImageX < _levelZeroPRP.x)) {
383: LOGGER
384: .warning("Using pyramid level 1 to render this request, as the data is unavailable at a negatively indexed tile.");
385: return createCoverage(requestedEnvelope,
386: requestedDim, new Integer(1));
387: } else if (maxImageY > _levelZeroPRP.y
388: && maxImageX > _levelZeroPRP.x) {
389: // we're on the south side of the PRP...need to shift everything up
390: sourceRegion.translate(_levelZeroPRP.x * -1,
391: _levelZeroPRP.y * -1);
392: } else {
393: // all the data we want is negatively indexed on one axis or another. Since
394: // we can't get at it, we'll have to shift up to level 1;
395: LOGGER
396: .warning("Using pyramid level 1 to render this request, as the data is unavailable at a negatively indexed tile.");
397: return createCoverage(requestedEnvelope,
398: requestedDim, new Integer(1));
399: }
400: }
401:
402: if (LOGGER.isLoggable(Level.FINE))
403: LOGGER
404: .fine("Expanded request to cover source region ["
405: + sourceRegion
406: + "] in level "
407: + level
408: + ". Spatial extent of this source region is "
409: + rasterGridInfo.envelope);
410:
411: rParam.setSourceRegion(sourceRegion);
412:
413: if (rasterGridInfo.image.x < 0
414: || rasterGridInfo.image.y < 0) {
415: Point destOffset = new Point(0, 0);
416: if (rasterGridInfo.image.x < 0)
417: destOffset.x = rasterGridInfo.image.x * -1;
418: if (rasterGridInfo.image.y < 0)
419: destOffset.y = rasterGridInfo.image.y * -1;
420: rParam.setDestination(outputImage.getSubimage(
421: destOffset.x, destOffset.y, outputImage
422: .getWidth()
423: - destOffset.x, outputImage
424: .getHeight()
425: - destOffset.y));
426: if (LOGGER.isLoggable(Level.FINER))
427: LOGGER.finer("source region is offset by "
428: + destOffset + " into the "
429: + outputImage.getWidth() + "x"
430: + outputImage.getHeight()
431: + " output image.");
432: }
433:
434: outputImageEnvelope = new ReferencedEnvelope(
435: rasterGridInfo.envelope);
436:
437: // not quite sure how, but I figure one could request a subset of all available bands...
438: // for now we'll just grab the first three, and assume they're RGB in order.
439: SeRasterBand[] seBands = rasterAttributes.getBands();
440: int[] bands = new int[Math.min(3, seBands.length)];
441: HashMap bandMapper = new HashMap();
442: for (int i = 0; i < bands.length; i++) {
443: bands[i] = i + 1;
444: bandMapper.put(new Integer((int) seBands[i].getId()
445: .longValue()), new Integer(i));
446: }
447: rParam.setSourceBands(bands);
448: rParam.setBandMapper(bandMapper);
449:
450: //if we don't provide an ImageLayout to the JAI ImageRead operation, it'll try to read
451: //the entire raster layer! It's only a slight abuse of the semantics of the word "tile"
452: //when we tell JAI that it can tile our image at exactly the size of the section of the
453: //raster layer we're looking to render.
454: final ImageLayout layout = new ImageLayout();
455: layout.setTileWidth(sourceRegion.width);
456: layout.setTileHeight(sourceRegion.height);
457:
458: ParameterBlock pb = new ParameterBlock();
459: pb.add(new Object());
460: pb.add(new Integer(level));
461: pb.add(Boolean.FALSE);
462: pb.add(Boolean.FALSE);
463: pb.add(Boolean.FALSE);
464: pb.add(null);
465: pb.add(null);
466: pb.add(rParam);
467: pb.add(imageIOReader);
468:
469: //We're not really interested in this renderedRaster, becasue I can't figure out how to get JAI
470: //to render the source area to a translated offset in the destination image. Note that this
471: //is really a throwaway operation. The ".getData()" call at the end forces the created RenderedImage
472: //to load its internal data, which causes the rParam.getDestination() bufferedImage to get loaded
473: //We use that at the end, essentially discarding this RenderedImage (it's not actually what we want,
474: //anyway).
475: JAI
476: .create(
477: "ImageRead",
478: pb,
479: new RenderingHints(
480: JAI.KEY_IMAGE_LAYOUT, layout))
481: .getData();
482:
483: if (DEBUG) {
484: Graphics2D g = outputImage.createGraphics();
485: g.setColor(Color.orange);
486: g.setFont(new Font("Sans-serif", Font.BOLD, 12));
487: g.drawString(sourceRegion.getMinX() + ","
488: + sourceRegion.getMinY(), 30, 40);
489: }
490:
491: }
492:
493: // Create the coverage
494: return coverageFactory.create(coverageName, outputImage,
495: new GeneralEnvelope(outputImageEnvelope),
496: gridBands, null, null);
497:
498: } catch (DataSourceException e) {
499: if (LOGGER.isLoggable(Level.SEVERE))
500: LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
501: throw new DataSourceException(e);
502: } catch (SeException se) {
503: if (LOGGER.isLoggable(Level.SEVERE))
504: LOGGER.log(Level.SEVERE, se.getSeError().getErrDesc(),
505: se);
506: throw new DataSourceException(se);
507: } catch (UnavailableArcSDEConnectionException uce) {
508: if (LOGGER.isLoggable(Level.SEVERE))
509: LOGGER
510: .log(Level.SEVERE, uce.getLocalizedMessage(),
511: uce);
512: throw new DataSourceException(uce);
513: } finally {
514: if (scon != null)
515: scon.close();
516: }
517: }
518:
519: /**
520: * @param sdeUrl -
521: * A StringBuffer containing a string of form
522: * 'sde://user:pass@sdehost:[port]/[dbname]
523: *
524: * @return a ConnectionConfig object representing these parameters
525: */
526: static ArcSDEConnectionConfig sdeURLToConnectionConfig(
527: StringBuffer sdeUrl) {
528: // annoyingly, geoserver currently stores the user-entered SDE string as
529: // a File, and passes us the
530: // File object. The File object strips the 'sde://user...' into a
531: // 'sde:/user..'. So we need to check
532: // for both forms of the url.
533: String sdeHost, sdeUser, sdePass, sdeDBName;
534: int sdePort;
535: if (sdeUrl.indexOf("sde:/") == -1) {
536: throw new IllegalArgumentException(
537: "ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName -- Got "
538: + sdeUrl);
539: } else {
540: if (sdeUrl.indexOf("sde://") == -1)
541: sdeUrl.delete(0, 5);
542: else
543: sdeUrl.delete(0, 6);
544: }
545:
546: int idx = sdeUrl.indexOf(":");
547: if (idx == -1) {
548: throw new IllegalArgumentException(
549: "ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName");
550: } else {
551: sdeUser = sdeUrl.substring(0, idx).toString();
552: sdeUrl.delete(0, idx);
553: }
554:
555: idx = sdeUrl.indexOf("@");
556: if (idx == -1) {
557: throw new IllegalArgumentException(
558: "ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName");
559: } else {
560: sdePass = sdeUrl.substring(1, idx).toString();
561: sdeUrl.delete(0, idx);
562: }
563:
564: idx = sdeUrl.indexOf(":");
565: if (idx == -1) {
566: // there's no "port" specification. Assume 5151;
567: sdePort = 5151;
568:
569: idx = sdeUrl.indexOf("/");
570: if (idx == -1) {
571: throw new IllegalArgumentException(
572: "ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName");
573: } else {
574: sdeHost = sdeUrl.substring(1, idx).toString();
575: sdeUrl.delete(0, idx);
576: }
577:
578: } else {
579: sdeHost = sdeUrl.substring(1, idx).toString();
580: sdeUrl.delete(0, idx);
581:
582: idx = sdeUrl.indexOf("/");
583: if (idx == -1) {
584: throw new IllegalArgumentException(
585: "ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName");
586: } else {
587: sdePort = Integer.parseInt(sdeUrl.substring(1, idx)
588: .toString());
589: sdeUrl.delete(0, idx);
590: }
591: }
592:
593: idx = sdeUrl.indexOf("#");
594: if (idx == -1) {
595: throw new IllegalArgumentException(
596: "ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName");
597: } else {
598: sdeDBName = sdeUrl.substring(1, idx).toString();
599: sdeUrl.delete(0, idx);
600: }
601:
602: return new ArcSDEConnectionConfig("arcsde", sdeHost, sdePort
603: + "", sdeDBName, sdeUser, sdePass);
604: }
605:
606: /**
607: * Gets the coordinate system that will be associated to the
608: * {@link GridCoverage}. The WGS84 coordinate system is used by default.
609: *
610: */
611: private void calculateCoordinateReferenceSystem()
612: throws DataSourceException {
613:
614: if (rasterAttributes == null) {
615: throw new DataSourceException(
616: "Raster Attributes are null, can't calculated CRS info.");
617: }
618:
619: ArcSDEPooledConnection con = null;
620: try {
621: con = connectionPool.getConnection();
622: SeRasterColumn rCol = new SeRasterColumn(con,
623: rasterAttributes.getRasterColumnId());
624:
625: PeProjectedCS pcs = new PeProjectedCS(rCol.getCoordRef()
626: .getProjectionDescription());
627: epsgCode = -1;
628: int[] projcs = PeFactory.projcsCodelist();
629: for (int i = 0; i < projcs.length; i++) {
630: try {
631: PeProjectedCS candidate = PeFactory
632: .projcs(projcs[i]);
633: //in ArcSDE 9.2, if the PeFactory doesn't support a projection it claimed
634: //to support, it returns 'null'. So check for it.
635: if (candidate != null
636: && candidate.getName().trim().equals(
637: pcs.getName()))
638: epsgCode = projcs[i];
639: } catch (PeProjectionException pe) {
640: // Strangely SDE includes codes in the projcsCodeList() that
641: // it doesn't actually support.
642: // Catch the exception and skip them here.
643: }
644: }
645:
646: if (epsgCode == -1) {
647: LOGGER
648: .warning("Couldn't determine EPSG code for this raster. Using SDE's WKT-like coordSysDescription() instead.");
649: crs = CRS.parseWKT(rCol.getCoordRef()
650: .getCoordSysDescription());
651: } else {
652: crs = CRS.decode("EPSG:" + epsgCode);
653: }
654:
655: SeExtent sdeExtent = rasterAttributes.getExtent();
656: originalEnvelope = new GeneralEnvelope(crs);
657: originalEnvelope.setRange(0, sdeExtent.getMinX(), sdeExtent
658: .getMaxX());
659: originalEnvelope.setRange(1, sdeExtent.getMinY(), sdeExtent
660: .getMaxY());
661: } catch (UnavailableArcSDEConnectionException e) {
662: LOGGER.log(Level.SEVERE, "", e);
663: throw new DataSourceException(e);
664: } catch (SeException e) {
665: LOGGER.log(Level.SEVERE, "", e);
666: throw new DataSourceException(e.getSeError().getErrDesc(),
667: e);
668: } catch (FactoryException e) {
669: LOGGER.log(Level.SEVERE, "", e);
670: throw new DataSourceException(e);
671: } catch (PeProjectionException e) {
672: LOGGER.log(Level.SEVERE, "", e);
673: throw new DataSourceException(e);
674: } finally {
675: if (con != null && !con.isClosed())
676: con.close();
677: }
678: }
679:
680: /**
681: * Checks the input prvided to this {@link ArcSDERasterGridCoverage2DReader} and sets all
682: * the other objects and flags accordingly.
683: *
684: * @param input provied to this {@link ArcSDERasterGridCoverage2DReader}.
685: * @throws DataSourceException
686: * @throws IOException
687: */
688: private void setupConnectionPool(Object input)
689: throws DataSourceException {
690: if (input == null) {
691: final DataSourceException ex = new DataSourceException(
692: "No source set to read this coverage.");
693: if (LOGGER.isLoggable(Level.SEVERE))
694: LOGGER.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
695: throw ex;
696: }
697:
698: // this will be our connection string
699: String sdeUrl = null;
700:
701: if (input instanceof String) {
702: sdeUrl = (String) input;
703: LOGGER.warning("connecting to ArcSDE Raster: " + sdeUrl);
704: } else if (input instanceof File) {
705: sdeUrl = ((File) input).getPath();
706: LOGGER
707: .warning("connectiong via file-hack to ArcSDE Raster: "
708: + sdeUrl);
709: } else {
710: throw new IllegalArgumentException(
711: "Unsupported input type: " + input.getClass());
712: }
713:
714: ArcSDEConnectionConfig sdeConfig = sdeURLToConnectionConfig(new StringBuffer(
715: sdeUrl));
716: if (sdeUrl.indexOf(";") != -1) {
717: final String extraParams = sdeUrl.substring(sdeUrl
718: .indexOf(";") + 1, sdeUrl.length());
719: sdeUrl = sdeUrl.substring(0, sdeUrl.indexOf(";"));
720:
721: // Right now we only support one kind of extra parameter, so we'll pull it out here.
722: if (extraParams.indexOf("LZERO_ORIGIN_TILE=") != -1) {
723: String offsetTile = extraParams.substring(extraParams
724: .indexOf("LZERO_ORIGIN_TILE=") + 18);
725: int xOffsetTile = Integer.parseInt(offsetTile
726: .substring(0, offsetTile.indexOf(",")));
727: int yOffsetTile = Integer.parseInt(offsetTile
728: .substring(offsetTile.indexOf(",") + 1));
729: _levelZeroPRP = new Point(xOffsetTile, yOffsetTile);
730: }
731:
732: }
733: rasterTable = sdeUrl.substring(sdeUrl.indexOf("#") + 1);
734:
735: if (LOGGER.isLoggable(Level.FINE))
736: LOGGER.fine("Building ArcSDEGridCoverageReader2D for "
737: + sdeConfig + ", with raster table " + rasterTable);
738:
739: connectionPool = ArcSDEConnectionPoolFactory.getInstance()
740: .createPool(sdeConfig);
741:
742: try {
743: ArcSDEPooledConnection scon = connectionPool
744: .getConnection();
745:
746: SeTable sTable = connectionPool.getSdeTable(scon,
747: rasterTable);
748: SeQuery q = null;
749: try {
750: SeColumnDefinition[] cols = sTable.describe();
751: ArrayList fetchColumns = new ArrayList();
752: for (int i = 0; i < cols.length; i++) {
753: if (cols[i].getType() == SeColumnDefinition.TYPE_RASTER)
754: fetchColumns.add(cols[i].getName());
755: }
756: if (fetchColumns.size() == 0)
757: throw new DataSourceException(
758: "Couldn't find any TYPE_RASTER columns in ArcSDE table "
759: + rasterTable);
760:
761: rasterColumns = (String[]) fetchColumns
762: .toArray(new String[fetchColumns.size()]);
763: q = new SeQuery(scon, rasterColumns,
764: new SeSqlConstruct(rasterTable));
765: q.prepareQuery();
766: q.execute();
767:
768: SeRow r = q.fetch();
769: rasterAttributes = r.getRaster(0);
770:
771: q.close();
772: } catch (SeException se) {
773: throw new DataSourceException(
774: "Error fetching raster connection data from "
775: + rasterTable + ": "
776: + se.getSeError().getErrDesc(), se);
777: } finally {
778: if (scon != null && !scon.isClosed())
779: scon.close();
780: }
781:
782: } catch (UnavailableArcSDEConnectionException uce) {
783: throw new DataSourceException(
784: "Unable to fetch a connection to ArcSDE server at "
785: + connectionPool.getConfig()
786: .getServerName() + ".", uce);
787: }
788:
789: }
790:
791: /**
792: * Inspects the band layout of this raster layer to determine whether this reader can actually
793: * support this raster layer, what sort of BufferedImage to create when rendering this layer
794: * and how to describe each band in rendered layers.
795: *
796: * @throws DataSourceException if there's an error communicating with SDE about this raster layer.
797: */
798: private void calculateBandDependentInfo()
799: throws DataSourceException {
800: try {
801: SeRasterBand[] sdeBands = rasterAttributes.getBands();
802: bandInfo = new HashMap();
803: for (int i = 0; i < sdeBands.length; i++) {
804: bandInfo.put(new Long(sdeBands[i].getId().longValue()),
805: ArcSDERasterBandCopier.getInstance(
806: rasterAttributes.getPixelType(),
807: pyramidInfo.tileHeight,
808: pyramidInfo.tileWidth));
809: }
810:
811: for (int i = 0; i < rasterAttributes.getNumBands(); i++) {
812: if (rasterAttributes.getBandInfo(i + 1).hasColorMap()) {
813: throw new IllegalArgumentException(
814: "ArcSDERasterGridCoverage2DReader doesn't support reading from ColorMapped SDE rasters yet.");
815: }
816: }
817:
818: if (rasterAttributes.getNumBands() == 1) {
819: if (rasterAttributes.getPixelType() == SeRaster.SE_PIXEL_TYPE_1BIT) {
820: LOGGER
821: .warning("Discovered 1-bit single-band raster. Using return image type: TYPE_BYTE_BINARY and 1-bit black/white category.");
822: NumberRange sampleValueRange = new NumberRange(0, 1);
823: Category bitBandCat = new Category(
824: this .coverageName + ": Band One (1-bit)",
825: new Color[] { Color.BLACK, Color.WHITE },
826: sampleValueRange,
827: LinearTransform1D.IDENTITY);
828: gridBands = new GridSampleDimension[1];
829: gridBands[0] = new GridSampleDimension(bitBandCat
830: .getName(), new Category[] { bitBandCat },
831: null).geophysics(true);
832:
833: bufferedImageType = BufferedImage.TYPE_BYTE_BINARY;
834: } else if (rasterAttributes.getPixelType() == SeRaster.SE_PIXEL_TYPE_8BIT_S
835: || rasterAttributes.getPixelType() == SeRaster.SE_PIXEL_TYPE_8BIT_U) {
836: LOGGER
837: .warning("Discovered 8-bit single-band raster. Using return image type: TYPE_BYTE_GRAY");
838: NumberRange sampleValueRange = new NumberRange(0,
839: 255);
840: Category greyscaleBandCat = new Category(
841: this .coverageName
842: + ": Band One (grayscale)",
843: new Color[] { Color.BLACK, Color.WHITE },
844: sampleValueRange,
845: LinearTransform1D.IDENTITY);
846: gridBands = new GridSampleDimension[1];
847: gridBands[0] = new GridSampleDimension(
848: greyscaleBandCat.getName(),
849: new Category[] { greyscaleBandCat }, null)
850: .geophysics(true);
851:
852: bufferedImageType = BufferedImage.TYPE_BYTE_GRAY;
853: } else {
854: throw new IllegalArgumentException(
855: "One-band, non-colormapped raster layers with type "
856: + rasterAttributes.getPixelType()
857: + " are not supported.");
858: }
859:
860: } else if (rasterAttributes.getNumBands() == 3
861: || rasterAttributes.getNumBands() == 4) {
862: if (rasterAttributes.getPixelType() != SeRaster.SE_PIXEL_TYPE_8BIT_U) {
863: throw new IllegalArgumentException(
864: "3 or 4 band rasters are only supported if they have pixel type 8-bit unsigned pixels.");
865: }
866: LOGGER
867: .warning("Three or four banded non-colormapped raster detected. Assuming bands 1,2 and 3 constitue a 3-band RGB image. Using return image type: TYPE_INT_ARGB (alpha will be used to support no-data pixels)");
868: NumberRange sampleValueRange = new NumberRange(0, 255);
869: Category nan = new Category("no-data",
870: new Color[] { new Color(0x00000000) },
871: new NumberRange(0, 0),
872: LinearTransform1D.IDENTITY);
873: Category white = new Category("valid-data",
874: new Color[] { new Color(0xff000000) },
875: new NumberRange(255, 255),
876: LinearTransform1D.IDENTITY);
877: Category redBandCat = new Category("red", new Color[] {
878: Color.BLACK, Color.RED }, sampleValueRange,
879: LinearTransform1D.IDENTITY);
880: Category blueBandCat = new Category("blue",
881: new Color[] { Color.BLACK, Color.BLUE },
882: sampleValueRange, LinearTransform1D.IDENTITY);
883: Category greenBandCat = new Category("green",
884: new Color[] { Color.BLACK, Color.GREEN },
885: sampleValueRange, LinearTransform1D.IDENTITY);
886:
887: gridBands = new GridSampleDimension[4];
888: gridBands[0] = new GridSampleDimension("Red band",
889: new Category[] { redBandCat }, null);
890: gridBands[1] = new GridSampleDimension("Green band",
891: new Category[] { blueBandCat }, null);
892: gridBands[2] = new GridSampleDimension("Blue band",
893: new Category[] { greenBandCat }, null);
894: gridBands[3] = new GridSampleDimension(
895: "NODATA Mask Band",
896: new Category[] { nan, white }, null);
897:
898: bufferedImageType = BufferedImage.TYPE_INT_ARGB;
899: } else {
900: StringBuffer errmsg = new StringBuffer();
901: errmsg
902: .append("ArcSDERasterGridCoverage2DReader doesn't support ");
903: errmsg.append(rasterAttributes.getNumBands());
904: errmsg.append("-banded images of type ");
905: errmsg.append(rasterAttributes.getPixelType());
906: throw new IllegalArgumentException();
907: }
908:
909: } catch (SeException se) {
910: LOGGER.log(Level.SEVERE, se.getSeError().getErrDesc(), se);
911: throw new DataSourceException(se);
912: }
913: }
914:
915: private void setupCoverageMetadata() {
916:
917: ArcSDEPyramidLevel highestRes = pyramidInfo
918: .getPyramidLevel(pyramidInfo.getNumLevels() - 1);
919: Rectangle actualDim = new Rectangle(0, 0, highestRes
920: .getNumTilesWide()
921: * tileWidth, highestRes.getNumTilesHigh() * tileHeight);
922: originalGridRange = new GeneralGridRange(actualDim);
923:
924: coverageName = rasterTable;
925: }
926:
927: private void setupImageIOReader() throws DataSourceException {
928: HashMap readerMap = new HashMap();
929: readerMap.put(ArcSDERasterReaderSpi.PYRAMID, pyramidInfo);
930: readerMap.put(ArcSDERasterReaderSpi.RASTER_TABLE, rasterTable);
931: readerMap.put(ArcSDERasterReaderSpi.RASTER_COLUMN,
932: rasterColumns[0]);
933:
934: try {
935: imageIOReader = (ArcSDERasterReader) new ArcSDERasterReaderSpi()
936: .createReaderInstance(readerMap);
937: } catch (IOException ioe) {
938: LOGGER
939: .log(
940: Level.SEVERE,
941: "Error creating ImageIOReader in ArcSDERasterGridCoverage2DReader",
942: ioe);
943: throw new DataSourceException(ioe);
944: }
945: }
946:
947: private BufferedImage createInitialBufferedImage(final int width,
948: final int height) throws DataSourceException {
949: final BufferedImage ret = new BufferedImage(width, height,
950: bufferedImageType);
951: // By default BufferedImages are created with all banks set to zero. That's an all-black, transparent image.
952: // Transparency is handled in the ArcSDERasterBandCopier. Blackness isn't. Let's fix that and set
953: // the image to white.
954: int[] pixels = new int[width * height];
955: for (int i = 0; i < width * height; i++) {
956: pixels[i] = 0x00ffffff;
957: }
958: ret.setRGB(0, 0, width, height, pixels, 0, 1);
959:
960: return ret;
961: }
962: }
|