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地图,或者基于墨卡托投影的切片地图