bes  Updated for version 3.20.6
FONgTransform.cc
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 <set>
30 
31 #include <gdal.h>
32 #include <gdal_priv.h>
33 #include <gdal_utils.h>
34 
35 #include <DDS.h>
36 #include <ConstraintEvaluator.h>
37 
38 #include <Structure.h>
39 #include <Array.h>
40 #include <Grid.h>
41 #include <util.h>
42 #include <Error.h>
43 
44 #include <BESDebug.h>
45 #include <BESInternalError.h>
46 
47 #include "FONgRequestHandler.h"
48 #include "FONgTransform.h"
49 
50 #include "FONgGrid.h"
51 
52 using namespace std;
53 using namespace libdap;
54 
65 FONgTransform::FONgTransform(DDS *dds, ConstraintEvaluator &/*evaluator*/, const string &localfile) :
66  d_dest(0), d_dds(dds), d_localfile(localfile),
67  d_geo_transform_set(false), d_width(0.0), d_height(0.0), d_top(0.0), d_left(0.0),
68  d_bottom(0.0), d_right(0.0), d_no_data(0.0), d_no_data_type(none), d_num_bands(0)
69 {
70  BESDEBUG("fong3", "dds numvars = " << dds->num_var() << endl);
71  if (localfile.empty())
72  throw BESInternalError("Empty local file name passed to constructor", __FILE__, __LINE__);
73 }
74 
80 {
81  vector<FONgGrid *>::iterator i = d_fong_vars.begin();
82  vector<FONgGrid *>::iterator e = d_fong_vars.end();
83  while (i != e) {
84  delete (*i++);
85  }
86 }
87 
91 static bool
92 is_convertable_type(const BaseType *b)
93 {
94  switch (b->type()) {
95  case dods_grid_c:
96  return true;
97 
98  // TODO Add support for Array (?)
99  case dods_array_c:
100  default:
101  return false;
102  }
103 }
104 
111 static FONgGrid *convert(BaseType *v)
112 {
113  switch (v->type()) {
114  case dods_grid_c:
115  return new FONgGrid(static_cast<Grid*>(v));
116 
117  default:
118  throw BESInternalError("file out GeoTiff, unable to write unknown variable type", __FILE__, __LINE__);
119  }
120 }
121 
122 #if 0
123 
141 void FONgTransform::m_scale_data(double *data)
142 {
143  // It is an error to call this if no_data_type() is 'none'
144  set<double> hist;
145  for (int i = 0; i < width() * height(); ++i)
146  hist.insert(data[i]);
147 
148  BESDEBUG("fong3", "Hist count: " << hist.size() << endl);
149 
150  if (no_data_type() == negative && hist.size() > 1) {
151  // Values are sorted by 'weak' <, so this is the smallest value
152  // and ++i is the next smallest value. Assume no_data is the
153  // smallest value in the data set and ++i is the smallest actual
154  // data value. Reset the no_data value to be 1.0 < the smallest
155  // actual value. This makes for a good grayscale photometric
156  // GeoTiff w/o changing the actual data values.
157  set<double>::iterator i = hist.begin();
158  double smallest = *(++i);
159  if (fabs(smallest + no_data()) > 1) {
160  smallest -= 1.0;
161 
162  BESDEBUG("fong3", "New no_data value: " << smallest << endl);
163 
164  for (int i = 0; i < width() * height(); ++i) {
165  if (data[i] <= no_data()) {
166  data[i] = smallest;
167  }
168  }
169  }
170  }
171  else {
172  set<double>::reverse_iterator r = hist.rbegin();
173  double biggest = *(++r);
174  if (fabs(no_data() - biggest) > 1) {
175  biggest += 1.0;
176 
177  BESDEBUG("fong3", "New no_data value: " << biggest << endl);
178 
179  for (int i = 0; i < width() * height(); ++i) {
180  if (data[i] >= no_data()) {
181  data[i] = biggest;
182  }
183  }
184  }
185  }
186 }
187 #endif
188 
201 {
202 
203  BESDEBUG("fong3", "left: " << d_left << ", top: " << d_top << ", right: " << d_right << ", bottom: " << d_bottom << endl);
204  BESDEBUG("fong3", "width: " << d_width << ", height: " << d_height << endl);
205 
206  d_gt[0] = d_left; // The leftmost 'x' value, which is longitude
207  d_gt[3] = d_top; // The topmost 'y' value, which is latitude should be max latitude
208 
209  // We assume data w/o any rotation (a north-up image)
210  d_gt[2] = 0.0;
211  d_gt[4] = 0.0;
212 
213  // Compute the lower left values. Note that wehn GDAL builds the geotiff
214  // output dataset, it correctly inverts the image when the source data has
215  // inverted latitude values.?
216  d_gt[1] = (d_right - d_left) / d_width; // width in pixels; top and bottom in lat
217  d_gt[5] = (d_bottom - d_top) / d_height;
218 
219  BESDEBUG("fong3", "gt[0]: " << d_gt[0] << ", gt[1]: " << d_gt[1] << ", gt[2]: " << d_gt[2] \
220  << ", gt[3]: " << d_gt[3] << ", gt[4]: " << d_gt[4] << ", gt[5]: " << d_gt[5] << endl);
221 
222  return d_gt;
223 }
224 
236 bool FONgTransform::effectively_two_D(FONgGrid *fbtp)
237 {
238  if (fbtp->type() == dods_grid_c) {
239  Grid *g = static_cast<FONgGrid*>(fbtp)->grid();
240 
241  if (g->get_array()->dimensions() == 2)
242  return true;
243 
244  // count the dimensions with sizes other than 1
245  Array *a = g->get_array();
246  int dims = 0;
247  for (Array::Dim_iter d = a->dim_begin(); d != a->dim_end(); ++d) {
248  if (a->dimension_size(d, true) > 1)
249  ++dims;
250  }
251 
252  return dims == 2;
253  }
254 
255  return false;
256 }
257 
258 static void build_delegate(BaseType *btp, FONgTransform &t)
259 {
260  if (btp->send_p() && is_convertable_type(btp)) {
261  BESDEBUG( "fong3", "converting " << btp->name() << endl);
262 
263  // Build the delegate
264  FONgGrid *fb = convert(btp);
265 
266  // Get the information needed for the transform.
267  // Note that FONgGrid::extract_coordinates() also pushes the
268  // new FONgBaseType instance onto the FONgTransform's vector of
269  // delagate variable objects.
270  fb->extract_coordinates(t);
271  }
272 }
273 
274 // Helper function to descend into Structures looking for Grids (and Arrays
275 // someday).
276 static void find_vars_helper(Structure *s, FONgTransform &t)
277 {
278  Structure::Vars_iter vi = s->var_begin();
279  while (vi != s->var_end()) {
280  if ((*vi)->send_p() && is_convertable_type(*vi)) {
281  build_delegate(*vi, t);
282  }
283  else if ((*vi)->type() == dods_structure_c) {
284  find_vars_helper(static_cast<Structure*>(*vi), t);
285  }
286 
287  ++vi;
288  }
289 }
290 
291 // Helper function to scan the DDS top-level for Grids, ...
292 // Note that FONgGrid::extract_coordinates() sets a bunch of
293 // values in the FONgBaseType instance _and_ this instance of
294 // FONgTransform. One of these is 'num_bands()'. For GeoTiff,
295 // num_bands() must be 1. This is tested in transform().
296 static void find_vars(DDS *dds, FONgTransform &t)
297 {
298  DDS::Vars_iter vi = dds->var_begin();
299  while (vi != dds->var_end()) {
300  BESDEBUG( "fong3", "looking at: " << (*vi)->name() << " and it is/isn't selected: " << (*vi)->send_p() << endl);
301  if ((*vi)->send_p() && is_convertable_type(*vi)) {
302  BESDEBUG( "fong3", "converting " << (*vi)->name() << endl);
303  build_delegate(*vi, t);
304  }
305  else if ((*vi)->type() == dods_structure_c) {
306  find_vars_helper(static_cast<Structure*>(*vi), t);
307  }
308 
309  ++vi;
310  }
311 }
312 
320 {
321  // Scan the entire DDS looking for variables that have been 'projected' and
322  // build the delegate objects for them.
323  find_vars(d_dds, *this);
324 
325  for (int i = 0; i < num_bands(); ++i)
326  if (!effectively_two_D(var(i)))
327  throw Error("GeoTiff responses can consist of two-dimensional variables only; use constraints to reduce the size of Grids as needed.");
328 
329  GDALDriver *Driver = GetGDALDriverManager()->GetDriverByName("MEM");
330  if( Driver == NULL )
331  throw Error("Could not get the MEM driver from/for GDAL: " + string(CPLGetLastErrorMsg()));
332 
333  char **Metadata = Driver->GetMetadata();
334  if (!CSLFetchBoolean(Metadata, GDAL_DCAP_CREATE, FALSE))
335  throw Error("Could not make output format.");
336 
337  BESDEBUG("fong3", "num_bands: " << num_bands() << "." << endl);
338 
339  // Create band in the memory using data type GDT_Byte.
340  // Most image viewers reproduce tiff files with Bits/Sample: 8
341 
342  // Make this type depend on the value of a bes.conf parameter.
343  // See FONgRequestHandler.cc and FONgRequestHandler::d_use_byte_for_geotiff_bands.
344  // FIXME This is a hack. But maybe it's good enough?
345  // jhrg 3/20/19
346  if (FONgRequestHandler::get_use_byte_for_geotiff_bands())
347  d_dest = Driver->Create("in_memory_dataset", width(), height(), num_bands(), GDT_Byte, 0/*options*/);
348  else
349  d_dest = Driver->Create("in_memory_dataset", width(), height(), num_bands(), GDT_Float32, 0/*options*/);
350 
351  if (!d_dest)
352  throw Error("Could not create the geotiff dataset: " + string(CPLGetLastErrorMsg()));
353 
354  d_dest->SetGeoTransform(geo_transform());
355 
356  BESDEBUG("fong3", "Made new temp file and set georeferencing (" << num_bands() << " vars)." << endl);
357 
358  bool projection_set = false;
359  string wkt = "";
360  for (int i = 0; i < num_bands(); ++i) {
361  FONgGrid *fbtp = var(i);
362 
363  if (!projection_set) {
364  wkt = fbtp->get_projection(d_dds);
365  if (d_dest->SetProjection(wkt.c_str()) != CPLE_None)
366  throw Error("Could not set the projection: " + string(CPLGetLastErrorMsg()));
367  projection_set = true;
368  }
369  else {
370  string wkt_i = fbtp->get_projection(d_dds);
371  if (wkt_i != wkt)
372  throw Error("In building a multiband response, different bands had different projection information.");
373  }
374 
375  GDALRasterBand *band = d_dest->GetRasterBand(i+1);
376  if (!band)
377  throw Error("Could not get the " + long_to_string(i+1) + "th band: " + string(CPLGetLastErrorMsg()));
378 
379  double *data = 0;
380 
381  try {
382  // TODO We can read any of the basic DAP2 types and let RasterIO convert it to any other type.
383  // That is, we can read these values in their native type, skipping the conversion here. That
384  // would make this code faster. jhrg 3/20/19
385  data = fbtp->get_data();
386 
387  // If the latitude values are inverted, the 0th value will be less than
388  // the last value.
389  vector<double> local_lat;
390  // TODO: Rewrite this to avoid expensive procedure
391  extract_double_array(fbtp->d_lat, local_lat);
392 
393  // NB: Here the 'type' value indicates the type of data in the buffer. The
394  // type of the band is set above when the dataset is created.
395 
396  if (local_lat[0] < local_lat[local_lat.size() - 1]) {
397  BESDEBUG("fong3", "Writing reversed raster. Lat[0] = " << local_lat[0] << endl);
398  //---------- write reverse raster----------
399  for (int row = 0; row <= height()-1; ++row) {
400  int offsety=height()-row-1;
401  CPLErr error_write = band->RasterIO(GF_Write, 0, offsety, width(), 1, data+(row*width()), width(), 1, GDT_Float64, 0, 0);
402  if (error_write != CPLE_None)
403  throw Error("Could not write data for band: " + long_to_string(i + 1) + ": " + string(CPLGetLastErrorMsg()));
404  }
405  }
406  else {
407  BESDEBUG("fong3", "calling band->RasterIO" << endl);
408  CPLErr error = band->RasterIO(GF_Write, 0, 0, width(), height(), data, width(), height(), GDT_Float64, 0, 0);
409  if (error != CPLE_None)
410  throw Error("Could not write data for band: " + long_to_string(i+1) + ": " + string(CPLGetLastErrorMsg()));
411  }
412 
413  delete[] data;
414  }
415  catch (...) {
416  delete[] data;
417  GDALClose(d_dest);
418  throw;
419  }
420  }
421 
422  // Now get the GTiff driver and use CreateCopy() on the d_dest "MEM" dataset
423  GDALDataset *tif_dst = 0;
424  try {
425  Driver = GetGDALDriverManager()->GetDriverByName("GTiff");
426  if (Driver == NULL)
427  throw Error("Could not get driver for GeoTiff: " + string(CPLGetLastErrorMsg()));
428 
429  // The drivers only support CreateCopy()
430  char **Metadata = Driver->GetMetadata();
431  if (!CSLFetchBoolean(Metadata, GDAL_DCAP_CREATECOPY, FALSE))
432  BESDEBUG("fong", "Driver does not support dataset creation via 'CreateCopy()'." << endl);
433 
434  // NB: Changing PHOTOMETIC to MINISWHITE doesn't seem to have any visible affect,
435  // although the resulting files differ. jhrg 11/21/12
436  char **options = NULL;
437  options = CSLSetNameValue(options, "PHOTOMETRIC", "MINISBLACK" ); // The default for GDAL
438 
439  BESDEBUG("fong3", "Before CreateCopy, number of bands: " << d_dest->GetRasterCount() << endl);
440 
441  // implementation of gdal_translate -scale to adjust color levels
442  char **argv = NULL;
443  argv = CSLAddString(argv, "-scale");
444  GDALTranslateOptions *opts = GDALTranslateOptionsNew(argv, NULL /*binary options*/);
445  int usage_error = CE_None;
446  GDALDatasetH dst_handle = GDALTranslate(d_localfile.c_str(), d_dest, opts, &usage_error);
447  if (!dst_handle || usage_error != CE_None) {
448  GDALClose(dst_handle);
449  GDALTranslateOptionsFree(opts);
450  string msg = string("Error calling GDAL translate: ") + CPLGetLastErrorMsg();
451  BESDEBUG("fong3", "ERROR transform_to_geotiff(): " << msg << endl);
452  throw BESError(msg, BES_INTERNAL_ERROR, __FILE__, __LINE__);
453  }
454 
455  tif_dst = Driver->CreateCopy(d_localfile.c_str(), reinterpret_cast<GDALDataset*>(dst_handle), FALSE/*strict*/,
456  options, NULL/*progress*/, NULL/*progress data*/);
457 
458  GDALClose(dst_handle);
459  GDALTranslateOptionsFree(opts);
460 
461  if (!tif_dst)
462  throw Error("Could not create the GeoTiff dataset: " + string(CPLGetLastErrorMsg()));
463  }
464  catch (...) {
465  GDALClose(d_dest);
466  GDALClose (tif_dst);
467  throw;
468  }
469  GDALClose(d_dest);
470  GDALClose(tif_dst);
471 }
472 
488 {
489  // Scan the entire DDS looking for variables that have been 'projected' and
490  // build the delegate objects for them.
491  find_vars(d_dds, *this);
492 
493  for (int i = 0; i < num_bands(); ++i)
494  if (!effectively_two_D(var(i)))
495  throw Error("GeoTiff responses can consist of two-dimensional variables only; use constraints to reduce the size of Grids as needed.");
496 
497  GDALDriver *Driver = GetGDALDriverManager()->GetDriverByName("MEM");
498  if( Driver == NULL )
499  throw Error("Could not get the MEM driver from/for GDAL: " + string(CPLGetLastErrorMsg()));
500 
501  char **Metadata = Driver->GetMetadata();
502  if (!CSLFetchBoolean(Metadata, GDAL_DCAP_CREATE, FALSE))
503  throw Error("Driver JP2OpenJPEG does not support dataset creation.");
504 
505  // No creation options for a memory dataset
506  // NB: This is where the type of the bands is set. JPEG2000 only supports integer types.
507  d_dest = Driver->Create("in_memory_dataset", width(), height(), num_bands(), GDT_Byte, 0 /*options*/);
508  if (!d_dest)
509  throw Error("Could not create in-memory dataset: " + string(CPLGetLastErrorMsg()));
510 
511  d_dest->SetGeoTransform(geo_transform());
512 
513  BESDEBUG("fong3", "Made new temp file and set georeferencing (" << num_bands() << " vars)." << endl);
514 
515  bool projection_set = false;
516  string wkt = "";
517  for (int i = 0; i < num_bands(); ++i) {
518  FONgGrid *fbtp = var(i);
519 
520  if (!projection_set) {
521  wkt = fbtp->get_projection(d_dds);
522  if (d_dest->SetProjection(wkt.c_str()) != CPLE_None)
523  throw Error("Could not set the projection: " + string(CPLGetLastErrorMsg()));
524  projection_set = true;
525  }
526  else {
527  string wkt_i = fbtp->get_projection(d_dds);
528  if (wkt_i != wkt)
529  throw Error("In building a multiband response, different bands had different projection information.");
530  }
531 
532  GDALRasterBand *band = d_dest->GetRasterBand(i+1);
533  if (!band)
534  throw Error("Could not get the " + long_to_string(i+1) + "th band: " + string(CPLGetLastErrorMsg()));
535 
536  double *data = 0;
537  try {
538  // TODO We can read any of the basic DAP2 types and let RasterIO convert it to any other type.
539  data = fbtp->get_data();
540 
541  // If the latitude values are inverted, the 0th value will be less than
542  // the last value.
543  vector<double> local_lat;
544  // TODO: Rewrite this to avoid expensive procedure
545  extract_double_array(fbtp->d_lat, local_lat);
546 
547  // NB: Here the 'type' value indicates the type of data in the buffer. The
548  // type of the band is set above when the dataset is created.
549 
550  if (local_lat[0] < local_lat[local_lat.size() - 1]) {
551  BESDEBUG("fong3", "Writing reversed raster. Lat[0] = " << local_lat[0] << endl);
552  //---------- write reverse raster----------
553  for (int row = 0; row <= height()-1; ++row) {
554  int offsety=height()-row-1;
555  CPLErr error_write = band->RasterIO(GF_Write, 0, offsety, width(), 1, data+(row*width()), width(), 1, GDT_Float64, 0, 0);
556  if (error_write != CPLE_None)
557  throw Error("Could not write data for band: " + long_to_string(i + 1) + ": " + string(CPLGetLastErrorMsg()));
558  }
559  }
560  else {
561  BESDEBUG("fong3", "calling band->RasterIO" << endl);
562  CPLErr error = band->RasterIO(GF_Write, 0, 0, width(), height(), data, width(), height(), GDT_Float64, 0, 0);
563  if (error != CPLE_None)
564  throw Error("Could not write data for band: " + long_to_string(i+1) + ": " + string(CPLGetLastErrorMsg()));
565  }
566 
567  delete[] data;
568 
569  }
570  catch (...) {
571  delete[] data;
572  GDALClose(d_dest);
573  throw;
574  }
575  }
576 
577  // Now get the OpenJPEG driver and use CreateCopy() on the d_dest "MEM" dataset
578  GDALDataset *jpeg_dst = 0;
579  try {
580  Driver = GetGDALDriverManager()->GetDriverByName("JP2OpenJPEG");
581  if (Driver == NULL)
582  throw Error("Could not get driver for JP2OpenJPEG: " + string(CPLGetLastErrorMsg()));
583 
584  // The JPEG2000 drivers only support CreateCopy()
585  char **Metadata = Driver->GetMetadata();
586  if (!CSLFetchBoolean(Metadata, GDAL_DCAP_CREATECOPY, FALSE))
587  BESDEBUG("fong", "Driver JP2OpenJPEG does not support dataset creation via 'CreateCopy()'." << endl);
588  //throw Error("Driver JP2OpenJPEG does not support dataset creation via 'CreateCopy()'.");
589 
590  char **options = NULL;
591  options = CSLSetNameValue(options, "CODEC", "JP2");
592  options = CSLSetNameValue(options, "GMLJP2", "YES");
593  options = CSLSetNameValue(options, "GeoJP2", "NO");
594  options = CSLSetNameValue(options, "QUALITY", "100"); // 25 is the default;
595  options = CSLSetNameValue(options, "REVERSIBLE", "YES"); // lossy compression
596 
597  BESDEBUG("fong3", "Before JPEG2000 CreateCopy, number of bands: " << d_dest->GetRasterCount() << endl);
598 
599  // implementation of gdal_translate -scale to adjust color levels
600  char **argv = NULL;
601  argv = CSLAddString(argv, "-scale");
602  GDALTranslateOptions *opts = GDALTranslateOptionsNew(argv, NULL /*binary options*/);
603  int usage_error = CE_None;
604  GDALDatasetH dst_h = GDALTranslate(d_localfile.c_str(), d_dest, opts, &usage_error);
605  if (!dst_h || usage_error != CE_None) {
606  GDALClose(dst_h);
607  GDALTranslateOptionsFree(opts);
608  string msg = string("Error calling GDAL translate: ") + CPLGetLastErrorMsg();
609  BESDEBUG("fong3", "ERROR transform_to_jpeg2000(): " << msg << endl);
610  throw BESError(msg, BES_INTERNAL_ERROR, __FILE__, __LINE__);
611  }
612 
613  jpeg_dst = Driver->CreateCopy(d_localfile.c_str(), reinterpret_cast<GDALDataset*>(dst_h), FALSE/*strict*/,
614  options, NULL/*progress*/, NULL/*progress data*/);
615 
616  GDALClose(dst_h);
617  GDALTranslateOptionsFree(opts);
618 
619  if (!jpeg_dst)
620  throw Error("Could not create the JPEG200 dataset: " + string(CPLGetLastErrorMsg()));
621  }
622  catch (...) {
623  GDALClose(d_dest);
624  GDALClose (jpeg_dst);
625  throw;
626  }
627 
628  GDALClose(d_dest);
629  GDALClose(jpeg_dst);
630 }
FONgTransform::~FONgTransform
virtual ~FONgTransform()
Destructor.
Definition: FONgTransform.cc:79
FONgTransform::transform_to_geotiff
virtual void transform_to_geotiff()
Transforms the variables of the DataDDS to a GeoTiff file.
Definition: FONgTransform.cc:319
FONgGrid::extract_coordinates
virtual void extract_coordinates(FONgTransform &t)
Get the GDAL/OGC WKT projection string.
Definition: FONgGrid.cc:203
FONgTransform::geo_transform
virtual double * geo_transform()
Build the geotransform array needed by GDAL.
Definition: FONgTransform.cc:200
libdap
Definition: BESDapFunctionResponseCache.h:35
FONgGrid::get_data
virtual double * get_data()
Get the data values for the band(s). Call must delete.
Definition: FONgGrid.cc:337
BESInternalError
exception thrown if internal error encountered
Definition: BESInternalError.h:43
FONgGrid
A DAP Grid with file out netcdf information included.
Definition: FONgGrid.h:52
FONgTransform::transform_to_jpeg2000
virtual void transform_to_jpeg2000()
Transforms the variables of the DataDDS to a JPEG2000 file.
Definition: FONgTransform.cc:487
FONgTransform
Transformation object that converts an OPeNDAP DataDDS to a GeoTiff file.
Definition: FONgTransform.h:41
Error
FONgGrid::get_projection
string get_projection(libdap::DDS *dds)
Set the projection information For Grids, look for CF information. If it's not present,...
Definition: FONgGrid.cc:296
FONgTransform::FONgTransform
FONgTransform(libdap::DDS *dds, libdap::ConstraintEvaluator &evaluator, const string &localfile)
Constructor that creates transformation object from the specified DataDDS object to the specified fil...
Definition: FONgTransform.cc:65
BESError
Abstract exception class for the BES with basic string message.
Definition: BESError.h:58