TEST(Transform, Antimeridian) {
    Transform transform;
    transform.resize({ 1000, 1000 });

    transform.jumpTo(CameraOptions().withCenter(LatLng()).withZoom(1.0));

    // San Francisco
    const LatLng coordinateSanFrancisco { 37.7833, -122.4167 };
    ScreenCoordinate pixelSF = transform.latLngToScreenCoordinate(coordinateSanFrancisco);
    ASSERT_DOUBLE_EQ(151.79249437176432, pixelSF.x);
    ASSERT_DOUBLE_EQ(383.76720782527661, pixelSF.y);

    transform.jumpTo(CameraOptions().withCenter(LatLng { 0.0, -181.0 }));

    ScreenCoordinate pixelSFLongest = transform.latLngToScreenCoordinate(coordinateSanFrancisco);
    ASSERT_DOUBLE_EQ(-357.36306616412816, pixelSFLongest.x);
    ASSERT_DOUBLE_EQ(pixelSF.y, pixelSFLongest.y);
    LatLng unwrappedSF = coordinateSanFrancisco.wrapped();
    unwrappedSF.unwrapForShortestPath(transform.getLatLng());

    ScreenCoordinate pixelSFShortest = transform.latLngToScreenCoordinate(unwrappedSF);
    ASSERT_DOUBLE_EQ(666.63694385219173, pixelSFShortest.x);
    ASSERT_DOUBLE_EQ(pixelSF.y, pixelSFShortest.y);

    transform.jumpTo(CameraOptions().withCenter(LatLng { 0.0, 179.0 }));
    pixelSFShortest = transform.latLngToScreenCoordinate(coordinateSanFrancisco);
    ASSERT_DOUBLE_EQ(pixelSFLongest.x, pixelSFShortest.x);
    ASSERT_DOUBLE_EQ(pixelSFLongest.y, pixelSFShortest.y);

    // Waikiri
    const LatLng coordinateWaikiri{ -16.9310, 179.9787 };
    transform.jumpTo(CameraOptions().withCenter(coordinateWaikiri).withZoom(10.0));
    ScreenCoordinate pixelWaikiri = transform.latLngToScreenCoordinate(coordinateWaikiri);
    ASSERT_DOUBLE_EQ(500, pixelWaikiri.x);
    ASSERT_DOUBLE_EQ(500, pixelWaikiri.y);

    transform.jumpTo(CameraOptions().withCenter(LatLng { coordinateWaikiri.latitude(), 180.0213 }));
    ScreenCoordinate pixelWaikiriLongest = transform.latLngToScreenCoordinate(coordinateWaikiri);
    ASSERT_DOUBLE_EQ(524725.96438108233, pixelWaikiriLongest.x);
    ASSERT_DOUBLE_EQ(pixelWaikiri.y, pixelWaikiriLongest.y);

    LatLng unwrappedWaikiri = coordinateWaikiri.wrapped();
    unwrappedWaikiri.unwrapForShortestPath(transform.getLatLng());
    ScreenCoordinate pixelWaikiriShortest = transform.latLngToScreenCoordinate(unwrappedWaikiri);
    ASSERT_DOUBLE_EQ(437.95925272648344, pixelWaikiriShortest.x);
    ASSERT_DOUBLE_EQ(pixelWaikiri.y, pixelWaikiriShortest.y);

    LatLng coordinateFromPixel = transform.screenCoordinateToLatLng(pixelWaikiriLongest);
    ASSERT_NEAR(coordinateWaikiri.latitude(), coordinateFromPixel.latitude(), 1e-4);  
    ASSERT_NEAR(coordinateWaikiri.longitude(), coordinateFromPixel.longitude(), 1e-4);

    transform.jumpTo(CameraOptions().withCenter(LatLng { coordinateWaikiri.latitude(), 180.0213 }));
    pixelWaikiriShortest = transform.latLngToScreenCoordinate(coordinateWaikiri);
    ASSERT_DOUBLE_EQ(pixelWaikiriLongest.x, pixelWaikiriShortest.x);
    ASSERT_DOUBLE_EQ(pixelWaikiriLongest.y, pixelWaikiriShortest.y);

    coordinateFromPixel = transform.screenCoordinateToLatLng(pixelWaikiriShortest);
    ASSERT_NEAR(coordinateWaikiri.latitude(), coordinateFromPixel.latitude(), 1e-4);  
    ASSERT_NEAR(coordinateWaikiri.longitude(), coordinateFromPixel.longitude(), 1e-4);
}
ScreenCoordinate Transform::latLngToScreenCoordinate(const LatLng& latLng) const {
    // If the center and point longitudes are not in the same side of the
    // antimeridian, we unwrap the point longitude so it would be seen if
    // e.g. the next antimeridian side is visible.
    LatLng unwrappedLatLng = latLng.wrapped();
    unwrappedLatLng.unwrapForShortestPath(getLatLng());
    ScreenCoordinate point = state.latLngToScreenCoordinate(unwrappedLatLng);
    point.y = state.height - point.y;
    return point;
}
/**
 * Change any combination of center, zoom, bearing, and pitch, with a smooth animation
 * between old and new values. The map will retain the current values for any options
 * not included in `options`.
 */
void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& animation) {
    const LatLng unwrappedLatLng = camera.center.value_or(getLatLng());
    const LatLng latLng = unwrappedLatLng.wrapped();
    double zoom = camera.zoom.value_or(getZoom());
    double angle = camera.angle.value_or(getAngle());
    double pitch = camera.pitch.value_or(getPitch());

    if (!latLng || std::isnan(zoom)) {
        return;
    }

    // Determine endpoints.
    EdgeInsets padding;
    if (camera.padding) padding = *camera.padding;
    LatLng startLatLng = getLatLng(padding);
    // If gesture in progress, we transfer the world rounds from the end
    // longitude into start, so we can guarantee the "scroll effect" of rounding
    // the world while assuring the end longitude remains wrapped.
    if (isGestureInProgress()) startLatLng.longitude -= unwrappedLatLng.longitude - latLng.longitude;
    // Find the shortest path otherwise.
    else startLatLng.unwrapForShortestPath(latLng);

    const ScreenCoordinate startPoint = {
        state.lngX(startLatLng.longitude),
        state.latY(startLatLng.latitude),
    };
    const ScreenCoordinate endPoint = {
        state.lngX(latLng.longitude),
        state.latY(latLng.latitude),
    };
    ScreenCoordinate center = getScreenCoordinate(padding);
    center.y = state.height - center.y;
    
    // Constrain camera options.
    zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
    const double scale = state.zoomScale(zoom);
    pitch = util::clamp(pitch, 0., util::PITCH_MAX);
    
    Update update = state.getZoom() == zoom ? Update::Repaint : Update::RecalculateStyle;
    
    // Minimize rotation by taking the shorter path around the circle.
    angle = _normalizeAngle(angle, state.angle);
    state.angle = _normalizeAngle(state.angle, angle);

    Duration duration = animation.duration ? *animation.duration : Duration::zero();
    
    const double startWorldSize = state.worldSize();
    state.Bc = startWorldSize / util::DEGREES_MAX;
    state.Cc = startWorldSize / util::M2PI;
    
    const double startScale = state.scale;
    const double startAngle = state.angle;
    const double startPitch = state.pitch;
    state.panning = latLng != startLatLng;
    state.scaling = scale != startScale;
    state.rotating = angle != startAngle;

    startTransition(camera, animation, [=](double t) {
        ScreenCoordinate framePoint = util::interpolate(startPoint, endPoint, t);
        LatLng frameLatLng = {
            state.yLat(framePoint.y, startWorldSize),
            state.xLng(framePoint.x, startWorldSize)
        };
        double frameScale = util::interpolate(startScale, scale, t);
        state.setLatLngZoom(frameLatLng, state.scaleZoom(frameScale));
        
        if (angle != startAngle) {
            state.angle = util::wrap(util::interpolate(startAngle, angle, t), -M_PI, M_PI);
        }
        if (pitch != startPitch) {
            state.pitch = util::interpolate(startPitch, pitch, t);
        }
        
        if (padding) {
            state.moveLatLng(frameLatLng, center);
        }
        return update;
    }, duration);
}
 PointAnnotation(const LatLng& position_, const std::string& icon_ = "")
     : position(position_.wrapped()), icon(icon_) {}