All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.limetri.client.mapviewer.fx.JFXMapPane Maven / Gradle / Ivy

There is a newer version: 1.4.4
Show newest version
/**
 * Copyright (C) 2008-2012 AgroSense Foundation.
 *
 * AgroSense is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * There are special exceptions to the terms and conditions of the GPLv3 as it is applied to
 * this software, see the FLOSS License Exception
 * .
 *
 * AgroSense is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with AgroSense.  If not, see .
 */
package eu.limetri.client.mapviewer.fx;

import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.BlendMode;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;


import eu.limetri.client.mapviewer.data.AbstractMapViewer;
import eu.limetri.client.mapviewer.data.GeoPosition;
import eu.limetri.client.mapviewer.data.MapViewer;
import eu.limetri.client.mapviewer.data.Tile;
import eu.limetri.client.mapviewer.data.TileFactory;
import eu.limetri.client.mapviewer.data.TileFactoryInfo;
import eu.limetri.client.mapviewer.data.common.TileFactoryInfoSelectionEventHandler;
import eu.limetri.client.mapviewer.data.tilefactory.OSMTileFactoryInfo;
import eu.limetri.client.mapviewer.fx.impl.OfflineTileFactoryFX;

/**
 * Map browser component
 * 
 * @author Frantisek Post
 */
public class JFXMapPane extends StackPane implements MapViewer>{

	private static final int TILE_SIZE = 256;
	public static final String ZOOM = "zoom";
	
	private Canvas canvas = new Canvas();
	private AbstractMapViewer> abstractMapViewer;
	private TileLoadListener tileLoadListener = new TileLoadListener();
	private Image loadingImage;

	private double prevX;
	private double prevY;
	
	private boolean zoomSelectActive = false;
	private double zoomX;
	private double zoomY;

	public static final int MIN_ZOOM = 1;
	public static final int MAX_ZOOM = 17;

	private WritableImage savedCanvas;
	private PropertyChangeSupport propertyChangeSupport;
	private boolean zoomEnabled = false;
	private boolean typeSelectionEnabled = false;
	
	private AnchorPane rightPane = new AnchorPane();
	private ZoomPanel zoomPanel;
	private MapTypeSelectionPanelFX typeSelectionPanel;
	
	private final class TileLoadListener implements PropertyChangeListener {

		public void propertyChange(PropertyChangeEvent evt) {
			if ("loaded".equals(evt.getPropertyName())
					&& Boolean.TRUE.equals(evt.getNewValue())) {
				Tile t = (Tile) evt.getSource();
				if (t.getZoom() == abstractMapViewer.getZoom()) {
					Platform.runLater(new Runnable() {

						@Override
						public void run() {
							paintTiles();
						}

					});
				}
			}
		}

	}

	/**
	 *  Constructor
	 */
	public JFXMapPane() {
		super();
		canvas.setBlendMode(BlendMode.MULTIPLY);
		canvas.widthProperty().bind(widthProperty());
		canvas.heightProperty().bind(heightProperty());
		getChildren().add(canvas);

		initMapViewer();
		loadingImage = new Image(getClass().getResourceAsStream("loading.png"));

		setOnMousePressed(new EventHandler() {

			@Override
			public void handle(MouseEvent event) {
				prevX = event.getX();
				prevY = event.getY();
				savedCanvas = null;
			}

		});

		setOnMouseReleased(new EventHandler() {

			@Override
			public void handle(MouseEvent event) {
				if (zoomSelectActive) {
					zoomSelectActive = false;
					abstractMapViewer.zoomToCoordinates(prevX, prevY, event.getX(), event.getY());
				}
			}
			
		});
		
		setOnMouseDragged(new EventHandler() {

			@Override
			public void handle(MouseEvent event) {
				if (event.isControlDown()) {
					zoomSelectActive = true;
					zoomX = event.getX();
					zoomY = event.getY();
					paintZoomRectangle();
				} else {
					zoomSelectActive = false;
				double mouseX = event.getX();
				double mouseY = event.getY();

				double x = abstractMapViewer.getCenter().getX()
						- (mouseX - prevX);
				double y = abstractMapViewer.getCenter().getY()
						- (mouseY - prevY);

				int maxHeight = (int) (abstractMapViewer.getTileFactory()
						.getMapSize(abstractMapViewer.getZoom()).getHeight() * abstractMapViewer
						.getTileFactory().getTileSize(
								abstractMapViewer.getZoom()));
				if (y > maxHeight) {
					y = maxHeight;
				}

				abstractMapViewer.setCenter(new Point2D.Double(x, y));
				paintTiles();
				
				prevX = mouseX;
				prevY = mouseY;
				}
			}

		});

		this.canvas.setOnScroll(new EventHandler() {
			@Override
			public void handle(ScrollEvent me) {
				if (me.getDeltaY() > 0) {
					if (getZoom() > MIN_ZOOM) {
						zoomOut();
					}
				} else {
					if (getZoom() < MAX_ZOOM) {
						zoomIn();
					}
				}
			}
		});

		ChangeListener changeListener = new ChangeListener() {
			
			@Override
			public void changed(ObservableValue observable, Number oldValue, Number newValue) {
				//TODO in demo invoked only when resized to bigger?
				refreshScene();
			}

		};
		
		widthProperty().addListener(changeListener);
		heightProperty().addListener(changeListener);
		
		propertyChangeSupport = new PropertyChangeSupport(this);
		
		rightPane = new AnchorPane();
		getChildren().add(rightPane);
		
		setZoomEnabled(true);
	}

	private void refreshScene() {
		paintTiles();
	}
	
	private void initMapViewer() {
		abstractMapViewer = new AbstractMapViewer>() {

			@Override
			protected void repaint() {
				paintTiles();
			}

			@Override
			protected int getWidth() {
				return (int) JFXMapPane.this.getWidth();
			}

			@Override
			protected java.awt.Insets getInsets() {
				return new java.awt.Insets(0, 0, 0, 0);
			}

			@Override
			protected int getHeight() {
				return (int) JFXMapPane.this.getHeight();
			}

			@Override
			protected void firePropertyChange(String name, Object oldValue,	Object newValue) {
				JFXMapPane.this.firePropertyChange(name, oldValue, newValue);
			}
		};

		abstractMapViewer.setTileFactory(new OfflineTileFactoryFX(new OSMTileFactoryInfo()));
	}

	protected void paintTiles() {
		
		//correcting map bounds
		Point2D center = abstractMapViewer.getCenter();
		double viewerHeight = getHeight();
        
		int maxHeight = (int) (getTileFactory().getMapSize(getZoom()).getHeight() * getTileFactory().getTileSize(getZoom()));
		
		double y = center.getY();
        if (y < (viewerHeight/2)) {
        	y = viewerHeight / 2;
        }
        
        if (y > maxHeight - (viewerHeight/2)) {
        	y = maxHeight - (viewerHeight/2);
        }
        
        if (y != center.getY()) {
        	abstractMapViewer.setCenter(new Point2D.Double(center.getX(), y));
        }
		
		int zoom = abstractMapViewer.getZoom();
		Rectangle viewportBounds = abstractMapViewer.getViewportBounds();
		drawMapTiles(zoom, viewportBounds);
		// drawOverlays(zoom, g, viewportBounds);
	}

	private void drawMapTiles(int zoom, Rectangle viewportBounds) {

		int size = abstractMapViewer.getTileFactory().getTileSize(zoom);

		// calculate the "visible" viewport area in tiles
		int numWide = viewportBounds.width / size + 2;
		int numHigh = viewportBounds.height / size + 2;

		TileFactoryInfo info = abstractMapViewer.getTileFactory().getInfo();
		int tpx = (int) Math.floor(viewportBounds.getX() / info.getTileSize(0));
		int tpy = (int) Math.floor(viewportBounds.getY() / info.getTileSize(0));

		GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
		graphicsContext.setFill(Color.GRAY);
		graphicsContext.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
		
		// p("top tile = " + topLeftTile);
		// fetch the tiles from the factory and store them in the tiles cache
		// attach the tileLoadListener
		for (int x = 0; x <= numWide; x++) {
			for (int y = 0; y <= numHigh; y++) {
				int itpx = x + tpx;// topLeftTile.getX();
				int itpy = y + tpy;// topLeftTile.getY();
				Tile tile = abstractMapViewer.getTileFactory().getTile(itpx,
						itpy, zoom);
				tile.addUniquePropertyChangeListener("loaded", tileLoadListener);

				int ox = ((itpx * abstractMapViewer.getTileFactory()
						.getTileSize(zoom)) - viewportBounds.x);
				int oy = ((itpy * abstractMapViewer.getTileFactory()
						.getTileSize(zoom)) - viewportBounds.y);

				if (tile.isLoaded()) {
					graphicsContext.drawImage(tile.getImage(), ox, oy);
				} else {
					//show resized image, while loading					
					Tile superTile = abstractMapViewer.getTileFactory().getTile(itpx / 2, itpy / 2, zoom + 1);
					if (superTile.isLoaded()) {
						
						int offX = (itpx % 2) * size / 2;
						int offY = (itpy % 2) * size / 2;
						
						graphicsContext.drawImage(superTile.getImage(), offX, offY, size / 2, size / 2, ox, oy, size, size);
						
					} else {
						Image image = getLoadingImage();
						double h = image.getHeight();
						double w = image.getWidth();
						double lx = TILE_SIZE / 2 - (w / 2);
						double ly = TILE_SIZE / 2 - (h / 2);

						graphicsContext.setFill(Color.GRAY);
						graphicsContext.fillRect(ox, oy, TILE_SIZE, TILE_SIZE);
						graphicsContext.drawImage(getLoadingImage(), ox + lx, oy + ly);
					}
				}
			}
		}

	}

	private void paintZoomRectangle() {
		if (zoomSelectActive) {
			GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
			if (savedCanvas == null) {
				savedCanvas = canvas.snapshot(null, null);
			} else {
				graphicsContext.drawImage(savedCanvas, 0, 0);
			}
			
			abstractMapViewer.fireRepaintCallbacks();
			
			graphicsContext.setLineWidth(3); //TODO color and width
			graphicsContext.setStroke(Color.RED);
			graphicsContext.strokeRect(Math.min(prevX, zoomX), Math.min(prevY, zoomY), Math.abs(zoomX-prevX), Math.abs(zoomY-prevY));
			
		}
	}

	protected void firePropertyChange(String propertyName, Object oldValue,	Object newValue) {
		propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
	}
	
	/**
	 * Set zoom level
	 * 
	 * @param zoom
	 */
	public void setZoom(int zoom) {
		abstractMapViewer.setZoom(zoom);
	}

	/**
	 * Set location of the center of the map
	 * 
	 * @param addressLocation
	 */
	public void setAddressLocation(GeoPosition addressLocation) {
		abstractMapViewer.setAddressLocation(addressLocation);
	}

	public Image getLoadingImage() {
		return loadingImage;
	}

	/**
	 * Returns current zoom level
	 * 
	 * @return
	 */
	public int getZoom() {
		return abstractMapViewer.getZoom();
	}

	/**
	 * Increase zoom level by one
	 */
	public void zoomIn() {
		if (getZoom() < MAX_ZOOM) {
			abstractMapViewer.setZoom(abstractMapViewer.getZoom() + 1);
		}
	}

	/**
	 * Decrease zoom level by one
	 */
	public void zoomOut() {
		if (getZoom() > MIN_ZOOM) {
			abstractMapViewer.setZoom(abstractMapViewer.getZoom() - 1);
		}
	}

	@Override
	public TileFactory> getTileFactory() {
		return abstractMapViewer.getTileFactory();
	}

	@Override
	public Rectangle2D getViewportBounds() {
		return abstractMapViewer.getViewportBounds();
	}
	
	public void setTileFactory(TileFactory> factory) {
		abstractMapViewer.setTileFactory(factory);
		paintTiles();
	}

	public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
	}

	public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
	}
	
	public void setZoomEnabled(boolean zoomEnabled) {
		if (this.zoomEnabled != zoomEnabled) {
			this.zoomEnabled= zoomEnabled;
			if (zoomEnabled) {
				enableZoom();
			} else {
				disableZoom();
			}
		}
	}
	
	public boolean isZoomEnabled() {
		return zoomEnabled ;
	}

	private void enableZoom() {
		zoomPanel = new ZoomPanel(this);
		zoomPanel.attach();
		AnchorPane.setTopAnchor(zoomPanel, 30d);
		AnchorPane.setRightAnchor(zoomPanel, 2d);
		rightPane.getChildren().add(zoomPanel);
	}
	
	private void disableZoom() {
		zoomPanel.detach();
		rightPane.getChildren().remove(zoomPanel);
	}
	
	public void setTypeSelectionEnabled(boolean typeSelectionEnabled, TileFactoryInfo...tileFactoryInfos) {
		if (this.typeSelectionEnabled != typeSelectionEnabled) {
			this.typeSelectionEnabled= typeSelectionEnabled;
			if (typeSelectionEnabled) {
				enableTypeSelection(tileFactoryInfos);
			} else {
				disableTypeSelection();
			}
		}
	}
	
	private void enableTypeSelection(TileFactoryInfo...tileFactoryInfos) {
		typeSelectionPanel = new MapTypeSelectionPanelFX();
		typeSelectionPanel.setItems(tileFactoryInfos);
		
		typeSelectionPanel.setOnSelectionChange(new TileFactoryInfoSelectionEventHandler() {
			
			@Override
			public void itemSelected(TileFactoryInfo tileFactoryInfo) {
				setTileFactory(new OfflineTileFactoryFX(tileFactoryInfo));
			}
			
		});
		typeSelectionPanel.setActiveTileFactoryInfo(getTileFactory().getInfo());
		
		AnchorPane.setTopAnchor(typeSelectionPanel, 2d);
		AnchorPane.setRightAnchor(typeSelectionPanel, 2d);
		rightPane.getChildren().add(typeSelectionPanel);
	}
	
	private void disableTypeSelection() {
		rightPane.getChildren().remove(typeSelectionPanel);
	}
	
	public boolean isTypeSelectionEnabled() {
		return typeSelectionEnabled;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy