// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2008 Torsten Rahn <rahn@kde.org>
// SPDX-FileCopyrightText: 2009 Patrick Spendrin <ps_ml@gmx.de>
//

#include "GeoDataLineString.h"
#include "GeoDataLineString_p.h"

#include "GeoDataLinearRing.h"
#include "GeoDataTypes.h"
#include "MarbleDebug.h"
#include "Quaternion.h"

#include <QDataStream>
#include <QVariant>

namespace Marble
{
GeoDataLineString::GeoDataLineString(TessellationFlags f)
    : GeoDataGeometry(new GeoDataLineStringPrivate(f))
{
    //    mDebug() << "1) GeoDataLineString created:" << p();
}

GeoDataLineString::GeoDataLineString(GeoDataLineStringPrivate *priv)
    : GeoDataGeometry(priv)
{
    //    mDebug() << "2) GeoDataLineString created:" << p();
}

GeoDataLineString::GeoDataLineString(const GeoDataGeometry &other)
    : GeoDataGeometry(other)
{
    //    mDebug() << "3) GeoDataLineString created:" << p();
}

GeoDataLineString::~GeoDataLineString()
{
#ifdef DEBUG_GEODATA
    mDebug() << "delete Linestring";
#endif
}

const char *GeoDataLineString::nodeType() const
{
    return GeoDataTypes::GeoDataLineStringType;
}

EnumGeometryId GeoDataLineString::geometryId() const
{
    return GeoDataLineStringId;
}

GeoDataGeometry *GeoDataLineString::copy() const
{
    return new GeoDataLineString(*this);
}

void GeoDataLineStringPrivate::interpolateDateLine(const GeoDataCoordinates &previousCoords,
                                                   const GeoDataCoordinates &currentCoords,
                                                   GeoDataCoordinates &previousAtDateLine,
                                                   GeoDataCoordinates &currentAtDateLine,
                                                   TessellationFlags f) const
{
    GeoDataCoordinates dateLineCoords;

    //    mDebug();

    if (f.testFlag(RespectLatitudeCircle) && previousCoords.latitude() == currentCoords.latitude()) {
        dateLineCoords = currentCoords;
    } else {
        int recursionCounter = 0;
        dateLineCoords = findDateLine(previousCoords, currentCoords, recursionCounter);
    }

    previousAtDateLine = dateLineCoords;
    currentAtDateLine = dateLineCoords;

    if (previousCoords.longitude() < 0) {
        previousAtDateLine.setLongitude(-M_PI);
        currentAtDateLine.setLongitude(+M_PI);
    } else {
        previousAtDateLine.setLongitude(+M_PI);
        currentAtDateLine.setLongitude(-M_PI);
    }
}

GeoDataCoordinates
GeoDataLineStringPrivate::findDateLine(const GeoDataCoordinates &previousCoords, const GeoDataCoordinates &currentCoords, int recursionCounter) const
{
    int currentSign = (currentCoords.longitude() < 0.0) ? -1 : +1;
    int previousSign = (previousCoords.longitude() < 0.0) ? -1 : +1;

    qreal longitudeDiff = fabs(previousSign * M_PI - previousCoords.longitude()) + fabs(currentSign * M_PI - currentCoords.longitude());

    if (longitudeDiff < 0.001 || recursionCounter == 100) {
        //        mDebug() << "stopped at recursion" << recursionCounter << " and longitude difference " << longitudeDiff;
        return currentCoords;
    }
    ++recursionCounter;

    const GeoDataCoordinates interpolatedCoords = previousCoords.nlerp(currentCoords, 0.5);

    int interpolatedSign = (interpolatedCoords.longitude() < 0.0) ? -1 : +1;

    /*
        mDebug() << "SRC" << previousCoords.toString();
        mDebug() << "TAR" << currentCoords.toString();
        mDebug() << "IPC" << interpolatedCoords.toString();
    */

    if (interpolatedSign != currentSign) {
        return findDateLine(interpolatedCoords, currentCoords, recursionCounter);
    }

    return findDateLine(previousCoords, interpolatedCoords, recursionCounter);
}

quint8 GeoDataLineStringPrivate::levelForResolution(qreal resolution) const
{
    if (m_previousResolution == resolution)
        return m_level;

    m_previousResolution = resolution;

    if (resolution < 0.0000005)
        m_level = 17;
    else if (resolution < 0.0000010)
        m_level = 16;
    else if (resolution < 0.0000020)
        m_level = 15;
    else if (resolution < 0.0000040)
        m_level = 14;
    else if (resolution < 0.0000080)
        m_level = 13;
    else if (resolution < 0.0000160)
        m_level = 12;
    else if (resolution < 0.0000320)
        m_level = 11;
    else if (resolution < 0.0000640)
        m_level = 10;
    else if (resolution < 0.0001280)
        m_level = 9;
    else if (resolution < 0.0002560)
        m_level = 8;
    else if (resolution < 0.0005120)
        m_level = 7;
    else if (resolution < 0.0010240)
        m_level = 6;
    else if (resolution < 0.0020480)
        m_level = 5;
    else if (resolution < 0.0040960)
        m_level = 4;
    else if (resolution < 0.0081920)
        m_level = 3;
    else if (resolution < 0.0163840)
        m_level = 2;
    else
        m_level = 1;

    return m_level;
}

qreal GeoDataLineStringPrivate::resolutionForLevel(int level)
{
    switch (level) {
    case 0:
        return 0.0655360;
    case 1:
        return 0.0327680;
    case 2:
        return 0.0163840;
    case 3:
        return 0.0081920;
    case 4:
        return 0.0040960;
    case 5:
        return 0.0020480;
    case 6:
        return 0.0010240;
    case 7:
        return 0.0005120;
    case 8:
        return 0.0002560;
    case 9:
        return 0.0001280;
    case 10:
        return 0.0000640;
    case 11:
        return 0.0000320;
    case 12:
        return 0.0000160;
    case 13:
        return 0.0000080;
    case 14:
        return 0.0000040;
    case 15:
        return 0.0000020;
    case 16:
        return 0.0000010;
    default:
    case 17:
        return 0.0000005;
    }
}

void GeoDataLineStringPrivate::optimize(GeoDataLineString &lineString) const
{
    QList<GeoDataCoordinates>::iterator itCoords = lineString.begin();
    QList<GeoDataCoordinates>::const_iterator itEnd = lineString.constEnd();

    if (lineString.size() < 2)
        return;

    // Calculate the least non-zero detail-level by checking the bounding box
    quint8 startLevel = levelForResolution((lineString.latLonAltBox().width() + lineString.latLonAltBox().height()) / 2);

    quint8 currentLevel = startLevel;
    quint8 maxLevel = startLevel;
    GeoDataCoordinates currentCoords;
    lineString.first().setDetail(startLevel);

    // Iterate through the linestring to assign different detail levels to the nodes.
    // In general the first and last node should have the start level assigned as
    // a detail level.
    // Starting from the first node the algorithm picks those nodes which
    // have a distance from each other that is just above the resolution that is
    // associated with the start level (which we use as a "current level").
    // Each of those nodes get the current level assigned as the detail level.
    // After iterating through the linestring we increment the current level value
    // and starting again with the first node we assign detail values in a similar way
    // to the remaining nodes which have no final detail level assigned yet.
    // We do as many iterations through the lineString as needed and bump up the
    // current level until all nodes have a non-zero detail level assigned.

    while (currentLevel < 16 && currentLevel <= maxLevel + 1) {
        itCoords = lineString.begin();

        currentCoords = *itCoords;
        ++itCoords;

        for (; itCoords != itEnd; ++itCoords) {
            if (itCoords->detail() != 0 && itCoords->detail() < currentLevel)
                continue;

            if (currentLevel == startLevel
                && (itCoords->longitude() == -M_PI || itCoords->longitude() == M_PI || itCoords->latitude() < -89 * DEG2RAD
                    || itCoords->latitude() > 89 * DEG2RAD)) {
                itCoords->setDetail(startLevel);
                currentCoords = *itCoords;
                maxLevel = currentLevel;
                continue;
            }
            if (currentCoords.sphericalDistanceTo(*itCoords) < resolutionForLevel(currentLevel + 1)) {
                itCoords->setDetail(currentLevel + 1);
            } else {
                itCoords->setDetail(currentLevel);
                currentCoords = *itCoords;
                maxLevel = currentLevel;
            }
        }
        ++currentLevel;
    }
    lineString.last().setDetail(startLevel);
}

bool GeoDataLineString::isEmpty() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.isEmpty();
}

int GeoDataLineString::size() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.size();
}

GeoDataCoordinates &GeoDataLineString::at(int pos)
{
    detach();

    Q_D(GeoDataLineString);
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    return d->m_vector[pos];
}

const GeoDataCoordinates &GeoDataLineString::at(int pos) const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.at(pos);
}

GeoDataCoordinates &GeoDataLineString::operator[](int pos)
{
    detach();

    Q_D(GeoDataLineString);
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    return d->m_vector[pos];
}

GeoDataLineString GeoDataLineString::mid(int pos, int length) const
{
    GeoDataLineString substring;
    auto d = substring.d_func();
    d->m_vector = d_func()->m_vector.mid(pos, length);
    d->m_dirtyBox = true;
    d->m_dirtyRange = true;
    d->m_tessellationFlags = d_func()->m_tessellationFlags;
    d->m_extrude = d_func()->m_extrude;
    return substring;
}

const GeoDataCoordinates &GeoDataLineString::operator[](int pos) const
{
    Q_D(const GeoDataLineString);
    return d->m_vector[pos];
}

GeoDataCoordinates &GeoDataLineString::last()
{
    detach();

    Q_D(GeoDataLineString);
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    return d->m_vector.last();
}

GeoDataCoordinates &GeoDataLineString::first()
{
    detach();

    Q_D(GeoDataLineString);
    return d->m_vector.first();
}

const GeoDataCoordinates &GeoDataLineString::last() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.last();
}

const GeoDataCoordinates &GeoDataLineString::first() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.first();
}

QList<GeoDataCoordinates>::Iterator GeoDataLineString::begin()
{
    detach();

    Q_D(GeoDataLineString);
    return d->m_vector.begin();
}

QList<GeoDataCoordinates>::ConstIterator GeoDataLineString::begin() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.constBegin();
}

QList<GeoDataCoordinates>::Iterator GeoDataLineString::end()
{
    detach();

    Q_D(GeoDataLineString);
    return d->m_vector.end();
}

QList<GeoDataCoordinates>::ConstIterator GeoDataLineString::end() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.constEnd();
}

QList<GeoDataCoordinates>::ConstIterator GeoDataLineString::constBegin() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.constBegin();
}

QList<GeoDataCoordinates>::ConstIterator GeoDataLineString::constEnd() const
{
    Q_D(const GeoDataLineString);
    return d->m_vector.constEnd();
}

void GeoDataLineString::insert(int index, const GeoDataCoordinates &value)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    d->m_vector.insert(index, value);
}

void GeoDataLineString::append(const GeoDataCoordinates &value)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    d->m_vector.append(value);
}

void GeoDataLineString::reserve(int size)
{
    Q_D(GeoDataLineString);
    d->m_vector.reserve(size);
}

void GeoDataLineString::append(const QList<GeoDataCoordinates> &values)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;

    d->m_vector.append(values);
}

GeoDataLineString &GeoDataLineString::operator<<(const GeoDataCoordinates &value)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    d->m_vector.append(value);
    return *this;
}

GeoDataLineString &GeoDataLineString::operator<<(const GeoDataLineString &value)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;

    QList<GeoDataCoordinates>::const_iterator itCoords = value.constBegin();
    QList<GeoDataCoordinates>::const_iterator itEnd = value.constEnd();

    d->m_vector.reserve(d->m_vector.size() + value.size());
    for (; itCoords != itEnd; ++itCoords) {
        d->m_vector.append(*itCoords);
    }

    return *this;
}

bool GeoDataLineString::operator==(const GeoDataLineString &other) const
{
    if (!GeoDataGeometry::equals(other) || size() != other.size() || tessellate() != other.tessellate()) {
        return false;
    }

    Q_D(const GeoDataLineString);
    const GeoDataLineStringPrivate *other_d = other.d_func();

    QList<GeoDataCoordinates>::const_iterator itCoords = d->m_vector.constBegin();
    QList<GeoDataCoordinates>::const_iterator otherItCoords = other_d->m_vector.constBegin();
    QList<GeoDataCoordinates>::const_iterator itEnd = d->m_vector.constEnd();
    QList<GeoDataCoordinates>::const_iterator otherItEnd = other_d->m_vector.constEnd();

    for (; itCoords != itEnd && otherItCoords != otherItEnd; ++itCoords, ++otherItCoords) {
        if (*itCoords != *otherItCoords) {
            return false;
        }
    }

    Q_ASSERT(itCoords == itEnd && otherItCoords == otherItEnd);
    return true;
}

bool GeoDataLineString::operator!=(const GeoDataLineString &other) const
{
    return !this->operator==(other);
}

void GeoDataLineString::clear()
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;

    d->m_vector.clear();
}

bool GeoDataLineString::isClosed() const
{
    return false;
}

bool GeoDataLineString::tessellate() const
{
    Q_D(const GeoDataLineString);
    return d->m_tessellationFlags.testFlag(Tessellate);
}

void GeoDataLineString::setTessellate(bool tessellate)
{
    detach();

    Q_D(GeoDataLineString);
    // According to the KML reference the tesselation of line strings in Google Earth
    // is generally done along great circles. However for subsequent points that share
    // the same latitude the latitude circles are followed. Our Tesselate and RespectLatitude
    // Flags provide this behaviour. For true polygons the latitude circles don't get considered.

    if (tessellate) {
        d->m_tessellationFlags |= (Tessellate | RespectLatitudeCircle);
    } else {
        d->m_tessellationFlags &= ~(Tessellate | RespectLatitudeCircle);
    }
}

TessellationFlags GeoDataLineString::tessellationFlags() const
{
    Q_D(const GeoDataLineString);
    return d->m_tessellationFlags;
}

void GeoDataLineString::setTessellationFlags(TessellationFlags f)
{
    detach();

    Q_D(GeoDataLineString);
    d->m_tessellationFlags = f;
}

void GeoDataLineString::reverse()
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    std::reverse(begin(), end());
}

GeoDataLineString GeoDataLineString::toNormalized() const
{
    Q_D(const GeoDataLineString);

    GeoDataLineString normalizedLineString;

    normalizedLineString.setTessellationFlags(tessellationFlags());

    qreal lon;
    qreal lat;

    // FIXME: Think about how we can avoid unnecessary copies
    //        if the linestring stays the same.
    QList<GeoDataCoordinates>::const_iterator end = d->m_vector.constEnd();
    for (QList<GeoDataCoordinates>::const_iterator itCoords = d->m_vector.constBegin(); itCoords != end; ++itCoords) {
        itCoords->geoCoordinates(lon, lat);
        qreal alt = itCoords->altitude();
        GeoDataCoordinates::normalizeLonLat(lon, lat);

        GeoDataCoordinates normalizedCoords(*itCoords);
        normalizedCoords.set(lon, lat, alt);
        normalizedLineString << normalizedCoords;
    }

    return normalizedLineString;
}

GeoDataLineString GeoDataLineString::toRangeCorrected() const
{
    Q_D(const GeoDataLineString);

    if (d->m_dirtyRange) {
        delete d->m_rangeCorrected;

        if (isClosed()) {
            d->m_rangeCorrected = new GeoDataLinearRing(toPoleCorrected());
        } else {
            d->m_rangeCorrected = new GeoDataLineString(toPoleCorrected());
        }
        d->m_dirtyRange = false;
    }

    return *d->m_rangeCorrected;
}

QList<GeoDataLineString *> GeoDataLineString::toDateLineCorrected() const
{
    Q_D(const GeoDataLineString);

    QList<GeoDataLineString *> lineStrings;

    d->toDateLineCorrected(*this, lineStrings);

    return lineStrings;
}

GeoDataLineString GeoDataLineString::toPoleCorrected() const
{
    Q_D(const GeoDataLineString);

    if (isClosed()) {
        GeoDataLinearRing poleCorrected;
        d->toPoleCorrected(*this, poleCorrected);
        return poleCorrected;
    } else {
        GeoDataLineString poleCorrected;
        d->toPoleCorrected(*this, poleCorrected);
        return poleCorrected;
    }
}

void GeoDataLineStringPrivate::toPoleCorrected(const GeoDataLineString &q, GeoDataLineString &poleCorrected) const
{
    poleCorrected.setTessellationFlags(q.tessellationFlags());

    GeoDataCoordinates previousCoords;
    GeoDataCoordinates currentCoords;

    if (q.isClosed()) {
        if (!(m_vector.first().isPole()) && (m_vector.last().isPole())) {
            qreal firstLongitude = (m_vector.first()).longitude();
            GeoDataCoordinates modifiedCoords(m_vector.last());
            modifiedCoords.setLongitude(firstLongitude);
            poleCorrected << modifiedCoords;
        }
    }

    QList<GeoDataCoordinates>::const_iterator itCoords = m_vector.constBegin();
    QList<GeoDataCoordinates>::const_iterator itEnd = m_vector.constEnd();

    for (; itCoords != itEnd; ++itCoords) {
        currentCoords = *itCoords;

        if (itCoords == m_vector.constBegin()) {
            previousCoords = currentCoords;
        }

        if (currentCoords.isPole()) {
            if (previousCoords.isPole()) {
                continue;
            } else {
                qreal previousLongitude = previousCoords.longitude();
                GeoDataCoordinates currentModifiedCoords(currentCoords);
                currentModifiedCoords.setLongitude(previousLongitude);
                poleCorrected << currentModifiedCoords;
            }
        } else {
            if (previousCoords.isPole()) {
                qreal currentLongitude = currentCoords.longitude();
                GeoDataCoordinates previousModifiedCoords(previousCoords);
                previousModifiedCoords.setLongitude(currentLongitude);
                poleCorrected << previousModifiedCoords;
                poleCorrected << currentCoords;
            } else {
                // No poles at all. Nothing special to handle
                poleCorrected << currentCoords;
            }
        }
        previousCoords = currentCoords;
    }

    if (q.isClosed()) {
        if ((m_vector.first().isPole()) && !(m_vector.last().isPole())) {
            qreal lastLongitude = (m_vector.last()).longitude();
            GeoDataCoordinates modifiedCoords(m_vector.first());
            modifiedCoords.setLongitude(lastLongitude);
            poleCorrected << modifiedCoords;
        }
    }
}

void GeoDataLineStringPrivate::toDateLineCorrected(const GeoDataLineString &q, QList<GeoDataLineString *> &lineStrings) const
{
    const bool isClosed = q.isClosed();

    const QList<GeoDataCoordinates>::const_iterator itStartPoint = q.constBegin();
    const QList<GeoDataCoordinates>::const_iterator itEndPoint = q.constEnd();
    QList<GeoDataCoordinates>::const_iterator itPoint = itStartPoint;
    QList<GeoDataCoordinates>::const_iterator itPreviousPoint = itPoint;

    TessellationFlags f = q.tessellationFlags();

    GeoDataLineString *unfinishedLineString = nullptr;

    GeoDataLineString *dateLineCorrected = isClosed ? new GeoDataLinearRing(f) : new GeoDataLineString(f);

    qreal previousLon = 0.0;
    int previousSign = 1;

    bool unfinished = false;

    for (; itPoint != itEndPoint; ++itPoint) {
        const qreal currentLon = itPoint->longitude();

        int currentSign = (currentLon < 0.0) ? -1 : +1;

        if (itPoint == q.constBegin()) {
            previousSign = currentSign;
            previousLon = currentLon;
        }

        // If we are crossing the date line ...
        if (previousSign != currentSign && fabs(previousLon) + fabs(currentLon) > M_PI) {
            unfinished = !unfinished;

            GeoDataCoordinates previousTemp;
            GeoDataCoordinates currentTemp;

            interpolateDateLine(*itPreviousPoint, *itPoint, previousTemp, currentTemp, q.tessellationFlags());

            *dateLineCorrected << previousTemp;

            if (isClosed && unfinished) {
                // If it's a linear ring and if it crossed the IDL only once then
                // store the current string inside the unfinishedLineString for later use ...
                unfinishedLineString = dateLineCorrected;
                // ... and start a new linear ring for now.
                dateLineCorrected = new GeoDataLinearRing(f);
            } else {
                // Now it can only be a (finished) line string or a finished linear ring.
                // Store it in the vector  if the size is not zero.
                if (dateLineCorrected->size() > 0) {
                    lineStrings << dateLineCorrected;
                } else {
                    // Or delete it.
                    delete dateLineCorrected;
                }

                // If it's a finished linear ring restore the "remembered" unfinished String
                if (isClosed && !unfinished && unfinishedLineString) {
                    dateLineCorrected = unfinishedLineString;
                } else {
                    // if it's a line string just create a new line string.
                    dateLineCorrected = new GeoDataLineString(f);
                }
            }

            *dateLineCorrected << currentTemp;
            *dateLineCorrected << *itPoint;

        } else {
            *dateLineCorrected << *itPoint;
        }

        previousSign = currentSign;
        previousLon = currentLon;
        itPreviousPoint = itPoint;
    }

    // If the line string doesn't cross the dateline an even number of times
    // then need to take care of the data stored in the unfinishedLineString
    if (unfinished && unfinishedLineString && !unfinishedLineString->isEmpty()) {
        *dateLineCorrected << *unfinishedLineString;
        delete unfinishedLineString;
    }

    lineStrings << dateLineCorrected;
}

const GeoDataLatLonAltBox &GeoDataLineString::latLonAltBox() const
{
    Q_D(const GeoDataLineString);

    // GeoDataLatLonAltBox::fromLineString is very expensive
    // that's why we recreate it only if the m_dirtyBox
    // is TRUE.
    // DO NOT REMOVE THIS CONSTRUCT OR MARBLE WILL BE SLOW.
    if (d->m_dirtyBox) {
        d->m_latLonAltBox = GeoDataLatLonAltBox::fromLineString(*this);
        d->m_dirtyBox = false;
    }

    return d->m_latLonAltBox;
}

qreal GeoDataLineString::length(qreal planetRadius, int offset) const
{
    if (offset < 0 || offset >= size()) {
        return 0;
    }

    Q_D(const GeoDataLineString);
    qreal length = 0.0;
    QList<GeoDataCoordinates> const &vector = d->m_vector;
    int const start = qMax(offset + 1, 1);
    int const end = d->m_vector.size();
    for (int i = start; i < end; ++i) {
        length += vector[i - 1].sphericalDistanceTo(vector[i]);
    }

    return planetRadius * length;
}

QList<GeoDataCoordinates>::Iterator GeoDataLineString::erase(const QList<GeoDataCoordinates>::Iterator &pos)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    return d->m_vector.erase(pos);
}

QList<GeoDataCoordinates>::Iterator GeoDataLineString::erase(const QList<GeoDataCoordinates>::Iterator &begin, const QList<GeoDataCoordinates>::Iterator &end)
{
    detach();

    Q_D(GeoDataLineString);
    delete d->m_rangeCorrected;
    d->m_rangeCorrected = nullptr;
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    return d->m_vector.erase(begin, end);
}

void GeoDataLineString::remove(int i)
{
    detach();

    Q_D(GeoDataLineString);
    d->m_dirtyRange = true;
    d->m_dirtyBox = true;
    d->m_vector.remove(i);
}

GeoDataLineString GeoDataLineString::optimized() const
{
    Q_D(const GeoDataLineString);

    if (isClosed()) {
        GeoDataLinearRing linearRing(*this);
        d->optimize(linearRing);
        return linearRing;
    } else {
        GeoDataLineString lineString(*this);
        d->optimize(lineString);
        return lineString;
    }
}

QVariantList GeoDataLineString::toVariantList() const
{
    Q_D(const GeoDataLineString);

    QVariantList variantList;
    for (const GeoDataCoordinates &itCoords : std::as_const(d->m_vector)) {
        QVariantMap map;
        map.insert(QLatin1StringView("lon"), itCoords.longitude(GeoDataCoordinates::Degree));
        map.insert(QLatin1StringView("lat"), itCoords.latitude(GeoDataCoordinates::Degree));
        map.insert(QLatin1StringView("alt"), itCoords.altitude());
        variantList << map;
    }

    if (isClosed()) {
        QVariantMap map;
        map.insert(QLatin1StringView("lon"), d->m_vector.first().longitude(GeoDataCoordinates::Degree));
        map.insert(QLatin1StringView("lat"), d->m_vector.first().latitude(GeoDataCoordinates::Degree));
        map.insert(QLatin1StringView("alt"), d->m_vector.first().altitude());
        variantList << map;
    }

    return variantList;
}

void GeoDataLineString::pack(QDataStream &stream) const
{
    Q_D(const GeoDataLineString);

    GeoDataGeometry::pack(stream);

    stream << size();
    stream << (qint32)(d->m_tessellationFlags);

    for (QList<GeoDataCoordinates>::const_iterator iterator = d->m_vector.constBegin(); iterator != d->m_vector.constEnd(); ++iterator) {
        mDebug() << "innerRing: size" << d->m_vector.size();
        GeoDataCoordinates coord = (*iterator);
        coord.pack(stream);
    }
}

void GeoDataLineString::unpack(QDataStream &stream)
{
    detach();

    Q_D(GeoDataLineString);

    GeoDataGeometry::unpack(stream);
    qint32 size;
    qint32 tessellationFlags;

    stream >> size;
    stream >> tessellationFlags;

    d->m_tessellationFlags = (TessellationFlags)(tessellationFlags);

    d->m_vector.reserve(d->m_vector.size() + size);

    for (qint32 i = 0; i < size; i++) {
        GeoDataCoordinates coord;
        coord.unpack(stream);
        d->m_vector.append(coord);
    }
}

}
