/***************************** LICENSE START ***********************************

 Copyright 2019 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

/********************************************************************************
 Important information:

 1. The Bottom/Top Levels will be computed automatically from the data, if
    they are not given as a input parameters.

 2. Relationship among fields and interpolation:

                  ML    ML&LNSP   PL    PL&LNSP
         INTY    ok      ok       ok     ok1
         INTN    ok     error     ok     ok1

   INTY/N = interpolation yes/no
   ok1    = LNSP is ignored

 3. If fieldset contains ML&LNSP, a conversion to PL will be computed automatically
 ********************************************************************************/

#include "CrossS.h"
#include "MvException.h"

#include <cassert>

#include <algorithm>
#include <initializer_list>

#define CROSS_S_CHK(str) ({if(!(str)) {setError(13); return false;} })

// Application mode
enum
{
    XWIND,
    XVELOCITY,
    XCONTOUR
};

CrossS::CrossS() :
    Xsect("MXSECTION")
{
    init();
    type_ = "MXSECTION";
    view_ = "MXSECTIONVIEW";
}

CrossS::CrossS(const char* kw) :
    Xsect(kw)
{
    init();
    type_ = kw;
    view_ = "MXSECTIONVIEW";
}

void CrossS::init()
{
    moduleLabel_ = "CrossSection-> ";
    Xsect::init();
    windProjection_ = NoWindProjection;
    windMode_ = NoWindMode;
    useWindIntensity_ = false;
    outputMode_ = ScalarOutput;
    oroColour_ = "CYAN";
    wScaling_ = -100;
    wMode_ = Mv3VerticalVelocity;
    plotVar_ = nullptr;
}

bool CrossS::getCoordinates(MvRequest& in, ApplicationInfo& appInfo)
{
    // Get input values
    double Y1 = in("LINE", 0);
    double X1 = in("LINE", 1);
    double Y2 = in("LINE", 2);
    double X2 = in("LINE", 3);

    // Save coordinates
    appInfo.setAreaLine(X1, X2, Y1, Y2);

    return true;
}

bool CrossS::getAppParameters(MvRequest& in, ApplicationInfo& appInfo)
{
    // Retrieve geographical coordinates
    if (!this->getCoordinates(in, appInfo))
        return false;

    std::string val;

    // Retrieve Wind input info
    if (in.getValue("WIND_PARALLEL", val) && val == "ON") {
        windProjection_ = ParallelWindProjection;
    }

    if (in.getValue("WIND_PERPENDICULAR", val) && val == "ON") {
        if (windProjection_ == ParallelWindProjection) {
            setError(1, "CrossS-> cannot set both WIND_PARALLEL and WIND_PERPENDICULAR to ON");
            return false;
        }
        else {
            windProjection_ = NormalWindProjection;
        }
    }

    if (in.getValue("WIND_INTENSITY", val) && val == "ON") {
        useWindIntensity_ = true;
    }

    if (in.getValue("WIND_UNPROJECTED", val) && val == "ON") {
        if (useWindIntensity_) {
            setError(1, "CrossS-> cannot set both WIND_INTENSITY and WIND_UNPROJECTED to ON");
            return false;
        }
        if (windProjection_ != NoWindProjection) {
            setError(1, "CrossS-> cannot set WIND_UNPROJECTED to ON when WIND_PARALLEL or WIND_PERPENDICULAR is ON");
            return false;
        }
        needVectorOutput_ = true;
    }

    // Retrive wind & lnsp param numbers (mostly for non-ECMWF data)
    if ((const char*)in("U_WIND_PARAM"))
        U_FIELD = (int)in("U_WIND_PARAM");

    if ((const char*)in("V_WIND_PARAM"))
        V_FIELD = (int)in("V_WIND_PARAM");

    if ((const char*)in("W_WIND_PARAM"))
        W_FIELD = (int)in("W_WIND_PARAM");

    if ((const char*)in("LNSP_PARAM"))
        LNSP_FIELD = (int)in("LNSP_PARAM");

    if ((const char*)in("T_PARAM"))
        T_FIELD = (int)in("T_PARAM");

    if ((const char*)in("HORIZONTAL_POINT_MODE"))
        appInfo.horPointMode((const char*)in("HORIZONTAL_POINT_MODE"));

    // Retrieve vertical coordinates information
    if ((const char*)in("VERTICAL_COORDINATES") &&
        (const char*)in("VERTICAL_COORDINATES") == std::string("USER"))
        appInfo.haveGHBC(true);

    if ((const char*)in("VERTICAL_COORDINATE_PARAM"))
        appInfo.setGHBC((int)in("VERTICAL_COORDINATE_PARAM"));

    if (const char* ch = in("VERTICAL_COORDINATE_EXTRAPOLATE")) {
        appInfo.setGHBCExtrapolate(strcmp(ch, "ON") == 0);
    }
    if (appInfo.extrapolateGHBC()) {
        if (const char* ch = in("VERTICAL_COORDINATE_EXTRAPOLATE_MODE")) {
            appInfo.setExtrapolateGHBCMode((strcmp(ch, "CONSTANT") == 0) ? ConstantExtrapolation : LinearExtrapolation);
        }
        if (const char* ch = in("VERTICAL_COORDINATE_EXTRAPOLATE_FIXED_SIGN")) {
            appInfo.setExtrapolateGHBCFixedSign((strcmp(ch, "ON") == 0) ? true : false);
        }
    }

    // W wind component computation mode/scaling factor
    std::string wModeStr;
    CROSS_S_CHK(in.getValue("W_WIND_SCALING_FACTOR_MODE", wModeStr));
    in.getValue(wScaling_, "W_WIND_SCALING_FACTOR");

    if (wModeStr == "AUTOMATIC") {
        wMode_ = Mv3VerticalVelocity;
    }
    else if (wModeStr == "USER") {
        wMode_ = ScaledVerticalVelocity;
    }
    else if (wModeStr == "COMPUTE") {
        wMode_ = ComputedVerticalVelocity;
    }
    else {
        setError(1, "CrossS -> Invalid value (=%s) specified for parameter W_WIND_SCALING_FACTOR_MODE!",
                 wModeStr.c_str());
    }

    // we only need to identify the T field if we need to compute the w from omega
    if (wMode_ != ComputedVerticalVelocity) {
        T_FIELD = -1;
    }

    // Retrieve parameters related to the vertical levels definition
    if ((const char*)in("LEVEL_SELECTION_TYPE")) {
        // TODO: turn it into an enum
        appInfo.levelSelectionType((const char*)in("LEVEL_SELECTION_TYPE"));
        if (appInfo.levelSelectionType() != "FROM_DATA" && appInfo.levelSelectionType() != "ADAPTIVE" &&
            appInfo.levelSelectionType() != "COUNT" && appInfo.levelSelectionType() != "LEVEL_LIST") {
            setError(1, "CrossS -> Invalid value(=%s) specified for parameter LEVEL_SELECTION_TYPE!",
                     appInfo.levelSelectionType().c_str());
        }
    }

    if ((const char*)in("LEVEL_LIST")) {
        double val = 0.;
        int cnt = in.iterInit("LEVEL_LIST");
        std::vector<double> lslist;
        lslist.reserve(cnt);
        for (int i = 0; i < cnt; ++i) {
            in.iterGetNextValue(val);
            lslist.push_back(val);
        }

        // Make sure the values are in crescent order
        std::sort(lslist.begin(), lslist.end());

        appInfo.levelList(lslist);
    }

    if ((const char*)in("LEVEL_COUNT"))
        appInfo.levelCount((int)in("LEVEL_COUNT"));

    if ((const char*)in("VERTICAL_SCALING")) {
        std::string vs = (const char*)in("VERTICAL_SCALING");
        bool bvs = (vs == "LINEAR") ? true : false;
        appInfo.vLinearScaling(bvs);
    }

    if ((const char*)in("BOTTOM_LEVEL"))
        appInfo.bottomLevel((double)in("BOTTOM_LEVEL"));

    if ((const char*)in("TOP_LEVEL"))
        appInfo.topLevel((double)in("TOP_LEVEL"));

    // Retrieve orography colour
    if ((const char*)in("OROGRAPHY_COLOUR"))
        oroColour_ = (const char*)in("OROGRAPHY_COLOUR");
    else if ((const char*)in("_CONTEXT")) {
        MvRequest contReq = in("_CONTEXT");
        if ((const char*)contReq("_ORIGINAL_REQUEST")) {
            MvRequest oriReq = contReq("_ORIGINAL_REQUEST");
            if ((const char*)oriReq("OROGRAPHY_COLOUR"))
                oroColour_ = (const char*)oriReq("OROGRAPHY_COLOUR");
        }
    }

    return true;
}

bool CrossS::consistencyCheck(MvRequest& req1, MvRequest& req2)
{
    // Check coordinates
    if ((double)req1("LINE", 0) != (double)req2("LINE", 0) ||
        (double)req1("LINE", 1) != (double)req2("LINE", 1) ||
        (double)req1("LINE", 2) != (double)req2("LINE", 2) ||
        (double)req1("LINE", 3) != (double)req2("LINE", 3)) {
        setError(1, "CrossS-> LINE coordinates are not consistent");
        return false;
    }

    // Check Wind parameters
    if ((const char*)req1("WIND_HORIZONTAL_MODE") != (const char*)req2("WIND_HORIZONTAL_MODE") ||
        (const char*)req1("WIND_PARALLEL") != (const char*)req2("WIND_PARALLEL") ||
        (const char*)req1("WIND_PERPENDICULAR") != (const char*)req2("WIND_PERPENDICULAR") ||
        (const char*)req1("WIND_INTENSITY") != (const char*)req2("WIND_INTENSITY")) {
        setError(1, "CrossS-> WIND parameters are not consistent");
        return false;
    }

    return true;
}

bool CrossS::generateData(ApplicationInfo& appInfo, ParamMap& params,
                          MvNetCDF& cdf, MvField& field,
                          const std::string& key)
{
    // Get parameter info
    ParamInfo* par = appInfo.getParamInfo(params, key);
    if (!par)
        return false;

    // Generate data
    if (appInfo.isLNSP(par->Parameter()))
        return generateLNSP(appInfo, cdf, par);
    else if (appInfo.isGHBC(par->Parameter()))
        return true;  // do we need to add it to the netCDF file????
    else
        return generateXsectData(appInfo, params, cdf, field, key);
}

// Generate data for Xsection. If some parameters are generated together, like in some
// cases U&V or u,v&w, just return if we have not got all the params. Otherwise,
// generate the data according to the values set.
bool CrossS::generateXsectData(ApplicationInfo& appInfo, ParamMap& params,
                               MvNetCDF& cdf, MvField& field,
                               const std::string& key)
{
    // Get parameter info
    ParamInfo* par = appInfo.getParamInfo(params, key);
    if (!par)
        return false;

    if (windMode_ == Wind2DMode) {
        if (par->Parameter() != std::max({U_FIELD, V_FIELD}))
            return true;
    }
    else if (windMode_ == Wind3DMode) {
        if (par->Parameter() != std::max({U_FIELD, V_FIELD, W_FIELD}))
            return true;
    }
    else if (windMode_ == Wind3DWithTMode) {
        if (par->Parameter() != std::max({U_FIELD, V_FIELD, W_FIELD, T_FIELD}))
            return true;
    }

    // Generate the netCDF parameter variable(s)
    if (windMode_ == Wind2DMode &&
        (!useWindIntensity_ && windProjection_ == NoWindProjection &&
         needVectorOutput_)) {
        if (par->Parameter() != std::max({U_FIELD, V_FIELD})) {
            throw MvException(moduleLabel_ + "wrong GRIB parameterId=" + std::to_string(par->Parameter()) +
                              " in Wind2DMode");
        }

        outputMode_ = determineWindOutputMode();
        assert(outputMode_ == VectorOutput);
        return wind2DValues(appInfo, params, cdf, key, field);
    }
    else if (windMode_ == Wind2DMode) {
        if (par->Parameter() != std::max({U_FIELD, V_FIELD})) {
            throw MvException(moduleLabel_ + "wrong GRIB parameterId=" + std::to_string(par->Parameter()) +
                              " in Wind2DMode");
        }
        outputMode_ = determineWindOutputMode();
        assert(outputMode_ == ScalarOutput);
        return wind2DValues(appInfo, params, cdf, key, field);
    }
    else if (windMode_ == Wind3DMode) {
        if (par->Parameter() != std::max({U_FIELD, V_FIELD, W_FIELD})) {
            throw MvException(moduleLabel_ + "wrong GRIB parameterId=" + std::to_string(par->Parameter()) +
                              " in Wind3DMode");
        }
        outputMode_ = determineWindOutputMode();
        return wind3DValues(appInfo, params, cdf, key, field);
    }
    else if (windMode_ == Wind3DWithTMode) {
        if (par->Parameter() != std::max({U_FIELD, V_FIELD, W_FIELD, T_FIELD})) {
            throw MvException(moduleLabel_ + "wrong GRIB parameterId=" + std::to_string(par->Parameter()) +
                              " in Wind3DMode");
        }
        outputMode_ = determineWindOutputMode();
        return wind3DValues(appInfo, params, cdf, key, field);
    }
    else {
        outputMode_ = ScalarOutput;
        return contourValues(appInfo, params, cdf, field, key);
    }

#if 0
    // Since the data are sorted by parameter, we wait until we get the
    // highest interesting parameter before doing the work.
    if (foundUV_ && par->Parameter() == U_FIELD) {
        return true;
    }

    if (foundW_ && (par->Parameter() == U_FIELD || par->Parameter() == V_FIELD)) {
        assert(!foundUV_);
        assert(!foundWT_);
        return true;
    }

    if (foundW_ && (par->Parameter() == U_FIELD || par->Parameter() == V_FIELD)) {
        assert(!foundUV_);
        assert(!foundWT_);
        return true;
    }

    // Generate the netCDF parameter variable
    if (foundUV_ && par->Parameter() == V_FIELD) {
        //outputMode_ = XWIND;
        outputMode_ = determineWindOutputMode();
        assert(outputMode_ == ScalarOutput);
        return wind2DValues(appInfo, params, cdf, key, field);
    }
    else if (foundW_ && par->Parameter() == W_FIELD) {


// TODO: does magics do any interpolation for cross section???
#if 0
        // Special 2 cases because Magics does not interpolate winds:
        // ML to PL or PL
        if (appInfo.levelType() == XS_ML_LNSP || appInfo.levelType() == XS_PL)
            appInfo.setVerticalInterpolationFlag(true);
#endif
        outputMode_ = determineWindOutputMode();
        //outputMode_ = XVELOCITY;
        return wind3DValues(appInfo, params, cdf, key, field);
    }
    else {
        //outputMode_ = XCONTOUR;
        outputMode_ = ScalarOutput;
        return contourValues(appInfo, params, cdf, field, key);
    }
#endif
}

// Generate data for plotting lnsp curve in xsection view
bool CrossS::generateLNSP(ApplicationInfo& appInfo, MvNetCDF& cdf, ParamInfo* par)
{
    // Get initial info
    double low = appInfo.tohPa(appInfo.topLevel());
    double high = appInfo.tohPa(appInfo.bottomLevel());
    int nrPoints = appInfo.NrPoints();

    // Add/update LNSP and auxiliary variables to the netCDF
    // These auxiliary variables will be used to plot the orography
    std::string varname = Xsect::getNetcdfVarname(par->ParamName());  // varname is LNSP
    MvNcVar* ncx1 = cdf.getVariable("orography_x_values");
    MvNcVar* ncy1 = cdf.getVariable("orography_y1_values");
    MvNcVar* ncy2 = cdf.getVariable("orography_y2_values");
    MvNcVar* ncln = cdf.getVariable(varname);
    if (!ncx1 || !ncy1 || !ncy2 || !ncln)  // Create new variables
    {
        ntime_ = -1;

        // Initialise dimensions
        std::vector<long> values_ndim;
        values_ndim.push_back(appInfo.NTimes());
        values_ndim.push_back(nrPoints);
        std::vector<std::string> values_sdim;
        values_sdim.push_back(XS_VARTIME);
        values_sdim.emplace_back("npoints");

        // Create LNSP variable
        if (!ncln) {
            ncln = cdf.addVariable(varname, ncDouble, values_ndim, values_sdim);
            ncln->addAttribute("long_name", par->ParamLongName().c_str());
            ncln->addAttribute("units", par->Units().c_str());
            ncln->addAttribute("_FillValue", XMISSING_VALUE);
        }

        // Create orography x values variable
        if (!ncx1) {
            ncx1 = cdf.addVariable("orography_x_values", ncFloat, values_ndim, values_sdim);
            ncx1->addAttribute("long_name", "orography_x_values");
            ncx1->addAttribute("units", " ");
            ncx1->addAttribute("_FillValue", (float)XMISSING_VALUE);
        }

        // Create orography y1 values variable
        if (!ncy1) {
            ncy1 = cdf.addVariable("orography_y1_values", ncDouble, values_ndim, values_sdim);
            ncy1->addAttribute("long_name", "orography_y1_values");
            ncy1->addAttribute("units", " ");
            ncy1->addAttribute("_FillValue", XMISSING_VALUE);
        }

        // Create orography y2 values variable
        if (!ncy2) {
            ncy2 = cdf.addVariable("orography_y2_values", ncDouble, values_ndim, values_sdim);
            ncy2->addAttribute("long_name", "orography_y2_values");
            ncy2->addAttribute("units", " ");
            ncy2->addAttribute("_FillValue", XMISSING_VALUE);
        }
    }

    // Get LNSP values
    double* splin = par->getOneLevelValues("1");
    if (!splin) {
        setError(1, "CrossS-> LNSP field not found");
        return false;
    }

    // Memory allocation
    auto* y1_values = new double[nrPoints];
    auto* y2_values = new double[nrPoints];

    // Get X values
    const std::vector<double>& lon = (appInfo.haveLonsForPlotting()) ? appInfo.getLongitudeForPlotting() : appInfo.getLongitude();
    const std::vector<double>& lat = (appInfo.haveLatsForPlotting()) ? appInfo.getLatitudeForPlotting() : appInfo.getLatitude();
    const std::vector<double>& x_values = (lon[0] == lon[1]) ? lat : lon;

    // Compute Y values
    for (int i = 0; i < nrPoints; i++) {
        if (splin[i] >= XMISSING_VALUE)
            y1_values[i] = high;
        else {
            double w = appInfo.tohPa(exp(splin[i]));
            w = MAX(low, w);
            w = MIN(high, w);
            y1_values[i] = w;
        }
        y2_values[i] = high;
    }

    // Write values to the netCDF
    ntime_++;
    ncln->setCurrent(ntime_);
    ncln->put(splin, 1, nrPoints);
    ncx1->setCurrent(ntime_);
    ncx1->put(x_values, 1, nrPoints);
    ncy1->setCurrent(ntime_);
    ncy1->put(y1_values, 1, nrPoints);
    ncy2->setCurrent(ntime_);
    ncy2->put(y2_values, 1, nrPoints);

    // Release memory
    delete[] y1_values;
    delete[] y2_values;

    return true;
}

// Generate values for horizontal wind
bool CrossS::wind2DValues(ApplicationInfo& appInfo, ParamMap& params, MvNetCDF& cdf,
                          const std::string& key, MvField& field)
{
    // Find the v field
    ParamInfo* par_v = appInfo.getParamInfo(params, V_FIELD, key);
    if (!par_v)
        return false;

    // The paraminfo contains the v field, now find the u field,
    // substitute u for v in the key and find info in list.
    ParamInfo* par_u = appInfo.getParamInfo(params, U_FIELD, key);
    if (!par_u)
        return false;

    assert(windMode_ == Wind2DMode);

    if (par_u->Levels().size() != par_v->Levels().size()) {
        throw MvException(moduleLabel_ + "number of levels for u and v must be the same! " +
                          std::to_string(par_u->Levels().size()) + " != " + std::to_string(par_v->Levels().size()));
    }

    // Wind speed
    if (outputMode_ == ScalarOutput) {
        assert(useWindIntensity_ || windProjection_ != NoWindProjection);

        // Temporary used for holding mapped u/v values.
        ParamInfo par_uv(par_v->Parameter(), par_v->Date(), par_v->Time(), par_v->Step(), par_v->ParamName(), par_v->ParamLongName(), par_v->Units(), par_v->LevelType());

        std::vector<double> y_values;
        std::vector<double> cp;

        // compute values
        computeWind2D(appInfo, par_uv, par_u, par_v);

        // interpolate result onto the output levels
        if (!appInfo.computeLevelMatrixValues(params, key, &par_uv, y_values, cp, field)) {
            setError(1, "CrossS::wind2DValues --> Could not find the requested key field: %s", key.c_str());
            return false;
        }

        int nrLevels = appInfo.outputLevels();
        int nrPoints = appInfo.NrPoints();

        if (nrLevels <= 0) {
            setError(1, "CrossS::wind2DValues --> Incorrect number of vertical levels =%d", nrLevels);
            return false;
        }

        if (nrPoints <= 0) {
            setError(1, "CrossS::wind2DValues --> Incorrect number of horizontal points =%d", nrPoints);
            return false;
        }

        // define the netCDF variable + level name
        std::string varname = this->getNetcdfVarname(par_u->ParamName());
        if (!plotVar_) {
            plotVar_ = par_u;
        }

        // Create variable 'levels'
        std::string levname = this->getNetcdfLevelVarname(varname);
        MvNcVar* nclevels = cdf.getVariable(levname);

        // Create variable 'levels'
        if (!nclevels) {
            // Initialise dimensions
            std::vector<long> values_ndim;
            values_ndim.push_back(appInfo.NTimes());
            values_ndim.push_back(nrLevels);

            std::vector<std::string> values_sdim;
            values_sdim.push_back(XS_VARTIME);
            values_sdim.push_back(XS_VARLEVEL);

            nclevels = cdf.addVariable(levname, ncDouble, values_ndim, values_sdim);
            if (!nclevels) {
                setError(1, "CrossS::wind2DValues --> Could not create netCDF level variable: %s", varname.c_str());
                return false;
            }

            // TODO: put proper level information here
            nclevels->addAttribute("long_name", "Atmospheric Levels");
            nclevels->addAttribute("units", "hPa");
            nclevels->addAttribute("positive", "down");
        }

        // Create netCDF variable
        MvNcVar* ncv = cdf.getVariable(varname);
        if (!ncv) {
            // Initialise dimensions
            std::vector<long> values_ndim;
            values_ndim.push_back(appInfo.NTimes());
            values_ndim.push_back(nrLevels);
            values_ndim.push_back(nrPoints);

            std::vector<std::string> values_sdim;
            values_sdim.push_back(XS_VARTIME);
            values_sdim.push_back(XS_VARLEVEL);
            values_sdim.push_back(XS_VARLON);

            // Create variable
            ncv = cdf.addVariable(varname, ncDouble, values_ndim, values_sdim);
            if (!ncv) {
                setError(1, "CrossS::wind2DValues --> Could not create netCDF variable: %s", varname.c_str());
                return false;
            }

            ncv->addAttribute("long_name", par_v->ParamLongName().c_str());
            ncv->addAttribute("units", par_v->Units().c_str());
            ncv->addAttribute("_FillValue", XMISSING_VALUE);
            ncv->addAttribute("title", titleVariable(appInfo, par_v).c_str());
        }

        // Write level values to the netCDF
        nclevels->setCurrent(ntime_);

        // Pa -> hPa
        if (appInfo.levelType() == XS_PL || appInfo.levelType() == XS_ML_LNSP) {
            for (int j = 0; j < nrLevels; j++) {
                y_values[j] = appInfo.tohPa(y_values[j]);
            }
        }

        nclevels->put(y_values, 1, (long)y_values.size());

        // Write values to the netCDF
        for (int j = 0; j < nrLevels; j++) {
            ncv->setCurrent(ntime_, j);
            ncv->put(&cp[j * nrPoints], 1, 1, (long)nrPoints);
        }

        ntime_++;

        // Unprojected wind components
    }
    else {
        assert(outputMode_ == VectorOutput);
        assert(windProjection_ == NoWindProjection);
        assert(useWindIntensity_ == false);

        std::vector<double> y_values;
        std::vector<double> cp;

        // ----------------------------------
        // First: handle u component
        // -----------------------------------

        // interpolate result onto the output levels
        if (!appInfo.computeLevelMatrixValues(params, key, par_u, y_values, cp, field)) {
            setError(1, "CrossS::wind2DValues --> Could not find the requested key field: %s", key.c_str());
            return false;
        }

        int nrLevels = appInfo.outputLevels();
        int nrPoints = appInfo.NrPoints();

        if (nrLevels <= 0) {
            setError(1, "CrossS::wind2DValues --> Incorrect number of vertical levels=%d", nrLevels);
            return false;
        }

        if (nrPoints <= 0) {
            setError(1, "CrossS::wind2DValues --> Incorrect number of horizontal points=%d", nrPoints);
            return false;
        }

        // define the netCDF variable + level name
        std::string varname = this->getNetcdfXComponentVarname();
        //        if (!plotVar_) {
        //            plotVar_ = par_u;
        //        }

        // Create variable 'levels'
        std::string levname = this->getNetcdfLevelVarname(varname);
        MvNcVar* nclevels = cdf.getVariable(levname);

        // Create variable 'levels'
        if (!nclevels) {
            // Initialise dimensions
            std::vector<long> values_ndim;
            values_ndim.push_back(appInfo.NTimes());
            values_ndim.push_back(nrLevels);

            std::vector<std::string> values_sdim;
            values_sdim.push_back(XS_VARTIME);
            values_sdim.push_back(XS_VARLEVEL);

            nclevels = cdf.addVariable(levname, ncDouble, values_ndim, values_sdim);
            if (!nclevels) {
                setError(1, "CrossS::wind2DValues --> Could not create netCDF level variable: %s", varname.c_str());
                return false;
            }

            // TODO: put proper level information here
            nclevels->addAttribute("long_name", "Atmospheric Levels");
            nclevels->addAttribute("units", "hPa");
            nclevels->addAttribute("positive", "down");
        }

        // Create netCDF variable
        MvNcVar* ncv = cdf.getVariable(varname);
        if (!ncv) {
            // Initialise dimensions
            std::vector<long> values_ndim;
            values_ndim.push_back(appInfo.NTimes());
            values_ndim.push_back(nrLevels);
            values_ndim.push_back(nrPoints);

            std::vector<std::string> values_sdim;
            values_sdim.push_back(XS_VARTIME);
            values_sdim.push_back(XS_VARLEVEL);
            values_sdim.push_back(XS_VARLON);

            // Create variable
            ncv = cdf.addVariable(varname, ncDouble, values_ndim, values_sdim);
            if (!ncv) {
                setError(1, "CrossS::wind2DValues --> Could not create netCDF variable: %s", varname.c_str());
                return false;
            }

            ncv->addAttribute("long_name", par_u->ParamLongName().c_str());
            ncv->addAttribute("units", par_u->Units().c_str());
            ncv->addAttribute("_FillValue", XMISSING_VALUE);
            ncv->addAttribute("title", titleVariable(appInfo, par_u).c_str());
        }

        // Write level values to the netCDF
        nclevels->setCurrent(ntime_);

        // Pa -> hPa
        if (appInfo.levelType() == XS_PL || appInfo.levelType() == XS_ML_LNSP) {
            for (int j = 0; j < nrLevels; j++) {
                y_values[j] = appInfo.tohPa(y_values[j]);
            }
        }

        nclevels->put(y_values, 1, (long)y_values.size());

        // Write u component values into the netCDF
        for (int j = 0; j < nrLevels; j++) {
            ncv->setCurrent(ntime_, j);
            ncv->put(&cp[j * nrPoints], 1, 1, (long)nrPoints);
        }

        //---------------------------------------
        // Second phase: handle v component
        //---------------------------------------

        size_t y_values_size_ori = y_values.size();
        size_t cp_size_ori = cp.size();

        if (!appInfo.computeLevelMatrixValues(params, key, par_v, y_values, cp, field)) {
            setError(1, "CrossS::wind2DValues --> Could not find the requested field for v wind component! key=%s", key.c_str());
            return false;
        }

        if (y_values_size_ori != y_values.size()) {
            setError(1, "CrossS::wind2DValues --> Inconsitent number of vertical levels for v wind component! u=%d v=%d",
                     y_values_size_ori, y_values.size());
            return false;
        }

        if (cp_size_ori != cp.size()) {
            setError(1, "CrossS::wind2DValues --> Inconsitent number of values for v wind component! u=%d v=%d",
                     cp_size_ori, cp.size());
            return false;
        }

        // define the netCDF variable + level name
        varname = this->getNetcdfYComponentVarname();

        // Create netCDF variable
        ncv = cdf.getVariable(varname);
        if (!ncv) {
            // Initialise dimensions
            std::vector<long> values_ndim;
            values_ndim.push_back(appInfo.NTimes());
            values_ndim.push_back(nrLevels);
            values_ndim.push_back(nrPoints);

            std::vector<std::string> values_sdim;
            values_sdim.push_back(XS_VARTIME);
            values_sdim.push_back(XS_VARLEVEL);
            values_sdim.push_back(XS_VARLON);

            // Create variable
            ncv = cdf.addVariable(varname, ncDouble, values_ndim, values_sdim);
            if (!ncv) {
                setError(1, "CrossS::wind2DValues --> Could not create netCDF variable: %s", varname.c_str());
                return false;
            }

            ncv->addAttribute("long_name", par_v->ParamLongName().c_str());
            ncv->addAttribute("units", par_v->Units().c_str());
            ncv->addAttribute("_FillValue", XMISSING_VALUE);
            ncv->addAttribute("title", titleVariable(appInfo, par_v).c_str());
        }

        // Write v component values into the netCDF
        for (int j = 0; j < nrLevels; j++) {
            ncv->setCurrent(ntime_, j);
            ncv->put(&cp[j * nrPoints], 1, 1, (long)nrPoints);
        }

        ntime_++;
    }

    return true;
}

// Generate wind data for U/V/W
bool CrossS::wind3DValues(ApplicationInfo& appInfo, ParamMap& params,
                          MvNetCDF& cdf, const std::string& key, MvField& field)
{
    // Find the w field
    ParamInfo* par_w = appInfo.getParamInfo(params, W_FIELD, key);
    if (!par_w)
        return false;

    // Use the W field key to find U/V info in the list
    ParamInfo* par_u = appInfo.getParamInfo(params, U_FIELD, key);
    if (!par_u)
        return false;

    ParamInfo* par_v = appInfo.getParamInfo(params, V_FIELD, key);
    if (!par_v)
        return false;

    if (par_u->Levels().size() != par_v->Levels().size()) {
        throw MvException(moduleLabel_ + "number of levels for u and v must be the same! " +
                          std::to_string(par_u->Levels().size()) + " != " + std::to_string(par_v->Levels().size()));
    }

    if (par_u->Levels().size() != par_w->Levels().size()) {
        throw MvException(moduleLabel_ + "number of levels for u and w must be the same! " +
                          std::to_string(par_u->Levels().size()) + " != " + std::to_string(par_w->Levels().size()));
    }

    // Create a structure for the result/horizontal component
    ParamInfo par_uv(par_v->Parameter(), par_v->Date(), par_v->Time(), par_v->Step(),
                     par_v->ParamName(), par_v->ParamLongName(), par_v->Units(),
                     par_v->LevelType());

    std::vector<double> y_values;
    std::vector<double> cp;

    if (windProjection_ == NormalWindProjection) {
        this->computeWind2D(appInfo, par_uv, par_u, par_v);
    }
    else {
        // Compute/scale the vertical wind component

        // MV3 method
        if (wMode_ == Mv3VerticalVelocity) {
            appInfo.scaleVerticalVelocity_mv3(par_w);

            // hydrostatic computation: omega to w
        }
        else if (wMode_ == ComputedVerticalVelocity) {
            ParamInfo* par_t = nullptr;
            ParamInfo* par_lnsp = nullptr;
            if (windMode_ == Wind3DWithTMode) {
                par_t = appInfo.getParamInfo(params, T_FIELD, key);
                if (!par_t)
                    return false;
            }
            if (appInfo.levelType() == XS_ML_LNSP) {
                par_lnsp = appInfo.getParamInfo(params, LNSP_FIELD, key);
                if (!par_lnsp)
                    return false;
            }

            appInfo.computeVerticalVelocity(par_w, par_t, par_lnsp, field, wScaling_);
        }
        else {
            appInfo.scaleVerticalVelocity(par_w, wScaling_);
        }

        //
        this->computeWind3D(appInfo, par_uv, par_u, par_v, par_w);
    }

    // interpolate result onto the output levels
    if (!appInfo.computeLevelMatrixValues(params, key, &par_uv, y_values, cp, field)) {
        setError(1, "CrossS::wind3DValues --> Could not find the requested key field: %s", key.c_str());
        return false;
    }

    int nrLevels = appInfo.outputLevels();
    int nrPoints = appInfo.NrPoints();

    if (nrLevels <= 0) {
        setError(1, "CrossS::wind3DValues --> Incorrect number of vertical levels =%d", nrLevels);
        return false;
    }

    if (nrPoints <= 0) {
        setError(1, "CrossS::wind3DValues --> Incorrect number of vertical levels =%d", nrLevels);
        return false;
    }

    // define the netCDF variable name
    std::string varname;

    if (outputMode_ == ScalarOutput) {
        varname = this->getNetcdfVarname(par_u->ParamName());
        assert(useWindIntensity_ || windProjection_ == NormalWindProjection);
        if (!plotVar_) {
            plotVar_ = par_u;
        }
    }
    else if (outputMode_ == VectorOutput) {
        assert(!useWindIntensity_);
        assert(windProjection_ != NormalWindProjection);
        varname = this->getNetcdfXComponentVarname();
    }

    // Create variable 'levels'
    // this depends on the output mode!
    std::string levname = this->getNetcdfLevelVarname(varname);
    MvNcVar* nclevels = cdf.getVariable(levname);

    // Create variable 'levels'
    if (!nclevels) {
        // Initialise dimensions
        std::vector<long> values_ndim;
        values_ndim.push_back(appInfo.NTimes());
        values_ndim.push_back(nrLevels);

        std::vector<std::string> values_sdim;
        values_sdim.push_back(XS_VARTIME);
        values_sdim.push_back(XS_VARLEVEL);

        nclevels = cdf.addVariable(levname, ncDouble, values_ndim, values_sdim);
        if (!nclevels) {
            setError(1, "CrossS::wind3DValues --> Could not create netCDF level variable: %s", varname.c_str());
            return false;
        }

        nclevels->addAttribute("long_name", "Atmospheric Levels");
        nclevels->addAttribute("units", "hPa");
        nclevels->addAttribute("positive", "down");
    }

    // Create netCDF variable
    MvNcVar* ncv = cdf.getVariable(varname);
    if (!ncv) {
        // Initialise dimensions
        std::vector<long> values_ndim;
        values_ndim.push_back(appInfo.NTimes());
        values_ndim.push_back(nrLevels);
        values_ndim.push_back(nrPoints);

        std::vector<std::string> values_sdim;
        values_sdim.push_back(XS_VARTIME);
        values_sdim.push_back(XS_VARLEVEL);
        values_sdim.push_back(XS_VARLON);

        // Create variable
        ncv = cdf.addVariable(varname, ncDouble, values_ndim, values_sdim);
        if (!ncv) {
            setError(1, "CrossS::wind3DValues --> Could not create netCDF variable: %s", varname.c_str());
            return false;
        }

        ncv->addAttribute("long_name", par_v->ParamLongName().c_str());
        ncv->addAttribute("units", par_v->Units().c_str());
        ncv->addAttribute("_FillValue", XMISSING_VALUE);
        ncv->addAttribute("title", titleVariable(appInfo, par_v).c_str());
    }

    // Write level values to the netCDF
    nclevels->setCurrent(ntime_);

    // Pa -> hPa
    if (appInfo.levelType() == XS_PL || appInfo.levelType() == XS_ML_LNSP) {
        for (int j = 0; j < nrLevels; j++) {
            y_values[j] = appInfo.tohPa(y_values[j]);
        }
    }

    nclevels->put(y_values, 1, (long)y_values.size());

    // Write values to the netCDF
    for (int j = 0; j < nrLevels; j++) {
        ncv->setCurrent(ntime_, j);
        ncv->put(&cp[j * nrPoints], 1, 1, (long)nrPoints);
    }

    //------------------------------------------------------
    // y component on the xs = vertical wind component (w)
    //------------------------------------------------------

    if (outputMode_ == VectorOutput) {
        assert(windProjection_ == ParallelWindProjection && !useWindIntensity_);

        size_t y_values_size_ori = y_values.size();
        size_t cp_size_ori = cp.size();

        // interpolate w result onto the output levels
        if (!appInfo.computeLevelMatrixValues(params, key, par_w, y_values, cp, field)) {
            setError(1, "CrossS::wind3DValues --> Could not find the requested key field: %s", key.c_str());
            return false;
        }

        if (y_values_size_ori != y_values.size()) {
            setError(1, "CrossS::wind3DValues --> Inconsitent number of vertical levels for projected wind components: uv=%d w=%d",
                     y_values_size_ori, y_values.size());
            return false;
        }

        if (cp_size_ori != cp.size()) {
            setError(1, "CrossS::wind3DValues --> Inconsitent number of values for projected wind components: uv=%d w=%d",
                     cp_size_ori, cp.size());
            return false;
        }

        // Get/create the netCDF variable (y_component)
        varname = this->getNetcdfYComponentVarname();
        // ncvy = this->getNetcdfVariable(cdf, varname, appInfo, varname, par_w);

        // Create netCDF variable
        ncv = cdf.getVariable(varname);
        if (!ncv) {
            // Initialise dimensions
            std::vector<long> values_ndim;
            values_ndim.push_back(appInfo.NTimes());
            values_ndim.push_back(nrLevels);
            values_ndim.push_back(nrPoints);

            std::vector<std::string> values_sdim;
            values_sdim.push_back(XS_VARTIME);
            values_sdim.push_back(XS_VARLEVEL);
            values_sdim.push_back(XS_VARLON);

            // Create variable
            ncv = cdf.addVariable(varname, ncDouble, values_ndim, values_sdim);
            if (!ncv) {
                setError(1, "CrossS::wind3DValues --> Could not create netCDF variable: %s", varname.c_str());
                return false;
            }

            ncv->addAttribute("long_name", par_w->ParamLongName().c_str());
            ncv->addAttribute("units", par_w->Units().c_str());
            ncv->addAttribute("_FillValue", XMISSING_VALUE);
            ncv->addAttribute("title", titleVariable(appInfo, par_v).c_str());
        }

        // Write values to the netCDF
        for (int j = 0; j < nrLevels; j++) {
            ncv->setCurrent(ntime_, j);
            ncv->put(&cp[j * nrPoints], 1, 1, (long)nrPoints);
        }
    }

    ntime_++;

    return true;

#if 0

    // Compute UV values (x_component).
    std::vector<double> cp;
    std::vector<double> y_values;
    double* splin = 0;
    if (!appInfo.isInterpolate()) {
        // Compute level values and interpolate matrix
        appInfo.computeLevelInfo(&par_uv, y_values);
        appInfo.InterpolateVerticala(cp, &par_uv, y_values);
    }
    else {
        // It will always go to interpolate on the vertical because Magics
        // can not interpolate wind arrows at the moment
        if (appInfo.levelType() == XS_ML_LNSP) {
            ParamInfo* parLnsp = appInfo.getParamInfo(params, appInfo.LNSP(), key);
            if (!parLnsp)  // LNSP not found
                return false;

            splin = parLnsp->getOneLevelValues("1");
            if (!splin)  // LNSP values not found
            {
                setError(1, "CrossS-> LNSP field not found");
                return false;
            }

            // Compute level values and interpolate matrix
            appInfo.computeLevelInfoUI(&par_uv, y_values, splin, field);
            appInfo.InterpolateVerticalb(field, cp, &par_uv, splin, y_values);
        }
        else {
            // Compute level values and interpolate matrix
            appInfo.computeLevelInfoUI(&par_uv, y_values, splin, field);
            appInfo.InterpolateVerticala(cp, &par_uv, y_values);
        }
    }

    // Get/create the netCDF variable (x_component)
    std::string varname = this->getNetcdfXComponentVarname();
    MvNcVar* ncvx  = this->getNetcdfVariable(cdf, varname, appInfo, varname, par_u);

    // Write UV values to the netCDF (x_component)
    int nrLevels = appInfo.outputLevels();
    int nrPoints = appInfo.NrPoints();
    ntime_       = 0;
    int i;
    for (i = 0; i < nrLevels; i++) {
        ncvx->setCurrent(ntime_, i);
        ncvx->put(&cp[i * nrPoints], 1, 1, (long)nrPoints);
    }

    // Compute Velocity values (y_component)
    if (appInfo.levelType() != XS_ML_LNSP) {
        // Compute level values and interpolate matrix
        // Assume that the level values are the same as of x_component
        //  appInfo.computeLevelInfo(par_w,y_values);
        appInfo.InterpolateVerticala(cp, par_w, y_values);
    }
    else {
        // Compute level values and interpolate matrix
        // appInfo.computeLevelInfo(par_w,y_values,splin,field);
        appInfo.InterpolateVerticalb(field, cp, par_w, splin, y_values);
    }

    // Get/create the netCDF variable (y_component)
    varname       = this->getNetcdfYComponentVarname();
    MvNcVar* ncvy = this->getNetcdfVariable(cdf, varname, appInfo, varname, par_w);

    // Write Velocity values to the netCDF (y_component)
    for (i = 0; i < nrLevels; i++) {
        ncvy->setCurrent(ntime_, i);
        ncvy->put(&cp[i * nrPoints], 1, 1, (long)nrPoints);
    }

    // Compute level values
    // Assume that the values are the same for the x and y component
    if (appInfo.isInterpolate())
        for (i = 0; i < nrLevels; i++)
            y_values[i] /= 100.;

    // Get/create the netCDF variable (levels)
    varname       = "lev";
    MvNcVar* ncvl = this->getNetcdfVariableLevel(cdf, varname, appInfo);

    // Write level values to the netCDF
    ncvl->setCurrent(ntime_);
    ncvl->put(y_values, 1, (long)y_values.size());

    return true;
#endif
}

#if 0
void CrossS::setHorComp(int no, int perp, int par, int intens)
{
    hcNumber_        = no;
    hcPerpendicular_ = perp;
    hcParallel_      = par;
    hcIntensity_     = intens;
}
#endif

void CrossS::setHorCompFlags(bool foundU, bool foundV, bool foundW, bool foundT)
{
    windMode_ = NoWindMode;

    if (foundU && foundV && foundW) {
        if (windProjection_ != NoWindProjection || useWindIntensity_) {
            if (wMode_ == ComputedVerticalVelocity && foundT) {
                windMode_ = Wind3DWithTMode;
            }
            else {
                windMode_ = Wind3DMode;
            }
        }
    }
    else if (foundU && foundV) {
        if (windProjection_ != NoWindProjection || useWindIntensity_ || needVectorOutput_) {
            windMode_ = Wind2DMode;
        }
    }

    // This is needed for the global title
    if (windMode_ != NoWindMode) {
        outputMode_ = determineWindOutputMode();
    }
}

CrossS::OutputMode CrossS::determineWindOutputMode() const
{
    if (windMode_ == Wind2DMode) {
        return (useWindIntensity_ || windProjection_ != NoWindProjection || !needVectorOutput_) ? ScalarOutput : VectorOutput;
    }
    else if (windMode_ != NoWindMode) {
        return (useWindIntensity_ || windProjection_ == NormalWindProjection) ? ScalarOutput : VectorOutput;
    }
    return ScalarOutput;
}

// Project the uv wind components in the specified way
void CrossS::computeWind2D(ApplicationInfo& appInfo, ParamInfo& par_uv, ParamInfo* par_u, ParamInfo* par_v)
{
    // Map U/V into planes
    LevelMap lmap_u = par_u->Levels();
    LevelMap lmap_v = par_v->Levels();
    auto uu = lmap_u.begin(), vv = lmap_v.begin();
    double *uValues = nullptr, *vValues = nullptr;

    assert(lmap_u.size() == lmap_v.size());

    int nrPoints = appInfo.NrPoints();
    auto* valuesuv = new double[nrPoints];
    double ansin = appInfo.Ansin();
    double ancos = appInfo.Ancos();

    for (; uu != lmap_u.end() && vv != lmap_v.end(); uu++, vv++) {
        uValues = (*uu).second->XValues();
        vValues = (*vv).second->XValues();

        for (int i = 0; i < nrPoints; i++) {
            if (uValues[i] < XMISSING_VALUE && vValues[i] < XMISSING_VALUE) {
                if (windProjection_ == NormalWindProjection) {
                    valuesuv[i] = -uValues[i] * ansin + vValues[i] * ancos;
                    if (useWindIntensity_) {
                        valuesuv[i] = fabs(valuesuv[i]);
                    }
                }
                else if (windProjection_ == ParallelWindProjection) {
                    valuesuv[i] = uValues[i] * ancos + vValues[i] * ansin;
                    if (useWindIntensity_) {
                        valuesuv[i] = fabs(valuesuv[i]);
                    }
                }
                else {
                    assert(windProjection_ == NoWindProjection);
                    assert(useWindIntensity_);
                    valuesuv[i] = sqrt(uValues[i] * uValues[i] + vValues[i] * vValues[i]);
                }
            }
            else {
                valuesuv[i] = XMISSING_VALUE;
            }
        }

        par_uv.Level(valuesuv, (*uu).first, nrPoints, appInfo.levelType() == XS_PL);
    }

    delete[] valuesuv;
}

void CrossS::computeWind3D(ApplicationInfo& appInfo, ParamInfo& par_uv, ParamInfo* par_u, ParamInfo* par_v,
                           ParamInfo* par_w)
{
    // Map U/V into planes
    LevelMap lmap_u = par_u->Levels();
    LevelMap lmap_v = par_v->Levels();
    LevelMap lmap_w = par_w->Levels();
    auto uu = lmap_u.begin();
    auto vv = lmap_v.begin();
    auto ww = lmap_w.begin();

    assert(lmap_u.size() == lmap_v.size());
    assert(lmap_u.size() == lmap_w.size());

    double *uValues = nullptr, *vValues = nullptr, *wValues = nullptr;

    int nrPoints = appInfo.NrPoints();
    auto* valuesuv = new double[nrPoints];
    double ansin = appInfo.Ansin();
    double ancos = appInfo.Ancos();

    for (; uu != lmap_u.end() && vv != lmap_v.end() && ww != lmap_w.end(); uu++, vv++, ww++) {
        uValues = (*uu).second->XValues();
        vValues = (*vv).second->XValues();
        wValues = (*ww).second->XValues();

        for (int i = 0; i < nrPoints; i++) {
            if (windProjection_ == NormalWindProjection) {
                if (uValues[i] < XMISSING_VALUE && vValues[i] < XMISSING_VALUE) {
                    valuesuv[i] = -uValues[i] * ansin + vValues[i] * ancos;
                    if (useWindIntensity_) {
                        valuesuv[i] = abs(valuesuv[i]);
                    }
                }
                else {
                    valuesuv[i] = XMISSING_VALUE;
                }
            }
            else {
                if (uValues[i] < XMISSING_VALUE && vValues[i] < XMISSING_VALUE && wValues[i] < XMISSING_VALUE) {
                    if (windProjection_ == ParallelWindProjection) {
                        valuesuv[i] = uValues[i] * ancos + vValues[i] * ansin;
                        if (useWindIntensity_) {
                            valuesuv[i] = sqrt(valuesuv[i] * valuesuv[i] + wValues[i] * wValues[i]);
                        }
                    }
                    else {
                        assert(windProjection_ == NoWindProjection);
                        assert(useWindIntensity_);
                        valuesuv[i] = sqrt(uValues[i] * uValues[i] + vValues[i] * vValues[i] + wValues[i] * wValues[i]);
                    }
                }
                else {
                    valuesuv[i] = XMISSING_VALUE;
                }
            }
        }
        par_uv.Level(valuesuv, (*uu).first, nrPoints, appInfo.levelType() == XS_PL);
    }

    delete[] valuesuv;
}

int CrossS::computeGeographicalPoints(ApplicationInfo& appInfo, MvField* field)
{
    // Compute distance
    double x1 = 0., x2 = 0., y1 = 0., y2 = 0.;
    appInfo.getAreaLine(x1, x2, y1, y2);
    double dellat = ABS(y2 - y1), dellon = ABS(x2 - x1);

    int np = 64;
    // Compute initial number of points
    if (appInfo.GridNS() != cValueNotGiven && appInfo.GridEW() != cValueNotGiven) {
        np = int(sqrt(dellon * dellon + dellat * dellat) / MIN(fabs(appInfo.GridNS()), fabs(appInfo.GridEW())));
        if (np <= 0) {
            setError(1, "CrossSection-> Failed to compute number of grid points. Input grid geometry might not be supported!");
            return 0;
        }
    }
    np = MAX(np, 64);

    // Compute the coordinates
    appInfo.computeLine(np, field);

    return np;
}

bool CrossS::fillValues(ApplicationInfo& appInfo, MvField& field, double* vals)
{
    int i;
    MvFieldExpander x(field);

    // Get number of points and lat/long interpolation points
    int npoint = appInfo.NrPoints();
    const std::vector<double>& lat = appInfo.getLatitude();
    const std::vector<double>& lon = appInfo.getLongitude();

    if (appInfo.isHorInterpolate()) {
        // Interpolatate along XS line
        for (i = 0; i < npoint; i++) {
            double val = field.interpolateAt(lon[i], lat[i]);
            vals[i] = (val >= DBL_MAX) ? XMISSING_VALUE : val;
        }
    }
    else {
        for (i = 0; i < npoint; i++) {
            double val = field.nearestGridpoint(lon[i], lat[i], false);
            vals[i] = (val >= DBL_MAX) ? XMISSING_VALUE : val;
        }
    }

    return true;
}

MvRequest CrossS::createOutputRequest(ApplicationInfo& appInfo, ParamInfo* parInfo)
{
    // Create netCDF data request
    MvRequest xs("NETCDF");
    xs("PATH") = ncName_.c_str();
    xs("TEMPORARY") = 1;

    std::string dims = XS_VARTIME + ":0";

    // Create NetCDF output request
    std::string varname = getNetcdfVarname(parInfo->ParamName()).c_str();
    MvRequest out1("NETCDF_XY_MATRIX");
    out1("NETCDF_DATA") = xs;
    out1("NETCDF_MISSING_ATTRIBUTE") = "_FillValue";
    out1("NETCDF_VALUE_VARIABLE") = varname.c_str();
    out1("NETCDF_Y_VARIABLE") = getNetcdfLevelVarname(varname).c_str();
    out1("NETCDF_DIMENSION_SETTING_METHOD") = "index";
    out1("NETCDF_DIMENSION_SETTING") = dims.c_str();

    double X1 = 0., X2 = 0., Y1 = 0., Y2 = 0.;
    appInfo.getAreaLine(X1, X2, Y1, Y2);


    // across the pole?
    if (X1 == X2) {
        const std::string latsVar = (appInfo.haveLatsForPlotting()) ? XS_VARLATPLOT : XS_VARLAT;
        const std::string lonsVar = (appInfo.haveLonsForPlotting()) ? XS_VARLONPLOT : XS_VARLON;
        out1("NETCDF_X_VARIABLE") = latsVar.c_str();
        out1("NETCDF_X_AUXILIARY_VARIABLE") = lonsVar.c_str();
        out1("NETCDF_X_GEOLINE_CONVENTION") = "latlon";
    }
    else {
        out1("NETCDF_X_VARIABLE") = "lon";
        out1("NETCDF_X_AUXILIARY_VARIABLE") = "lat";
        out1("NETCDF_X_GEOLINE_CONVENTION") = "lonlat";
    }

    // Add X/Y Component, if this is the case
    if (outputMode_ == VectorOutput) {
        out1("NETCDF_X_COMPONENT_VARIABLE") = (const char*)this->getNetcdfXComponentVarname().c_str();
        out1("NETCDF_Y_COMPONENT_VARIABLE") = (const char*)this->getNetcdfYComponentVarname().c_str();

        // Add flag to indicate that a MWIND visdef should be applied to this data
        out1("_VISDEF") = "MWIND";
    }

    // Add information to help the Macro Converter to translate
    // the output request to a Macro code
    out1("_ORIGINAL_REQUEST") = buildMacroConverterRequest();

    // Add the orography curve visualiser
    if (appInfo.haveLNSP() && (appInfo.levelType() == XS_ML_LNSP || appInfo.levelType() == XS_PL)) {
        MvRequest out_orog("NETCDF_XY_POINTS");
        out_orog("NETCDF_DATA") = xs;
        out_orog("NETCDF_Y_VARIABLE") = "orography_y2_values";
        out_orog("NETCDF_Y2_VARIABLE") = "orography_y1_values";
        out_orog("NETCDF_X_VARIABLE") = "orography_x_values";
        out_orog("NETCDF_X2_VARIABLE") = "orography_x_values";  //"orography_x2_values";
        out_orog("NETCDF_MISSING_ATTRIBUTE") = "_FillValue";
        out_orog("NETCDF_DIMENSION_SETTING_METHOD") = "index";
        out_orog("NETCDF_DIMENSION_SETTING") = dims.c_str();
        out_orog("_SKIP_MACRO") = 1;  // tell MacroConverter to ignore this request

        // create Graph Plotting subrequest
        // OBS: itt should use a definition from the Default folder
        MvRequest visdefReq("MGRAPH");
        visdefReq("GRAPH_TYPE") = "AREA";
        visdefReq("GRAPH_SHADE_COLOUR") = oroColour_.c_str();
        visdefReq("_CLASS") = "MGRAPH";
        out_orog = out_orog + visdefReq;

        // Create an empty request.
        // This is very important because it indicates to uPlot that this
        // sequence of requests must be processed independently. For example,
        // if in uPlot there is a visdef request after this sequence of requests
        // then this visdef will be processed separately, e.g. it will not be
        // added together with the visdef MGRAPH request
        MvRequest empty("EMPTY");

        out1 = out1 + out_orog + empty;
    }

    // Create View request using some parameters from the xsection data request
    MvRequest viewReq("MXSECTIONVIEW");
    if ((const char*)origReq_("LINE")) {
        viewReq.addValue("LINE", (double)origReq_("LINE", 0));
        viewReq.addValue("LINE", (double)origReq_("LINE", 1));
        viewReq.addValue("LINE", (double)origReq_("LINE", 2));
        viewReq.addValue("LINE", (double)origReq_("LINE", 3));
    }

    if ((const char*)origReq_("WIND_HORIZONTAl_MODE"))
        viewReq("WIND_HORIZONTAL_MODE") = origReq_("WIND_HORIZONTAL_MODE");
    if ((const char*)origReq_("WIND_PARALLEL"))
        viewReq("WIND_PARALLEL") = origReq_("WIND_PARALLEL");
    if ((const char*)origReq_("WIND_PERPENDICULAR"))
        viewReq("WIND_PERPENDICULAR") = origReq_("WIND_PERPENDICULAR");
    if ((const char*)origReq_("WIND_INTENSITY"))
        viewReq("WIND_INTENSITY") = origReq_("WIND_INTENSITY");

    viewReq("_CLASS") = "MXSECTIONVIEW";
    viewReq("_DEFAULT") = true;
    viewReq("_DATAATTACHED") = "YES";
    if (appInfo.levelType() != cML_UKMO_ND &&
        appInfo.levelType() != XS_ML_GHBC)  // UK MetOffice model levels and GHBC fields are 'upside-down'
        viewReq("_Y_AUTOMATIC_REVERSE") = "on";

    if (!appInfo.vLinearScaling())
        viewReq("VERTICAL_SCALING") = "log";

    // If action is not visualisation related then return the netcdf data.
    // Also, add the visualisation and original requests as hidden parameters.
    // These may be used later to help the visualisation of the netcdf data.
    if (!isVisualise(appInfo)) {
        xs("_VIEW") = view_.c_str();
        xs("_VISUALISE") = out1;
        xs("_VIEW_REQUEST") = viewReq;
        return xs;
    }

    // Final output request
    MvRequest out = viewReq + out1;
    return out;
}

std::string CrossS::titleVariable(ApplicationInfo&, ParamInfo* par)
{
    char titlestr[150];

    // Generate title
    if (windMode_ != NoWindMode) {
        if (outputMode_ == ScalarOutput && windProjection_ != NoWindProjection) {
            sprintf(titlestr, "Cross section of wind %s %d %02d step %d %s",
                    (windProjection_ == NormalWindProjection) ? "normal component" : "parallel speed",
                    par->Date(), par->Time(), par->Step(),
                    par->ExpVerTitle().c_str());
        }
        else if (outputMode_ == ScalarOutput && windMode_ == Wind2DMode) {
            sprintf(titlestr, "Cross section of UV speed  %d %02d step %d %s",
                    par->Date(), par->Time(), par->Step(),
                    par->ExpVerTitle().c_str());
        }
        else if (outputMode_ == VectorOutput) {
            if (windMode_ == Wind2DMode) {
                sprintf(titlestr, "Cross section of UV %d %02d step %d %s",
                        par->Date(), par->Time(), par->Step(),
                        par->ExpVerTitle().c_str());
            }
            else {
                sprintf(titlestr, "Cross section of UVW parallel %d %02d step %d %s",
                        par->Date(), par->Time(), par->Step(),
                        par->ExpVerTitle().c_str());
            }
        }
        else {
            sprintf(titlestr, "Cross section of %s %d %02d step %d %s",
                    par->ParamLongName().c_str(), par->Date(), par->Time(), par->Step(),
                    par->ExpVerTitle().c_str());
        }
    }
    else {
        sprintf(titlestr, "Cross section of %s %d %02d step %d %s",
                par->ParamLongName().c_str(), par->Date(), par->Time(), par->Step(),
                par->ExpVerTitle().c_str());
    }

    return {titlestr};
}

std::string CrossS::getNetcdfLevelVarname(std::string& varname)
{
    if (outputMode_ == VectorOutput)
        return {"lev"};

    return Xsect::getNetcdfLevelVarname(varname);
}

std::string CrossS::getNetcdfVarname(const std::string& varname)
{
    if (outputMode_ == VectorOutput)
        return getNetcdfXComponentVarname();

    return Xsect::getNetcdfVarname(varname);
}

ParamInfo* CrossS::plotVariable(ApplicationInfo& appInfo, ParamMap& params)
{
    if (plotVar_) {
        return plotVar_;
    }

    return Xsect::plotVariable(appInfo, params);
}

//------------------------------------------------------------------------------

// All these methods are created to allow backwards compatibility with Metvew 3.
// They should be removed when the backwards compatibility is no longer needed.

// Translate Metview 3 Cross Section Data to Metview 4 definition. Call Metview 4
// server to process the job.
void CrossSM3::serve(MvRequest& in, MvRequest& out)
{
    // Send a general warning message
    setError(0, "Icon Metview 3 CROSS SECTION DATA is obsolete. An automatic translation to the equivalent icon in Metview 4 will be performed, but may not work for all cases. It is recommended to replace it to the new definition manually.");

    // There are input parameters that are no longer available in Metview 4.
    // Remove them and send a warning message.
    setError(0, "Icon Metview 3 CROSS SECTION DATA is obsolete. Parameters PRESSURE_LEVEL_AXIS, INTERPOLATE_VALUES, BOTTOM_PRESSURE and TOP_PRESSURE will not be automatically translated.");
    MvRequest req = in;
    req.unsetParam("PRESSURE_LEVEL_AXIS");
    req.unsetParam("INTERPOLATE_VALUES");
    req.unsetParam("BOTTOM_PRESSURE");
    req.unsetParam("TOP_PRESSURE");

    // Keep the remaining parameters and update the VERB
    req.setVerb("MXSECTION");

    // Call the Xsection server to process the job
    Xsect::serve(req, out);
}
