/******************************************************************************
 * $Id: netcdfdataset.h 747442274b44f53e85c1532d3dae82e623d4f56b 2018-08-08 13:31:38 +0200 Even Rouault $
 *
 * Project:  netCDF read/write Driver
 * Purpose:  GDAL bindings over netCDF library.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2004, Frank Warmerdam
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#ifndef NETCDFDATASET_H_INCLUDED_
#define NETCDFDATASET_H_INCLUDED_

#include <cfloat>
#include <map>
#include <vector>

#include "cpl_string.h"
#include "gdal_frmts.h"
#include "gdal_pam.h"
#include "gdal_priv.h"
#include "netcdf.h"
#include "ogr_spatialref.h"
#include "ogrsf_frmts.h"
#include "netcdfuffd.h"

#if defined(DEBUG) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) || defined(ALLOW_FORMAT_DUMPS)
// Whether to support opening a ncdump file as a file dataset
// Useful for fuzzing purposes
#define ENABLE_NCDUMP
#endif


/************************************************************************/
/* ==================================================================== */
/*                           defines                                    */
/* ==================================================================== */
/************************************************************************/

/* -------------------------------------------------------------------- */
/*      Creation and Configuration Options                              */
/* -------------------------------------------------------------------- */

/* Creation options

   FORMAT=NC/NC2/NC4/NC4C (COMPRESS=DEFLATE sets FORMAT=NC4C)
   COMPRESS=NONE/DEFLATE (default: NONE)
   ZLEVEL=[1-9] (default: 1)
   WRITE_BOTTOMUP=YES/NO (default: YES)
   WRITE_GDAL_TAGS=YES/NO (default: YES)
   WRITE_LONLAT=YES/NO/IF_NEEDED (default: YES for geographic, NO for projected)
   TYPE_LONLAT=float/double (default: double for geographic, float for projected)
   PIXELTYPE=DEFAULT/SIGNEDBYTE (use SIGNEDBYTE to get a signed Byte Band)
*/

/* Config Options

   GDAL_NETCDF_BOTTOMUP=YES/NO overrides bottom-up value on import
   GDAL_NETCDF_CONVERT_LAT_180=YES/NO convert longitude values from ]180,360] to [-180,180]
*/

/* -------------------------------------------------------------------- */
/*      Driver-specific defines                                         */
/* -------------------------------------------------------------------- */

/* NETCDF driver defs */
static const size_t NCDF_MAX_STR_LEN = 8192;
#define NCDF_CONVENTIONS_CF_V1_5  "CF-1.5"
#define NCDF_CONVENTIONS_CF_V1_6  "CF-1.6"
#define NCDF_SPATIAL_REF     "spatial_ref"
#define NCDF_GEOTRANSFORM    "GeoTransform"
#define NCDF_DIMNAME_X       "x"
#define NCDF_DIMNAME_Y       "y"
#define NCDF_DIMNAME_LON     "lon"
#define NCDF_DIMNAME_LAT     "lat"
#define NCDF_LONLAT          "lon lat"

/* netcdf file types, as in libcdi/cdo and compat w/netcdf.h */
typedef enum
{
    NCDF_FORMAT_NONE    = 0,   /* Not a netCDF file */
    NCDF_FORMAT_NC      = 1,   /* netCDF classic format */
    NCDF_FORMAT_NC2     = 2,   /* netCDF version 2 (64-bit)  */
    NCDF_FORMAT_NC4     = 3,   /* netCDF version 4 */
    NCDF_FORMAT_NC4C    = 4,   /* netCDF version 4 (classic) */
/* HDF files (HDF5 or HDF4) not supported because of lack of support */
/* in libnetcdf installation or conflict with other drivers */
    NCDF_FORMAT_HDF5    = 5,   /* HDF4 file, not supported */
    NCDF_FORMAT_HDF4    = 6,   /* HDF4 file, not supported */
    NCDF_FORMAT_UNKNOWN = 10  /* Format not determined (yet) */
} NetCDFFormatEnum;

/* compression parameters */
typedef enum
{
    NCDF_COMPRESS_NONE    = 0,
/* TODO */
/* http://www.unidata.ucar.edu/software/netcdf/docs/BestPractices.html#Packed%20Data%20Values */
    NCDF_COMPRESS_PACKED  = 1,
    NCDF_COMPRESS_DEFLATE = 2,
    NCDF_COMPRESS_SZIP    = 3  /* no support for writing */
} NetCDFCompressEnum;

static const int NCDF_DEFLATE_LEVEL    = 1;  /* best time/size ratio */

/* helper for libnetcdf errors */
#define NCDF_ERR(status) if ( status != NC_NOERR ){ \
CPLError( CE_Failure,CPLE_AppDefined, \
"netcdf error #%d : %s .\nat (%s,%s,%d)\n",status, nc_strerror(status), \
__FILE__, __FUNCTION__, __LINE__ ); }

/* Check for NC2 support in case it was not enabled at compile time. */
/* NC4 has to be detected at compile as it requires a special build of netcdf-4. */
#ifndef NETCDF_HAS_NC2
#ifdef NC_64BIT_OFFSET
#define NETCDF_HAS_NC2 1
#endif
#endif

/* -------------------------------------------------------------------- */
/*       CF-1 or NUG (NetCDF User's Guide) defs                         */
/* -------------------------------------------------------------------- */

/* CF: http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.5/cf-conventions.html */
/* NUG: http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Variables */
#define CF_STD_NAME          "standard_name"
#define CF_LNG_NAME          "long_name"
#define CF_UNITS             "units"
#define CF_ADD_OFFSET        "add_offset"
#define CF_SCALE_FACTOR      "scale_factor"
/* should be SRS_UL_METER but use meter now for compat with gtiff files */
#define CF_UNITS_M           "metre"
#define CF_UNITS_D           SRS_UA_DEGREE
#define CF_PROJ_X_VAR_NAME   "x"
#define CF_PROJ_Y_VAR_NAME   "y"
#define CF_PROJ_X_COORD      "projection_x_coordinate"
#define CF_PROJ_Y_COORD      "projection_y_coordinate"
#define CF_PROJ_X_COORD_LONG_NAME "x coordinate of projection"
#define CF_PROJ_Y_COORD_LONG_NAME "y coordinate of projection"
#define CF_GRD_MAPPING_NAME  "grid_mapping_name"
#define CF_GRD_MAPPING       "grid_mapping"
#define CF_COORDINATES       "coordinates"

#define CF_LONGITUDE_VAR_NAME      "lon"
#define CF_LONGITUDE_STD_NAME      "longitude"
#define CF_LONGITUDE_LNG_NAME      "longitude"
#define CF_LATITUDE_VAR_NAME       "lat"
#define CF_LATITUDE_STD_NAME       "latitude"
#define CF_LATITUDE_LNG_NAME       "latitude"
#define CF_DEGREES_NORTH           "degrees_north"
#define CF_DEGREES_EAST            "degrees_east"

#define CF_AXIS            "axis"
/* #define CF_BOUNDS          "bounds" */
/* #define CF_ORIG_UNITS      "original_units" */

/* -------------------------------------------------------------------- */
/*      CF-1 convention standard variables related to                   */
/*      mapping & projection - see http://cf-pcmdi.llnl.gov/            */
/* -------------------------------------------------------------------- */

/* projection types */
#define CF_PT_AEA                    "albers_conical_equal_area"
#define CF_PT_AE                     "azimuthal_equidistant"
#define CF_PT_CEA                    "cylindrical_equal_area"
#define CF_PT_LAEA                   "lambert_azimuthal_equal_area"
#define CF_PT_LCEA                   "lambert_cylindrical_equal_area"
#define CF_PT_LCC                    "lambert_conformal_conic"
#define CF_PT_TM                     "transverse_mercator"
#define CF_PT_LATITUDE_LONGITUDE     "latitude_longitude"
#define CF_PT_MERCATOR               "mercator"
#define CF_PT_ORTHOGRAPHIC           "orthographic"
#define CF_PT_POLAR_STEREO           "polar_stereographic"
#define CF_PT_STEREO                 "stereographic"
#define CF_PT_GEOS                   "geostationary"

/* projection parameters */
#define CF_PP_STD_PARALLEL           "standard_parallel"
/* CF uses only "standard_parallel" */
#define CF_PP_STD_PARALLEL_1         "standard_parallel_1"
#define CF_PP_STD_PARALLEL_2         "standard_parallel_2"
#define CF_PP_CENTRAL_MERIDIAN       "central_meridian"
#define CF_PP_LONG_CENTRAL_MERIDIAN  "longitude_of_central_meridian"
#define CF_PP_LON_PROJ_ORIGIN        "longitude_of_projection_origin"
#define CF_PP_LAT_PROJ_ORIGIN        "latitude_of_projection_origin"
/* #define PROJ_X_ORIGIN             "projection_x_coordinate_origin" */
/* #define PROJ_Y_ORIGIN             "projection_y_coordinate_origin" */
#define CF_PP_EARTH_SHAPE            "GRIB_earth_shape"
#define CF_PP_EARTH_SHAPE_CODE       "GRIB_earth_shape_code"
/* scale_factor is not CF, there are two possible translations  */
/* for WKT scale_factor : SCALE_FACTOR_MERIDIAN and SCALE_FACTOR_ORIGIN */
#define CF_PP_SCALE_FACTOR_MERIDIAN  "scale_factor_at_central_meridian"
#define CF_PP_SCALE_FACTOR_ORIGIN    "scale_factor_at_projection_origin"
#define CF_PP_VERT_LONG_FROM_POLE    "straight_vertical_longitude_from_pole"
#define CF_PP_FALSE_EASTING          "false_easting"
#define CF_PP_FALSE_NORTHING         "false_northing"
#define CF_PP_EARTH_RADIUS           "earth_radius"
#define CF_PP_EARTH_RADIUS_OLD       "spherical_earth_radius_meters"
#define CF_PP_INVERSE_FLATTENING     "inverse_flattening"
#define CF_PP_LONG_PRIME_MERIDIAN    "longitude_of_prime_meridian"
#define CF_PP_SEMI_MAJOR_AXIS        "semi_major_axis"
#define CF_PP_SEMI_MINOR_AXIS        "semi_minor_axis"
#define CF_PP_VERT_PERSP             "vertical_perspective" /*not used yet */
#define CF_PP_PERSPECTIVE_POINT_HEIGHT "perspective_point_height"
#define CF_PP_SWEEP_ANGLE_AXIS        "sweep_angle_axis"
#define CF_PP_GRID_NORTH_POLE_LONGITUDE "grid_north_pole_longitude"
#define CF_PP_GRID_NORTH_POLE_LATITUDE  "grid_north_pole_latitude"
#define CF_PP_NORTH_POLE_GRID_LONGITUDE "north_pole_grid_longitude"

/* -------------------------------------------------------------------- */
/*         CF-1 Coordinate Type Naming (Chapter 4.  Coordinate Types )  */
/* -------------------------------------------------------------------- */
static const char* const papszCFLongitudeVarNames[] = { CF_LONGITUDE_VAR_NAME, "longitude", nullptr };
static const char* const papszCFLongitudeAttribNames[] = { CF_UNITS, CF_STD_NAME, CF_AXIS, nullptr };
static const char* const papszCFLongitudeAttribValues[] = { CF_DEGREES_EAST, CF_LONGITUDE_STD_NAME, "X", nullptr };
static const char* const papszCFLatitudeVarNames[] = { CF_LATITUDE_VAR_NAME, "latitude", nullptr };
static const char* const papszCFLatitudeAttribNames[] = { CF_UNITS, CF_STD_NAME, CF_AXIS, nullptr };
static const char* const papszCFLatitudeAttribValues[] = { CF_DEGREES_NORTH, CF_LATITUDE_STD_NAME, "Y", nullptr };

static const char* const papszCFProjectionXVarNames[] = { CF_PROJ_X_VAR_NAME, "xc", nullptr };
static const char* const papszCFProjectionXAttribNames[] = { CF_STD_NAME, nullptr };
static const char* const papszCFProjectionXAttribValues[] = { CF_PROJ_X_COORD, nullptr };
static const char* const papszCFProjectionYVarNames[] = { CF_PROJ_Y_VAR_NAME, "yc", nullptr };
static const char* const papszCFProjectionYAttribNames[] = { CF_STD_NAME, nullptr };
static const char* const papszCFProjectionYAttribValues[] = { CF_PROJ_Y_COORD, nullptr };

static const char* const papszCFVerticalAttribNames[] = { CF_AXIS, "positive", "positive", nullptr };
static const char* const papszCFVerticalAttribValues[] = { "Z", "up", "down", nullptr };
static const char* const papszCFVerticalUnitsValues[] = {
    /* units of pressure */
    "bar", "bars", "millibar", "millibars", "decibar", "decibars",
    "atmosphere", "atmospheres", "atm", "pascal", "pascals", "Pa", "hPa",
    /* units of length */
    "meter", "meters", "m", "kilometer", "kilometers", "km",
    /* dimensionless vertical coordinates */
    "level", "layer", "sigma_level",
    nullptr };
/* dimensionless vertical coordinates */
static const char* const papszCFVerticalStandardNameValues[] = {
    "atmosphere_ln_pressure_coordinate", "atmosphere_sigma_coordinate",
    "atmosphere_hybrid_sigma_pressure_coordinate",
    "atmosphere_hybrid_height_coordinate",
    "atmosphere_sleve_coordinate", "ocean_sigma_coordinate",
    "ocean_s_coordinate", "ocean_sigma_z_coordinate",
    "ocean_double_sigma_coordinate", "atmosphere_ln_pressure_coordinate",
    "atmosphere_sigma_coordinate",
    "atmosphere_hybrid_sigma_pressure_coordinate",
    "atmosphere_hybrid_height_coordinate",
    "atmosphere_sleve_coordinate", "ocean_sigma_coordinate",
    "ocean_s_coordinate", "ocean_sigma_z_coordinate",
    "ocean_double_sigma_coordinate", nullptr };

static const char* const papszCFTimeAttribNames[] = { CF_AXIS, nullptr };
static const char* const papszCFTimeAttribValues[] = { "T", nullptr };
static const char* const papszCFTimeUnitsValues[] = {
    "days since", "day since", "d since",
    "hours since", "hour since", "h since", "hr since",
    "minutes since", "minute since", "min since",
    "seconds since", "second since", "sec since", "s since",
    nullptr };

/* -------------------------------------------------------------------- */
/*         CF-1 to GDAL mappings                                        */
/* -------------------------------------------------------------------- */

/* Following are a series of mappings from CF-1 convention parameters
 * for each projection, to the equivalent in OGC WKT used internally by GDAL.
 * See: http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.5/apf.html
 */

/* A struct allowing us to map between GDAL(OGC WKT) and CF-1 attributes */
typedef struct {
    const char *CF_ATT;
    const char *WKT_ATT;
    // TODO: mappings may need default values, like scale factor?
    //double defval;
} oNetcdfSRS_PP;

// default mappings, for the generic case
/* These 'generic' mappings are based on what was previously in the
   poNetCDFSRS struct. They will be used as a fallback in case none
   of the others match (i.e. you are exporting a projection that has
   no CF-1 equivalent).
   They are not used for known CF-1 projections since there is not a
   unique 2-way projection-independent
   mapping between OGC WKT params and CF-1 ones: it varies per-projection.
*/

static const oNetcdfSRS_PP poGenericMappings[] = {
    /* scale_factor is handled as a special case, write 2 values */
    {CF_PP_STD_PARALLEL_1, SRS_PP_STANDARD_PARALLEL_1 },
    {CF_PP_STD_PARALLEL_2, SRS_PP_STANDARD_PARALLEL_2 },
    {CF_PP_LONG_CENTRAL_MERIDIAN, SRS_PP_CENTRAL_MERIDIAN },
    {CF_PP_LONG_CENTRAL_MERIDIAN, SRS_PP_LONGITUDE_OF_CENTER },
    {CF_PP_LON_PROJ_ORIGIN, SRS_PP_LONGITUDE_OF_ORIGIN },
    //Multiple mappings to LAT_PROJ_ORIGIN
    {CF_PP_LAT_PROJ_ORIGIN, SRS_PP_LATITUDE_OF_ORIGIN },
    {CF_PP_LAT_PROJ_ORIGIN, SRS_PP_LATITUDE_OF_CENTER },
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr },
};

// Albers equal area
//
// grid_mapping_name = albers_conical_equal_area
// WKT: Albers_Conic_Equal_Area
// ESPG:9822
//
// Map parameters:
//
//    * standard_parallel - There may be 1 or 2 values.
//    * longitude_of_central_meridian
//    * latitude_of_projection_origin
//    * false_easting
//    * false_northing
//
static const oNetcdfSRS_PP poAEAMappings[] = {
    {CF_PP_STD_PARALLEL_1, SRS_PP_STANDARD_PARALLEL_1},
    {CF_PP_STD_PARALLEL_2, SRS_PP_STANDARD_PARALLEL_2},
    {CF_PP_LAT_PROJ_ORIGIN, SRS_PP_LATITUDE_OF_CENTER},
    {CF_PP_LONG_CENTRAL_MERIDIAN, SRS_PP_LONGITUDE_OF_CENTER},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
 };

// Azimuthal equidistant
//
// grid_mapping_name = azimuthal_equidistant
// WKT: Azimuthal_Equidistant
//
// Map parameters:
//
//    * longitude_of_projection_origin
//    * latitude_of_projection_origin
//    * false_easting
//    * false_northing
//
static const oNetcdfSRS_PP poAEMappings[] = {
    {CF_PP_LAT_PROJ_ORIGIN, SRS_PP_LATITUDE_OF_CENTER},
    {CF_PP_LON_PROJ_ORIGIN, SRS_PP_LONGITUDE_OF_CENTER},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
 };

// Lambert azimuthal equal area
//
// grid_mapping_name = lambert_azimuthal_equal_area
// WKT: Lambert_Azimuthal_Equal_Area
//
// Map parameters:
//
//    * longitude_of_projection_origin
//    * latitude_of_projection_origin
//    * false_easting
//    * false_northing
//
static const oNetcdfSRS_PP poLAEAMappings[] = {
    {CF_PP_LAT_PROJ_ORIGIN, SRS_PP_LATITUDE_OF_CENTER},
    {CF_PP_LON_PROJ_ORIGIN, SRS_PP_LONGITUDE_OF_CENTER},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
 };

// Lambert conformal
//
// grid_mapping_name = lambert_conformal_conic
// WKT: Lambert_Conformal_Conic_1SP / Lambert_Conformal_Conic_2SP
//
// Map parameters:
//
//    * standard_parallel - There may be 1 or 2 values.
//    * longitude_of_central_meridian
//    * latitude_of_projection_origin
//    * false_easting
//    * false_northing
//
// See http://www.remotesensing.org/geotiff/proj_list/lambert_conic_conformal_1sp.html

// Lambert conformal conic - 1SP
/* See bug # 3324
   It seems that the missing scale factor can be computed from standard_parallel1 and latitude_of_projection_origin.
   If both are equal (the common case) then scale factor=1, else use Snyder eq. 15-4.
   We save in the WKT standard_parallel1 for export to CF, but do not export scale factor.
   If a WKT has a scale factor != 1 and no standard_parallel1 then export is not CF, but we output scale factor for compat.
     is there a formula for that?
*/
static const oNetcdfSRS_PP poLCC1SPMappings[] = {
    {CF_PP_STD_PARALLEL_1, SRS_PP_STANDARD_PARALLEL_1},
    {CF_PP_LAT_PROJ_ORIGIN, SRS_PP_LATITUDE_OF_ORIGIN},
    {CF_PP_LONG_CENTRAL_MERIDIAN, SRS_PP_CENTRAL_MERIDIAN},
    {CF_PP_SCALE_FACTOR_ORIGIN, SRS_PP_SCALE_FACTOR}, /* special case */
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
 };

// Lambert conformal conic - 2SP
static const oNetcdfSRS_PP poLCC2SPMappings[] = {
    {CF_PP_STD_PARALLEL_1, SRS_PP_STANDARD_PARALLEL_1},
    {CF_PP_STD_PARALLEL_2, SRS_PP_STANDARD_PARALLEL_2},
    {CF_PP_LAT_PROJ_ORIGIN, SRS_PP_LATITUDE_OF_ORIGIN},
    {CF_PP_LONG_CENTRAL_MERIDIAN, SRS_PP_CENTRAL_MERIDIAN},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
 };

// Lambert cylindrical equal area
//
// grid_mapping_name = lambert_cylindrical_equal_area
// WKT: Cylindrical_Equal_Area
// EPSG:9834 (Spherical) and EPSG:9835
//
// Map parameters:
//
//    * longitude_of_central_meridian
//    * either standard_parallel or scale_factor_at_projection_origin
//    * false_easting
//    * false_northing
//
// NB: CF-1 specifies a 'scale_factor_at_projection' alternative
//  to std_parallel ... but no reference to this in EPSG/remotesensing.org
//  ignore for now.
//
static const oNetcdfSRS_PP poLCEAMappings[] = {
    {CF_PP_STD_PARALLEL_1, SRS_PP_STANDARD_PARALLEL_1},
    {CF_PP_LONG_CENTRAL_MERIDIAN, SRS_PP_CENTRAL_MERIDIAN},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
 };

// Latitude-Longitude
//
// grid_mapping_name = latitude_longitude
//
// Map parameters:
//
//    * None
//
// NB: handled as a special case - !isProjected()

// Mercator
//
// grid_mapping_name = mercator
// WKT: Mercator_1SP / Mercator_2SP
//
// Map parameters:
//
//    * longitude_of_projection_origin
//    * either standard_parallel or scale_factor_at_projection_origin
//    * false_easting
//    * false_northing

// Mercator 1 Standard Parallel (EPSG:9804)
static const oNetcdfSRS_PP poM1SPMappings[] = {
    {CF_PP_LON_PROJ_ORIGIN, SRS_PP_CENTRAL_MERIDIAN},
    //LAT_PROJ_ORIGIN is always equator (0) in CF-1
    {CF_PP_SCALE_FACTOR_ORIGIN, SRS_PP_SCALE_FACTOR},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
 };

// Mercator 2 Standard Parallel
static const oNetcdfSRS_PP poM2SPMappings[] = {
    {CF_PP_LON_PROJ_ORIGIN, SRS_PP_CENTRAL_MERIDIAN},
    {CF_PP_STD_PARALLEL_1, SRS_PP_STANDARD_PARALLEL_1},
    // From best understanding of this projection, only
    // actually specify one SP - it is the same N/S of equator.
    // {CF_PP_STD_PARALLEL_2, SRS_PP_LATITUDE_OF_ORIGIN},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
 };

// Orthographic
// grid_mapping_name = orthographic
// WKT: Orthographic
//
// Map parameters:
//
//    * longitude_of_projection_origin
//    * latitude_of_projection_origin
//    * false_easting
//    * false_northing
//
static const oNetcdfSRS_PP poOrthoMappings[] = {
    {CF_PP_LAT_PROJ_ORIGIN, SRS_PP_LATITUDE_OF_ORIGIN},
    {CF_PP_LON_PROJ_ORIGIN, SRS_PP_CENTRAL_MERIDIAN},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
 };

// Polar stereographic
//
// grid_mapping_name = polar_stereographic
// WKT: Polar_Stereographic
//
// Map parameters:
//
//    * straight_vertical_longitude_from_pole
//    * latitude_of_projection_origin - Either +90. or -90.
//    * Either standard_parallel or scale_factor_at_projection_origin
//    * false_easting
//    * false_northing

/*
   (http://www.remotesensing.org/geotiff/proj_list/polar_stereographic.html)

   Note: Projection parameters for this projection are quite different in CF-1 from
     OGC WKT/GeoTiff (for the latter, see above).
   From our best understanding, this projection requires more than a straight mapping:
     - As defined below, 'latitude_of_origin' (WKT) -> 'standard_parallel' (CF-1)
       and 'central_meridian' (WKT) -> 'straight_vertical_longitude_from_pole' (CF-1)
     - Then the 'latitude_of_projection_origin' in CF-1 must be set to either +90 or -90,
       depending on the sign of 'latitude_of_origin' in WKT.
   CF allows the use of standard_parallel (lat_ts in proj4) OR scale_factor (k0 in proj4).
   This is analogous to the B and A variants (resp.) in EPSG guidelines.
   When importing a CF file with scale_factor, we compute standard_parallel using
     Snyder eq. 22-7 with k=1 and lat=standard_parallel.
   Currently OGR does NOT relate the scale factor with the standard parallel, so we
   use the default. It seems that proj4 uses lat_ts (standard_parallel) and not k0.
*/
static const oNetcdfSRS_PP poPSmappings[] = {
    {CF_PP_STD_PARALLEL_1, SRS_PP_LATITUDE_OF_ORIGIN},
    /* {CF_PP_SCALE_FACTOR_ORIGIN, SRS_PP_SCALE_FACTOR},   */
    {CF_PP_VERT_LONG_FROM_POLE, SRS_PP_CENTRAL_MERIDIAN},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
};

// Rotated Pole
//
// grid_mapping_name = rotated_latitude_longitude
// WKT: N/A
//
// Map parameters:
//
//    * grid_north_pole_latitude
//    * grid_north_pole_longitude
//    * north_pole_grid_longitude - This parameter is optional (default is 0.).

// No WKT equivalent

// Stereographic
//
// grid_mapping_name = stereographic
// WKT: Stereographic (and/or Oblique_Stereographic??)
//
// Map parameters:
//
//    * longitude_of_projection_origin
//    * latitude_of_projection_origin
//    * scale_factor_at_projection_origin
//    * false_easting
//    * false_northing
//
// NB: see bug#4267 Stereographic vs. Oblique_Stereographic
//
static const oNetcdfSRS_PP poStMappings[] = {
    {CF_PP_LAT_PROJ_ORIGIN, SRS_PP_LATITUDE_OF_ORIGIN},
    {CF_PP_LON_PROJ_ORIGIN, SRS_PP_CENTRAL_MERIDIAN},
    {CF_PP_SCALE_FACTOR_ORIGIN, SRS_PP_SCALE_FACTOR},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
  };

// Transverse Mercator
//
// grid_mapping_name = transverse_mercator
// WKT: Transverse_Mercator
//
// Map parameters:
//
//    * scale_factor_at_central_meridian
//    * longitude_of_central_meridian
//    * latitude_of_projection_origin
//    * false_easting
//    * false_northing
//
static const oNetcdfSRS_PP poTMMappings[] = {
    {CF_PP_SCALE_FACTOR_MERIDIAN, SRS_PP_SCALE_FACTOR},
    {CF_PP_LONG_CENTRAL_MERIDIAN, SRS_PP_CENTRAL_MERIDIAN},
    {CF_PP_LAT_PROJ_ORIGIN, SRS_PP_LATITUDE_OF_ORIGIN},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    {nullptr, nullptr}
  };

// Vertical perspective
//
// grid_mapping_name = vertical_perspective
// WKT: ???
//
// Map parameters:
//
//    * latitude_of_projection_origin
//    * longitude_of_projection_origin
//    * perspective_point_height
//    * false_easting
//    * false_northing
//
// TODO: see how to map this to OGR

static const oNetcdfSRS_PP poGEOSMappings[] = {
    {CF_PP_LON_PROJ_ORIGIN, SRS_PP_CENTRAL_MERIDIAN},
    {CF_PP_PERSPECTIVE_POINT_HEIGHT, SRS_PP_SATELLITE_HEIGHT},
    {CF_PP_FALSE_EASTING, SRS_PP_FALSE_EASTING },
    {CF_PP_FALSE_NORTHING, SRS_PP_FALSE_NORTHING },
    /* { CF_PP_SWEEP_ANGLE_AXIS, .... } handled as a proj.4 extension */
    {nullptr, nullptr}
  };

/* Mappings for various projections, including netcdf and GDAL projection names
   and corresponding oNetcdfSRS_PP mapping struct.
   A NULL mappings value means that the projection is not included in the CF
   standard and the generic mapping (poGenericMappings) will be used. */
typedef struct {
    const char *CF_SRS;
    const char *WKT_SRS;
    const oNetcdfSRS_PP* mappings;
} oNetcdfSRS_PT;

static const oNetcdfSRS_PT poNetcdfSRS_PT[] = {
    {CF_PT_AEA, SRS_PT_ALBERS_CONIC_EQUAL_AREA, poAEAMappings },
    {CF_PT_AE, SRS_PT_AZIMUTHAL_EQUIDISTANT, poAEMappings },
    {"cassini_soldner", SRS_PT_CASSINI_SOLDNER, nullptr },
    {CF_PT_LCEA, SRS_PT_CYLINDRICAL_EQUAL_AREA, poLCEAMappings },
    {"eckert_iv", SRS_PT_ECKERT_IV, nullptr },
    {"eckert_vi", SRS_PT_ECKERT_VI, nullptr },
    {"equidistant_conic", SRS_PT_EQUIDISTANT_CONIC, nullptr },
    {"equirectangular", SRS_PT_EQUIRECTANGULAR, nullptr },
    {"gall_stereographic", SRS_PT_GALL_STEREOGRAPHIC, nullptr },
    {CF_PT_GEOS, SRS_PT_GEOSTATIONARY_SATELLITE, poGEOSMappings },
    {"goode_homolosine", SRS_PT_GOODE_HOMOLOSINE, nullptr },
    {"gnomonic", SRS_PT_GNOMONIC, nullptr },
    {"hotine_oblique_mercator", SRS_PT_HOTINE_OBLIQUE_MERCATOR, nullptr },
    {"hotine_oblique_mercator_2P",
     SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN, nullptr },
    {"laborde_oblique_mercator", SRS_PT_LABORDE_OBLIQUE_MERCATOR, nullptr },
    {CF_PT_LCC, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP, poLCC1SPMappings },
    {CF_PT_LCC, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP, poLCC2SPMappings },
    {CF_PT_LAEA, SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA, poLAEAMappings },
    {CF_PT_MERCATOR, SRS_PT_MERCATOR_1SP, poM1SPMappings },
    {CF_PT_MERCATOR, SRS_PT_MERCATOR_2SP, poM2SPMappings },
    {"miller_cylindrical", SRS_PT_MILLER_CYLINDRICAL, nullptr },
    {"mollweide", SRS_PT_MOLLWEIDE, nullptr },
    {"new_zealand_map_grid", SRS_PT_NEW_ZEALAND_MAP_GRID, nullptr },
    /* for now map to STEREO, see bug #4267 */
    {"oblique_stereographic", SRS_PT_OBLIQUE_STEREOGRAPHIC, nullptr },
    /* {STEREO, SRS_PT_OBLIQUE_STEREOGRAPHIC, poStMappings },  */
    {CF_PT_ORTHOGRAPHIC, SRS_PT_ORTHOGRAPHIC, poOrthoMappings },
    {CF_PT_POLAR_STEREO, SRS_PT_POLAR_STEREOGRAPHIC, poPSmappings },
    {"polyconic", SRS_PT_POLYCONIC, nullptr },
    {"robinson", SRS_PT_ROBINSON, nullptr },
    {"sinusoidal", SRS_PT_SINUSOIDAL, nullptr },
    {CF_PT_STEREO, SRS_PT_STEREOGRAPHIC, poStMappings },
    {"swiss_oblique_cylindrical", SRS_PT_SWISS_OBLIQUE_CYLINDRICAL, nullptr },
    {CF_PT_TM, SRS_PT_TRANSVERSE_MERCATOR, poTMMappings },
    {"TM_south_oriented", SRS_PT_TRANSVERSE_MERCATOR_SOUTH_ORIENTED, nullptr },
    {nullptr, nullptr, nullptr },
};

/************************************************************************/
/* ==================================================================== */
/*                        netCDFWriterConfig classes                    */
/* ==================================================================== */
/************************************************************************/

class netCDFWriterConfigAttribute
{
  public:
    CPLString m_osName;
    CPLString m_osType;
    CPLString m_osValue;

    bool Parse(CPLXMLNode *psNode);
};

class netCDFWriterConfigField
{
  public:
    CPLString m_osName;
    CPLString m_osNetCDFName;
    CPLString m_osMainDim;
    std::vector<netCDFWriterConfigAttribute> m_aoAttributes;

    bool Parse(CPLXMLNode *psNode);
};

class netCDFWriterConfigLayer
{
  public:
    CPLString m_osName;
    CPLString m_osNetCDFName;
    std::map<CPLString, CPLString> m_oLayerCreationOptions;
    std::vector<netCDFWriterConfigAttribute> m_aoAttributes;
    std::map<CPLString, netCDFWriterConfigField> m_oFields;

    bool Parse(CPLXMLNode *psNode);
};

class netCDFWriterConfiguration
{
  public:
    bool m_bIsValid;
    std::map<CPLString, CPLString> m_oDatasetCreationOptions;
    std::map<CPLString, CPLString> m_oLayerCreationOptions;
    std::vector<netCDFWriterConfigAttribute> m_aoAttributes;
    std::map<CPLString, netCDFWriterConfigField> m_oFields;
    std::map<CPLString, netCDFWriterConfigLayer> m_oLayers;

    netCDFWriterConfiguration() : m_bIsValid(false) {}

    bool Parse(const char *pszFilename);
    static bool SetNameValue(CPLXMLNode *psNode,
                             std::map<CPLString, CPLString> &oMap);
};

/************************************************************************/
/* ==================================================================== */
/*                           netCDFDataset                              */
/* ==================================================================== */
/************************************************************************/

class netCDFRasterBand;
class netCDFLayer;

class netCDFDataset final: public GDALPamDataset
{
    friend class netCDFRasterBand; //TMP
    friend class netCDFLayer;

    typedef enum
    {
        SINGLE_LAYER,
        SEPARATE_FILES,
        SEPARATE_GROUPS
    } MultipleLayerBehaviour;

    /* basic dataset vars */
    CPLString     osFilename;
#ifdef ENABLE_NCDUMP
    bool          bFileToDestroyAtClosing;
#endif
    int           cdfid;
#ifdef ENABLE_UFFD
    cpl_uffd_context *pCtx = nullptr;
#endif
    char          **papszSubDatasets;
    char          **papszMetadata;
    CPLStringList papszDimName;
    bool          bBottomUp;
    NetCDFFormatEnum eFormat;
    bool          bIsGdalFile; /* was this file created by GDAL? */
    bool          bIsGdalCfFile; /* was this file created by the (new) CF-compliant driver? */
    char         *pszCFProjection;
    const char   *pszCFCoordinates;
    MultipleLayerBehaviour eMultipleLayerBehaviour;
    std::vector<netCDFDataset*> apoVectorDatasets;

    /* projection/GT */
    double       adfGeoTransform[6];
    char         *pszProjection;
    int          nXDimID;
    int          nYDimID;
    bool         bIsProjected;
    bool         bIsGeographic;

    /* state vars */
    bool         bDefineMode;
    bool         bSetProjection;
    bool         bSetGeoTransform;
    bool         bAddedProjectionVarsDefs;
    bool         bAddedProjectionVarsData;
    bool         bAddedGridMappingRef;

    /* create vars */
    char         **papszCreationOptions;
    NetCDFCompressEnum eCompress;
    int          nZLevel;
#ifdef NETCDF_HAS_NC4
    bool         bChunking;
#endif
    int          nCreateMode;
    bool         bSignedData;

    int          nLayers;
    netCDFLayer   **papoLayers;

    netCDFWriterConfiguration oWriterConfig;

    static double       rint( double );

    double       FetchCopyParm( const char *pszGridMappingValue,
                                const char *pszParm, double dfDefault );

    char **      FetchStandardParallels( const char *pszGridMappingValue );

    void ProcessCreationOptions( );
    int DefVarDeflate( int nVarId, bool bChunkingArg=true );
    CPLErr AddProjectionVars( bool bDefsOnly,
                              GDALProgressFunc pfnProgress,
                              void * pProgressData );
    void AddGridMappingRef();

    bool GetDefineMode() const { return bDefineMode; }
    bool SetDefineMode( bool bNewDefineMode );

    CPLErr      ReadAttributes( int, int );

    void  CreateSubDatasetList( );

    void  SetProjectionFromVar( int, bool bReadSRSOnly );

    int ProcessCFGeolocation( int );
    CPLErr Set1DGeolocation( int nVarId, const char *szDimName );
    double * Get1DGeolocation( const char *szDimName, int &nVarLen );

    static bool CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId, int nDstVarId);
    static bool CloneVariableContent(int old_cdfid, int new_cdfid, int nSrcVarId, int nDstVarId);
    static bool CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4, int nLayerId,
                         int nDimIdToGrow, size_t nNewSize);
    bool GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize);

  protected:

    CPLXMLNode *SerializeToXML( const char *pszVRTPath ) override;

    virtual OGRLayer   *ICreateLayer( const char *pszName,
                                     OGRSpatialReference *poSpatialRef,
                                     OGRwkbGeometryType eGType,
                                     char ** papszOptions ) override;

  public:

    netCDFDataset();
    virtual ~netCDFDataset();

    /* Projection/GT */
    CPLErr      GetGeoTransform( double * ) override;
    CPLErr      SetGeoTransform (double *) override;
    const char * GetProjectionRef() override;
    CPLErr      SetProjection (const char *) override;

    virtual char      **GetMetadataDomainList() override;
    char ** GetMetadata( const char * ) override;

    virtual int  TestCapability(const char* pszCap) override;

    virtual int  GetLayerCount() override { return nLayers; }
    virtual OGRLayer* GetLayer(int nIdx) override;

    int GetCDFID() const { return cdfid; }

    /* static functions */
    static int Identify( GDALOpenInfo * );
    static NetCDFFormatEnum IdentifyFormat( GDALOpenInfo *, bool );
    static GDALDataset *Open( GDALOpenInfo * );

    static netCDFDataset *CreateLL( const char * pszFilename,
                                    int nXSize, int nYSize, int nBands,
                                    char ** papszOptions );
    static GDALDataset *Create( const char * pszFilename,
                                int nXSize, int nYSize, int nBands,
                                GDALDataType eType,
                                char ** papszOptions );
    static GDALDataset* CreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
                                    int bStrict, char ** papszOptions,
                                    GDALProgressFunc pfnProgress, void * pProgressData );
};

class netCDFLayer final: public OGRLayer
{
        typedef union
        {
            signed char chVal;
            unsigned char uchVal;
            short   sVal;
            unsigned short usVal;
            int     nVal;
            unsigned int unVal;
            GIntBig nVal64;
            GUIntBig unVal64;
            float   fVal;
            double  dfVal;
        } NCDFNoDataUnion;

        typedef struct
        {
            NCDFNoDataUnion uNoData;
            nc_type         nType;
            int             nVarId;
            int             nDimCount;
            bool            bHasWarnedAboutTruncation;
            int             nMainDimId;
            int             nSecDimId;
            bool            bIsDays;
        } FieldDesc;

        netCDFDataset  *m_poDS;
        int             m_nLayerCDFId;
        OGRFeatureDefn *m_poFeatureDefn;
        CPLString       m_osRecordDimName;
        int             m_nRecordDimID;
        int             m_nDefaultWidth;
        bool            m_bAutoGrowStrings;
        int             m_nDefaultMaxWidthDimId;
        int             m_nXVarID;
        int             m_nYVarID;
        int             m_nZVarID;
        nc_type         m_nXVarNCDFType;
        nc_type         m_nYVarNCDFType;
        nc_type         m_nZVarNCDFType;
        NCDFNoDataUnion m_uXVarNoData;
        NCDFNoDataUnion m_uYVarNoData;
        NCDFNoDataUnion m_uZVarNoData;
        CPLString       m_osWKTVarName;
        int             m_nWKTMaxWidth;
        int             m_nWKTMaxWidthDimId;
        int             m_nWKTVarID;
        nc_type         m_nWKTNCDFType;
        CPLString       m_osCoordinatesValue;
        std::vector<FieldDesc> m_aoFieldDesc;
        int             m_nCurFeatureId;
        CPLString       m_osGridMapping;
        bool            m_bWriteGDALTags;
        bool            m_bUseStringInNC4;
        bool            m_bNCDumpCompat;

        CPLString       m_osProfileDimName;
        int             m_nProfileDimID;
        CPLString       m_osProfileVariables;
        int             m_nProfileVarID;
        bool            m_bProfileVarUnlimited;
        int             m_nParentIndexVarID;

        const netCDFWriterConfigLayer* m_poLayerConfig;

        OGRFeature     *GetNextRawFeature();
        double          Get1DVarAsDouble( int nVarId, nc_type nVarType,
                                          size_t nIndex,
                                          NCDFNoDataUnion noDataVal,
                                          bool* pbIsNoData );
        CPLErr          GetFillValue( int nVarID, char **ppszValue );
        CPLErr          GetFillValue( int nVarID, double *pdfValue );
        void            GetNoDataValueForFloat( int nVarId, NCDFNoDataUnion* puNoData );
        void            GetNoDataValueForDouble( int nVarId, NCDFNoDataUnion* puNoData );
        void            GetNoDataValue( int nVarId, nc_type nVarType, NCDFNoDataUnion* puNoData );
        bool            FillFeatureFromVar(OGRFeature* poFeature, int nMainDimId, size_t nIndex);
        bool            FillVarFromFeature(OGRFeature* poFeature, int nMainDimId, size_t nIndex);

    public:
                netCDFLayer(netCDFDataset* poDS,
                            int nLayerCDFId,
                            const char* pszName,
                            OGRwkbGeometryType eGeomType,
                            OGRSpatialReference* poSRS);
        virtual ~netCDFLayer();

        bool            Create(char** papszOptions, const netCDFWriterConfigLayer* poLayerConfig);
        void            SetRecordDimID(int nRecordDimID);
        void            SetXYZVars(int nXVarId, int nYVarId, int nZVarId);
        void            SetWKTGeometryField(const char* pszWKTVarName);
        void            SetGridMapping(const char* pszGridMapping);
        void            SetProfile(int nProfileDimID, int nParentIndexVarID);
        bool            AddField(int nVarId);

        int             GetCDFID() const { return m_nLayerCDFId; }
        void            SetCDFID(int nId) { m_nLayerCDFId = nId; }

        virtual void ResetReading() override;
        virtual OGRFeature* GetNextFeature() override;

        virtual GIntBig GetFeatureCount(int bForce) override;

        virtual int  TestCapability(const char* pszCap) override;

        virtual OGRFeatureDefn* GetLayerDefn() override;

        virtual OGRErr ICreateFeature(OGRFeature* poFeature) override;
        virtual OGRErr CreateField(OGRFieldDefn* poFieldDefn, int bApproxOK) override;
};

void NCDFWriteLonLatVarsAttributes(int cdfid, int nVarLonID, int nVarLatID);
void NCDFWriteXYVarsAttributes(int cdfid, int nVarXID, int nVarYID,
                                      OGRSpatialReference* poSRS);
int NCDFWriteSRSVariable(int cdfid, OGRSpatialReference* poSRS,
                                char** ppszCFProjection, bool bWriteGDALTags);
CPLErr NCDFGetAttr( int nCdfId, int nVarId, const char *pszAttrName,
                    double *pdfValue );
CPLErr NCDFGetAttr( int nCdfId, int nVarId, const char *pszAttrName,
                    char **pszValue );
bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId);

#endif
