// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2014, Oracle and/or its affiliates.

// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_TEST_DISTANCE_BRUTE_FORCE_HPP
#define BOOST_GEOMETRY_TEST_DISTANCE_BRUTE_FORCE_HPP

#include <iterator>

#include <boost/mpl/assert.hpp>
#include <boost/mpl/or.hpp>
#include <boost/range.hpp>

#include <boost/geometry/core/reverse_dispatch.hpp>
#include <boost/geometry/core/tag.hpp>
#include <boost/geometry/core/tag_cast.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/iterators/segment_iterator.hpp>

#include <boost/geometry/algorithms/distance.hpp>
#include <boost/geometry/algorithms/intersects.hpp>
#include <boost/geometry/algorithms/not_implemented.hpp>

namespace boost { namespace geometry
{

namespace unit_test
{

namespace detail { namespace distance_brute_force
{

struct distance_from_bg
{
    template <typename Geometry1, typename Geometry2, typename Strategy>
    static inline
    typename distance_result<Geometry1, Geometry2, Strategy>::type
    apply(Geometry1 const& geometry1,
          Geometry2 const& geometry2,
          Strategy const& strategy)
    {
        BOOST_MPL_ASSERT
            ((typename boost::mpl::or_
                  <
                      boost::is_same<typename tag<Geometry1>::type, point_tag>,
                      boost::is_same<typename tag<Geometry1>::type, segment_tag>
                  >::type));

        BOOST_MPL_ASSERT
            ((typename boost::mpl::or_
                  <
                      boost::is_same<typename tag<Geometry2>::type, point_tag>,
                      boost::is_same<typename tag<Geometry2>::type, segment_tag>
                  >::type));

        return geometry::distance(geometry1, geometry2, strategy);
    }
};

template <typename Geometry1, typename Geometry2, typename Strategy>
inline
typename distance_result<Geometry1, Geometry2, Strategy>::type
bg_distance(Geometry1 const& geometry1,
            Geometry2 const& geometry2,
            Strategy const& strategy)
{
    return distance_from_bg::apply(geometry1, geometry2, strategy);
}

template <typename Policy>
struct one_to_many
{
    template <typename Geometry, typename Iterator, typename Strategy>
    static inline typename distance_result
        <
            Geometry,
            typename std::iterator_traits<Iterator>::value_type,
            Strategy
        >::type
    apply(Geometry const& geometry, Iterator begin, Iterator end,
          Strategy const& strategy)
    {
        typedef typename distance_result
        <
            Geometry,
            typename std::iterator_traits<Iterator>::value_type,
            Strategy
        >::type distance_type;

        bool first = true;
        distance_type d_min(0);
        for (Iterator it = begin; it != end; ++it, first = false)
        {
            distance_type d = Policy::apply(geometry, *it, strategy);

            if ( first || d < d_min )
            {
                d_min = d;
            }
        }
        return d_min;
    }
};

}} // namespace detail::distance_brute_force

namespace dispatch
{

template
<
    typename Geometry1,
    typename Geometry2,
    typename Strategy,
    typename Tag1 = typename tag_cast
        <
            typename tag<Geometry1>::type,
            segment_tag,
            linear_tag
        >::type,
    typename Tag2 = typename tag_cast
        <
            typename tag<Geometry2>::type,
            segment_tag,
            linear_tag
        >::type,
    bool Reverse = reverse_dispatch<Geometry1, Geometry2>::type::value
>
struct distance_brute_force
    : not_implemented<Geometry1, Geometry2>
{};

template
<
    typename Geometry1,
    typename Geometry2,
    typename Strategy,
    typename Tag1,
    typename Tag2
>
struct distance_brute_force<Geometry1, Geometry2, Strategy, Tag1, Tag2, true>
{
    static inline typename distance_result<Geometry1, Geometry2, Strategy>::type
    apply(Geometry1 const& geometry1,
          Geometry2 const& geometry2,
          Strategy const& strategy)
    {
        return distance_brute_force
            <
                Geometry2, Geometry1, Strategy
            >::apply(geometry2, geometry1, strategy);
    }
};

template
<
    typename Point1,
    typename Point2,
    typename Strategy
>
struct distance_brute_force
<
    Point1, Point2, Strategy,
    point_tag, point_tag, false
> : detail::distance_brute_force::distance_from_bg
{};

template
<
    typename Point,
    typename Segment,
    typename Strategy
>
struct distance_brute_force
<
    Point, Segment, Strategy,
    point_tag, segment_tag, false
> : detail::distance_brute_force::distance_from_bg
{};

template
<
    typename Segment1,
    typename Segment2,
    typename Strategy
>
struct distance_brute_force
<
    Segment1, Segment2, Strategy,
    segment_tag, segment_tag, false
> : detail::distance_brute_force::distance_from_bg
{};

template
<
    typename Point,
    typename Linear,
    typename Strategy
>
struct distance_brute_force
<
    Point, Linear, Strategy,
    point_tag, linear_tag, false
>
{
    typedef typename distance_result
        <
            Point, Linear, Strategy
        >::type distance_type;

    static inline distance_type apply(Point const& point,
                                      Linear const& linear,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                detail::distance_brute_force::distance_from_bg
            >::apply(point,
                     geometry::segments_begin(linear),
                     geometry::segments_end(linear),
                     strategy);
    }
};

template
<
    typename Point,
    typename MultiPoint,
    typename Strategy
>
struct distance_brute_force
<
    Point, MultiPoint, Strategy,
    point_tag, multi_point_tag, false
>
{
    typedef typename distance_result
        <
            Point, MultiPoint, Strategy
        >::type distance_type;

    static inline distance_type apply(Point const& p,
                                      MultiPoint const& mp,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                detail::distance_brute_force::distance_from_bg
            >::apply(p, boost::begin(mp), boost::end(mp), strategy);
    }
};

template
<
    typename MultiPoint1,
    typename MultiPoint2,
    typename Strategy
>
struct distance_brute_force
<
    MultiPoint1, MultiPoint2, Strategy,
    multi_point_tag, multi_point_tag, false
>
{
    typedef typename distance_result
        <
            MultiPoint1, MultiPoint2, Strategy
        >::type distance_type;

    static inline distance_type apply(MultiPoint1 const& mp1,
                                      MultiPoint2 const& mp2,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                distance_brute_force
                <
                    MultiPoint1,
                    typename boost::range_value<MultiPoint2>::type,
                    Strategy
                >
            >::apply(mp1, boost::begin(mp2), boost::end(mp2), strategy);
    }
};

template
<
    typename MultiPoint,
    typename Linear,
    typename Strategy
>
struct distance_brute_force
<
    MultiPoint, Linear, Strategy,
    multi_point_tag, linear_tag, false
>
{
    typedef typename distance_result
        <
            MultiPoint, Linear, Strategy
        >::type distance_type;

    static inline distance_type apply(MultiPoint const& mp,
                                      Linear const& linear,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                distance_brute_force
                <
                    Linear,
                    typename boost::range_value<MultiPoint>::type,
                    Strategy
                >
            >::apply(linear, boost::begin(mp), boost::end(mp), strategy);
    }
};

template
<
    typename Linear,
    typename MultiPoint,
    typename Strategy
>
struct distance_brute_force
<
    Linear, MultiPoint, Strategy,
    linear_tag, multi_point_tag, false
>
{
    typedef typename distance_result
        <
            Linear, MultiPoint, Strategy
        >::type distance_type;

    static inline distance_type apply(Linear const& linear,
                                      MultiPoint const& multipoint,
                                      Strategy const& strategy)
    {
        return distance_brute_force
            <
                MultiPoint, Linear, Strategy,
                multi_point_tag, linear_tag, false
            >::apply(multipoint, linear, strategy);
    }
};

template
<
    typename MultiPoint,
    typename Segment,
    typename Strategy
>
struct distance_brute_force
<
    MultiPoint, Segment, Strategy,
    multi_point_tag, segment_tag, false
>
{
    typedef typename distance_result
        <
            MultiPoint, Segment, Strategy
        >::type distance_type;

    static inline distance_type apply(MultiPoint const& mp,
                                      Segment const& segment,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                detail::distance_brute_force::distance_from_bg
            >::apply(segment, boost::begin(mp), boost::end(mp), strategy);
    }
};

template
<
    typename Linear,
    typename Segment,
    typename Strategy
>
struct distance_brute_force
<
    Linear, Segment, Strategy,
    linear_tag, segment_tag, false
>
{
    typedef typename distance_result
        <
            Linear, Segment, Strategy
        >::type distance_type;

    static inline distance_type apply(Linear const& linear,
                                      Segment const& segment,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                detail::distance_brute_force::distance_from_bg
            >::apply(segment,
                     geometry::segments_begin(linear),
                     geometry::segments_end(linear),
                     strategy);
    }
};

template
<
    typename Linear1,
    typename Linear2,
    typename Strategy
>
struct distance_brute_force
<
    Linear1, Linear2, Strategy,
    linear_tag, linear_tag, false
>
{
    typedef typename distance_result
        <
            Linear1, Linear2, Strategy
        >::type distance_type;

    static inline distance_type apply(Linear1 const& linear1,
                                      Linear2 const& linear2,
                                      Strategy const& strategy)
    {
        return detail::distance_brute_force::one_to_many
            <
                distance_brute_force
                    <
                        Linear1,
                        typename std::iterator_traits
                            <
                                segment_iterator<Linear2 const>
                            >::value_type,
                        Strategy
                    >
            >::apply(linear1,
                     geometry::segments_begin(linear2),
                     geometry::segments_end(linear2),
                     strategy);
    }
};

} // namespace dispatch

template <typename Geometry1, typename Geometry2, typename Strategy>
inline typename distance_result<Geometry1, Geometry2, Strategy>::type
distance_brute_force(Geometry1 const& geometry1,
                     Geometry2 const& geometry2,
                     Strategy const& strategy)
{
    return dispatch::distance_brute_force
        <
            Geometry1, Geometry2, Strategy
        >::apply(geometry1, geometry2, strategy);
}

} // namespace unit_test

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_TEST_DISTANCE_BRUTE_FORCE_HPP


/*

Copyright (c) 2013, Project OSRM, Dennis Luxen, others
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list
of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#include <osrm/Coordinate.h>
#include "../Util/MercatorUtil.h"
#include "../Util/SimpleLogger.h"
#include "../Util/StringUtil.h"
#include "../Util/TrigonometryTables.h"

#include <boost/assert.hpp>

#ifndef NDEBUG
#include <bitset>
#endif
#include <iostream>
#include <limits>

FixedPointCoordinate::FixedPointCoordinate()
    : lat(std::numeric_limits<int>::min()), lon(std::numeric_limits<int>::min())
{
}

FixedPointCoordinate::FixedPointCoordinate(int lat, int lon) : lat(lat), lon(lon)
{
#ifndef NDEBUG
    if (0 != (std::abs(lat) >> 30))
    {
        std::bitset<32> y(lat);
        SimpleLogger().Write(logDEBUG) << "broken lat: " << lat << ", bits: " << y;
    }
    if (0 != (std::abs(lon) >> 30))
    {
        std::bitset<32> x(lon);
        SimpleLogger().Write(logDEBUG) << "broken lon: " << lon << ", bits: " << x;
    }
#endif
}

void FixedPointCoordinate::Reset()
{
    lat = std::numeric_limits<int>::min();
    lon = std::numeric_limits<int>::min();
}
bool FixedPointCoordinate::isSet() const
{
    return (std::numeric_limits<int>::min() != lat) && (std::numeric_limits<int>::min() != lon);
}
bool FixedPointCoordinate::isValid() const
{
    if (lat > 90 * COORDINATE_PRECISION || lat < -90 * COORDINATE_PRECISION ||
        lon > 180 * COORDINATE_PRECISION || lon < -180 * COORDINATE_PRECISION)
    {
        return false;
    }
    return true;
}
bool FixedPointCoordinate::operator==(const FixedPointCoordinate &other) const
{
    return lat == other.lat && lon == other.lon;
}

double FixedPointCoordinate::ApproximateDistance(const int lat1,
                                                 const int lon1,
                                                 const int lat2,
                                                 const int lon2)
{
    BOOST_ASSERT(lat1 != std::numeric_limits<int>::min());
    BOOST_ASSERT(lon1 != std::numeric_limits<int>::min());
    BOOST_ASSERT(lat2 != std::numeric_limits<int>::min());
    BOOST_ASSERT(lon2 != std::numeric_limits<int>::min());
    double RAD = 0.017453292519943295769236907684886;
    double lt1 = lat1 / COORDINATE_PRECISION;
    double ln1 = lon1 / COORDINATE_PRECISION;
    double lt2 = lat2 / COORDINATE_PRECISION;
    double ln2 = lon2 / COORDINATE_PRECISION;
    double dlat1 = lt1 * (RAD);

    double dlong1 = ln1 * (RAD);
    double dlat2 = lt2 * (RAD);
    double dlong2 = ln2 * (RAD);

    double dLong = dlong1 - dlong2;
    double dLat = dlat1 - dlat2;

    double aHarv = pow(sin(dLat / 2.0), 2.0) + cos(dlat1) * cos(dlat2) * pow(sin(dLong / 2.), 2);
    double cHarv = 2. * atan2(sqrt(aHarv), sqrt(1.0 - aHarv));
    // earth radius varies between 6,356.750-6,378.135 km (3,949.901-3,963.189mi)
    // The IUGG value for the equatorial radius is 6378.137 km (3963.19 miles)
    const double earth = 6372797.560856;
    return earth * cHarv;
}

double FixedPointCoordinate::ApproximateDistance(const FixedPointCoordinate &c1,
                                                 const FixedPointCoordinate &c2)
{
    return ApproximateDistance(c1.lat, c1.lon, c2.lat, c2.lon);
}

float FixedPointCoordinate::ApproximateEuclideanDistance(const FixedPointCoordinate &c1,
                                                 const FixedPointCoordinate &c2)
{
    return ApproximateEuclideanDistance(c1.lat, c1.lon, c2.lat, c2.lon);
}

float FixedPointCoordinate::ApproximateEuclideanDistance(const int lat1,
                                                          const int lon1,
                                                          const int lat2,
                                                          const int lon2)
{
    BOOST_ASSERT(lat1 != std::numeric_limits<int>::min());
    BOOST_ASSERT(lon1 != std::numeric_limits<int>::min());
    BOOST_ASSERT(lat2 != std::numeric_limits<int>::min());
    BOOST_ASSERT(lon2 != std::numeric_limits<int>::min());

    const float RAD = 0.017453292519943295769236907684886;
    const float float_lat1 = (lat1 / COORDINATE_PRECISION) * RAD;
    const float float_lon1 = (lon1 / COORDINATE_PRECISION) * RAD;
    const float float_lat2 = (lat2 / COORDINATE_PRECISION) * RAD;
    const float float_lon2 = (lon2 / COORDINATE_PRECISION) * RAD;

    const float x = (float_lon2 - float_lon1) * cos((float_lat1 + float_lat2) / 2.);
    const float y = (float_lat2 - float_lat1);
    const float earth_radius = 6372797.560856;
    return sqrt(x * x + y * y) * earth_radius;
}

float FixedPointCoordinate::ComputePerpendicularDistance(const FixedPointCoordinate &point,
                                                          const FixedPointCoordinate &segA,
                                                          const FixedPointCoordinate &segB)
{
    const float x = lat2y(point.lat / COORDINATE_PRECISION);
    const float y = point.lon / COORDINATE_PRECISION;
    const float a = lat2y(segA.lat / COORDINATE_PRECISION);
    const float b = segA.lon / COORDINATE_PRECISION;
    const float c = lat2y(segB.lat / COORDINATE_PRECISION);
    const float d = segB.lon / COORDINATE_PRECISION;
    float p, q, nY;
    if (std::abs(a - c) > std::numeric_limits<float>::epsilon())
    {
        const float m = (d - b) / (c - a); // slope
        // Projection of (x,y) on line joining (a,b) and (c,d)
        p = ((x + (m * y)) + (m * m * a - m * b)) / (1. + m * m);
        q = b + m * (p - a);
    }
    else
    {
        p = c;
        q = y;
    }
    nY = (d * p - c * q) / (a * d - b * c);

    // discretize the result to coordinate precision. it's a hack!
    if (std::abs(nY) < (1. / COORDINATE_PRECISION))
    {
        nY = 0.;
    }

    float r = (p - nY * a) / c;
    if (std::isnan(r))
    {
        r = ((segB.lat == point.lat) && (segB.lon == point.lon)) ? 1. : 0.;
    }
    else if (std::abs(r) <= std::numeric_limits<float>::epsilon())
    {
        r = 0.;
    }
    else if (std::abs(r - 1.) <= std::numeric_limits<float>::epsilon())
    {
        r = 1.;
    }
    FixedPointCoordinate nearest_location;
    BOOST_ASSERT(!std::isnan(r));
    if (r <= 0.)
    { // point is "left" of edge
        nearest_location.lat = segA.lat;
        nearest_location.lon = segA.lon;
    }
    else if (r >= 1.)
    { // point is "right" of edge
        nearest_location.lat = segB.lat;
        nearest_location.lon = segB.lon;
    }
    else
    { // point lies in between
        nearest_location.lat = y2lat(p) * COORDINATE_PRECISION;
        nearest_location.lon = q * COORDINATE_PRECISION;
    }
    BOOST_ASSERT(nearest_location.isValid());
    const float approximated_distance =
        FixedPointCoordinate::ApproximateEuclideanDistance(point, nearest_location);
    BOOST_ASSERT(0. <= approximated_distance);
    return approximated_distance;
}

float FixedPointCoordinate::ComputePerpendicularDistance(const FixedPointCoordinate &coord_a,
                                                   const FixedPointCoordinate &coord_b,
                                                   const FixedPointCoordinate &query_location,
                                                   FixedPointCoordinate &nearest_location,
                                                   float &r)
{
    BOOST_ASSERT(query_location.isValid());

    const float x = lat2y(query_location.lat / COORDINATE_PRECISION);
    const float y = query_location.lon / COORDINATE_PRECISION;
    const float a = lat2y(coord_a.lat / COORDINATE_PRECISION);
    const float b = coord_a.lon / COORDINATE_PRECISION;
    const float c = lat2y(coord_b.lat / COORDINATE_PRECISION);
    const float d = coord_b.lon / COORDINATE_PRECISION;
    float p, q /*,mX*/, nY;
    if (std::abs(a - c) > std::numeric_limits<float>::epsilon())
    {
        const float m = (d - b) / (c - a); // slope
        // Projection of (x,y) on line joining (a,b) and (c,d)
        p = ((x + (m * y)) + (m * m * a - m * b)) / (1. + m * m);
        q = b + m * (p - a);
    }
    else
    {
        p = c;
        q = y;
    }
    nY = (d * p - c * q) / (a * d - b * c);

    // discretize the result to coordinate precision. it's a hack!
    if (std::abs(nY) < (1. / COORDINATE_PRECISION))
    {
        nY = 0.;
    }

    r = (p - nY * a) / c; // These values are actually n/m+n and m/m+n , we need
    // not calculate the explicit values of m an n as we
    // are just interested in the ratio
    if (std::isnan(r))
    {
        r = ((coord_b.lat == query_location.lat) && (coord_b.lon == query_location.lon)) ? 1. : 0.;
    }
    else if (std::abs(r) <= std::numeric_limits<float>::epsilon())
    {
        r = 0.;
    }
    else if (std::abs(r - 1.) <= std::numeric_limits<float>::epsilon())
    {
        r = 1.;
    }
    BOOST_ASSERT(!std::isnan(r));
    if (r <= 0.)
    {
        nearest_location = coord_a;
    }
    else if (r >= 1.)
    {
        nearest_location = coord_b;
    }
    else
    {
        // point lies in between
        nearest_location.lat = y2lat(p) * COORDINATE_PRECISION;
        nearest_location.lon = q * COORDINATE_PRECISION;
    }
    BOOST_ASSERT(nearest_location.isValid());

    // TODO: Replace with euclidean approximation when k-NN search is done
    // const float approximated_distance = FixedPointCoordinate::ApproximateEuclideanDistance(
    const float approximated_distance =
        FixedPointCoordinate::ApproximateEuclideanDistance(query_location, nearest_location);
    BOOST_ASSERT(0. <= approximated_distance);
    return approximated_distance;
}

void FixedPointCoordinate::convertInternalLatLonToString(const int value, std::string &output)
{
    char buffer[12];
    buffer[11] = 0; // zero termination
    output = printInt<11, 6>(buffer, value);
}

void FixedPointCoordinate::convertInternalCoordinateToString(const FixedPointCoordinate &coord,
                                                             std::string &output)
{
    std::string tmp;
    tmp.reserve(23);
    convertInternalLatLonToString(coord.lon, tmp);
    output = tmp;
    output += ",";
    convertInternalLatLonToString(coord.lat, tmp);
    output += tmp;
}

void
FixedPointCoordinate::convertInternalReversedCoordinateToString(const FixedPointCoordinate &coord,
                                                                std::string &output)
{
    std::string tmp;
    tmp.reserve(23);
    convertInternalLatLonToString(coord.lat, tmp);
    output = tmp;
    output += ",";
    convertInternalLatLonToString(coord.lon, tmp);
    output += tmp;
}

void FixedPointCoordinate::Output(std::ostream &out) const
{
    out << "(" << lat / COORDINATE_PRECISION << "," << lon / COORDINATE_PRECISION << ")";
}

float FixedPointCoordinate::GetBearing(const FixedPointCoordinate &A, const FixedPointCoordinate &B)
{
    const float delta_long = DegreeToRadian(B.lon / COORDINATE_PRECISION - A.lon / COORDINATE_PRECISION);
    const float lat1 = DegreeToRadian(A.lat / COORDINATE_PRECISION);
    const float lat2 = DegreeToRadian(B.lat / COORDINATE_PRECISION);
    const float y = sin(delta_long) * cos(lat2);
    const float x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(delta_long);
    float result = RadianToDegree(std::atan2(y, x));
    while (result < 0.f)
    {
        result += 360.f;
    }

    while (result >= 360.f)
    {
        result -= 360.f;
    }
    return result;
}

float FixedPointCoordinate::GetBearing(const FixedPointCoordinate &other) const
{
    const float delta_long = DegreeToRadian(lon / COORDINATE_PRECISION - other.lon / COORDINATE_PRECISION);
    const float lat1 = DegreeToRadian(other.lat / COORDINATE_PRECISION);
    const float lat2 = DegreeToRadian(lat / COORDINATE_PRECISION);
    const float y = std::sin(delta_long) * std::cos(lat2);
    const float x = std::cos(lat1) * std::sin(lat2) - std::sin(lat1) * std::cos(lat2) * std::cos(delta_long);
    float result = RadianToDegree(std::atan2(y, x));

    while (result < 0.f)
    {
        result += 360.f;
    }

    while (result >= 360.f)
    {
        result -= 360.f;
    }
    return result;
}

float FixedPointCoordinate::DegreeToRadian(const float degree)
{
    return degree * (M_PI / 180.f);
}

float FixedPointCoordinate::RadianToDegree(const float radian)
{
    return radian * (180.f / M_PI);
}


/*!
 * @file
 * Defines the @ref Orderable typeclass.
 *
 *
 * @copyright Louis Dionne 2014
 * Distributed under the Boost Software License, Version 1.0.
 *         (See accompanying file LICENSE.md or copy at
 *             http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_MPL11_ORDERABLE_HPP
#define BOOST_MPL11_ORDERABLE_HPP

#include <boost/mpl11/fwd/orderable.hpp>

#include <boost/mpl11/fwd/bool.hpp>
#include <boost/mpl11/fwd/core.hpp>
#include <boost/mpl11/fwd/logical.hpp>

namespace boost { namespace mpl11 {
    template <>
    struct instantiate<Orderable> {
        template <typename Left, typename Right = Left>
        struct with : true_ {
            template <typename x, typename y>
            using less_equal_impl = not_<less<box<y>, box<x>>>;

            template <typename x, typename y>
            using greater_impl = less<box<y>, box<x>>;

            template <typename x, typename y>
            using greater_equal_impl = not_<less<box<x>, box<y>>>;

            template <typename x, typename y>
            using min_impl = if_c<less<box<x>, box<y>>::value, box<x>, box<y>>;

            template <typename x, typename y>
            using max_impl = if_c<less<box<x>, box<y>>::value, box<y>, box<x>>;
        };
    };
}} // end namespace boost::mpl11

#include <boost/mpl11/bool.hpp>
#include <boost/mpl11/core.hpp>
#include <boost/mpl11/detail/left_folds/variadic.hpp>
#include <boost/mpl11/logical.hpp>

namespace boost { namespace mpl11 {
    template <typename Left, typename Right, typename>
    struct Orderable : false_ { };

    template <typename x1, typename x2, typename ...xs>
    struct less
        : and_<less<x1, x2>, less<x2, xs...>>
    { };

    template <typename x, typename y>
    struct less<x, y> :
        Orderable<
            typename datatype<typename x::type>::type,
            typename datatype<typename y::type>::type
        >::template less_impl<typename x::type, typename y::type>
    { };

    template <typename x1, typename x2, typename ...xs>
    struct less_equal
        : and_<less_equal<x1, x2>, less_equal<x2, xs...>>
    { };

    template <typename x, typename y>
    struct less_equal<x, y> :
        Orderable<
            typename datatype<typename x::type>::type,
            typename datatype<typename y::type>::type
        >::template less_equal_impl<typename x::type, typename y::type>
    { };

    template <typename x1, typename x2, typename ...xs>
    struct greater
        : and_<greater<x1, x2>, greater<x2, xs...>>
    { };

    template <typename x, typename y>
    struct greater<x, y> :
        Orderable<
            typename datatype<typename x::type>::type,
            typename datatype<typename y::type>::type
        >::template greater_impl<typename x::type, typename y::type>
    { };

    template <typename x1, typename x2, typename ...xs>
    struct greater_equal
        : and_<greater_equal<x1, x2>, greater_equal<x2, xs...>>
    { };

    template <typename x, typename y>
    struct greater_equal<x, y> :
        Orderable<
            typename datatype<typename x::type>::type,
            typename datatype<typename y::type>::type
        >::template greater_equal_impl<typename x::type, typename y::type>
    { };

    template <typename x1, typename x2, typename ...xs>
    struct min
        : detail::left_folds::variadic<min, x1, x2, xs...>
    { };

    template <typename x, typename y>
    struct min<x, y> :
        Orderable<
            typename datatype<typename x::type>::type,
            typename datatype<typename y::type>::type
        >::template min_impl<typename x::type, typename y::type>
    { };

    template <typename x1, typename x2, typename ...xs>
    struct max
        : detail::left_folds::variadic<max, x1, x2, xs...>
    { };

    template <typename x, typename y>
    struct max<x, y> :
        Orderable<
            typename datatype<typename x::type>::type,
            typename datatype<typename y::type>::type
        >::template max_impl<typename x::type, typename y::type>
    { };
}} // end namespace boost::mpl11

#endif // !BOOST_MPL11_ORDERABLE_HPP


//==================================================================================================
/*!
  @file

  @copyright 2016 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_X86_SSE4_1_SIMD_FUNCTION_EXTRACT_HPP_INCLUDED
#define BOOST_SIMD_ARCH_X86_SSE4_1_SIMD_FUNCTION_EXTRACT_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>
#include <boost/dispatch/adapted/std/integral_constant.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = ::boost::dispatch;
  namespace bs = ::boost::simd;

  //------------------------------------------------------------------------------------------------
  BOOST_DISPATCH_OVERLOAD ( extract_
                          , (typename A0, typename A1)
                          , bs::sse4_1_
                          , bs::pack_<bd::ints8_<A0>, bs::sse_>
                          , bd::constant_<bd::integer_<A1>>
                          )
  {
    BOOST_FORCEINLINE bd::scalar_of_t<A0> operator()(A0 const & a0, A1 const &) const
    {
      enum { value = A1::value };
      return _mm_extract_epi8(a0, value);
    }
  };

  //------------------------------------------------------------------------------------------------
  BOOST_DISPATCH_OVERLOAD ( extract_
                          , (typename A0, typename A1)
                          , bs::sse4_1_
                          , bs::pack_<bd::ints16_<A0>, bs::sse_>
                          , bd::constant_<bd::integer_<A1>>
                          )
  {
    BOOST_FORCEINLINE bd::scalar_of_t<A0> operator()(A0 const & a0, A1 const &) const
    {
      enum { value = A1::value };
      return _mm_extract_epi16(a0, value);
    }
  };

  //------------------------------------------------------------------------------------------------
  BOOST_DISPATCH_OVERLOAD ( extract_
                          , (typename A0, typename A1)
                          , bs::sse4_1_
                          , bs::pack_<bd::ints32_<A0>, bs::sse_>
                          , bd::constant_<bd::integer_<A1>>
                          )
  {
    BOOST_FORCEINLINE bd::scalar_of_t<A0> operator()(A0 const & a0, A1 const &) const
    {
      enum { value = A1::value };
      return _mm_extract_epi32(a0, value);
    }
  };

  //------------------------------------------------------------------------------------------------
  BOOST_DISPATCH_OVERLOAD ( extract_
                          , (typename A0, typename A1)
                          , bs::sse4_1_
                          , bs::pack_<bd::ints64_<A0>, bs::sse_>
                          , bd::constant_<bd::integer_<A1>>
                          )
  {
    BOOST_FORCEINLINE bd::scalar_of_t<A0> operator()(A0 const & a0, A1 const &) const
    {
      enum { value = A1::value };
      return _mm_extract_epi64(a0, value);
    }
  };

  //------------------------------------------------------------------------------------------------
  BOOST_DISPATCH_OVERLOAD ( extract_
                          , (typename A0, typename A1)
                          , bs::sse4_1_
                          , bs::pack_<bd::single_<A0>, bs::sse_>
                          , bd::constant_<bd::integer_<A1>>
                          )
  {
    BOOST_FORCEINLINE bd::scalar_of_t<A0> operator()(A0 const & a0, A1 const &) const
    {
      float f;
      enum { value = A1::value };
      _MM_EXTRACT_FLOAT(f, a0, value);
      return f;
    }
  };

} } }

#endif


#include <boost/heap/binomial_heap.hpp>
#include <iostream>

using namespace boost::heap;

int main() {
  binomial_heap<int> bh;
  bh.push(2);
  bh.push(3);
  bh.push(1);

  binomial_heap<int> bh2;
  bh2.push(4);
  bh.merge(bh2);

  for (auto it = bh.ordered_begin(); it != bh.ordered_end(); ++it)
    std::cout << *it << " ";
  std::cout << std::endl;
  std::cout << std::boolalpha << bh2.empty() << std::endl;
}


#include <boost/ratio/mpl/gcd.hpp>


/**
 * @file hitool.cpp
 *
 */

#include "hitool.h"
#include "logger.h"
#include "plugin_factory.h"
#include "util.h"
#include <algorithm>
#include <boost/format.hpp>

#include "fetcher.h"
#include "neons.h"
#include "radon.h"

using namespace std;
using namespace himan;
using namespace himan::plugin;

double min(const vector<double>& vec)
{
	double ret = 1e38;

	for (double val : vec)
	{
		if (val != kFloatMissing && val < ret) ret = val;
	}

	if (ret == 1e38) ret = kFloatMissing;

	return ret;
}

double max(const vector<double>& vec)
{
	double ret = -1e38;

	for (double val : vec)
	{
		if (val != kFloatMissing && val > ret) ret = val;
	}

	if (ret == -1e38) ret = kFloatMissing;

	return ret;
}

pair<double, double> minmax(const vector<double>& vec)
{
	double min = 1e38, max = -1e38;

	for (double val : vec)
	{
		if (val != kFloatMissing)
		{
			if (val < min) min = val;
			if (val > max) max = val;
		}
	}

	if (min == 1e38)
	{
		min = kFloatMissing;
		max = kFloatMissing;
	}

	return make_pair(min, max);
}

hitool::hitool() : itsTime(), itsForecastType(kDeterministic), itsHeightUnit(kM)
{
	itsLogger = logger("hitool");
}

hitool::hitool(shared_ptr<plugin_configuration> conf) : itsTime(), itsForecastType(kDeterministic), itsHeightUnit(kM)
{
	itsLogger = logger("hitool");
	itsConfiguration = conf;
}

shared_ptr<modifier> hitool::CreateModifier(HPModifierType modifierType) const
{
	shared_ptr<himan::modifier> mod;

	switch (modifierType)
	{
		case kMaximumModifier:
			mod = make_shared<modifier_max>();
			break;

		case kMinimumModifier:
			mod = make_shared<modifier_min>();
			break;

		case kMaximumMinimumModifier:
			mod = make_shared<modifier_maxmin>();
			break;

		case kFindHeightModifier:
			mod = make_shared<modifier_findheight>();
			break;

		case kFindValueModifier:
			mod = make_shared<modifier_findvalue>();
			break;

		case kAverageModifier:
			mod = make_shared<modifier_mean>();
			break;

		case kCountModifier:
			mod = make_shared<modifier_count>();
			break;

		case kAccumulationModifier:
			mod = make_shared<modifier_sum>();
			break;

		case kPlusMinusAreaModifier:
			mod = make_shared<modifier_plusminusarea>();
			break;

		case kFindHeightGreaterThanModifier:
			mod = make_shared<modifier_findheight_gt>();
			break;

		case kFindHeightLessThanModifier:
			mod = make_shared<modifier_findheight_lt>();
			break;

		default:
			itsLogger.Fatal("Unknown modifier type: " + boost::lexical_cast<string>(modifierType));
			abort();
			break;
	}
	itsLogger.Trace("Creating " + string(HPModifierTypeToString.at(mod->Type())));
	return mod;
}

pair<level, level> hitool::LevelForHeight(const producer& prod, double height) const
{
	assert(itsConfiguration);

	using boost::lexical_cast;

	long producerId = 0;

	// Hybrid level heights are calculated by himan, so coalesce the related
	// forecast producer id with the himan producer id.

	switch (prod.Id())
	{
		case 1:
		case 230:
			producerId = 230;
			break;

		case 131:
		case 240:
			producerId = 240;
			break;

		case 134:
		case 243:
			producerId = 243;
			break;

		case 199:
		case 210:
			producerId = 210;
			break;

		case 4:
		case 260:
			producerId = 260;
			break;

		default:
			itsLogger.Error("Unsupported producer for hitool::LevelForHeight(): " + lexical_cast<string>(prod.Id()));
			break;
	}

	stringstream query;

	if (itsHeightUnit == kM)
	{
		query << "SELECT min(CASE WHEN maximum_height <= " << height
		      << " THEN level_value ELSE NULL END) AS lowest_level, "
		      << "max(CASE WHEN minimum_height >= " << height << " THEN level_value ELSE NULL END) AS highest_level "
		      << "FROM "
		      << "hybrid_level_height "
		      << "WHERE "
		      << "producer_id = " << producerId;
	}
	else if (itsHeightUnit == kHPa)
	{
		// Add/subtract 1 already in the query, since due to the composition of the query it will return
		// the first level that is higher than lower height and vice versa

		query << "SELECT max(CASE WHEN minimum_pressure <= " << height
		      << " THEN level_value+1 ELSE NULL END) AS lowest_level, "
		      << "min(CASE WHEN maximum_pressure >= " << height
		      << " THEN level_value-1 ELSE NULL END) AS highest_level "
		      << "FROM "
		      << "hybrid_level_height "
		      << "WHERE "
		      << "producer_id = " << producerId;
		;
	}

	HPDatabaseType dbtype = itsConfiguration->DatabaseType();

	vector<string> row;

	long absolutelowest = kHPMissingInt, absolutehighest = kHPMissingInt;

	if (dbtype == kNeons || dbtype == kNeonsAndRadon)
	{
		auto n = GET_PLUGIN(neons);
		n->NeonsDB().Query(query.str());

		row = n->NeonsDB().FetchRow();

		absolutelowest = lexical_cast<long>(n->ProducerMetaData(prod.Id(), "last hybrid level number"));
		absolutehighest = lexical_cast<long>(n->ProducerMetaData(prod.Id(), "first hybrid level number"));
	}

	if (row.empty() && (dbtype == kRadon || dbtype == kNeonsAndRadon))
	{
		auto r = GET_PLUGIN(radon);
		r->RadonDB().Query(query.str());

		row = r->RadonDB().FetchRow();

		absolutelowest = lexical_cast<long>(r->RadonDB().GetProducerMetaData(prod.Id(), "last hybrid level number"));
		absolutehighest = lexical_cast<long>(r->RadonDB().GetProducerMetaData(prod.Id(), "first hybrid level number"));
	}

	long newlowest = absolutelowest, newhighest = absolutehighest;

	if (!row.empty())
	{
		// If requested height is below lowest level (f.ex. 0 meters) or above highest (f.ex. 80km)
		// database query will return null

		if (row[0] != "")
		{
			// SQL query returns the level value that precedes the requested value.
			// For first hybrid level (the highest ie max), get one level above the max level if possible
			// For last hybrid level (the lowest ie min), get one level below the min level if possible
			// This means that we have a buffer of three levels for both directions!

			newlowest = lexical_cast<long>(row[0]) + 1;

			if (newlowest > absolutelowest)
			{
				newlowest = absolutelowest;
			}
		}

		if (row[1] != "")
		{
			newhighest = lexical_cast<long>(row[1]) - 1;

			if (newhighest < absolutehighest)
			{
				newhighest = absolutehighest;
			}
		}

		if (newhighest > newlowest)
		{
			newhighest = newlowest;
		}
	}

	assert(newlowest >= newhighest);

	return make_pair<level, level>(level(kHybrid, newlowest), level(kHybrid, newhighest));
}

vector<double> hitool::VerticalExtremeValue(shared_ptr<modifier> mod, HPLevelType wantedLevelType,
                                            const param& wantedParam, const vector<double>& lowerHeight,
                                            const vector<double>& upperHeight, const vector<double>& findValue) const
{
	assert(wantedLevelType == kHybrid);

	if (findValue.size())
	{
		mod->FindValue(findValue);
	}

	if (lowerHeight.size())
	{
		mod->LowerHeight(lowerHeight);
	}

	if (upperHeight.size())
	{
		mod->UpperHeight(upperHeight);
	}

	if (itsHeightUnit == kHPa)
	{
		mod->HeightInMeters(false);
	}

	// Should we loop over all producers ?

	producer prod = itsConfiguration->SourceProducer(0);

	// first means first in sorted order, ie smallest number ie the highest level

	HPDatabaseType dbtype = itsConfiguration->DatabaseType();

	long highestHybridLevel = kHPMissingInt, lowestHybridLevel = kHPMissingInt;

	if (dbtype == kNeons || dbtype == kNeonsAndRadon)
	{
		auto n = GET_PLUGIN(neons);

		highestHybridLevel = boost::lexical_cast<long>(n->ProducerMetaData(prod.Id(), "first hybrid level number"));
		lowestHybridLevel = boost::lexical_cast<long>(n->ProducerMetaData(prod.Id(), "last hybrid level number"));
	}

	if (highestHybridLevel == kHPMissingInt && (dbtype == kRadon || dbtype == kNeonsAndRadon))
	{
		auto r = GET_PLUGIN(radon);

		try
		{
			highestHybridLevel =
			    boost::lexical_cast<long>(r->RadonDB().GetProducerMetaData(prod.Id(), "first hybrid level number"));
			lowestHybridLevel =
			    boost::lexical_cast<long>(r->RadonDB().GetProducerMetaData(prod.Id(), "last hybrid level number"));
		}
		catch (const boost::bad_lexical_cast& e)
		{
			itsLogger.Error("Unable to get hybrid level information from database");
			throw;
		}
	}

	// Karkeaa haarukointia

	string heightUnit = (itsHeightUnit == kM) ? "meters" : "hectopascal";

	switch (mod->Type())
	{
		/*
		 * -- FIRST HYBRID LEVEL --
		 *
		 * -- pick highest max above MAX HEIGHT --
		 *
		 * -- MAX HEIGHT in data--
		 *
		 *
		 * -- MIN HEIGHT in data --
		 *
		 * -- pick lowest min below MIN HEIGHT --
		 *
		 * -- LAST HYBRID LEVEL --
		 */

		case kAverageModifier:
		case kMinimumModifier:
		case kMaximumModifier:
		case kCountModifier:
		{
			double max_value = ::max(upperHeight);
			double min_value = ::min(lowerHeight);

			if (max_value == kFloatMissing || min_value == kFloatMissing)
			{
				itsLogger.Error("Min or max values of given heights are missing");
				throw kFileDataNotFound;
			}

			auto levelsForMaxHeight = LevelForHeight(prod, max_value);
			auto levelsForMinHeight = LevelForHeight(prod, min_value);

			highestHybridLevel = static_cast<long>(levelsForMaxHeight.second.Value());
			lowestHybridLevel = static_cast<long>(levelsForMinHeight.first.Value());

			assert(lowestHybridLevel >= highestHybridLevel);

			itsLogger.Debug("Adjusting level range to " + boost::lexical_cast<string>(lowestHybridLevel) + " .. " +
							boost::lexical_cast<string>(highestHybridLevel) + " for height range " +
							boost::str(boost::format("%.2f") % min_value) + " .. " +
							boost::str(boost::format("%.2f") % max_value) + " " + heightUnit);
		}
		break;

		case kFindValueModifier:
		{
			auto p = ::minmax(findValue);

			double max_value = p.second;  // highest
			double min_value = p.first;   // lowest

			if (itsHeightUnit == kHPa)
			{
				// larger value is closer to ground

				double temp = max_value;
				max_value = min_value;
				min_value = temp;

				assert(min_value >= 10);
				assert(max_value < 1200);
			}

			auto levelsForMaxHeight = LevelForHeight(prod, max_value);
			auto levelsForMinHeight = LevelForHeight(prod, min_value);

			highestHybridLevel = static_cast<long>(levelsForMaxHeight.second.Value());
			lowestHybridLevel = static_cast<long>(levelsForMinHeight.first.Value());

			assert(lowestHybridLevel >= highestHybridLevel);

			itsLogger.Debug("Adjusting level range to " + boost::lexical_cast<string>(lowestHybridLevel) + " .. " +
							boost::lexical_cast<string>(highestHybridLevel) + " for height range " +
							boost::str(boost::format("%.2f") % min_value) + " .. " +
							boost::str(boost::format("%.2f") % max_value) + " " + heightUnit);
		}
		break;

		default:
			break;
	}

	for (long levelValue = lowestHybridLevel; levelValue >= highestHybridLevel && !mod->CalculationFinished();
	     levelValue--)
	{
		level currentLevel(kHybrid, levelValue, "HYBRID");

		valueheight data = GetData(currentLevel, wantedParam, itsTime, itsForecastType);

		auto values = data.first;
		auto heights = data.second;

		assert(heights->Grid()->Size() == values->Grid()->Size());

		values->First();
		heights->First();

		mod->Process(values->Grid()->Data().Values(), heights->Grid()->Data().Values());

#ifdef DEBUG
		size_t heightsCrossed = mod->HeightsCrossed();

		string msg = "Level " + boost::lexical_cast<string>(currentLevel.Value()) + ": height range crossed for " +
		             boost::lexical_cast<string>(heightsCrossed) + "/" +
		             boost::lexical_cast<string>(values->Data().Size()) + " grid points";

		itsLogger.Debug(msg);
#endif
	}

	auto ret = mod->Result();

	assert(mod->HeightsCrossed() == ret.size());
	return ret;
}

valueheight hitool::GetData(const level& wantedLevel, const param& wantedParam, const forecast_time& wantedTime,
                            const forecast_type& wantedType) const
{
	shared_ptr<info> values, heights;
	auto f = GET_PLUGIN(fetcher);

	param heightParam;

	if (itsHeightUnit == kM)
	{
		heightParam = param("HL-M");
	}
	else if (itsHeightUnit == kHPa)
	{
		heightParam = param("P-HPA");
	}
	else
	{
		itsLogger.Fatal("Invalid height unit: " + boost::lexical_cast<string>(itsHeightUnit));
	}

	try
	{
		if (!values)
		{
			values = f->Fetch(itsConfiguration, wantedTime, wantedLevel, wantedParam, wantedType);
		}

		if (!heights)
		{
			heights = f->Fetch(itsConfiguration, wantedTime, wantedLevel, heightParam, wantedType);
		}
	}
	catch (HPExceptionType& e)
	{
		switch (e)
		{
			case kFileDataNotFound:
				throw;
				break;

			default:
				throw runtime_error("hitool: Unable to proceed");
				break;
		}
	}

	assert(values);
	assert(heights);
	assert(values->Grid()->Size() == heights->Grid()->Size());
	assert(heights->Data().MissingCount() != heights->Data().Size());

	// No Merge() here since that will mess up cache

	valueheight ret = valueheight(values, heights);
	return ret;
}

/* CONVENIENCE FUNCTIONS */

vector<double> hitool::VerticalHeight(const vector<param>& wantedParamList, double lowerHeight, double upperHeight,
                                      const vector<double>& findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalHeight(wantedParamList, firstLevelValue, lastLevelValue, findValue, findNth);
}

vector<double> hitool::VerticalHeight(const vector<param>& wantedParamList, const vector<double>& firstLevelValue,
                                      const vector<double>& lastLevelValue, const vector<double>& findValue,
                                      size_t findNth) const
{
	assert(!wantedParamList.empty());

	size_t p_i = 0;

	param foundParam = wantedParamList[p_i];

	for (size_t i = 0; i < wantedParamList.size(); i++)
	{
		try
		{
			return VerticalHeight(foundParam, firstLevelValue, lastLevelValue, findValue, findNth);
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				if (++p_i < wantedParamList.size())
				{
					itsLogger.Debug("Switching parameter from " + foundParam.Name() + " to " +
									wantedParamList[p_i].Name());
					foundParam = wantedParamList[p_i];
				}
				else
				{
					itsLogger.Warning("No more valid parameters left");
					throw;
				}
			}
			else
			{
				throw;
			}
		}
	}

	throw runtime_error("Data not found");
}

vector<double> hitool::VerticalHeight(const param& wantedParam, double lowerHeight, double upperHeight,
                                      double findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);
	vector<double> findValueVector(itsConfiguration->Info()->Grid()->Size(), findValue);

	return VerticalHeight(wantedParam, firstLevelValue, lastLevelValue, findValueVector, findNth);
}

vector<double> hitool::VerticalHeight(const params& wantedParamList, double lowerHeight, double upperHeight,
                                      double findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);
	vector<double> findValueVector(itsConfiguration->Info()->Grid()->Size(), findValue);

	return VerticalHeight(wantedParamList, firstLevelValue, lastLevelValue, findValueVector, findNth);
}

vector<double> hitool::VerticalHeight(const param& wantedParam, double lowerHeight, double upperHeight,
                                      const vector<double>& findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalHeight(wantedParam, firstLevelValue, lastLevelValue, findValue, findNth);
}

vector<double> hitool::VerticalHeight(const param& wantedParam, const vector<double>& firstLevelValue,
                                      const vector<double>& lastLevelValue, const vector<double>& findValue,
                                      size_t findNth) const
{
	auto modifier = CreateModifier(kFindHeightModifier);
	modifier->FindNth(findNth);

	return VerticalExtremeValue(modifier, kHybrid, wantedParam, firstLevelValue, lastLevelValue, findValue);
}

vector<double> hitool::VerticalHeightGreaterThan(const param& wantedParam, double lowerHeight, double upperHeight,
                                                 double findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);
	vector<double> findValueVector(itsConfiguration->Info()->Grid()->Size(), findValue);

	return VerticalHeightGreaterThan(wantedParam, firstLevelValue, lastLevelValue, findValueVector, findNth);
}

vector<double> hitool::VerticalHeightGreaterThan(const params& wantedParamList, double lowerHeight, double upperHeight,
                                                 double findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);
	vector<double> findValueVector(itsConfiguration->Info()->Grid()->Size(), findValue);

	return VerticalHeightGreaterThan(wantedParamList, firstLevelValue, lastLevelValue, findValueVector, findNth);
}

vector<double> hitool::VerticalHeightGreaterThan(const param& wantedParam, double lowerHeight, double upperHeight,
                                                 const vector<double>& findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalHeightGreaterThan(wantedParam, firstLevelValue, lastLevelValue, findValue, findNth);
}

vector<double> hitool::VerticalHeightGreaterThan(const vector<param>& wantedParamList, double lowerHeight,
                                                 double upperHeight, const vector<double>& findValue,
                                                 size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalHeightGreaterThan(wantedParamList, firstLevelValue, lastLevelValue, findValue, findNth);
}

vector<double> hitool::VerticalHeightGreaterThan(const vector<param>& wantedParamList,
                                                 const vector<double>& firstLevelValue,
                                                 const vector<double>& lastLevelValue, const vector<double>& findValue,
                                                 size_t findNth) const
{
	assert(!wantedParamList.empty());

	size_t p_i = 0;

	param foundParam = wantedParamList[p_i];

	for (size_t i = 0; i < wantedParamList.size(); i++)
	{
		try
		{
			return VerticalHeightGreaterThan(foundParam, firstLevelValue, lastLevelValue, findValue, findNth);
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				if (++p_i < wantedParamList.size())
				{
					itsLogger.Debug("Switching parameter from " + foundParam.Name() + " to " +
									wantedParamList[p_i].Name());
					foundParam = wantedParamList[p_i];
				}
				else
				{
					itsLogger.Warning("No more valid parameters left");
					throw;
				}
			}
			else
			{
				throw;
			}
		}
	}

	throw runtime_error("Data not found");
}

vector<double> hitool::VerticalHeightGreaterThan(const param& wantedParam, const vector<double>& firstLevelValue,
                                                 const vector<double>& lastLevelValue, const vector<double>& findValue,
                                                 size_t findNth) const
{
	auto modifier = CreateModifier(kFindHeightGreaterThanModifier);
	modifier->FindNth(findNth);

	return VerticalExtremeValue(modifier, kHybrid, wantedParam, firstLevelValue, lastLevelValue, findValue);
}

vector<double> hitool::VerticalHeightLessThan(const param& wantedParam, double lowerHeight, double upperHeight,
                                              double findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);
	vector<double> findValueVector(itsConfiguration->Info()->Grid()->Size(), findValue);

	return VerticalHeightLessThan(wantedParam, firstLevelValue, lastLevelValue, findValueVector, findNth);
}

vector<double> hitool::VerticalHeightLessThan(const params& wantedParamList, double lowerHeight, double upperHeight,
                                              double findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);
	vector<double> findValueVector(itsConfiguration->Info()->Grid()->Size(), findValue);

	return VerticalHeightLessThan(wantedParamList, firstLevelValue, lastLevelValue, findValueVector, findNth);
}

vector<double> hitool::VerticalHeightLessThan(const param& wantedParam, double lowerHeight, double upperHeight,
                                              const vector<double>& findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalHeightLessThan(wantedParam, firstLevelValue, lastLevelValue, findValue, findNth);
}

vector<double> hitool::VerticalHeightLessThan(const vector<param>& wantedParamList, double lowerHeight,
                                              double upperHeight, const vector<double>& findValue, size_t findNth) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalHeightLessThan(wantedParamList, firstLevelValue, lastLevelValue, findValue, findNth);
}

vector<double> hitool::VerticalHeightLessThan(const vector<param>& wantedParamList,
                                              const vector<double>& firstLevelValue,
                                              const vector<double>& lastLevelValue, const vector<double>& findValue,
                                              size_t findNth) const
{
	assert(!wantedParamList.empty());

	size_t p_i = 0;

	param foundParam = wantedParamList[p_i];

	for (size_t i = 0; i < wantedParamList.size(); i++)
	{
		try
		{
			return VerticalHeightLessThan(foundParam, firstLevelValue, lastLevelValue, findValue, findNth);
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				if (++p_i < wantedParamList.size())
				{
					itsLogger.Debug("Switching parameter from " + foundParam.Name() + " to " +
									wantedParamList[p_i].Name());
					foundParam = wantedParamList[p_i];
				}
				else
				{
					itsLogger.Warning("No more valid parameters left");
					throw;
				}
			}
			else
			{
				throw;
			}
		}
	}

	throw runtime_error("Data not found");
}

vector<double> hitool::VerticalHeightLessThan(const param& wantedParam, const vector<double>& firstLevelValue,
                                              const vector<double>& lastLevelValue, const vector<double>& findValue,
                                              size_t findNth) const
{
	auto modifier = CreateModifier(kFindHeightLessThanModifier);
	modifier->FindNth(findNth);

	return VerticalExtremeValue(modifier, kHybrid, wantedParam, firstLevelValue, lastLevelValue, findValue);
}

vector<double> hitool::VerticalMinimum(const vector<param>& wantedParamList, double lowerHeight,
                                       double upperHeight) const
{
	assert(!wantedParamList.empty());

	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalMinimum(wantedParamList, firstLevelValue, lastLevelValue);
}

vector<double> hitool::VerticalMinimum(const vector<param>& wantedParamList, const vector<double>& firstLevelValue,
                                       const vector<double>& lastLevelValue) const
{
	assert(!wantedParamList.empty());

	size_t p_i = 0;

	param foundParam = wantedParamList[p_i];

	for (size_t i = 0; i < wantedParamList.size(); i++)
	{
		try
		{
			return VerticalMinimum(foundParam, firstLevelValue, lastLevelValue);
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				if (++p_i < wantedParamList.size())
				{
					itsLogger.Debug("Switching parameter from " + foundParam.Name() + " to " +
									wantedParamList[p_i].Name());
					foundParam = wantedParamList[p_i];
				}
				else
				{
					itsLogger.Warning("No more valid parameters left");
					throw;
				}
			}
			else
			{
				throw;
			}
		}
	}

	throw runtime_error("Data not found");
}

vector<double> hitool::VerticalMinimum(const param& wantedParam, double lowerHeight, double upperHeight) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalExtremeValue(CreateModifier(kMinimumModifier), kHybrid, wantedParam, firstLevelValue,
	                            lastLevelValue);
}

vector<double> hitool::VerticalMinimum(const param& wantedParam, const vector<double>& firstLevelValue,
                                       const vector<double>& lastLevelValue) const
{
	return VerticalExtremeValue(CreateModifier(kMinimumModifier), kHybrid, wantedParam, firstLevelValue,
	                            lastLevelValue);
}

vector<double> hitool::VerticalMaximum(const vector<param>& wantedParamList, double lowerHeight,
                                       double upperHeight) const
{
	assert(!wantedParamList.empty());

	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalMaximum(wantedParamList, firstLevelValue, lastLevelValue);
}

vector<double> hitool::VerticalMaximum(const vector<param>& wantedParamList, const vector<double>& firstLevelValue,
                                       const vector<double>& lastLevelValue) const
{
	assert(!wantedParamList.empty());

	size_t p_i = 0;

	param foundParam = wantedParamList[p_i];

	for (size_t i = 0; i < wantedParamList.size(); i++)
	{
		try
		{
			return VerticalMaximum(foundParam, firstLevelValue, lastLevelValue);
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				if (++p_i < wantedParamList.size())
				{
					itsLogger.Debug("Switching parameter from " + foundParam.Name() + " to " +
									wantedParamList[p_i].Name());
					foundParam = wantedParamList[p_i];
				}
				else
				{
					itsLogger.Warning("No more valid parameters left");
					throw;
				}
			}
			else
			{
				throw;
			}
		}
	}

	throw runtime_error("Data not found");
}

vector<double> hitool::VerticalMaximum(const param& wantedParam, double lowerHeight, double upperHeight) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalExtremeValue(CreateModifier(kMaximumModifier), kHybrid, wantedParam, firstLevelValue,
	                            lastLevelValue);
}

vector<double> hitool::VerticalMaximum(const param& wantedParam, const vector<double>& firstLevelValue,
                                       const vector<double>& lastLevelValue) const
{
	return VerticalExtremeValue(CreateModifier(kMaximumModifier), kHybrid, wantedParam, firstLevelValue,
	                            lastLevelValue);
}

vector<double> hitool::VerticalAverage(const params& wantedParamList, double lowerHeight, double upperHeight) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalAverage(wantedParamList, firstLevelValue, lastLevelValue);
}

vector<double> hitool::VerticalAverage(const vector<param>& wantedParamList, const vector<double>& firstLevelValue,
                                       const vector<double>& lastLevelValue) const
{
	assert(!wantedParamList.empty());

	size_t p_i = 0;

	param foundParam = wantedParamList[p_i];

	for (size_t i = 0; i < wantedParamList.size(); i++)
	{
		try
		{
			return VerticalAverage(foundParam, firstLevelValue, lastLevelValue);
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				if (++p_i < wantedParamList.size())
				{
					itsLogger.Debug("Switching parameter from " + foundParam.Name() + " to " +
									wantedParamList[p_i].Name());
					foundParam = wantedParamList[p_i];
				}
				else
				{
					itsLogger.Warning("No more valid parameters left");
					throw;
				}
			}
			else
			{
				throw;
			}
		}
	}

	throw runtime_error("Data not found");
}

vector<double> hitool::VerticalAverage(const param& wantedParam, double lowerHeight, double upperHeight) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalExtremeValue(CreateModifier(kAverageModifier), kHybrid, wantedParam, firstLevelValue,
	                            lastLevelValue);
}

vector<double> hitool::VerticalAverage(const param& wantedParam, const vector<double>& firstLevelValue,
                                       const vector<double>& lastLevelValue) const
{
	return VerticalExtremeValue(CreateModifier(kAverageModifier), kHybrid, wantedParam, firstLevelValue,
	                            lastLevelValue);
}

vector<double> hitool::VerticalSum(const vector<param>& wantedParamList, const vector<double>& firstLevelValue,
                                   const vector<double>& lastLevelValue) const
{
	assert(!wantedParamList.empty());

	size_t p_i = 0;

	param foundParam = wantedParamList[p_i];

	for (size_t i = 0; i < wantedParamList.size(); i++)
	{
		try
		{
			return VerticalSum(foundParam, firstLevelValue, lastLevelValue);
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				if (++p_i < wantedParamList.size())
				{
					itsLogger.Debug("Switching parameter from " + foundParam.Name() + " to " +
									wantedParamList[p_i].Name());
					foundParam = wantedParamList[p_i];
				}
				else
				{
					itsLogger.Warning("No more valid parameters left");
					throw;
				}
			}
			else
			{
				throw;
			}
		}
	}

	throw runtime_error("Data not found");
}

vector<double> hitool::VerticalSum(const param& wantedParam, double firstLevelValue, double lastLevelValue) const
{
	vector<double> firstLevelValueVector(itsConfiguration->Info()->Grid()->Size(), firstLevelValue);
	vector<double> lastLevelValueVector(itsConfiguration->Info()->Grid()->Size(), lastLevelValue);

	return VerticalExtremeValue(CreateModifier(kAccumulationModifier), kHybrid, wantedParam, firstLevelValueVector,
	                            lastLevelValueVector);
}

vector<double> hitool::VerticalSum(const params& wantedParamList, double firstLevelValue, double lastLevelValue) const
{
	vector<double> firstLevelValueVector(itsConfiguration->Info()->Grid()->Size(), firstLevelValue);
	vector<double> lastLevelValueVector(itsConfiguration->Info()->Grid()->Size(), lastLevelValue);

	return VerticalSum(wantedParamList, firstLevelValueVector, lastLevelValueVector);
}

vector<double> hitool::VerticalSum(const param& wantedParam, const vector<double>& firstLevelValue,
                                   const vector<double>& lastLevelValue) const
{
	return VerticalExtremeValue(CreateModifier(kAccumulationModifier), kHybrid, wantedParam, firstLevelValue,
	                            lastLevelValue);
}

vector<double> hitool::VerticalCount(const vector<param>& wantedParamList, const vector<double>& firstLevelValue,
                                     const vector<double>& lastLevelValue, const vector<double>& findValue) const
{
	assert(!wantedParamList.empty());

	size_t p_i = 0;

	param foundParam = wantedParamList[p_i];

	for (size_t i = 0; i < wantedParamList.size(); i++)
	{
		try
		{
			return VerticalCount(foundParam, firstLevelValue, lastLevelValue, findValue);
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				if (++p_i < wantedParamList.size())
				{
					itsLogger.Debug("Switching parameter from " + foundParam.Name() + " to " +
									wantedParamList[p_i].Name());
					foundParam = wantedParamList[p_i];
				}
				else
				{
					itsLogger.Warning("No more valid parameters left");
					throw;
				}
			}
			else
			{
				throw;
			}
		}
	}

	throw runtime_error("Data not found");
}

vector<double> hitool::VerticalCount(const param& wantedParam, const vector<double>& firstLevelValue,
                                     const vector<double>& lastLevelValue, const vector<double>& findValue) const
{
	return VerticalExtremeValue(CreateModifier(kCountModifier), kHybrid, wantedParam, firstLevelValue, lastLevelValue,
	                            findValue);
}

vector<double> hitool::VerticalCount(const param& wantedParam, double firstLevelValue, double lastLevelValue,
                                     double findValue) const
{
	vector<double> firstLevelValueVector(itsConfiguration->Info()->Grid()->Size(), firstLevelValue);
	vector<double> lastLevelValueVector(itsConfiguration->Info()->Grid()->Size(), lastLevelValue);
	vector<double> findValueVector(itsConfiguration->Info()->Grid()->Size(), findValue);

	return VerticalExtremeValue(CreateModifier(kCountModifier), kHybrid, wantedParam, firstLevelValueVector,
	                            lastLevelValueVector, findValueVector);
}

vector<double> hitool::VerticalCount(const params& wantedParamList, double firstLevelValue, double lastLevelValue,
                                     double findValue) const
{
	vector<double> firstLevelValueVector(itsConfiguration->Info()->Grid()->Size(), firstLevelValue);
	vector<double> lastLevelValueVector(itsConfiguration->Info()->Grid()->Size(), lastLevelValue);
	vector<double> findValueVector(itsConfiguration->Info()->Grid()->Size(), findValue);

	return VerticalCount(wantedParamList, firstLevelValueVector, lastLevelValueVector, findValueVector);
}

vector<double> hitool::VerticalValue(const vector<param>& wantedParamList, double wantedHeight) const
{
	assert(!wantedParamList.empty());

	vector<double> heightInfo(itsConfiguration->Info()->Grid()->Size(), wantedHeight);

	return VerticalValue(wantedParamList, heightInfo);
}

vector<double> hitool::VerticalValue(const vector<param>& wantedParamList, const vector<double>& heightInfo) const
{
	assert(!wantedParamList.empty());

	size_t p_i = 0;

	param foundParam = wantedParamList[p_i];

	for (size_t i = 0; i < wantedParamList.size(); i++)
	{
		try
		{
			return VerticalValue(foundParam, heightInfo);
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				if (++p_i < wantedParamList.size())
				{
					itsLogger.Debug("Switching parameter from " + foundParam.Name() + " to " +
									wantedParamList[p_i].Name());
					foundParam = wantedParamList[p_i];
				}
				else
				{
					itsLogger.Warning("No more valid parameters left");
					throw;
				}
			}
			else
			{
				throw;
			}
		}
	}

	throw runtime_error("Data not found");
}

vector<double> hitool::VerticalValue(const param& wantedParam, double height) const
{
	vector<double> heightInfo(itsConfiguration->Info()->Grid()->Size(), height);

	return VerticalExtremeValue(CreateModifier(kFindValueModifier), kHybrid, wantedParam, vector<double>(),
	                            vector<double>(), heightInfo);
}

vector<double> hitool::VerticalValue(const param& wantedParam, const vector<double>& heightInfo) const
{
	return VerticalExtremeValue(CreateModifier(kFindValueModifier), kHybrid, wantedParam, vector<double>(),
	                            vector<double>(), heightInfo);
}

vector<double> hitool::PlusMinusArea(const params& wantedParamList, double lowerHeight, double upperHeight) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return PlusMinusArea(wantedParamList, firstLevelValue, lastLevelValue);
}

vector<double> hitool::PlusMinusArea(const vector<param>& wantedParamList, const vector<double>& firstLevelValue,
                                     const vector<double>& lastLevelValue) const
{
	assert(!wantedParamList.empty());

	size_t p_i = 0;

	param foundParam = wantedParamList[p_i];

	for (size_t i = 0; i < wantedParamList.size(); i++)
	{
		try
		{
			return PlusMinusArea(foundParam, firstLevelValue, lastLevelValue);
		}
		catch (const HPExceptionType& e)
		{
			if (e == kFileDataNotFound)
			{
				if (++p_i < wantedParamList.size())
				{
					itsLogger.Debug("Switching parameter from " + foundParam.Name() + " to " +
									wantedParamList[p_i].Name());
					foundParam = wantedParamList[p_i];
				}
				else
				{
					itsLogger.Warning("No more valid parameters left");
					throw;
				}
			}
			else
			{
				throw;
			}
		}
	}

	throw runtime_error("Data not found");
}

vector<double> hitool::PlusMinusArea(const param& wantedParam, double lowerHeight, double upperHeight) const
{
	vector<double> firstLevelValue(itsConfiguration->Info()->Grid()->Size(), lowerHeight);
	vector<double> lastLevelValue(itsConfiguration->Info()->Grid()->Size(), upperHeight);

	return VerticalExtremeValue(CreateModifier(kPlusMinusAreaModifier), kHybrid, wantedParam, firstLevelValue,
	                            lastLevelValue);
}

vector<double> hitool::PlusMinusArea(const param& wantedParam, const vector<double>& firstLevelValue,
                                     const vector<double>& lastLevelValue) const
{
	return VerticalExtremeValue(CreateModifier(kPlusMinusAreaModifier), kHybrid, wantedParam, firstLevelValue,
	                            lastLevelValue);
}

void hitool::Time(const forecast_time& theTime) { itsTime = theTime; }
void hitool::ForecastType(const forecast_type& theForecastType) { itsForecastType = theForecastType; }
void hitool::Configuration(shared_ptr<const plugin_configuration> conf)
{
	itsConfiguration = make_shared<plugin_configuration>(
	    *conf);  // not an ideal solution but the next line changes iterator positions so we need a local copy
	itsConfiguration->Info()->First();
}

HPParameterUnit hitool::HeightUnit() const { return itsHeightUnit; }
void hitool::HeightUnit(HPParameterUnit theHeightUnit)
{
	if (theHeightUnit != kM && theHeightUnit != kHPa)
	{
		itsLogger.Error("Invalid height unit: " + boost::lexical_cast<string>(theHeightUnit));
		return;
	}

	itsHeightUnit = theHeightUnit;
}