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

org.geomajas.gwt2.client.gfx.GeometryPath Maven / Gradle / Ivy

/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2015 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.gfx;

import java.util.ArrayList;
import java.util.List;

import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.Coordinate;
import org.geomajas.geometry.Geometry;
import org.geomajas.geometry.service.BboxService;
import org.vaadin.gwtgraphics.client.Shape;
import org.vaadin.gwtgraphics.client.VectorObject;
import org.vaadin.gwtgraphics.client.shape.Path;
import org.vaadin.gwtgraphics.client.shape.path.ClosePath;
import org.vaadin.gwtgraphics.client.shape.path.LineTo;
import org.vaadin.gwtgraphics.client.shape.path.MoveTo;
import org.vaadin.gwtgraphics.client.shape.path.PathStep;
import org.vaadin.gwtgraphics.client.shape.path.ScaleHelper;

/**
 * Path based on a coordinate array. Can be used to represent a geometry.
 * 
 * @author Jan De Moerloose
 */
public class GeometryPath extends Shape {

	private List steps = new ArrayList();

	private boolean closed;

	private Coordinate[] coordinates;

	private List subPaths = new ArrayList();

	private Bbox bounds;

	private boolean skipTransform;

	/**
	 * Create a zero-length path at (0,0).
	 * 
	 * @param closed if true, the path is closed or a ring, else the path is open or a line.
	 */
	public GeometryPath(boolean closed) {
		this(new Coordinate[] { new Coordinate(), new Coordinate() }, closed);
	}

	/**
	 * Create a path with these coordinates.
	 * 
	 * @param coordinates
	 * @param closed if true, path is closed
	 */
	public GeometryPath(Coordinate[] coordinates, boolean closed) {
		super(0, 0);
		setClosed(closed);
		setCoordinates(coordinates);
	}

	/**
	 * Create a path with this Geometry.
	 * 
	 * @param geometry
	 */
	public GeometryPath(Geometry geometry) {
		super(0, 0);
		setGeometry(geometry);
	}

	public void setGeometry(Geometry geometry) {
		setClosed(
				Geometry.POLYGON.equals(geometry.getGeometryType())
				|| Geometry.MULTI_POLYGON.equals(geometry.getGeometryType())
				|| Geometry.LINEAR_RING.equals(geometry.getGeometryType()));
		skipTransform = true;
		try {
			if (Geometry.LINE_STRING.equals(geometry.getGeometryType())) {
				setLineString(geometry);
			} else if (Geometry.LINEAR_RING.equals(geometry.getGeometryType())) {
				setLinearRing(geometry);
			} else if (Geometry.POLYGON.equals(geometry.getGeometryType())) {
				setPolygon(geometry);
			} else if (Geometry.MULTI_LINE_STRING.equals(geometry.getGeometryType())) {
				setMultiLineString(geometry);
			} else if (Geometry.MULTI_POLYGON.equals(geometry.getGeometryType())) {
				setMultiPolygon(geometry);
			} else {
				throw new IllegalArgumentException("Unsupported geometry " + geometry.getGeometryType());
			}
		} finally {
			skipTransform = false;
			drawTransformed();
		}
	}

	@Override
	protected Class getType() {
		return Path.class;
	}

	public void setCoordinates(Coordinate[] coordinates) {
		this.coordinates = coordinates;
		updateCoordinates();
	}

	public Coordinate[] getCoordinates() {
		return coordinates;
	}

	public Coordinate getLastCoordinate() {
		return (Coordinate) coordinates[coordinates.length - 1].clone();
	}

	/**
	 * Add a new sub-path to the path. A path can have multiple sub-paths. Sub-paths may be disconnected lines or rings
	 * but, thanks to even-odd rule, holes can be added as well.
	 * 
	 * @param coordinates
	 * @param closed
	 */
	public void addSubPath(Coordinate[] coordinates, boolean closed) {
		subPaths.add(new SubPath(coordinates, closed));
		updateCoordinates();

	}

	/**
	 * Add a coordinate to the path.
	 * 
	 * @param coordinate
	 */
	public void addCoordinate(Coordinate coordinate) {
		Coordinate[] newCoords = new Coordinate[coordinates.length + 1];
		System.arraycopy(coordinates, 0, newCoords, 0, coordinates.length);
		newCoords[coordinates.length] = coordinate;
		setCoordinates(newCoords);
	}

	/**
	 * Move the coordinate at the specified index.
	 * 
	 * @param coordinate the new coordinate
	 * @param index
	 */
	public void moveCoordinate(Coordinate coordinate, int index) {
		if (index < coordinates.length) {
			coordinates[index] = (Coordinate) coordinate.clone();
		}
		setCoordinates(coordinates);
	}

	public int getCoordinateCount() {
		return coordinates.length;
	}

	public void setUserPosition(Coordinate position) {
		double x = coordinates[0].getX();
		double y = coordinates[0].getY();
		double dX = position.getX() - x;
		double dY = position.getY() - y;
		translateCoordinates(coordinates, dX, dY);
		for (SubPath s : subPaths) {
			translateCoordinates(s.getCoordinates(), dX, dY);
		}
		setCoordinates(coordinates);
		super.setUserX(x);
		super.setUserY(y);
	}

	@Override
	public void setUserX(double userX) {
		// called by Shape constructor !
		if (coordinates != null) {
			setUserPosition(new Coordinate(userX, getUserPosition().getY()));
		}
	}

	@Override
	public void setUserY(double userY) {
		// called by Shape constructor !
		if (coordinates != null) {
			setUserPosition(new Coordinate(getUserPosition().getX(), userY));
		}
	}

	public Coordinate getUserPosition() {
		return (Coordinate) coordinates[0].clone();
	}

	public void setUserBounds(Bbox bounds) {
		double width = this.bounds.getWidth();
		double height = this.bounds.getHeight();

		double newWidth = bounds.getWidth();
		double newHeight = bounds.getHeight();

		double scaleX = width == 0 ? 1 : newWidth / width;
		double scaleY = height == 0 ? 1 : newHeight / height;

		double x = this.bounds.getX();
		double y = this.bounds.getY();

		double newX = bounds.getX();
		double newY = bounds.getY();

		scaleCoordinates(coordinates, scaleX, scaleY, x, y, newX, newY);
		for (SubPath s : subPaths) {
			scaleCoordinates(s.getCoordinates(), scaleX, scaleY, x, y, newX, newY);
		}
		updateCoordinates();
	}

	public Bbox getUserbounds() {
		return this.bounds;
	}

	public Bbox getBounds() {
		// transform all points and calculate new bounds
		double x1 = getUserbounds().getX() * getScaleX() + getDeltaX();
		double y1 = getUserbounds().getY() * getScaleY() + getDeltaY();
		double x2 = (getUserX() + getUserbounds().getWidth()) * getScaleX() + getDeltaX();
		double y2 = (getUserY() + getUserbounds().getHeight()) * getScaleY() + getDeltaY();
		return new Bbox(Math.round(Math.min(x1, x2)), Math.round(Math.min(y1, y2)), Math.abs(x1 - x2),
				Math.abs(y1 - y2));
	}

	public boolean isClosed() {
		return closed;
	}

	private void updateCoordinates() {
		this.bounds = calcBounds();
		this.steps = calcSteps();
		if (!skipTransform) {
			drawTransformed();
		}
	}

	protected List calcSteps() {
		List result = calcSteps(coordinates, closed);
		for (SubPath path : subPaths) {
			result.addAll(calcSteps(path.getCoordinates(), path.isClosed()));
		}
		return result;
	}

	private List calcSteps(Coordinate[] coordinates, boolean closed) {
		List result = new ArrayList();
		Coordinate prev = coordinates[0];
		result.add(new MoveTo(false, prev.getX(), prev.getY()));
		for (int i = 1; i < coordinates.length; i++) {
			Coordinate next = coordinates[i];
			result.add(new LineTo(true, next.getX() - prev.getX(), next.getY() - prev.getY()));
			prev = next;
		}
		if (closed) {
			result.add(new ClosePath());
		}
		return result;
	}

	protected Bbox calcBounds() {
		Bbox result = calcBounds(coordinates);
		for (SubPath s : subPaths) {
			result = BboxService.union(result, calcBounds(s.getCoordinates()));
		}
		return result;
	}

	private Bbox calcBounds(Coordinate[] coordinates) {
		Coordinate c = coordinates[0];
		Bbox result = new Bbox(c.getX(), c.getY(), 0, 0);
		for (int i = 1; i < coordinates.length; i++) {
			c = coordinates[i];
			result = BboxService.union(result, new Bbox(c.getX(), c.getY(), 0, 0));
		}
		return result;
	}

	protected void drawTransformed() {
		if (steps != null) {
			// apply translation
			MoveTo moveTo = (MoveTo) steps.get(0);
			steps.set(0, new MoveTo(moveTo.isRelativeCoords(), moveTo.getUserX() + getDeltaX(), moveTo.getUserY()
					+ getDeltaY()));
			// apply scale
			ScaleHelper scaleHelper = new ScaleHelper(getScaleX(), getScaleY());
			for (PathStep step : steps) {
				step.scale(scaleHelper);
			}
			getImpl().drawPath(getElement(), steps);
		}
	}

	private void scaleCoordinates(Coordinate[] coordinates, double scaleX, double scaleY, double x, double y,
			double newX, double newY) {
		for (int i = 0; i < coordinates.length; i++) {
			coordinates[i].setX(newX + scaleX * (coordinates[i].getX() - x));
			coordinates[i].setY(newY + scaleY * (coordinates[i].getY() - y));
		}
	}

	private void translateCoordinates(Coordinate[] coordinates, double dX, double dY) {
		for (int i = 0; i < coordinates.length; i++) {
			coordinates[i].setX(coordinates[i].getX() + dX);
			coordinates[i].setY(coordinates[i].getY() + dY);
		}
	}

	private void setMultiPolygon(Geometry multiPolygon) {
		if (multiPolygon.getGeometries() != null && multiPolygon.getGeometries().length > 0) {
			setPolygon(multiPolygon.getGeometries()[0]);
			for (int i = 1; i < multiPolygon.getGeometries().length; i++) {
				Geometry polygon = multiPolygon.getGeometries()[i];
				for (int j = 0; j < polygon.getGeometries().length; j++) {
					Geometry ring = polygon.getGeometries()[j];
					Coordinate[] pathCoords = removeLastCoordinate(ring);
					addSubPath(pathCoords, true);
				}
			}
		}
	}

	private void setMultiLineString(Geometry multiLineString) {
		if (multiLineString.getGeometries() != null && multiLineString.getGeometries().length > 0) {
			setLineString(multiLineString.getGeometries()[0]);
			for (int i = 1; i < multiLineString.getGeometries().length; i++) {
				Geometry lineString = multiLineString.getGeometries()[i];
				addSubPath(clone(lineString.getCoordinates()), false);
			}
		}
	}

	private void setPolygon(Geometry polygon) {
		if (polygon.getGeometries() != null && polygon.getGeometries().length > 0) {
			setLinearRing(polygon.getGeometries()[0]);
			getElement().getStyle().setProperty("fillRule", "evenOdd");
			for (int i = 1; i < polygon.getGeometries().length; i++) {
				Geometry ring = polygon.getGeometries()[i];
				Coordinate[] pathCoords = removeLastCoordinate(ring);
				if (pathCoords != null && pathCoords.length > 0) {
					addSubPath(pathCoords, true);
				}
			}
		}
	}

	private void setLinearRing(Geometry linearRing) {
		Coordinate[] coordinates = linearRing.getCoordinates();
		if (coordinates != null && coordinates.length > 1) {
			Coordinate[] pathCoords = removeLastCoordinate(linearRing);
			setClosed(true);
			setCoordinates(pathCoords);
		}
	}

	private void setLineString(Geometry lineString) {
		if (lineString.getCoordinates() != null && lineString.getCoordinates().length > 0) {
			setCoordinates(clone(lineString.getCoordinates()));
		}
	}

	private Coordinate[] clone(Coordinate[] coordinates) {
		Coordinate[] copy = new Coordinate[coordinates.length];
		System.arraycopy(coordinates, 0, copy, 0, coordinates.length);
		return copy;
	}

	private Coordinate[] removeLastCoordinate(Geometry ring) {
		if (ring.getCoordinates() == null) {
			return new Coordinate[0];
		}
		Coordinate[] coordinates = ring.getCoordinates();
		Coordinate[] pathCoords = new Coordinate[coordinates.length - 1];
		System.arraycopy(coordinates, 0, pathCoords, 0, coordinates.length - 1);
		return pathCoords;
	}

	private void setClosed(boolean closed) {
		this.closed = closed;
		if (!closed) {
			setFillOpacity(0);
		}
	}

	/**
	 * Inner class for tracking sub-paths.
	 * 
	 * @author Jan De Moerloose
	 */
	class SubPath {

		private Coordinate[] coordinates;

		private boolean closed;

		public SubPath(Coordinate[] coordinates, boolean closed) {
			this.coordinates = coordinates;
			this.closed = closed;
		}

		public Coordinate[] getCoordinates() {
			return coordinates;
		}

		public boolean isClosed() {
			return closed;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy