/***********************************************************************************/
/* Copyright 2009 WSL Institute for Snow and Avalanche Research SLF-DAVOS */
/***********************************************************************************/
/* This file is part of MeteoIO.
MeteoIO is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
MeteoIO is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with MeteoIO. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
namespace mio {
/**
* @page pngio PNGIO
* @section pngio_format Format
* This plugin write data to the Portable Network Graphics format (see https://secure.wikimedia.org/wikipedia/en/wiki/Portable_Network_Graphics).
* No data read has been implemented, because reading an existing file would require the exact knowlege of the color gradient that has been used
* to create it. When writing grids, various color gradients will be used depending on the parameter that the data represents. Nodata values
* are represented by transparent pixels (transparency is acheived through a transparent color instead of a true alpha channel for size and performance).
* If a grid containing no data (ie: size 0x0) is sent to the plugin, then no file will be written.
* Finally, the naming scheme for meteo grids should be: YYYY-MM-DDTHH.mm_{MeteoGrids::Parameters}.png
*
* @section pngio_units Units
* All units are MKSA except temperatures that are expressed in celcius.
*
* @section pngio_keywords Keywords
* This plugin uses the following keywords:
* - COORDSYS: input coordinate system (see Coords) specified in the [Output] section
* - COORDPARAM: extra input coordinates parameters (see Coords) specified in the [Output] section
* - GRID2DPATH: meteo grids directory where to read the grids; [Output] section
* - PNG_LEGEND: plot legend on the side of the graph? (default: true)
* - PNG_MIN_SIZE: guarantee that a 2D plot will have at least the given size
* - PNG_MAX_SIZE: guarantee that a 2D plot will have at most the given size
* - PNG_SCALING: scaling algorithm, either nearest or bilinear (default=bilinear)
* - PNG_AUTOSCALE: autoscale for the color gradient? (default=true)
* - PNG_WORLD_FILE: create world file with each file? (default=false)
*
* Advanced parameters (ie: don't mess up with them if you don't know what you're doing):
* - PNG_INDEXED: create an indexed PNG? (default=true)
* - PNG_NR_LEVELS: number of colors to use (less=smaller files, but it must be at least 5 and less than 255. default=30)
* - PNG_SPEED_OPTIMIZE: optimize file creation for speed? (default=true, otherwise optimize for file size)
*
* The size are specified as width followed by height, with the separator being either a space, 'x' or '*'. If a minimum and a maximum size are given, the average of the smallest and largest permissible sizes will be used.
* The world file is used for geolocalization and goes alongside the graphics output. By convention,
* the file has the same name as the image file, with the third letter of the extension jammed with a w: tif->tfw, jpg->jqw.
* The format is the following:
* @code
* 5.000000000000 (size of pixel in x direction)
* 0.000000000000 (rotation term for row)
* 0.000000000000 (rotation term for column)
* -5.000000000000 (size of pixel in y direction)
* 492169.690845528910 (x coordinate of centre of upper left pixel in map units)
* 5426523.318065105000 (y coordinate of centre of upper left pixel in map units)
* @endcode
*
* @section pngio_example Example use
* @code
* GRID2D = PNG
* png_legend = false
* png_min_size = 400x400
* png_max_size = 1366*768
* @endcode
*
* @section pngio_compilation
* In order to compile this plugin, you need libpng and zlib. For Linux, please select both the libraries and their development files in your package manager.
*
* For Mac, you can either install using Fink or directly from source.
* In this case, please install zlib at the prefix of your choice (for example with the "--prefix=/usr/local" options
* to its configure script, build with "make" and install with "sudo make install"). Then provide the libpng configure script with the
* "--enable-shared --prefix=/usr/local" options (and build with "make" followed by "sudo make install").
*
* For Windows, you can find zlib at http://switch.dl.sourceforge.net/project/gnuwin32/zlib/1.2.3/zlib-1.2.3.exe
* and libpng at http://switch.dl.sourceforge.net/project/gnuwin32/libpng/1.2.37/libpng-1.2.37-setup.exe . Once this has been installed, if you plan on using
* Visual c++, you also need to edit the file zconf.h in the libpng installation directory and transform the line 287:
* @code
* #if 0 // HAVE_UNISTD_H etc etc
* @endcode
* should become
* @code
* #if 1 // HAVE_UNISTD_H etc etc
* @endcode
*/
const double PNGIO::plugin_nodata = -999.; //plugin specific nodata value. It can also be read by the plugin (depending on what is appropriate)
const unsigned char PNGIO::channel_depth = 8;
const unsigned char PNGIO::channel_max_color = 255;
const unsigned char PNGIO::transparent_grey = channel_max_color;
PNGIO::PNGIO(const std::string& configfile)
: cfg(configfile),
fp(NULL), autoscale(true), has_legend(true), has_world_file(false), optimize_for_speed(true),
indexed_png(true), nr_levels(30),
coordout(), coordoutparam(), grid2dpath(),
scaling("bilinear"), min_w(IOUtils::unodata), min_h(IOUtils::unodata), max_w(IOUtils::unodata), max_h(IOUtils::unodata),
metadata_key(), metadata_text()
{
setOptions();
}
PNGIO::PNGIO(const Config& cfgreader)
: cfg(cfgreader),
fp(NULL), autoscale(true), has_legend(true), has_world_file(false), optimize_for_speed(true),
indexed_png(true), nr_levels(30),
coordout(), coordoutparam(), grid2dpath(),
scaling("bilinear"), min_w(IOUtils::unodata), min_h(IOUtils::unodata), max_w(IOUtils::unodata), max_h(IOUtils::unodata),
metadata_key(), metadata_text()
{
setOptions();
}
PNGIO& PNGIO::operator=(const PNGIO& source) {
if (this != &source) {
fp = NULL;
autoscale = source.autoscale;
has_legend = source.has_legend;
has_world_file = source.has_world_file;
optimize_for_speed = source.optimize_for_speed;
indexed_png = source.indexed_png;
nr_levels = source.nr_levels;
coordout = source.coordout;
coordoutparam = source.coordoutparam;
grid2dpath = source.grid2dpath;
scaling = source.scaling;
min_w = source.min_w;
min_h = source.min_h;
max_w = source.max_w;
max_h = source.max_h;
metadata_key = source.metadata_key;
metadata_text = source.metadata_text;
}
return *this;
}
PNGIO::~PNGIO() throw()
{
if (fp!=NULL) fclose(fp);
fp=NULL;
}
void PNGIO::setOptions()
{
//default values have been set by the constructors
cfg.getValue("COORDSYS", "Output", coordout);
cfg.getValue("COORDPARAM", "Output", coordoutparam, IOUtils::nothrow);
cfg.getValue("GRID2DPATH", "Output", grid2dpath);
//cfg.getValue("TIME_ZONE", "Output", tz_out, IOUtils::nothrow);
//get size specifications
std::string min_size, max_size;
cfg.getValue("PNG_MIN_SIZE", "Output", min_size, IOUtils::nothrow);
if (!min_size.empty()) parse_size(min_size, min_w, min_h);
cfg.getValue("PNG_MAX_SIZE", "Output", max_size, IOUtils::nothrow);
if (!max_size.empty()) parse_size(max_size, max_w, max_h);
cfg.getValue("PNG_AUTOSCALE", "Output", autoscale, IOUtils::nothrow);
cfg.getValue("PNG_LEGEND", "Output", has_legend, IOUtils::nothrow);
cfg.getValue("PNG_SCALING", "Output", scaling, IOUtils::nothrow);
cfg.getValue("PNG_WORLD_FILE", "Output", has_world_file, IOUtils::nothrow);
if (has_legend) { //we need to save room for the legend
if (min_w!=IOUtils::unodata) min_w -= Legend::getLegendWidth();
if (max_w!=IOUtils::unodata) max_w -= Legend::getLegendWidth();
}
cfg.getValue("PNG_INDEXED", "Output", indexed_png, IOUtils::nothrow);
cfg.getValue("PNG_SPEED_OPTIMIZE", "Output", optimize_for_speed, IOUtils::nothrow);
unsigned int tmp=IOUtils::unodata;
cfg.getValue("PNG_NR_LEVELS", "Output", tmp, IOUtils::nothrow);
if (tmp!=IOUtils::unodata && (tmp>255 || tmp<5)) {
throw InvalidFormatException("PNG_NR_LEVELS must be between 5 and 255!", AT);
}
if (tmp!=IOUtils::unodata) nr_levels=static_cast(tmp);
}
void PNGIO::parse_size(const std::string& size_spec, size_t& width, size_t& height)
{
char rest[32] = "";
unsigned int w,h;
if (sscanf(size_spec.c_str(), "%u %u%31s", &w, &h, rest) < 2)
if (sscanf(size_spec.c_str(), "%u*%u%31s", &w, &h, rest) < 2)
if (sscanf(size_spec.c_str(), "%ux%u%31s", &w, &h, rest) < 2) {
std::ostringstream ss;
ss << "Can not parse PNGIO size specification \"" << size_spec << "\"";
throw InvalidFormatException(ss.str(), AT);
}
width = static_cast( w );
height = static_cast( h );
std::string tmp(rest);
IOUtils::trim(tmp);
if ((tmp.length() > 0) && tmp[0] != '#' && tmp[0] != ';') {//if line holds more than one value it's invalid
throw InvalidFormatException("Invalid PNGIO size specification \"" + size_spec + "\"", AT);
}
}
double PNGIO::getScaleFactor(const size_t& grid_w, const size_t& grid_h) const
{
if (grid_w==0 || grid_h==0) {
return 1.;
}
double min_factor = IOUtils::nodata;
if (min_w!=IOUtils::unodata) { //min_w & min_w are read together
const double min_w_factor = (double)min_w / (double)grid_w;
const double min_h_factor = (double)min_h / (double)grid_h;
min_factor = std::max(min_w_factor, min_h_factor);
}
double max_factor = IOUtils::nodata;
if (max_w!=IOUtils::unodata) { //max_w & max_h are read together
const double max_w_factor = (double)max_w / (double)grid_w;
const double max_h_factor = (double)max_h / (double)grid_h;
max_factor = std::min(max_w_factor, max_h_factor);
}
if (min_factor==IOUtils::nodata && max_factor==IOUtils::nodata)
return 1.; //no user given specification
if (min_factor!=IOUtils::nodata && max_factor!=IOUtils::nodata)
return (min_factor+max_factor)/2.; //both min & max -> average
//only one size specification provided -> return its matching factor
if (min_factor!=IOUtils::nodata)
return min_factor;
else
return max_factor;
}
Grid2DObject PNGIO::scaleGrid(const Grid2DObject& grid_in) const
{ //scale input image
const double factor = getScaleFactor(grid_in.getNx(), grid_in.getNy());
if (scaling=="nearest")
return ResamplingAlgorithms2D::NearestNeighbour(grid_in, factor);
else if (scaling=="bilinear")
return ResamplingAlgorithms2D::BilinearResampling(grid_in, factor);
else {
ostringstream ss;
ss << "Grid scaling algorithm \"" << scaling << "\" unknown";
throw UnknownValueException(ss.str(), AT);
}
}
void PNGIO::setFile(const std::string& filename, png_structp& png_ptr, png_infop& info_ptr, const size_t &width, const size_t &height)
{
// Open file for writing (binary mode)
if (!FileUtils::validFileAndPath(filename)) throw InvalidNameException(filename, AT);
errno=0;
fp = fopen(filename.c_str(), "wb");
if (fp == NULL)
throw AccessException("Error opening file \""+filename+"\", possible reason: "+std::string(strerror(errno)), AT);
// Initialize write structure
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_ptr == NULL) {
fclose(fp); fp=NULL;
throw IOException("Could not allocate write structure. Are you running with the same version of libpng that you linked to?", AT);
}
// Initialize info structure
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
fclose(fp); fp=NULL;
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
free(png_ptr);
throw IOException("Could not allocate info structure", AT);
}
// Setup Exception handling
#ifdef _MSC_VER
#pragma warning(disable:4611) //the setjmp of libpng has been set up so that it can safely be called from c++
#endif
if (setjmp(png_jmpbuf(png_ptr))) {
closePNG(png_ptr, info_ptr, NULL);
throw IOException("Error during png creation. Can not set jump pointer (I have no clue what it means too!)", AT);
}
png_init_io(png_ptr, fp);
if (optimize_for_speed) png_set_compression_level(png_ptr, Z_BEST_SPEED);
else png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_SUB|PNG_FILTER_UP); //any other filter is costly and brings close to nothing...
if (indexed_png) png_set_compression_strategy(png_ptr, Z_RLE); //Z_DEFAULT_STRATEGY, Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE
// Write header (8 bit colour depth). Full alpha channel with PNG_COLOR_TYPE_RGB_ALPHA
if (indexed_png) {
png_set_IHDR(png_ptr, info_ptr, static_cast(width), static_cast(height),
channel_depth, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
//set transparent color (ie: cheap transparency: leads to smaller files and shorter run times)
png_byte trans = 0; //by convention, the gradient define it as color 0
png_set_tRNS(png_ptr, info_ptr, &trans, 1, 0);
} else {
png_set_IHDR(png_ptr, info_ptr, static_cast(width), static_cast(height),
channel_depth, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
//set transparent color (ie: cheap transparency: leads to smaller files and shorter run times)
png_color_16 trans_rgb_value = {transparent_grey, transparent_grey, transparent_grey, transparent_grey, transparent_grey};
png_set_tRNS(png_ptr, info_ptr, 0, 0, &trans_rgb_value);
}
//set background color to help applications show the picture when no background is present
png_color_16 background = {channel_max_color, channel_max_color, channel_max_color, channel_max_color, channel_max_color};
png_set_background(png_ptr, &background, PNG_BACKGROUND_GAMMA_SCREEN, true, 1.0);
}
size_t PNGIO::setLegend(const size_t &ncols, const size_t &nrows, const double &min, const double &max, Array2D &legend_array) const
{
if (has_legend) {
const Legend legend(static_cast(nrows), min, max);
legend_array = legend.getLegend();
const size_t nx = legend_array.getNx();
return (ncols+nx);
} else {
return ncols;
}
}
void PNGIO::setPalette(const Gradient &gradient, png_structp& png_ptr, png_infop& info_ptr, png_color *palette)
{
std::vector pal;
size_t nr_colors;
gradient.getPalette(pal, nr_colors);
palette = (png_color*)calloc(nr_colors, sizeof (png_color)); //ie: three png_bytes, each being an unsigned char
for (size_t ii=0; ii(pal[interlace]);
palette[ii].green = static_cast(pal[interlace+1]);
palette[ii].blue = static_cast(pal[interlace+2]);
}
png_set_PLTE(png_ptr, info_ptr, palette, static_cast(nr_colors));
}
void PNGIO::writeDataSection(const Grid2DObject& grid, const Array2D& legend_array, const Gradient& gradient, const size_t& full_width, const png_structp& png_ptr, png_infop& info_ptr)
{
const size_t ncols = grid.getNx();
const size_t nrows = grid.getNy();
// Allocate memory for one row (3 bytes per pixel - RGB)
const unsigned char channels = (indexed_png)? 1 : 3; //4 for rgba
png_bytep row = (png_bytep)calloc(full_width, channels*sizeof(png_byte));
if (row==NULL) {
fclose(fp); fp=NULL;
png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
free(png_ptr);
throw IOException("Can not allocate row memory for PNG!", AT);
}
// Write image data
if (indexed_png) {
for (size_t y=nrows ; y-- > 0; ) {
size_t x=0;
for (; x(index);
}
for (; x(index);
}
png_write_row(png_ptr, row);
}
} else {
for (size_t y=nrows ; y -- > 0; ) {
size_t x=0;
for (; x(transparent_grey); row[i+1]=static_cast(transparent_grey); row[i+2]=static_cast(transparent_grey);
} else {
row[i]=static_cast(r); row[i+1]=static_cast(g); row[i+2]=static_cast(b);
}
}
for (; x(transparent_grey); row[i+1]=static_cast(transparent_grey); row[i+2]=static_cast(transparent_grey);
} else {
row[i]=static_cast(r); row[i+1]=static_cast(g); row[i+2]=static_cast(b);
}
}
png_write_row(png_ptr, row);
}
}
png_write_flush(png_ptr);
png_free(png_ptr, row);
}
void PNGIO::closePNG(png_structp& png_ptr, png_infop& info_ptr, png_color *palette)
{
png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
if (indexed_png && palette!=NULL) free(palette);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp); fp=NULL;
free(info_ptr);
free(png_ptr);
}
void PNGIO::write2DGrid(const Grid2DObject& grid_in, const std::string& filename)
{
const std::string full_name( grid2dpath+"/"+filename );
fp=NULL;
png_color *palette=NULL;
png_structp png_ptr=NULL;
png_infop info_ptr=NULL;
//scale input image
const Grid2DObject grid( scaleGrid(grid_in) );
const size_t ncols = grid.getNx(), nrows = grid.getNy();
if (ncols==0 || nrows==0) return;
const double min = grid.grid2D.getMin();
const double max = grid.grid2D.getMax();
Gradient gradient(Gradient::heat, min, max, autoscale);
if (indexed_png) gradient.setNrOfLevels(nr_levels);
Array2D legend_array; //it will remain empty if there is no legend
const size_t full_width = setLegend(ncols, nrows, min, max, legend_array);
setFile(full_name, png_ptr, info_ptr, full_width, nrows);
if (indexed_png) setPalette(gradient, png_ptr, info_ptr, palette);
if (has_world_file) writeWorldFile(grid, full_name);
createMetadata(grid);
metadata_key.push_back("Title"); //adding generic title
metadata_text.push_back("Unknown Gridded data");
writeMetadata(png_ptr, info_ptr);
writeDataSection(grid, legend_array, gradient, full_width, png_ptr, info_ptr);
png_write_end(png_ptr, NULL);
closePNG(png_ptr, info_ptr, palette);
}
void PNGIO::write2DGrid(const Grid2DObject& grid_in, const MeteoGrids::Parameters& parameter, const Date& date)
{
const bool isNormalParam = (parameter!=MeteoGrids::DEM && parameter!=MeteoGrids::SLOPE && parameter!=MeteoGrids::AZI);
std::string date_str = (isNormalParam)? date.toString(Date::ISO)+"_" : "";
std::replace( date_str.begin(), date_str.end(), ':', '.');
const std::string filename( grid2dpath + "/" + date_str + MeteoGrids::getParameterName(parameter) + ".png" );
fp=NULL;
png_color *palette=NULL;
png_structp png_ptr=NULL;
png_infop info_ptr=NULL;
//scale input image
Grid2DObject grid( scaleGrid(grid_in) );
const size_t ncols = grid.getNx(), nrows = grid.getNy();
if (ncols==0 || nrows==0) return;
double min = grid.grid2D.getMin();
double max = grid.grid2D.getMax();
Gradient gradient;
if (parameter==MeteoGrids::DEM) {
if (!autoscale) {
min = 0.; //we want a 3000 snow line with a full scale legend
max = 3500.;
gradient.set(Gradient::terrain, min, max, autoscale); //max used as snow line reference
} else
gradient.set(Gradient::terrain, min, max, autoscale);
} else if (parameter==MeteoGrids::SLOPE) {
gradient.set(Gradient::slope, min, max, autoscale);
} else if (parameter==MeteoGrids::SHADE) {
if (!autoscale) {
min = 0.; max = 1.;
}
gradient.set(Gradient::blktowhite, min, max, autoscale);
} else if (parameter==MeteoGrids::AZI) {
if (!autoscale) {
min = 0.;
max = 360.;
}
gradient.set(Gradient::azi, min, max, autoscale);
} else if (parameter==MeteoGrids::DW) {
if (!autoscale) {
min = 0.;
max = 360.;
}
gradient.set(Gradient::azi, min, max, autoscale);
} else if (parameter==MeteoGrids::HS) {
if (!autoscale) {
min = 0.; max = 2.5;
}
gradient.set(Gradient::blue, min, max, autoscale);
} else if (parameter==MeteoGrids::TA) {
grid.grid2D -= Cst::t_water_freezing_pt; //convert to celsius
if (!autoscale) {
min = -15.; max = 15.;
} else {
min -= Cst::t_water_freezing_pt;
max -= Cst::t_water_freezing_pt;
}
gradient.set(Gradient::heat, min, max, autoscale);
} else if (parameter==MeteoGrids::TSS) {
grid.grid2D -= Cst::t_water_freezing_pt; //convert to celsius
if (!autoscale) {
min = -20.; max = 5.;
} else {
min -= Cst::t_water_freezing_pt;
max -= Cst::t_water_freezing_pt;
}
gradient.set(Gradient::freeze, min, max, autoscale);
} else if (parameter==MeteoGrids::TSOIL) {
grid.grid2D -= Cst::t_water_freezing_pt; //convert to celsius
if (!autoscale) {
min = -5.; max = 5.;
} else {
min -= Cst::t_water_freezing_pt;
max -= Cst::t_water_freezing_pt;
}
gradient.set(Gradient::heat, min, max, autoscale);
} else if (parameter==MeteoGrids::RH) {
if (!autoscale) {
min = 0.; max = 1.;
}
gradient.set(Gradient::bg_isomorphic, min, max, autoscale);
} else if (parameter==MeteoGrids::P) {
if (!autoscale) {
//lowest and highest sea level pressures ever recorded on Earth: 87000 and 108570
min = 87000.; max = 115650.; //centered around 1 atm
gradient.set(Gradient::bluewhitered, min, max, autoscale);
} else {
const double delta1 = fabs(Cst::std_press-min);
const double delta2 = fabs(max - Cst::std_press);
const double delta = (delta1>delta2)?delta1:delta2;
gradient.set(Gradient::bluewhitered, Cst::std_press-delta, Cst::std_press+delta, autoscale);
}
} else if (parameter==MeteoGrids::ALB) {
if (!autoscale) {
min = 0.; max = 1.;
}
gradient.set(Gradient::blktowhite, min, max, autoscale);
} else if (parameter==MeteoGrids::TAU_CLD) {
if (!autoscale) {
min = 0.; max = 1.;
}
gradient.set(Gradient::blktowhite, min, max, autoscale);
} else if (parameter==MeteoGrids::ISWR) {
if (!autoscale) {
min = 0.; max = 800.;
}
gradient.set(Gradient::heat, min, max, autoscale);
} else if (parameter==MeteoGrids::ILWR) {
if (!autoscale) {
min = 150.; max = 400.;
}
gradient.set(Gradient::heat, min, max, autoscale);
} else if (parameter==MeteoGrids::SWE) {
if (!autoscale) {
min = 0.; max = 250.;
}
gradient.set(Gradient::blue_pink, min, max, autoscale);
} else if (parameter==MeteoGrids::PSUM_PH) {
min = 0.; max = 1.;
gradient.set(Gradient::bluewhitered, min, max, autoscale);
} else {
gradient.set(Gradient::heat, min, max, autoscale);
}
gradient.setNrOfLevels(nr_levels);
Array2D legend_array; //it will remain empty if there is no legend
const size_t full_width = setLegend(ncols, nrows, min, max, legend_array);
setFile(filename, png_ptr, info_ptr, full_width, nrows);
if (indexed_png) setPalette(gradient, png_ptr, info_ptr, palette);
if (has_world_file) writeWorldFile(grid, filename);
createMetadata(grid);
metadata_key.push_back("Title"); //adding title
metadata_text.push_back( MeteoGrids::getParameterName(parameter)+" on "+date.toString(Date::ISO) );
metadata_key.push_back("Simulation Date");
metadata_text.push_back( date.toString(Date::ISO) );
metadata_key.push_back("Simulation Parameter");
metadata_text.push_back( MeteoGrids::getParameterName(parameter) );
writeMetadata(png_ptr, info_ptr);
writeDataSection(grid, legend_array, gradient, full_width, png_ptr, info_ptr);
png_write_end(png_ptr, NULL);
closePNG(png_ptr, info_ptr, palette);
}
void PNGIO::writeWorldFile(const Grid2DObject& grid_in, const std::string& filename) const
{
const std::string world_file( FileUtils::removeExtension(filename)+".pnw" );
if (!FileUtils::validFileAndPath(world_file)) throw InvalidNameException(world_file, AT);
std::ofstream fout(world_file.c_str(), ios::out);
if (fout.fail()) throw AccessException(world_file, AT);
const double cellsize = grid_in.cellsize;
Coords world_ref( grid_in.llcorner );
world_ref.setProj(coordout, coordoutparam);
world_ref.moveByXY(.5*cellsize, (double(grid_in.getNy())+.5)*cellsize); //moving to center of upper left cell
try {
fout << std::setprecision(12) << cellsize << "\n";
fout << "0.000000000000\n";
fout << "0.000000000000\n";
fout << std::setprecision(12) << -cellsize << "\n";
fout << std::setprecision(12) << world_ref.getEasting() << "\n";
fout << std::setprecision(12) << world_ref.getNorthing() << "\n";
} catch(...) {
fout.close();
throw AccessException("Failed when writing to PNG world file \""+world_file+"\"", AT);
}
fout.close();
}
void PNGIO::createMetadata(const Grid2DObject& grid)
{
const double lat = grid.llcorner.getLat();
const double lon = grid.llcorner.getLon();
ostringstream ss;
metadata_key.clear();
metadata_text.clear();
metadata_key.push_back("Creation Time");
Date cr_date;
cr_date.setFromSys();
metadata_text.push_back( cr_date.toString(Date::ISO) );
metadata_key.push_back("Author");
metadata_text.push_back(IOUtils::getLogName());
metadata_key.push_back("Software");
metadata_text.push_back("MeteoIO "+getLibVersion());
metadata_key.push_back("Position");
metadata_text.push_back("llcorner");
metadata_key.push_back("Cellsize");
ss.str(""); ss << fixed << setprecision(2) << grid.cellsize;
metadata_text.push_back(ss.str());
metadata_key.push_back("LL_Latitude");
ss.str(""); ss << fixed << setprecision(7) << lat;
metadata_text.push_back(ss.str());
metadata_key.push_back("LL_Longitude");
ss.str(""); ss << fixed << setprecision(7) << lon;
metadata_text.push_back(ss.str());
Coords UR(grid.llcorner);
UR.moveByXY( grid.cellsize*static_cast(grid.getNx()) , grid.cellsize*static_cast(grid.getNy()) );
metadata_key.push_back("UR_Latitude");
ss.str(""); ss << fixed << setprecision(7) << UR.getLat();
metadata_text.push_back(ss.str());
metadata_key.push_back("UR_Longitude");
ss.str(""); ss << fixed << setprecision(7) << UR.getLon();
metadata_text.push_back(ss.str());
if (lat<0.) {
metadata_key.push_back("LatitudeRef");
metadata_text.push_back("S");
metadata_key.push_back("GPSLatitude");
metadata_text.push_back(decimal_to_dms(-lat));
} else {
metadata_key.push_back("LatitudeRef");
metadata_text.push_back("N");
metadata_key.push_back("GPSLatitude");
metadata_text.push_back(decimal_to_dms(lat));
}
if (lon<0.) {
metadata_key.push_back("LongitudeRef");
metadata_text.push_back("W");
metadata_key.push_back("GPSLongitude");
metadata_text.push_back(decimal_to_dms(-lon));
} else {
metadata_key.push_back("LongitudeRef");
metadata_text.push_back("E");
metadata_key.push_back("GPSLongitude");
metadata_text.push_back(decimal_to_dms(lon));
}
}
void PNGIO::writeMetadata(png_structp &png_ptr, png_infop &info_ptr)
{
const size_t max_len = 79; //according to the official specs' recommendation
const size_t nr = metadata_key.size();
png_text *info_text = (png_text *)calloc(nr, sizeof(png_text));
char **key = (char**)calloc(nr, sizeof(char)*max_len);
char **text = (char**)calloc(nr, sizeof(char)*max_len);
for (size_t ii=0; ii(nr));
png_write_info(png_ptr, info_ptr);
free(info_text);
for (size_t ii=0; ii( floor(decimal) );
const double m = floor( ((decimal - (double)d)*60.)*100. ) / 100.;
const double s = 3600.*(decimal - (double)d) - 60.*m;
std::ostringstream dms;
dms << d << "/1 " << static_cast(m*100.) << "/100 " << fixed << setprecision(6) << s << "/1";
return dms.str();
}
} //namespace