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

 Copyright 2012 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 *************************************/

#include "MvQFeatureCurveItem.h"

#include <algorithm>
#include <cmath>

#include <QApplication>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QImage>
#include <QMouseEvent>
#include <QPainter>
#include <QPolygonF>
#include <QStyle>

#include <QJsonArray>
#include <QJsonObject>

#include "GenAppService.hpp"
#include "MvQJson.h"
#include "MvQPlotView.h"
#include "MvQFeatureFactory.h"
#include "MvQFeatureCommand.h"
#include "WsType.h"
#include "WsDecoder.h"
#include "MvQFeatureSelector.h"
#include "MvQMethods.h"
#include "MgQLayoutItem.h"
#include "IconProvider.h"
#include "ObjectList.h"
#include "MvSci.h"
#include "MvLog.h"
#include "MvException.h"

#include <map>
#include <string>

#include "cavc/polylineoffset.hpp"

//#define MVQFEATURECURVE_DEBUG_
//#define MVQFEATURECURVEPOINT_DEBUG_

//========================================
//
// MvQFeatureLineEdgeShape
//
//========================================

void MvQFeatureLineEdgeShape::paint(QPainter* painter, const QPen& pen)
{
    if (type_ == "FILLED_ARROW") {
        painter->setBrush(pen.color());
        painter->setPen(Qt::NoPen);
        painter->drawPolygon(shape_);
    }
    else if (type_ == "OPEN_ARROW") {
        painter->setPen(pen);
        painter->drawPolyline(shape_);
    }
}

bool MvQFeatureLineEdgeShape::update(const std::string& type, float angle, float len)
{
    //qDebug() << " MvQFeatureLineEdgeShape::update" << type.c_str() << angle << len;
    bool changed           = false;
    static const float eps = 1E-3;
    changed                = (type_ != type || fabs(angle_ - angle) > eps || fabs(length_ - len) > eps);

    type_   = type;
    angle_  = angle;
    length_ = len;

    return (shape_.isEmpty()) ? true : changed;
}

void MvQFeatureLineEdgeShape::clearShape()
{
    shape_.clear();
}

void MvQFeatureLineEdgeShape::build(const QPointF& p1, const QPointF& p2)
{
    if (type_ == "NONE") {
        return;
    }

    // Compute line slope and arrow angle in radians
    float theta = atan2(p2.y() - p1.y(), p2.x() - p1.x());
    float rad   = MvSci::degToRad(angle_);

    // Compute first point
    shape_.clear();
    shape_ << QPointF(p1.x() + length_ * cos(theta + rad),
                      p1.y() + length_ * sin(theta + rad));

    // Assign middle point
    shape_ << p1;

    // Compute third point
    shape_ << QPointF(p1.x() + length_ * cos(theta - rad),
                      p1.y() + length_ * sin(theta - rad));

    // Compute the intersection of two lines: 1) main arrow line and 2) line
    // joining the arrow's ends
    // The simpler way is to find the middle point between the two arrow's ends
    crossPoint_.setX((shape_[0].x() + shape_[2].x()) / 2.);
    crossPoint_.setY((shape_[0].y() + shape_[2].y()) / 2.);
}

// this is only called when the parent curve's bounding box is recomputed
QRectF MvQFeatureLineEdgeShape::bRect(int penWidth) const
{
    // an open arrow is basically two lines rendered with a line with penWidth.
    // To compute its bounding box we need to take into account the line width. We
    // simply magnify the shape from its centre and assume that the resulting shape
    // gives a good estimate of the real shape that paint() will actually render onto the
    // target device.
    if (type_ == "OPEN_ARROW") {
        if (shape_.size() > 0) {
            // compute centre
            QPointF c(0, 0);
            for (auto p : shape_) {
                c += p;
            }
            c /= shape_.size();
            // magnifiy from centre
            QPolygonF pp;
            for (auto p : shape_) {
                auto d    = p - c;
                auto norm = sqrt(pow((d.x()), 2) + pow((d.y()), 2));
                auto unit = QPointF(0, 0);
                if (norm > 1E-9) {
                    unit = d / norm;
                }
                pp << p + unit * static_cast<double>(penWidth);
            }
            return pp.boundingRect();
        }
    }
    return shape_.boundingRect();
}

void MvQFeatureLineEdgeShape::adjustLineSize(QPolygonF& p, bool start) const
{
    if (type_ == "FILLED_ARROW") {
        int nel;
        if (start) {
            if ((nel = adjustLineSizeFA(p.cbegin(), p.cend())) < 0)
                return;
            nel = p.size() - nel;
            p.remove(0, nel);
            p.insert(0, crossPoint_);
        }
        else {
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
            if ((nel = adjustLineSizeFA(p.crbegin(), p.crend())) < 0)
                return;
#else
            QPolygonF p1;
            std::reverse_copy(p.begin(), p.end(), std::back_inserter(p1));
            if ((nel = adjustLineSizeFA(p1.cbegin(), p1.cend())) < 0)
                return;
#endif
            p.remove(nel, p.size() - nel);
            p.append(crossPoint_);
        }
    }
}

template <typename Iterator>
int MvQFeatureLineEdgeShape::adjustLineSizeFA(Iterator iter, Iterator end) const
{
    int nel = -1;
    iter++;
    for (; iter != end; ++iter) {
        if (!shape_.containsPoint(*iter, Qt::OddEvenFill)) {
            nel = std::distance(iter, end);
            break;
        }
    }
    // number of elements to the last iterator
    return nel;

// the former solution
#if 0
//    // Compute distance from initial point to arrow ends crossing point
//    float x0 = iter->x();
//    float y0 = iter->y();
//    float dist1 = sqrt(pow(crossPoint_.x()-x0,2) + pow(crossPoint_.y()-y0,2));

//    // Check the first distance greater than dist1
//    float dist2;
//    int nel = -1;
//    iter++;
//    for ( ; iter != end; ++iter ) {
//        dist2 = sqrt(pow(iter->x()-x0,2) + pow(iter->y()-y0,2));
//        if (dist2 > dist1) {
//            nel = std::distance(iter, end);
//            break;
//        }
//    }

//    return nel; // number of elements to the last iterator
#endif
}


//========================================
//
// MvQFeatureCurvePointItem
//
//========================================

MvQFeatureCurvePointItem::MvQFeatureCurvePointItem(MvQFeatureCurveBaseItem* curve) :
    MvQFeatureItem("CurvePoint", curve),
    curve_(curve)
{
    Q_ASSERT(parentItem());
    float w     = 8;
    bRect_      = QRectF(-w / 2, -w / 2, w, w);
    dataLayout_ = curve_->dataLayout();
    sceneId_    = curve_->sceneId();

    // the parent is the curve. A child item always appears on the parent's z-level. We want to
    // show this item on top of the parent (the curve). It would be
    // enough to leave the z-level unset (the default value is 0) to achive it. However, in this case
    // MgQScene::checkItemIsVisible would mark it as visible (even if it is hidden) and it would appear
    // in the scene rendered for the magnifier and qt-based output drivers! So we set the z-value to the
    // top of the feature layer!
    setZValue(1.4);
}

void MvQFeatureCurvePointItem::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
{
    if (isSelected() || hover_) {
        painter->setPen(selPen_);
        painter->setBrush(selBrush_);
    }
    else {
        painter->setPen(pen_);
        painter->setBrush(brush_);
    }
    painter->drawRect(bRect_);
}

// in this mode the point is selected and grabs all the mouse actions
// (i.e. it is moved as the mouse moves)
void MvQFeatureCurvePointItem::enterCreateMode()
{
    setSelected(true);
    currentAction_ = CreationAction;
    grabMouse();
}

// in this mode the point is selected and grabs all the mouse actions
// (i.e. it is moved as the mouse moves)
void MvQFeatureCurvePointItem::enterInsertMode()
{
    setSelected(true);
    currentAction_ = InsertAction;
    grabMouse();
}

void MvQFeatureCurvePointItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeatureCurvePointItem::mousePressEvent";
    qDebug() << " selected (before):" << isSelected();
#endif
    event->accept();
    if (currentAction_ == CreationAction) {
        currentAction_ = NoAction;
        ungrabMouse();
        setSelected(false);
        curve_->createNext();
    }
    else if (currentAction_ == InsertAction) {
        currentAction_ = NoAction;
        ungrabMouse();
        setSelected(false);
    }
    else if (curve_->currentAction() == PointEditAction) {
        currentAction_ = PointEditAction;
    }
#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << " selected (after):" << isSelected();
#endif
    dragPos_ = mapToParent(event->pos());
    inMove_  = false;
}

void MvQFeatureCurvePointItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeatureCurvePointItem::mouseMoveEvent" << parentItem()->isSelected();
    qDebug() << "parent selected:" << parentItem()->isSelected();
#endif
    QPointF pp  = mapToParent(event->pos());
    QPointF scp = mapToScene(event->pos());
    float eps   = 1E-3;
    if (inMove_ || dragPos_.isNull() ||
        (fabs(pp.x() - dragPos_.x()) > eps && fabs(pp.y() - dragPos_.y()) > eps)) {
        if (currentAction_ == PointEditAction || currentAction_ == NoAction) {
            MvQFeatureHandler::Instance()->moveNode(curve_->getptr(), scp, curve_->nodeIndex(this), !inMove_);
        }
        else {
            setPos(pp);
            curve_->pointMoved(this);
        }
        inMove_ = true;
    }
}

void MvQFeatureCurvePointItem::mouseReleaseEvent(QGraphicsSceneMouseEvent*)
{
#ifdef MVQFEATURECURVE_DEBUG_
    // qDebug() << "MvQFeatureCurvePointItem::mouseReleaseEvent";
#endif
    if (currentAction_ == PointEditAction) {
        currentAction_ = NoAction;
    }
    dragPos_ = QPointF();
    inMove_  = false;
}

void MvQFeatureCurvePointItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent*)
{
    if (currentAction_ == CreationAction) {
        currentAction_ = NoAction;
        ungrabMouse();
        setSelected(false);
        curve_->createStop();
    }
}


void MvQFeatureCurvePointItem::hoverEnterEvent(QGraphicsSceneHoverEvent*)
{
#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeatureCurvePointItem::hoverEnterEvent"
             << "hover:" << hover_ << "currentAction:" << currentAction_ << "point:" << this;
#endif
    if (!hover_ &&
        (currentAction_ == PointEditAction || currentAction_ == NoAction)) {
        hover_ = true;
        MvQFeatureHandler::Instance()->setCurvePointCursor();
        update();
        curve_->adjustHover(this);
    }
}

void MvQFeatureCurvePointItem::hoverLeaveEvent(QGraphicsSceneHoverEvent*)
{
#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeatureCurvePointItem::hoverLeaveEvent"
             << "hover:" << hover_ << "point:" << this;
#endif
    if (hover_) {
        hover_ = false;
        MvQFeatureHandler::Instance()->resetCursor();
        update();
    }
}

QVariant MvQFeatureCurvePointItem::itemChange(GraphicsItemChange change, const QVariant& value)
{
    // we call the default implementation - otherwise MvQFeatureItem would handle it!
    return QGraphicsItem::itemChange(change, value);
}

void MvQFeatureCurvePointItem::resetHover()
{
#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeatureCurvePointItem::resetHover"
             << "hover:" << hover_ << "point:" << this;
#endif
    if (hover_) {
        hover_ = false;
        if (!curve_->hasHoveredPoints()) {
            MvQFeatureHandler::Instance()->resetCursor();
        }
        update();
    }
}

bool MvQFeatureCurvePointItem::containsInHalo(QPointF pp)
{
    double halo = 10;
#ifdef MVQFEATURECURVE_DEBUG_
    //qDebug() << "containsInHalo" << pp << bRect_;
#endif
    return bRect_.adjusted(-halo, -halo, halo, halo).contains(mapFromParent(pp));
}

//void MvQFeatureCurvePointItem::adjustProjection()
//{
//    dataLayout_ = curve_->dataLayout();
//    sceneId_.clear();
//    outOfView_ = true;
//    if (dataLayout_) {
//        sceneId_ = dataLayout_->layout().id();
//        outOfView_ = false;
//    }
//}

// Recompute the position of the item using the current geoCoord
void MvQFeatureCurvePointItem::reproject(bool /*init*/)
{
#ifdef MVQFEATURECURVEPOINT_DEBUG_
    qDebug() << "MvQFeatureCurvePointItem::reproject()" << typeName() << "outOfPlot:" << outOfView_ << "hasValidGeoCoord:" << hasValidGeoCoord_;
#endif
    dataLayout_ = curve_->dataLayout();
    sceneId_.clear();
    outOfView_ = true;
    if (dataLayout_) {
        sceneId_          = dataLayout_->layout().id();
        outOfView_        = false;
        QPointF scPos     = parentItem()->mapToScene(pos());
        hasValidGeoCoord_ = isValidPoint(geoCoord_);
        if (hasValidGeoCoord_) {
            // TODO: it is not guaranteed the pos is valid, since the coordinate might not
            // properly projected in the current view
            dataLayout_->mapFromGeoToSceneCoords(geoCoord_, scPos);
            setPos(parentItem()->mapFromScene(scPos));
        }
    }
}

QString MvQFeatureCurvePointItem::describe() const
{
    MvQFeatureDescriber ds;
    ds.addTitle("point_" + QString::number(curve_->nodeIndex(this)) + ":");
    ds.add("dataLayout=", dataLayout_);
    ds.add("sceneId=", QString::fromStdString(sceneId_));
    ds.add("outOfView=", outOfView_);
    ds.add("hasValidGeoCoord=", hasValidGeoCoord_);
    ds.add("geoCoord=", geoCoord_);
    ds.add("pos=", pos());
    ds.add("scPos=", scenePos());
    return ds.text();
}

//========================================
//
// MvQFeatureCurveBaseItem
//
//========================================

MvQFeatureCurveBaseItem::MvQFeatureCurveBaseItem(WsType* feature, bool closed) :
    MvQFeatureItem(feature),
    closed_(closed)
{
    linePen_    = MvQ::makePen("SOLID", 1, "BLACK");
    fillBrush_  = QBrush(QColor(55, 93, 160, 128));
    realZValue_ = zValue();
}

void MvQFeatureCurveBaseItem::init(const QJsonObject& obj)
{
    QJsonObject g = obj["geometry"].toObject();
    auto gcLst    = MvQJson::toPointFList(g["coordinates"].toArray(), g["type"].toString());
    for (int i = 0; i < gcLst.size(); i++) {
        auto pp = new MvQFeatureCurvePointItem(this);
        pp->setVisible(false);
        pp->geoCoord_ = gcLst[i];
        points_ << pp;
        if (maxPoints_ > 0 && i + 1 == maxPoints_) {
            break;
        }
    }

    if (maxPoints_ > 0 && points_.size() != maxPoints_) {
        for (auto pp : points_) {
            if (scene()) {
                scene()->removeItem(pp);
            }
            delete pp;
        }
        points_.clear();
    }

    if (points_.size() > 0) {
        geoCoord_ = points_[0]->geoCoord_;
    }

    MvQFeatureItem::init(obj);
}

void MvQFeatureCurveBaseItem::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
{
    painter->save();

    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->setPen(linePen_);

    // clip to map boundaries
    setClipping(painter);

    generateCurvePath();

    if (closed_) {
        painter->setBrush(fillBrush_);
        painter->drawPath(curvePath_);
    }
    else {
        startPointShape_.paint(painter, linePen_);
        endPointShape_.paint(painter, linePen_);
        painter->setBrush(Qt::NoBrush);
        painter->setPen(linePen_);
    }

    painter->drawPath(curvePath_);


    painter->restore();
}

// Recompute the position of the item using the current geoCoord
void MvQFeatureCurveBaseItem::reproject(bool /*init*/)
{
    if (!dataLayout_) {
        outOfView_ = true;
        return;
    }
#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeatureCurveBaseItem::reproject";
    for (int i = 0; i < points_.size(); i++) {
        qDebug() << " " << i << "pos:" << points_[i]->pos() << "geocoord:" << points_[i]->geoCoord();
    }
#endif
    QPointF scPos;
    dataLayout_->mapFromGeoToSceneCoords(geoCoord_, scPos);
    setPos(parentItem()->mapFromScene(scPos));

    if (points_.count() == 0) {
        createStart();
    }
    else {
        // reproject all the control points
        for (int i = 0; i < points_.count(); i++) {
            //points_[i]->adjustProjection();
            points_[i]->reproject(false);
        }
    }

#ifdef MVQFEATURECURVE_DEBUG_
    for (int i = 0; i < points_.size(); i++) {
        qDebug() << " " << i << "pos:" << points_[i]->pos() << "geocoord:" << points_[i]->geoCoord();
    }
#endif
    updateRect();
    adjustBRect();
}

// Place into a geocoord pos when geocoord is available and valid
void MvQFeatureCurveBaseItem::initAtGeoCoord(QPointF geoCoord)
{
    // At this point none of the points may have a scene pos! It can happen
    // when the item was loaded from json and we add it to the scene for the first time.
    // We simply use the difference between the stored and the taget gecoord of the
    // item and use this delta to recompute the geocords of the control points!
    QList<QPointF> gc;
    if (isValidPoint(geoCoord_)) {
        auto delta = geoCoord - geoCoord_;
        for (auto p : points_) {
            gc << p->geoCoord() + delta;
        }
    }

    geoCoord_ = geoCoord;

    QPointF scPos;
    Q_ASSERT(dataLayout_);
    dataLayout_->mapFromGeoToSceneCoords(geoCoord_, scPos);
    setPos(parentItem()->mapFromScene(scPos));

    if (points_.count() == 0) {
        createStart();
    }
    else {
        for (int i = 0; i < points_.count(); i++) {
            points_[i]->dataLayout_ = dataLayout_;
            points_[i]->initAtGeoCoord(gc[i]);
        }
    }

    updateRect();
    adjustBRect();
}

// Place into a scene pos when no geocoord is available
void MvQFeatureCurveBaseItem::initAtScenePos(const QPointF& scPos)
{
    Q_ASSERT(points_.count() > 1);
    if (points_.count() < 1) {
        throw MvException("MvQFeatureCurveBaseItem::" + std::string(__func__) +
                          "()\n\nInternal error: points_ size must be at least 2!");
    }

    // Here we suppose that all the points have a proper scene position
    setPos(parentItem()->mapFromScene(scPos));
    for (int i = 0; i < points_.count(); i++) {
        points_[i]->dataLayout_ = dataLayout_;
    }
    updateRect();
    adjustBRect();
}

void MvQFeatureCurveBaseItem::createStart()
{
    Q_ASSERT(points_.empty());
    if (!points_.empty())
        return;

    //start point
    auto* p1 = new MvQFeatureCurvePointItem(this);
    p1->initAtGeoCoord(geoCoord_);

    //second point
    auto* p2 = new MvQFeatureCurvePointItem(this);
    p2->initAtGeoCoord(geoCoord_);

    currentAction_ = CreationAction;
    points_ << p1 << p2;

    //put the second point into move mode!
    p2->enterCreateMode();
}

void MvQFeatureCurveBaseItem::createNext()
{
    Q_ASSERT(points_.count() >= 2);
    if (points_.count() < 2)
        return;

    if (maxPoints_ != -1 && points_.count() == maxPoints_) {
        updateRect();
        adjustBRect();

        // hide the control points
        for (int i = 0; i < points_.count(); i++) {
            points_[i]->setVisible(false);
            points_[i]->resetHover();
        }
        return;
    }

    //prev point
    MvQFeatureCurvePointItem* p1 = points_.back();
    p1->adjustGeoCoord();

    //next point
    auto* p2 = new MvQFeatureCurvePointItem(this);
    p2->initAtGeoCoord(p1->geoCoord());

    currentAction_ = CreationAction;
    points_ << p2;

    //put the second point into move mode
    p2->enterCreateMode();
}

void MvQFeatureCurveBaseItem::createStop()
{
    Q_ASSERT(points_.count() >= 2);
    if (points_.count() < 2)
        return;

    MvQFeatureCurvePointItem* p = points_.back();
    points_.pop_back();
    scene()->removeItem(p);
    delete p;
    updateRect();
    adjustBRect();

    // hide the control points
    for (int i = 0; i < points_.count(); i++) {
        points_[i]->setVisible(false);
        points_[i]->resetHover();
    }
}

// Enter point edit mode - the control points become visible but we have
// not clicked on them yet
void MvQFeatureCurveBaseItem::enterPointEdit()
{
    // we move the item temporarily atop
    realZValue_ = zValue();
    setZValue(topZValue_);

    //qDebug() << "MvQFeatureCurveBaseItem::enterPointEdit";
    currentAction_ = PointEditEnterAction;
    for (int i = 0; i < points_.count(); i++) {
        points_[i]->setVisible(true);
    }
    MvQFeatureHandler::Instance()->resetCursor();
    update();
}

// start the pont edit - this mode indicates that the interaction with the control points
// have started
void MvQFeatureCurveBaseItem::startPointEdit()
{
    //qDebug() << "MvQFeatureCurveBaseItem::startPointEdit";
    if (currentAction_ == PointEditEnterAction) {
        currentAction_ = PointEditAction;
    }
}

// leave the point edit mode
void MvQFeatureCurveBaseItem::leavePointEdit()
{
    //qDebug() << "MvQFeatureCurveBaseItem::leavePointEdit";
    currentAction_ = NoAction;
    for (int i = 0; i < points_.count(); i++) {
        points_[i]->setVisible(false);
        points_[i]->resetHover();
    }

    // we move back the item into its real Z level
    setZValue(realZValue_);  // move back to original z level
    update();
}

// determine if there is a control point that is being edited
bool MvQFeatureCurveBaseItem::hasEditedPoint()
{
    if (currentAction_ == PointEditAction) {
        for (int i = 0; i < points_.count(); i++) {
            if (points_[i]->currentAction() == PointEditAction) {
                return true;
            }
        }
    }
    return false;
}

// the level is changed temporarily while in point edit mode. realZValue_ stores the
// original z level
double MvQFeatureCurveBaseItem::realZValue() const
{
    return realZValue_;
}

void MvQFeatureCurveBaseItem::setRealZValue(double z)
{
    realZValue_ = z;
    // we only set the actual z value when we are not in point edit mode
    if (currentAction_ != PointEditEnterAction && currentAction_ != PointEditAction) {
        setZValue(z);
    }
}

void MvQFeatureCurveBaseItem::adjustHover(MvQFeatureCurvePointItem* p)
{
    for (auto pt : points_) {
        if (pt != p) {
            pt->resetHover();
        }
    }
}

bool MvQFeatureCurveBaseItem::hasHoveredPoints() const
{
    for (auto pt : points_) {
        if (pt->hover_) {
            return true;
        }
    }
    return false;
}

QRectF MvQFeatureCurveBaseItem::pointBoundingBox() const
{
    Q_ASSERT(points_.size() > 0);
    QPointF minP = points_[0]->pos();
    QPointF maxP = minP;
    for (int i = 1; i < points_.count(); i++) {
        QPointF pp = points_[i]->pos();
        if (pp.x() < minP.x()) {
            minP.setX(pp.x());
        }
        if (pp.y() < minP.y()) {
            minP.setY(pp.y());
        }
        if (pp.x() > maxP.x()) {
            maxP.setX(pp.x());
        }
        if (pp.y() > maxP.y()) {
            maxP.setY(pp.y());
        }
    }

    return QRectF(minP, maxP);
}

MvQFeatureGeometry MvQFeatureCurveBaseItem::getGeometry() const
{
    MvQFeatureGeometry g = MvQFeatureItem::getGeometry();
    for (auto p : points_) {
        g.pointsScenePos_ << mapToScene(p->pos());
        g.pointsGeoCoord_ << p->geoCoord_;
    }
    return g;
}

void MvQFeatureCurveBaseItem::moveBy(QPointF delta)
{
    prepareGeometryChange();

    QPointF prPos = pos() + delta;
    QPointF scPos = parentItem()->mapToScene(prPos);
    if (dataLayout_->contains(dataLayout_->mapFromScene(scPos))) {
        outOfView_ = false;
        dataLayout_->mapFromSceneToGeoCoords(scPos, geoCoord_);
    }
    else {
        outOfView_ = true;
    }

    setPos(prPos);
    for (int i = 0; i < points_.count(); i++) {
        points_[i]->adjustGeoCoord();
    }
}

void MvQFeatureCurveBaseItem::moveTo(const MvQFeatureGeometry& g)
{
    prepareGeometryChange();
    if (dataLayout_) {
        if (isValidPoint(g.geoCoord_)) {
            geoCoord_ = g.geoCoord_;
        }
    }

    setPos(g.pos_);
    for (int i = 0; i < points_.count(); i++) {
        points_[i]->adjustGeoCoord();
    }
}

void MvQFeatureCurveBaseItem::pointMoved(MvQFeatureCurvePointItem* p)
{
    if (p == points_[0]) {
        // moves the origo to the new position of the first point
        QPointF scPos = mapToScene(p->pos());
        QPointF pos1  = parentItem()->mapFromScene(scPos);
        QPointF delta = p->pos();
        setPos(pos1);
        adjustGeoCoord();

        // the first point's postion is the origo
        points_[0]->setPos(QPointF(0., 0.));

        for (int i = 1; i < points_.count(); i++) {
            points_[i]->setPos(points_[i]->pos() - delta);
        }

        for (int i = 0; i < points_.count(); i++) {
            points_[i]->adjustGeoCoord();
        }
    }
    else {
        p->adjustGeoCoord();
    }

    updateRect();
    adjustBRect();
}

void MvQFeatureCurveBaseItem::moveNodeTo(int nodeIndex, const QPointF& scenePos)
{
    if (nodeIndex >= 0 && nodeIndex < points_.size()) {
        auto p = points_[nodeIndex];
        p->setPos(mapFromScene(scenePos));
        pointMoved(p);
    }
}

int MvQFeatureCurveBaseItem::nodeForContextMenu(const QPointF& p)
{
    for (int i = 0; i < points_.size(); i++) {
        //qDebug() << "node" << i << points_[i]->isSelected();
        //        if (points_[i]->isSelected() && points_[i]->containsInHalo(p)) {
        if (points_[i]->containsInHalo(p)) {
            return i;
        }
    }
    return -1;
}

int MvQFeatureCurveBaseItem::nodeIndex(const MvQFeatureCurvePointItem* p) const
{
    return points_.indexOf(const_cast<MvQFeatureCurvePointItem*>(p));
}

void MvQFeatureCurveBaseItem::addNode(int nodeIdx, bool before)
{
    if (nodeIdx == -1)
        return;

    int otherIdx = -1;
    //insert node before the selected node
    if (before) {
        otherIdx = nodeIdx - 1;
        if (closed_ && nodeIdx == 0) {
            otherIdx = points_.count() - 1;
        }
        Q_ASSERT(otherIdx <= points_.count() - 1);
    }
    else {
        otherIdx = nodeIdx + 1;
        if (closed_ && nodeIdx == points_.count() - 1) {
            otherIdx = 0;
        }
        Q_ASSERT(otherIdx >= 0);
    }

    QPointF insPos;
    if (otherIdx >= points_.size()) {
        insPos = extrapolatePoint(false);
    }
    else if (otherIdx < 0) {
        insPos = extrapolatePoint(true);
    }
    else {
        // the insertion position is halfway between the two nodes
        insPos = midPoint(nodeIdx, otherIdx);
    }
    prepareGeometryChange();

    auto* p2 = new MvQFeatureCurvePointItem(this);
    p2->setPos(insPos);
    p2->adjustGeoCoord();

    if (before) {
        points_.insert(nodeIdx, p2);
    }
    else {
        points_.insert(nodeIdx + 1, p2);
    }

    updateRect();
    adjustBRect();
    p2->setSelected(true);
}

void MvQFeatureCurveBaseItem::addNode(int nodeIdx, const QPointF& scenePos)
{
    QPointF insPos = mapFromScene(scenePos);
    prepareGeometryChange();

    auto* p2 = new MvQFeatureCurvePointItem(this);
    p2->setPos(insPos);
    p2->adjustGeoCoord();
    points_.insert(nodeIdx, p2);
    updateRect();
    adjustBRect();
    p2->setSelected(true);
}

void MvQFeatureCurveBaseItem::removeNode(int nodeIdx)
{
    if (nodeIdx == -1)
        return;

    if (points_.count() > 2) {
        prepareGeometryChange();
        auto item = points_[nodeIdx];
        prepareForPointRemove(nodeIdx);
        points_.remove(nodeIdx);
        scene()->removeItem(item);
        delete item;
        if (nodeIdx == 0) {
            pointMoved(points_[0]);
        }
        else {
            updateRect();
            adjustBRect();
        }
    }
}

QRectF MvQFeatureCurveBaseItem::curveBoundingBox()
{
    Q_ASSERT(curveX_.size() == curveY_.size());

    QPointF minP = points_[0]->pos();
    QPointF maxP = minP;
    if (points_.size() >= 2) {
        const auto xMM = std::minmax_element(curveX_.begin(), curveX_.end());
        const auto yMM = std::minmax_element(curveY_.begin(), curveY_.end());
        minP           = QPointF(*xMM.first, *yMM.first);
        maxP           = QPointF(*xMM.second, *yMM.second);
    }
    return QRectF(minP, maxP);
}

void MvQFeatureCurveBaseItem::parallelCurves(const std::vector<double>& px, const std::vector<double>& py, QVector<QPointF>& pLeft, QVector<QPointF>& pRight, float distLeft, float distRight) const
{
    if (px.size() < 2) {
        return;
    }

    // add vertexes as (x, y, bulge)
    cavc::Polyline<double> pp;
    double eps = 1E-5;
    for (size_t i = 0; i < px.size(); i++) {
        if (pp.size() == 0 || fabs(px[i] - pp.lastVertex().x()) > eps ||
            fabs(py[i] - pp.lastVertex().y()) > eps) {
            pp.addVertex(px[i], py[i], 0);
        }
    }

    if (pp.size() < 2) {
        return;
    }

    pp.isClosed() = false;

    // left offset polyline
    auto res = cavc::parallelOffset(pp, static_cast<double>(distLeft));
    for (auto p : res) {
        for (std::size_t i = 0; i < p.size(); i++) {
            pLeft << QPointF(p[i].x(), p[i].y());
        }
    }

    // right offset polyline
    res = cavc::parallelOffset(pp, -static_cast<double>(distRight));
    for (auto p : res) {
        for (std::size_t i = 0; i < p.size(); i++) {
            pRight << QPointF(p[i].x(), p[i].y());
        }
    }
}

void MvQFeatureCurveBaseItem::buildShape()
{
    auto d = 8;  //symbolWidth_ + 4;
    if (points_.size() >= 1) {
        buildShapeCore(curveX_, curveY_, d, d);
    }
}

void MvQFeatureCurveBaseItem::buildShapeCore(const std::vector<double>& px, const std::vector<double>& py, float distLeft, float distRight)
{
    bShape_ = QPainterPath();
    QVector<QPointF> pp1;
    QVector<QPointF> pp2;
    parallelCurves(px, py, pp1, pp2, distLeft, distRight);

    if (isFilled()) {
        if (!pp1.isEmpty() && !pp2.isEmpty()) {
            QPolygonF pg1(pp1);
            if (pg1.contains(pp2[0])) {
                bShape_.addPolygon(pp1);
                return;
            }
            else {
                bShape_.addPolygon(pp2);
                return;
            }
        }
    }
    else if (!pp1.isEmpty()) {
        for (int i = pp2.size() - 1; i >= 0; i--) {
            pp1 << pp2[i];
        }
        bShape_.addPolygon(pp1);
    }
}

void MvQFeatureCurveBaseItem::generateCurvePath()
{
    if (curvePath_.isEmpty()) {
        QPolygonF p;
        if (curveX_.size() > 0) {
            for (size_t i = 0; i < curveX_.size(); i++) {
                p << QPointF(curveX_[i], curveY_[i]);
            }
        }
        else {
            for (auto pp : points_) {
                p << pp->pos();
            }
        }

        if (!closed_) {
            // Adjust line size according to arrow size
            startPointShape_.adjustLineSize(p, true);
            endPointShape_.adjustLineSize(p, false);
        }

        // with Qt 6.2 (on MacOS) a straight line is not always rendered correctly. So we add the
        // last point again to the curve to turn it into a polyline comprising 3 point!!!
        // This trick results in a correctly rendered line
        if (!closed_ && maxPoints_ == 2 && p.size() == 2) {
            p.push_back(p.back());
            p.push_back(p.back());
        }

        curvePath_.addPolygon(p);

        if (closed_) {
            curvePath_.closeSubpath();
        }
    }
}

bool MvQFeatureCurveBaseItem::getRequestParameters()
{
    if (!req_)
        return false;

    // Expand defaults
    expandStyleRequest();

    // Set line parameters
    std::string val;
    req_.getValue("LINE", val, true);
    if (val == "OFF") {
        linePen_ = QPen(Qt::NoPen);
    }
    else {
        linePen_ = MvQ::makePen((const char*)req_("LINE_STYLE"),
                                (int)req_("LINE_THICKNESS"),
                                (const char*)req_("LINE_COLOUR"),
                                Qt::FlatCap, Qt::MiterJoin);
    }

    bool oriFilled    = isFilled();
    bool arrowChanged = false;
    if (closed_) {
        // Get other parameters
        req_.getValue("FILL", val);
        if (val == "OFF") {
            fillBrush_ = QBrush(Qt::NoBrush);
        }
        else {
            fillBrush_ = MvQ::makeBrush("SOLID",
                                        (const char*)req_("FILL_COLOUR"));
        }
    }
    else {
        // Set endpoint parameters
        double angle = (double)req_("ARROW_ANGLE");
        double len   = (double)req_("ARROW_LENGTH");

        req_.getValue("LINE_START_TYPE", val);
        bool changedStart = startPointShape_.update(val, angle, len);

        req_.getValue("LINE_END_TYPE", val);
        bool changedEnd = endPointShape_.update(val, angle, len);

        arrowChanged = (changedStart || changedEnd);

        //        MvLog().dbg() << " changedStart=" << changedStart <<
        //                         " changedEnd=" << changedEnd << " arrowChanged=" << arrowChanged;
    }

    if (isFilled() != oriFilled) {
        buildShape();
    }

    if (arrowChanged) {
        // will be rebuilt in paint automatically
        curvePath_ = QPainterPath();
        buildArrow();
    }

    return true;
}

bool MvQFeatureCurveBaseItem::isFilled() const
{
    return closed_ && (fillBrush_.style() != Qt::NoBrush);
}

void MvQFeatureCurveBaseItem::resize(QRectF targetRectInParent)
{
    if (points_.size() <= 0)
        return;

#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeaturePolylineItem::resize";
    qDebug() << " targetRectInParent:" << targetRectInParent;
#endif
    // the input rect is in parent coordinates. It defines the desired
    // boundingRect size
    QRectF targetRect = mapFromParent(targetRectInParent).boundingRect();

#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << " targetRect(0):" << targetRect;
#endif

    // reduce the targetRect with the halo. We want to fit the itemRect into it!!!
    targetRect.adjust(halo() + bRectMargin_.x(), halo() + bRectMargin_.y(),
                      -halo() - bRectMargin_.x(), -halo() - bRectMargin_.y());

#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << " targetRect(1):" << targetRect;
    qDebug() << "itemRect:" << itemRect_ << "bRect:" << bRect_ << "bRectMargin:" << bRectMargin_;
#endif

    // further reduce the targetRect taking into account the curve shape
    auto dTL = itemRect_.topLeft() - curveRect_.topLeft();
    auto dBR = itemRect_.bottomRight() - curveRect_.bottomRight();
    targetRect.adjust(dTL.x(), dTL.y(), dBR.x(), dBR.y());

#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << " targetRect(2):" << targetRect;
    qDebug() << "itemRect:" << itemRect_ << "bRect:" << bRect_ << " splineRect:" << curveRect_;
#endif

    if (!targetRect.isValid()) {
        return;
    }

    // we compute the displacement for each point in each direction
    QList<QPointF> delta;
    for (int i = 0; i < points_.size(); i++) {
        delta << QPointF(0., 0.);
    }

    Q_ASSERT(points_.size() == delta.size());

    // the points themselves define the item rect
    //QRectF pRect = bRect_;
    QRectF pRect = itemRect_;

    // horizontal resize

    // find resize center position
    float dLeft  = targetRect.left() - itemRect_.left();
    float dRight = targetRect.right() - itemRect_.right();
    float dTotal = fabs(dLeft) + fabs(dRight);

    if (dTotal > 1E-4) {
        float rcX = itemRect_.center().x() + fabs(dLeft / dTotal) * pRect.width() / 2. -
                    fabs(dRight / dTotal) * pRect.width() / 2.;

        float leftW  = rcX - pRect.x();
        float rightW = pRect.width() - leftW;
        if (leftW < 0.)
            leftW = 0.;
        if (rightW < 0.)
            rightW = 0.;

        for (int i = 0; i < points_.size(); i++) {
            float dd = 0.;
            if (points_[i]->pos().x() < rcX) {
                if (leftW > 1E-5)
                    dd = dLeft * (rcX - points_[i]->pos().x()) / leftW;
                else
                    dd = 0.;
            }
            else {
                if (rightW > 1E-5)
                    dd = dRight * (points_[i]->pos().x() - rcX) / rightW;
                else
                    dd = 0.;
            }

            delta[i].setX(dd);
        }
    }

    // vertical resize

    // find resize center position
    float dTop    = targetRect.top() - itemRect_.top();
    float dBottom = targetRect.bottom() - itemRect_.bottom();
    dTotal        = fabs(dTop) + fabs(dBottom);

    if (dTotal > 1E-4) {
        float rcY = itemRect_.center().y() + fabs(dTop / dTotal) * pRect.height() / 2. -
                    fabs(dBottom / dTotal) * pRect.height() / 2.;

        float topH    = rcY - pRect.y();
        float bottomH = pRect.height() - topH;
        if (topH < 0.)
            topH = 0;
        if (bottomH < 0.)
            bottomH = 0.;

        for (int i = 0; i < points_.size(); i++) {
            float dd = 0.;
            if (points_[i]->pos().y() < rcY) {
                if (topH > 1E-5)
                    dd = dTop * (rcY - points_[i]->pos().y()) / topH;
                else
                    dd = 0.;
            }
            else {
                if (bottomH > 1E-5)
                    dd = dBottom * (points_[i]->pos().y() - rcY) / bottomH;
                else
                    dd = 0.;
            }

            delta[i].setY(dd);

#ifdef MVQFEATURECURVE_DEBUG_
            qDebug() << " " << i << "pos:" << points_[i]->pos() << "delta(y):" << dd;
#endif
        }
    }

    adjustPointsInResize(delta);

#if 0
    // moves the origo to the new position of the first point
    QPointF scPos0 = mapToScene(points_[0]->pos() + delta[0]);
    QPointF pos0= parentItem()->mapFromScene(scPos0);
    setPos(pos0);

    // the first point's postion is the origo
    points_[0]->setPos(QPointF(0.,0.));

    // adjust the positions of the rest of the points
    for(int i=1; i < points_.size(); i++) {
       points_[i]->setPos(points_[i]->pos() + delta[i] - delta[0]);
    }

    adjustPointPositions();

    for (int i = 0; i < points_.size(); i++) {
        points_[i]->adjustGeoCoord();
    }
#endif
    updateRect();

    //    targetRect = mapFromParent(targetRectInParent).boundingRect();
    //    float eps = 1E-6;
    //    Q_ASSERT(fabs(bRect_.x() - targetRect.x()) < eps);
    //    Q_ASSERT(fabs(bRect_.y() - targetRect.y()) < eps);
    //    Q_ASSERT(fabs(bRect_.width() - targetRect.width()) < eps);
    //    Q_ASSERT(fabs(bRect_.height() - targetRect.height()) < eps);

    adjustBRect();

#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << " -> targetRectInParent" << targetRectInParent << "itemRect(parent):" << mapToParent(itemRect_).boundingRect() << "bRect:" << mapToParent(bRect_).boundingRect();
#endif

    adjustSelectorIfNeeded();
}

void MvQFeatureCurveBaseItem::resizeTo(const MvQFeatureGeometry& geom)
{
    setPos(geom.pos_);
    // the first point's postion is the origo
    points_[0]->setPos(QPointF(0., 0.));

    // setthe positions of the rest of the points
    for (int i = 1; i < points_.size(); i++) {
        points_[i]->setPos(mapFromScene(geom.pointsScenePos_[i]));
    }

    for (auto& point : points_) {
        point->adjustGeoCoord();
    }

    updateRect();
    adjustBRect();
}

void MvQFeatureCurveBaseItem::adjustPointsInResize(QList<QPointF> delta)
{
    // moves the origo to the new position of the first point
    QPointF scPos0 = mapToScene(points_[0]->pos() + delta[0]);
    QPointF pos0   = parentItem()->mapFromScene(scPos0);
    setPos(pos0);

    // the first point's postion is the origo
    points_[0]->setPos(QPointF(0., 0.));

    // adjust the positions of the rest of the points
    for (int i = 1; i < points_.size(); i++) {
        points_[i]->setPos(points_[i]->pos() + delta[i] - delta[0]);
    }

    for (auto& point : points_) {
        point->adjustGeoCoord();
    }
}

void MvQFeatureCurveBaseItem::updateRect()
{
    prepareGeometryChange();
    itemRect_ = pointBoundingBox();

    buildCurve();
    buildShape();
    buildArrow();
    curveRect_ = curveBoundingBox();

    bRectMargin_ = QPointF(0, 0);
    //    double mSize =  8 + 2*halo();

    //    if (itemRect_.width() < mSize) {
    //        bRectMargin_.setX(0.01 + (mSize - itemRect_.width())/2.);
    //    }
    //    if (itemRect_.height() < mSize) {
    //       bRectMargin_.setY(0.01 + (mSize- itemRect_.height())/2.);
    //    }
    //    bRect_ = itemRect_;

    bRect_ = curveRect_;
}

void MvQFeatureCurveBaseItem::adjustBRect()
{
    if (points_.size() < 2) {
        return;
    }

    prepareGeometryChange();
    bRect_ = curveRect_.adjusted(-halo(), -halo(), halo(), halo());

    // adjust brect with arrow shapes
    if (!closed_) {
        auto r = startPointShape_.bRect(linePen_.width()).united(endPointShape_.bRect(linePen_.width()));
        if (!r.isEmpty()) {
            bRect_ = bRect_.united(r);
        }
    }
}

void MvQFeatureCurveBaseItem::buildArrow()
{
    if (!closed_) {
        if (curveX_.size() >= 2) {
            QPointF p1, p2;
            p1 = QPointF(curveX_[0], curveY_[0]);
            p2 = QPointF(curveX_[1], curveY_[1]);
            startPointShape_.build(p1, p2);

            p1 = QPointF(curveX_[curveX_.size() - 1], curveY_[curveY_.size() - 1]);
            p2 = QPointF(curveX_[curveX_.size() - 2], curveY_[curveY_.size() - 2]);
            endPointShape_.build(p1, p2);
        }
        else {
            startPointShape_.clearShape();
            endPointShape_.clearShape();
        }
    }
}

QStringList MvQFeatureCurveBaseItem::disabledCommands(int nodeIdx) const
{
    QStringList lst;
    if (nodeIdx != -1 && points_.size() <= 2) {
        lst << "delete";
    }
    return lst;
}

QString MvQFeatureCurveBaseItem::describe() const
{
    QString txt = MvQFeatureItem::describe();
    for (auto point : points_) {
        txt += point->describe();
    }
    return txt;
}

QList<QPointF> MvQFeatureCurveBaseItem::geoCoords() const
{
    QList<QPointF> lst;
    for (auto p : points_) {
        lst << p->geoCoord();
    }
    return lst;
}

//========================================
//
// MvQFeaturePolylineItem
//
//========================================

void MvQFeaturePolylineItem::buildCurve()
{
    curveX_.clear();
    curveY_.clear();
    curvePath_ = QPainterPath();

    if (points_.size() >= 2) {
        for (auto p : points_) {
            curveX_.push_back(p->pos().x());
            curveY_.push_back(p->pos().y());
        }
    }

    Q_ASSERT(curveX_.size() == curveY_.size());
}

QPointF MvQFeaturePolylineItem::extrapolatePoint(bool before) const
{
    if (points_.size() >= 2) {
        if (before) {
            int idx = 0;
            return points_[idx]->pos() + (points_[idx]->pos() - points_[idx + 1]->pos()) / 2.;
        }
        else {
            int idx = points_.size() - 1;
            return points_[idx]->pos() + (points_[idx]->pos() - points_[idx - 1]->pos()) / 2.;
        }
    }
    return {};
}


QPointF MvQFeaturePolylineItem::midPoint(int idx1, int idx2) const
{
    return (points_[idx1]->pos() + points_[idx2]->pos()) / 2.;
}

double MvQFeaturePolylineItem::halo() const
{
    auto w = linePen_.width();
    return (w <= 1) ? 0 : 1 + w / 2;
}

QPointF MvQFeaturePolylineItem::computeBRectMargin(const QRectF& itemRect) const
{
    double mSize = 8 + 2 * halo();
    QPointF r;
    if (itemRect.width() < mSize) {
        r.setX(mSize - itemRect.width());
    }
    if (itemRect.height() < mSize) {
        r.setY(mSize - itemRect.height());
    }
    return r;
}

QSizeF MvQFeaturePolylineItem::minSize() const
{
    return QSizeF(2 * halo() + 8, 2 * halo() + 8);
}

//========================================
//
// MvQFeaturePolylineLineItem
//
//========================================

MvQFeaturePolylineLineItem::MvQFeaturePolylineLineItem(WsType* feature) :
    MvQFeaturePolylineItem(feature, false)
{
}

//========================================
//
// MvQFeatureLineItem
//
//========================================

MvQFeatureLineItem::MvQFeatureLineItem(WsType* feature) :
    MvQFeaturePolylineLineItem(feature)
{
    maxPoints_ = 2;
}

//========================================
//
// MvQFeatureClosedPolylineItem
//
//========================================

MvQFeatureClosedPolylineItem::MvQFeatureClosedPolylineItem(WsType* feature) :
    MvQFeaturePolylineItem(feature, true)
{
}

//========================================
//
// MvQFeatureGeoPolylineItem
//
//========================================

void MvQFeatureGeoPolylineItem::init(const QJsonObject& obj)
{
    MvQFeatureCurveBaseItem::init(obj);

    WsDecoder::styleValue(obj, "SEGMENT_DATELINE_CROSS", segmentAcrossDateLine_);
    WsDecoder::styleValue(obj, "SEGMENT_DX", segmentDx_);

    //    MvLog().dbg() << "dateLine=" << segmentAcrossDateLine_;
    //    MvLog().dbg() << "dx=" << segmentDx_;

    if (static_cast<int>(segmentAcrossDateLine_.size()) != points_.size() ||
        static_cast<int>(segmentDx_.size()) != points_.size()) {
        segmentAcrossDateLine_.clear();
        segmentDx_.clear();
    }

    assert(segmentAcrossDateLine_.size() == segmentDx_.size());
}

void MvQFeatureGeoPolylineItem::toJson(QJsonObject& obj)
{
    MvQFeatureCurveBaseItem::toJson(obj);
    WsDecoder::setStyleValue(obj, "SEGMENT_DATELINE_CROSS", segmentAcrossDateLine_);
    WsDecoder::setStyleValue(obj, "SEGMENT_DX", segmentDx_);
}

void MvQFeatureGeoPolylineItem::initAtGeoCoord(QPointF geoCoord)
{
    geoCoord_ = geoCoord;
    reproject(true);
    updateRect();
    adjustBRect();
}

// Recompute the position of the item using the current geoCoord
void MvQFeatureGeoPolylineItem::reproject(bool /*init*/)
{
    if (!dataLayout_) {
        return;
    }

    QPointF scPos;
    dataLayout_->mapFromGeoToSceneCoords(geoCoord_, scPos);
    setPos(parentItem()->mapFromScene(scPos));

    if (points_.count() == 0) {
        createStart();
    }
    else {
        // reproject all the control points
        auto delta = geoCoord_ - points_[0]->geoCoord_;
        for (int i = 0; i < points_.count(); i++) {
            //points_[i]->adjustProjection();
            points_[i]->geoCoord_ += delta;
            points_[i]->reproject(false);
        }
    }
#ifdef MVQFEATURECURVE_DEBUG_
//    for(int i=0; i < points_.size(); i++) {
//        qDebug() << " " << i << "pos:" << points_[i]->pos() << "geocoord:" << points_[i]->geoCoord();
//    }
#endif

    updateRect();
    adjustBRect();
}

void MvQFeatureGeoPolylineItem::adjustGeoShapeToMove(QPointF delta, QList<QPointF>& gc)
{
    // The idea is that we compute the displacement for the centre

    // compute centre in scene pos
    QPointF sc(0, 0);
    for (auto p : points_) {
        sc += mapToScene(p->pos());
    }
    sc /= points_.size();
    QPointF gc1 = computeGeoCoord(sc);

    // add delta
    sc          = parentItem()->mapToScene(parentItem()->mapFromScene(sc) + delta);
    QPointF gc2 = computeGeoCoord(sc);

    // get geo delta
    auto geoDelta = gc2 - gc1;

    for (auto p : points_) {
        gc << p->geoCoord_ + geoDelta;
    }
    checkGeoRange(gc);

#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "delta:" << delta << "geoDelta:" << geoDelta;
    for (int i = 0; i < gc.size(); i++) {
        qDebug() << " " << i << "gc:" << gc[i] << "geoCoord" << points_[i]->geoCoord_;
    }
#endif
}


void MvQFeatureGeoPolylineItem::adjustGeoShapeToDelta(QList<QPointF> delta, QList<QPointF>& gc)
{
    // compute the new geocoord for each point
    for (int i = 0; i < points_.size(); i++) {
        QPointF scPos = mapToScene(points_[i]->pos() + delta[i]);
        QPointF gcPos;
        dataLayout_->mapFromSceneToGeoCoords(scPos, gcPos);
        gc << gcPos;
    }
}

bool MvQFeatureGeoPolylineItem::adjustGeoShape(MvQFeatureCurvePointItem* p, QList<QPointF>& gc)
{
    // At this stage point p has a new pos() but its geocoord is not yet adjusted. The other
    // points still have their old positions and geoords. The pos() of the item is not changed either.
    //
    for (auto cp : points_) {
        gc << cp->geoCoord_;
    }

    for (int i = 0; i < points_.size(); i++) {
        if (points_[i] == p) {
            gc[i] = p->computeGeoCoord();
#ifdef MVQFEATURECURVE_DEBUG_
            //            qDebug() << "POINT: " << points_[i]->geoCoord_ << " ->" << gc[i];
#endif
            break;
        }
    }

    return (p == points_[0]);
}

// delta is in parent coordinates
void MvQFeatureGeoPolylineItem::moveBy(QPointF delta)
{
    if (points_.size() < 1 || parentItem() == nullptr || dataLayout_ == nullptr) {
        return;
    }

#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeatureGeoPolylineItem::moveBy";
#endif

    prepareGeometryChange();
#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << " pos:" << pos() << "geoCoord[0]:" << points_[0]->geoCoord_;
    qDebug() << " geocoords:";
    for (int i = 0; i < points_.size(); i++)
        qDebug() << "  " << i << points_[i]->geoCoord_;
#endif

    QList<QPointF> gc, sc;
    adjustGeoShapeToMove(delta, gc);

    // Compute the positions
    for (auto& i : gc) {
        QPointF scPos;
        dataLayout_->mapFromGeoToSceneCoords(i, scPos);
        // if any of the points is invalid we reset to the original shape
        if (!isValidPoint(scPos)) {
            return;
        }
        else {
            sc << scPos;
        }
    }

    Q_ASSERT(sc.size() == gc.size());

    // handle the firts point. If it changes the items pos has to be changed!
    auto pos1 = parentItem()->mapFromScene(sc[0]);
    setPos(pos1);
    adjustGeoCoord();

    // the first point's postion is the origo
    points_[0]->setPos(QPointF(0., 0.));
    points_[0]->geoCoord_ = gc[0];

    for (int i = 1; i < points_.count(); i++) {
        auto p = mapFromScene(sc[i]);
        points_[i]->setPos(p);
        points_[i]->geoCoord_ = gc[i];
    }

    updateRect();
    adjustBRect();
    selector_->adjustToItems();
}

void MvQFeatureGeoPolylineItem::moveTo(const MvQFeatureGeometry& g)
{
    if (g.pointsGeoCoord_.size() > 0) {
        initAtGeoCoord(g.pointsGeoCoord_[0]);
    }
    else {
        initAtGeoCoord(g.geoCoord_);
    }
}

void MvQFeatureGeoPolylineItem::pointMoved(MvQFeatureCurvePointItem* p)
{
#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeatureGeoPolylineItem::pointMoved";
#endif
    QPointF pGc = p->computeGeoCoord();

    // if the new geoocord is invalid we move back the point to the previous pos
    if (!isValidPoint(pGc)) {
#ifdef MVQFEATURECURVE_DEBUG_
        qDebug() << " -> invalid new geoccord for p";
#endif
        if (p == points_[0]) {
            p->setPos(QPointF(0, 0));
        }
        else {
            p->reproject(false);
        }
        return;
    }

    // Compute the new geoocords of the points (not yet set on the points). The only
    // change at this stage is that of p->pos() (p was dragged by the user).
    QList<QPointF> gc;
    bool firstChanged = adjustGeoShape(p, gc);

    // make sure the other points do not stay in hover mode
    adjustHover(p);

    // Compute the pos
    QList<QPointF> sc;
    for (int i = 0; i < points_.count(); i++) {
        QPointF scPos;
        dataLayout_->mapFromGeoToSceneCoords(gc[i], scPos);
        // if any of the points is invalid we reset to the original shape
        if (!isValidPoint(scPos)) {
            if (p == points_[0]) {
                p->setPos(QPointF(0, 0));
            }
            else {
                p->reproject(false);
            }
            return;
        }
        else {
            sc << scPos;
        }
    }

#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << " pos:" << pos();
    for (int i = 0; i < sc.size(); i++) {
        qDebug() << "  " << i << "sc:" << sc[i];
    }
#endif
    Q_ASSERT(gc.size() == sc.size());

    // handle the firts point. If it changes the items pos has to be changed!
    if (firstChanged || p == points_[0]) {
        auto pos1 = parentItem()->mapFromScene(sc[0]);
        setPos(pos1);
        adjustGeoCoord();
        // the first point's postion is the origo
        points_[0]->setPos(QPointF(0., 0.));
        points_[0]->geoCoord_ = gc[0];
    }

    for (int i = 1; i < points_.count(); i++) {
        auto p = mapFromScene(sc[i]);
        points_[i]->setPos(p);
        points_[i]->geoCoord_ = gc[i];
    }

#ifdef MVQFEATURECURVE_DEBUG_
    for (int i = 0; i < points_.size(); i++) {
        qDebug() << "  " << i << "pos:" << points_[i]->pos() << "gc:" << points_[i]->geoCoord();
    }
#endif
    updateRect();
    adjustBRect();
}


void MvQFeatureGeoPolylineItem::adjustPointsInResize(QList<QPointF> delta)
{
    if (!dataLayout_ || points_.size() == 0) {
        return;
    }

    // adjust the geocoords to the desired shape
    QList<QPointF> gc;
    adjustGeoShapeToDelta(delta, gc);

    // if any of the geoords is invalid we discard the changes
    for (auto v : gc) {
        if (!isValidPoint(v)) {
            return;
        }
    }

    // compute the scene pos
    QList<QPointF> sc;
    for (int i = 0; i < points_.count(); i++) {
        QPointF scPos;
        dataLayout_->mapFromGeoToSceneCoords(gc[i], scPos);
        if (!isValidPoint(scPos)) {
            return;
        }
        sc << scPos;
    }

    // at this point all the geo and scene coords are valid

    // set the first point and the pos
    points_[0]->setPos(QPointF(0, 0));
    points_[0]->geoCoord_ = gc[0];
    setPos(parentItem()->mapFromScene(sc[0]));
    adjustGeoCoord();

    // determine the other points' positions
    for (int i = 1; i < points_.count(); i++) {
        points_[i]->setPos(mapFromScene(sc[i]));
        points_[i]->geoCoord_ = gc[i];
    }
}

void MvQFeatureGeoPolylineItem::prepareForPointRemove(int idx)
{
    if (idx >= 0 && idx < static_cast<int>(segmentDx_.size())) {
        segmentDx_.erase(segmentDx_.begin() + idx);
        segmentAcrossDateLine_.erase(segmentAcrossDateLine_.begin() + idx);
        Q_ASSERT(segmentDx_.size() == segmentAcrossDateLine_.size());
    }
}

void MvQFeatureGeoPolylineItem::buildCurve()
{
    curveX_.clear();
    curveY_.clear();
    curvePath_ = QPainterPath();

    if (points_.size() >= 2) {
        for (int i = 0; i < points_.size(); i++) {
            int actIdx  = i;
            int nextIdx = (i < points_.size() - 1) ? (i + 1) : 0;
            curveX_.push_back(points_[actIdx]->pos().x());
            curveY_.push_back(points_[actIdx]->pos().y());
#ifdef MVQFEATURECURVE_DEBUG_
//            qDebug() << "actIdx:" << actIdx << "x:" << curveX_.back() << "y:" << curveY_.back();
#endif
            if (dataLayout_ && parentItem() && (closed_ || nextIdx != 0)) {
                auto c1 = points_[actIdx]->geoCoord();
                auto c2 = points_[nextIdx]->geoCoord();
#ifdef MVQFEATURECURVE_DEBUG_
//                qDebug() << " c1:" << c1 << "c2:" << c2;
#endif
                checkDateLineCrossing(c1, c2, i);

                auto dx = c2.x() - c1.x();
                auto dy = c2.y() - c1.y();
                auto ds = sqrt(pow(dx, 2) + pow(dy, 2));
                int num = (std::max(0, static_cast<int>(ds / latlonIncr_)));
#ifdef MVQFEATURECURVE_DEBUG_
//                qDebug() << " c1:" << c1 << "c2:" << c2 << "pos:"<< points_[i]->pos() << "dx:" << dx << "dy:" << dy << "num:" << num;
#endif
                if (num > 0) {
                    double delta = 1. / static_cast<double>(num);
                    for (double t = delta; t < 1. - 0.5 * delta; t += delta) {
                        auto xp = c1.x() * (1. - t) + c2.x() * t;
                        auto yp = c1.y() * (1. - t) + c2.y() * t;
                        QPointF c(xp, yp);
                        QPointF scPos;
                        dataLayout_->mapFromGeoToSceneCoords(c, scPos);
                        auto p = mapFromScene(scPos);
                        curveX_.push_back(p.x());
                        curveY_.push_back(p.y());
                    }
                }
            }
        }
    }
#ifdef MVQFEATURECURVE_DEBUG_
//    qDebug() << "MvQFeatureGeoPolylineItem::buildCurve --> size=" << curveX_.size();

//    for (size_t i=0; i < curveX_.size() ; i++) {
//        qDebug() << curveX_[i] << curveY_[i];
//    }
#endif
    Q_ASSERT(curveX_.size() == curveY_.size());
}

void MvQFeatureGeoPolylineItem::checkDateLineCrossing(QPointF& c1, QPointF& c2, int idx)
{
    auto dx = c2.x() - c1.x();

    if (projPeriodic_) {
#ifdef MVQFEATURECURVE_DEBUG_
        qDebug() << idx << " dx:" << dx << "lon1:" << c1.x() << "lon2:" << c2.x();
#endif
        if (static_cast<int>(segmentDx_.size()) > idx) {
#ifdef MVQFEATURECURVE_DEBUG_
            qDebug() << "  segmentAcrossDateLine:" << segmentAcrossDateLine_[idx] << "segementDx:" << segmentDx_[idx];
#endif
            // previously we crossed the dateline
            if (segmentAcrossDateLine_[idx]) {
                if (dx * segmentDx_[idx] > 0) {
                    segmentAcrossDateLine_[idx] = false;
                }
                // previously we did not cross the dateline
            }
            else {
                if (c1.x() * c2.x() < 0) {
                    if (dx * segmentDx_[idx] < 0) {
                        segmentAcrossDateLine_[idx] = true;
                    }
                }
                else if (c1.x() > 0 && c2.x() > 0) {
                    if (dx * segmentDx_[idx] < 0 && fabs(segmentDx_[idx]) > 180) {
                        segmentAcrossDateLine_[idx] = true;
                    }
                }
                else {
                    if (dx * segmentDx_[idx] < 0 && fabs(segmentDx_[idx]) > 180) {
                        segmentAcrossDateLine_[idx] = true;
                    }
                }
            }
        }
        else {
            segmentDx_.push_back(dx);
            bool dateLine = (fabs(dx) > 179 && c1.x() * c2.x() < 0);
            segmentAcrossDateLine_.push_back(dateLine);
        }
#ifdef MVQFEATURECURVE_DEBUG_
        qDebug() << "  -> segmentAcrossDateLine:" << segmentAcrossDateLine_[idx];
#endif

        if (segmentAcrossDateLine_[idx]) {
            if (c1.x() >= 0 && c2.x() <= 0) {
                c2.setX(c2.x() + 360);
            }
            else if (c1.x() <= 0 && c2.x() >= 0) {
                c1.setX(c1.x() + 360);
            }
            else if (c1.x() >= 0 && c2.x() >= 0) {
                if (c1.x() < c2.x()) {
                    c1.setX(c1.x() + 360);
                }
                else {
                    c2.setX(c2.x() + 360);
                }
            }
            else if (c1.x() <= 0 && c2.x() <= 0) {
                if (c1.x() < c2.x()) {
                    c1.setX(c1.x() + 360);
                }
                else {
                    c2.setX(c2.x() + 360);
                }
            }
#ifdef MVQFEATURECURVE_DEBUG_
            qDebug() << "  -> segmentDx:" << segmentDx_[idx];
#endif
        }

        dx              = c2.x() - c1.x();
        segmentDx_[idx] = dx;

#ifdef MVQFEATURECURVE_DEBUG_
        qDebug() << "  -> dx: " << dx << "lat1:" << c1.x() << "lat2:" << c2.x();
#endif
    }
    else {
        if (static_cast<int>(segmentDx_.size()) > idx) {
            segmentDx_[idx]             = dx;
            segmentAcrossDateLine_[idx] = false;
        }
        else {
            segmentDx_.push_back(dx);
            segmentAcrossDateLine_.push_back(false);
        }
    }
}


void MvQFeatureGeoPolylineItem::checkGeoRange(QList<QPointF>& gc)
{
    if (projPeriodic_) {
        for (auto& i : gc) {
            if (i.x() < -180) {
                i.setX(i.x() + 360);
            }
            else if (i.x() > 180) {
                i.setX(i.x() - 360);
            }
        }
    }
}

//========================================
//
// MvQFeatureGeoPolylineLineItem
//
//========================================

MvQFeatureGeoPolylineLineItem::MvQFeatureGeoPolylineLineItem(WsType* feature) :
    MvQFeatureGeoPolylineItem(feature, false)
{
}

//========================================
//
// MvQFeatureClosedGeoPolylineItem
//
//========================================

MvQFeatureClosedGeoPolylineItem::MvQFeatureClosedGeoPolylineItem(WsType* feature) :
    MvQFeatureGeoPolylineItem(feature, true)
{
}

//========================================
//
// MvQFeatureGeoLineItem
//
//========================================

MvQFeatureGeoLineItem::MvQFeatureGeoLineItem(WsType* feature) :
    MvQFeatureGeoPolylineLineItem(feature)
{
    maxPoints_ = 2;
}

//========================================
//
// MvQFeatureGeoQuadItem
//
//========================================

MvQFeatureGeoQuadItem::MvQFeatureGeoQuadItem(WsType* feature) :
    MvQFeatureGeoPolylineItem(feature, true)
{
    maxPoints_ = 4;
}

void MvQFeatureGeoQuadItem::createStart()
{
    Q_ASSERT(points_.empty());
    if (!points_.empty())
        return;

#if 0
    // initial size in degrees
    double dx = 20., dy =20.;
    // determine the size in degrees. The quad must fit into the layout.
    QPointF c2 = geoCoord_ + QPointF(dx, -dy);
    // first point view coordinate in pixels
    QPointF vPos1 = plotView_->mapFromScene(parentItem()->mapToScene(pos()));
    // size of the plot layout in pixels
    auto vSize = plotView_->mapFromScene(dataLayout_->mapRectToScene(dataLayout_->boundingRect())).boundingRect().size();
    const int maxIter = 8;
    for (int i=0; i < maxIter; i++) {
        // second point view coordinate in pixels
        QPointF vPos2;
        dataLayout_->mapFromGeoToSceneCoords(c2, vPos2);
        vPos2 = plotView_->mapFromScene(vPos2);
        auto w = fabs(vPos1.x() - vPos2.x());
        auto h = fabs(vPos1.y() - vPos2.y());
        if (w > vSize.width()/4. || h > vSize.height()/4.) {
            dx *= 0.7;
            dy *= 0.7;
            c2 = geoCoord_ + QPointF(dx, -dy);
        }
    }
#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeatureGeoQuadItem::createStart" << "dx:" << dx << "dy:" << dy;
#endif
#endif

    for (int i = 0; i < 4; i++) {
        auto p       = new MvQFeatureCurvePointItem(this);
        p->geoCoord_ = geoCoord_;
        p->setPos(QPointF(0, 0));
        points_ << p;
    }

    for (auto cp : points_) {
        if (cp != points_[2]) {
            cp->setVisible(false);
            cp->resetHover();
        }
    }

    // the user grabs p2 automatically
    points_[2]->enterCreateMode();
}

void MvQFeatureGeoQuadItem::adjustGeoShapeToDelta(QList<QPointF> delta, QList<QPointF>& gc)
{
    int dMaxIndex = 0;
    float dMax    = -1;
    // compute the new geocoord for each point
    for (int i = 0; i < points_.size(); i++) {
        float d = pow(delta[i].x(), 2) + pow(delta[i].y(), 2);
        if (d > dMax) {
            dMax      = d;
            dMaxIndex = i;
        }
        QPointF scPos = mapToScene(points_[i]->pos() + delta[i]);
        QPointF gcPos;
        gcPos = points_[i]->computeGeoCoord(scPos);
        //dataLayout_->mapFromSceneToGeoCoords(scPos, gcPos);
        gc << gcPos;
    }

    // perform the adjustemnt
    if (dMaxIndex == 0) {
        gc[1] = QPointF(gc[1].x(), gc[0].y());
        gc[3] = QPointF(gc[0].x(), gc[3].y());
        gc[2] = QPointF(gc[1].x(), gc[3].y());
    }
    else if (dMaxIndex == 1) {
        gc[0] = QPointF(gc[0].x(), gc[1].y());
        gc[2] = QPointF(gc[1].x(), gc[2].y());
        gc[3] = QPointF(gc[0].x(), gc[2].y());
    }
    else if (dMaxIndex == 2) {
        gc[1] = QPointF(gc[2].x(), gc[1].y());
        gc[3] = QPointF(gc[3].x(), gc[2].y());
        gc[0] = QPointF(gc[3].x(), gc[1].y());
    }
    else if (dMaxIndex == 3) {
        gc[0] = QPointF(gc[3].x(), gc[0].y());
        gc[2] = QPointF(gc[2].x(), gc[3].y());
        gc[1] = QPointF(gc[2].x(), gc[0].y());
    }
    checkGeoRange(gc);
}


bool MvQFeatureGeoQuadItem::adjustGeoShape(MvQFeatureCurvePointItem* p, QList<QPointF>& gc)
{
    Q_ASSERT(points_.size() == 4);

    // At this stage point p has a new pos() but its geocoord is not yet adjusted. The other
    // points still have their old positions and geoords. The pos() of the item is not changed either.
    //

    for (auto cp : points_) {
        gc << cp->geoCoord_;
    }

    // figure out which point was moved
    int idx = -1;

    for (int i = 0; i < points_.size(); i++) {
        if (points_[i] == p) {
            gc[i] = points_[i]->computeGeoCoord();
            idx   = i;
            break;
        }
    }

#ifdef MVQFEATURECURVE_DEBUG_
    qDebug() << "MvQFeatureGeoQuadItem::adjustQuad"
             << "idx:" << idx;
    qDebug() << " before:";
    for (int i = 0; i < gc.size(); i++)
        qDebug() << " " << i << gc[i];
#endif
    bool firstChanged = false;

    if (idx == 0) {
        gc[1] = QPointF(gc[1].x(), gc[0].y());
        gc[3] = QPointF(gc[0].x(), gc[3].y());
    }
    else if (idx == 1) {
        firstChanged = true;
        gc[0]        = QPointF(gc[0].x(), gc[1].y());
        gc[2]        = QPointF(gc[1].x(), gc[2].y());
    }
    else if (idx == 2) {
        gc[3] = QPointF(gc[3].x(), gc[2].y());
        gc[1] = QPointF(gc[2].x(), gc[1].y());
    }
    else if (idx == 3) {
        firstChanged = true;
        gc[2]        = QPointF(gc[2].x(), gc[3].y());
        gc[0]        = QPointF(gc[3].x(), gc[0].y());
    }
//    // generic case, used for resize
//    } else {
//        gc[1] = QPointF(gc[1].x(), gc[0].y());
//        gc[2] = QPointF(gc[1].x(), gc[2].y());
//        gc[3] = QPointF(gc[0].x(), gc[2].y());
//    }
#ifdef MVQFEATURECURVE_DEBUG_
    checkGeoRange(gc);
    qDebug() << " after:";
    for (int i = 0; i < gc.size(); i++)
        qDebug() << " " << i << gc[i];
#endif
    return firstChanged;
}

void MvQFeatureGeoQuadItem::adjustSelectorIfNeeded() const
{
    selector_->adjustToItems();
}

//========================================
//
// MvQFeatureCurveItem
//
//========================================

MvQFeatureCurveItem::MvQFeatureCurveItem(WsType* feature, bool closed) :
    MvQFeatureCurveBaseItem(feature, closed)
{
}

void MvQFeatureCurveItem::buildCurve()
{
    curveX_.clear();
    curveY_.clear();
    curvePath_ = QPainterPath();

    if (points_.size() >= 2) {
        std::vector<double> xp, yp;
        std::vector<int> sampleNum;

        if (closed_) {
            int last = points_.size() - 1;
            xp.push_back(points_[last]->pos().x());
            yp.push_back(points_[last]->pos().y());
            auto ds = sqrt(pow(points_[0]->pos().x() - points_[last]->pos().x(), 2) +
                           pow(points_[0]->pos().y() - points_[last]->pos().y(), 2));
            sampleNum.push_back(std::max(5, static_cast<int>(ds / sampleDs_)));
#ifdef MVQFEATURECURVE_DEBUG_
            //                qDebug() << "sampleNum" << sampleNum.back();
#endif
        }

        for (int i = 0; i < points_.size(); i++) {
            xp.push_back(points_[i]->pos().x());
            yp.push_back(points_[i]->pos().y());
            if (i < points_.size() - 1) {
                auto ds = sqrt(pow(points_[i + 1]->pos().x() - points_[i]->pos().x(), 2) +
                               pow(points_[i + 1]->pos().y() - points_[i]->pos().y(), 2));
                sampleNum.push_back(std::max(5, static_cast<int>(ds / sampleDs_)));
#ifdef MVQFEATURECURVE_DEBUG_
                //qDebug() << "sampleNum" << sampleNum.back();
#endif
            }
        }

        if (closed_) {
            int last = points_.size() - 1;
            xp.push_back(points_[0]->pos().x());
            yp.push_back(points_[0]->pos().y());
            auto ds = sqrt(pow(points_[0]->pos().x() - points_[last]->pos().x(), 2) +
                           pow(points_[0]->pos().y() - points_[last]->pos().y(), 2));
            sampleNum.push_back(std::max(5, static_cast<int>(ds / sampleDs_)));
#ifdef MVQFEATURECURVE_DEBUG_
            //                qDebug() << "sampleNum" << sampleNum.back();
#endif

            xp.push_back(points_[1]->pos().x());
            yp.push_back(points_[1]->pos().y());
        }

        spline_ = SplineParam(xp, yp);

        size_t start = 0;
        size_t num   = xp.size() - 1;
        if (closed_) {
            start = 1;
            num -= 1;
        }

        Q_ASSERT(num <= sampleNum.size());

        for (size_t i = start; i < num; i++) {
            for (int j = 0; j < sampleNum[i]; j++) {
                double t = static_cast<double>(i) + static_cast<double>(j) / static_cast<double>(sampleNum[i]);
                double x, y;
                spline_.eval(t, x, y);
                curveX_.push_back(x);
                curveY_.push_back(y);
            }
        }

        // add last point
        if (!closed_) {
            curveX_.push_back(points_.back()->pos().x());
            curveY_.push_back(points_.back()->pos().y());
        }
#ifdef MVQFEATURECURVE_DEBUG_
//        qDebug() << "first" << splineX_[0] << splineY_[0] << points_.first()->pos();
//        qDebug() << "last" << splineX_.back()<< splineY_.back() << points_.back()->pos();
#endif
    }

    Q_ASSERT(curveX_.size() == curveY_.size());
}

QPointF MvQFeatureCurveItem::extrapolatePoint(bool before) const
{
    if (points_.size() >= 2) {
        if (before) {
            int idx  = 0;
            double t = 0.2;
            double x, y;
            spline_.eval(t, x, y);
            return points_[idx]->pos() + 2. * (points_[idx]->pos() - QPointF(x, y));
        }
        else {
            int idx  = points_.size() - 1;
            double t = static_cast<double>(idx) - 0.2;
            double x, y;
            spline_.eval(t, x, y);
            return points_[idx]->pos() + 2. * (points_[idx]->pos() - QPointF(x, y));
        }
    }
    return {};
}

QPointF MvQFeatureCurveItem::midPoint(int idx1, int idx2) const
{
    double t = 0.;
    double x, y;
    if (closed_) {
        // for closed splines for the first point t=1 !!

        // midpoint before the first point
        if (idx1 == 0 && idx2 == points_.size() - 1) {
            t = 0.5;
            // midpoint after the last point
        }
        else if (idx1 == points_.size() - 1 && idx2 == 0) {
            t = static_cast<double>(idx1) + 1.5;
            // standard midpoint
        }
        else {
            t = 1. + (static_cast<double>(idx1) + static_cast<double>(idx2)) / 2.;
        }
    }
    else {
        t = (static_cast<double>(idx1) + static_cast<double>(idx2)) / 2.;
    }

    spline_.eval(t, x, y);
    return QPointF(x, y);
}

double MvQFeatureCurveItem::halo() const
{
    auto w    = linePen_.width();
    double dw = (w <= 1) ? 0 : 1 + w / 2;
    return std::max(dw, symbolSize_.width() / 2.);
}

QSizeF MvQFeatureCurveItem::minSize() const
{
    QSize r(2 * halo() + 8, 2 * halo() + 8);
    auto dTL = itemRect_.topLeft() - curveRect_.topLeft();
    auto dBR = itemRect_.bottomRight() - curveRect_.bottomRight();
    r += QSize(fabs(dTL.x()) + fabs(dBR.x()), fabs(dTL.y()) + fabs(dBR.y()));
    return r;
}

//========================================
//
// MvQFeatureCurveLineItem
//
//========================================

MvQFeatureCurveLineItem::MvQFeatureCurveLineItem(WsType* feature) :
    MvQFeatureCurveItem(feature, false)
{
}

//========================================
//
// MvQFeatureClosedCurveItem
//
//========================================

MvQFeatureClosedCurveItem::MvQFeatureClosedCurveItem(WsType* feature) :
    MvQFeatureCurveItem(feature, true)
{
}

static MvQFeatureMaker<MvQFeatureLineItem> lineMaker("line");
static MvQFeatureMaker<MvQFeaturePolylineLineItem> polylineMaker("polyline");
static MvQFeatureMaker<MvQFeatureClosedPolylineItem> polygonMaker("polygon");
static MvQFeatureMaker<MvQFeatureCurveLineItem> curveMaker("curve");
static MvQFeatureMaker<MvQFeatureClosedCurveItem> closedCurveMaker("closed_curve");

static MvQFeatureMaker<MvQFeatureGeoPolylineLineItem> geoPolylineMaker("geo_polyline");
static MvQFeatureMaker<MvQFeatureClosedGeoPolylineItem> geoPolygoneMaker("geo_polygon");
static MvQFeatureMaker<MvQFeatureGeoLineItem> geoLineMaker("geo_line");
static MvQFeatureMaker<MvQFeatureGeoQuadItem> quadMaker("geo_quad");
