/*!
    \internal
*/
void QDeclarativeGeoMapQuickItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map)
{
    QDeclarativeGeoMapItemBase::setMap(quickMap,map);
    if (map && quickMap) {
        QObject::connect(quickMap, SIGNAL(heightChanged()), this, SLOT(updateMapItem()));
        QObject::connect(quickMap, SIGNAL(widthChanged()), this, SLOT(updateMapItem()));
        QObject::connect(map, SIGNAL(cameraDataChanged(QGeoCameraData)), this, SLOT(updateMapItem()));
        updateMapItem();
    }
}
void QDeclarativePolylineMapItem::setPath(const QJSValue &value)
{
    if (!value.isArray())
        return;

    QList<QGeoCoordinate> pathList;
    quint32 length = value.property(QStringLiteral("length")).toUInt();
    for (quint32 i = 0; i < length; ++i) {
        bool ok;
        QGeoCoordinate c = parseCoordinate(value.property(i), &ok);

        if (!ok || !c.isValid()) {
            qmlInfo(this) << "Unsupported path type";
            return;
        }

        pathList.append(c);
    }

    if (path_ == pathList)
        return;

    path_ = pathList;

    geometry_.markSourceDirty();
    updateMapItem();
    emit pathChanged();
}
/*!
    \internal
*/
void QDeclarativeRectangleMapItem::dragEnded()
{
    QPointF newTopLeftPoint = QPointF(x(),y());
    QGeoCoordinate newTopLeft = map()->screenPositionToCoordinate(newTopLeftPoint, false);
    if (newTopLeft.isValid()) {
        // calculate new geo width while checking for dateline crossing
        const double lonW = bottomRight_.longitude() > topLeft_.longitude() ?
                    bottomRight_.longitude() - topLeft_.longitude() :
                    bottomRight_.longitude() + 360 - topLeft_.longitude();
        const double latH = qAbs(bottomRight_.latitude() - topLeft_.latitude());
        QGeoCoordinate newBottomRight;
        // prevent dragging over valid min and max latitudes
        if (QLocationUtils::isValidLat(newTopLeft.latitude() - latH)) {
            newBottomRight.setLatitude(newTopLeft.latitude() - latH);
        } else {
            newBottomRight.setLatitude(QLocationUtils::clipLat(newTopLeft.latitude() - latH));
            newTopLeft.setLatitude(newBottomRight.latitude() + latH);
        }
        // handle dateline crossing
        newBottomRight.setLongitude(QLocationUtils::wrapLong(newTopLeft.longitude() + lonW));
        newBottomRight.setAltitude(newTopLeft.altitude());
        topLeft_ = newTopLeft;
        bottomRight_ = newBottomRight;
        geometry_.setPreserveGeometry(true, newTopLeft);
        borderGeometry_.setPreserveGeometry(true, newTopLeft);
        geometry_.markSourceDirty();
        borderGeometry_.markSourceDirty();
        updateMapItem();
        emit topLeftChanged(topLeft_);
        emit bottomRightChanged(bottomRight_);
    }
}
/*!
    \internal
*/
void QDeclarativePolylineMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event)
{
    if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
        return;

    // if the scene is tilted, we must regenerate our geometry every frame
    if (map()->cameraCapabilities().supportsTilting()
            && (event.cameraData.tilt() > 0.1
                || event.cameraData.tilt() < -0.1)) {
        geometry_.markSourceDirty();
    }

    // if the scene is rolled, we must regen too
    if (map()->cameraCapabilities().supportsRolling()
            && (event.cameraData.roll() > 0.1
                || event.cameraData.roll() < -0.1)) {
        geometry_.markSourceDirty();
    }

    // otherwise, only regen on rotate, resize and zoom
    if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) {
        geometry_.markSourceDirty();
    }
    geometry_.setPreserveGeometry(true, geometry_.geoLeftBound());
    geometry_.markScreenDirty();
    updateMapItem();
}
void QDeclarativePolylineMapItem::addCoordinate(const QGeoCoordinate &coordinate)
{
    path_.append(coordinate);

    geometry_.markSourceDirty();
    updateMapItem();
    emit pathChanged();
}
/*!
    \internal
*/
void QDeclarativePolylineMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map)
{
    QDeclarativeGeoMapItemBase::setMap(quickMap,map);
    if (map) {
        geometry_.markSourceDirty();
        updateMapItem();
    }
}
void QDeclarativeRectangleMapItem::setColor(const QColor &color)
{
    if (color_ == color)
        return;
    color_ = color;
    dirtyMaterial_ = true;
    updateMapItem();
    emit colorChanged(color_);
}
/*!
    \qmlproperty object MapQuickItem::sourceItem

    This property holds the source item that will be drawn on the map.
*/
void QDeclarativeGeoMapQuickItem::setSourceItem(QQuickItem *sourceItem)
{
    if (sourceItem_.data() == sourceItem)
        return;
    sourceItem_ = sourceItem;

    updateMapItem();

    emit sourceItemChanged();
}
/*!
    \qmlproperty coordinate MapQuickItem::coordinate

    This property holds the anchor coordinate of the MapQuickItem. The point
    on the sourceItem that is specified by anchorPoint is kept aligned with
    this coordinate when drawn on the map.

    In the image below, there are 3 MapQuickItems that are identical except
    for the value of their anchorPoint properties. The values of anchorPoint
    for each are written on top of the item.

    \image api-mapquickitem-anchor.png
*/
void QDeclarativeGeoMapQuickItem::setCoordinate(const QGeoCoordinate &coordinate)
{
    if (coordinate_ == coordinate)
        return;

    coordinate_ = coordinate;

    updateMapItem();

    emit coordinateChanged();
}
/*!
    \qmlproperty real MapCircle::radius

    This property holds the radius of the circle, in meters on the ground.

    \sa center
*/
void QDeclarativeCircleMapItem::setRadius(qreal radius)
{
    if (radius_ == radius)
        return;

    radius_ = radius;
    geometry_.markSourceDirty();
    borderGeometry_.markSourceDirty();
    updateMapItem();
    emit radiusChanged(radius);
}
/*!
    \qmlproperty coordinate MapCircle::center

    This property holds the central point about which the circle is defined.

    \sa radius
*/
void QDeclarativeCircleMapItem::setCenter(const QGeoCoordinate &center)
{
    if (center_ == center)
        return;

    center_ = center;

    geometry_.markSourceDirty();
    borderGeometry_.markSourceDirty();
    updateMapItem();
    emit centerChanged(center_);
}
/*!
    \qmlproperty coordinate MapRectangle::bottomRight

    This property holds the bottom-right coordinate of the MapRectangle which
    can be used to retrieve its longitude, latitude and altitude.
*/
void QDeclarativeRectangleMapItem::setBottomRight(const QGeoCoordinate &bottomRight)
{
    if (bottomRight_ == bottomRight)
        return;

    bottomRight_ = bottomRight;

    geometry_.markSourceDirty();
    borderGeometry_.markSourceDirty();
    updateMapItem();
    emit bottomRightChanged(bottomRight_);
}
/*!
    \qmlproperty coordinate MapRectangle::topLeft

    This property holds the top-left coordinate of the MapRectangle which
    can be used to retrieve its longitude, latitude and altitude.
*/
void QDeclarativeRectangleMapItem::setTopLeft(const QGeoCoordinate &topLeft)
{
    if (topLeft_ == topLeft)
        return;

    topLeft_ = topLeft;

    geometry_.markSourceDirty();
    borderGeometry_.markSourceDirty();
    updateMapItem();
    emit topLeftChanged(topLeft_);
}
/*!
    \internal
*/
void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    if (updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) {
        QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry);
        return;
    }

    QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(geometry_.firstPointOffset());
    QGeoCoordinate newCoordinate = map()->screenPositionToCoordinate(newPoint, false);
    if (newCoordinate.isValid()) {
        double firstLongitude = path_.at(0).longitude();
        double firstLatitude = path_.at(0).latitude();
        double minMaxLatitude = firstLatitude;
        // prevent dragging over valid min and max latitudes
        for (int i = 0; i < path_.count(); ++i) {
            double newLatitude = path_.at(i).latitude()
                    + newCoordinate.latitude() - firstLatitude;
            if (!QLocationUtils::isValidLat(newLatitude)) {
                if (qAbs(newLatitude) > qAbs(minMaxLatitude)) {
                    minMaxLatitude = newLatitude;
                }
            }
        }
        // calculate offset needed to re-position the item within map border
        double offsetLatitude = minMaxLatitude - QLocationUtils::clipLat(minMaxLatitude);
        for (int i = 0; i < path_.count(); ++i) {
            QGeoCoordinate coord = path_.at(i);
            // handle dateline crossing
            coord.setLongitude(QLocationUtils::wrapLong(coord.longitude()
                               + newCoordinate.longitude() - firstLongitude));
            coord.setLatitude(coord.latitude()
                              + newCoordinate.latitude() - firstLatitude - offsetLatitude);
            path_.replace(i, coord);
        }

        QGeoCoordinate leftBoundCoord = geometry_.geoLeftBound();
        leftBoundCoord.setLongitude(QLocationUtils::wrapLong(leftBoundCoord.longitude()
                           + newCoordinate.longitude() - firstLongitude));
        geometry_.setPreserveGeometry(true, leftBoundCoord);
        geometry_.markSourceDirty();
        updateMapItem();
        emit pathChanged();
    }

    // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested
    // call to this function.
}
void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordinate)
{
    int index = path_.lastIndexOf(coordinate);

    if (index == -1) {
        qmlInfo(this) << QCoreApplication::translate(CONTEXT_NAME, COORD_NOT_BELONG_TO).arg("PolylineMapItem");
        return;
    }

    if (path_.count() < index + 1) {
        qmlInfo(this) << QCoreApplication::translate(CONTEXT_NAME, COORD_NOT_BELONG_TO).arg("PolylineMapItem");
        return;
    }
    path_.removeAt(index);

    geometry_.markSourceDirty();
    updateMapItem();
    emit pathChanged();
}
void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordinate)
{
    int index = path_.lastIndexOf(coordinate);

    if (index == -1) {
        qmlInfo(this) << COORD_NOT_BELONG_TO << QStringLiteral("PolylineMapItem");
        return;
    }

    if (path_.count() < index + 1) {
        qmlInfo(this) << COORD_NOT_BELONG_TO << QStringLiteral("PolylineMapItem");
        return;
    }
    path_.removeAt(index);

    geometry_.markSourceDirty();
    updateMapItem();
    emit pathChanged();
}
/*!
    \internal
*/
void QDeclarativePolygonMapItem::dragEnded()
{
    QPointF newPoint = QPointF(x(),y()) + geometry_.firstPointOffset();
    QGeoCoordinate newCoordinate = map()->screenPositionToCoordinate(newPoint, false);
    if (newCoordinate.isValid()) {
        double firstLongitude = path_.at(0).longitude();
        double firstLatitude = path_.at(0).latitude();
        double minMaxLatitude = firstLatitude;
        // prevent dragging over valid min and max latitudes
        for (int i = 0; i < path_.count(); ++i) {
            double newLatitude = path_.at(i).latitude()
                    + newCoordinate.latitude() - firstLatitude;
            if (!QLocationUtils::isValidLat(newLatitude)) {
                if (qAbs(newLatitude) > qAbs(minMaxLatitude)) {
                    minMaxLatitude = newLatitude;
                }
            }
        }
        // calculate offset needed to re-position the item within map border
        double offsetLatitude = minMaxLatitude - QLocationUtils::clipLat(minMaxLatitude);
        for (int i = 0; i < path_.count(); ++i) {
            QGeoCoordinate coord = path_.at(i);
            // handle dateline crossing
            coord.setLongitude(QLocationUtils::wrapLong(coord.longitude()
                               + newCoordinate.longitude() - firstLongitude));
            coord.setLatitude(coord.latitude()
                              + newCoordinate.latitude() - firstLatitude - offsetLatitude);

            path_.replace(i, coord);
        }

        QGeoCoordinate leftBoundCoord = geometry_.geoLeftBound();
        leftBoundCoord.setLongitude(QLocationUtils::wrapLong(leftBoundCoord.longitude()
                           + newCoordinate.longitude() - firstLongitude));
        geometry_.setPreserveGeometry(true, leftBoundCoord);
        borderGeometry_.setPreserveGeometry(true, leftBoundCoord);
        geometry_.markSourceDirty();
        borderGeometry_.markSourceDirty();
        updateMapItem();
        emit pathChanged();
    }
}
/*!
    \internal
*/
void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event)
{
    if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0)
        return;

    // if the scene is tilted, we must regenerate our geometry every frame
    if (map()->cameraCapabilities().supportsTilting()
            && (event.cameraData.tilt() > 0.1
                || event.cameraData.tilt() < -0.1)) {
        geometry_.markSourceDirty();
        borderGeometry_.markSourceDirty();
    }

    // if the scene is rolled, we must regen too
    if (map()->cameraCapabilities().supportsRolling()
            && (event.cameraData.roll() > 0.1
                || event.cameraData.roll() < -0.1)) {
        geometry_.markSourceDirty();
        borderGeometry_.markSourceDirty();
    }

    // otherwise, only regen on rotate, resize and zoom
    if (event.bearingChanged || event.mapSizeChanged || event.zoomLevelChanged) {
        geometry_.markSourceDirty();
        borderGeometry_.markSourceDirty();
    }

    if (event.centerChanged && crossEarthPole(center_, radius_)) {
        geometry_.markSourceDirty();
        borderGeometry_.markSourceDirty();
    }

    geometry_.markScreenDirty();
    borderGeometry_.markScreenDirty();
    updateMapItem();
}
/*!
    \internal
*/
void QDeclarativeCircleMapItem::dragStarted()
{
    geometry_.markFullScreenDirty();
    borderGeometry_.markFullScreenDirty();
    updateMapItem();
}
/*!
    \internal
*/
void QDeclarativePolygonMapItem::handleBorderUpdated()
{
    borderGeometry_.markSourceDirty();
    updateMapItem();
}
/*!
    \internal
*/
void QDeclarativeRectangleMapItem::updateMapItemAssumeDirty()
{
    geometry_.markSourceDirty();
    borderGeometry_.markSourceDirty();
    updateMapItem();
}
/*!
    \internal
*/
void QDeclarativeRectangleMapItem::dragStarted()
{
    borderGeometry_.markFullScreenDirty();
    updateMapItem();
}
/*!
    \internal
*/
void QDeclarativePolylineMapItem::dragStarted()
{
    geometry_.markFullScreenDirty();
    updateMapItem();
}
/*!
    \internal
*/
void QDeclarativePolylineMapItem::updateAfterLinePropertiesChanged()
{
    // mark dirty just in case we're a width change
    geometry_.markSourceDirty();
    updateMapItem();
}