OPeNDAP Hyrax Back End Server (BES)  Updated for version 3.8.3
FONgTransform.cc
Go to the documentation of this file.
1 // FONgTransform.cc
2 
3 // This file is part of BES GDAL File Out Module
4 
5 // Copyright (c) 2012 OPeNDAP, Inc.
6 // Author: James Gallagher <jgallagher@opendap.org>
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Lesser General Public
10 // License as published by the Free Software Foundation; either
11 // version 2.1 of the License, or (at your option) any later version.
12 //
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Lesser General Public License for more details.
17 //
18 // You should have received a copy of the GNU Lesser General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 //
22 // You can contact University Corporation for Atmospheric Research at
23 // 3080 Center Green Drive, Boulder, CO 80301
24 
25 #include "config.h"
26 
27 #include <cstdlib>
28 
29 #include <gdal.h>
30 #include <gdal_priv.h>
31 
32 #include <DDS.h>
33 #include <ConstraintEvaluator.h>
34 
35 #include <Structure.h>
36 #include <Array.h>
37 #include <Grid.h>
38 //#include <ce_functions.h>
39 #include <util.h>
40 
41 #include <BESDebug.h>
42 #include <BESInternalError.h>
43 
44 #include "FONgTransform.h"
45 
46 #include "FONgBaseType.h"
47 #include "FONgGrid.h"
48 
49 using namespace std;
50 using namespace libdap;
51 
62 FONgTransform::FONgTransform(DDS *dds, ConstraintEvaluator &/*evaluator*/, const string &localfile) :
63  d_dds(dds), /*d_evaluator(evaluator),*/ d_localfile(localfile),
64  d_geo_transform_set(false), d_no_data_type(none), d_num_bands(0)
65 {
66  if (localfile.empty())
67  throw BESInternalError("Empty local file name passed to constructor", __FILE__, __LINE__);
68 }
69 
75 {
76  vector<FONgBaseType *>::iterator i = d_fong_vars.begin();
77  vector<FONgBaseType *>::iterator e = d_fong_vars.end();
78  while (i != e) {
79  delete (*i++);
80  }
81 }
82 
86 static bool
87 is_convertable_type(const BaseType *b)
88 {
89  switch (b->type()) {
90  case dods_grid_c:
91  return true;
92 
93  // TODO Add support for Array (?)
94  case dods_array_c:
95  default:
96  return false;
97  }
98 }
99 
106 static FONgBaseType *convert(BaseType *v)
107 {
108  switch (v->type()) {
109  case dods_grid_c:
110  return new FONgGrid(static_cast<Grid*>(v));
111 
112  default:
113  throw BESInternalError("file out GeoTiff, unable to write unknown variable type", __FILE__, __LINE__);
114  }
115 }
116 
134 void FONgTransform::m_scale_data(double *data)
135 {
136  // It is an error to call this if no_data_type() is 'none'
137  set<double> hist;
138  for (int i = 0; i < width() * height(); ++i)
139  hist.insert(data[i]);
140 
141  BESDEBUG("fong3", "Hist count: " << hist.size() << endl);
142 
143  if (no_data_type() == negative && hist.size() > 1) {
144  // Values are sorted by 'weak' <, so this is the smallest value
145  // and ++i is the next smallest value. Assume no_data is the
146  // smallest value in the data set and ++i is the smallest actual
147  // data value. Reset the no_data value to be 1.0 < the smallest
148  // actual value. This makes for a good grayscale photometric
149  // GeoTiff w/o changing the actual data values.
150  set<double>::iterator i = hist.begin();
151  double smallest = *(++i);
152  if (fabs(smallest + no_data()) > 1) {
153  smallest -= 1.0;
154 
155  BESDEBUG("fong3", "New no_data value: " << smallest << endl);
156 
157  for (int i = 0; i < width() * height(); ++i) {
158  if (data[i] <= no_data()) {
159  data[i] = smallest;
160  }
161  }
162  }
163  }
164  else {
165  set<double>::reverse_iterator r = hist.rbegin();
166  double biggest = *(++r);
167  if (fabs(no_data() - biggest) > 1) {
168  biggest += 1.0;
169 
170  BESDEBUG("fong3", "New no_data value: " << biggest << endl);
171 
172  for (int i = 0; i < width() * height(); ++i) {
173  if (data[i] >= no_data()) {
174  data[i] = biggest;
175  }
176  }
177  }
178  }
179 }
180 
193 {
194 
195  BESDEBUG("fong3", "left: " << d_left << ", top: " << d_top << ", right: " << d_right << ", bottom: " << d_bottom << endl);
196  BESDEBUG("fong3", "width: " << d_width << ", height: " << d_height << endl);
197 
198  d_gt[0] = d_left; // The leftmost 'x' value, which is longitude
199  d_gt[3] = d_top; // The topmost 'y' value, which is latitude
200 
201  // We assume data w/o any rotation (a north-up image)
202  d_gt[2] = 0.0;
203  d_gt[4] = 0.0;
204 
205  // Compute the lower left values. Note that wehn GDAL builds the geotiff
206  // output dataset, it correctly inverts the image when the source data has
207  // inverted latitude values.
208  d_gt[1] = (d_right - d_left) / d_width; // width in pixels; top and bottom in lat
209  d_gt[5] = (d_bottom - d_top) / d_height;
210 
211  BESDEBUG("fong3", "gt[0]: " << d_gt[0] << ", gt[1]: " << d_gt[1] << ", gt[2]: " << d_gt[2] \
212  << ", gt[3]: " << d_gt[3] << ", gt[4]: " << d_gt[4] << ", gt[5]: " << d_gt[5] << endl);
213 
214  return d_gt;
215 }
216 
228 bool FONgTransform::effectively_two_D(FONgBaseType *fbtp)
229 {
230  if (fbtp->type() == dods_grid_c) {
231  Grid *g = static_cast<FONgGrid*>(fbtp)->grid();
232 
233  if (g->get_array()->dimensions() == 2)
234  return true;
235 
236  // count the dimensions with sizes other than 1
237  Array *a = g->get_array();
238  int dims = 0;
239  for (Array::Dim_iter d = a->dim_begin(); d != a->dim_end(); ++d) {
240  if (a->dimension_size(d, true) > 1)
241  ++dims;
242  }
243 
244  return dims == 2;
245  }
246 
247  return false;
248 }
249 
250 static void build_delegate(BaseType *btp, FONgTransform &t)
251 {
252  if (btp->send_p() && is_convertable_type(btp)) {
253  BESDEBUG( "fong3", "converting " << btp->name() << endl);
254 
255  // Build the delegate
256  FONgBaseType *fb = convert(btp);
257 
258  // Get the information needed for the transform.
259  // Note that FONgBaseType::extract_coordinates() also pushes the
260  // new FONgBaseType instance onto the FONgTransform's vector of
261  // delagate variable objects.
262  fb->extract_coordinates(t);
263  }
264 }
265 
266 // Helper function to descend into Structures looking for Grids (and Arrays
267 // someday).
268 static void find_vars_helper(Structure *s, FONgTransform &t)
269 {
270  Structure::Vars_iter vi = s->var_begin();
271  while (vi != s->var_end()) {
272  if ((*vi)->send_p() && is_convertable_type(*vi)) {
273  build_delegate(*vi, t);
274  }
275  else if ((*vi)->type() == dods_structure_c) {
276  find_vars_helper(static_cast<Structure*>(*vi), t);
277  }
278 
279  ++vi;
280  }
281 }
282 
283 // Helper function to scan the DDS top-level for Grids, ...
284 // Note that FONgBaseType::extract_coordinates() sets a bunch of
285 // values in the FONgBaseType instance _and_ this instance of
286 // FONgTransform. One of these is 'num_bands()'. For GeoTiff,
287 // num_bands() must be 1. This is tested in transform().
288 static void find_vars(DDS *dds, FONgTransform &t)
289 {
290  DDS::Vars_iter vi = dds->var_begin();
291  while (vi != dds->var_end()) {
292  BESDEBUG( "fong3", "looking at: " << (*vi)->name() << " and it is/isn't selected: " << (*vi)->send_p() << endl);
293  if ((*vi)->send_p() && is_convertable_type(*vi)) {
294  BESDEBUG( "fong3", "converting " << (*vi)->name() << endl);
295  build_delegate(*vi, t);
296  }
297  else if ((*vi)->type() == dods_structure_c) {
298  find_vars_helper(static_cast<Structure*>(*vi), t);
299  }
300 
301  ++vi;
302  }
303 }
304 
312 {
313  // Scan the entire DDS looking for variables that have been 'projected' and
314  // build the delegate objects for them.
315  find_vars(d_dds, *this);
316 
317  for (int i = 0; i < num_bands(); ++i)
318  if (!effectively_two_D(var(i)))
319  throw Error("GeoTiff responses can consist of two-dimensional variables only; use constraints to reduce the size of Grids as needed.");
320 
321  GDALAllRegister();
322  CPLSetErrorHandler(CPLQuietErrorHandler);
323 
324  GDALDriver *Driver = GetGDALDriverManager()->GetDriverByName("GTiff");
325  if( Driver == NULL )
326  throw Error("Could not get the GTiff driver from/for GDAL: " + string(CPLGetLastErrorMsg()));
327 
328  char **Metadata = Driver->GetMetadata();
329  if (!CSLFetchBoolean(Metadata, GDAL_DCAP_CREATE, FALSE))
330  throw Error("Could not make output format.");
331 
332  BESDEBUG("fong3", "num_bands: " << num_bands() << "." << endl);
333 
334  // NB: Changing PHOTOMETIC to MINISWHITE doesn't seem to have any visible affect,
335  // although the resulting files differ. jhrg 11/21/12
336  char **options = NULL;
337  options = CSLSetNameValue(options, "PHOTOMETRIC", "MINISBLACK" ); // The default for GDAL
338  d_dest = Driver->Create(d_localfile.c_str(), width(), height(), num_bands(), GDT_Float64, options);
339  if (!d_dest)
340  throw Error("Could not create the geotiff dataset: " + string(CPLGetLastErrorMsg()));
341 
342  d_dest->SetGeoTransform(geo_transform());
343  // Take the mapping data from the first variable
344  // var(0)->get_projection(d_dds, d_dest);
345 
346  BESDEBUG("fong3", "Made new temp file and set georeferencing (" << num_bands() << " vars)." << endl);
347 
348  bool projection_set = false;
349  string wkt = "";
350  for (int i = 0; i < num_bands(); ++i) {
351  FONgBaseType *fbtp = var(i);
352 
353  if (!projection_set) {
354  wkt = fbtp->get_projection(d_dds);
355  if (d_dest->SetProjection(wkt.c_str()) != CPLE_None)
356  throw Error("Could not set the projection: " + string(CPLGetLastErrorMsg()));
357  projection_set = true;
358  }
359  else {
360  string wkt_i = fbtp->get_projection(d_dds);
361  if (wkt_i != wkt)
362  throw Error("In building a multiband response, different bands had different projection information.");
363  }
364 #if 0
365  d_dest->AddBand(GDT_Float64, 0);
366  GDALRasterBand *band = d_dest->GetRasterBand(d_dest->GetRasterCount());
367  if (!band)
368  throw Error("Could not get the " + long_to_string(i) + "th band: " + string(CPLGetLastErrorMsg()));
369 #endif
370  GDALRasterBand *band = d_dest->GetRasterBand(i+1);
371  if (!band)
372  throw Error("Could not get the " + long_to_string(i+1) + "th band: " + string(CPLGetLastErrorMsg()));
373 
374  try {
375  // TODO We can read any of the basic DAP2 types and let RasterIO convert it to any other type.
376  double *data = fbtp->get_data();
377 
378  // hack the values; because the missing value used with many datasets
379  // is often really small it'll skew the mapping of values to the grayscale
380  // that GDAL performs. Move the no_data values to something closer to the
381  // other values in the dataset.
382  BESDEBUG("fong3", "no_data_type(): " << no_data_type() << endl);
383  if (no_data_type() != none)
384  m_scale_data(data);
385 
386  BESDEBUG("fong3", "calling band->RasterIO" << endl);
387  CPLErr error = band->RasterIO(GF_Write, 0, 0, width(), height(),
388  data, width(), height(), GDT_Float64, 0, 0);
389  delete[] data;
390 
391  if (error != CPLE_None)
392  throw Error("Could not write data for band: " + long_to_string(i+1) + ": " + string(CPLGetLastErrorMsg()));
393  }
394  catch (...) {
395  GDALClose(d_dest);
396  throw;
397  }
398  }
399 
400  GDALClose(d_dest);
401 }
402 
418 {
419  // Scan the entire DDS looking for variables that have been 'projected' and
420  // build the delegate objects for them.
421  find_vars(d_dds, *this);
422 
423  for (int i = 0; i < num_bands(); ++i)
424  if (!effectively_two_D(var(i)))
425  throw Error("GeoTiff responses can consist of two-dimensional variables only; use constraints to reduce the size of Grids as needed.");
426 
427  GDALAllRegister();
428  CPLSetErrorHandler(CPLQuietErrorHandler);
429 
430  GDALDriver *Driver = GetGDALDriverManager()->GetDriverByName("MEM");
431  if( Driver == NULL )
432  throw Error("Could not get the MEM driver from/for GDAL: " + string(CPLGetLastErrorMsg()));
433 
434  char **Metadata = Driver->GetMetadata();
435  if (!CSLFetchBoolean(Metadata, GDAL_DCAP_CREATE, FALSE))
436  throw Error("Driver JP2OpenJPEG does not support dataset creation.");
437 
438  // No creation options for a memory dataset
439  // NB: This is where the type of the bands is set. JPEG2000 only supports integer types.
440  d_dest = Driver->Create("in_memory_dataset", width(), height(), num_bands(), GDT_Int32, 0 /*options*/);
441  if (!d_dest)
442  throw Error("Could not create in-memory dataset: " + string(CPLGetLastErrorMsg()));
443 
444  d_dest->SetGeoTransform(geo_transform());
445 
446  BESDEBUG("fong3", "Made new temp file and set georeferencing (" << num_bands() << " vars)." << endl);
447 
448  bool projection_set = false;
449  string wkt = "";
450  for (int i = 0; i < num_bands(); ++i) {
451  FONgBaseType *fbtp = var(i);
452 
453  if (!projection_set) {
454  wkt = fbtp->get_projection(d_dds);
455  if (d_dest->SetProjection(wkt.c_str()) != CPLE_None)
456  throw Error("Could not set the projection: " + string(CPLGetLastErrorMsg()));
457  projection_set = true;
458  }
459  else {
460  string wkt_i = fbtp->get_projection(d_dds);
461  if (wkt_i != wkt)
462  throw Error("In building a multiband response, different bands had different projection information.");
463  }
464 
465  GDALRasterBand *band = d_dest->GetRasterBand(i+1);
466  if (!band)
467  throw Error("Could not get the " + long_to_string(i+1) + "th band: " + string(CPLGetLastErrorMsg()));
468 
469  try {
470  // TODO We can read any of the basic DAP2 types and let RasterIO convert it to any other type.
471  double *data = fbtp->get_data();
472 
473  // hack the values; because the missing value used with many datasets
474  // is often really small it'll skew the mapping of values to the grayscale
475  // that GDAL performs. Move the no_data values to something closer to the
476  // other values in the dataset.
477  BESDEBUG("fong3", "no_data_type(): " << no_data_type() << endl);
478  if (no_data_type() != none)
479  m_scale_data(data);
480 
481  BESDEBUG("fong3", "calling band->RasterIO" << endl);
482 
483  // NB: Here the 'type' value indicates the type of data in the buffer. The
484  // type of the band is set above when the dataset is created.
485  CPLErr error = band->RasterIO(GF_Write, 0, 0, width(), height(),
486  data, width(), height(), GDT_Float64, 0, 0);
487  delete[] data;
488 
489  if (error != CPLE_None)
490  throw Error(CPLGetLastErrorMsg());
491  }
492  catch (...) {
493  GDALClose(d_dest);
494  throw;
495  }
496  }
497 
498  // Now get the OpenJPEG driver and use CreateCopy() on the d_dest "MEM" dataset
499  GDALDataset *jpeg_dst = 0;
500  try {
501  Driver = GetGDALDriverManager()->GetDriverByName("JP2OpenJPEG");
502  if (Driver == NULL)
503  throw Error("Could not get driver for JP2OpenJPEG: " + string(CPLGetLastErrorMsg()));
504 
505  // The JPEG2000 drivers only support CreateCopy()
506  char **Metadata = Driver->GetMetadata();
507  if (!CSLFetchBoolean(Metadata, GDAL_DCAP_CREATECOPY, FALSE))
508  BESDEBUG("fong", "Driver JP2OpenJPEG does not support dataset creation via 'CreateCopy()'." << endl);
509  //throw Error("Driver JP2OpenJPEG does not support dataset creation via 'CreateCopy()'.");
510 
511  char **options = NULL;
512  options = CSLSetNameValue(options, "CODEC", "JP2");
513  options = CSLSetNameValue(options, "GMLJP2", "YES");
514  options = CSLSetNameValue(options, "GeoJP2", "NO");
515  options = CSLSetNameValue(options, "QUALITY", "100"); // 25 is the default;
516  options = CSLSetNameValue(options, "REVERSIBLE", "YES"); // lossy compression
517 
518  BESDEBUG("fong3", "Before JPEG2000 CreateCopy, number of bands: " << d_dest->GetRasterCount() << endl);
519 
520  jpeg_dst = Driver->CreateCopy(d_localfile.c_str(), d_dest, FALSE/*strict*/,
521  options, NULL/*progress*/, NULL/*progress data*/);
522 
523  if (!jpeg_dst)
524  throw Error("Could not create the JPEG200 dataset: " + string(CPLGetLastErrorMsg()));
525  }
526  catch (...) {
527  GDALClose(d_dest);
528  GDALClose (jpeg_dst);
529  throw;
530  }
531 
532  GDALClose(d_dest);
533  GDALClose(jpeg_dst);
534 
535 }
virtual int height()
exception thrown if inernal error encountered
virtual void transform_to_geotiff()
Transforms the variables of the DataDDS to a GeoTiff file.
A DAP Grid with file out netcdf information included.
Definition: FONgGrid.h:50
virtual string get_projection(libdap::DDS *dds)=0
Get the GDAL/OGC WKT projection string.
STL namespace.
virtual ~FONgTransform()
Destructor.
virtual double * geo_transform()
Build the geotransform array needed by GDAL.
static class NCMLUtil overview
FONgTransform(libdap::DDS *dds, libdap::ConstraintEvaluator &evaluator, const string &localfile)
Constructor that creates transformation object from the specified DataDDS object to the specified fil...
virtual int width()
#define NULL
Definition: wcsUtil.h:65
virtual libdap::Type type()
Definition: FONgBaseType.h:51
FONgBaseType * var(int i)
A DAP BaseType with file out gdal information included.
Definition: FONgBaseType.h:38
#define FALSE
Definition: gse_parser.h:33
double no_data()
Definition: FONgTransform.h:89
no_data_type_t no_data_type()
Definition: FONgTransform.h:93
Transformation object that converts an OPeNDAP DataDDS to a GeoTiff file.
Definition: FONgTransform.h:41
#define BESDEBUG(x, y)
macro used to send debug information to the debug stream
Definition: BESDebug.h:64
virtual double * get_data()=0
Get the data values for the band(s). Call must delete.
virtual void transform_to_jpeg2000()
Transforms the variables of the DataDDS to a JPEG2000 file.