如何在 JavaFx 中重置缩放级别?
How can I reset zoom level in JavaFx?
我正在用 Java 制作地图,我已经设法创建了放大和缩小的方法,但我想实现一个功能,可以将缩放级别重置为默认值。这样地图就全部缩小了。我曾尝试在使用 canvas.zoom() 时更改该系数,但它不起作用。有人知道吗?
下面是我的方法。这是我正在尝试修复的 TabResetBtnAction。
FXML:
<Scene fx:id="scene" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller.Controller">
<AnchorPane fx:id="MainPane" prefHeight="443.0" prefWidth="670.0">
<children>
<MapCanvas fx:id="canvas" height="${scene.height}" width="${scene.width}" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" onScroll="#onScroll" onMousePressed="#onMousePressed" onMouseDragged="#onMouseDragged"/>
<ToggleButton fx:id="ectBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#ectBtnAction" prefHeight="25.0" prefWidth="25.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0">
<graphic>
<ImageView fitHeight="29.0" fitWidth="22.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/gear.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
<TabPane fx:id="ectMenu" layoutX="456.0" layoutY="27.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="189.0" prefWidth="197.0" side="RIGHT" tabClosingPolicy="UNAVAILABLE" AnchorPane.rightAnchor="-197.0" AnchorPane.topAnchor="32.0">
<tabs>
<Tab fx:id="ThemeTab" text="Themes">
<content>
<FlowPane fx:id="TabThemePane" alignment="CENTER" columnHalignment="CENTER" hgap="5.0" prefHeight="210.0" prefWidth="135.0" vgap="5.0">
<children>
<Button fx:id="colorFadedBtn" mnemonicParsing="false" onAction="#colorFadedBtnAction" text="Faded" />
<Button fx:id="colorGoogleBtn" mnemonicParsing="false" onAction="#colorGoogleBtnAction" text="Google maps">
<FlowPane.margin>
<Insets />
</FlowPane.margin>
</Button>
<Button fx:id="colorOSMBtn" mnemonicParsing="false" onAction="#colorOSMBtnAction" text="Open Street Map" />
<Button fx:id="colorMwtDewBtn" mnemonicParsing="false" onAction="#colorMwtDewBtnAction" text="Mountain Dew" />
</children>
<opaqueInsets>
<Insets />
</opaqueInsets>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</FlowPane>
</content>
</Tab>
<Tab text="View">
<content>
<VBox fx:id="TabViewPane" alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="5.0">
<children>
<Button fx:id="TabCenterBtn" mnemonicParsing="false" onAction="#TabCenterBtnAction" text="Center" />
<Button fx:id="TabResetBtn" mnemonicParsing="false" onAction="#TabResetBtnAction" text="Reset view" />
<Separator prefWidth="168.0" />
<ToggleButton fx:id="TabToggleAllBtn" mnemonicParsing="false" onAction="#TabToggleAllBtnAction" text="Toggle all" />
<ToggleButton fx:id="TabToggleSearchBtn" mnemonicParsing="false" onAction="#TabToggleSearchBtnAction" text="Toggle search" />
<ToggleButton fx:id="TabToggleToolsBtn" mnemonicParsing="false" onAction="#TabToggleToolsBtnAction" text="Toggle tools" />
<ToggleButton fx:id="TabToggleZoomBtn" mnemonicParsing="false" onAction="#TabToggleZoomBtnAction" text="Toggle zoom" />
</children>
</VBox>
</content>
</Tab>
<Tab text="Exit">
<content>
<VBox fx:id="TabExitPane" alignment="CENTER_LEFT" spacing="5.0">
<children>
<Button fx:id="UploadFileBtn" mnemonicParsing="false" onAction="#UploadFileBtnAction" text="Upload file">
<graphic>
<ImageView fitHeight="13.0" fitWidth="14.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/folder.png" />
</image>
</ImageView>
</graphic>
</Button>
<Button fx:id="ExitBtn" mnemonicParsing="false" onAction="#ExitBtnAction" text="Exit">
<graphic>
<ImageView fitHeight="13.0" fitWidth="14.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/logout.png" />
</image>
</ImageView>
</graphic>
</Button>
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</VBox>
</content>
</Tab>
</tabs>
</TabPane>
<ScrollPane fx:id="FromScrollPane" layoutX="5.0" layoutY="5.0" prefWidth="${searchbar.width}" visible="false">
<content>
<VBox fx:id="ToVBox" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" spacing="5.0">
<padding>
<Insets left="5.0" right="5.0" top="35.0" />
</padding>
</VBox>
</content>
<opaqueInsets>
<Insets />
</opaqueInsets>
</ScrollPane>
<HBox fx:id="SearchPane" layoutX="20.0" layoutY="20.0" spacing="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="5.0">
<children>
<TextField fx:id="searchbar" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onAction="#searchbarAction" promptText="Search" />
<Button maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0">
<graphic>
<ImageView fitHeight="21.0" fitWidth="25.0" nodeOrientation="INHERIT" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/68213.png" />
</image>
</ImageView>
</graphic>
</Button>
<ToggleButton fx:id="routeBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#routeBtnAction" prefHeight="30.0" prefWidth="30.0" HBox.hgrow="NEVER">
<graphic>
<ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/route(16).png" />
</image>
</ImageView>
</graphic>
<HBox.margin>
<Insets />
</HBox.margin>
</ToggleButton>
</children>
</HBox>
<VBox fx:id="routeMenu" layoutX="-150.0" layoutY="35.0" spacing="5.0" AnchorPane.leftAnchor="-150.0" AnchorPane.topAnchor="35.0">
<children>
<TextField fx:id="searchbar1" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" promptText="To" />
<HBox alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" spacing="5.0">
<children>
<ToggleButton minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0">
<graphic>
<ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/car.png" />
</image>
</ImageView>
</graphic>
<toggleGroup>
<ToggleGroup fx:id="Vehicle" />
</toggleGroup>
</ToggleButton>
<ToggleButton maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0" toggleGroup="$Vehicle">
<graphic>
<ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/bike.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
<ToggleButton maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0" toggleGroup="$Vehicle">
<graphic>
<ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/walk.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
</children>
</HBox>
</children>
</VBox>
<HBox fx:id="ZoomPane" alignment="CENTER_RIGHT" layoutX="341.0" layoutY="411.0" spacing="5.0" AnchorPane.bottomAnchor="5.0" AnchorPane.rightAnchor="5.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Nearest road" />
<Text fx:id="NearestRoadText" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" />
<Separator orientation="VERTICAL" />
<Text fx:id="ZoomText" strokeType="OUTSIDE" strokeWidth="0.0" text="Zoom text" />
<ImageView fitHeight="16.0" fitWidth="141.0" pickOnBounds="true" preserveRatio="true" />
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</HBox>
<VBox fx:id="ToolPane" alignment="BOTTOM_RIGHT" layoutX="640.0" layoutY="261.0" spacing="5.0" AnchorPane.bottomAnchor="37.0" AnchorPane.rightAnchor="5.0">
<children>
<Button fx:id="ZoomInBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#ZoomInBtnAction" prefHeight="25.0" prefWidth="25.0">
<graphic>
<ImageView fitHeight="22.0" fitWidth="17.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/plus.png" />
</image>
</ImageView>
</graphic>
</Button>
<Button fx:id="ZoomOutBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#ZoomOutBtnAction" prefHeight="25.0" prefWidth="25.0">
<graphic>
<ImageView fitHeight="18.0" fitWidth="21.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/minus.png" />
</image>
</ImageView>
</graphic>
</Button>
<ToggleButton fx:id="PanBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#PanBtnAction" prefHeight="25.0" prefWidth="25.0" selected="true">
<toggleGroup>
<ToggleGroup fx:id="mapFunction1" />
</toggleGroup>
<graphic>
<ImageView fitHeight="15.0" fitWidth="18.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/drag.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
<ToggleButton fx:id="POIBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#POIBtnAction" prefHeight="25.0" prefWidth="25.0" toggleGroup="$mapFunction1">
<graphic>
<ImageView fitHeight="18.0" fitWidth="70.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/starPin.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
<ToggleButton fx:id="BBBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#BBBtnAction" prefHeight="25.0" prefWidth="25.0" toggleGroup="$mapFunction1">
<graphic>
<ImageView fitHeight="16.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/rectangle.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
</children>
</VBox>
</children></AnchorPane>
</Scene>
控制器:
public class Controller{
private Model model;
private Point2D lastMouse;
...
public void init(Model model) throws NonInvertibleTransformException {
this.model = model;
canvas.init(model);
}
@FXML
private void onMousePressed(MouseEvent e) throws NonInvertibleTransformException {
lastMouse = new Point2D(e.getX(), e.getY());
if (e.getButton() == MouseButton.SECONDARY) {
canvas.drawNearest(lastMouse);
}
if (canvas.getNearestName() != null) {
NearestRoadText.setText(canvas.getNearestName());
} else {
NearestRoadText.setText("Unnamed road/path");
}
}
@FXML
private void repaint() throws NonInvertibleTransformException{
canvas.repaint();;
}
@FXML
private void TabResetBtnAction() throws NonInvertibleTransformException {
Point2D center = new Point2D(ZoomInBtn.getScene().getWindow().getWidth()/2,ZoomInBtn.getScene().getWindow().getHeight()/2);
canvas.zoom(1.0 ,center);
}
@FXML
private void ZoomInBtnAction () throws NonInvertibleTransformException{
Point2D center = new Point2D(ZoomInBtn.getScene().getWindow().getWidth()/2,ZoomInBtn.getScene().getWindow().getHeight()/2);
canvas.zoom(1.4888637335882213,center);
}
@FXML
private void ZoomOutBtnAction () throws NonInvertibleTransformException {
Point2D center = new Point2D(ZoomInBtn.getScene().getWindow().getWidth()/2,ZoomInBtn.getScene().getWindow().getHeight()/2);
canvas.zoom(0.6716531388604381,center);
}
canvas:
public class MapCanvas extends Canvas {
private Model model;
private Affine trans = new Affine();
private Point2D max, min, point;
private double zoomFactor = 1;
private double initialZoomFactor;
private double maxZoomFactor = 500000;
private int counter = 0;
private static ThemeDB themeDB = new ThemeDB();
private ArrayList<Way> roadsSmall, roadsLarge, motorways;
private Way nearest;
//private long lastZoom;
//private final int zoomDelay = 100; // this is in milliseconds
public void init(Model model) throws NonInvertibleTransformException {
this.model = model;
pan(-model.min_x,-model.min_y);
zoom(getWidth()/(model.max_x-model.min_x), new Point2D(0,0));
}
public void pan(double dx, double dy) throws NonInvertibleTransformException {
trans.prependTranslation(dx, dy);
repaint();
}
public void zoom(double factor, Point2D center) throws NonInvertibleTransformException {
if (counter < 1) {
setStartZoom(factor, center);
} else {
if (zoomFactor * factor >= initialZoomFactor && zoomFactor * factor <= maxZoomFactor) {
trans.prependScale(factor, factor, center);
zoomFactor *= factor;
}
}
repaint();
}
private void setStartZoom (double factor, Point2D center) {
initialZoomFactor = factor;
trans.prependScale(factor, factor, center);
zoomFactor *= factor;
counter++;
}
类似于
public void resetZoom() throws NonInvertibleTransformException {
Point2D p1 = trans.inverseTransform(0, 0);
Point2D p2 = trans.inverseTransform(getWidth(), getHeight());
Rectangle2D rect = new Rectangle2D(
Math.min(p1.getX(), p2.getX()),
Math.min(p1.getY(), p2.getY()),
Math.max(p1.getX(), p2.getX()) - Math.min(p1.getX(), p2.getX()),
Math.max(p1.getY(), p2.getY()) - Math.min(p1.getY(), p2.getY()));
trans.prependScale(initialZoomFactor / zoomFactor, initialZoomFactor / zoomFactor, rect.getMaxX() - rect.getMinX() / 2, rect.getMaxY() - rect.getMinY() / 2);
zoomFactor = initialZoomFactor;
repaint();
}
确定的部分是通过返回 initialZoomFactor
来反转缩放。
现在你需要确定我不清楚的轴心点。在这里,我尝试使用视图的当前中心,因此 canvas 的绘图大致位于视口内。
如果我不设置轴心点或不使用 (0, 0),它们就会完全失焦
可用class
package test.mapcanvas;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.transform.Affine;
import javafx.scene.transform.NonInvertibleTransformException;
public class MapCanvas extends Canvas {
private Affine trans = new Affine();
private double zoomFactor = 1;
private double initialZoomFactor;
private double maxZoomFactor = 500000;
private int counter = 0;
public void init() throws NonInvertibleTransformException {
zoom(2, new Point2D(0,0));
}
public void pan(double dx, double dy) throws NonInvertibleTransformException {
trans.prependTranslation(dx, dy);
repaint();
}
public void zoom(double factor, Point2D center) throws NonInvertibleTransformException {
if (counter < 1) {
setStartZoom(factor, center);
} else {
if (zoomFactor * factor >= initialZoomFactor && zoomFactor * factor <= maxZoomFactor) {
trans.prependScale(factor, factor, center);
zoomFactor *= factor;
}
}
repaint();
}
private void setStartZoom (double factor, Point2D center) {
initialZoomFactor = factor;
trans.prependScale(factor, factor, center);
zoomFactor *= factor;
counter++;
}
public void resetZoom() throws NonInvertibleTransformException {
Rectangle2D rect = getCurrentViewBounds();
trans.prependScale(initialZoomFactor / zoomFactor, initialZoomFactor / zoomFactor, rect.getMaxX() - rect.getMinX() / 2, rect.getMaxY() - rect.getMinY() / 2);
zoomFactor = initialZoomFactor;
repaint();
}
public void repaint() {
Platform.runLater(() -> {
try {
/* requires file example.jpg in class' package */
Image image = new Image(getClass().getResource("example.jpg").toExternalForm());
GraphicsContext g = getGraphicsContext2D();
g.setTransform(trans);
Rectangle2D rect = getCurrentViewBounds();
g.clearRect(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight());
g.drawImage(image, 0, 0);
} catch (NonInvertibleTransformException e) {
e.printStackTrace();
}
});
}
private Rectangle2D getCurrentViewBounds() throws NonInvertibleTransformException {
Point2D p1 = trans.inverseTransform(0, 0);
Point2D p2 = trans.inverseTransform(getWidth(), getHeight());
return new Rectangle2D(
Math.min(p1.getX(), p2.getX()),
Math.min(p1.getY(), p2.getY()),
Math.max(p1.getX(), p2.getX()) - Math.min(p1.getX(), p2.getX()),
Math.max(p1.getY(), p2.getY()) - Math.min(p1.getY(), p2.getY()));
}
}
考虑这个重构:
public class MapCanvas extends Canvas {
// ...
// public void init(Model model) throws NonInvertibleTransformException {
// this.model = model;
// pan(-model.min_x,-model.min_y);
// zoom(getWidth()/(model.max_x-model.min_x), new Point2D(0,0));
// }
public void init(Model model) throws NonInvertibleTransformException {
this.model = model;
init();
}
private void init() throws NonInvertibleTransformException {
pan(-model.min_x,-model.min_y);
zoom(getWidth()/(model.max_x-model.min_x), new Point2D(0,0));
}
public void resetZoom() throws NonInvertibleTransformException {
trans.setToTransform(1, 0, 0, 1, 0, 0);
init();
}
// ...
}
然后从控制器调用 canvas.resetZoom()
应该重置为初始缩放设置。 trans.setToTransform(1, 0, 0, 1, 0, 0);
调用将仿射变换重置为标识(其初始状态),然后执行与原始 init(...)
方法相同的初始化。
我正在用 Java 制作地图,我已经设法创建了放大和缩小的方法,但我想实现一个功能,可以将缩放级别重置为默认值。这样地图就全部缩小了。我曾尝试在使用 canvas.zoom() 时更改该系数,但它不起作用。有人知道吗?
下面是我的方法。这是我正在尝试修复的 TabResetBtnAction。
FXML:
<Scene fx:id="scene" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller.Controller">
<AnchorPane fx:id="MainPane" prefHeight="443.0" prefWidth="670.0">
<children>
<MapCanvas fx:id="canvas" height="${scene.height}" width="${scene.width}" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" onScroll="#onScroll" onMousePressed="#onMousePressed" onMouseDragged="#onMouseDragged"/>
<ToggleButton fx:id="ectBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#ectBtnAction" prefHeight="25.0" prefWidth="25.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0">
<graphic>
<ImageView fitHeight="29.0" fitWidth="22.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/gear.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
<TabPane fx:id="ectMenu" layoutX="456.0" layoutY="27.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="189.0" prefWidth="197.0" side="RIGHT" tabClosingPolicy="UNAVAILABLE" AnchorPane.rightAnchor="-197.0" AnchorPane.topAnchor="32.0">
<tabs>
<Tab fx:id="ThemeTab" text="Themes">
<content>
<FlowPane fx:id="TabThemePane" alignment="CENTER" columnHalignment="CENTER" hgap="5.0" prefHeight="210.0" prefWidth="135.0" vgap="5.0">
<children>
<Button fx:id="colorFadedBtn" mnemonicParsing="false" onAction="#colorFadedBtnAction" text="Faded" />
<Button fx:id="colorGoogleBtn" mnemonicParsing="false" onAction="#colorGoogleBtnAction" text="Google maps">
<FlowPane.margin>
<Insets />
</FlowPane.margin>
</Button>
<Button fx:id="colorOSMBtn" mnemonicParsing="false" onAction="#colorOSMBtnAction" text="Open Street Map" />
<Button fx:id="colorMwtDewBtn" mnemonicParsing="false" onAction="#colorMwtDewBtnAction" text="Mountain Dew" />
</children>
<opaqueInsets>
<Insets />
</opaqueInsets>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</FlowPane>
</content>
</Tab>
<Tab text="View">
<content>
<VBox fx:id="TabViewPane" alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="5.0">
<children>
<Button fx:id="TabCenterBtn" mnemonicParsing="false" onAction="#TabCenterBtnAction" text="Center" />
<Button fx:id="TabResetBtn" mnemonicParsing="false" onAction="#TabResetBtnAction" text="Reset view" />
<Separator prefWidth="168.0" />
<ToggleButton fx:id="TabToggleAllBtn" mnemonicParsing="false" onAction="#TabToggleAllBtnAction" text="Toggle all" />
<ToggleButton fx:id="TabToggleSearchBtn" mnemonicParsing="false" onAction="#TabToggleSearchBtnAction" text="Toggle search" />
<ToggleButton fx:id="TabToggleToolsBtn" mnemonicParsing="false" onAction="#TabToggleToolsBtnAction" text="Toggle tools" />
<ToggleButton fx:id="TabToggleZoomBtn" mnemonicParsing="false" onAction="#TabToggleZoomBtnAction" text="Toggle zoom" />
</children>
</VBox>
</content>
</Tab>
<Tab text="Exit">
<content>
<VBox fx:id="TabExitPane" alignment="CENTER_LEFT" spacing="5.0">
<children>
<Button fx:id="UploadFileBtn" mnemonicParsing="false" onAction="#UploadFileBtnAction" text="Upload file">
<graphic>
<ImageView fitHeight="13.0" fitWidth="14.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/folder.png" />
</image>
</ImageView>
</graphic>
</Button>
<Button fx:id="ExitBtn" mnemonicParsing="false" onAction="#ExitBtnAction" text="Exit">
<graphic>
<ImageView fitHeight="13.0" fitWidth="14.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/logout.png" />
</image>
</ImageView>
</graphic>
</Button>
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</VBox>
</content>
</Tab>
</tabs>
</TabPane>
<ScrollPane fx:id="FromScrollPane" layoutX="5.0" layoutY="5.0" prefWidth="${searchbar.width}" visible="false">
<content>
<VBox fx:id="ToVBox" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" spacing="5.0">
<padding>
<Insets left="5.0" right="5.0" top="35.0" />
</padding>
</VBox>
</content>
<opaqueInsets>
<Insets />
</opaqueInsets>
</ScrollPane>
<HBox fx:id="SearchPane" layoutX="20.0" layoutY="20.0" spacing="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="5.0">
<children>
<TextField fx:id="searchbar" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onAction="#searchbarAction" promptText="Search" />
<Button maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0">
<graphic>
<ImageView fitHeight="21.0" fitWidth="25.0" nodeOrientation="INHERIT" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/68213.png" />
</image>
</ImageView>
</graphic>
</Button>
<ToggleButton fx:id="routeBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#routeBtnAction" prefHeight="30.0" prefWidth="30.0" HBox.hgrow="NEVER">
<graphic>
<ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/route(16).png" />
</image>
</ImageView>
</graphic>
<HBox.margin>
<Insets />
</HBox.margin>
</ToggleButton>
</children>
</HBox>
<VBox fx:id="routeMenu" layoutX="-150.0" layoutY="35.0" spacing="5.0" AnchorPane.leftAnchor="-150.0" AnchorPane.topAnchor="35.0">
<children>
<TextField fx:id="searchbar1" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" promptText="To" />
<HBox alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" spacing="5.0">
<children>
<ToggleButton minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0">
<graphic>
<ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/car.png" />
</image>
</ImageView>
</graphic>
<toggleGroup>
<ToggleGroup fx:id="Vehicle" />
</toggleGroup>
</ToggleButton>
<ToggleButton maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0" toggleGroup="$Vehicle">
<graphic>
<ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/bike.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
<ToggleButton maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0" toggleGroup="$Vehicle">
<graphic>
<ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/walk.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
</children>
</HBox>
</children>
</VBox>
<HBox fx:id="ZoomPane" alignment="CENTER_RIGHT" layoutX="341.0" layoutY="411.0" spacing="5.0" AnchorPane.bottomAnchor="5.0" AnchorPane.rightAnchor="5.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Nearest road" />
<Text fx:id="NearestRoadText" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" />
<Separator orientation="VERTICAL" />
<Text fx:id="ZoomText" strokeType="OUTSIDE" strokeWidth="0.0" text="Zoom text" />
<ImageView fitHeight="16.0" fitWidth="141.0" pickOnBounds="true" preserveRatio="true" />
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</HBox>
<VBox fx:id="ToolPane" alignment="BOTTOM_RIGHT" layoutX="640.0" layoutY="261.0" spacing="5.0" AnchorPane.bottomAnchor="37.0" AnchorPane.rightAnchor="5.0">
<children>
<Button fx:id="ZoomInBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#ZoomInBtnAction" prefHeight="25.0" prefWidth="25.0">
<graphic>
<ImageView fitHeight="22.0" fitWidth="17.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/plus.png" />
</image>
</ImageView>
</graphic>
</Button>
<Button fx:id="ZoomOutBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#ZoomOutBtnAction" prefHeight="25.0" prefWidth="25.0">
<graphic>
<ImageView fitHeight="18.0" fitWidth="21.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/minus.png" />
</image>
</ImageView>
</graphic>
</Button>
<ToggleButton fx:id="PanBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#PanBtnAction" prefHeight="25.0" prefWidth="25.0" selected="true">
<toggleGroup>
<ToggleGroup fx:id="mapFunction1" />
</toggleGroup>
<graphic>
<ImageView fitHeight="15.0" fitWidth="18.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/drag.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
<ToggleButton fx:id="POIBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#POIBtnAction" prefHeight="25.0" prefWidth="25.0" toggleGroup="$mapFunction1">
<graphic>
<ImageView fitHeight="18.0" fitWidth="70.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/starPin.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
<ToggleButton fx:id="BBBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#BBBtnAction" prefHeight="25.0" prefWidth="25.0" toggleGroup="$mapFunction1">
<graphic>
<ImageView fitHeight="16.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/rectangle.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
</children>
</VBox>
</children></AnchorPane>
</Scene>
控制器:
public class Controller{
private Model model;
private Point2D lastMouse;
...
public void init(Model model) throws NonInvertibleTransformException {
this.model = model;
canvas.init(model);
}
@FXML
private void onMousePressed(MouseEvent e) throws NonInvertibleTransformException {
lastMouse = new Point2D(e.getX(), e.getY());
if (e.getButton() == MouseButton.SECONDARY) {
canvas.drawNearest(lastMouse);
}
if (canvas.getNearestName() != null) {
NearestRoadText.setText(canvas.getNearestName());
} else {
NearestRoadText.setText("Unnamed road/path");
}
}
@FXML
private void repaint() throws NonInvertibleTransformException{
canvas.repaint();;
}
@FXML
private void TabResetBtnAction() throws NonInvertibleTransformException {
Point2D center = new Point2D(ZoomInBtn.getScene().getWindow().getWidth()/2,ZoomInBtn.getScene().getWindow().getHeight()/2);
canvas.zoom(1.0 ,center);
}
@FXML
private void ZoomInBtnAction () throws NonInvertibleTransformException{
Point2D center = new Point2D(ZoomInBtn.getScene().getWindow().getWidth()/2,ZoomInBtn.getScene().getWindow().getHeight()/2);
canvas.zoom(1.4888637335882213,center);
}
@FXML
private void ZoomOutBtnAction () throws NonInvertibleTransformException {
Point2D center = new Point2D(ZoomInBtn.getScene().getWindow().getWidth()/2,ZoomInBtn.getScene().getWindow().getHeight()/2);
canvas.zoom(0.6716531388604381,center);
}
canvas:
public class MapCanvas extends Canvas {
private Model model;
private Affine trans = new Affine();
private Point2D max, min, point;
private double zoomFactor = 1;
private double initialZoomFactor;
private double maxZoomFactor = 500000;
private int counter = 0;
private static ThemeDB themeDB = new ThemeDB();
private ArrayList<Way> roadsSmall, roadsLarge, motorways;
private Way nearest;
//private long lastZoom;
//private final int zoomDelay = 100; // this is in milliseconds
public void init(Model model) throws NonInvertibleTransformException {
this.model = model;
pan(-model.min_x,-model.min_y);
zoom(getWidth()/(model.max_x-model.min_x), new Point2D(0,0));
}
public void pan(double dx, double dy) throws NonInvertibleTransformException {
trans.prependTranslation(dx, dy);
repaint();
}
public void zoom(double factor, Point2D center) throws NonInvertibleTransformException {
if (counter < 1) {
setStartZoom(factor, center);
} else {
if (zoomFactor * factor >= initialZoomFactor && zoomFactor * factor <= maxZoomFactor) {
trans.prependScale(factor, factor, center);
zoomFactor *= factor;
}
}
repaint();
}
private void setStartZoom (double factor, Point2D center) {
initialZoomFactor = factor;
trans.prependScale(factor, factor, center);
zoomFactor *= factor;
counter++;
}
类似于
public void resetZoom() throws NonInvertibleTransformException {
Point2D p1 = trans.inverseTransform(0, 0);
Point2D p2 = trans.inverseTransform(getWidth(), getHeight());
Rectangle2D rect = new Rectangle2D(
Math.min(p1.getX(), p2.getX()),
Math.min(p1.getY(), p2.getY()),
Math.max(p1.getX(), p2.getX()) - Math.min(p1.getX(), p2.getX()),
Math.max(p1.getY(), p2.getY()) - Math.min(p1.getY(), p2.getY()));
trans.prependScale(initialZoomFactor / zoomFactor, initialZoomFactor / zoomFactor, rect.getMaxX() - rect.getMinX() / 2, rect.getMaxY() - rect.getMinY() / 2);
zoomFactor = initialZoomFactor;
repaint();
}
确定的部分是通过返回 initialZoomFactor
来反转缩放。
现在你需要确定我不清楚的轴心点。在这里,我尝试使用视图的当前中心,因此 canvas 的绘图大致位于视口内。
如果我不设置轴心点或不使用 (0, 0),它们就会完全失焦
可用class
package test.mapcanvas;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.transform.Affine;
import javafx.scene.transform.NonInvertibleTransformException;
public class MapCanvas extends Canvas {
private Affine trans = new Affine();
private double zoomFactor = 1;
private double initialZoomFactor;
private double maxZoomFactor = 500000;
private int counter = 0;
public void init() throws NonInvertibleTransformException {
zoom(2, new Point2D(0,0));
}
public void pan(double dx, double dy) throws NonInvertibleTransformException {
trans.prependTranslation(dx, dy);
repaint();
}
public void zoom(double factor, Point2D center) throws NonInvertibleTransformException {
if (counter < 1) {
setStartZoom(factor, center);
} else {
if (zoomFactor * factor >= initialZoomFactor && zoomFactor * factor <= maxZoomFactor) {
trans.prependScale(factor, factor, center);
zoomFactor *= factor;
}
}
repaint();
}
private void setStartZoom (double factor, Point2D center) {
initialZoomFactor = factor;
trans.prependScale(factor, factor, center);
zoomFactor *= factor;
counter++;
}
public void resetZoom() throws NonInvertibleTransformException {
Rectangle2D rect = getCurrentViewBounds();
trans.prependScale(initialZoomFactor / zoomFactor, initialZoomFactor / zoomFactor, rect.getMaxX() - rect.getMinX() / 2, rect.getMaxY() - rect.getMinY() / 2);
zoomFactor = initialZoomFactor;
repaint();
}
public void repaint() {
Platform.runLater(() -> {
try {
/* requires file example.jpg in class' package */
Image image = new Image(getClass().getResource("example.jpg").toExternalForm());
GraphicsContext g = getGraphicsContext2D();
g.setTransform(trans);
Rectangle2D rect = getCurrentViewBounds();
g.clearRect(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight());
g.drawImage(image, 0, 0);
} catch (NonInvertibleTransformException e) {
e.printStackTrace();
}
});
}
private Rectangle2D getCurrentViewBounds() throws NonInvertibleTransformException {
Point2D p1 = trans.inverseTransform(0, 0);
Point2D p2 = trans.inverseTransform(getWidth(), getHeight());
return new Rectangle2D(
Math.min(p1.getX(), p2.getX()),
Math.min(p1.getY(), p2.getY()),
Math.max(p1.getX(), p2.getX()) - Math.min(p1.getX(), p2.getX()),
Math.max(p1.getY(), p2.getY()) - Math.min(p1.getY(), p2.getY()));
}
}
考虑这个重构:
public class MapCanvas extends Canvas {
// ...
// public void init(Model model) throws NonInvertibleTransformException {
// this.model = model;
// pan(-model.min_x,-model.min_y);
// zoom(getWidth()/(model.max_x-model.min_x), new Point2D(0,0));
// }
public void init(Model model) throws NonInvertibleTransformException {
this.model = model;
init();
}
private void init() throws NonInvertibleTransformException {
pan(-model.min_x,-model.min_y);
zoom(getWidth()/(model.max_x-model.min_x), new Point2D(0,0));
}
public void resetZoom() throws NonInvertibleTransformException {
trans.setToTransform(1, 0, 0, 1, 0, 0);
init();
}
// ...
}
然后从控制器调用 canvas.resetZoom()
应该重置为初始缩放设置。 trans.setToTransform(1, 0, 0, 1, 0, 0);
调用将仿射变换重置为标识(其初始状态),然后执行与原始 init(...)
方法相同的初始化。