QVariant cameraInterpolator(const QGeoCameraData &start,
                            const QGeoCameraData &end,
                            qreal progress)
{
    QGeoCameraData result = start;
    QGeoCoordinate from = start.center();
    QGeoCoordinate to = end.center();

    if (from == to) {
        if (progress < 0.5) {
            result.setCenter(from);
        } else {
            result.setCenter(to);
        }
    }
    else {
        QGeoCoordinate coordinateResult = QGeoProjection::coordinateInterpolation(from, to, progress);
        result.setCenter(coordinateResult);
    }

    double sf = 1.0 - progress;
    double ef = progress;

    result.setBearing(sf * start.bearing() + ef * end.bearing());
    result.setTilt(sf * start.tilt() + ef * end.tilt());
    result.setRoll(sf * start.roll() + ef * end.roll());
    result.setZoomLevel(sf * start.zoomLevel() + ef * end.zoomLevel());

    return QVariant::fromValue(result);
}
void QGeoMapController::setZoom(qreal zoom)
{
    QGeoCameraData cd = map_->cameraData();

    if (zoom == cd.zoomLevel())
        return;

    cd.setZoomLevel(zoom);
    map_->setCameraData(cd);
}
void QGeoMapController::cameraDataChanged(const QGeoCameraData &cameraData)
{
    if (oldCameraData_.center() != cameraData.center())
        emit centerChanged(cameraData.center());

    if (oldCameraData_.bearing() != cameraData.bearing())
        emit bearingChanged(cameraData.bearing());

    if (oldCameraData_.tilt() != cameraData.tilt())
        emit tiltChanged(cameraData.tilt());

    if (oldCameraData_.roll() != cameraData.roll())
        emit rollChanged(cameraData.roll());

    if (oldCameraData_.zoomLevel() != cameraData.zoomLevel())
        emit zoomChanged(cameraData.zoomLevel());

    oldCameraData_ = cameraData;
}
void QGeoTiledMapDataPrivate::changeCameraData(const QGeoCameraData &oldCameraData)
{
    double lat = oldCameraData.center().latitude();

    if (mapScene_->verticalLock()) {
        QGeoCoordinate coord = map_->cameraData().center();
        coord.setLatitude(lat);
        map_->cameraData().setCenter(coord);
    }

    // For zoomlevel, "snap" 0.05 either side of a whole number.
    // This is so that when we turn off bilinear scaling, we're
    // snapped to the exact pixel size of the tiles
    QGeoCameraData cam = map_->cameraData();
    int izl = static_cast<int>(std::floor(cam.zoomLevel()));
    float delta = cam.zoomLevel() - izl;
    if (delta > 0.5) {
        izl++;
        delta -= 1.0;
    }
    if (qAbs(delta) < 0.05) {
        cam.setZoomLevel(izl);
    }

    cameraTiles_->setCamera(cam);

    mapScene_->setCameraData(cam);
    mapScene_->setVisibleTiles(cameraTiles_->tiles());

    if (tileRequests_) {
        // don't request tiles that are already built and textured
        QList<QSharedPointer<QGeoTileTexture> > cachedTiles =
                tileRequests_->requestTiles(cameraTiles_->tiles() - mapScene_->texturedTiles());

        foreach (const QSharedPointer<QGeoTileTexture> &tex, cachedTiles) {
            mapScene_->addTile(tex->spec, tex);
        }

        if (!cachedTiles.isEmpty())
            map_->update();
    }
Frustum QGeoCameraTilesPrivate::frustum(double fieldOfViewGradient) const
{
    QDoubleVector3D center = sideLength_ * QGeoProjection::coordToMercator(camera_.center());
    center.setZ(0.0);

    double f = qMin(screenSize_.width(), screenSize_.height()) / (1.0 * tileSize_);

    double z = std::pow(2.0, camera_.zoomLevel() - intZoomLevel_);

    double altitude = f / (2.0 * z);
    QDoubleVector3D eye = center;
    eye.setZ(altitude);

    QDoubleVector3D view = eye - center;
    QDoubleVector3D side = QDoubleVector3D::normal(view, QDoubleVector3D(0.0, 1.0, 0.0));
    QDoubleVector3D up = QDoubleVector3D::normal(side, view);

    double nearPlane = sideLength_ / (1.0 * tileSize_ * (1 << maxZoom_));
    double farPlane = 3.0;

    double aspectRatio = 1.0 * screenSize_.width() / screenSize_.height();

    double hn = 0.0;
    double wn = 0.0;
    double hf = 0.0;
    double wf = 0.0;

    // fixes field of view at 45 degrees
    // this assumes that viewSize = 2*nearPlane x 2*nearPlane

    if (aspectRatio > 1.0) {
        hn = 2 * fieldOfViewGradient * nearPlane;
        wn = hn * aspectRatio;

        hf = 2 * fieldOfViewGradient * farPlane;
        wf = hf * aspectRatio;
    } else {
        wn = 2 * fieldOfViewGradient * nearPlane;
        hn = wn / aspectRatio;

        wf = 2 * fieldOfViewGradient * farPlane;
        hf = wf / aspectRatio;
    }

    QDoubleVector3D d = center - eye;
    d.normalize();
    up.normalize();
    QDoubleVector3D right = QDoubleVector3D::normal(d, up);

    QDoubleVector3D cf = eye + d * farPlane;
    QDoubleVector3D cn = eye + d * nearPlane;

    Frustum frustum;

    frustum.topLeftFar = cf + (up * hf / 2) - (right * wf / 2);
    frustum.topRightFar = cf + (up * hf / 2) + (right * wf / 2);
    frustum.bottomLeftFar = cf - (up * hf / 2) - (right * wf / 2);
    frustum.bottomRightFar = cf - (up * hf / 2) + (right * wf / 2);

    frustum.topLeftNear = cn + (up * hn / 2) - (right * wn / 2);
    frustum.topRightNear = cn + (up * hn / 2) + (right * wn / 2);
    frustum.bottomLeftNear = cn - (up * hn / 2) - (right * wn / 2);
    frustum.bottomRightNear = cn - (up * hn / 2) + (right * wn / 2);

    return frustum;
}