WorldWind Java Google Earth Comme Zoom


J'ai créé un gestionnaire d'entrée pour NASA Worldwind que j'essaie de reproduire Google Earth comme un zoom avec.

J'essaie de faire un zoom vers le curseur de la souris, au lieu du centre de l'écran (comme il le fait par défaut).

Je l'ai quelque peu travaillé except sauf qu'il ne zoome pas constamment vers le lat/long sous le curseur, il semble dériver trop loin. Ce que je veux arriver, c'est que le même lat/long soit maintenu sous le curseur pendant la durée du zoom. Ainsi, par exemple, si vous placez le curseur sur un point de repère particulier (comme un plan d'eau), il restera sous le curseur lorsque la roue défilera.

Le code que j'utilise est fortement basé sur ceci: https://forum.worldwindcentral.com/forum/world-wind-java-forums/development-help/11977-zoom-at-mouse-cursor?p=104793#post104793

Voici mon gestionnaire d'entrée:

import java.awt.event.MouseWheelEvent;

import gov.nasa.worldwind.awt.AbstractViewInputHandler;
import gov.nasa.worldwind.awt.ViewInputAttributes;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.view.orbit.BasicOrbitView;
import gov.nasa.worldwind.view.orbit.OrbitViewInputHandler;

public class ZoomToCursorViewInputHandler extends OrbitViewInputHandler {
    protected class ZoomActionHandler extends VertTransMouseWheelActionListener {
        @Override
        public boolean inputActionPerformed(AbstractViewInputHandler inputHandler, MouseWheelEvent mouseWheelEvent,
                ViewInputAttributes.ActionAttributes viewAction) {
            double zoomInput = mouseWheelEvent.getWheelRotation();
                Position position = getView().computePositionFromScreenPoint(mousePoint.x, mousePoint.y);


            // Zoom toward the cursor if we're zooming in. Move straight out when zooming
            // out.
            if (zoomInput < 0 && position != null)
                return this.zoomToPosition(position, zoomInput, viewAction);
            else
                return super.inputActionPerformed(inputHandler, mouseWheelEvent, viewAction);
        }

        protected boolean zoomToPosition(Position position, double zoomInput,
                ViewInputAttributes.ActionAttributes viewAction) {


            double zoomChange = zoomInput * getScaleValueZoom(viewAction);

            BasicOrbitView view = (BasicOrbitView) getView();
            System.out.println("================================");

            System.out.println("Center Position: \t\t"+view.getCenterPosition());
            System.out.println("Mouse is on Position: \t\t"+position);

            Vec4 centerVector = view.getCenterPoint();
            Vec4 cursorVector = view.getGlobe().computePointFromLocation(position);
            Vec4 delta = cursorVector.subtract3(centerVector);

            delta = delta.multiply3(-zoomChange);

            centerVector = centerVector.add3(delta);
            Position newPosition = view.getGlobe().computePositionFromPoint(centerVector);

            System.out.println("New Center Position is: \t"+newPosition);

            setCenterPosition(view, uiAnimControl, newPosition, viewAction);

            onVerticalTranslate(zoomChange, viewAction);


            return true;
        }
    }

    public ZoomToCursorViewInputHandler() {
        ViewInputAttributes.ActionAttributes actionAttrs = this.getAttributes()
                .getActionMap(ViewInputAttributes.DEVICE_MOUSE_WHEEL)
                .getActionAttributes(ViewInputAttributes.VIEW_VERTICAL_TRANSLATE);
        actionAttrs.setMouseActionListener(new ZoomActionHandler());
    }
}

Pour activer, définissez cette propriété dans worldwind.xml pour pointer vers cette classe:

<Property name="gov.nasa.worldwind.avkey.ViewInputHandlerClassName"
        value="gov.nasa.worldwindx.examples.ZoomToCursorViewInputHandler"/>
Author: systemoutprintln, 2017-12-06

1 answers

Après avoir réfléchi à ce problème, je pense qu'il n'y a pas de solution analytique de forme fermée pour cela. Il suffit de prendre en compte beaucoup de choses: la forme de la Terre, la façon dont "l'œil" se déplace lorsque vous déplacez le centre. Donc, la meilleure astuce que je pense que vous pouvez faire est de "suivre" l'animation principale "zoom" et de faire de petits ajustements après chaque étape d'animation. Comme les étapes d'animation sont petites, les erreurs de calcul devraient également être plus petites et elles devraient s'accumuler moins car à l'étape suivante, vous prenez en compte toutes les erreurs précédentes. Donc, mon idée dans le code est à peu près la suivante: créer une classe FixZoomPositionAnimator comme

static class FixZoomPositionAnimator extends BasicAnimator
{
    static final String VIEW_ANIM_KEY = "FixZoomPositionAnimator";
    static final double EPS = 0.005;

    private final java.awt.Point mouseControlPoint;
    private final Position mouseGeoLocation;
    private final Vec4 mouseGeoPoint;
    private final BasicOrbitView orbitView;
    private final Animator zoomAnimator;

    private int lastDxSign = 0;
    private int lastDySign = 0;
    int stepNumber = 0;
    int stepsNoAdjustments = 0;


    FixZoomPositionAnimator(BasicOrbitView orbitView, Animator zoomAnimator, java.awt.Point mouseControlPoint, Position mouseGeoLocation)
    {
        this.orbitView = orbitView;
        this.zoomAnimator = zoomAnimator;
        this.mouseControlPoint = mouseControlPoint;
        this.mouseGeoLocation = mouseGeoLocation;
        mouseGeoPoint = orbitView.getGlobe().computePointFromLocation(mouseGeoLocation);
    }

    public Point getMouseControlPoint()
    {
        return mouseControlPoint;
    }

    public Position getMouseGeoLocation()
    {
        return mouseGeoLocation;
    }

    private static int sign(double d)
    {
        if (Math.abs(d) < EPS)
            return 0;
        else if (d > 0)
            return 1;
        else
            return -1;
    }

    double calcAccelerationK(double dSign, double lastDSign)
    {
        // as we are following zoom trying to catch up - accelerate adjustment
        // but slow down if we overshot the target last time
        if (!zoomAnimator.hasNext())
            return 1.0;
        else if (dSign != lastDSign)
            return 0.5;
        else
        {
            // reduce acceleration over time
            if (stepNumber < 10)
                return 5;
            else if (stepNumber < 20)
                return 3;
            else
                return 2;
        }
    }

    static boolean isBetween(double checkedValue, double target1, double target2)
    {
        return ((target1 < checkedValue) && (checkedValue < target2))
            || ((target1 > checkedValue) && (checkedValue > target2));
    }

    static boolean isValid(Position position)
    {
        return isBetween(position.longitude.degrees, -180, 180)
            && isBetween(position.latitude.degrees, -90, 90);
    }

    @Override
    public void next()
    {
        // super.next();   // do not call super to avoid NullPointerException!

        nextWithTilt(); // works OK on tilted Earth
        // nextOld();   // IMHO better looking but stops working is user tilts the Earth

    }

    private void nextOld()
    {
        stepNumber++;

        Vec4 curProjection = orbitView.project(mouseGeoPoint);
        Rectangle viewport = orbitView.getViewport();

        // for Y sign is inverted
        double dX = (mouseControlPoint.x - curProjection.x);
        double dY = (mouseControlPoint.y + curProjection.y - viewport.getHeight());

        if (Math.abs(dX) > EPS || Math.abs(dY) > EPS)
        {

            double dCX = (mouseControlPoint.x - viewport.getCenterX());
            double dCY = (mouseControlPoint.y + viewport.getCenterY() - viewport.getHeight());

            final double stepPx = 10;

            // As the Earth is curved and we are not guaranteed to have a frontal view on it
            // latitude an longitude lines are not really parallel to X or Y. But we assume that
            // locally they are parallel enough both around the mousePoint and around the center.
            // So we use reference points near center to calculate how we want to move the center.
            Vec4 controlPointRight = new Vec4(viewport.getCenterX() + stepPx, viewport.getCenterY());
            Vec4 geoPointRight = orbitView.unProject(controlPointRight);
            Position positionRight = (geoPointRight != null) ? orbitView.getGlobe().computePositionFromPoint(geoPointRight) : null;
            Vec4 controlPointUp = new Vec4(viewport.getCenterX(), viewport.getCenterY() - stepPx);
            Vec4 geoPointUp = orbitView.unProject(controlPointUp);
            Position positionUp = (geoPointUp != null) ? orbitView.getGlobe().computePositionFromPoint(geoPointUp) : null;

            Position centerPosition = orbitView.getCenterPosition();

            double newCenterLongDeg;
            if (Math.abs(dCX) <= 1.0) // same X => same longitude
            {
                newCenterLongDeg = mouseGeoLocation.longitude.degrees;
            }
            else if (positionRight == null)  // if controlPointRight is outside of the globe - don't try to fix this coordinate
            {
                newCenterLongDeg = centerPosition.longitude.degrees;
            }
            else
            {
                double scaleX = -dX / stepPx;
                // apply acceleration if possible
                int dXSign = sign(dX);
                double accScaleX = scaleX * calcAccelerationK(dXSign, lastDxSign);
                lastDxSign = dXSign;
                newCenterLongDeg = centerPosition.longitude.degrees * (1 - accScaleX) + positionRight.longitude.degrees * accScaleX;
                // if we overshot - use non-accelerated mode
                if (!isBetween(newCenterLongDeg, centerPosition.longitude.degrees, mouseGeoLocation.longitude.degrees)
                    || !isBetween(newCenterLongDeg, -180, 180))
                {
                    newCenterLongDeg = centerPosition.longitude.degrees * (1 - scaleX) + positionRight.longitude.degrees * scaleX;
                }
            }

            double newCenterLatDeg;
            if (Math.abs(dCY) <= 1.0) // same Y => same latitude
            {
                newCenterLatDeg = mouseGeoLocation.latitude.degrees;
            }
            else if (positionUp == null)  // if controlPointUp is outside of the globe - don't try to fix this coordinate
            {
                newCenterLatDeg = centerPosition.latitude.degrees;
            }
            else
            {
                double scaleY = -dY / stepPx;

                // apply acceleration if possible
                int dYSign = sign(dY);
                double accScaleY = scaleY * calcAccelerationK(dYSign, lastDySign);
                lastDySign = dYSign;
                newCenterLatDeg = centerPosition.latitude.degrees * (1 - accScaleY) + positionUp.latitude.degrees * accScaleY;
                // if we overshot - use non-accelerated mode
                if (!isBetween(newCenterLatDeg, centerPosition.latitude.degrees, mouseGeoLocation.latitude.degrees)
                    || !isBetween(newCenterLatDeg, -90, 90))
                {
                    newCenterLatDeg = centerPosition.latitude.degrees * (1 - scaleY) + positionUp.latitude.degrees * scaleY;
                }
            }
            Position newCenterPosition = Position.fromDegrees(newCenterLatDeg, newCenterLongDeg);
            orbitView.setCenterPosition(newCenterPosition);
        }

        if (!zoomAnimator.hasNext())
            stop();
    }

    private void nextWithTilt()
    {
        stepNumber++;

        if (!zoomAnimator.hasNext() || (stepsNoAdjustments > 20))
        {
            System.out.println("Stop after " + stepNumber);
            stop();
        }

        Vec4 curProjection = orbitView.project(mouseGeoPoint);
        Rectangle viewport = orbitView.getViewport();
        System.out.println("----------------------------------");
        System.out.println("Mouse: mouseControlPoint = " + mouseControlPoint + "\t location = " + mouseGeoLocation + "\t viewSize = " + viewport);
        System.out.println("Mouse: curProjection = " + curProjection);

        double dX = (mouseControlPoint.x - curProjection.x);
        double dY = (viewport.getHeight() - mouseControlPoint.y - curProjection.y);  // Y is inverted
        Vec4 dTgt = new Vec4(dX, dY);

        // sometimes if you zoom close to the edge curProjection is calculated as somewhere
        // way beyond where it is and it leads to overflow. This is a protection against it
        if (Math.abs(dX) > viewport.width / 4 || Math.abs(dY) > viewport.height / 4)
        {
            Vec4 unproject = orbitView.unProject(new Vec4(mouseControlPoint.x, viewport.getHeight() - mouseControlPoint.y));
            System.out.println("!!!End Mouse:"
                + " dX = " + dX + "\t" + " dY = " + dY
                + "\n" + "unprojectPt = " + unproject
                + "\n" + "unprojectPos = " + orbitView.getGlobe().computePositionFromPoint(unproject)
            );

            stepsNoAdjustments += 1;
            return;
        }

        if (Math.abs(dX) <= EPS && Math.abs(dY) <= EPS)
        {
            stepsNoAdjustments += 1;
            System.out.println("Mouse: No adjustment: " + " dX = " + dX + "\t" + " dY = " + dY);
            return;
        }
        else
        {
            stepsNoAdjustments = 0;
        }

        // create reference points about 10px away from the center to the Up and to the Right
        // and then map them to screen coordinates and geo coordinates
        // Unfortunately unproject often generates points far from the Earth surface (and
        // thus with significantly less difference in lat/long)
        // So this longer but more fool-proof calculation is used
        final double stepPx = 10;
        Position centerPosition = orbitView.getCenterPosition();
        Position eyePosition = orbitView.getEyePosition();
        double pixelGeoSize = orbitView.computePixelSizeAtDistance(eyePosition.elevation - centerPosition.elevation);
        Vec4 geoCenterPoint = orbitView.getCenterPoint();
        Vec4 geoRightPoint = geoCenterPoint.add3(new Vec4(pixelGeoSize * stepPx, 0, 0));
        Vec4 geoUpPoint = geoCenterPoint.add3(new Vec4(0, pixelGeoSize * stepPx, 0));

        Position geoRightPosition = orbitView.getGlobe().computePositionFromPoint(geoRightPoint);
        Position geoUpPosition = orbitView.getGlobe().computePositionFromPoint(geoUpPoint);

        Vec4 controlCenter = orbitView.project(geoCenterPoint);
        Vec4 controlRight = orbitView.project(geoRightPoint);
        Vec4 controlUp = orbitView.project(geoUpPoint);

        Vec4 controlRightDif = controlRight.subtract3(controlCenter);
        controlRightDif = new Vec4(controlRightDif.x, controlRightDif.y); // ignore z for scale calculation
        Vec4 controlUpDif = controlUp.subtract3(controlCenter);
        controlUpDif = new Vec4(controlUpDif.x, controlUpDif.y); // ignore z for scale calculation

        double scaleRight = -dTgt.dot3(controlRightDif) / controlRightDif.getLengthSquared3();
        double scaleUp = -dTgt.dot3(controlUpDif) / controlUpDif.getLengthSquared3();

        Position posRightDif = geoRightPosition.subtract(centerPosition);
        Position posUpDif = geoUpPosition.subtract(centerPosition);

        double totalLatDifDeg = posRightDif.latitude.degrees * scaleRight + posUpDif.latitude.degrees * scaleUp;
        double totalLongDifDeg = posRightDif.longitude.degrees * scaleRight + posUpDif.longitude.degrees * scaleUp;
        Position totalDif = Position.fromDegrees(totalLatDifDeg, totalLongDifDeg);

        // don't copy elevation!
        Position newCenterPosition = Position.fromDegrees(centerPosition.latitude.degrees + totalLatDifDeg,
            centerPosition.longitude.degrees + totalLongDifDeg);

        // if we overshot - try to slow down
        if (!isValid(newCenterPosition))
        {
            newCenterPosition = Position.fromDegrees(centerPosition.latitude.degrees + totalLatDifDeg / 2,
                centerPosition.longitude.degrees + totalLongDifDeg / 2);
            if (!isValid(newCenterPosition))
            {
                System.out.println("Too much overshot: " + newCenterPosition);
                stepsNoAdjustments += 1;
                return;
            }
        }

        System.out.println("Mouse:"
            + " dX = " + dX + "\t" + " dY = " + dY

            + "\n"
            + " centerPosition = " + centerPosition

            + "\n"
            + " geoUpPoint = " + geoUpPoint + "\t " + " geoUpPosition = " + geoUpPosition
            + "\n"
            + " geoRightPoint = " + geoRightPoint + "\t " + " geoRightPosition = " + geoRightPosition

            + "\n"
            + " posRightDif = " + posRightDif
            + "\t"
            + " posUpDif = " + posUpDif
            + "\n"
            + " scaleRight = " + scaleRight + "\t" + " scaleUp = " + scaleUp);
        System.out.println("Mouse: oldCenterPosition = " + centerPosition);
        System.out.println("Mouse: newCenterPosition = " + newCenterPosition);

        orbitView.setCenterPosition(newCenterPosition);
    }

}

Mise à Jour

FixZoomPositionAnimator a été mis à jour pour prendre en compte le fait que sur une grande échelle, vous ne pouvez pas supposer que les lignes de longitude et de latitude sont parallèles à X et Y. Pour travailler autour de points de référence autour du centre sont utilisés pour calculer l'ajustement. Cela signifie que le code ne fonctionnera pas si la taille du globe est inférieure à environ 20 pixels (2*stepPx) ou si l'utilisateur a l'inclinaison de la Terre rend la latitude / longitude significativement non parallèle à X / Y.

Fin de la mise à jour

Mise à Jour #2

J'ai déplacé la logique précédente vers nextOld et ajouté nextWithTilt. La nouvelle fonction devrait fonctionner même si le monde est incliné, mais comme la logique de base est plus compliquée maintenant, il n'y a plus d'accélération, ce qui, à mon humble avis, la rend un peu pire pour les cas plus typiques. De plus, il y a toujours un journal de journalisation à l'intérieur de nextWithTilt parce que je ne suis pas sûr que cela fonctionne vraiment OK. Utilisez à vos propres risques.

Fin de la mise à Jour #2

, puis vous pouvez l'utiliser comme

public class ZoomToCursorViewInputHandler extends OrbitViewInputHandler
{
    public ZoomToCursorViewInputHandler()
    {
        ViewInputAttributes.ActionAttributes actionAttrs = this.getAttributes()
            .getActionMap(ViewInputAttributes.DEVICE_MOUSE_WHEEL)
            .getActionAttributes(ViewInputAttributes.VIEW_VERTICAL_TRANSLATE);
        actionAttrs.setMouseActionListener(new ZoomActionHandler());
    }

    protected class ZoomActionHandler extends VertTransMouseWheelActionListener
    {
        @Override
        public boolean inputActionPerformed(AbstractViewInputHandler inputHandler, MouseWheelEvent mouseWheelEvent,
            final ViewInputAttributes.ActionAttributes viewAction)
        {
            double zoomInput = mouseWheelEvent.getWheelRotation();
            Position position = wwd.getCurrentPosition();
            Point mouseControlPoint = mouseWheelEvent.getPoint();

            // Zoom toward the cursor if we're zooming in. Move straight out when zooming
            // out.
            if (zoomInput < 0 && position != null)
            {
                boolean res = super.inputActionPerformed(inputHandler, mouseWheelEvent, viewAction);

                BasicOrbitView view = (BasicOrbitView) getView();
                OrbitViewMoveToZoomAnimator zoomAnimator = (OrbitViewMoveToZoomAnimator) uiAnimControl.get(VIEW_ANIM_ZOOM);

                // for continuous scroll preserve the original target if mouse was not moved
                FixZoomPositionAnimator old = (FixZoomPositionAnimator) uiAnimControl.get(FixZoomPositionAnimator.VIEW_ANIM_KEY);
                if (old != null && old.getMouseControlPoint().equals(mouseControlPoint))
                {
                    position = old.getMouseGeoLocation();
                }
                FixZoomPositionAnimator fixZoomPositionAnimator = new FixZoomPositionAnimator(view, zoomAnimator, mouseControlPoint, position);
                fixZoomPositionAnimator.start();
                uiAnimControl.put(FixZoomPositionAnimator.VIEW_ANIM_KEY, fixZoomPositionAnimator);
                return res;
            }
            else
            {

                uiAnimControl.remove(FixZoomPositionAnimator.VIEW_ANIM_KEY); // when zoom direction changes we don't want to make position adjustments anymore
                return super.inputActionPerformed(inputHandler, mouseWheelEvent, viewAction);
            }
        }
    }

    // here goes aforementioned FixZoomPositionAnimator 

}
 2
Author: SergGr, 2017-12-19 02:45:14