2017-12-06 13 views
10

Ich habe einen Eingabe-Handler für NASA Worldwind erstellt, den ich versuche Google Earth zu replizieren, wie das Zoomen mit.WorldWind Java Google Earth wie Zoom

Ich versuche, Zoom in Richtung des Mauszeigers statt der Mitte des Bildschirms (wie es standardmäßig tut).

Ich habe es etwas funktioniert - außer es ist nicht in Richtung der lat/long unter dem Cursor konsequent, es scheint zu weit driften. Was ich möchte, ist, dass das gleiche lat/long unter dem Cursor während der Dauer des Zooms gehalten wird. Wenn Sie zum Beispiel den Mauszeiger über einen bestimmten Orientierungspunkt (wie eine Wasserfläche) bewegen, bleibt er unter dem Cursor, wenn das Rad gescrollt wird.

Der Code Ich verwende basierte stark auf diesem: https://forum.worldwindcentral.com/forum/world-wind-java-forums/development-help/11977-zoom-at-mouse-cursor?p=104793#post104793

Hier ist mein Eingang Handler:

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

zu aktivieren, diese Eigenschaft in der worldwind.xml auf diese Klasse Punkt :

+0

Versuchen mit delta = delta.multiply3 (zoomChange - 1); anstelle von delta = delta.multiply3 (-zoomChange); – vsoni

+0

Ich habe das versucht und es scheint nicht zu funktionieren.Ist das die einzige Änderung zu versuchen? – systemoutprintln

+0

Versuchen Sie delta = delta.multipl3 ((zoomChange - 1)/zoomChange); Noch eine Sache, bevor Sie das versuchen, wollte wissen, welchen Wertwert Sie in ZoomChange bekommen? Ist es absolut oder%? wenn ich sage, 150% zu vergrößern ... dann bekommst du 1,5 oder 150? Wenn Sie abs, d. H. 1,5 erhalten, dann sollte diese neue Änderung funktionieren. Wenn Sie 150 erhalten, müssen Sie es durch 100 teilen, um den ABS-Wert 1,5 zu erhalten. – vsoni

Antwort

2

Nach einigem Nachdenken über dieses Problem glaube ich, dass es keine geschlossene Form analytische Lösung dafür gibt. Man muss nur zu viele Dinge berücksichtigen: Form der Erde, wie sich das "Auge" bewegt, wenn man das Zentrum bewegt. Also, der beste Trick, den ich denke, ist, der "Zoom" -Animation zu folgen und kleine Anpassungen nach jedem Animationsschritt vorzunehmen. Da Animationsschritte klein sind, sollten Berechnungsfehler auch kleiner sein und sie sollten weniger akkumulieren, weil Sie im nächsten Schritt alle vorherigen Fehler berücksichtigen. Also meine Idee im Code folgt grob: Erstellen Sie eine FixZoomPositionAnimator Klasse als

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

} 

aktualisieren

FixZoomPositionAnimator wurde aktualisiert, um die Tatsache zu berücksichtigen, dass man eine große Skala Sie nicht, dass die Länge annehmen kann und Breitenlinien verlaufen parallel zu X und Y. Um dies zu umgehen, werden Referenzpunkte um das Zentrum herum verwendet, um die Anpassung zu berechnen. Dies bedeutet, dass der Code nicht funktioniert, wenn die Globusgröße weniger als 20px (2*stepPx) beträgt oder wenn der Benutzer die Erde gekippt hat, wodurch der Breitengrad/Längengrad deutlich nicht parallel zu X/Y ist.

Ende des Update

Update # 2

Ich habe vorherige Logik nextOld bewegt und hinzugefügt nextWithTilt. Die neue Funktion sollte funktionieren, auch wenn die Welt geneigt ist, aber da die Basislogik jetzt komplizierter ist, gibt es keine Beschleunigung mehr, was IMHO für typische Fälle etwas schlimmer macht. Auch gibt es noch ein Protokoll der Protokollierung innerhalb nextWithTilt, weil ich nicht ganz sicher bin, dass es wirklich funktioniert, OK. Benutzung auf eigene Gefahr.

Ende Update # 2

und dann können Sie es verwenden, als

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 

} 
+0

Große Antwort - das ist sehr nah an dem, was ich suche. Ich bin in einige Fälle geraten, in denen ich eine Flut von Ausnahmen bekomme und der Globus in eine zufällige Position springt. Irgendwelche Ideen? Ausnahmen sind von dieser Form: – systemoutprintln

+0

gov.nasa.worldwind.WorldWindowGLAutoDrawable Anzeige SCHWERE: Ausnahme beim Versuch, neu zu zeichnen WorldWindow java.lang.IllegalArgumentException: Breitengrad außerhalb des zulässigen Bereichs 137,00189790206832 ° \t bei gov.nasa.worldwind.view.orbit. BasicOrbitView.setCenterPosition (BasicOrbitView.java:132) \t bei gov.nasa.worldwindx.examples.ZoomToCursorViewInputHandler $ FixZoomPositionAnimator.next (ZoomToCursorViewInputHandler.java:157) \t bei gov.nasa.worldwind.animation.AnimationController.stepAnimators (AnimationController. java: 78) – systemoutprintln

+0

@mainstringargs, könnten Sie sagen, wo zoomen Sie und von welchem ​​Auge Punkt, um es zu reproduzieren? Ich hatte ursprünglich ähnliche Ausnahmen, bis ich den Zweig 'if (Math.abs (dCX) <= 1.0)' und ähnlich für 'dCY' eingeführt habe. Wahrscheinlich sollte es ein bisschen strenger sein. Sie könnten die Protokollierung verschiedener Dinge hinzufügen: 'dX',' dY', 'dCX',' dCY', 'mouseControlPoint',' mouseGeoLocation' und 'orbitView.getCenterPosition', um mehr Details darüber zu erhalten, was falsch läuft. – SergGr