I am making a map with Java, and I have managed to create methods for zooming in and out, but I want to implement a feature where I can reset the zoom level to default. So that the map is all zoomed out. I have tried to change the factor when using canvas.zoom(), but it doesent work. Anyone have any idea?
Here are my methods now. It's the TabResetBtnAction im trying to fix.
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>
controller:
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++;
}
Something along the lines of
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();
}
The sure part is reversing the zoom by going back to initialZoomFactor
.
Now you need to determine your pivot point which is unclear to me. Here, I'm trying to use the current center of the view so the drawings of the canvas approximately stay inside the view port.
They go completely out of focus if I don't set a pivot point or use (0, 0)
Usable 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()));
}
}