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