001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.vfny.geoserver.util;
006:
007: import org.geotools.coverage.grid.GridCoverage2D;
008: import org.geotools.coverage.grid.GridGeometry2D;
009: import org.geotools.coverage.processing.DefaultProcessor;
010: import org.geotools.coverage.processing.operation.Crop;
011: import org.geotools.coverage.processing.operation.FilteredSubsample;
012: import org.geotools.coverage.processing.operation.Interpolate;
013: import org.geotools.coverage.processing.operation.Resample;
014: import org.geotools.coverage.processing.operation.Scale;
015: import org.geotools.coverage.processing.operation.SelectSampleDimension;
016: import org.geotools.factory.Hints;
017: import org.geotools.geometry.GeneralEnvelope;
018: import org.geotools.resources.CRSUtilities;
019: import org.opengis.coverage.Coverage;
020: import org.opengis.coverage.grid.GridCoverage;
021: import org.opengis.coverage.grid.GridRange;
022: import org.opengis.parameter.ParameterValueGroup;
023: import org.opengis.referencing.crs.CoordinateReferenceSystem;
024: import org.vfny.geoserver.wcs.WcsException;
025: import java.util.ArrayList;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.Map;
029: import java.util.logging.Logger;
030: import javax.media.jai.BorderExtender;
031: import javax.media.jai.Interpolation;
032: import javax.media.jai.InterpolationNearest;
033:
034: /**
035: *
036: * @author Simone Giannecchini, GeoSolutions
037: * @author Alessio Fabiani, GeoSolutions
038: *
039: */
040: public class WCSUtils {
041: private final static Hints LENIENT_HINT = new Hints(
042: Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);
043: private static final Logger LOGGER = org.geotools.util.logging.Logging
044: .getLogger("org.vfny.geoserver.util");
045: private final static SelectSampleDimension bandSelectFactory = new SelectSampleDimension();
046: private final static Crop cropFactory = new Crop();
047: private final static Interpolate interpolateFactory = new Interpolate();
048: private final static Scale scaleFactory = new Scale();
049: private final static FilteredSubsample filteredSubsampleFactory = new FilteredSubsample();
050: private final static Resample resampleFactory = new Resample();
051:
052: static {
053: // ///////////////////////////////////////////////////////////////////
054: //
055: // Static Processors
056: //
057: // ///////////////////////////////////////////////////////////////////
058: final DefaultProcessor processor = new DefaultProcessor(
059: LENIENT_HINT);
060: bandSelectParams = processor.getOperation(
061: "SelectSampleDimension").getParameters();
062: cropParams = processor.getOperation("CoverageCrop")
063: .getParameters();
064: interpolateParams = processor.getOperation("Interpolate")
065: .getParameters();
066: scaleParams = processor.getOperation("Scale").getParameters();
067: resampleParams = processor.getOperation("Resample")
068: .getParameters();
069: filteredSubsampleParams = processor.getOperation(
070: "FilteredSubsample").getParameters();
071: }
072:
073: private final static ParameterValueGroup bandSelectParams;
074: private final static ParameterValueGroup cropParams;
075: private final static ParameterValueGroup interpolateParams;
076: private final static ParameterValueGroup resampleParams;
077: private final static ParameterValueGroup scaleParams;
078: private final static ParameterValueGroup filteredSubsampleParams;
079: private final static Hints hints = new Hints(new HashMap(5));
080:
081: static {
082: hints.add(LENIENT_HINT);
083: }
084:
085: /**
086: * <strong>Reprojecting</strong><br>
087: * The new grid geometry can have a different coordinate reference system
088: * than the underlying grid geometry. For example, a grid coverage can be
089: * reprojected from a geodetic coordinate reference system to Universal
090: * Transverse Mercator CRS.
091: *
092: * @param coverage
093: * GridCoverage2D
094: * @param sourceCRS
095: * CoordinateReferenceSystem
096: * @param targetCRS
097: * CoordinateReferenceSystem
098: * @return GridCoverage2D
099: * @throws WcsException
100: */
101: public static GridCoverage2D reproject(GridCoverage2D coverage,
102: final CoordinateReferenceSystem sourceCRS,
103: final CoordinateReferenceSystem targetCRS,
104: final Interpolation interpolation) throws WcsException {
105: // ///////////////////////////////////////////////////////////////////
106: //
107: // REPROJECT
108: //
109: //
110: // ///////////////////////////////////////////////////////////////////
111: if (!CRSUtilities.equalsIgnoreMetadata(sourceCRS, targetCRS)) {
112: /*
113: * Operations.DEFAULT.resample( coverage, targetCRS, null,
114: * Interpolation.getInstance(Interpolation.INTERP_NEAREST))
115: */
116: final ParameterValueGroup param = (ParameterValueGroup) resampleParams
117: .clone();
118: param.parameter("Source").setValue(coverage);
119: param.parameter("CoordinateReferenceSystem").setValue(
120: targetCRS);
121: param.parameter("GridGeometry").setValue(null);
122: param.parameter("InterpolationType")
123: .setValue(interpolation);
124:
125: coverage = (GridCoverage2D) resampleFactory.doOperation(
126: param, hints);
127: }
128:
129: return coverage;
130: }
131:
132: /**
133: * <strong>Interpolating</strong><br>
134: * Specifies the interpolation type to be used to interpolate values for
135: * points which fall between grid cells. The default value is nearest
136: * neighbor. The new interpolation type operates on all sample dimensions.
137: * Possible values for type are: {@code "NearestNeighbor"},
138: * {@code "Bilinear"} and {@code "Bicubic"} (the {@code "Optimal"}
139: * interpolation type is currently not supported).
140: *
141: * @param coverage
142: * GridCoverage2D
143: * @param interpolation
144: * Interpolation
145: * @return GridCoverage2D
146: * @throws WcsException
147: */
148: public static GridCoverage2D interpolate(GridCoverage2D coverage,
149: final Interpolation interpolation) throws WcsException {
150: // ///////////////////////////////////////////////////////////////////
151: //
152: // INTERPOLATE
153: //
154: //
155: // ///////////////////////////////////////////////////////////////////
156: if (interpolation != null) {
157: /* Operations.DEFAULT.interpolate(coverage, interpolation) */
158: final ParameterValueGroup param = (ParameterValueGroup) interpolateParams
159: .clone();
160: param.parameter("Source").setValue(coverage);
161: param.parameter("Type").setValue(interpolation);
162:
163: coverage = (GridCoverage2D) interpolateFactory.doOperation(
164: param, hints);
165: }
166:
167: return coverage;
168: }
169:
170: /**
171: * <strong>Scaling</strong><br>
172: * Let user to scale down to the EXACT needed resolution. This step does not
173: * prevent from having loaded an overview of the original image based on the
174: * requested scale.
175: *
176: * @param coverage
177: * GridCoverage2D
178: * @param newGridRange
179: * GridRange
180: * @param sourceCoverage
181: * GridCoverage
182: * @param sourceCRS
183: * CoordinateReferenceSystem
184: * @param destinationEnvelopeInSourceCRS
185: * @return GridCoverage2D
186: */
187: public static GridCoverage2D scale(final GridCoverage2D coverage,
188: final GridRange newGridRange,
189: final GridCoverage sourceCoverage,
190: final CoordinateReferenceSystem sourceCRS,
191: final GeneralEnvelope destinationEnvelopeInSourceCRS) {
192: // ///////////////////////////////////////////////////////////////////
193: //
194: // SCALE to the needed resolution
195: // Let me now scale down to the EXACT needed resolution. This step does
196: // not prevent from having loaded an overview of the original image
197: // based on the requested scale.
198: //
199: // ///////////////////////////////////////////////////////////////////
200: GridGeometry2D scaledGridGeometry = new GridGeometry2D(
201: newGridRange,
202: (destinationEnvelopeInSourceCRS != null) ? destinationEnvelopeInSourceCRS
203: : sourceCoverage.getEnvelope());
204:
205: /*
206: * Operations.DEFAULT.resample( coverage, sourceCRS, scaledGridGeometry,
207: * Interpolation.getInstance(Interpolation.INTERP_NEAREST));
208: */
209: final ParameterValueGroup param = (ParameterValueGroup) resampleParams
210: .clone();
211: param.parameter("Source").setValue(coverage);
212: param.parameter("CoordinateReferenceSystem")
213: .setValue(sourceCRS);
214: param.parameter("GridGeometry").setValue(scaledGridGeometry);
215: param
216: .parameter("InterpolationType")
217: .setValue(
218: Interpolation
219: .getInstance(Interpolation.INTERP_NEAREST));
220:
221: final GridCoverage2D scaledGridCoverage = (GridCoverage2D) resampleFactory
222: .doOperation(param, hints);
223:
224: return scaledGridCoverage;
225: }
226:
227: /**
228: * Scaling the input coverage using the provided parameters.
229: *
230: * @param scaleX
231: * @param scaleY
232: * @param xTrans
233: * @param yTrans
234: * @param interpolation
235: * @param be
236: * @param gc
237: * @return
238: */
239: public static GridCoverage2D scale(final double scaleX,
240: final double scaleY, float xTrans, float yTrans,
241: final Interpolation interpolation, final BorderExtender be,
242: final GridCoverage2D gc) {
243: final ParameterValueGroup param = (ParameterValueGroup) scaleParams
244: .clone();
245: param.parameter("source").setValue(gc);
246: param.parameter("xScale").setValue(new Float(scaleX));
247: param.parameter("yScale").setValue(new Float(scaleY));
248: param.parameter("xTrans").setValue(new Float(xTrans));
249: param.parameter("yTrans").setValue(new Float(yTrans));
250: param.parameter("Interpolation").setValue(interpolation);
251: param.parameter("BorderExtender").setValue(be);
252:
253: return (GridCoverage2D) scaleFactory.doOperation(param, hints);
254: }
255:
256: /**
257: * Reprojecting the input coverage using the provided parameters.
258: *
259: * @param gc
260: * @param crs
261: * @param interpolation
262: * @return
263: */
264: public static GridCoverage2D resample(final GridCoverage2D gc,
265: CoordinateReferenceSystem crs,
266: final Interpolation interpolation) {
267: final ParameterValueGroup param = (ParameterValueGroup) resampleParams
268: .clone();
269: param.parameter("source").setValue(gc);
270: param.parameter("CoordinateReferenceSystem").setValue(crs);
271: param.parameter("InterpolationType").setValue(interpolation);
272:
273: return (GridCoverage2D) resampleFactory.doOperation(param,
274: hints);
275: }
276:
277: /**
278: * Subsampling the provided {@link GridCoverage2D} with the provided
279: * parameters.
280: *
281: * @param gc
282: * @param scaleXInt
283: * @param scaleYInt
284: * @param interpolation
285: * @param be
286: * @return
287: */
288: public static GridCoverage2D filteredSubsample(
289: final GridCoverage2D gc, int scaleXInt, int scaleYInt,
290: final Interpolation interpolation, final BorderExtender be) {
291: final GridCoverage2D preScaledGridCoverage;
292:
293: if ((scaleXInt == 1) && (scaleYInt == 1)) {
294: preScaledGridCoverage = gc;
295: } else {
296: final ParameterValueGroup param = (ParameterValueGroup) filteredSubsampleParams
297: .clone();
298: param.parameter("source").setValue(gc);
299: param.parameter("scaleX").setValue(new Integer(scaleXInt));
300: param.parameter("scaleY").setValue(new Integer(scaleYInt));
301:
302: if (interpolation.equals(new InterpolationNearest())) {
303: param.parameter("qsFilterArray").setValue(
304: new float[] { 1.0F });
305: } else {
306: param.parameter("qsFilterArray").setValue(
307: new float[] { 0.5F, 1.0F / 3.0F, 0.0F,
308: -1.0F / 12.0F });
309: }
310:
311: param.parameter("Interpolation").setValue(interpolation);
312: param.parameter("BorderExtender").setValue(be);
313: preScaledGridCoverage = (GridCoverage2D) filteredSubsampleFactory
314: .doOperation(param, hints);
315: }
316:
317: return preScaledGridCoverage;
318: }
319:
320: /**
321: * <strong>Cropping</strong><br>
322: * The crop operation is responsible for selecting geographic subareas of
323: * the source coverage.
324: *
325: * @param coverage
326: * Coverage
327: * @param sourceEnvelope
328: * GeneralEnvelope
329: * @param sourceCRS
330: * CoordinateReferenceSystem
331: * @param destinationEnvelopeInSourceCRS
332: * GeneralEnvelope
333: * @return GridCoverage2D
334: * @throws WcsException
335: */
336: public static GridCoverage2D crop(final Coverage coverage,
337: final GeneralEnvelope sourceEnvelope,
338: final CoordinateReferenceSystem sourceCRS,
339: final GeneralEnvelope destinationEnvelopeInSourceCRS,
340: final Boolean conserveEnvelope) throws WcsException {
341: // ///////////////////////////////////////////////////////////////////
342: //
343: // CROP
344: //
345: //
346: // ///////////////////////////////////////////////////////////////////
347: final GridCoverage2D croppedGridCoverage;
348:
349: // intersect the envelopes
350: final GeneralEnvelope intersectionEnvelope = new GeneralEnvelope(
351: destinationEnvelopeInSourceCRS);
352: intersectionEnvelope.setCoordinateReferenceSystem(sourceCRS);
353: intersectionEnvelope
354: .intersect((GeneralEnvelope) sourceEnvelope);
355:
356: // dow we have something to show?
357: if (intersectionEnvelope.isEmpty()) {
358: throw new WcsException(
359: "The Intersection is null. Check the requested BBOX!");
360: }
361:
362: if (!intersectionEnvelope
363: .equals((GeneralEnvelope) sourceEnvelope)) {
364: // get the cropped grid geometry
365: // final GridGeometry2D cropGridGeometry = getCroppedGridGeometry(
366: // intersectionEnvelope, gridCoverage);
367:
368: /* Operations.DEFAULT.crop(coverage, intersectionEnvelope) */
369: final ParameterValueGroup param = (ParameterValueGroup) cropParams
370: .clone();
371: param.parameter("Source").setValue(coverage);
372: param.parameter("Envelope").setValue(intersectionEnvelope);
373: param.parameter("ConserveEnvelope").setValue(
374: conserveEnvelope);
375:
376: croppedGridCoverage = (GridCoverage2D) cropFactory
377: .doOperation(param, hints);
378: } else {
379: croppedGridCoverage = (GridCoverage2D) coverage;
380: }
381:
382: // prefetch to be faster afterwards.
383: // This step is important since at this stage we might be loading tiles
384: // from disk
385: croppedGridCoverage.prefetch(intersectionEnvelope
386: .toRectangle2D());
387:
388: return croppedGridCoverage;
389: }
390:
391: /**
392: * <strong>Band Selecting</strong><br>
393: * Chooses <var>N</var>
394: * {@linkplain org.geotools.coverage.GridSampleDimension sample dimensions}
395: * from a grid coverage and copies their sample data to the destination grid
396: * coverage in the order specified. The {@code "SampleDimensions"} parameter
397: * specifies the source {@link org.geotools.coverage.GridSampleDimension}
398: * indices, and its size ({@code SampleDimensions.length}) determines the
399: * number of sample dimensions of the destination grid coverage. The
400: * destination coverage may have any number of sample dimensions, and a
401: * particular sample dimension of the source coverage may be repeated in the
402: * destination coverage by specifying it multiple times in the
403: * {@code "SampleDimensions"} parameter.
404: *
405: * @param params
406: * Set
407: * @param coverage
408: * GridCoverage
409: * @return Coverage
410: * @throws WcsException
411: */
412: public static Coverage bandSelect(final Map params,
413: final GridCoverage coverage) throws WcsException {
414: // ///////////////////////////////////////////////////////////////////
415: //
416: // BAND SELECT
417: //
418: //
419: // ///////////////////////////////////////////////////////////////////
420: final int numDimensions = coverage.getNumSampleDimensions();
421: final Map dims = new HashMap();
422: final ArrayList selectedBands = new ArrayList();
423:
424: for (int d = 0; d < numDimensions; d++) {
425: dims.put("band" + (d + 1), new Integer(d));
426: }
427:
428: if ((params != null) && !params.isEmpty()) {
429: for (Iterator p = params.keySet().iterator(); p.hasNext();) {
430: final String param = (String) p.next();
431:
432: if (param.equalsIgnoreCase("BAND")) {
433: try {
434: final String values = (String) params
435: .get(param);
436:
437: if (values.indexOf("/") > 0) {
438: final String[] minMaxRes = values
439: .split("/");
440: final int min = (int) Math.round(Double
441: .parseDouble(minMaxRes[0]));
442: final int max = (int) Math.round(Double
443: .parseDouble(minMaxRes[1]));
444: final double res = ((minMaxRes.length > 2) ? Double
445: .parseDouble(minMaxRes[2])
446: : 0.0);
447:
448: for (int v = min; v <= max; v++) {
449: final String key = param.toLowerCase()
450: + v;
451:
452: if (dims.containsKey(key)) {
453: selectedBands.add(dims.get(key));
454: }
455: }
456: } else {
457: final String[] bands = values.split(",");
458:
459: for (int v = 0; v < bands.length; v++) {
460: final String key = param.toLowerCase()
461: + bands[v];
462:
463: if (dims.containsKey(key)) {
464: selectedBands.add(dims.get(key));
465: }
466: }
467:
468: if (selectedBands.size() == 0) {
469: throw new Exception(
470: "WRONG PARAM VALUES.");
471: }
472: }
473: } catch (Exception e) {
474: throw new WcsException(
475: "Band parameters incorrectly specified: "
476: + e.getLocalizedMessage());
477: }
478: }
479: }
480: }
481:
482: final int length = selectedBands.size();
483: final int[] bands = new int[length];
484:
485: for (int b = 0; b < length; b++) {
486: bands[b] = ((Integer) selectedBands.get(b)).intValue();
487: }
488:
489: Coverage bandSelectedCoverage;
490:
491: if ((bands != null) && (bands.length > 0)) {
492: /* Operations.DEFAULT.selectSampleDimension(coverage, bands) */
493: final ParameterValueGroup param = (ParameterValueGroup) bandSelectParams
494: .clone();
495: param.parameter("Source").setValue(coverage);
496: param.parameter("SampleDimensions").setValue(bands);
497: // param.parameter("VisibleSampleDimension").setValue(bands);
498: bandSelectedCoverage = bandSelectFactory.doOperation(param,
499: hints);
500: } else {
501: bandSelectedCoverage = coverage;
502: }
503:
504: return bandSelectedCoverage;
505: }
506: }
|