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

org.geomajas.gwt2.client.map.ViewPortImpl Maven / Gradle / Ivy

/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2013 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the GNU Affero
 * General Public License. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */

package org.geomajas.gwt2.client.map;

import org.geomajas.configuration.client.ClientLayerInfo;
import org.geomajas.configuration.client.ClientMapInfo;
import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.Coordinate;
import org.geomajas.geometry.Geometry;
import org.geomajas.geometry.Matrix;
import org.geomajas.geometry.service.BboxService;
import org.geomajas.geometry.service.GeometryService;
import org.geomajas.gwt.client.map.RenderSpace;
import org.geomajas.gwt2.client.event.ViewPortChangedEvent;
import org.geomajas.gwt2.client.event.ViewPortChangingEvent;
import org.geomajas.gwt2.client.event.ViewPortScaledEvent;
import org.geomajas.gwt2.client.event.ViewPortScalingEvent;
import org.geomajas.gwt2.client.event.ViewPortTranslatedEvent;
import org.geomajas.gwt2.client.event.ViewPortTranslatingEvent;
import org.geomajas.gwt2.client.map.ZoomStrategy.ZoomOption;

import com.google.inject.Inject;

/**
 * Implementation of the ViewPort interface.
 * 
 * @author Pieter De Graef
 */
public final class ViewPortImpl implements ViewPort {
	
	private ClientMapInfo mapInfo;

	/** The map's width in pixels. */
	private int mapWidth;

	/** The map's height in pixels. */
	private int mapHeight;

	/** The maximum bounding box available to this MapView. Never go outside it! */
	private Bbox maxBounds;

	private MapEventBus eventBus;

	private String crs;

	private ZoomStrategy zoomStrategy;

	// Current viewing parameters:

	private double scale;

	private Coordinate position;

	private boolean initialized;

	// -------------------------------------------------------------------------
	// Constructors:
	// -------------------------------------------------------------------------

	@Inject
	private ViewPortImpl() {
		position = new Coordinate();
	}

	// -------------------------------------------------------------------------
	// Configuration stuff:
	// -------------------------------------------------------------------------

	@Override
	public void initialize(ClientMapInfo mapInfo, MapEventBus eventBus) {
		this.mapInfo = mapInfo;
		this.eventBus = eventBus;
		crs = mapInfo.getCrs();

		// Calculate maximum bounds:
		maxBounds = new Bbox(mapInfo.getMaxBounds().getX(), mapInfo.getMaxBounds().getY(), mapInfo.getMaxBounds()
				.getWidth(), mapInfo.getMaxBounds().getHeight());

		// if the max bounds was not configured, take the union of initial and layer bounds
		if (BboxService.equals(maxBounds, Bbox.ALL, 1e-10)) {
			maxBounds = new Bbox(mapInfo.getInitialBounds().getX(), mapInfo.getInitialBounds().getY(), mapInfo
					.getInitialBounds().getWidth(), mapInfo.getInitialBounds().getHeight());
			if (mapInfo.getLayers() != null && mapInfo.getLayers().size() > 0) {
				for (ClientLayerInfo layerInfo : mapInfo.getLayers()) {
					if (layerInfo.getMaxExtent() != null) {
						maxBounds = BboxService.union(maxBounds, layerInfo.getMaxExtent());
					}
				}
			}
		}

		// Set the best zoom strategy given the map info:
		if (mapInfo.getScaleConfiguration().getZoomLevels() != null
				&& mapInfo.getScaleConfiguration().getZoomLevels().size() > 0) {
			zoomStrategy = new FixedStepZoomStrategy(mapInfo, maxBounds);
		} else {
			zoomStrategy = new FreeForAllZoomStrategy(mapInfo, maxBounds);
		}
		zoomStrategy.setMapSize(mapWidth, mapHeight);
		initialized = true;
	}

	@Override
	public Bbox getMaximumBounds() {
		return maxBounds;
	}

	@Override
	public void setMapSize(int width, int height) {
		if (this.mapWidth != width || this.mapHeight != height) {
			position = transform(new Coordinate(width / 2, height / 2), RenderSpace.SCREEN, RenderSpace.WORLD);
			this.mapWidth = width;
			this.mapHeight = height;
			if (zoomStrategy != null) {
				zoomStrategy.setMapSize(width, height);
			}
			if (eventBus != null) {
				eventBus.fireEvent(new ViewPortChangedEvent(this));
			}
		}
	}

	@Override
	public String getCrs() {
		return crs;
	}

	@Override
	public int getMapWidth() {
		return mapWidth;
	}

	@Override
	public int getMapHeight() {
		return mapHeight;
	}

	// -------------------------------------------------------------------------
	// Methods that retrieve what is visible on the map:
	// -------------------------------------------------------------------------

	@Override
	public Coordinate getPosition() {
		return new Coordinate(position);
	}

	@Override
	public double getScale() {
		return scale;
	}

	/**
	 * Given the information in this ViewPort object, what is the currently visible area? This value is expressed in
	 * world coordinates.
	 * 
	 * @return Returns the bounding box that covers the currently visible area on the map.
	 */
	public Bbox getBounds() {
		double w = mapWidth / scale;
		double h = mapHeight / scale;
		double x = position.getX() - w / 2;
		double y = position.getY() - h / 2;
		return new Bbox(x, y, w, h);
	}

	// -------------------------------------------------------------------------
	// Methods that manipulate what is visible on the map:
	// -------------------------------------------------------------------------

	@Override
	public ZoomStrategy getZoomStrategy() {
		return zoomStrategy;
	}

	@Override
	public void setZoomStrategy(ZoomStrategy zoomStrategy) {
		this.zoomStrategy = zoomStrategy;
	}

	@Override
	public void dragToPosition(Coordinate coordinate) {
		position = checkPosition(coordinate, scale);
		if (eventBus != null) {
			eventBus.fireEvent(new ViewPortTranslatingEvent(this));
		}
	}

	@Override
	public void applyPosition(Coordinate coordinate) {
		position = checkPosition(coordinate, scale);
		if (eventBus != null) {
			eventBus.fireEvent(new ViewPortTranslatedEvent(this));
		}
	}

	@Override
	public void dragToScale(double scale) {
		applyScale(scale, position, true);
	}

	@Override
	public void applyScale(double scale) {
		applyScale(scale, position, false);
	}

	@Override
	public void dragToScale(double newScale, Coordinate rescalePoint) {
		applyScale(newScale, rescalePoint, true);
	}

	@Override
	public void applyScale(double newScale, Coordinate rescalePoint) {
		applyScale(newScale, rescalePoint, false);
	}

	private void applyScale(double newScale, Coordinate rescalePoint, boolean dragging) {
		double limitedScale = zoomStrategy.checkScale(newScale, ZoomOption.LEVEL_CLOSEST);
		if (limitedScale != scale) {
			// Calculate theoretical new bounds. First create a BBOX of correct size:
			Bbox newBbox = new Bbox(0, 0, getMapWidth() / limitedScale, getMapHeight() / limitedScale);

			// Calculate translate vector to assure rescalePoint is on the same position as before.
			double factor = limitedScale / scale;
			double dX = (rescalePoint.getX() - position.getX()) * (1 - 1 / factor);
			double dY = (rescalePoint.getY() - position.getY()) * (1 - 1 / factor);

			// Apply translation to set the BBOX on the correct location:
			newBbox = BboxService.setCenterPoint(newBbox, new Coordinate(position.getX(), position.getY()));
			newBbox = BboxService.translate(newBbox, dX, dY);

			// Now apply on this view port:
			scale = limitedScale;
			position = checkPosition(BboxService.getCenterPoint(newBbox), scale);
			if (eventBus != null) {
				if (dX == 0 && dY == 0) {
					eventBus.fireEvent(dragging ? new ViewPortScalingEvent(this) : new ViewPortScaledEvent(this));
				} else {
					eventBus.fireEvent(dragging ? new ViewPortChangingEvent(this) : new ViewPortChangedEvent(this));
				}
			}
		}
	}

	@Override
	public void applyBounds(Bbox bounds) {
		applyBounds(bounds, ZoomOption.LEVEL_FIT);
	}

	@Override
	public void applyBounds(Bbox bounds, ZoomOption zoomOption) {
		double newScale = getScaleForBounds(bounds, zoomOption);
		Coordinate tempPosition = checkPosition(BboxService.getCenterPoint(bounds), newScale);
		if (newScale == scale) {
			if (!position.equals(tempPosition)) {
				position = tempPosition;
				if (eventBus != null) {
					eventBus.fireEvent(new ViewPortTranslatedEvent(this));
				}
			}
		} else {
			position = tempPosition;
			scale = newScale;
			if (eventBus != null) {
				eventBus.fireEvent(new ViewPortChangedEvent(this));
			}
		}
	}

	// ------------------------------------------------------------------------
	// ViewPort transformation methods:
	// ------------------------------------------------------------------------

	@Override
	public Coordinate transform(Coordinate coordinate, RenderSpace from, RenderSpace to) {
		switch (from) {
			case SCREEN:
				switch (to) {
					case SCREEN:
						return new Coordinate(coordinate);
					case WORLD:
						return screenToWorld(coordinate);
				}
			case WORLD:
				switch (to) {
					case SCREEN:
						return worldToScreen(coordinate);
					case WORLD:
						return new Coordinate(coordinate);
				}
		}
		return coordinate;
	}

	@Override
	public Geometry transform(Geometry geometry, RenderSpace from, RenderSpace to) {
		switch (from) {
			case SCREEN:
				switch (to) {
					case SCREEN:
						return GeometryService.clone(geometry);
					case WORLD:
						return screenToWorld(geometry);
				}
			case WORLD:
				switch (to) {
					case SCREEN:
						return worldToScreen(geometry);
					case WORLD:
						return GeometryService.clone(geometry);
				}
		}
		return geometry;
	}

	@Override
	public Bbox transform(Bbox bbox, RenderSpace from, RenderSpace to) {
		switch (from) {
			case SCREEN:
				switch (to) {
					case SCREEN:
						return new Bbox(bbox.getX(), bbox.getY(), bbox.getWidth(), bbox.getHeight());
					case WORLD:
						return screenToWorld(bbox);
				}
			case WORLD:
				switch (to) {
					case SCREEN:
						return worldToScreen(bbox);
					case WORLD:
						return new Bbox(bbox.getX(), bbox.getY(), bbox.getWidth(), bbox.getHeight());
				}
		}
		return bbox;
	}

	@Override
	public Matrix getTransformationMatrix(RenderSpace from, RenderSpace to) {
		switch (from) {
			case SCREEN:
				switch (to) {
					case SCREEN:
						return Matrix.IDENTITY;
					case WORLD:
						throw new RuntimeException("Not implemented.");
				}
			case WORLD:
				switch (to) {
					case SCREEN:
						if (scale > 0) {
							double dX = -(position.getX() * scale) + mapWidth / 2;
							double dY = position.getY() * scale + mapHeight / 2;
							return new Matrix(scale, 0, 0, -scale, dX, dY);
						}
						return new Matrix(1, 0, 0, 1, 0, 0);
					case WORLD:
						return Matrix.IDENTITY;
				}
		}
		return null;
	}

	@Override
	public Matrix getTranslationMatrix(RenderSpace from, RenderSpace to) {
		switch (from) {
			case SCREEN:
				switch (to) {
					case SCREEN:
						return Matrix.IDENTITY;
					case WORLD:
						throw new RuntimeException("Not implemented.");
				}
			case WORLD:
				switch (to) {
					case SCREEN:
						if (scale > 0) {
							double dX = -(position.getX() * scale) + mapWidth / 2;
							double dY = position.getY() * scale + mapHeight / 2;
							return new Matrix(1, 0, 0, 1, dX, dY);
						}
						return new Matrix(1, 0, 0, 1, 0, 0);
					case WORLD:
						return Matrix.IDENTITY;
				}
		}
		return null;
	}

	@Override
	public boolean isInitialized() {
		return initialized;
	}

	@Override
	public double toScale(double scaleDenominator) {
		return mapInfo.getUnitLength() / (mapInfo.getPixelLength() * scaleDenominator);
	}

	// -------------------------------------------------------------------------
	// Private functions:
	// -------------------------------------------------------------------------

	private double getScaleForBounds(Bbox bounds, ZoomOption zoomOption) {
		double wRatio;
		double boundsWidth = bounds.getWidth();
		if (boundsWidth <= 0) {
			wRatio = zoomStrategy.getMinimumScale();
		} else {
			wRatio = mapWidth / boundsWidth;
		}
		double hRatio;
		double boundsHeight = bounds.getHeight();
		if (boundsHeight <= 0) {
			hRatio = zoomStrategy.getMinimumScale();
		} else {
			hRatio = mapHeight / boundsHeight;
		}
		// Return the checked scale for the minimum to fit inside:
		return zoomStrategy.checkScale(wRatio < hRatio ? wRatio : hRatio, zoomOption);
	}

	private Coordinate checkPosition(final Coordinate newPosition, final double newScale) {
		double xCenter = newPosition.getX();
		double yCenter = newPosition.getY();
		if (maxBounds != null) {
			double w = mapWidth / (newScale * 2);
			double h = mapHeight / (newScale * 2);
			Coordinate minCoordinate = BboxService.getOrigin(maxBounds);
			Coordinate maxCoordinate = BboxService.getEndPoint(maxBounds);

			if ((w * 2) > maxBounds.getWidth()) {
				xCenter = BboxService.getCenterPoint(maxBounds).getX();
			} else {
				if ((xCenter - w) < minCoordinate.getX()) {
					xCenter = minCoordinate.getX() + w;
				}
				if ((xCenter + w) > maxCoordinate.getX()) {
					xCenter = maxCoordinate.getX() - w;
				}
			}
			if ((h * 2) > maxBounds.getHeight()) {
				yCenter = BboxService.getCenterPoint(maxBounds).getY();
			} else {
				if ((yCenter - h) < minCoordinate.getY()) {
					yCenter = minCoordinate.getY() + h;
				}
				if ((yCenter + h) > maxCoordinate.getY()) {
					yCenter = maxCoordinate.getY() - h;
				}
			}
		}
		return new Coordinate(xCenter, yCenter);
	}

	// -------------------------------------------------------------------------
	// Private Transformation methods:
	// -------------------------------------------------------------------------

	private Coordinate worldToScreen(Coordinate coordinate) {
		if (coordinate != null) {
			double x = coordinate.getX() * scale;
			double y = -coordinate.getY() * scale;
			double translateX = -(position.getX() * scale) + (mapWidth / 2);
			double translateY = (position.getY() * scale) + (mapHeight / 2);
			x += translateX;
			y += translateY;
			return new Coordinate(x, y);
		}
		return null;
	}

	private Geometry worldToScreen(Geometry geometry) {
		if (geometry != null) {
			Geometry result = new Geometry(geometry.getGeometryType(), geometry.getSrid(), geometry.getPrecision());
			if (geometry.getGeometries() != null) {
				Geometry[] transformed = new Geometry[geometry.getGeometries().length];
				for (int i = 0; i < geometry.getGeometries().length; i++) {
					transformed[i] = worldToScreen(geometry.getGeometries()[i]);
				}
				result.setGeometries(transformed);
			}
			if (geometry.getCoordinates() != null) {
				Coordinate[] transformed = new Coordinate[geometry.getCoordinates().length];
				for (int i = 0; i < geometry.getCoordinates().length; i++) {
					transformed[i] = worldToScreen(geometry.getCoordinates()[i]);
				}
				result.setCoordinates(transformed);
			}
			return result;
		}
		throw new IllegalArgumentException("Cannot transform null geometry.");
	}

	private Bbox worldToScreen(Bbox bbox) {
		if (bbox != null) {
			Coordinate c1 = worldToScreen(BboxService.getOrigin(bbox));
			Coordinate c2 = worldToScreen(BboxService.getEndPoint(bbox));
			double x = (c1.getX() < c2.getX()) ? c1.getX() : c2.getX();
			double y = (c1.getY() < c2.getY()) ? c1.getY() : c2.getY();
			return new Bbox(x, y, Math.abs(c1.getX() - c2.getX()), Math.abs(c1.getY() - c2.getY()));
		}
		return null;
	}

	private Coordinate screenToWorld(Coordinate coordinate) {
		if (coordinate != null) {
			double inverseScale = 1 / scale;
			double x = coordinate.getX() * inverseScale;
			double y = -coordinate.getY() * inverseScale;

			double w = mapWidth / scale;
			double h = mapHeight / scale;
			// -cam: center X axis around cam. +bbox.w/2: to place the origin in the center of the screen
			double translateX = -position.getX() + (w / 2);
			double translateY = -position.getY() - (h / 2); // Inverted Y-axis here...
			x -= translateX;
			y -= translateY;
			return new Coordinate(x, y);
		}
		return null;
	}

	private Geometry screenToWorld(Geometry geometry) {
		if (geometry != null) {
			Geometry result = new Geometry(geometry.getGeometryType(), geometry.getSrid(), geometry.getPrecision());
			if (geometry.getGeometries() != null) {
				Geometry[] transformed = new Geometry[geometry.getGeometries().length];
				for (int i = 0; i < geometry.getGeometries().length; i++) {
					transformed[i] = screenToWorld(geometry.getGeometries()[i]);
				}
				result.setGeometries(transformed);
			}
			if (geometry.getCoordinates() != null) {
				Coordinate[] transformed = new Coordinate[geometry.getCoordinates().length];
				for (int i = 0; i < geometry.getCoordinates().length; i++) {
					transformed[i] = screenToWorld(geometry.getCoordinates()[i]);
				}
				result.setCoordinates(transformed);
			}
			return result;
		}
		throw new IllegalArgumentException("Cannot transform null geometry.");
	}

	private Bbox screenToWorld(Bbox bbox) {
		if (bbox != null) {
			Coordinate c1 = screenToWorld(BboxService.getOrigin(bbox));
			Coordinate c2 = screenToWorld(BboxService.getEndPoint(bbox));
			double x = (c1.getX() < c2.getX()) ? c1.getX() : c2.getX();
			double y = (c1.getY() < c2.getY()) ? c1.getY() : c2.getY();
			return new Bbox(x, y, Math.abs(c1.getX() - c2.getX()), Math.abs(c1.getY() - c2.getY()));
		}
		return null;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy