WorldWind Java Google Earth Like Zoom

Posted

技术标签:

【中文标题】WorldWind Java Google Earth Like Zoom【英文标题】: 【发布时间】:2018-05-20 15:27:01 【问题描述】:

我已经为 NASA Worldwind 创建了一个输入处理程序,我正在尝试复制 Google 地球,就像缩放一样。

我正在尝试向鼠标光标而不是屏幕中心进行缩放(就像默认情况下那样)。

我已经让它有点工作了——除了它没有始终如一地向光标下的纬度/经度缩放,它似乎漂移得太远了。我想要发生的是在缩放期间光标下保持相同的纬度/经度。因此,例如,如果您将光标悬停在特定地标(如水体)上,当滚轮滚动时,它将停留在光标下方。

我使用的代码很大程度上基于此:https://forum.worldwindcentral.com/forum/world-wind-java-forums/development-help/11977-zoom-at-mouse-cursor?p=104793#post104793

这是我的输入处理程序:

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());
    

要启用,请在 worldwind.xml 中设置此属性以指向此类:

<Property name="gov.nasa.worldwind.avkey.ViewInputHandlerClassName"
        value="gov.nasa.worldwindx.examples.ZoomToCursorViewInputHandler"/>

【问题讨论】:

尝试使用 delta = delta.multiply3(zoomChange - 1);而不是 delta = delta.multiply3(-zoomChange); 我试过了,它似乎不起作用。这是唯一可以尝试的改变吗? 尝试 delta = delta.multiply3((zoomChange - 1)/zoomChange);在你尝试这个之前还有一件事,想知道你在 zoomChange 中获得了什么价值?是绝对值还是百分比?即当我说缩放 150% 时……那么你得到的是 1.5 还是 150?如果你得到的是腹肌,即 1.5,那么这个新的变化应该会起作用。如果你得到 150,那么你需要将它除以 100 得到 abs 值 1.5。 让我们continue this discussion in chat. 如果向量数学是delta = cursorVector - centerVector,当你计算新的delta时,不应该找到新的centerVector是centerVector = cursorVector - delta吗?这将使 cursorVector 保持不变。如果我理解的话,制作centerVector = centerVector + delta 会将中心移向光标位置,并根据增量的大小过冲或下冲。 【参考方案1】:

在考虑了这个问题之后,我相信没有封闭形式的分析解决方案。你只需要考虑很多事情:地球的形状,当你移动中心时“眼睛”如何移动。所以我认为你能做的最好的技巧是“跟随”主要的“缩放”动画,并在每个动画步骤之后做一些小的调整。由于动画步骤很小,计算误差也应该更小,它们应该累积更少,因为在下一步你会考虑所有以前的错误。所以我在代码中的想法大致如下:创建一个FixZoomPositionAnimator类为

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);
    


更新

FixZoomPositionAnimator 已更新,以考虑到一个大比例尺,您不能假设经度和纬度线平行于 X 和 Y。要围绕中心周围的参考点进行此操作,用于计算调整.这意味着如果地球大小小于大约 20 像素 (2*stepPx) 或者如果用户倾斜地球使纬度/经度与 X/Y 明显不平行,则代码将不起作用。

更新结束

更新 #2

我已将之前的逻辑移至 nextOld 并添加了 nextWithTilt。即使世界倾斜,新功能也应该可以工作,但是由于现在基本逻辑更加复杂,不再有加速,恕我直言,对于更典型的情况来说,情况会变得更糟。此外,nextWithTilt 内部还有一个日志记录,因为我不太确定它是否真的可以正常工作。使用风险自负。

更新 #2 结束

然后你可以把它当作

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 


【讨论】:

很好的答案——这与我正在寻找的非常接近。我遇到过一些情况,我遇到大量异常并且地球会跳到一个随机位置。有任何想法吗?例外情况如下: gov.nasa.worldwind.WorldWindowGLAutoDrawable 显示严重:尝试重新绘制 WorldWindow java.lang.IllegalArgumentException 时出现异常:gov.nasa.worldwind.view.orbit.BasicOrbitView.setCenterPosition 的纬度超出范围 137.00189790206832° (BasicOrbitView.java:132) 在 gov.nasa.worldwindx.examples.ZoomToCursorViewInputHandler$FixZoomPositionAnimator.next(ZoomToCursorViewInputHandler.java:157) 在 gov.nasa.worldwind.animation.AnimationController.stepAnimators(AnimationController.java:78) @mainstringargs,我更新了FixZoomPositionAnimator,如果您尝试缩放到地球边缘附近的点,效果会更好。请看一看。 @mainstringargs,我相信我的整个FixZoomPositionAnimator 依赖于smoothing 默认为zoom 启用的事实,因此OrbitViewMoveToZoomAnimator 确实使用(默认)@ 放大了许多步骤987654334@ multiply 这样我就可以在许多步骤中完成我的“跟随”技巧并最终赶上它。 @mainstringargs,我添加了一些nextWithTilt 方法,乍一看似乎也适用于倾斜的世界。另请参阅答案中的我的 Update #2

以上是关于WorldWind Java Google Earth Like Zoom的主要内容,如果未能解决你的问题,请参考以下文章

[转]worldwind在线加载google地图,或者基于墨卡托投影的切片地图

worldwind java 文档和设置说明

从 WorldWind Java 中删除默认 WMS 层

Java NASA Worldwind 多边形与内部边界

Worldwind PointPlacemark Pitch

Worldwind 服务器不显示在 OpenLayers 3 中