[Schmitzm-commits] r531 - in branches/1.0-gt2-2.6/src: gtmig/org/geotools/swing schmitzm/geotools/gui schmitzm/geotools/map/event schmitzm/geotools/styling skrueger/geotools
scm-commit@wald.intevation.org
scm-commit at wald.intevation.org
Thu Nov 19 10:43:12 CET 2009
Author: alfonx
Date: 2009-11-19 10:42:59 +0100 (Thu, 19 Nov 2009)
New Revision: 531
Added:
branches/1.0-gt2-2.6/src/skrueger/geotools/GeomFilterGenerator.java
branches/1.0-gt2-2.6/src/skrueger/geotools/MouseSelectionTracker.java
branches/1.0-gt2-2.6/src/skrueger/geotools/SelectXMapPaneMouseListener.java
branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java
branches/1.0-gt2-2.6/src/skrueger/geotools/ZoomXMapPaneMouseListener.java
Removed:
branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/GeomFilterGenerator.java
branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/MouseSelectionTracker.java
branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/SelectXMapPaneMouseListener.java
branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/XMapPane.java
branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/ZoomXMapPaneMouseListener.java
Modified:
branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/FeatureTablePane.java
branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/MouseSelectionTracker.java
branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/SelectableXMapPane.java
branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/FeatureSelectedEvent.java
branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/GeneralSelectionEvent.java
branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/GridCoverageSelectedEvent.java
branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/MapPaneEvent.java
branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/ObjectSelectionEvent.java
branches/1.0-gt2-2.6/src/schmitzm/geotools/styling/StylingUtil.java
branches/1.0-gt2-2.6/src/skrueger/geotools/RenderingExecutor.java
Log:
Moved XMapPane from gtmig to skrueger.geotools
Deleted: branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/GeomFilterGenerator.java
===================================================================
--- branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/GeomFilterGenerator.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/GeomFilterGenerator.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -1,249 +0,0 @@
-package gtmig.org.geotools.swing;
-
-import java.awt.geom.Point2D;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.geotools.data.FeatureSource;
-import org.geotools.filter.AbstractFilter;
-import org.geotools.filter.GeometryFilterImpl;
-import org.geotools.filter.spatial.DWithinImpl;
-import org.geotools.referencing.CRS;
-import org.geotools.resources.CRSUtilities;
-import org.opengis.feature.simple.SimpleFeature;
-import org.opengis.feature.simple.SimpleFeatureType;
-import org.opengis.feature.type.GeometryDescriptor;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.spatial.BinarySpatialOperator;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-
-import schmitzm.geotools.FilterUtil;
-import schmitzm.geotools.JTSUtil;
-
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.Geometry;
-
-/**
- * The {@link GeomFilterGenerator} prepares a {@link BinarySpatialOperator}
- * filter for multiple use. Only the "right" argument is prepared. The
- * "left" argument (the geometry attribute of the {@link FeatureSource} to
- * filter) is first set on calling {@link #adaptFilter(FeatureSource)} for a
- * specific {@link FeatureSource}. This method also takes care to recreate
- * the filter (or its "right" argument) if the given {@link FeatureSource}
- * has another {@link CoordinateReferenceSystem} than the base constraint.<br>
- * The type of filter (e.g. distance or bounding box) is specified by the
- * subclass implemenation of
- * {@link #prepareFilter(CoordinateReferenceSystem)} .
- *
- * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
- */
- public abstract class GeomFilterGenerator {
- /**
- * Holds the {@link CoordinateReferenceSystem} the filter constraint
- * bases on.
- */
- protected CoordinateReferenceSystem baseCRS = null;
- /**
- * Caches the base filter constraint for several
- * {@link CoordinateReferenceSystem CoordinateReferenceSystems}.
- */
- protected Map<CoordinateReferenceSystem, GeometryFilterImpl> filterCache = new HashMap<CoordinateReferenceSystem, GeometryFilterImpl>();
-
- /**
- * Creates a new filter generator
- *
- * @param crs
- * {@link CoordinateReferenceSystem} the base constraint
- * ("right" filter argument is relative to)
- */
- public GeomFilterGenerator(CoordinateReferenceSystem crs) {
- this.baseCRS = crs;
- }
-
- /**
- * Creates a filter containing the base constraint ("right" argument)
- * transformed to the given {@link CoordinateReferenceSystem}.
- *
- * @param crs
- * the {@link CoordinateReferenceSystem} the base constraint
- * is transformed to
- */
- protected abstract GeometryFilterImpl prepareFilter(
- CoordinateReferenceSystem crs);
-
- /**
- * Completes the filter with its "left" argument for a concrete
- * {@link FeatureSource}. If the {@link FeatureSource FeatureSource's}
- * CRS differs from the CRS the base constraint is specified in, first a
- * new filter is created by calling
- * {@link #prepareFilter(CoordinateReferenceSystem)}.
- *
- * @param fs
- * {@link FeatureSource} the filter is adaped to
- * @return
- */
- public GeometryFilterImpl adaptFilter(
- FeatureSource<SimpleFeatureType, SimpleFeature> fs) {
- GeometryDescriptor geomDescr = fs.getSchema()
- .getGeometryDescriptor();
- CoordinateReferenceSystem fsCRS = geomDescr
- .getCoordinateReferenceSystem();
- GeometryFilterImpl filter = filterCache.get(fsCRS);
- if (filter == null) {
- filter = prepareFilter(fsCRS);
- filterCache.put(fsCRS, filter);
- }
- Expression geometry = FilterUtil.FILTER_FAC2.property(geomDescr.getLocalName());
- filter.setExpression1(geometry);
- return filter;
- }
-
- /**
- * {@link GeomFilterGenerator} for a bounding box constraint.
- *
- * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
- */
- public static class BoundingBoxFilterGenerator extends GeomFilterGenerator {
- /**
- * Holds the base constraint (bounding box {@link Envelope}) relative to
- * the {@linkplain GeomFilterGenerator#baseCRS base CRS}.
- */
- protected Envelope baseEnv = null;
-
- /**
- * Creates a new filter generator.
- *
- * @param baseEnv
- * defines the bounding box
- * @param baseCrs
- * defines the CRS of the bounding box
- */
- public BoundingBoxFilterGenerator(Envelope baseEnv,
- CoordinateReferenceSystem baseCrs) {
- super(baseCrs);
- this.baseEnv = baseEnv;
- }
-
- /**
- * Prepares a filter with the bounding box transformed to the given
- * {@link CoordinateReferenceSystem} as the "right" argument.
- *
- * @param crs
- * the {@link CoordinateReferenceSystem} the bounding box is
- * transformed to
- */
- protected GeometryFilterImpl prepareFilter(CoordinateReferenceSystem crs) {
- Envelope bbEnv = baseEnv;
- if (!CRS.equalsIgnoreMetadata(baseCRS,crs))
- bbEnv = JTSUtil.transformEnvelope(baseEnv, baseCRS, crs);
- // Filter fuer Envelope zusammenstellen
- Expression bbox = FilterUtil.FILTER_FAC.createBBoxExpression(bbEnv);
- GeometryFilterImpl bboxFilter = (GeometryFilterImpl) FilterUtil.FILTER_FAC
- .createGeometryFilter(AbstractFilter.GEOMETRY_BBOX);
- // GeometryFilterImpl bboxFilter =
- // (GeometryFilterImpl)ff.createGeometryFilter(AbstractFilter.
- // GEOMETRY_WITHIN);
- bboxFilter.setExpression2(bbox);
- return bboxFilter;
- }
- }
-
- /**
- * {@link GeomFilterGenerator} for a "near distance" constraint.
- *
- * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
- */
- public static class NearPointFilterGenerator extends GeomFilterGenerator {
- /**
- * Holds the base constraint (coordinate) relative to the
- * {@linkplain GeomFilterGenerator#baseCRS base CRS}.
- */
- protected Coordinate basePoint = null;
- /**
- * Holds the distance "around" the base point relative to the
- * {@linkplain GeomFilterGenerator#baseCRS base CRS}.
- */
- protected double baseDist = 0.0;
- /**
- * Holds a point which is in distance {@link #baseDist} to the
- * {@link #basePoint}.
- */
- protected Coordinate basePoint2 = null;
-
- /**
- * Creates a new filter generator.
- *
- * @param basePoint
- * defNearPointFilterGeneratorines the point for the "near point" constraint
- * @param dist
- * defines the distance around the base point
- * @param crs
- * defines the CRS of base point
- */
- public NearPointFilterGenerator(Coordinate basePoint, double dist,
- CoordinateReferenceSystem crs) {
- super(crs);
- this.basePoint = basePoint;
- this.baseDist = dist;
- // Create a point which is in distance "dist" to the base point
- this.basePoint2 = new Coordinate(basePoint.x + dist, basePoint.y);
- }
-
- /**
- * Creates a new filter generator.
- *
- * @param basePoint
- * defines the point for the "near point" constraint
- * @param dist
- * defines the distance around the base point
- * @param crs
- * defines the CRS of base point
- */
- public NearPointFilterGenerator(Point2D basePoint, double dist,
- CoordinateReferenceSystem crs) {
- this(new Coordinate(basePoint.getX(), basePoint.getY()), dist, crs);
- }
-
- /**
- * Prepares a filter with the base point and distance transformed to the
- * given {@link CoordinateReferenceSystem} as the "right" argument.
- *
- * @param crs
- * the {@link CoordinateReferenceSystem} the point and
- * distance is transformed to
- */
- protected GeometryFilterImpl prepareFilter(CoordinateReferenceSystem crs) {
- Coordinate nearPoint = basePoint;
- double nearDist = baseDist;
- if (!baseCRS.equals(crs)) {
- nearPoint = JTSUtil
- .transformCoordinate(basePoint, baseCRS, crs);
- // Transform the distance (maybe "dirty")
- // --> transform the point2 and calculate the
- // distance to the tranformed base point
- Coordinate nearPoint2 = JTSUtil.transformCoordinate(basePoint2,
- baseCRS, crs);
-
- if (nearPoint == null || nearPoint2 == null)
- throw new RuntimeException("Unable to transform CRS from "
- + baseCRS + " to " + crs);
-
- nearDist = Math.abs(nearPoint.x - nearPoint2.x);
- }
- // Filter fuer Point zusammenstellen
- final Geometry geometry = FilterUtil.GEOMETRY_FAC
- .createPoint(nearPoint);
-
- final DWithinImpl dwithinFilter = new DWithinImpl(
- FilterUtil.FILTER_FAC2, null, null);
- dwithinFilter.setDistance(nearDist);
- dwithinFilter.setExpression2(FilterUtil.FILTER_FAC2
- .literal(geometry));
-
- return dwithinFilter;
- }
-
- }
-
-}
\ No newline at end of file
Deleted: branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/MouseSelectionTracker.java
===================================================================
--- branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/MouseSelectionTracker.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/MouseSelectionTracker.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -1,377 +0,0 @@
-// Migration process to Geotools 2.6:
-// Because not included in gt2-2.6-M2 this class is taken 1:1
-// from Geotools 2.4.5 and made public.
-// TODO: It should be removed or included in SCHMITZM.
-
-/*
- * GeoTools - OpenSource mapping toolkit
- * http://geotools.org
- * (C) 2003-2006, Geotools Project Managment Committee (PMC)
- * (C) 2001, Institut de Recherche pour le D�veloppement
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library 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
- * Lesser General Public License for more details.
- */
-package gtmig.org.geotools.swing;
-
-// Geometry
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Graphics2D;
-import java.awt.Rectangle;
-import java.awt.Shape;
-import java.awt.event.MouseEvent;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Ellipse2D;
-import java.awt.geom.Line2D;
-import java.awt.geom.NoninvertibleTransformException;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-import java.awt.geom.RectangularShape;
-import java.awt.geom.RoundRectangle2D;
-
-import javax.swing.event.MouseInputAdapter;
-
-
-/**
- * Controller which allows the user to select a region of a component. The user must click on a
- * point in the component, then drag the mouse pointer whilst keeping the button pressed. During
- * the dragging, the shape which is drawn will normally be a rectangle. Other shapes could always
- * be used such as, for example, an ellipse. To use this class, it is necessary to create a derived
- * class which defines the following methods:
- *
- * <ul>
- * <li>{@link #selectionPerformed} (obligatory)</li>
- * <li>{@link #getModel} (optional)</li>
- * </ul>
- *
- * This controller should then be registered with one, and only one, component
- * using the following syntax:
- *
- * <blockquote><pre>
- * {@link Component} component=...
- * MouseSelectionTracker control=...
- * component.addMouseListener(control);
- * </pre></blockquote>
- *
- * @since 2.0
- * @source $URL: http://svn.geotools.org/tags/2.4.5/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/MouseSelectionTracker.java $
- * @version $Id: MouseSelectionTracker.java 22482 2006-10-31 02:58:00Z desruisseaux $
- * @author Martin Desruisseaux
- */
-public abstract class MouseSelectionTracker extends MouseInputAdapter {
- /**
- * Stippled rectangle representing the region which the user is currently
- * selecting. This rectangle can be empty. These coordinates are only
- * significant in the period between the user pressing the mouse button
- * and then releasing it to outline a region. Conventionally, the
- * {@code null} value indicates that a line should be used instead of
- * a rectangular shape. The coordinates are always expressed in pixels.
- */
- private transient RectangularShape mouseSelectedArea;
-
- /**
- * Colour to replace during XOR drawings on a graphic.
- * This colour is specified in {@link Graphics2D#setColor}.
- */
- private Color backXORColor = Color.white;
-
- /**
- * Colour to replace with during the XOR drawings on a graphic.
- * This colour is specified in {@link Graphics2D#setXORMode}.
- */
- private Color lineXORColor = Color.black;
-
- /**
- * <var>x</var> coordinate of the mouse when the button is pressed.
- */
- private transient int ox;
-
- /**
- * <var>y</var> coordinate of the mouse when the button is pressed.
- */
- private transient int oy;
-
- /**
- * <var>x</var> coordinate of the mouse during the last drag.
- */
- private transient int px;
-
- /**
- * <var>y</var> coordinate of the mouse during the last drag.
- */
- private transient int py;
-
- /**
- * Indicates whether a selection is underway.
- */
- private transient boolean isDragging;
-
- /**
- * Constructs an object which will allow rectangular regions to be selected using the mouse.
- */
- public MouseSelectionTracker() {
- }
-
- /**
- * Specifies the colours to be used for drawing the outline of a box when
- * the user selects a region. All {@code a} colours will be replaced
- * by {@code b} colours and vice versa.
- */
- public void setXORColors(final Color a, final Color b) {
- backXORColor = a;
- lineXORColor = b;
- }
-
- /**
- * Returns the geometric shape to use for marking the boundaries of a region. This shape is
- * normally a rectangle but could also be an ellipse, an arrow or even other shapes. The
- * coordinates of the returned shape will not be taken into account. In fact, these coordinates
- * will regularly be discarded. Only the class of the returned shape will count (for example,
- * {@link java.awt.geom.Ellipse2D} vs {@link java.awt.geom.Rectangle2D}) and their parameters
- * which are not linked to their position (for example, the rounding of a rectangle's
- * corners).
- * <p>
- * The shape returned will normally be from a class derived from {@link RectangularShape},
- * but could also be from the {@link Line2D} class. <strong>Any other class risks throwing a
- * {@link ClassCastException} when executed</strong>.
- *
- * The default implementation always returns an object {@link Rectangle}.
- *
- * @param event Mouse coordinate when the button is pressed. This information can be used by
- * the derived classes which like to be informed of the position of the mouse before
- * chosing a geometric shape.
- * @return Shape from the class {link RectangularShape} or {link Line2D}, or {@code null}
- * to indicate that we do not want to make a selection.
- */
- protected Shape getModel(final MouseEvent event) {
- return new Rectangle();
- }
-
- /**
- * Method which is automatically called after the user selects a region with the mouse.
- * All coordinates passed in as parameters are expressed in pixels.
- *
- * @param ox <var>x</var> coordinate of the mouse when the user pressed the mouse button.
- * @param oy <var>y</var> coordinate of the mouse when the user pressed the mouse button.
- * @param px <var>x</var> coordinate of the mouse when the user released the mouse button.
- * @param py <var>y</var> coordinate of the mouse when the user released the mouse button.
- */
- protected abstract void selectionPerformed(int ox, int oy, int px, int py);
-
- /**
- * Returns the geometric shape surrounding the last region to be selected by the user. An
- * optional affine transform can be specified to convert the region selected by the user
- * into logical coordinates. The class of the shape returned depends on the model returned by
- * {@link #getModel}:
- *
- * <ul>
- * <li>If the model is null (which means that this {@code MouseSelectionTracker} object only
- * draws a line between points), the object returned will belong to the {@link Line2D}
- * class.</li>
- * <li>If the model is not null, the object returned can be from the same class (most often
- * {@link java.awt.geom.Rectangle2D}). There could always be situations where the object
- * returned is from another class, for example if the affine transform carries out a
- * rotation.</li>
- * </ul>
- *
- * @param transform Affine transform which converts logical coordinates into pixel coordinates.
- * It is usually an affine transform which is used in a {@code paint(...)} method to
- * draw shapes expressed in logical coordinates.
- * @return A geometric shape enclosing the last region to be selected by the user, or
- * {@code null} if no selection has yet been made.
- * @throws NoninvertibleTransformException If the affine transform can't be inverted.
- */
- public Shape getSelectedArea(final AffineTransform transform) throws NoninvertibleTransformException {
- if (ox == px && oy == py) {
- return null;
- }
- RectangularShape shape = mouseSelectedArea;
- if (transform != null && !transform.isIdentity()) {
- if (shape == null) {
- final Point2D.Float po = new Point2D.Float(ox, oy);
- final Point2D.Float pp = new Point2D.Float(px, py);
- transform.inverseTransform(po, po);
- transform.inverseTransform(pp, pp);
- return new Line2D.Float(po, pp);
- } else {
- if (canReshape(shape, transform)) {
- final Point2D.Double point = new Point2D.Double();
- double xmin = Double.POSITIVE_INFINITY;
- double ymin = Double.POSITIVE_INFINITY;
- double xmax = Double.NEGATIVE_INFINITY;
- double ymax = Double.NEGATIVE_INFINITY;
- for (int i = 0; i < 4; i++) {
- point.x = (i&1) == 0 ? shape.getMinX() : shape.getMaxX();
- point.y = (i&2) == 0 ? shape.getMinY() : shape.getMaxY();
- transform.inverseTransform(point, point);
- if (point.x < xmin) xmin = point.x;
- if (point.x > xmax) xmax = point.x;
- if (point.y < ymin) ymin = point.y;
- if (point.y > ymax) ymax = point.y;
- }
- if (shape instanceof Rectangle) {
- return new Rectangle2D.Float((float) xmin,
- (float) ymin,
- (float) (xmax - xmin),
- (float) (ymax - ymin));
- } else {
- shape = (RectangularShape) shape.clone();
- shape.setFrame(xmin, ymin, xmax - xmin, ymax - ymin);
- return shape;
- }
- }
- else {
- return transform.createInverse().createTransformedShape(shape);
- }
- }
- }
- else {
- return (shape != null) ? (Shape) shape.clone() : new Line2D.Float(ox, oy, px, py);
- }
- }
-
- /**
- * Indicates whether we can transform {@code shape} simply by calling its
- * {@code shape.setFrame(...)} method rather than by using the heavy artillery
- * that is the {@code transform.createTransformedShape(shape)} method.
- */
- private static boolean canReshape(final RectangularShape shape,
- final AffineTransform transform) {
- final int type=transform.getType();
- if ((type & AffineTransform.TYPE_GENERAL_TRANSFORM) != 0) return false;
- if ((type & AffineTransform.TYPE_MASK_ROTATION) != 0) return false;
- if ((type & AffineTransform.TYPE_FLIP) != 0) {
- if (shape instanceof Rectangle2D) return true;
- if (shape instanceof Ellipse2D) return true;
- if (shape instanceof RoundRectangle2D) return true;
- return false;
- }
- return true;
- }
-
- /**
- * Returns a {@link Graphics2D} object to be used for drawing in the specified component. We
- * must not forget to call {@link Graphics2D#dispose} when the graphics object is no longer
- * needed.
- */
- private Graphics2D getGraphics(final Component c) {
- final Graphics2D graphics = (Graphics2D) c.getGraphics();
- graphics.setXORMode(lineXORColor);
- graphics.setColor (backXORColor);
- return graphics;
- }
-
- /**
- * Informs this controller that the mouse button has been pressed.
- * The default implementation retains the mouse coordinate (which will
- * become one of the corners of the future rectangle to be drawn)
- * and prepares {@code this} to observe the mouse movements.
- *
- * @throws ClassCastException if {@link #getModel} doesn't return a shape
- * from the class {link RectangularShape} or {link Line2D}.
- */
- public void mousePressed(final MouseEvent event) throws ClassCastException {
- if (!event.isConsumed() && (event.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
- final Component source = event.getComponent();
- if (source != null) {
- Shape model = getModel(event);
- if (model != null) {
- isDragging = true;
- ox = px = event.getX();
- oy = py = event.getY();
- if (model instanceof Line2D) {
- model = null;
- }
- mouseSelectedArea = (RectangularShape) model;
- if (mouseSelectedArea != null) {
- mouseSelectedArea.setFrame(ox, oy, 0, 0);
- }
- source.addMouseMotionListener(this);
- }
- source.requestFocus();
- event.consume();
- }
- }
- }
-
- /**
- * Informs this controller that the mouse has been dragged. The default
- * implementation uses this to move a corner of the rectangle used to
- * select the region. The other corner remains fixed at the point
- * where the mouse was at the moment its button was pressed..
- */
- public void mouseDragged(final MouseEvent event) {
- if (isDragging) {
- final Graphics2D graphics = getGraphics(event.getComponent());
- if (mouseSelectedArea == null) {
- graphics.drawLine(ox, oy, px, py);
- px = event.getX();
- py = event.getY();
- graphics.drawLine(ox, oy, px, py);
- } else {
- graphics.draw(mouseSelectedArea);
- int xmin = this.ox;
- int ymin = this.oy;
- int xmax = px = event.getX();
- int ymax = py = event.getY();
- if (xmin > xmax) {
- final int xtmp = xmin;
- xmin = xmax; xmax = xtmp;
- }
- if (ymin > ymax) {
- final int ytmp = ymin;
- ymin = ymax; ymax = ytmp;
- }
- mouseSelectedArea.setFrame(xmin, ymin, xmax - xmin, ymax - ymin);
- graphics.draw(mouseSelectedArea);
- }
- graphics.dispose();
- event.consume();
- }
- }
-
- /**
- * Informs this controller that the mouse button has been released.
- * The default implementation calls {@link #selectionPerformed} with
- * the bounds of the selected region as parameters.
- */
- public void mouseReleased(final MouseEvent event) {
- if (isDragging && (event.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
- isDragging = false;
- final Component component = event.getComponent();
- component.removeMouseMotionListener(this);
-
- final Graphics2D graphics = getGraphics(event.getComponent());
- if (mouseSelectedArea == null) {
- graphics.drawLine(ox, oy, px, py);
- } else {
- graphics.draw(mouseSelectedArea);
- }
- graphics.dispose();
- px = event.getX();
- py = event.getY();
- selectionPerformed(ox, oy, px, py);
- event.consume();
- }
- }
-
- /**
- * Informs this controller that the mouse has been moved but not as a
- * result of the user selecting a region. The default implementation
- * signals to the source component that {@code this} is no longer
- * interested in being informed about mouse movements.
- */
- public void mouseMoved(final MouseEvent event) {
- // Normally not necessary, but it seems that this "listener"
- // sometimes stays in place when it shouldn't.
- event.getComponent().removeMouseMotionListener(this);
- }
-}
Deleted: branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/SelectXMapPaneMouseListener.java
===================================================================
--- branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/SelectXMapPaneMouseListener.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/SelectXMapPaneMouseListener.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -1,268 +0,0 @@
-package gtmig.org.geotools.swing;
-
-import gtmig.org.geotools.swing.GeomFilterGenerator.BoundingBoxFilterGenerator;
-
-import java.awt.Cursor;
-import java.awt.Point;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseWheelEvent;
-import java.awt.geom.Point2D;
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Iterator;
-
-import org.apache.log4j.Logger;
-import org.geotools.data.memory.MemoryFeatureCollection;
-import org.geotools.feature.FeatureCollection;
-import org.geotools.map.MapLayer;
-import org.opengis.feature.simple.SimpleFeature;
-import org.opengis.feature.simple.SimpleFeatureType;
-
-import schmitzm.geotools.gui.SelectableXMapPane;
-import schmitzm.geotools.map.event.FeatureSelectedEvent;
-import sun.misc.PerformanceLogger;
-
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-
-public class SelectXMapPaneMouseListener extends MouseAdapter {
-
- private final SelectableXMapPane xMapPane;
- private boolean enabled;
-
- public boolean isEnabled() {
- if (!xMapPane.isWellDefined())
- return false;
- if ((xMapPane.getState() == XMapPane.ZOOM_IN
- || xMapPane.getState() == xMapPane.ZOOM_OUT || xMapPane
- .getState() == xMapPane.PAN))
- return false;
- if (xMapPane.getState() == XMapPane.NONE)
- ;
- return enabled;
- }
-
- /**
- * Reagiert auf Linksklick mit der ueber {@link #setState(int)}eingestellten
- * Aktion und auf Rechtsklick mit Zoom-Out (sofern {@link #ZOOM_IN}-State
- * fuer Linksklick eingestellt). Alle anderen Klicks werden ignoriert.
- *
- * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
- * (University of Bonn/Germany)
- * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
- * Krüger</a>
- */
- public void mouseClicked(MouseEvent e) {
- if (!isEnabled())
- return;
-
- xMapPane.setCursor(XMapPane.WAIT_CURSOR);
- //
- // int clickX = e.getX();
- // int clickY = e.getY();
- // selectionPerformed(clickX, clickY, clickX, clickY);}
- //
- int state = xMapPane.getState();
-
- /**
- * BEGIN StefanChange Dieser Block findet sichtbare Features im Umkreis
- * des Mausklicks und kümmert sich selbst um das verschicken der Events.
- * Dabei wird z.Zt. immer eine FeaterCollection mit nur einem
- * SimpleFeature verschickt. Und zwar jenes SimpleFeature, welches am
- * nächsten liegt.
- */
-
- // Fenster-Koordinate in Geo-Koordinate umwandelt
- Point2D geoCoord = xMapPane.getScreenToWorld().transform(e.getPoint(),
- null);
-
- com.vividsolutions.jts.geom.Point mousePoint = new GeometryFactory()
- .createPoint(new Coordinate(geoCoord.getX(), geoCoord.getY()));
-
- // Rechnet 10 Pixel in die Einheit der Karte um.
- final Point pAtDistance = new Point((int) e.getPoint().getX() + 10,
- (int) e.getPoint().getY());
- final Point2D geoCoordAtDistance = xMapPane.getScreenToWorld()
- .transform(pAtDistance, null);
- final Double dist = Math.abs(geoCoordAtDistance.getX()
- - geoCoord.getX()) / 2;
-
-// final Envelope envelope = new Envelope(geoCoord.getX(),
-// geoCoord.getY(), geoCoord.getX(), geoCoord.getY());
-
- //
- // This is a tweak. The NearPointFilterGenerator doesn't work
- // any more in GT2.6... So we use a small BBOX.
- // Well, it's not the
- // NearPointFilterGenerator, but GeoTools that fails. I already
- // invested 3h in this... I guess the bug is in
- // ExtractBoundsFilterVisitor.visit(ProperyName) retuning null.
- // It should return the BBOX of value the named property.
-
- // Hashtable<MapLayer, FeatureCollection<SimpleFeatureType,
- // SimpleFeature>> result = findVisibleFeatures(
- // new NearPointFilterGenerator(geoCoord, dist,
- // getContext().getCoordinateReferenceSystem()),
- // state, envelope);
-
-// envelope.expandBy(dist);
-//
- Envelope envelope = new Envelope(geoCoord.getX() - dist, geoCoord
- .getY()
- - dist, geoCoord.getX() + dist, geoCoord.getY() + dist);
-
- Hashtable<MapLayer, FeatureCollection<SimpleFeatureType, SimpleFeature>> result = xMapPane
- .findVisibleFeatures(new BoundingBoxFilterGenerator( envelope,
- xMapPane.getContext().getCoordinateReferenceSystem()),
- state, envelope);
-
- // Ein Event auslösen für das jeweils nächste Feature pro Layer
- for (Enumeration<MapLayer> element = result.keys(); element
- .hasMoreElements();) {
-
- MapLayer layer = element.nextElement();
- FeatureCollection<SimpleFeatureType, SimpleFeature> fc = result
- .get(layer);
- FeatureCollection<SimpleFeatureType, SimpleFeature> fcOne;
-
- if (fc != null && !fc.isEmpty()) {
-
- if (fc.size() > 1) {
- // Hier werden alle Features weggeschmissen, bis auf
- // das raeumlich naechste.
-
- SimpleFeature nearestFeature = null;
- Double nearestDist = 0.0;
-
- Iterator<SimpleFeature> fcIt = fc.iterator();
- try {
-
- while (fcIt.hasNext()) {
- SimpleFeature f = fcIt.next();
- Object obj = f.getAttribute(0);
-
- if (obj instanceof Geometry) {
- // Bei Punkten ja noch ganz einfach:
- Geometry featureGeometry = (Geometry) obj;
- double distance = featureGeometry
- .distance(mousePoint);
-
- if ((nearestFeature == null)
- || (distance < nearestDist)) {
- nearestFeature = f;
- nearestDist = distance;
- }
- } else {
- LOGGER
- .info("!obj instanceof Geometry obj = "
- + obj.getClass()
- .getSimpleName());
- }
-
- }
-
- } finally {
- fc.close(fcIt);
- }
-
- fcOne = new MemoryFeatureCollection(fc.getSchema());
- fc.clear();
- fcOne.add(nearestFeature);
- } else {
- fcOne = fc;
- }
- xMapPane.fireMapPaneEvent(new FeatureSelectedEvent(xMapPane,
- layer, envelope, fcOne));
- }
- }
-
- xMapPane.updateCursor();
-
- }
-
- /**
- * Verarbeitet die Mausrad-Aktion, indem gezoomed wird.
- *
- * @param wheelEvt
- * Mausrad-Event
- */
- @Override
- public void mouseWheelMoved(MouseWheelEvent wheelEvt) {
- if (!isEnabled())
- return;
-
- final int units = wheelEvt.getUnitsToScroll();
- if (units > 0)
- xMapPane.zoomTo(wheelEvt.getPoint(), 1. + .11 * units);
- else
- xMapPane.zoomTo(wheelEvt.getPoint(), 2. / -units);
- }
-
- @Override
- public void mouseDragged(final MouseEvent event) {
- if (!isEnabled())
- return;
-
- xMapPane.mouseDragged(startPos, lastPos, event);
-
- lastPos = event.getPoint();
- }
-
- /**
- * Stores beginning of a drag event in window coordinates
- */
- protected Point startPos;
-
- /**
- * Stores last position of a drag event in window coordinates
- */
- protected Point lastPos;
-
- public void mouseReleased(final MouseEvent event) {
- if (!isEnabled())
- return;
-
- if (xMapPane.getState() == XMapPane.PAN
- || event.getButton() == MouseEvent.BUTTON3) {
- xMapPane.performPan();
- } else if (xMapPane.getState() == XMapPane.SELECT_ALL
- || xMapPane.getState() == XMapPane.SELECT_TOP) {
- xMapPane.drawRectangle(xMapPane.getGraphics(), startPos, event
- .getPoint());
-
- Point mp = event.getPoint();
-
- // keine wirkliche Selektion, sondern nur ein Klick
- if (startPos.x == mp.x || startPos.y == mp.y) {
- mouseClicked(event);
- return;
- }
-
- // SELECTION!
- xMapPane.performSelectionEvent(startPos.x, startPos.y, mp.x, mp.y);
- }
-
- }
-
- public void mousePressed(final MouseEvent e) {
- if (!isEnabled())
- return;
- startPos = new Point(e.getPoint());
- lastPos = new Point(e.getPoint());
- }
-
- public SelectXMapPaneMouseListener(SelectableXMapPane xMapPane) {
- this.xMapPane = xMapPane;
- }
-
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
- }
-
- private static Logger LOGGER = Logger
- .getLogger(SelectXMapPaneMouseListener.class);
-
-}
Deleted: branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/XMapPane.java
===================================================================
--- branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/XMapPane.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/XMapPane.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -1,2267 +0,0 @@
-package gtmig.org.geotools.swing;
-
-import java.awt.Color;
-import java.awt.Cursor;
-import java.awt.Font;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Image;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.RenderingHints;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.event.InputEvent;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.NoninvertibleTransformException;
-import java.awt.geom.Point2D;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Vector;
-
-import javax.swing.Timer;
-
-import org.apache.log4j.Logger;
-import org.geotools.feature.FeatureCollection;
-import org.geotools.geometry.jts.JTS;
-import org.geotools.geometry.jts.ReferencedEnvelope;
-import org.geotools.map.DefaultMapContext;
-import org.geotools.map.MapContext;
-import org.geotools.map.MapLayer;
-import org.geotools.map.event.MapLayerEvent;
-import org.geotools.map.event.MapLayerListEvent;
-import org.geotools.map.event.MapLayerListListener;
-import org.geotools.map.event.MapLayerListener;
-import org.geotools.referencing.CRS;
-import org.geotools.renderer.GTRenderer;
-import org.geotools.renderer.label.LabelCacheImpl;
-import org.geotools.renderer.lite.LabelCache;
-import org.geotools.renderer.lite.StreamingRenderer;
-import org.geotools.swing.JMapPane;
-import org.geotools.swing.event.MapMouseEvent;
-import org.geotools.swing.event.MapPaneEvent;
-import org.geotools.swing.event.MapPaneListener;
-import org.opengis.feature.simple.SimpleFeature;
-import org.opengis.feature.simple.SimpleFeatureType;
-import org.opengis.referencing.FactoryException;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.TransformException;
-
-import schmitzm.geotools.GTUtil;
-import schmitzm.geotools.JTSUtil;
-import schmitzm.geotools.gui.SelectableXMapPane;
-import schmitzm.geotools.map.event.JMapPaneListener;
-import schmitzm.geotools.map.event.MapLayerAdapter;
-import schmitzm.lang.LangUtil;
-import schmitzm.swing.JPanel;
-import schmitzm.swing.SwingUtil;
-import skrueger.geotools.RenderingExecutor;
-
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-import com.vividsolutions.jts.geom.Geometry;
-
-/**
- * The {@link XMapPane} class uses a Geotools {@link GTRenderer} to paint up to
- * two {@link MapContext}s: a "local" {@link MapContext} and a "background"
- * {@link MapContext}. The idea is, that rendering a background layer made up of
- * e.g. OSM data, may take much longer than rendering local data.<br>
- * Every {@link MapContext} is rendered on a {@link Thread} of it's own.
- * Starting/ cancelling these threads is done by the {@link RenderingExecutor}.<br>
- * <br>
- * While the renderers are rending the map, a <br>
- * The {@link XMapPane} is based on schmitzm {@link JPanel}, so
- * {@link #print(Graphics)} will automatically set the background of components
- * to pure white.
- *
- * The XMapPane has a {@link MouseListener} that manages zooming.<br>
- * A logo/icon to float in the lower left corner may be set with
- * {@link #setMapImage(BufferedImage)}<br>
- *
- * @see SelectableXMapPane - an extension of {@link XMapPane} that supports
- * selecting features.
- *
- * @author stefan
- *
- */
-public class XMapPane extends JPanel {
-
- private static final int IMAGETYPE = BufferedImage.TYPE_INT_RGB;
- private static final int IMAGETYPE_withAlpha = BufferedImage.TYPE_INT_ARGB;
-
- private static Logger LOGGER = Logger.getLogger(XMapPane.class);
-
- private boolean acceptsRepaintCalls = true;
-
- /**
- * Main {@link MapContext} that holds all layers that are rendered into the
- * {@link #localImage} by the {@link #localRenderer}
- */
- MapContext localContext;
-
- /**
- * {@link MapContext} holding the background layers. Use it for layers that
- * CAN take very long for rendering, like layer from the Internet: WMS, WFS,
- * OSM...<br>
- * <code>null</code> by default.
- *
- * @see #setBgContext(MapContext)
- * @see #getBgContext()
- */
- MapContext bgContext;
-
- /**
- * While threads are working, calls {@link XMapPane#updateFinalImage()}
- * regularly and {@link #repaint()}. This {@link Timer} is stopped when all
- * renderers have finished.
- *
- * @see #INITIAL_REPAINT_DELAYAL
- * @see #REPEATING_REPAINT_DELAY
- */
- final private Timer repaintTimer;
-
- /**
- * The initial delay in milliseconds until the {@link #finalImage} is
- * updated the first time.
- */
- public static final int INITIAL_REPAINT_DELAY = 1000;
-
- /**
- * While the {@link #bgExecuter} and {@link #localExecuter} are rendering,
- * the {@link #repaintTimer} is regularly updating the {@link #finalImage}
- * with previews.
- */
- public static final long REPEATING_REPAINT_DELAY = 500;
-
- /**
- * Default delay (milliseconds) before the map will be redrawn when resizing
- * the pane. This is to avoid flickering while drag-resizing.
- */
- public static final int DEFAULT_RESIZING_PAINT_DELAY = 500;
-
- public static final int NONE = -123;
-
- /**
- * Flag fuer Modus "Kartenausschnitt bewegen". Nicht fuer Window-Auswahl
- * moeglich!
- *
- * @see #setState(int)
- */
- public static final int PAN = 1;
-
- /**
- * Flag fuer Modus "SimpleFeature-Auswahl auf allen (sichtbaren) Layern".
- *
- * @see #setState(int)
- * @see #setState(int)
- */
- public static final int SELECT_ALL = 103;
- /**
- * Flag fuer Modus
- * "Auswahl nur eines Features, das erste sichtbare von Oben".
- *
- * @see #setState(int)
- * @see #setState(int)
- */
- public static final int SELECT_ONE_FROM_TOP = 104;
- /**
- * Flag fuer Modus
- * "SimpleFeature-Auswahl auf dem obersten (sichtbaren) Layer".
- *
- * @see #setState(int)
- * @see #setState(int)
- */
- public static final int SELECT_TOP = 4;
-
- public static final Cursor WAIT_CURSOR = Cursor
- .getPredefinedCursor(Cursor.WAIT_CURSOR);
-
- final static Font waitFont = new Font("Arial", Font.BOLD, 30);
-
- /**
- * Flag fuer Modus "Heran zoomen".
- *
- * @see #setState(int)
- * @see #setState(int)
- */
- public static final int ZOOM_IN = 2;
-
- /**
- * Flag fuer Modus "Heraus zoomen". Nicht fuer Window-Auswahl moeglich!
- *
- * @see #setState(int)
- */
- public static final int ZOOM_OUT = 3;
-
- /**
- * Konvertiert die Maus-Koordinaten (relativ zum <code>JMapPane</code>) in
- * Karten-Koordinaten.
- *
- * @param e
- * Maus-Ereignis
- */
- public static Point2D getMapCoordinatesFromEvent(final MouseEvent e) {
- // aktuelle Geo-Position aus GeoMouseEvent ermitteln
- if (e != null && e instanceof MapMouseEvent)
- try {
- return ((MapMouseEvent) e).getMapPosition().toPoint2D();
- } catch (final Exception err) {
- LOGGER
- .error(
- "return ((GeoMouseEvent) e).getMapCoordinate(null).toPoint2D();",
- err);
- }
-
- // aktuelle Geo-Position ueber Transformation des JMapPane berechnen
- if (e != null && e.getSource() instanceof XMapPane) {
-
- final XMapPane xMapPane = (XMapPane) e.getSource();
-
- if (!xMapPane.isWellDefined())
- return null;
-
- final AffineTransform at = xMapPane.getScreenToWorld();
- if (at != null)
- return at.transform(e.getPoint(), null);
- return null;
- }
- throw new IllegalArgumentException(
- "MouseEvent has to be of instance MapMouseEvent or come from an XMapPane");
- }
-
- /**
- * Listens to changes of the "background" {@link MapContext} and triggers
- * repaints where needed.
- */
- private final MapLayerListListener bgContextListener = new MapLayerListListener() {
-
- @Override
- public void layerAdded(final MapLayerListEvent event) {
- event.getLayer().addMapLayerListener(bgMapLayerListener);
-
- if (localContext.getLayers().length == 0
- && bgContext.getLayers().length == 1) { // the first one and
- // localContext is
- // empty
- if (!setMapArea(localContext.getAreaOfInterest()))
- requestStartRendering();
- return;
- }
-
- // We need to redraw, even in case that the mapArea didn't change
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
-
- }
-
- @Override
- public void layerChanged(final MapLayerListEvent event) {
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
- }
-
- @Override
- public void layerMoved(final MapLayerListEvent event) {
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
- }
-
- @Override
- public void layerRemoved(final MapLayerListEvent event) {
- if (event.getLayer() != null)
- event.getLayer().removeMapLayerListener(bgMapLayerListener);
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
- }
- };
-
- /**
- * compass and icon are rendered into this image
- */
- // protected BufferedImage gadgetsImage;
-
- protected RenderingExecutor bgExecuter;
-
- /**
- * The Renderer for the LocalLayers uses this Image. When set to null,
- * please dispose this {@link Graphics2D}
- */
- private BufferedImage localImage;
-
- private BufferedImage finalImage;
- /**
- * If # {@link #bgExecuter} is using {@link #bgRenderer} for the Background
- * uses this Image. When set to null, please dispose the {@link Graphics2D}
- */
- private BufferedImage bgImage;
-
- /**
- * Optionally a transparent image to paint over the map in the lower right
- * corner.
- *
- * @see #addGadgets(Graphics2D)
- * @see #setMapImage(BufferedImage)
- **/
- private BufferedImage mapImage = null;
-
- /**
- * Listens to each layer in the local {@link MapContext} for changes and
- * triggers repaints.
- */
- protected MapLayerListener bgMapLayerListener = new MapLayerAdapter() {
-
- @Override
- public void layerChanged(final MapLayerEvent event) {
- // Change of SLD for example
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
- }
-
- @Override
- public void layerHidden(final MapLayerEvent event) {
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
- }
-
- @Override
- public void layerShown(final MapLayerEvent event) {
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
- }
- };
-
- /**
- * A flag indicating if dispose() was already called. If true, then further
- * use of this {@link SelectableXMapPane} is undefined.
- */
- private boolean disposed = false;
-
- /**
- * While dragging, the {@link #updateFinalImage()} method is translating the
- * cached images while setting it together.
- **/
- Point imageOrigin = new Point(0, 0);
- /**
- * For every rendering thread started,
- * {@link GTUtil#createGTRenderer(MapContext)} is called to create a new
- * renderer. These Java2D rendering hints are passed to the
- * {@link GTRenderer}. The java2dHints are the same for all renderers (bg
- * and local).
- */
- private RenderingHints java2dHints;
-
- protected LabelCache labelCache = new LabelCacheImpl();
-
- /**
- * Listens to changes of the "local" {@link MapContext} and triggers
- * repaints where needed.
- */
- private final MapLayerListListener localContextListener = new MapLayerListListener() {
-
- @Override
- public void layerAdded(final MapLayerListEvent event) {
- event.getLayer().addMapLayerListener(localMapLayerListener);
-
- localRenderer.setContext(getContext());
-
- if (localContext.getLayers().length == 1) { // the first one
- // if the Area of Interest is unset, the LayerBounds are used
- if (!setMapArea(localContext.getAreaOfInterest()))
- repaint();
-
- return;
- }
-
- // We need to redraw, even in case that the mapArea didn't change
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
-
- }
-
- @Override
- public void layerChanged(final MapLayerListEvent event) {
- // mapImageInvalid = true;
- // repaint();
- localRenderer.setContext(getContext());
- requestStartRendering();
- }
-
- @Override
- public void layerMoved(final MapLayerListEvent event) {
- // mapImageInvalid = true;
- // repaint();
- localRenderer.setContext(getContext());
- requestStartRendering();
- }
-
- @Override
- public void layerRemoved(final MapLayerListEvent event) {
- if (event.getLayer() != null)
- event.getLayer().removeMapLayerListener(localMapLayerListener);
- // mapImageInvalid = true;
- // repaint();
- localRenderer.setContext(getContext());
- requestStartRendering();
- }
- };
-
- private final RenderingExecutor localExecuter = new RenderingExecutor(this);
-
- /**
- * Listens to each layer in the local {@link MapContext} for changes and
- * triggers repaints.
- */
- protected MapLayerListener localMapLayerListener = new MapLayerAdapter() {
-
- @Override
- public void layerChanged(final MapLayerEvent event) {
- localRenderer.setContext(getContext()); // betters for SLD changes?!
- // Change of SLD for example
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
- }
-
- @Override
- public void layerHidden(final MapLayerEvent event) {
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
- }
-
- @Override
- public void layerShown(final MapLayerEvent event) {
- // mapImageInvalid = true;
- // repaint();
- requestStartRendering();
- }
- };
-
- private final GTRenderer localRenderer = GTUtil.createGTRenderer();
-
- private final GTRenderer bgRenderer = GTUtil.createGTRenderer();
-
- /**
- * the area of the map to draw
- */
- protected Envelope mapArea = null;
-
- /**
- * A flag set it {@link #setMapArea(Envelope)} to indicated the
- * {@link #paintComponent(Graphics)} method, that the image on-screen is
- * associated with {@link #oldMapArea} and may hence be scaled for a quick
- * preview.<br>
- * The flag is reset
- **/
- private boolean mapAreaChanged = false;
-
- /**
- * This color is used as the default background color when painting a map.
- */
- private Color mapBackgroundColor = Color.WHITE;
-
- /**
- * A flag indicating that the shown image is invalid and needs to be
- * re-rendered.
- */
- protected boolean mapImageInvalid = true;
-
- /**
- * Holds a flag for each layer, whether it is regarded or ignored on
- * {@link #SELECT_TOP}, {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP}
- * actions.
- */
- final protected HashMap<MapLayer, Boolean> mapLayerSelectable = new HashMap<MapLayer, Boolean>();
-
- /**
- * List of listeners of this {@link XMapPane}
- */
- protected Vector<JMapPaneListener> mapPaneListeners = new Vector<JMapPaneListener>();
- /**
- * If not <code>null</code>, the {@link XMapPane} will not allow to zoom/pan
- * out of that area
- **/
- private Envelope maxExtend = null;
- private Double maxZoomScale = Double.MIN_VALUE;
-
- private Double minZoomScale = Double.MAX_VALUE;
-
- /**
- * We store the old mapArea for a moment to use it for the
- * "quick scaled preview" in case of ZoomOut
- **/
- protected Envelope oldMapArea = null;
-
- /**
- * We store the old transform for a moment to use it for the
- * "quick scaled preview" in case of ZoomIn
- **/
- protected AffineTransform oldScreenToWorld = null;
-
- /**
- * A flag indicating, that the image size has changed and the buffered
- * images are not big enough any more
- **/
- protected boolean paneResized = false;
-
- private BufferedImage preFinalImage;
-
- // if null, no quick preview will be shown
- private int quickPreviewHint = 0;
-
- private Map<Object, Object> rendererHints;
-
- private volatile Boolean requestStartRendering = false;
-
- private final Timer resizeTimer;
-
- private final int resizingPaintDelay;
-
- /**
- * Transformation zwischen Fenster-Koordinaten und Karten-Koordinaten
- * (lat/lon)
- */
- protected AffineTransform screenToWorld = null;
-
- /**
- * The flag {@link #requestStartRendering} can be set to true by events.
- * This {@link Timer} checks the flag regularly and starts one renderer
- * thread.
- */
- final private Timer startRenderThreadsTimer;
-
- /**
- * The default state is ZOOM_IN, hence by default the
- * {@link #zoomMapPaneMouseListener} is also enabled.
- **/
- private int state = ZOOM_IN;
-
- /**
- * Manuell gesetzter statischer Cursor, unabhaengig von der aktuellen
- * MapPane-Funktion
- */
- protected Cursor staticCursor = null;
-
- private AffineTransform worldToScreen;
-
- /**
- * This {@link MouseListener} is managing all zoom related tasks
- */
- public final ZoomXMapPaneMouseListener zoomMapPaneMouseListener = new ZoomXMapPaneMouseListener(
- this);
-
- // TODO doku
- public XMapPane() {
- this(null, null);
- }
-
- /**
- * full constructor extending JPanel
- *
- * @param rendererHints
- *
- * @param layout
- * - layout (probably shouldn't be set)
- * @param isDoubleBuffered
- * - a Swing thing I don't really understand
- * @param render
- * - what to draw the map with
- * @param localContext
- * - {@link MapContext} of layer to render.
- */
- public XMapPane(final MapContext localContext_,
- final Map<Object, Object> rendererHints) {
- super(true);
-
- setRendererHints(rendererHints);
-
- setOpaque(true);
-
- localRenderer.setJava2DHints(getJava2dHints());
-
- if (localContext_ != null)
- setLocalContext(localContext_);
-
- /**
- * Adding the #zoomMapPaneMouseListener
- */
- this.addMouseListener(zoomMapPaneMouseListener);
- this.addMouseMotionListener(zoomMapPaneMouseListener);
- this.addMouseWheelListener(zoomMapPaneMouseListener);
-
- /*
- * We use a Timer object to avoid rendering delays and flickering when
- * the user is drag-resizing the parent container of this map pane.
- *
- * Using a ComponentListener doesn't work because, unlike a JFrame, the
- * pane receives a stream of events during drag-resizing.
- */
- resizingPaintDelay = DEFAULT_RESIZING_PAINT_DELAY;
- resizeTimer = new Timer(resizingPaintDelay, new ActionListener() {
-
- public void actionPerformed(final ActionEvent e) {
- paneResized = true;
-
- if (!isWellDefined())
- return;
-
- final Rectangle bounds = getVisibleRect();
-
- final Envelope geoMapArea = tranformWindowToGeo(bounds.x,
- bounds.y, bounds.x + bounds.width, bounds.y
- + bounds.height);
-
- setMapArea(bestAllowedMapArea(geoMapArea));
- }
- });
- resizeTimer.setRepeats(false);
- this.addComponentListener(new ComponentAdapter() {
-
- @Override
- public void componentResized(final ComponentEvent e) {
- if (bgExecuter != null)
- bgExecuter.cancelTask();
- if (localExecuter != null)
- localExecuter.cancelTask();
- resizeTimer.restart();
- }
-
- });
-
- /*
- * Setting up the repaintTimer. Not started automatically.
- */
- repaintTimer = new Timer((int) REPEATING_REPAINT_DELAY,
- new ActionListener() {
-
- @Override
- public void actionPerformed(final ActionEvent e) {
- updateFinalImage();
- XMapPane.this.repaint();
- }
- });
- repaintTimer.setInitialDelay(INITIAL_REPAINT_DELAY);
- repaintTimer.setRepeats(true);
-
- /*
- * Setting up the startRenderThreadsTimer. This Timer starts
- * automatically.
- */
- startRenderThreadsTimer = new Timer(100, new ActionListener() {
-
- @Override
- public void actionPerformed(final ActionEvent e) {
- synchronized (requestStartRendering) {
- if (requestStartRendering && isWellDefined()) {
-
- if (localExecuter.isRunning()) {
- localExecuter.cancelTask();
- } else {
- requestStartRendering = false;
- startRendering();
- }
- }
- }
- }
- });
- startRenderThreadsTimer.start();
-
- }
-
- /**
- * Fuegt der Map einen Listener hinzu.
- *
- * @param l
- * neuer Listener
- */
- public void addMapPaneListener(final JMapPaneListener l) {
- mapPaneListeners.add(l);
- }
-
- /**
- * Korrigiert den {@link Envelope} aka {@code mapArea} auf die beste
- * erlaubte Flaeche damit die Massstabsbeschaenkungen noch eingehalten
- * werden, FALLS der uebergeben Envelope nicht schon gueltig sein sollte.<br>
- * Since 21. April 09: Before thecalculation starts, the aspect ratio is
- * corrected. This change implies, that setMapArea() will most of the time
- * not allow setting to a wrong aspectRatio.
- *
- * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
- * Krüger</a>
- */
- public Envelope bestAllowedMapArea(Envelope env) {
- if (getWidth() == 0)
- return env;
- if (env == null)
- return null;
-
- Envelope newArea = null;
-
- /**
- * Correct the aspect Ratio before we check the rest. Otherwise we might
- * easily fail. We allow to grow here, because we don't check against
- * the maxExtend
- */
- final Rectangle curPaintArea = getVisibleRect();
-
- env = JTSUtil.fixAspectRatio(curPaintArea, env, true);
-
- final double scale = env.getWidth() / getWidth();
- final double centerX = env.getMinX() + env.getWidth() / 2.;
- final double centerY = env.getMinY() + env.getHeight() / 2.;
- double newWidth2 = 0;
- double newHeight2 = 0;
- if (scale < getMaxZoomScale()) {
- // ****************************************************************************
- // Wir zoomen weiter rein als erlaubt => Anpassen des envelope
- // ****************************************************************************
- newWidth2 = getMaxZoomScale() * getWidth() / 2.;
- newHeight2 = getMaxZoomScale() * getHeight() / 2.;
- } else if (scale > getMinZoomScale()) {
- // ****************************************************************************
- // Wir zoomen weiter raus als erlaubt => Anpassen des envelope
- // ****************************************************************************
- newWidth2 = getMinZoomScale() * getWidth() / 2.;
- newHeight2 = getMinZoomScale() * getHeight() / 2.;
- } else {
- // ****************************************************************************
- // Die mapArea / der Envelope ist ist gueltig! Keine Aenderungen
- // ****************************************************************************
- newArea = env;
- }
-
- if (newArea == null) {
-
- final Coordinate ll = new Coordinate(centerX - newWidth2, centerY
- - newHeight2);
- final Coordinate ur = new Coordinate(centerX + newWidth2, centerY
- + newHeight2);
-
- newArea = new Envelope(ll, ur);
- }
-
- final Envelope maxAllowedExtend = getMaxExtend();
- while (maxAllowedExtend != null && !maxAllowedExtend.contains(newArea)
- && newArea != null && !newArea.isNull()
- && !Double.isNaN(newArea.getMinX())
- && !Double.isNaN(newArea.getMaxX())
- && !Double.isNaN(newArea.getMinY())
- && !Double.isNaN(newArea.getMaxY())) {
- /*
- * If a maxExtend is set, we have to honour that...
- */
-
- // Exceeds top? Move down and maybe cut
- if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
- final double divY = newArea.getMaxY()
- - maxAllowedExtend.getMaxY();
- // LOGGER.debug("Moving area down by " + divY);
-
- newArea = new Envelope(new Coordinate(newArea.getMinX(),
- newArea.getMinY() - divY), new Coordinate(newArea
- .getMaxX(), newArea.getMaxY() - divY));
-
- if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
- // LOGGER.debug("Now it exeeds the bottom border.. cut!");
- // And cut the bottom if it moved out of the area
- newArea = new Envelope(new Coordinate(newArea.getMinX(),
- maxAllowedExtend.getMinY()), new Coordinate(newArea
- .getMaxX(), newArea.getMaxY()));
-
- // LOGGER.debug("and fix aspect ratio");
-
- newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
- false);
- }
- }
-
- // Exceeds bottom? Move up and maybe cut
- if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
- final double divY = newArea.getMinY()
- - maxAllowedExtend.getMinY();
- // LOGGER.debug("Moving area up by " + divY);
-
- newArea = new Envelope(new Coordinate(newArea.getMinX(),
- newArea.getMinY() - divY), new Coordinate(newArea
- .getMaxX(), newArea.getMaxY() - divY));
-
- if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
- // LOGGER.debug("Now it exeeds the top border.. cut!");
- // And cut the bottom if it moved out of the area
- newArea = new Envelope(new Coordinate(newArea.getMinX(),
- newArea.getMinY()), new Coordinate(newArea
- .getMaxX(), maxAllowedExtend.getMaxY()));
-
- // LOGGER.debug("and fix aspect ratio");
-
- newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
- false);
- }
- }
-
- // Exceeds to the right? move and maybe cut
- if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
-
- // Move left..
- final double divX = newArea.getMaxX()
- - maxAllowedExtend.getMaxX();
- // LOGGER.debug("Moving area left by " + divX);
-
- newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,
- newArea.getMinY()), new Coordinate(newArea.getMaxX()
- - divX, newArea.getMaxY()));
-
- if (newArea.getMinX() < maxAllowedExtend.getMinX()) {
- // LOGGER.debug("Now it exeeds the left border.. cut!");
- // And cut the left if it moved out of the area
- newArea = new Envelope(new Coordinate(maxAllowedExtend
- .getMinX(), newArea.getMinY()), new Coordinate(
- newArea.getMaxX(), newArea.getMaxY()));
-
- // LOGGER.debug("and fix aspect ratio");
-
- newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
- false);
- }
- }
-
- // Exceeds to the left? move and maybe cut
- if (newArea.getMinX() < maxAllowedExtend.getMinX()) {
-
- // Move right..
- final double divX = newArea.getMinX()
- - maxAllowedExtend.getMinX();
- // LOGGER.debug("Moving area right by " + divX);
-
- newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,
- newArea.getMinY()), new Coordinate(newArea.getMaxX()
- - divX, newArea.getMaxY()));
-
- if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
- // LOGGER.debug("Now it exeeds the right border.. cut!");
- // And cut the left if it moved out of the area
- newArea = new Envelope(new Coordinate(newArea.getMinX(),
- newArea.getMinY()), new Coordinate(maxAllowedExtend
- .getMaxX(), newArea.getMaxY()));
-
- // LOGGER.debug("and fix aspect ratio");
-
- newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
- false);
- }
- }
- }
-
- return newArea;
- }
-
- /**
- * Should be called when the {@link JMapPane} is not needed no more to help
- * the GarbageCollector
- *
- * Removes all {@link JMapPaneListener}s that are registered
- *
- * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
- * Krüger</a>
- */
- public void dispose() {
- if (isDisposed())
- return;
-
- setPainting(false);
-
- resizeTimer.stop();
- startRenderThreadsTimer.stop();
-
- disposed = true;
-
- if (bgExecuter != null) {
- bgExecuter.cancelTask();
- bgExecuter.dispose();
- }
-
- if (localExecuter != null) {
- int i = 0;
- localExecuter.cancelTask();
- while (i++ < 10 && localExecuter.isRunning()) {
- try {
- Thread.sleep(200);
- } catch (final InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- if (localExecuter.isRunning()) {
- System.out
- .println("BAD BAD BAD... still running the thread....");
- }
- localExecuter.dispose();
- }
- //
- // if (bgImage != null) {
- // bgImage = dispose(bgImage);
- // bgImage = null;
- // // LangUtil.gcTotal();
- // }
- //
- // if (localImage != null) {
- // localImage = dispose(localImage);
- // localImage = null;
- // // LangUtil.gcTotal();
- // }
- //
- // if (finalImage != null) {
- // finalImage = dispose(finalImage);
- // finalImage = null;
- // // LangUtil.gcTotal();
- // }
- //
- // if (preFinalImage != null) {
- // preFinalImage = dispose(preFinalImage);
- // preFinalImage = null;
- // }
- disposeImages();
-
- // LangUtil.gcTotal();
-
- // Alle mapPaneListener entfernen
- mapPaneListeners.clear();
-
- removeMouseMotionListener(zoomMapPaneMouseListener);
- removeMouseListener(zoomMapPaneMouseListener);
-
- if (localContext != null)
- getContext().clearLayerList();
- if (bgContext != null)
- getBgContext().clearLayerList();
-
- removeAll();
- }
-
- /**
- * Draws a rectangle in XOR mode from the origin at {@link #startPos} to the
- * given point. All in screen coordinates.
- */
- protected void drawRectangle(final Graphics graphics, final Point startPos,
- final Point e) {
-
- if (!isWellDefined())
- return;
-
- // undraw last box/draw new box
- final int left = Math.min(startPos.x, e.x);
- final int right = Math.max(startPos.x, e.x);
- final int top = Math.max(startPos.y, e.y);
- final int bottom = Math.min(startPos.y, e.y);
- final int width = right - left;
- final int height = top - bottom;
-
- if (width == 0 && height == 0)
- return;
-
- graphics.setXORMode(Color.WHITE);
- graphics.drawRect(left, bottom, width, height);
- }
-
- /**
- * Diretly paints scaled preview into the {@link SelectableXMapPane}. Used
- * to give the user something to look at while we are rendering. Method
- * should be called after {@link #setMapArea(Envelope)} has been set to the
- * new mapArea and transform has been reset.<br>
- *
- * @param g
- * Graphics2D to paint the preview into
- *
- * @param state
- * Max be {@link #ZOOM_IN} or {@link #ZOOM_OUT}
- */
- protected boolean drawScaledPreviewImage_Zoom(final Graphics2D graphics) {
-
- if (quickPreviewHint == 0)
- return false;
-
- graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
- graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
- RenderingHints.VALUE_ANTIALIAS_OFF);
- graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
- RenderingHints.VALUE_RENDER_SPEED);
-
- if (oldMapArea == null)
- return false;
-
- final Rectangle visibleArea = getVisibleRect();
-
- // Calculate the oldMapArea in the current WindowCoordinates:
- final Envelope oldMapWindow = tranformGeoToWindow(oldMapArea.getMinX(),
- oldMapArea.getMinY(), oldMapArea.getMaxX(), oldMapArea
- .getMaxY(), null);
-
- final int xx1 = (int) Math.round(oldMapWindow.getMinX());
- final int yy1 = (int) Math.round(oldMapWindow.getMinY());
- final int xx2 = (int) Math.round(oldMapWindow.getMaxX());
- final int yy2 = (int) Math.round(oldMapWindow.getMaxY());
-
- graphics.drawImage(getPreFinalImage(), xx1, yy1, xx2, yy2,
- (int) visibleArea.getMinX(), (int) visibleArea.getMinY(),
- (int) visibleArea.getMaxX(), (int) visibleArea.getMaxY(),
- getMapBackgroundColor(), null);
-
- final Rectangle painedArea = new Rectangle(xx1, yy1, xx2 - xx1, yy2
- - yy1);
-
- SwingUtil.clearAround(graphics, painedArea, visibleArea);
-
- addGadgets(graphics);
-
- quickPreviewHint = 0;
-
- graphics.dispose();
-
- // Something has been drawn
- return true;
- }
-
- public MapContext getBgContext() {
- return bgContext;
- }
-
- /**
- * Lazyly initializes a {@link BufferedImage} for the background renderer.
- */
- private Image getBgImage() {
- //
- // if (bgImage == null) {
- // bgImage = createImage(getBounds().width, getBounds().height);
- // }
-
- return bgImage;
- }
-
- public MapContext getContext() {
- if (localContext == null) {
- setLocalContext(new DefaultMapContext());
- }
- return localContext;
- }
-
- private BufferedImage getFinalImage() {
- //
- if (finalImage == null) {
- // Rectangle curPaintArea = getVisibleRect();
- finalImage = new BufferedImage(getBounds().width,
- getBounds().height, IMAGETYPE);
-
- requestStartRendering();
- }
- return finalImage;
- }
-
- public RenderingHints getJava2dHints() {
- return java2dHints;
- }
-
- /**
- * Lazyly initializes a {@link BufferedImage} for the background renderer.
- */
- private BufferedImage getLocalImage() {
-
- if (localImage == null) {
- localImage = new BufferedImage(getBounds().width,
- getBounds().height, IMAGETYPE_withAlpha);
- }
-
- return localImage;
- }
-
- /**
- * Returns a copy of the mapArea
- *
- * @return
- */
- public Envelope getMapArea() {
- if (mapArea == null) {
- ReferencedEnvelope mapArea_ = null;
- try {
- mapArea_ = localContext.getLayerBounds();
- } catch (final IOException e) {
- LOGGER.warn("context.getLayerBounds()", e);
- }
-
- if (mapArea_ != null) {
- mapImageInvalid = true; /* note we need to redraw */
-// setMapArea(mapArea_); // results in a loop
- mapArea = bestAllowedMapArea(mapArea_);
- }
- }
-
- if (mapArea == null)
- return null;
-
- return new Envelope(mapArea);
- }
-
- /**
- * Returns the background {@link Color} of the map pane. Default is white.
- **/
- public Color getMapBackgroundColor() {
- return mapBackgroundColor;
- }
-
- /**
- * Get the BufferedImage to use as a flaoting icon in the lower right
- * corner.
- *
- * @return <code>null</code> if the feature is deactivated.
- */
- public BufferedImage getMapImage() {
- return mapImage;
- }
-
- /**
- * Returns the evelope of the viewable area. The JMapPane will never show
- * anything outside of this extend. If this has been set to
- * <code>null</code> via {@link #setMaxExtend(Envelope)}, it tries to return
- * quickly the context's bounds. It it takes to long to determine the
- * context bounds, <code>null</code> is returned.
- *
- * @param maxExtend
- * <code>null</code> to not have this restriction.
- */
-
- public Envelope getMaxExtend() {
- if (maxExtend == null) {
- final ReferencedEnvelope layerBounds = GTUtil
- .getVisibleLayoutBounds(localContext);
- if (layerBounds == null) {
- // TODO Last fallback could be the CRS valid area
- return null;
- }
-
- // Kartenbereich um 10% vergroessern
- return JTSUtil.fixAspectRatio(this.getBounds(), JTSUtil
- .expandEnvelope(layerBounds, 0.1), true);
- }
- return maxExtend;
- }
-
- /**
- * Retuns the maximum allowed zoom scale. This is the smaller number value
- * of the two. Defaults to {@link Double}.MIN_VALUE
- *
- * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
- * Krüger</a>
- */
- public Double getMaxZoomScale() {
- return maxZoomScale;
- }
-
- /**
- * Retuns the minimum allowed zoom scale. This is the bigger number value of
- * the two. Defaults to {@link Double}.MAX_VALUE
- *
- * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
- * Krüger</a>
- */
- public Double getMinZoomScale() {
- return minZoomScale;
- }
-
- private Image getPreFinalImage() {
- // if (preFinalImage == null) {
- //
- // // Rectangle curPaintArea = getVisibleRect();
- // // preFinalImage = new BufferedImage(curPaintArea.width,
- // // curPaintArea.height, BufferedImage.TYPE_INT_RGB);
- //
- // preFinalImage = createImage(getBounds().width, getBounds().height);
- //
- // requestStartRendering();
- // }
- return preFinalImage;
- }
-
- public Map<Object, Object> getRendererHints() {
- return rendererHints;
- }
-
- /**
- * Liefert eine affine Transformation, um von den Fenster-Koordinaten in die
- * Karten-Koordinaten (Lat/Lon) umzurechnen.
- *
- * @return eine Kopie der aktuellen Transformation; <code>null</code> wenn
- * noch keine Karte angezeigt wird
- */
- public AffineTransform getScreenToWorld() {
- if (screenToWorld == null)
- resetTransforms();
- // nur Kopie der Transformation zurueckgeben!
- if (screenToWorld == null)
- return null;
- return new AffineTransform(screenToWorld);
- }
-
- public int getState() {
- return state;
- }
-
- /**
- * Liefert den statisch eingestellten Cursor, der unabhaengig von der
- * eingestellten MapPane-Aktion (Zoom, Auswahl, ...) verwendet wird.
- *
- * @return {@code null}, wenn kein statischer Cursor verwendet, sondern der
- * Cursor automatisch je nach MapPane-Aktion eingestellt wird.
- */
- public Cursor getStaticCursor() {
- return this.staticCursor;
- }
-
- public AffineTransform getWorldToScreenTransform() {
- if (worldToScreen == null) {
- resetTransforms();
- }
- // nur Kopie der Transformation zurueckgeben!
- return new AffineTransform(worldToScreen);
- }
-
- /**
- * A flag indicating if dispose() has already been called. If true, then
- * further use of this {@link SelectableXMapPane} is undefined.
- */
- private boolean isDisposed() {
- return disposed;
- }
-
- /**
- * Returns whether a layer is regarded or ignored on {@link #SELECT_TOP},
- * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions. Returns
- * <code>true</code> if the selectability has not been defined.
- *
- * @param layer
- * a layer
- */
- public boolean isMapLayerSelectable(final MapLayer layer) {
- final Boolean selectable = mapLayerSelectable.get(layer);
- return selectable == null ? true : selectable;
- }
-
- /**
- * Return <code>true</code> if a CRS and a {@link #mapArea} are set and the
- * {@link XMapPane} is visible and has bounds set.
- */
- public boolean isWellDefined() {
-
- try {
-
- if (getContext() == null)
- return false;
- if (getContext().getLayerCount() <= 0)
- return false;
- if (getMapArea() == null)
- return false;
- if (getBounds().getWidth() == 0)
- return false;
- if (getBounds().getHeight() == 0)
- return false;
- } catch (final Exception e) {
- return false;
- }
- return true;
- }
-
- public void mouseDragged(final Point startPos, final Point lastPos,
- final MouseEvent event) {
-
- if ((getState() == XMapPane.PAN)
- || ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)) {
-
- if (getCursor() != SwingUtil.PANNING_CURSOR) {
- setCursor(SwingUtil.PANNING_CURSOR);
-
- // While panning, we deactivate the rendering. So the tasts are
- // ready to start when the panning os done.
- if (bgExecuter != null)
- bgExecuter.cancelTask();
- if (localExecuter != null)
- localExecuter.cancelTask();
- }
-
- if (lastPos.x > 0 && lastPos.y > 0) {
- final int dx = event.getX() - lastPos.x;
- final int dy = event.getY() - lastPos.y;
-
- // TODO Stop dragging when the drag would not be valid...
- // boolean dragValid = true;
- // // check if this panning results in a valid mapArea
- // {
- // Rectangle winBounds = xMapPane.getBounds();
- // winBounds.translate(xMapPane.imageOrigin.x,
- // -xMapPane.imageOrigin.y);
- // Envelope newMapAreaBefore = xMapPane.tranformWindowToGeo(
- // winBounds.x, winBounds.y, winBounds.x
- // + winBounds.width, winBounds.y
- // + winBounds.height);
- //
- //
- // winBounds = xMapPane.getBounds();
- // Point testIng = new Point(xMapPane.imageOrigin);
- // testIng.translate(dx, dy);
- // winBounds.translate(testIng.x, -testIng.y);
- // Envelope newMapAreaAfter = xMapPane.tranformWindowToGeo(
- // winBounds.x, winBounds.y, winBounds.x
- // + winBounds.width, winBounds.y
- // + winBounds.height);
- //
- // // If the last drag doesn't change the MapArea anymore cancel
- // it.
- // if (xMapPane.bestAllowedMapArea(newMapAreaAfter).equals(
- // xMapPane.bestAllowedMapArea(newMapAreaBefore))){
- // dragValid = false;
- // return;
- // }
- // }
-
- imageOrigin.translate(dx, dy);
- updateFinalImage();
- repaint();
- }
-
- } else if ((getState() == XMapPane.ZOOM_IN)
- || (getState() == XMapPane.ZOOM_OUT)
- || (getState() == XMapPane.SELECT_ALL)
- || (getState() == XMapPane.SELECT_TOP)
- // || (getState() == XMapPane.SELECT_ONE_FROM_TOP)
- ) {
- final Graphics graphics = getGraphics();
-
- drawRectangle(graphics, startPos, event.getPoint());
-
- if ((lastPos.x > 0) && (lastPos.y > 0)) {
- drawRectangle(graphics, startPos, lastPos);
- }
-
- graphics.dispose();
-
- }
-
- }
-
- /**
- * Called by the {@link RenderingExecutor} when rendering was cancelled.
- */
- public void onRenderingCancelled() {
- repaintTimer.stop();
- LOGGER.debug("Rendering cancelled");
- }
-
- /**
- * Called by the {@link RenderingExecutor} when rendering has been
- * completed.
- */
- public void onRenderingCompleted() {
- repaintTimer.stop();
- updateFinalImage();
- repaint();
- }
-
- /**
- * Called by the {@linkplain XMapPane.RenderingTask} when rendering failed.
- * Publishes a {@linkplain MapPaneEvent} of type {@code
- * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.
- *
- * @param renderingError
- * The error that occured during rendering
- *
- * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
- */
- public void onRenderingFailed(final Exception renderingError) {
- repaintTimer.stop();
- LOGGER.warn("Rendering failed", renderingError);
- updateFinalImage();
- repaint();
-
- }
-
- public void onRenderingPending() {
- // LOGGER.debug("Pending rendering updates the preview...");
- updateFinalImage();
- repaint();
- }
-
- @Override
- protected void paintComponent(final Graphics g) {
- if (!acceptsRepaintCalls)
- return;
-
- // Maybe update the cursor and maybe stop the repainting timer
- updateCursor();
-
- super.paintComponent(g); // candidate for removal
-
- boolean paintedSomething = false;
-
- if (mapImageInvalid) { /* if the map changed then redraw */
-
- mapImageInvalid = false; // Reset for next round
-
- // If the new mapArea and the oldMapArea intersect, we can draw some
- // quick scaled preview to make the user feel that something is
- // happening.
- if (mapAreaChanged && oldMapArea != null
- && getMapArea().intersects(oldMapArea)
- & !getMapArea().equals(oldMapArea)) {
-
- mapAreaChanged = false;
-
- if (getMapArea().covers(oldMapArea)) {
- setQuickPreviewHint(ZOOM_OUT);
- paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
- } else if (oldMapArea.covers(getMapArea())) {
- setQuickPreviewHint(ZOOM_IN);
- paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
- }
-
- }
-
- if (paneResized) {
- paneResized = false;
- disposeImages();
- }
-
- // Start the Threads and Timers to render the image
- requestStartRendering();
-
- }
-
- if (!paintedSomething) {
-
- g.drawImage(getFinalImage(), 0, 0, null);
-
- g.dispose(); // cand. for removal
-
- paintedSomething = true; // cand. for removal
- }
-
- }
-
- /**
- * Heavily works on releasing all resources related to the four
- * {@link Image}s used to cache the results of the different renderers.<br>
- * In November 2009 i had some memory leaking problems with {@link XMapPane}
- * . The resources of the buffered images were never released. It seem to be
- * important to call the GC right after flushing the images.<br>
- * Hence this method may take a while, because it calls the GC up to four
- * times.
- */
- private void disposeImages() {
-
- // System.out.println("vorher = "
- // + new MbDecimalFormatter().format(LangUtil.gcTotal()));
- // bi.flush();
- // return bi = null;
- // System.out.println("nacher = "
- // + new MbDecimalFormatter().format(LangUtil.gcTotal()));
- //
- // System.out.println("\n");
-
- if (preFinalImage != null) {
- preFinalImage.flush();
- preFinalImage = null;
- LangUtil.gc();
- }
- if (finalImage != null) {
- finalImage.flush();
- finalImage = null;
- LangUtil.gc();
- }
- if (localImage != null) {
- localImage.flush();
- localImage = null;
- LangUtil.gc();
- }
- if (bgImage != null) {
- bgImage.flush();
- bgImage = null;
- LangUtil.gc();
- }
-
- }
-
- /**
- * Performs a {@value #PAN} action. During panning, the displacement is
- * stored in {@link #imageOrigin} object. Calling {@link #performPan()} will
- * reset the offset and call {@link #setMapArea(Envelope)}.
- */
- public void performPan() {
-
- final Rectangle winBounds = getBounds();
- winBounds.translate(-imageOrigin.x, -imageOrigin.y);
- final Envelope newMapArea = tranformWindowToGeo(winBounds.x,
- winBounds.y, winBounds.x + winBounds.width, winBounds.y
- + winBounds.height);
-
- imageOrigin.x = 0;
- imageOrigin.y = 0;
-
- if (!setMapArea(newMapArea)) {
- /**
- * If setMapArea returns true, the finalImage is updated anyways.
- * This if-case exists to ensure that we repaint a correct image
- * even if the new panning area has been denied.
- */
- updateFinalImage();
- repaint();
- }
-
- if (getCursor() == SwingUtil.PANNING_CURSOR)
- setCursor(SwingUtil.PAN_CURSOR);
- }
-
- //
- // /**
- // * Nuetzlich wenn die Componente gedruckt (z.B. wenn ein Screenshot
- // gemacht
- // * wird) wird. Dann werden wird der Hintergrund auf WEISS gesetzt.
- // *
- // * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
- // * Krüger</a>
- // */
- // @Override
- // public void print(final Graphics g) {
- // final Color orig = getBackground();
- // setBackground(Color.WHITE);
- //
- // // wrap in try/finally so that we always restore the state
- // try {
- // super.print(g);
- // } finally {
- // setBackground(orig);
- // }
- // }
-
- /**
- * Entfernt einen Listener von der Map.
- *
- * @param l
- * zu entfernender Listener
- */
- public void removeMapPaneListener(final JMapPaneListener l) {
- mapPaneListeners.remove(l);
- }
-
- /**
- * Cancels all running renderers and sets the flag to start new ones. <br>
- *
- * @see #startRenderThreadsTimer
- */
- private void requestStartRendering() {
- if (bgExecuter != null)
- bgExecuter.cancelTask();
- if (localExecuter != null)
- localExecuter.cancelTask();
- requestStartRendering = true;
-
- }
-
- /**
- * Berechnet die Transformation zwischen Fenster- und Karten-Koordinaten
- * neu.
- */
- protected void resetTransforms() {
- if (getMapArea() == null || getWidth() == 0 || getHeight() == 0)
- return;
-
- // We store the last Transform
- oldScreenToWorld = screenToWorld;
-
- this.screenToWorld = new AffineTransform(
- // Genauso wie die Fenster-Koordinaten, werden die Longitude-Koordinaten
- // nach rechts (Osten) hin groesser
- // --> positive Verschiebung
- getMapArea().getWidth() / getWidth(),
- // keine Verzerrung
- 0.0, 0.0,
- // Waehrend die Fenster-Koordinaten nach unten hin groesser
- // werden,
- // werden Latitude-Koordinaten nach Sueden hin keiner
- // --> negative Verschiebung
- -getMapArea().getHeight() / getHeight(),
- // Die Longitude-Koordinaten werden nach Osten hin groesser
- // --> obere linke Ecke des Fensters hat also den Minimalwert
- getMapArea().getMinX(),
- // Die Latitude-Koordinaten werden nach Norden hin groesser
- // --> obere linke Ecke des Fensters hat also den Maximalwert
- getMapArea().getMaxY());
-
- try {
- this.worldToScreen = screenToWorld.createInverse();
- } catch (final NoninvertibleTransformException e) {
- LOGGER.error(e);
- }
- }
-
- public void setBgContext(final MapContext context) {
-
- // Remove the default listener from the old context
- if (this.bgContext != null) {
- this.bgContext.removeMapLayerListListener(bgContextListener);
-
- // adding listener to all layers
- for (final MapLayer mapLayer : bgContext.getLayers()) {
- mapLayer.removeMapLayerListener(bgMapLayerListener);
- }
- }
-
- this.bgContext = context;
-
- if (context != null) {
- setMapArea(bgContext.getAreaOfInterest());
-
- this.bgContext.addMapLayerListListener(bgContextListener);
-
- // adding listener to all layers
- for (final MapLayer mapLayer : bgContext.getLayers()) {
- mapLayer.addMapLayerListener(bgMapLayerListener);
- }
- }
- mapImageInvalid = true;
- repaint();
- }
-
- public void setJava2dHints(final RenderingHints java2dHints) {
- this.java2dHints = java2dHints;
- }
-
- /**
- *
- * @param context
- */
- public void setLocalContext(final MapContext context) {
- // Remove the default listener from the old context
- if (this.localContext != null) {
- this.localContext.removeMapLayerListListener(localContextListener);
-
- // adding listener to all layers
- for (final MapLayer mapLayer : localContext.getLayers()) {
- mapLayer.removeMapLayerListener(localMapLayerListener);
- }
- }
-
- this.localContext = context;
-
- if (context != null) {
-
- setMapArea(localContext.getAreaOfInterest());
-
- localRenderer.setContext(localContext);
-
- this.localContext.addMapLayerListListener(localContextListener);
-
- // adding listener to all layers
- for (final MapLayer mapLayer : localContext.getLayers()) {
- mapLayer.addMapLayerListener(localMapLayerListener);
- }
- }
-
- mapImageInvalid = true;
- repaint();
- }
-
- /**
- * @param newMapArea
- * @return <code>true</code> if the mapArea has been changed and a repaint
- * has been triggered.
- */
- public boolean setMapArea(final Envelope newMapArea) {
-
- if (newMapArea == null
- || bestAllowedMapArea(newMapArea).equals(mapArea)) {
- // No change.. no need to repaint
- return false;
- }
-
- // Testing, whether NaN or Infinity are used in the newMapArea
- if (newMapArea.isNull() || Double.isInfinite(newMapArea.getMaxX())
- || Double.isInfinite(newMapArea.getMaxY())
- || Double.isInfinite(newMapArea.getMinX())
- || Double.isInfinite(newMapArea.getMinY())) {
- // No change.. ugly new values
- LOGGER.warn("setMapArea has been called with newArea = "
- + newMapArea);
- return false;
- }
-
- // Testing, whether the difference if just minimal
- if (mapArea != null) {
- final Envelope candNew = bestAllowedMapArea(newMapArea);
- final double tolX = mapArea.getWidth() / 1000.;
- final double tolY = mapArea.getHeight() / 1000.;
- if ((candNew.getMinX() - tolX < mapArea.getMinX())
- && (mapArea.getMinX() < candNew.getMinX() + tolX)
- && (candNew.getMaxX() - tolX < mapArea.getMaxX())
- && (mapArea.getMaxX() < candNew.getMaxX() + tolX)
-
- && (candNew.getMinY() - tolY < mapArea.getMinY())
- && (mapArea.getMinY() < candNew.getMinY() + tolY)
- && (candNew.getMaxY() - tolY < mapArea.getMaxY())
- && (mapArea.getMaxY() < candNew.getMaxY() + tolY)
-
- ) {
- // The two mapAreas only differ my 1/1000th.. ignore
-
- return false;
- }
- }
-
- oldMapArea = mapArea;
-
- this.mapArea = bestAllowedMapArea(newMapArea);
-
- if (localContext != null) {
- localContext.setAreaOfInterest(mapArea, localContext
- .getCoordinateReferenceSystem());
- }
- if (bgContext != null) {
- bgContext.setAreaOfInterest(mapArea, localContext
- .getCoordinateReferenceSystem());
- }
- resetTransforms();
- mapImageInvalid = true;
- mapAreaChanged = true;
- repaint();
-
- // LOGGER.debug("New maparea = " + mapArea);
- return true;
- }
-
- /**
- * Set the background color of the map.
- *
- * @param if <code>null</code>, white is used.
- */
- public void setMapBackgroundColor(Color bgColor) {
- if (bgColor == null)
- bgColor = Color.WHITE;
- this.mapBackgroundColor = bgColor;
- }
-
- /**
- * Set the BufferedImage to use as a flaoting icon in the lower right corner
- *
- * @param mapImageIcon
- * <code>null</code> is allowed and deactivates this icon.
- */
- public void setMapImage(final BufferedImage mapImage) {
- this.mapImage = mapImage;
- }
-
- /**
- * Sets whether a layer is regarded or ignored on {@link #SELECT_TOP},
- * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions.
- *
- * @param layer
- * a layer
- * @param selectable
- * if {@code false} the layer is ignored during the upper
- * mentioned actions. If <code>null</code>, the default (true)
- * will be used.
- */
- public void setMapLayerSelectable(final MapLayer layer,
- final Boolean selectable) {
- if (selectable == null)
- mapLayerSelectable.remove(layer);
- else
- mapLayerSelectable.put(layer, selectable);
- }
-
- /**
- * Defines an evelope of the viwable area. The JMapPane will never show
- * anything outside of this extend.
- *
- * @param maxExtend
- * <code>null</code> to not have this restriction.
- */
- public void setMaxExtend(final Envelope maxExtend) {
- this.maxExtend = maxExtend;
- }
-
- /**
- * Set the maximum allowed zoom scale. This is the smaller number value of
- * the two. If <code>null</code> is passed, Double.MINVALUE are used which
- * mean there is no restriction.
- *
- * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
- * Krüger</a>
- */
- public void setMaxZoomScale(final Double maxZoomScale) {
- this.maxZoomScale = maxZoomScale == null ? Double.MIN_VALUE
- : maxZoomScale;
- }
-
- // /** Stored the time used for the last real rendering in ms. **/
- // private long lastRenderingDuration = Long.MAX_VALUE;
-
- /**
- * Set the minimum (nearest) allowed zoom scale. This is the bigger number
- * value of the two. If <code>null</code> is passed, Double.MAXVALUE are
- * used which mean there is no restriction.
- *
- *
- * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
- * Krüger</a>
- */
- public void setMinZoomScale(final Double minZoomScale) {
- this.minZoomScale = minZoomScale == null ? Double.MAX_VALUE
- : minZoomScale;
- }
-
- /**
- *
- * @param b
- */
- public void setPainting(final boolean b) {
- acceptsRepaintCalls = b;
- }
-
- // /**
- // * Returns in milli seconds the time the last rending of the
- // * {@link SelectableXMapPane} took. #Long.MAX_VALUE if the JMapPane has
- // not
- // * been rendered yet.
- // */
- // public long getLastRenderingDuration() {
- // return lastRenderingDuration;
- // }
-
- public void setQuickPreviewHint(final int quickPreviewHint) {
- this.quickPreviewHint = quickPreviewHint;
-
- }
-
- private void setRendererHints(final Map<Object, Object> rendererHints) {
- this.rendererHints = rendererHints;
- }
-
- /**
- * Enables/Disables the ZOOM Mouse Listener. Upates the Cursor and stops the
- * repaint Timer if
- *
- * @param state
- */
- public void setState(final int state) {
- this.state = state;
-
- zoomMapPaneMouseListener.setEnabled((state == ZOOM_IN
- || state == ZOOM_OUT || state == PAN));
-
- // Je nach Aktion den Cursor umsetzen
- updateCursor();
- }
-
- /**
- * Standardmaessig wird der Cursor automatisch je nach MapPane-Aktion (Zoom,
- * Auswahl, ...) gesetzt. Mit dieser Methode kann ein statischer Cursor
- * gesetzt werden, der unabhaengig von der aktuellen MapPanes-Aktion
- * beibehalten wird. Um diesen statischen Cursor wieder zu entfernen, kann
- * {@code null} als Parameter uebergeben werden
- *
- * @param cursor
- * Cursor
- */
- public void setStaticCursor(final Cursor cursor) {
- this.staticCursor = cursor;
- if (cursor != null)
- super.setCursor(cursor);
- }
-
- /**
- * Starts rendering on one or two threads
- */
- private void startRendering() {
-
- if (!isWellDefined())
- return;
-
- if (bgExecuter != null) {
- // Stop all renderers
- bgExecuter.cancelTask();
- }
-
- if (localExecuter != null) {
- localExecuter.cancelTask();
- }
-
- final Rectangle curPaintArea = getVisibleRect();
-
- /**
- * We have to set new renderer
- */
-
- if (getBgContext() != null) {
- // bgExecuter = new RenderingExecutor();
- // LOGGER.debug("starting bg renderer:");
- // // /* System.out.println("rendering"); */
- // final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
- // bgContext, getRendererHints());
- // createGTRenderer.setJava2DHints(getJava2dHints());
- // bgExecuter.submit(getBgContext().getAreaOfInterest(),
- // curPaintArea,
- // (Graphics2D) getBgImage().getGraphics(), createGTRenderer);
- }
-
- if (getContext() != null) {
- // localExecuter = new RenderingExecutor(this, 150l);
- // LOGGER.debug("starting local renderer:");
-
- // Clear label cache
- labelCache.clear();
- final Map<Object, Object> rh = localRenderer.getRendererHints();
- rh.put(StreamingRenderer.LABEL_CACHE_KEY, labelCache);
- localRenderer.setRendererHints(rh);
-
- final boolean submitted = localExecuter.submit(getContext()
- .getAreaOfInterest(), curPaintArea,
- (Graphics2D) getLocalImage().getGraphics(), localRenderer,
- getWorldToScreenTransform());
- if (submitted)
- repaintTimer.restart();
- else
- requestStartRendering = true; // Try to start rendering again in
- // a moment
- }
-
- updateCursor();
- }
-
- /**
- * Transformiert einen Geo-Koordinaten-Bereich in Fenster-Koordinaten.
- *
- * @param ox
- * X-Koordinate der VON-Position
- * @param oy
- * Y-Koordinate der VON-Position
- * @param px
- * X-Koordinate der BIS-Position
- * @param py
- * Y-Koordinate der BIS-Position
- * @param winToGeotransform
- * Eine Window to Geo transform. If <code>null</code>,
- * {@link #getScreenToWorld()} is used.
- */
- public Envelope tranformGeoToWindow(final double ox, final double oy,
- final double px, final double py,
- final AffineTransform winToGeotransform) {
- final AffineTransform at = winToGeotransform == null ? getScreenToWorld()
- : winToGeotransform;
- Point2D geoO;
- try {
- geoO = at.inverseTransform(new Point2D.Double(ox, oy), null);
- final Point2D geoP = at.inverseTransform(
- new Point2D.Double(px, py), null);
- return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP
- .getY());
- } catch (final NoninvertibleTransformException e) {
- LOGGER.error(e);
- return new Envelope(ox, oy, px, py);
- }
- }
-
- /**
- * Transformiert einen Fenster-Koordinaten-Bereich in Geo-Koordinaten.
- *
- * @param ox
- * X-Koordinate der VON-Position
- * @param oy
- * Y-Koordinate der VON-Position
- * @param px
- * X-Koordinate der BIS-Position
- * @param py
- * Y-Koordinate der BIS-Position
- */
- public Envelope tranformWindowToGeo(final int ox, final int oy,
- final int px, final int py) {
- final AffineTransform at = getScreenToWorld();
- final Point2D geoO = at.transform(new Point2D.Double(ox, oy), null);
- final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
- return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
- }
-
- /**
- * Will update the cursor. If all rendering is finished also stops the
- * {@link #repaintTimer}
- */
- public void updateCursor() {
-
- // if the renderers have stopped, also stop the timer that is updating
- // the final image
- if (bgExecuter != null && bgExecuter.isRunning()
- || localExecuter != null && localExecuter.isRunning()) {
- setCursor(WAIT_CURSOR);
- return;
- } else {
- // Allow one last rendering
- if (repaintTimer.isRunning()) {
- repaintTimer.stop();
- updateFinalImage();
- repaint();
- }
- }
-
- // wenn manueller Cursor gesetzt ist, dann diesen verwenden (unabhaengig
- // von der aktuellen Aktion
- if (this.staticCursor != null) {
- setCursor(staticCursor);
- return;
- }
- if (getCursor() == SwingUtil.PANNING_CURSOR) {
- // This cursor will reset itself
- return;
- }
-
- // Set the cursor depending on what tool is in use...
- switch (state) {
- case SELECT_TOP:
- case SELECT_ONE_FROM_TOP:
- case SELECT_ALL:
- setCursor(SwingUtil.CROSSHAIR_CURSOR);
- break;
- case ZOOM_IN:
- setCursor(SwingUtil.ZOOMIN_CURSOR);
- break;
- case ZOOM_OUT:
- setCursor(SwingUtil.ZOOMOUT_CURSOR);
- break;
- case PAN:
- setCursor(SwingUtil.PAN_CURSOR);
- break;
- default:
- setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
- break;
- }
- }
-
- /**
- * The renderers are all rendering into their own {@link Image}s. This
- * method combines all images to one {@link #finalImage}. The
- * {@link #repaintTimer} is calling this method regularely to update the
- * {@link #finalImage} even if the renderers are still working.
- */
- synchronized protected Image updateFinalImage() {
-
- // Render the two map images first, into the preFinalImage
- if (bgExecuter != null) {
- final Graphics2D preFinalG = (Graphics2D) getPreFinalImage()
- .getGraphics();
- preFinalG.setBackground(getMapBackgroundColor());
-
- preFinalG.drawImage(getBgImage(), 0, 0, getMapBackgroundColor(),
- null);
-
- // // Draw the local layers image
- preFinalG.drawImage(getLocalImage(), 0, 0, null);
- preFinalG.dispose();
-
- } else {
- preFinalImage = getLocalImage();
- }
-
- final Graphics2D finalG = getFinalImage().createGraphics();
- finalG.setBackground(getMapBackgroundColor());
- finalG.drawImage(getPreFinalImage(), imageOrigin.x, imageOrigin.y,
- getMapBackgroundColor(), null);
-
- final int finalImageHeight = getFinalImage().getHeight(null);
- final int finalImageWidth = getFinalImage().getWidth(null);
-
- final Rectangle painedArea = new Rectangle(imageOrigin.x,
- imageOrigin.y, finalImageWidth, finalImageHeight);
- SwingUtil.clearAround(finalG, painedArea, getVisibleRect());
-
- addGadgets(finalG);
-
- finalG.dispose();
-
- return finalImage;
- }
-
- /**
- * Paints some optional stuff into the given {@link Graphics2D}. Usually
- * called as the last layer when {@link #updateFinalImage()}
- */
- private void addGadgets(final Graphics2D graphics) {
-
- // Paint a logo to the bottom right if available
- if (mapImage != null) {
- graphics.drawImage(mapImage, getBounds().width
- - mapImage.getWidth() - 10, getBounds().height
- - mapImage.getHeight() - 10, null);
- }
-
- // If the rendering process is still running, indicate this is the image
- if (bgExecuter != null && bgExecuter.isRunning()
- || localExecuter != null && localExecuter.isRunning()) {
-
- final Color c = graphics.getColor();
- graphics.setFont(waitFont);
- String waitStr = "Wait..."; //i8n
-
- graphics.setColor(Color.WHITE);
- graphics.drawString(waitStr, 39, 69);
- graphics.setColor(Color.BLACK);
- graphics.drawString(waitStr, 40, 70);
-
- graphics.setColor(c);
- }
-
- }
-
- /**
- * Sets the {@link #mapArea} to best possibly present the given features. If only one
- * single point is given, the window is moved over the point.
- *
- * @param features
- * if <code>null</code> or size==0, the function doesn nothing.
- */
- public void zoomTo(
- final FeatureCollection<SimpleFeatureType, SimpleFeature> features) {
-
- final CoordinateReferenceSystem mapCRS = getContext()
- .getCoordinateReferenceSystem();
- final CoordinateReferenceSystem fCRS = features.getSchema()
- .getGeometryDescriptor().getCoordinateReferenceSystem();
-
- double width = mapArea.getWidth();
- double height = mapArea.getHeight();
- final double ratio = height / width;
-
- if (features == null || features.size() == 0) {
- // feature count == 0 Zoom to the full extend
- return;
- } else if (features.size() == 1) {
-
- // feature count == 1 Just move the window to the point and zoom 'a
- // bit'
- final SimpleFeature singleFeature = features.iterator().next();
-
- if (((Geometry) singleFeature.getDefaultGeometry())
- .getCoordinates().length > 1) {
- // System.out.println("Zoomed to only pne poylgon");
- // Poly
- // TODO max width vs. height
- width = features.getBounds().getWidth() * 3;
- height = ratio * width;
- } else {
- // System.out.println("Zoomed in a bit becasue only one point");
- // width *= .9;
- // height *= .9;
- }
-
- Coordinate centre = features.getBounds().centre();
- if (!mapCRS.equals(fCRS)) {
- // only to calculations if the CRS differ
- try {
- MathTransform fToMap;
- fToMap = CRS.findMathTransform(fCRS, mapCRS);
- // centre is transformed to the mapCRS
- centre = JTS.transform(centre, null, fToMap);
- } catch (final FactoryException e) {
- LOGGER.error("Looking for a Math transform", e);
- } catch (final TransformException e) {
- LOGGER.error("Looking for a Math transform", e);
- }
- }
-
- final Coordinate newLeftBottom = new Coordinate(centre.x - width
- / 2., centre.y - height / 2.);
- final Coordinate newTopRight = new Coordinate(
- centre.x + width / 2., centre.y + height / 2.);
-
- final Envelope newMapArea = new Envelope(newLeftBottom, newTopRight);
-
- setMapArea(newMapArea);
-
- } else {
- final ReferencedEnvelope fBounds = features.getBounds();
-
- Envelope bounds;
- if (!mapCRS.equals(fCRS)) {
- bounds = JTSUtil.transformEnvelope(fBounds, fCRS, mapCRS);
- } else {
- bounds = fBounds;
- }
- // BB umrechnen von Layer-CRS in Map-CRS
-
- // Expand a bit
- bounds.expandBy(bounds.getWidth() / 6., bounds.getHeight() / 6.);
-
- setMapArea(bounds);
- }
- }
-
- /**
- * Zooms towards a point.
- *
- * @param center
- * position in window coordinates
- * @param zoomFactor
- * > 1 for zoom in, < 1 for zoom out. Default is 1.33
- */
- public void zoomTo(final Point center) {
- zoomTo(center, null);
- }
-
- /**
- * Zooms towards a point.
- *
- * @param center
- * position in window coordinates
- * @param zoomFaktor
- * > 1 for zoom in, < 1 for zoom out. Default is 1.33.
- */
- public void zoomTo(Point center, Double zoomFaktor) {
- if (zoomFaktor == null || zoomFaktor == 0.)
- zoomFaktor = 2.;
-
- final Point2D gcenter = getScreenToWorld().transform(center, null);
- center = null;
-
- if (Double.isNaN(gcenter.getX()) || Double.isNaN(gcenter.getY())
- || Double.isInfinite(gcenter.getX())
- || Double.isInfinite(gcenter.getY())
-
- ) {
- // Not inside valid CRS area! cancel
- return;
- }
-
- final Envelope mapArea = getMapArea();
-
- final Envelope newMapArea = new Envelope(mapArea);
- newMapArea.expandBy((mapArea.getWidth() * zoomFaktor - mapArea
- .getWidth()) / 2., (mapArea.getHeight() * zoomFaktor - mapArea
- .getHeight()) / 2.);
-
- // Move the newMapArea above the new center
- newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter
- .getY()
- - mapArea.centre().y);
-
- setMapArea(newMapArea);
- }
-
-}
Deleted: branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/ZoomXMapPaneMouseListener.java
===================================================================
--- branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/ZoomXMapPaneMouseListener.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/ZoomXMapPaneMouseListener.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -1,251 +0,0 @@
-package gtmig.org.geotools.swing;
-
-import java.awt.Color;
-import java.awt.Graphics;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.event.InputEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseWheelEvent;
-
-import org.apache.log4j.Logger;
-
-import schmitzm.swing.SwingUtil;
-
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.Envelope;
-
-public class ZoomXMapPaneMouseListener extends MouseAdapter {
-
- private static Logger LOGGER = Logger
- .getLogger(ZoomXMapPaneMouseListener.class);
-
-
- protected final XMapPane xMapPane;
-
- protected boolean enabled = true;
-
- public ZoomXMapPaneMouseListener(XMapPane xMapPane) {
- if (xMapPane == null) throw new IllegalArgumentException("xMapPane may not be null");
- this.xMapPane = xMapPane;
- }
-
- /**
- * Stores beginning of a drag event in window coordinates
- */
- protected Point startPos;
-
- /**
- * Stores last position of a drag event in window coordinates
- */
- protected Point lastPos;
-
- /**
- * Draws a rectangle in XOR mode from the origin at {@link #startPos} to the
- * given point. All in screen coordinates.
- */
- protected void drawRectangle(final Graphics graphics, Point e) {
- // undraw last box/draw new box
- final int left = Math.min(startPos.x, e.x);
- final int right = Math.max(startPos.x, e.x);
- final int top = Math.max(startPos.y, e.y);
- final int bottom = Math.min(startPos.y, e.y);
- final int width = right - left;
- final int height = top - bottom;
-
- if (width == 0 && height == 0)
- return;
-
- graphics.setXORMode(Color.WHITE);
- graphics.drawRect(left, bottom, width, height);
- }
-
- public void mousePressed(final MouseEvent e) {
- if (!isEnabled())
- return;
- startPos = new Point(e.getPoint());
- lastPos = new Point(e.getPoint());
- }
-
- public void mouseReleased(final MouseEvent e) {
- if (!isEnabled())
- return;
-
- final Rectangle bounds = xMapPane.getBounds();
-
- Envelope mapArea = xMapPane.getMapArea();
-
- // Replace with transform and translate
- final double mapWidth = mapArea.getWidth();
- final double mapHeight = mapArea.getHeight();
-
- final double startX = ((startPos.x * mapWidth) / (double) bounds.width)
- + mapArea.getMinX();
- final double startY = (((bounds.getHeight() - startPos.y) * mapHeight) / (double) bounds.height)
- + mapArea.getMinY();
- final double endX = ((e.getPoint().x * mapWidth) / (double) bounds.width)
- + mapArea.getMinX();
- final double endY = (((bounds.getHeight() - e.getPoint().y) * mapHeight) / (double) bounds.height)
- + mapArea.getMinY();
-
- if ((xMapPane.getState() == XMapPane.PAN)
- || (e.getButton() == MouseEvent.BUTTON3)) {
- xMapPane.performPan();
-
- } else if (xMapPane.getState() == XMapPane.ZOOM_IN) {
-
- drawRectangle(xMapPane.getGraphics(), e.getPoint());
-
-
- // Don't zoom too small areas. Handle it like a click then
- if ((Math.abs(startPos.x - e.getPoint().x) * Math.abs(e.getPoint().y - startPos.y)) < 160) {
- xMapPane.zoomTo(e.getPoint(), .5);
- return;
- }
-
- final double left = Math.min(startX, endX);
- final double right = Math.max(startX, endX);
- final double bottom = Math.min(startY, endY);
- final double top = Math.max(startY, endY);
- final Coordinate ll = new Coordinate(left, bottom);
- final Coordinate ur = new Coordinate(right, top);
-
- xMapPane.setMapArea(new Envelope(ll, ur));
-
- } else if (xMapPane.getState() == XMapPane.ZOOM_OUT) {
-
- // Remove the rectangle
- drawRectangle(xMapPane.getGraphics(), e.getPoint());
-
-
-
- // Don't zoom too small areas
- if ((Math.abs(startPos.x - e.getPoint().x) * Math.abs(e.getPoint().y - startPos.y)) < 100) {
- xMapPane.zoomTo(e.getPoint(), 2.);
- return;
- }
-
- // make the dragged rectangle in screen coords the new map size?
- final double left = Math.min(startX, endX);
- final double right = Math.max(startX, endX);
- final double bottom = Math.min(startY, endY);
- final double top = Math.max(startY, endY);
- final double nWidth = (mapWidth * mapWidth) / (right - left);
- final double nHeight = (mapHeight * mapHeight) / (top - bottom);
- final double deltaX1 = left - mapArea.getMinX();
- final double nDeltaX1 = (deltaX1 * nWidth) / mapWidth;
- final double deltaY1 = bottom - mapArea.getMinY();
- final double nDeltaY1 = (deltaY1 * nHeight) / mapHeight;
- final Coordinate ll = new Coordinate(mapArea.getMinX() - nDeltaX1,
- mapArea.getMinY() - nDeltaY1);
- final double deltaX2 = mapArea.getMaxX() - right;
- final double nDeltaX2 = (deltaX2 * nWidth) / mapWidth;
- final double deltaY2 = mapArea.getMaxY() - top;
- final double nDeltaY2 = (deltaY2 * nHeight) / mapHeight;
- final Coordinate ur = new Coordinate(mapArea.getMaxX() + nDeltaX2,
- mapArea.getMaxY() + nDeltaY2);
-
- xMapPane.setMapArea(new Envelope(ll, ur));
- }
-
-
- }
-
-
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
- }
-
- /**
- * Returns <code>true</code> if the xMapPane is well defined and the
- * listener is enabled.
- */
- public boolean isEnabled() {
- return enabled && xMapPane.isWellDefined();
- }
-
- /**
- * Verarbeitet die Mausrad-Aktion, indem gezoomed wird.
- *
- * @param wheelEvt
- * Mausrad-Event
- */
- public void mouseWheelMoved(MouseWheelEvent wheelEvt) {
- if (!isEnabled()) return;
-
- final int units = wheelEvt.getUnitsToScroll();
- if (units > 0)
- xMapPane.zoomTo(wheelEvt.getPoint(), 1. + .11 * units);
- else
- xMapPane.zoomTo(wheelEvt.getPoint(), 2. / -units);
- }
-
- @Override
- public void mouseClicked(final MouseEvent e) {
-
- if (!isEnabled())
- return;
-
- int state = xMapPane.getState();
-
- // Nothing to do for panning
- if (state == XMapPane.PAN)
- return;
-
- // double zoomFactor = 1.;
- //
- // final Envelope mapArea = xMapPane.getMapArea();
- // if (mapArea == null)
- // return;
- //
- // final Rectangle bounds = xMapPane.getVisibleRect();
- //
- // final double x = (double) (e.getX());
- // final double y = (double) (e.getY());
- // final double width = mapArea.getWidth();
- // final double height = mapArea.getHeight();
- // final double width2 = width / 2.0;
- // final double height2 = height / 2.0;
- // final double mapX = ((x * width) / (double) bounds.width)
- // + mapArea.getMinX();
- // final double mapY = (((bounds.getHeight() - y) * height) / (double)
- // bounds.height)
- // + mapArea.getMinY();
- // double zlevel = 1.0;
- //
- if (state == XMapPane.ZOOM_IN && e.getButton() == MouseEvent.BUTTON1) {
- xMapPane.zoomTo(e.getPoint(), 1 / 2.);
- } else if (state == XMapPane.ZOOM_IN
- && e.getButton() == MouseEvent.BUTTON3
- || state == XMapPane.ZOOM_OUT) {
- xMapPane.zoomTo(e.getPoint(), 2.);
- }
- //
- // final Coordinate ll = new Coordinate(mapX - (width2 / zlevel), mapY
- // - (height2 / zlevel));
- // final Coordinate ur = new Coordinate(mapX + (width2 / zlevel), mapY
- // + (height2 / zlevel));
- // final Envelope newMapArea = new Envelope(ll, ur);
- //
- // xMapPane.setMapArea(newMapArea);
-
- // xMapPane.repaint();
-
- }
-
- /**
- * Constantly fired while the mouse is dragging
- */
- public void mouseDragged(final MouseEvent event) {
- if (!isEnabled())
- return;
-
- xMapPane.mouseDragged(startPos, lastPos, event);
-
-
- lastPos = event.getPoint();
- }
-
-
-}
Modified: branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/FeatureTablePane.java
===================================================================
--- branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/FeatureTablePane.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/FeatureTablePane.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -29,8 +29,6 @@
******************************************************************************/
package schmitzm.geotools.gui;
-import gtmig.org.geotools.swing.XMapPane;
-
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
@@ -58,6 +56,7 @@
import schmitzm.swing.JPanel;
import schmitzm.swing.SortableJTable;
import schmitzm.swing.SwingUtil;
+import skrueger.geotools.XMapPane;
/**
* Diese Komponente stellt eine Tabelle dar, in der die Attribute einer
Modified: branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/MouseSelectionTracker.java
===================================================================
--- branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/MouseSelectionTracker.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/MouseSelectionTracker.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -38,7 +38,7 @@
* @see #setSelectionMode(SelectionMode)
* @author <a href="mailto:Martin.Schmitz at koeln.de">Martin Schmitz</a>
*/
-public abstract class MouseSelectionTracker extends gtmig.org.geotools.swing.MouseSelectionTracker {
+public abstract class MouseSelectionTracker extends skrueger.geotools.MouseSelectionTracker {
/** Selection events the tracker reacts on. */
public enum SelectionMode {
/** Tracker reacts on single clicks as well as on
Modified: branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/SelectableXMapPane.java
===================================================================
--- branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/SelectableXMapPane.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/schmitzm/geotools/gui/SelectableXMapPane.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -29,11 +29,6 @@
******************************************************************************/
package schmitzm.geotools.gui;
-import gtmig.org.geotools.swing.GeomFilterGenerator;
-import gtmig.org.geotools.swing.SelectXMapPaneMouseListener;
-import gtmig.org.geotools.swing.XMapPane;
-import gtmig.org.geotools.swing.GeomFilterGenerator.BoundingBoxFilterGenerator;
-
import java.awt.BorderLayout;
import java.awt.LayoutManager;
import java.awt.Point;
@@ -91,6 +86,10 @@
import schmitzm.geotools.map.event.MapPaneEvent;
import schmitzm.geotools.map.event.ScaleChangedEvent;
import schmitzm.geotools.styling.StylingUtil;
+import skrueger.geotools.GeomFilterGenerator;
+import skrueger.geotools.SelectXMapPaneMouseListener;
+import skrueger.geotools.XMapPane;
+import skrueger.geotools.GeomFilterGenerator.BoundingBoxFilterGenerator;
import com.vividsolutions.jts.geom.Envelope;
@@ -137,7 +136,7 @@
* (University of Bonn/Germany)
* @version 1.0
*/
-public class SelectableXMapPane extends gtmig.org.geotools.swing.XMapPane {
+public class SelectableXMapPane extends XMapPane {
/** Logger for debug messages. */
protected static final Logger LOGGER = Logger
Modified: branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/FeatureSelectedEvent.java
===================================================================
--- branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/FeatureSelectedEvent.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/FeatureSelectedEvent.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -29,12 +29,11 @@
******************************************************************************/
package schmitzm.geotools.map.event;
-import gtmig.org.geotools.swing.XMapPane;
-
import org.geotools.feature.FeatureCollection;
import org.geotools.map.MapLayer;
import schmitzm.geotools.gui.SelectableXMapPane;
+import skrueger.geotools.XMapPane;
import com.vividsolutions.jts.geom.Envelope;
Modified: branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/GeneralSelectionEvent.java
===================================================================
--- branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/GeneralSelectionEvent.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/GeneralSelectionEvent.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -29,7 +29,6 @@
******************************************************************************/
package schmitzm.geotools.map.event;
-import gtmig.org.geotools.swing.XMapPane;
import schmitzm.geotools.gui.SelectableXMapPane;
import com.vividsolutions.jts.geom.Envelope;
Modified: branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/GridCoverageSelectedEvent.java
===================================================================
--- branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/GridCoverageSelectedEvent.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/GridCoverageSelectedEvent.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -29,12 +29,11 @@
******************************************************************************/
package schmitzm.geotools.map.event;
-import gtmig.org.geotools.swing.XMapPane;
-
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.map.MapLayer;
import schmitzm.geotools.gui.SelectableXMapPane;
+import skrueger.geotools.XMapPane;
import com.vividsolutions.jts.geom.Envelope;
Modified: branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/MapPaneEvent.java
===================================================================
--- branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/MapPaneEvent.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/MapPaneEvent.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -29,8 +29,8 @@
******************************************************************************/
package schmitzm.geotools.map.event;
-import gtmig.org.geotools.swing.XMapPane;
import schmitzm.geotools.gui.SelectableXMapPane;
+import skrueger.geotools.XMapPane;
/**
* Diese Klasse stellt ein Ereignis dar, das von einem {@link SelectableXMapPane}
Modified: branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/ObjectSelectionEvent.java
===================================================================
--- branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/ObjectSelectionEvent.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/schmitzm/geotools/map/event/ObjectSelectionEvent.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -29,11 +29,10 @@
******************************************************************************/
package schmitzm.geotools.map.event;
-import gtmig.org.geotools.swing.XMapPane;
-
import org.geotools.map.MapLayer;
import schmitzm.geotools.gui.SelectableXMapPane;
+import skrueger.geotools.XMapPane;
import com.vividsolutions.jts.geom.Envelope;
Modified: branches/1.0-gt2-2.6/src/schmitzm/geotools/styling/StylingUtil.java
===================================================================
--- branches/1.0-gt2-2.6/src/schmitzm/geotools/styling/StylingUtil.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/schmitzm/geotools/styling/StylingUtil.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -29,10 +29,7 @@
******************************************************************************/
package schmitzm.geotools.styling;
-import gtmig.org.geotools.swing.XMapPane;
-
import java.awt.Color;
-import java.awt.Component;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -107,8 +104,6 @@
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
-import org.opengis.feature.type.AttributeDescriptor;
-import org.opengis.feature.type.Name;
import org.opengis.filter.And;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
@@ -124,6 +119,7 @@
import schmitzm.lang.LangUtil;
import skrueger.geotools.StyledFeaturesInterface;
import skrueger.geotools.StyledRasterInterface;
+import skrueger.geotools.XMapPane;
import skrueger.geotools.selection.FeatureMapLayerSelectionSynchronizer;
import com.vividsolutions.jts.geom.Geometry;
Copied: branches/1.0-gt2-2.6/src/skrueger/geotools/GeomFilterGenerator.java (from rev 512, branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/GeomFilterGenerator.java)
===================================================================
--- branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/GeomFilterGenerator.java 2009-11-06 15:00:17 UTC (rev 512)
+++ branches/1.0-gt2-2.6/src/skrueger/geotools/GeomFilterGenerator.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -0,0 +1,249 @@
+package skrueger.geotools;
+
+import java.awt.geom.Point2D;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.geotools.data.FeatureSource;
+import org.geotools.filter.AbstractFilter;
+import org.geotools.filter.GeometryFilterImpl;
+import org.geotools.filter.spatial.DWithinImpl;
+import org.geotools.referencing.CRS;
+import org.geotools.resources.CRSUtilities;
+import org.opengis.feature.simple.SimpleFeature;
+import org.opengis.feature.simple.SimpleFeatureType;
+import org.opengis.feature.type.GeometryDescriptor;
+import org.opengis.filter.expression.Expression;
+import org.opengis.filter.spatial.BinarySpatialOperator;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
+import schmitzm.geotools.FilterUtil;
+import schmitzm.geotools.JTSUtil;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+
+/**
+ * The {@link GeomFilterGenerator} prepares a {@link BinarySpatialOperator}
+ * filter for multiple use. Only the "right" argument is prepared. The
+ * "left" argument (the geometry attribute of the {@link FeatureSource} to
+ * filter) is first set on calling {@link #adaptFilter(FeatureSource)} for a
+ * specific {@link FeatureSource}. This method also takes care to recreate
+ * the filter (or its "right" argument) if the given {@link FeatureSource}
+ * has another {@link CoordinateReferenceSystem} than the base constraint.<br>
+ * The type of filter (e.g. distance or bounding box) is specified by the
+ * subclass implemenation of
+ * {@link #prepareFilter(CoordinateReferenceSystem)} .
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+ */
+ public abstract class GeomFilterGenerator {
+ /**
+ * Holds the {@link CoordinateReferenceSystem} the filter constraint
+ * bases on.
+ */
+ protected CoordinateReferenceSystem baseCRS = null;
+ /**
+ * Caches the base filter constraint for several
+ * {@link CoordinateReferenceSystem CoordinateReferenceSystems}.
+ */
+ protected Map<CoordinateReferenceSystem, GeometryFilterImpl> filterCache = new HashMap<CoordinateReferenceSystem, GeometryFilterImpl>();
+
+ /**
+ * Creates a new filter generator
+ *
+ * @param crs
+ * {@link CoordinateReferenceSystem} the base constraint
+ * ("right" filter argument is relative to)
+ */
+ public GeomFilterGenerator(CoordinateReferenceSystem crs) {
+ this.baseCRS = crs;
+ }
+
+ /**
+ * Creates a filter containing the base constraint ("right" argument)
+ * transformed to the given {@link CoordinateReferenceSystem}.
+ *
+ * @param crs
+ * the {@link CoordinateReferenceSystem} the base constraint
+ * is transformed to
+ */
+ protected abstract GeometryFilterImpl prepareFilter(
+ CoordinateReferenceSystem crs);
+
+ /**
+ * Completes the filter with its "left" argument for a concrete
+ * {@link FeatureSource}. If the {@link FeatureSource FeatureSource's}
+ * CRS differs from the CRS the base constraint is specified in, first a
+ * new filter is created by calling
+ * {@link #prepareFilter(CoordinateReferenceSystem)}.
+ *
+ * @param fs
+ * {@link FeatureSource} the filter is adaped to
+ * @return
+ */
+ public GeometryFilterImpl adaptFilter(
+ FeatureSource<SimpleFeatureType, SimpleFeature> fs) {
+ GeometryDescriptor geomDescr = fs.getSchema()
+ .getGeometryDescriptor();
+ CoordinateReferenceSystem fsCRS = geomDescr
+ .getCoordinateReferenceSystem();
+ GeometryFilterImpl filter = filterCache.get(fsCRS);
+ if (filter == null) {
+ filter = prepareFilter(fsCRS);
+ filterCache.put(fsCRS, filter);
+ }
+ Expression geometry = FilterUtil.FILTER_FAC2.property(geomDescr.getLocalName());
+ filter.setExpression1(geometry);
+ return filter;
+ }
+
+ /**
+ * {@link GeomFilterGenerator} for a bounding box constraint.
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+ */
+ public static class BoundingBoxFilterGenerator extends GeomFilterGenerator {
+ /**
+ * Holds the base constraint (bounding box {@link Envelope}) relative to
+ * the {@linkplain GeomFilterGenerator#baseCRS base CRS}.
+ */
+ protected Envelope baseEnv = null;
+
+ /**
+ * Creates a new filter generator.
+ *
+ * @param baseEnv
+ * defines the bounding box
+ * @param baseCrs
+ * defines the CRS of the bounding box
+ */
+ public BoundingBoxFilterGenerator(Envelope baseEnv,
+ CoordinateReferenceSystem baseCrs) {
+ super(baseCrs);
+ this.baseEnv = baseEnv;
+ }
+
+ /**
+ * Prepares a filter with the bounding box transformed to the given
+ * {@link CoordinateReferenceSystem} as the "right" argument.
+ *
+ * @param crs
+ * the {@link CoordinateReferenceSystem} the bounding box is
+ * transformed to
+ */
+ protected GeometryFilterImpl prepareFilter(CoordinateReferenceSystem crs) {
+ Envelope bbEnv = baseEnv;
+ if (!CRS.equalsIgnoreMetadata(baseCRS,crs))
+ bbEnv = JTSUtil.transformEnvelope(baseEnv, baseCRS, crs);
+ // Filter fuer Envelope zusammenstellen
+ Expression bbox = FilterUtil.FILTER_FAC.createBBoxExpression(bbEnv);
+ GeometryFilterImpl bboxFilter = (GeometryFilterImpl) FilterUtil.FILTER_FAC
+ .createGeometryFilter(AbstractFilter.GEOMETRY_BBOX);
+ // GeometryFilterImpl bboxFilter =
+ // (GeometryFilterImpl)ff.createGeometryFilter(AbstractFilter.
+ // GEOMETRY_WITHIN);
+ bboxFilter.setExpression2(bbox);
+ return bboxFilter;
+ }
+ }
+
+ /**
+ * {@link GeomFilterGenerator} for a "near distance" constraint.
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+ */
+ public static class NearPointFilterGenerator extends GeomFilterGenerator {
+ /**
+ * Holds the base constraint (coordinate) relative to the
+ * {@linkplain GeomFilterGenerator#baseCRS base CRS}.
+ */
+ protected Coordinate basePoint = null;
+ /**
+ * Holds the distance "around" the base point relative to the
+ * {@linkplain GeomFilterGenerator#baseCRS base CRS}.
+ */
+ protected double baseDist = 0.0;
+ /**
+ * Holds a point which is in distance {@link #baseDist} to the
+ * {@link #basePoint}.
+ */
+ protected Coordinate basePoint2 = null;
+
+ /**
+ * Creates a new filter generator.
+ *
+ * @param basePoint
+ * defNearPointFilterGeneratorines the point for the "near point" constraint
+ * @param dist
+ * defines the distance around the base point
+ * @param crs
+ * defines the CRS of base point
+ */
+ public NearPointFilterGenerator(Coordinate basePoint, double dist,
+ CoordinateReferenceSystem crs) {
+ super(crs);
+ this.basePoint = basePoint;
+ this.baseDist = dist;
+ // Create a point which is in distance "dist" to the base point
+ this.basePoint2 = new Coordinate(basePoint.x + dist, basePoint.y);
+ }
+
+ /**
+ * Creates a new filter generator.
+ *
+ * @param basePoint
+ * defines the point for the "near point" constraint
+ * @param dist
+ * defines the distance around the base point
+ * @param crs
+ * defines the CRS of base point
+ */
+ public NearPointFilterGenerator(Point2D basePoint, double dist,
+ CoordinateReferenceSystem crs) {
+ this(new Coordinate(basePoint.getX(), basePoint.getY()), dist, crs);
+ }
+
+ /**
+ * Prepares a filter with the base point and distance transformed to the
+ * given {@link CoordinateReferenceSystem} as the "right" argument.
+ *
+ * @param crs
+ * the {@link CoordinateReferenceSystem} the point and
+ * distance is transformed to
+ */
+ protected GeometryFilterImpl prepareFilter(CoordinateReferenceSystem crs) {
+ Coordinate nearPoint = basePoint;
+ double nearDist = baseDist;
+ if (!baseCRS.equals(crs)) {
+ nearPoint = JTSUtil
+ .transformCoordinate(basePoint, baseCRS, crs);
+ // Transform the distance (maybe "dirty")
+ // --> transform the point2 and calculate the
+ // distance to the tranformed base point
+ Coordinate nearPoint2 = JTSUtil.transformCoordinate(basePoint2,
+ baseCRS, crs);
+
+ if (nearPoint == null || nearPoint2 == null)
+ throw new RuntimeException("Unable to transform CRS from "
+ + baseCRS + " to " + crs);
+
+ nearDist = Math.abs(nearPoint.x - nearPoint2.x);
+ }
+ // Filter fuer Point zusammenstellen
+ final Geometry geometry = FilterUtil.GEOMETRY_FAC
+ .createPoint(nearPoint);
+
+ final DWithinImpl dwithinFilter = new DWithinImpl(
+ FilterUtil.FILTER_FAC2, null, null);
+ dwithinFilter.setDistance(nearDist);
+ dwithinFilter.setExpression2(FilterUtil.FILTER_FAC2
+ .literal(geometry));
+
+ return dwithinFilter;
+ }
+
+ }
+
+}
\ No newline at end of file
Copied: branches/1.0-gt2-2.6/src/skrueger/geotools/MouseSelectionTracker.java (from rev 512, branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/MouseSelectionTracker.java)
===================================================================
--- branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/MouseSelectionTracker.java 2009-11-06 15:00:17 UTC (rev 512)
+++ branches/1.0-gt2-2.6/src/skrueger/geotools/MouseSelectionTracker.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -0,0 +1,377 @@
+// Migration process to Geotools 2.6:
+// Because not included in gt2-2.6-M2 this class is taken 1:1
+// from Geotools 2.4.5 and made public.
+// TODO: It should be removed or included in SCHMITZM.
+
+/*
+ * GeoTools - OpenSource mapping toolkit
+ * http://geotools.org
+ * (C) 2003-2006, Geotools Project Managment Committee (PMC)
+ * (C) 2001, Institut de Recherche pour le D�veloppement
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+package skrueger.geotools;
+
+// Geometry
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.event.MouseEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+import java.awt.geom.RoundRectangle2D;
+
+import javax.swing.event.MouseInputAdapter;
+
+
+/**
+ * Controller which allows the user to select a region of a component. The user must click on a
+ * point in the component, then drag the mouse pointer whilst keeping the button pressed. During
+ * the dragging, the shape which is drawn will normally be a rectangle. Other shapes could always
+ * be used such as, for example, an ellipse. To use this class, it is necessary to create a derived
+ * class which defines the following methods:
+ *
+ * <ul>
+ * <li>{@link #selectionPerformed} (obligatory)</li>
+ * <li>{@link #getModel} (optional)</li>
+ * </ul>
+ *
+ * This controller should then be registered with one, and only one, component
+ * using the following syntax:
+ *
+ * <blockquote><pre>
+ * {@link Component} component=...
+ * MouseSelectionTracker control=...
+ * component.addMouseListener(control);
+ * </pre></blockquote>
+ *
+ * @since 2.0
+ * @source $URL: http://svn.geotools.org/tags/2.4.5/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/MouseSelectionTracker.java $
+ * @version $Id: MouseSelectionTracker.java 22482 2006-10-31 02:58:00Z desruisseaux $
+ * @author Martin Desruisseaux
+ */
+public abstract class MouseSelectionTracker extends MouseInputAdapter {
+ /**
+ * Stippled rectangle representing the region which the user is currently
+ * selecting. This rectangle can be empty. These coordinates are only
+ * significant in the period between the user pressing the mouse button
+ * and then releasing it to outline a region. Conventionally, the
+ * {@code null} value indicates that a line should be used instead of
+ * a rectangular shape. The coordinates are always expressed in pixels.
+ */
+ private transient RectangularShape mouseSelectedArea;
+
+ /**
+ * Colour to replace during XOR drawings on a graphic.
+ * This colour is specified in {@link Graphics2D#setColor}.
+ */
+ private Color backXORColor = Color.white;
+
+ /**
+ * Colour to replace with during the XOR drawings on a graphic.
+ * This colour is specified in {@link Graphics2D#setXORMode}.
+ */
+ private Color lineXORColor = Color.black;
+
+ /**
+ * <var>x</var> coordinate of the mouse when the button is pressed.
+ */
+ private transient int ox;
+
+ /**
+ * <var>y</var> coordinate of the mouse when the button is pressed.
+ */
+ private transient int oy;
+
+ /**
+ * <var>x</var> coordinate of the mouse during the last drag.
+ */
+ private transient int px;
+
+ /**
+ * <var>y</var> coordinate of the mouse during the last drag.
+ */
+ private transient int py;
+
+ /**
+ * Indicates whether a selection is underway.
+ */
+ private transient boolean isDragging;
+
+ /**
+ * Constructs an object which will allow rectangular regions to be selected using the mouse.
+ */
+ public MouseSelectionTracker() {
+ }
+
+ /**
+ * Specifies the colours to be used for drawing the outline of a box when
+ * the user selects a region. All {@code a} colours will be replaced
+ * by {@code b} colours and vice versa.
+ */
+ public void setXORColors(final Color a, final Color b) {
+ backXORColor = a;
+ lineXORColor = b;
+ }
+
+ /**
+ * Returns the geometric shape to use for marking the boundaries of a region. This shape is
+ * normally a rectangle but could also be an ellipse, an arrow or even other shapes. The
+ * coordinates of the returned shape will not be taken into account. In fact, these coordinates
+ * will regularly be discarded. Only the class of the returned shape will count (for example,
+ * {@link java.awt.geom.Ellipse2D} vs {@link java.awt.geom.Rectangle2D}) and their parameters
+ * which are not linked to their position (for example, the rounding of a rectangle's
+ * corners).
+ * <p>
+ * The shape returned will normally be from a class derived from {@link RectangularShape},
+ * but could also be from the {@link Line2D} class. <strong>Any other class risks throwing a
+ * {@link ClassCastException} when executed</strong>.
+ *
+ * The default implementation always returns an object {@link Rectangle}.
+ *
+ * @param event Mouse coordinate when the button is pressed. This information can be used by
+ * the derived classes which like to be informed of the position of the mouse before
+ * chosing a geometric shape.
+ * @return Shape from the class {link RectangularShape} or {link Line2D}, or {@code null}
+ * to indicate that we do not want to make a selection.
+ */
+ protected Shape getModel(final MouseEvent event) {
+ return new Rectangle();
+ }
+
+ /**
+ * Method which is automatically called after the user selects a region with the mouse.
+ * All coordinates passed in as parameters are expressed in pixels.
+ *
+ * @param ox <var>x</var> coordinate of the mouse when the user pressed the mouse button.
+ * @param oy <var>y</var> coordinate of the mouse when the user pressed the mouse button.
+ * @param px <var>x</var> coordinate of the mouse when the user released the mouse button.
+ * @param py <var>y</var> coordinate of the mouse when the user released the mouse button.
+ */
+ protected abstract void selectionPerformed(int ox, int oy, int px, int py);
+
+ /**
+ * Returns the geometric shape surrounding the last region to be selected by the user. An
+ * optional affine transform can be specified to convert the region selected by the user
+ * into logical coordinates. The class of the shape returned depends on the model returned by
+ * {@link #getModel}:
+ *
+ * <ul>
+ * <li>If the model is null (which means that this {@code MouseSelectionTracker} object only
+ * draws a line between points), the object returned will belong to the {@link Line2D}
+ * class.</li>
+ * <li>If the model is not null, the object returned can be from the same class (most often
+ * {@link java.awt.geom.Rectangle2D}). There could always be situations where the object
+ * returned is from another class, for example if the affine transform carries out a
+ * rotation.</li>
+ * </ul>
+ *
+ * @param transform Affine transform which converts logical coordinates into pixel coordinates.
+ * It is usually an affine transform which is used in a {@code paint(...)} method to
+ * draw shapes expressed in logical coordinates.
+ * @return A geometric shape enclosing the last region to be selected by the user, or
+ * {@code null} if no selection has yet been made.
+ * @throws NoninvertibleTransformException If the affine transform can't be inverted.
+ */
+ public Shape getSelectedArea(final AffineTransform transform) throws NoninvertibleTransformException {
+ if (ox == px && oy == py) {
+ return null;
+ }
+ RectangularShape shape = mouseSelectedArea;
+ if (transform != null && !transform.isIdentity()) {
+ if (shape == null) {
+ final Point2D.Float po = new Point2D.Float(ox, oy);
+ final Point2D.Float pp = new Point2D.Float(px, py);
+ transform.inverseTransform(po, po);
+ transform.inverseTransform(pp, pp);
+ return new Line2D.Float(po, pp);
+ } else {
+ if (canReshape(shape, transform)) {
+ final Point2D.Double point = new Point2D.Double();
+ double xmin = Double.POSITIVE_INFINITY;
+ double ymin = Double.POSITIVE_INFINITY;
+ double xmax = Double.NEGATIVE_INFINITY;
+ double ymax = Double.NEGATIVE_INFINITY;
+ for (int i = 0; i < 4; i++) {
+ point.x = (i&1) == 0 ? shape.getMinX() : shape.getMaxX();
+ point.y = (i&2) == 0 ? shape.getMinY() : shape.getMaxY();
+ transform.inverseTransform(point, point);
+ if (point.x < xmin) xmin = point.x;
+ if (point.x > xmax) xmax = point.x;
+ if (point.y < ymin) ymin = point.y;
+ if (point.y > ymax) ymax = point.y;
+ }
+ if (shape instanceof Rectangle) {
+ return new Rectangle2D.Float((float) xmin,
+ (float) ymin,
+ (float) (xmax - xmin),
+ (float) (ymax - ymin));
+ } else {
+ shape = (RectangularShape) shape.clone();
+ shape.setFrame(xmin, ymin, xmax - xmin, ymax - ymin);
+ return shape;
+ }
+ }
+ else {
+ return transform.createInverse().createTransformedShape(shape);
+ }
+ }
+ }
+ else {
+ return (shape != null) ? (Shape) shape.clone() : new Line2D.Float(ox, oy, px, py);
+ }
+ }
+
+ /**
+ * Indicates whether we can transform {@code shape} simply by calling its
+ * {@code shape.setFrame(...)} method rather than by using the heavy artillery
+ * that is the {@code transform.createTransformedShape(shape)} method.
+ */
+ private static boolean canReshape(final RectangularShape shape,
+ final AffineTransform transform) {
+ final int type=transform.getType();
+ if ((type & AffineTransform.TYPE_GENERAL_TRANSFORM) != 0) return false;
+ if ((type & AffineTransform.TYPE_MASK_ROTATION) != 0) return false;
+ if ((type & AffineTransform.TYPE_FLIP) != 0) {
+ if (shape instanceof Rectangle2D) return true;
+ if (shape instanceof Ellipse2D) return true;
+ if (shape instanceof RoundRectangle2D) return true;
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns a {@link Graphics2D} object to be used for drawing in the specified component. We
+ * must not forget to call {@link Graphics2D#dispose} when the graphics object is no longer
+ * needed.
+ */
+ private Graphics2D getGraphics(final Component c) {
+ final Graphics2D graphics = (Graphics2D) c.getGraphics();
+ graphics.setXORMode(lineXORColor);
+ graphics.setColor (backXORColor);
+ return graphics;
+ }
+
+ /**
+ * Informs this controller that the mouse button has been pressed.
+ * The default implementation retains the mouse coordinate (which will
+ * become one of the corners of the future rectangle to be drawn)
+ * and prepares {@code this} to observe the mouse movements.
+ *
+ * @throws ClassCastException if {@link #getModel} doesn't return a shape
+ * from the class {link RectangularShape} or {link Line2D}.
+ */
+ public void mousePressed(final MouseEvent event) throws ClassCastException {
+ if (!event.isConsumed() && (event.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
+ final Component source = event.getComponent();
+ if (source != null) {
+ Shape model = getModel(event);
+ if (model != null) {
+ isDragging = true;
+ ox = px = event.getX();
+ oy = py = event.getY();
+ if (model instanceof Line2D) {
+ model = null;
+ }
+ mouseSelectedArea = (RectangularShape) model;
+ if (mouseSelectedArea != null) {
+ mouseSelectedArea.setFrame(ox, oy, 0, 0);
+ }
+ source.addMouseMotionListener(this);
+ }
+ source.requestFocus();
+ event.consume();
+ }
+ }
+ }
+
+ /**
+ * Informs this controller that the mouse has been dragged. The default
+ * implementation uses this to move a corner of the rectangle used to
+ * select the region. The other corner remains fixed at the point
+ * where the mouse was at the moment its button was pressed..
+ */
+ public void mouseDragged(final MouseEvent event) {
+ if (isDragging) {
+ final Graphics2D graphics = getGraphics(event.getComponent());
+ if (mouseSelectedArea == null) {
+ graphics.drawLine(ox, oy, px, py);
+ px = event.getX();
+ py = event.getY();
+ graphics.drawLine(ox, oy, px, py);
+ } else {
+ graphics.draw(mouseSelectedArea);
+ int xmin = this.ox;
+ int ymin = this.oy;
+ int xmax = px = event.getX();
+ int ymax = py = event.getY();
+ if (xmin > xmax) {
+ final int xtmp = xmin;
+ xmin = xmax; xmax = xtmp;
+ }
+ if (ymin > ymax) {
+ final int ytmp = ymin;
+ ymin = ymax; ymax = ytmp;
+ }
+ mouseSelectedArea.setFrame(xmin, ymin, xmax - xmin, ymax - ymin);
+ graphics.draw(mouseSelectedArea);
+ }
+ graphics.dispose();
+ event.consume();
+ }
+ }
+
+ /**
+ * Informs this controller that the mouse button has been released.
+ * The default implementation calls {@link #selectionPerformed} with
+ * the bounds of the selected region as parameters.
+ */
+ public void mouseReleased(final MouseEvent event) {
+ if (isDragging && (event.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
+ isDragging = false;
+ final Component component = event.getComponent();
+ component.removeMouseMotionListener(this);
+
+ final Graphics2D graphics = getGraphics(event.getComponent());
+ if (mouseSelectedArea == null) {
+ graphics.drawLine(ox, oy, px, py);
+ } else {
+ graphics.draw(mouseSelectedArea);
+ }
+ graphics.dispose();
+ px = event.getX();
+ py = event.getY();
+ selectionPerformed(ox, oy, px, py);
+ event.consume();
+ }
+ }
+
+ /**
+ * Informs this controller that the mouse has been moved but not as a
+ * result of the user selecting a region. The default implementation
+ * signals to the source component that {@code this} is no longer
+ * interested in being informed about mouse movements.
+ */
+ public void mouseMoved(final MouseEvent event) {
+ // Normally not necessary, but it seems that this "listener"
+ // sometimes stays in place when it shouldn't.
+ event.getComponent().removeMouseMotionListener(this);
+ }
+}
Modified: branches/1.0-gt2-2.6/src/skrueger/geotools/RenderingExecutor.java
===================================================================
--- branches/1.0-gt2-2.6/src/skrueger/geotools/RenderingExecutor.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/skrueger/geotools/RenderingExecutor.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -1,7 +1,5 @@
package skrueger.geotools;
-import gtmig.org.geotools.swing.XMapPane;
-
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
Copied: branches/1.0-gt2-2.6/src/skrueger/geotools/SelectXMapPaneMouseListener.java (from rev 514, branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/SelectXMapPaneMouseListener.java)
===================================================================
--- branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/SelectXMapPaneMouseListener.java 2009-11-09 15:05:41 UTC (rev 514)
+++ branches/1.0-gt2-2.6/src/skrueger/geotools/SelectXMapPaneMouseListener.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -0,0 +1,265 @@
+package skrueger.geotools;
+
+import java.awt.Point;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.geom.Point2D;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+import org.geotools.data.memory.MemoryFeatureCollection;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.map.MapLayer;
+import org.opengis.feature.simple.SimpleFeature;
+import org.opengis.feature.simple.SimpleFeatureType;
+
+import schmitzm.geotools.gui.SelectableXMapPane;
+import schmitzm.geotools.map.event.FeatureSelectedEvent;
+import skrueger.geotools.GeomFilterGenerator.BoundingBoxFilterGenerator;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryFactory;
+
+public class SelectXMapPaneMouseListener extends MouseAdapter {
+
+ private final SelectableXMapPane xMapPane;
+ private boolean enabled;
+
+ public boolean isEnabled() {
+ if (!xMapPane.isWellDefined())
+ return false;
+ if ((xMapPane.getState() == XMapPane.ZOOM_IN
+ || xMapPane.getState() == xMapPane.ZOOM_OUT || xMapPane
+ .getState() == xMapPane.PAN))
+ return false;
+ if (xMapPane.getState() == XMapPane.NONE)
+ ;
+ return enabled;
+ }
+
+ /**
+ * Reagiert auf Linksklick mit der ueber {@link #setState(int)}eingestellten
+ * Aktion und auf Rechtsklick mit Zoom-Out (sofern {@link #ZOOM_IN}-State
+ * fuer Linksklick eingestellt). Alle anderen Klicks werden ignoriert.
+ *
+ * @author <a href="mailto:martin.schmitz at koeln.de">Martin Schmitz</a>
+ * (University of Bonn/Germany)
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+ * Krüger</a>
+ */
+ public void mouseClicked(MouseEvent e) {
+ if (!isEnabled())
+ return;
+
+ xMapPane.setCursor(XMapPane.WAIT_CURSOR);
+ //
+ // int clickX = e.getX();
+ // int clickY = e.getY();
+ // selectionPerformed(clickX, clickY, clickX, clickY);}
+ //
+ int state = xMapPane.getState();
+
+ /**
+ * BEGIN StefanChange Dieser Block findet sichtbare Features im Umkreis
+ * des Mausklicks und kümmert sich selbst um das verschicken der Events.
+ * Dabei wird z.Zt. immer eine FeaterCollection mit nur einem
+ * SimpleFeature verschickt. Und zwar jenes SimpleFeature, welches am
+ * nächsten liegt.
+ */
+
+ // Fenster-Koordinate in Geo-Koordinate umwandelt
+ Point2D geoCoord = xMapPane.getScreenToWorld().transform(e.getPoint(),
+ null);
+
+ com.vividsolutions.jts.geom.Point mousePoint = new GeometryFactory()
+ .createPoint(new Coordinate(geoCoord.getX(), geoCoord.getY()));
+
+ // Rechnet 10 Pixel in die Einheit der Karte um.
+ final Point pAtDistance = new Point((int) e.getPoint().getX() + 10,
+ (int) e.getPoint().getY());
+ final Point2D geoCoordAtDistance = xMapPane.getScreenToWorld()
+ .transform(pAtDistance, null);
+ final Double dist = Math.abs(geoCoordAtDistance.getX()
+ - geoCoord.getX()) / 2;
+
+// final Envelope envelope = new Envelope(geoCoord.getX(),
+// geoCoord.getY(), geoCoord.getX(), geoCoord.getY());
+
+ //
+ // This is a tweak. The NearPointFilterGenerator doesn't work
+ // any more in GT2.6... So we use a small BBOX.
+ // Well, it's not the
+ // NearPointFilterGenerator, but GeoTools that fails. I already
+ // invested 3h in this... I guess the bug is in
+ // ExtractBoundsFilterVisitor.visit(ProperyName) retuning null.
+ // It should return the BBOX of value the named property.
+
+ // Hashtable<MapLayer, FeatureCollection<SimpleFeatureType,
+ // SimpleFeature>> result = findVisibleFeatures(
+ // new NearPointFilterGenerator(geoCoord, dist,
+ // getContext().getCoordinateReferenceSystem()),
+ // state, envelope);
+
+// envelope.expandBy(dist);
+//
+ Envelope envelope = new Envelope(geoCoord.getX() - dist, geoCoord
+ .getY()
+ - dist, geoCoord.getX() + dist, geoCoord.getY() + dist);
+
+ Hashtable<MapLayer, FeatureCollection<SimpleFeatureType, SimpleFeature>> result = xMapPane
+ .findVisibleFeatures(new BoundingBoxFilterGenerator( envelope,
+ xMapPane.getContext().getCoordinateReferenceSystem()),
+ state, envelope);
+
+ // Ein Event auslösen für das jeweils nächste Feature pro Layer
+ for (Enumeration<MapLayer> element = result.keys(); element
+ .hasMoreElements();) {
+
+ MapLayer layer = element.nextElement();
+ FeatureCollection<SimpleFeatureType, SimpleFeature> fc = result
+ .get(layer);
+ FeatureCollection<SimpleFeatureType, SimpleFeature> fcOne;
+
+ if (fc != null && !fc.isEmpty()) {
+
+ if (fc.size() > 1) {
+ // Hier werden alle Features weggeschmissen, bis auf
+ // das raeumlich naechste.
+
+ SimpleFeature nearestFeature = null;
+ Double nearestDist = 0.0;
+
+ Iterator<SimpleFeature> fcIt = fc.iterator();
+ try {
+
+ while (fcIt.hasNext()) {
+ SimpleFeature f = fcIt.next();
+ Object obj = f.getAttribute(0);
+
+ if (obj instanceof Geometry) {
+ // Bei Punkten ja noch ganz einfach:
+ Geometry featureGeometry = (Geometry) obj;
+ double distance = featureGeometry
+ .distance(mousePoint);
+
+ if ((nearestFeature == null)
+ || (distance < nearestDist)) {
+ nearestFeature = f;
+ nearestDist = distance;
+ }
+ } else {
+ LOGGER
+ .info("!obj instanceof Geometry obj = "
+ + obj.getClass()
+ .getSimpleName());
+ }
+
+ }
+
+ } finally {
+ fc.close(fcIt);
+ }
+
+ fcOne = new MemoryFeatureCollection(fc.getSchema());
+ fc.clear();
+ fcOne.add(nearestFeature);
+ } else {
+ fcOne = fc;
+ }
+ xMapPane.fireMapPaneEvent(new FeatureSelectedEvent(xMapPane,
+ layer, envelope, fcOne));
+ }
+ }
+
+ xMapPane.updateCursor();
+
+ }
+
+ /**
+ * Verarbeitet die Mausrad-Aktion, indem gezoomed wird.
+ *
+ * @param wheelEvt
+ * Mausrad-Event
+ */
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent wheelEvt) {
+ if (!isEnabled())
+ return;
+
+ final int units = wheelEvt.getUnitsToScroll();
+ if (units > 0)
+ xMapPane.zoomTo(wheelEvt.getPoint(), 1. + .11 * units);
+ else
+ xMapPane.zoomTo(wheelEvt.getPoint(), 2. / -units);
+ }
+
+ @Override
+ public void mouseDragged(final MouseEvent event) {
+ if (!isEnabled())
+ return;
+
+ xMapPane.mouseDragged(startPos, lastPos, event);
+
+ lastPos = event.getPoint();
+ }
+
+ /**
+ * Stores beginning of a drag event in window coordinates
+ */
+ protected Point startPos;
+
+ /**
+ * Stores last position of a drag event in window coordinates
+ */
+ protected Point lastPos;
+
+ public void mouseReleased(final MouseEvent event) {
+ if (!isEnabled())
+ return;
+
+ if (xMapPane.getState() == XMapPane.PAN
+ || event.getButton() == MouseEvent.BUTTON3) {
+ xMapPane.performPan();
+ } else if (xMapPane.getState() == XMapPane.SELECT_ALL
+ || xMapPane.getState() == XMapPane.SELECT_TOP) {
+ xMapPane.drawRectangle(xMapPane.getGraphics(), startPos, event
+ .getPoint());
+
+ Point mp = event.getPoint();
+
+ // keine wirkliche Selektion, sondern nur ein Klick
+ if (startPos.x == mp.x || startPos.y == mp.y) {
+ mouseClicked(event);
+ return;
+ }
+
+ // SELECTION!
+ xMapPane.performSelectionEvent(startPos.x, startPos.y, mp.x, mp.y);
+ }
+
+ }
+
+ public void mousePressed(final MouseEvent e) {
+ if (!isEnabled())
+ return;
+ startPos = new Point(e.getPoint());
+ lastPos = new Point(e.getPoint());
+ }
+
+ public SelectXMapPaneMouseListener(SelectableXMapPane xMapPane) {
+ this.xMapPane = xMapPane;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ private static Logger LOGGER = Logger
+ .getLogger(SelectXMapPaneMouseListener.class);
+
+}
Copied: branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java (from rev 530, branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/XMapPane.java)
===================================================================
--- branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/XMapPane.java 2009-11-19 09:31:14 UTC (rev 530)
+++ branches/1.0-gt2-2.6/src/skrueger/geotools/XMapPane.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -0,0 +1,2266 @@
+package skrueger.geotools;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.swing.Timer;
+
+import org.apache.log4j.Logger;
+import org.geotools.feature.FeatureCollection;
+import org.geotools.geometry.jts.JTS;
+import org.geotools.geometry.jts.ReferencedEnvelope;
+import org.geotools.map.DefaultMapContext;
+import org.geotools.map.MapContext;
+import org.geotools.map.MapLayer;
+import org.geotools.map.event.MapLayerEvent;
+import org.geotools.map.event.MapLayerListEvent;
+import org.geotools.map.event.MapLayerListListener;
+import org.geotools.map.event.MapLayerListener;
+import org.geotools.referencing.CRS;
+import org.geotools.renderer.GTRenderer;
+import org.geotools.renderer.label.LabelCacheImpl;
+import org.geotools.renderer.lite.LabelCache;
+import org.geotools.renderer.lite.StreamingRenderer;
+import org.geotools.swing.JMapPane;
+import org.geotools.swing.event.MapMouseEvent;
+import org.geotools.swing.event.MapPaneEvent;
+import org.geotools.swing.event.MapPaneListener;
+import org.opengis.feature.simple.SimpleFeature;
+import org.opengis.feature.simple.SimpleFeatureType;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+
+import schmitzm.geotools.GTUtil;
+import schmitzm.geotools.JTSUtil;
+import schmitzm.geotools.gui.SelectableXMapPane;
+import schmitzm.geotools.map.event.JMapPaneListener;
+import schmitzm.geotools.map.event.MapLayerAdapter;
+import schmitzm.lang.LangUtil;
+import schmitzm.swing.JPanel;
+import schmitzm.swing.SwingUtil;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+
+/**
+ * The {@link XMapPane} class uses a Geotools {@link GTRenderer} to paint up to
+ * two {@link MapContext}s: a "local" {@link MapContext} and a "background"
+ * {@link MapContext}. The idea is, that rendering a background layer made up of
+ * e.g. OSM data, may take much longer than rendering local data.<br>
+ * Every {@link MapContext} is rendered on a {@link Thread} of it's own.
+ * Starting/ cancelling these threads is done by the {@link RenderingExecutor}.<br>
+ * <br>
+ * While the renderers are rending the map, a <br>
+ * The {@link XMapPane} is based on schmitzm {@link JPanel}, so
+ * {@link #print(Graphics)} will automatically set the background of components
+ * to pure white.
+ *
+ * The XMapPane has a {@link MouseListener} that manages zooming.<br>
+ * A logo/icon to float in the lower left corner may be set with
+ * {@link #setMapImage(BufferedImage)}<br>
+ *
+ * @see SelectableXMapPane - an extension of {@link XMapPane} that supports
+ * selecting features.
+ *
+ * @author stefan
+ *
+ */
+public class XMapPane extends JPanel {
+
+ private static final int IMAGETYPE = BufferedImage.TYPE_INT_RGB;
+ private static final int IMAGETYPE_withAlpha = BufferedImage.TYPE_INT_ARGB;
+
+ private static Logger LOGGER = Logger.getLogger(XMapPane.class);
+
+ private boolean acceptsRepaintCalls = true;
+
+ /**
+ * Main {@link MapContext} that holds all layers that are rendered into the
+ * {@link #localImage} by the {@link #localRenderer}
+ */
+ MapContext localContext;
+
+ /**
+ * {@link MapContext} holding the background layers. Use it for layers that
+ * CAN take very long for rendering, like layer from the Internet: WMS, WFS,
+ * OSM...<br>
+ * <code>null</code> by default.
+ *
+ * @see #setBgContext(MapContext)
+ * @see #getBgContext()
+ */
+ MapContext bgContext;
+
+ /**
+ * While threads are working, calls {@link XMapPane#updateFinalImage()}
+ * regularly and {@link #repaint()}. This {@link Timer} is stopped when all
+ * renderers have finished.
+ *
+ * @see #INITIAL_REPAINT_DELAYAL
+ * @see #REPEATING_REPAINT_DELAY
+ */
+ final private Timer repaintTimer;
+
+ /**
+ * The initial delay in milliseconds until the {@link #finalImage} is
+ * updated the first time.
+ */
+ public static final int INITIAL_REPAINT_DELAY = 1000;
+
+ /**
+ * While the {@link #bgExecuter} and {@link #localExecuter} are rendering,
+ * the {@link #repaintTimer} is regularly updating the {@link #finalImage}
+ * with previews.
+ */
+ public static final long REPEATING_REPAINT_DELAY = 500;
+
+ /**
+ * Default delay (milliseconds) before the map will be redrawn when resizing
+ * the pane. This is to avoid flickering while drag-resizing.
+ */
+ public static final int DEFAULT_RESIZING_PAINT_DELAY = 500;
+
+ public static final int NONE = -123;
+
+ /**
+ * Flag fuer Modus "Kartenausschnitt bewegen". Nicht fuer Window-Auswahl
+ * moeglich!
+ *
+ * @see #setState(int)
+ */
+ public static final int PAN = 1;
+
+ /**
+ * Flag fuer Modus "SimpleFeature-Auswahl auf allen (sichtbaren) Layern".
+ *
+ * @see #setState(int)
+ * @see #setState(int)
+ */
+ public static final int SELECT_ALL = 103;
+ /**
+ * Flag fuer Modus
+ * "Auswahl nur eines Features, das erste sichtbare von Oben".
+ *
+ * @see #setState(int)
+ * @see #setState(int)
+ */
+ public static final int SELECT_ONE_FROM_TOP = 104;
+ /**
+ * Flag fuer Modus
+ * "SimpleFeature-Auswahl auf dem obersten (sichtbaren) Layer".
+ *
+ * @see #setState(int)
+ * @see #setState(int)
+ */
+ public static final int SELECT_TOP = 4;
+
+ public static final Cursor WAIT_CURSOR = Cursor
+ .getPredefinedCursor(Cursor.WAIT_CURSOR);
+
+ final static Font waitFont = new Font("Arial", Font.BOLD, 30);
+
+ /**
+ * Flag fuer Modus "Heran zoomen".
+ *
+ * @see #setState(int)
+ * @see #setState(int)
+ */
+ public static final int ZOOM_IN = 2;
+
+ /**
+ * Flag fuer Modus "Heraus zoomen". Nicht fuer Window-Auswahl moeglich!
+ *
+ * @see #setState(int)
+ */
+ public static final int ZOOM_OUT = 3;
+
+ /**
+ * Konvertiert die Maus-Koordinaten (relativ zum <code>JMapPane</code>) in
+ * Karten-Koordinaten.
+ *
+ * @param e
+ * Maus-Ereignis
+ */
+ public static Point2D getMapCoordinatesFromEvent(final MouseEvent e) {
+ // aktuelle Geo-Position aus GeoMouseEvent ermitteln
+ if (e != null && e instanceof MapMouseEvent)
+ try {
+ return ((MapMouseEvent) e).getMapPosition().toPoint2D();
+ } catch (final Exception err) {
+ LOGGER
+ .error(
+ "return ((GeoMouseEvent) e).getMapCoordinate(null).toPoint2D();",
+ err);
+ }
+
+ // aktuelle Geo-Position ueber Transformation des JMapPane berechnen
+ if (e != null && e.getSource() instanceof XMapPane) {
+
+ final XMapPane xMapPane = (XMapPane) e.getSource();
+
+ if (!xMapPane.isWellDefined())
+ return null;
+
+ final AffineTransform at = xMapPane.getScreenToWorld();
+ if (at != null)
+ return at.transform(e.getPoint(), null);
+ return null;
+ }
+ throw new IllegalArgumentException(
+ "MouseEvent has to be of instance MapMouseEvent or come from an XMapPane");
+ }
+
+ /**
+ * Listens to changes of the "background" {@link MapContext} and triggers
+ * repaints where needed.
+ */
+ private final MapLayerListListener bgContextListener = new MapLayerListListener() {
+
+ @Override
+ public void layerAdded(final MapLayerListEvent event) {
+ event.getLayer().addMapLayerListener(bgMapLayerListener);
+
+ if (localContext.getLayers().length == 0
+ && bgContext.getLayers().length == 1) { // the first one and
+ // localContext is
+ // empty
+ if (!setMapArea(localContext.getAreaOfInterest()))
+ requestStartRendering();
+ return;
+ }
+
+ // We need to redraw, even in case that the mapArea didn't change
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+
+ }
+
+ @Override
+ public void layerChanged(final MapLayerListEvent event) {
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+ }
+
+ @Override
+ public void layerMoved(final MapLayerListEvent event) {
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+ }
+
+ @Override
+ public void layerRemoved(final MapLayerListEvent event) {
+ if (event.getLayer() != null)
+ event.getLayer().removeMapLayerListener(bgMapLayerListener);
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+ }
+ };
+
+ /**
+ * compass and icon are rendered into this image
+ */
+ // protected BufferedImage gadgetsImage;
+
+ protected RenderingExecutor bgExecuter;
+
+ /**
+ * The Renderer for the LocalLayers uses this Image. When set to null,
+ * please dispose this {@link Graphics2D}
+ */
+ private BufferedImage localImage;
+
+ private BufferedImage finalImage;
+ /**
+ * If # {@link #bgExecuter} is using {@link #bgRenderer} for the Background
+ * uses this Image. When set to null, please dispose the {@link Graphics2D}
+ */
+ private BufferedImage bgImage;
+
+ /**
+ * Optionally a transparent image to paint over the map in the lower right
+ * corner.
+ *
+ * @see #addGadgets(Graphics2D)
+ * @see #setMapImage(BufferedImage)
+ **/
+ private BufferedImage mapImage = null;
+
+ /**
+ * Listens to each layer in the local {@link MapContext} for changes and
+ * triggers repaints.
+ */
+ protected MapLayerListener bgMapLayerListener = new MapLayerAdapter() {
+
+ @Override
+ public void layerChanged(final MapLayerEvent event) {
+ // Change of SLD for example
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+ }
+
+ @Override
+ public void layerHidden(final MapLayerEvent event) {
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+ }
+
+ @Override
+ public void layerShown(final MapLayerEvent event) {
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+ }
+ };
+
+ /**
+ * A flag indicating if dispose() was already called. If true, then further
+ * use of this {@link SelectableXMapPane} is undefined.
+ */
+ private boolean disposed = false;
+
+ /**
+ * While dragging, the {@link #updateFinalImage()} method is translating the
+ * cached images while setting it together.
+ **/
+ Point imageOrigin = new Point(0, 0);
+ /**
+ * For every rendering thread started,
+ * {@link GTUtil#createGTRenderer(MapContext)} is called to create a new
+ * renderer. These Java2D rendering hints are passed to the
+ * {@link GTRenderer}. The java2dHints are the same for all renderers (bg
+ * and local).
+ */
+ private RenderingHints java2dHints;
+
+ protected LabelCache labelCache = new LabelCacheImpl();
+
+ /**
+ * Listens to changes of the "local" {@link MapContext} and triggers
+ * repaints where needed.
+ */
+ private final MapLayerListListener localContextListener = new MapLayerListListener() {
+
+ @Override
+ public void layerAdded(final MapLayerListEvent event) {
+ event.getLayer().addMapLayerListener(localMapLayerListener);
+
+ localRenderer.setContext(getContext());
+
+ if (localContext.getLayers().length == 1) { // the first one
+ // if the Area of Interest is unset, the LayerBounds are used
+ if (!setMapArea(localContext.getAreaOfInterest()))
+ repaint();
+
+ return;
+ }
+
+ // We need to redraw, even in case that the mapArea didn't change
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+
+ }
+
+ @Override
+ public void layerChanged(final MapLayerListEvent event) {
+ // mapImageInvalid = true;
+ // repaint();
+ localRenderer.setContext(getContext());
+ requestStartRendering();
+ }
+
+ @Override
+ public void layerMoved(final MapLayerListEvent event) {
+ // mapImageInvalid = true;
+ // repaint();
+ localRenderer.setContext(getContext());
+ requestStartRendering();
+ }
+
+ @Override
+ public void layerRemoved(final MapLayerListEvent event) {
+ if (event.getLayer() != null)
+ event.getLayer().removeMapLayerListener(localMapLayerListener);
+ // mapImageInvalid = true;
+ // repaint();
+ localRenderer.setContext(getContext());
+ requestStartRendering();
+ }
+ };
+
+ private final RenderingExecutor localExecuter = new RenderingExecutor(this);
+
+ /**
+ * Listens to each layer in the local {@link MapContext} for changes and
+ * triggers repaints.
+ */
+ protected MapLayerListener localMapLayerListener = new MapLayerAdapter() {
+
+ @Override
+ public void layerChanged(final MapLayerEvent event) {
+ localRenderer.setContext(getContext()); // betters for SLD changes?!
+ // Change of SLD for example
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+ }
+
+ @Override
+ public void layerHidden(final MapLayerEvent event) {
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+ }
+
+ @Override
+ public void layerShown(final MapLayerEvent event) {
+ // mapImageInvalid = true;
+ // repaint();
+ requestStartRendering();
+ }
+ };
+
+ private final GTRenderer localRenderer = GTUtil.createGTRenderer();
+
+ private final GTRenderer bgRenderer = GTUtil.createGTRenderer();
+
+ /**
+ * the area of the map to draw
+ */
+ protected Envelope mapArea = null;
+
+ /**
+ * A flag set it {@link #setMapArea(Envelope)} to indicated the
+ * {@link #paintComponent(Graphics)} method, that the image on-screen is
+ * associated with {@link #oldMapArea} and may hence be scaled for a quick
+ * preview.<br>
+ * The flag is reset
+ **/
+ private boolean mapAreaChanged = false;
+
+ /**
+ * This color is used as the default background color when painting a map.
+ */
+ private Color mapBackgroundColor = Color.WHITE;
+
+ /**
+ * A flag indicating that the shown image is invalid and needs to be
+ * re-rendered.
+ */
+ protected boolean mapImageInvalid = true;
+
+ /**
+ * Holds a flag for each layer, whether it is regarded or ignored on
+ * {@link #SELECT_TOP}, {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP}
+ * actions.
+ */
+ final protected HashMap<MapLayer, Boolean> mapLayerSelectable = new HashMap<MapLayer, Boolean>();
+
+ /**
+ * List of listeners of this {@link XMapPane}
+ */
+ protected Vector<JMapPaneListener> mapPaneListeners = new Vector<JMapPaneListener>();
+ /**
+ * If not <code>null</code>, the {@link XMapPane} will not allow to zoom/pan
+ * out of that area
+ **/
+ private Envelope maxExtend = null;
+ private Double maxZoomScale = Double.MIN_VALUE;
+
+ private Double minZoomScale = Double.MAX_VALUE;
+
+ /**
+ * We store the old mapArea for a moment to use it for the
+ * "quick scaled preview" in case of ZoomOut
+ **/
+ protected Envelope oldMapArea = null;
+
+ /**
+ * We store the old transform for a moment to use it for the
+ * "quick scaled preview" in case of ZoomIn
+ **/
+ protected AffineTransform oldScreenToWorld = null;
+
+ /**
+ * A flag indicating, that the image size has changed and the buffered
+ * images are not big enough any more
+ **/
+ protected boolean paneResized = false;
+
+ private BufferedImage preFinalImage;
+
+ // if null, no quick preview will be shown
+ private int quickPreviewHint = 0;
+
+ private Map<Object, Object> rendererHints;
+
+ private volatile Boolean requestStartRendering = false;
+
+ private final Timer resizeTimer;
+
+ private final int resizingPaintDelay;
+
+ /**
+ * Transformation zwischen Fenster-Koordinaten und Karten-Koordinaten
+ * (lat/lon)
+ */
+ protected AffineTransform screenToWorld = null;
+
+ /**
+ * The flag {@link #requestStartRendering} can be set to true by events.
+ * This {@link Timer} checks the flag regularly and starts one renderer
+ * thread.
+ */
+ final private Timer startRenderThreadsTimer;
+
+ /**
+ * The default state is ZOOM_IN, hence by default the
+ * {@link #zoomMapPaneMouseListener} is also enabled.
+ **/
+ private int state = ZOOM_IN;
+
+ /**
+ * Manuell gesetzter statischer Cursor, unabhaengig von der aktuellen
+ * MapPane-Funktion
+ */
+ protected Cursor staticCursor = null;
+
+ private AffineTransform worldToScreen;
+
+ /**
+ * This {@link MouseListener} is managing all zoom related tasks
+ */
+ public final ZoomXMapPaneMouseListener zoomMapPaneMouseListener = new ZoomXMapPaneMouseListener(
+ this);
+
+ // TODO doku
+ public XMapPane() {
+ this(null, null);
+ }
+
+ /**
+ * full constructor extending JPanel
+ *
+ * @param rendererHints
+ *
+ * @param layout
+ * - layout (probably shouldn't be set)
+ * @param isDoubleBuffered
+ * - a Swing thing I don't really understand
+ * @param render
+ * - what to draw the map with
+ * @param localContext
+ * - {@link MapContext} of layer to render.
+ */
+ public XMapPane(final MapContext localContext_,
+ final Map<Object, Object> rendererHints) {
+ super(true);
+
+ setRendererHints(rendererHints);
+
+ setOpaque(true);
+
+ localRenderer.setJava2DHints(getJava2dHints());
+
+ if (localContext_ != null)
+ setLocalContext(localContext_);
+
+ /**
+ * Adding the #zoomMapPaneMouseListener
+ */
+ this.addMouseListener(zoomMapPaneMouseListener);
+ this.addMouseMotionListener(zoomMapPaneMouseListener);
+ this.addMouseWheelListener(zoomMapPaneMouseListener);
+
+ /*
+ * We use a Timer object to avoid rendering delays and flickering when
+ * the user is drag-resizing the parent container of this map pane.
+ *
+ * Using a ComponentListener doesn't work because, unlike a JFrame, the
+ * pane receives a stream of events during drag-resizing.
+ */
+ resizingPaintDelay = DEFAULT_RESIZING_PAINT_DELAY;
+ resizeTimer = new Timer(resizingPaintDelay, new ActionListener() {
+
+ public void actionPerformed(final ActionEvent e) {
+ paneResized = true;
+
+ if (!isWellDefined())
+ return;
+
+ final Rectangle bounds = getVisibleRect();
+
+ final Envelope geoMapArea = tranformWindowToGeo(bounds.x,
+ bounds.y, bounds.x + bounds.width, bounds.y
+ + bounds.height);
+
+ setMapArea(bestAllowedMapArea(geoMapArea));
+ }
+ });
+ resizeTimer.setRepeats(false);
+ this.addComponentListener(new ComponentAdapter() {
+
+ @Override
+ public void componentResized(final ComponentEvent e) {
+ if (bgExecuter != null)
+ bgExecuter.cancelTask();
+ if (localExecuter != null)
+ localExecuter.cancelTask();
+ resizeTimer.restart();
+ }
+
+ });
+
+ /*
+ * Setting up the repaintTimer. Not started automatically.
+ */
+ repaintTimer = new Timer((int) REPEATING_REPAINT_DELAY,
+ new ActionListener() {
+
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ updateFinalImage();
+ XMapPane.this.repaint();
+ }
+ });
+ repaintTimer.setInitialDelay(INITIAL_REPAINT_DELAY);
+ repaintTimer.setRepeats(true);
+
+ /*
+ * Setting up the startRenderThreadsTimer. This Timer starts
+ * automatically.
+ */
+ startRenderThreadsTimer = new Timer(100, new ActionListener() {
+
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ synchronized (requestStartRendering) {
+ if (requestStartRendering && isWellDefined()) {
+
+ if (localExecuter.isRunning()) {
+ localExecuter.cancelTask();
+ } else {
+ requestStartRendering = false;
+ startRendering();
+ }
+ }
+ }
+ }
+ });
+ startRenderThreadsTimer.start();
+
+ }
+
+ /**
+ * Fuegt der Map einen Listener hinzu.
+ *
+ * @param l
+ * neuer Listener
+ */
+ public void addMapPaneListener(final JMapPaneListener l) {
+ mapPaneListeners.add(l);
+ }
+
+ /**
+ * Korrigiert den {@link Envelope} aka {@code mapArea} auf die beste
+ * erlaubte Flaeche damit die Massstabsbeschaenkungen noch eingehalten
+ * werden, FALLS der uebergeben Envelope nicht schon gueltig sein sollte.<br>
+ * Since 21. April 09: Before thecalculation starts, the aspect ratio is
+ * corrected. This change implies, that setMapArea() will most of the time
+ * not allow setting to a wrong aspectRatio.
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+ * Krüger</a>
+ */
+ public Envelope bestAllowedMapArea(Envelope env) {
+ if (getWidth() == 0)
+ return env;
+ if (env == null)
+ return null;
+
+ Envelope newArea = null;
+
+ /**
+ * Correct the aspect Ratio before we check the rest. Otherwise we might
+ * easily fail. We allow to grow here, because we don't check against
+ * the maxExtend
+ */
+ final Rectangle curPaintArea = getVisibleRect();
+
+ env = JTSUtil.fixAspectRatio(curPaintArea, env, true);
+
+ final double scale = env.getWidth() / getWidth();
+ final double centerX = env.getMinX() + env.getWidth() / 2.;
+ final double centerY = env.getMinY() + env.getHeight() / 2.;
+ double newWidth2 = 0;
+ double newHeight2 = 0;
+ if (scale < getMaxZoomScale()) {
+ // ****************************************************************************
+ // Wir zoomen weiter rein als erlaubt => Anpassen des envelope
+ // ****************************************************************************
+ newWidth2 = getMaxZoomScale() * getWidth() / 2.;
+ newHeight2 = getMaxZoomScale() * getHeight() / 2.;
+ } else if (scale > getMinZoomScale()) {
+ // ****************************************************************************
+ // Wir zoomen weiter raus als erlaubt => Anpassen des envelope
+ // ****************************************************************************
+ newWidth2 = getMinZoomScale() * getWidth() / 2.;
+ newHeight2 = getMinZoomScale() * getHeight() / 2.;
+ } else {
+ // ****************************************************************************
+ // Die mapArea / der Envelope ist ist gueltig! Keine Aenderungen
+ // ****************************************************************************
+ newArea = env;
+ }
+
+ if (newArea == null) {
+
+ final Coordinate ll = new Coordinate(centerX - newWidth2, centerY
+ - newHeight2);
+ final Coordinate ur = new Coordinate(centerX + newWidth2, centerY
+ + newHeight2);
+
+ newArea = new Envelope(ll, ur);
+ }
+
+ final Envelope maxAllowedExtend = getMaxExtend();
+ while (maxAllowedExtend != null && !maxAllowedExtend.contains(newArea)
+ && newArea != null && !newArea.isNull()
+ && !Double.isNaN(newArea.getMinX())
+ && !Double.isNaN(newArea.getMaxX())
+ && !Double.isNaN(newArea.getMinY())
+ && !Double.isNaN(newArea.getMaxY())) {
+ /*
+ * If a maxExtend is set, we have to honour that...
+ */
+
+ // Exceeds top? Move down and maybe cut
+ if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
+ final double divY = newArea.getMaxY()
+ - maxAllowedExtend.getMaxY();
+ // LOGGER.debug("Moving area down by " + divY);
+
+ newArea = new Envelope(new Coordinate(newArea.getMinX(),
+ newArea.getMinY() - divY), new Coordinate(newArea
+ .getMaxX(), newArea.getMaxY() - divY));
+
+ if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
+ // LOGGER.debug("Now it exeeds the bottom border.. cut!");
+ // And cut the bottom if it moved out of the area
+ newArea = new Envelope(new Coordinate(newArea.getMinX(),
+ maxAllowedExtend.getMinY()), new Coordinate(newArea
+ .getMaxX(), newArea.getMaxY()));
+
+ // LOGGER.debug("and fix aspect ratio");
+
+ newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
+ false);
+ }
+ }
+
+ // Exceeds bottom? Move up and maybe cut
+ if (newArea.getMinY() < maxAllowedExtend.getMinY()) {
+ final double divY = newArea.getMinY()
+ - maxAllowedExtend.getMinY();
+ // LOGGER.debug("Moving area up by " + divY);
+
+ newArea = new Envelope(new Coordinate(newArea.getMinX(),
+ newArea.getMinY() - divY), new Coordinate(newArea
+ .getMaxX(), newArea.getMaxY() - divY));
+
+ if (newArea.getMaxY() > maxAllowedExtend.getMaxY()) {
+ // LOGGER.debug("Now it exeeds the top border.. cut!");
+ // And cut the bottom if it moved out of the area
+ newArea = new Envelope(new Coordinate(newArea.getMinX(),
+ newArea.getMinY()), new Coordinate(newArea
+ .getMaxX(), maxAllowedExtend.getMaxY()));
+
+ // LOGGER.debug("and fix aspect ratio");
+
+ newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
+ false);
+ }
+ }
+
+ // Exceeds to the right? move and maybe cut
+ if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
+
+ // Move left..
+ final double divX = newArea.getMaxX()
+ - maxAllowedExtend.getMaxX();
+ // LOGGER.debug("Moving area left by " + divX);
+
+ newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,
+ newArea.getMinY()), new Coordinate(newArea.getMaxX()
+ - divX, newArea.getMaxY()));
+
+ if (newArea.getMinX() < maxAllowedExtend.getMinX()) {
+ // LOGGER.debug("Now it exeeds the left border.. cut!");
+ // And cut the left if it moved out of the area
+ newArea = new Envelope(new Coordinate(maxAllowedExtend
+ .getMinX(), newArea.getMinY()), new Coordinate(
+ newArea.getMaxX(), newArea.getMaxY()));
+
+ // LOGGER.debug("and fix aspect ratio");
+
+ newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
+ false);
+ }
+ }
+
+ // Exceeds to the left? move and maybe cut
+ if (newArea.getMinX() < maxAllowedExtend.getMinX()) {
+
+ // Move right..
+ final double divX = newArea.getMinX()
+ - maxAllowedExtend.getMinX();
+ // LOGGER.debug("Moving area right by " + divX);
+
+ newArea = new Envelope(new Coordinate(newArea.getMinX() - divX,
+ newArea.getMinY()), new Coordinate(newArea.getMaxX()
+ - divX, newArea.getMaxY()));
+
+ if (newArea.getMaxX() > maxAllowedExtend.getMaxX()) {
+ // LOGGER.debug("Now it exeeds the right border.. cut!");
+ // And cut the left if it moved out of the area
+ newArea = new Envelope(new Coordinate(newArea.getMinX(),
+ newArea.getMinY()), new Coordinate(maxAllowedExtend
+ .getMaxX(), newArea.getMaxY()));
+
+ // LOGGER.debug("and fix aspect ratio");
+
+ newArea = JTSUtil.fixAspectRatio(this.getBounds(), newArea,
+ false);
+ }
+ }
+ }
+
+ return newArea;
+ }
+
+ /**
+ * Should be called when the {@link JMapPane} is not needed no more to help
+ * the GarbageCollector
+ *
+ * Removes all {@link JMapPaneListener}s that are registered
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+ * Krüger</a>
+ */
+ public void dispose() {
+ if (isDisposed())
+ return;
+
+ setPainting(false);
+
+ resizeTimer.stop();
+ startRenderThreadsTimer.stop();
+
+ disposed = true;
+
+ if (bgExecuter != null) {
+ bgExecuter.cancelTask();
+ bgExecuter.dispose();
+ }
+
+ if (localExecuter != null) {
+ int i = 0;
+ localExecuter.cancelTask();
+ while (i++ < 10 && localExecuter.isRunning()) {
+ try {
+ Thread.sleep(200);
+ } catch (final InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ if (localExecuter.isRunning()) {
+ System.out
+ .println("BAD BAD BAD... still running the thread....");
+ }
+ localExecuter.dispose();
+ }
+ //
+ // if (bgImage != null) {
+ // bgImage = dispose(bgImage);
+ // bgImage = null;
+ // // LangUtil.gcTotal();
+ // }
+ //
+ // if (localImage != null) {
+ // localImage = dispose(localImage);
+ // localImage = null;
+ // // LangUtil.gcTotal();
+ // }
+ //
+ // if (finalImage != null) {
+ // finalImage = dispose(finalImage);
+ // finalImage = null;
+ // // LangUtil.gcTotal();
+ // }
+ //
+ // if (preFinalImage != null) {
+ // preFinalImage = dispose(preFinalImage);
+ // preFinalImage = null;
+ // }
+ disposeImages();
+
+ // LangUtil.gcTotal();
+
+ // Alle mapPaneListener entfernen
+ mapPaneListeners.clear();
+
+ removeMouseMotionListener(zoomMapPaneMouseListener);
+ removeMouseListener(zoomMapPaneMouseListener);
+
+ if (localContext != null)
+ getContext().clearLayerList();
+ if (bgContext != null)
+ getBgContext().clearLayerList();
+
+ removeAll();
+ }
+
+ /**
+ * Draws a rectangle in XOR mode from the origin at {@link #startPos} to the
+ * given point. All in screen coordinates.
+ */
+ protected void drawRectangle(final Graphics graphics, final Point startPos,
+ final Point e) {
+
+ if (!isWellDefined())
+ return;
+
+ // undraw last box/draw new box
+ final int left = Math.min(startPos.x, e.x);
+ final int right = Math.max(startPos.x, e.x);
+ final int top = Math.max(startPos.y, e.y);
+ final int bottom = Math.min(startPos.y, e.y);
+ final int width = right - left;
+ final int height = top - bottom;
+
+ if (width == 0 && height == 0)
+ return;
+
+ graphics.setXORMode(Color.WHITE);
+ graphics.drawRect(left, bottom, width, height);
+ }
+
+ /**
+ * Diretly paints scaled preview into the {@link SelectableXMapPane}. Used
+ * to give the user something to look at while we are rendering. Method
+ * should be called after {@link #setMapArea(Envelope)} has been set to the
+ * new mapArea and transform has been reset.<br>
+ *
+ * @param g
+ * Graphics2D to paint the preview into
+ *
+ * @param state
+ * Max be {@link #ZOOM_IN} or {@link #ZOOM_OUT}
+ */
+ protected boolean drawScaledPreviewImage_Zoom(final Graphics2D graphics) {
+
+ if (quickPreviewHint == 0)
+ return false;
+
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+ graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_SPEED);
+
+ if (oldMapArea == null)
+ return false;
+
+ final Rectangle visibleArea = getVisibleRect();
+
+ // Calculate the oldMapArea in the current WindowCoordinates:
+ final Envelope oldMapWindow = tranformGeoToWindow(oldMapArea.getMinX(),
+ oldMapArea.getMinY(), oldMapArea.getMaxX(), oldMapArea
+ .getMaxY(), null);
+
+ final int xx1 = (int) Math.round(oldMapWindow.getMinX());
+ final int yy1 = (int) Math.round(oldMapWindow.getMinY());
+ final int xx2 = (int) Math.round(oldMapWindow.getMaxX());
+ final int yy2 = (int) Math.round(oldMapWindow.getMaxY());
+
+ graphics.drawImage(getPreFinalImage(), xx1, yy1, xx2, yy2,
+ (int) visibleArea.getMinX(), (int) visibleArea.getMinY(),
+ (int) visibleArea.getMaxX(), (int) visibleArea.getMaxY(),
+ getMapBackgroundColor(), null);
+
+ final Rectangle painedArea = new Rectangle(xx1, yy1, xx2 - xx1, yy2
+ - yy1);
+
+ SwingUtil.clearAround(graphics, painedArea, visibleArea);
+
+ addGadgets(graphics);
+
+ quickPreviewHint = 0;
+
+ graphics.dispose();
+
+ // Something has been drawn
+ return true;
+ }
+
+ public MapContext getBgContext() {
+ return bgContext;
+ }
+
+ /**
+ * Lazyly initializes a {@link BufferedImage} for the background renderer.
+ */
+ private Image getBgImage() {
+ //
+ // if (bgImage == null) {
+ // bgImage = createImage(getBounds().width, getBounds().height);
+ // }
+
+ return bgImage;
+ }
+
+ public MapContext getContext() {
+ if (localContext == null) {
+ setLocalContext(new DefaultMapContext());
+ }
+ return localContext;
+ }
+
+ private BufferedImage getFinalImage() {
+ //
+ if (finalImage == null) {
+ // Rectangle curPaintArea = getVisibleRect();
+ finalImage = new BufferedImage(getBounds().width,
+ getBounds().height, IMAGETYPE);
+
+ requestStartRendering();
+ }
+ return finalImage;
+ }
+
+ public RenderingHints getJava2dHints() {
+ return java2dHints;
+ }
+
+ /**
+ * Lazyly initializes a {@link BufferedImage} for the background renderer.
+ */
+ private BufferedImage getLocalImage() {
+
+ if (localImage == null) {
+ localImage = new BufferedImage(getBounds().width,
+ getBounds().height, IMAGETYPE_withAlpha);
+ }
+
+ return localImage;
+ }
+
+ /**
+ * Returns a copy of the mapArea
+ *
+ * @return
+ */
+ public Envelope getMapArea() {
+ if (mapArea == null) {
+ ReferencedEnvelope mapArea_ = null;
+ try {
+ mapArea_ = localContext.getLayerBounds();
+ } catch (final IOException e) {
+ LOGGER.warn("context.getLayerBounds()", e);
+ }
+
+ if (mapArea_ != null) {
+ mapImageInvalid = true; /* note we need to redraw */
+// setMapArea(mapArea_); // results in a loop
+ mapArea = bestAllowedMapArea(mapArea_);
+ }
+ }
+
+ if (mapArea == null)
+ return null;
+
+ return new Envelope(mapArea);
+ }
+
+ /**
+ * Returns the background {@link Color} of the map pane. Default is white.
+ **/
+ public Color getMapBackgroundColor() {
+ return mapBackgroundColor;
+ }
+
+ /**
+ * Get the BufferedImage to use as a flaoting icon in the lower right
+ * corner.
+ *
+ * @return <code>null</code> if the feature is deactivated.
+ */
+ public BufferedImage getMapImage() {
+ return mapImage;
+ }
+
+ /**
+ * Returns the evelope of the viewable area. The JMapPane will never show
+ * anything outside of this extend. If this has been set to
+ * <code>null</code> via {@link #setMaxExtend(Envelope)}, it tries to return
+ * quickly the context's bounds. It it takes to long to determine the
+ * context bounds, <code>null</code> is returned.
+ *
+ * @param maxExtend
+ * <code>null</code> to not have this restriction.
+ */
+
+ public Envelope getMaxExtend() {
+ if (maxExtend == null) {
+ final ReferencedEnvelope layerBounds = GTUtil
+ .getVisibleLayoutBounds(localContext);
+ if (layerBounds == null) {
+ // TODO Last fallback could be the CRS valid area
+ return null;
+ }
+
+ // Kartenbereich um 10% vergroessern
+ return JTSUtil.fixAspectRatio(this.getBounds(), JTSUtil
+ .expandEnvelope(layerBounds, 0.1), true);
+ }
+ return maxExtend;
+ }
+
+ /**
+ * Retuns the maximum allowed zoom scale. This is the smaller number value
+ * of the two. Defaults to {@link Double}.MIN_VALUE
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+ * Krüger</a>
+ */
+ public Double getMaxZoomScale() {
+ return maxZoomScale;
+ }
+
+ /**
+ * Retuns the minimum allowed zoom scale. This is the bigger number value of
+ * the two. Defaults to {@link Double}.MAX_VALUE
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+ * Krüger</a>
+ */
+ public Double getMinZoomScale() {
+ return minZoomScale;
+ }
+
+ private Image getPreFinalImage() {
+ // if (preFinalImage == null) {
+ //
+ // // Rectangle curPaintArea = getVisibleRect();
+ // // preFinalImage = new BufferedImage(curPaintArea.width,
+ // // curPaintArea.height, BufferedImage.TYPE_INT_RGB);
+ //
+ // preFinalImage = createImage(getBounds().width, getBounds().height);
+ //
+ // requestStartRendering();
+ // }
+ return preFinalImage;
+ }
+
+ public Map<Object, Object> getRendererHints() {
+ return rendererHints;
+ }
+
+ /**
+ * Liefert eine affine Transformation, um von den Fenster-Koordinaten in die
+ * Karten-Koordinaten (Lat/Lon) umzurechnen.
+ *
+ * @return eine Kopie der aktuellen Transformation; <code>null</code> wenn
+ * noch keine Karte angezeigt wird
+ */
+ public AffineTransform getScreenToWorld() {
+ if (screenToWorld == null)
+ resetTransforms();
+ // nur Kopie der Transformation zurueckgeben!
+ if (screenToWorld == null)
+ return null;
+ return new AffineTransform(screenToWorld);
+ }
+
+ public int getState() {
+ return state;
+ }
+
+ /**
+ * Liefert den statisch eingestellten Cursor, der unabhaengig von der
+ * eingestellten MapPane-Aktion (Zoom, Auswahl, ...) verwendet wird.
+ *
+ * @return {@code null}, wenn kein statischer Cursor verwendet, sondern der
+ * Cursor automatisch je nach MapPane-Aktion eingestellt wird.
+ */
+ public Cursor getStaticCursor() {
+ return this.staticCursor;
+ }
+
+ public AffineTransform getWorldToScreenTransform() {
+ if (worldToScreen == null) {
+ resetTransforms();
+ }
+ // nur Kopie der Transformation zurueckgeben!
+ return new AffineTransform(worldToScreen);
+ }
+
+ /**
+ * A flag indicating if dispose() has already been called. If true, then
+ * further use of this {@link SelectableXMapPane} is undefined.
+ */
+ private boolean isDisposed() {
+ return disposed;
+ }
+
+ /**
+ * Returns whether a layer is regarded or ignored on {@link #SELECT_TOP},
+ * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions. Returns
+ * <code>true</code> if the selectability has not been defined.
+ *
+ * @param layer
+ * a layer
+ */
+ public boolean isMapLayerSelectable(final MapLayer layer) {
+ final Boolean selectable = mapLayerSelectable.get(layer);
+ return selectable == null ? true : selectable;
+ }
+
+ /**
+ * Return <code>true</code> if a CRS and a {@link #mapArea} are set and the
+ * {@link XMapPane} is visible and has bounds set.
+ */
+ public boolean isWellDefined() {
+
+ try {
+
+ if (getContext() == null)
+ return false;
+ if (getContext().getLayerCount() <= 0)
+ return false;
+ if (getMapArea() == null)
+ return false;
+ if (getBounds().getWidth() == 0)
+ return false;
+ if (getBounds().getHeight() == 0)
+ return false;
+ } catch (final Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ public void mouseDragged(final Point startPos, final Point lastPos,
+ final MouseEvent event) {
+
+ if ((getState() == XMapPane.PAN)
+ || ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)) {
+
+ if (getCursor() != SwingUtil.PANNING_CURSOR) {
+ setCursor(SwingUtil.PANNING_CURSOR);
+
+ // While panning, we deactivate the rendering. So the tasts are
+ // ready to start when the panning os done.
+ if (bgExecuter != null)
+ bgExecuter.cancelTask();
+ if (localExecuter != null)
+ localExecuter.cancelTask();
+ }
+
+ if (lastPos.x > 0 && lastPos.y > 0) {
+ final int dx = event.getX() - lastPos.x;
+ final int dy = event.getY() - lastPos.y;
+
+ // TODO Stop dragging when the drag would not be valid...
+ // boolean dragValid = true;
+ // // check if this panning results in a valid mapArea
+ // {
+ // Rectangle winBounds = xMapPane.getBounds();
+ // winBounds.translate(xMapPane.imageOrigin.x,
+ // -xMapPane.imageOrigin.y);
+ // Envelope newMapAreaBefore = xMapPane.tranformWindowToGeo(
+ // winBounds.x, winBounds.y, winBounds.x
+ // + winBounds.width, winBounds.y
+ // + winBounds.height);
+ //
+ //
+ // winBounds = xMapPane.getBounds();
+ // Point testIng = new Point(xMapPane.imageOrigin);
+ // testIng.translate(dx, dy);
+ // winBounds.translate(testIng.x, -testIng.y);
+ // Envelope newMapAreaAfter = xMapPane.tranformWindowToGeo(
+ // winBounds.x, winBounds.y, winBounds.x
+ // + winBounds.width, winBounds.y
+ // + winBounds.height);
+ //
+ // // If the last drag doesn't change the MapArea anymore cancel
+ // it.
+ // if (xMapPane.bestAllowedMapArea(newMapAreaAfter).equals(
+ // xMapPane.bestAllowedMapArea(newMapAreaBefore))){
+ // dragValid = false;
+ // return;
+ // }
+ // }
+
+ imageOrigin.translate(dx, dy);
+ updateFinalImage();
+ repaint();
+ }
+
+ } else if ((getState() == XMapPane.ZOOM_IN)
+ || (getState() == XMapPane.ZOOM_OUT)
+ || (getState() == XMapPane.SELECT_ALL)
+ || (getState() == XMapPane.SELECT_TOP)
+ // || (getState() == XMapPane.SELECT_ONE_FROM_TOP)
+ ) {
+ final Graphics graphics = getGraphics();
+
+ drawRectangle(graphics, startPos, event.getPoint());
+
+ if ((lastPos.x > 0) && (lastPos.y > 0)) {
+ drawRectangle(graphics, startPos, lastPos);
+ }
+
+ graphics.dispose();
+
+ }
+
+ }
+
+ /**
+ * Called by the {@link RenderingExecutor} when rendering was cancelled.
+ */
+ public void onRenderingCancelled() {
+ repaintTimer.stop();
+ LOGGER.debug("Rendering cancelled");
+ }
+
+ /**
+ * Called by the {@link RenderingExecutor} when rendering has been
+ * completed.
+ */
+ public void onRenderingCompleted() {
+ repaintTimer.stop();
+ updateFinalImage();
+ repaint();
+ }
+
+ /**
+ * Called by the {@linkplain XMapPane.RenderingTask} when rendering failed.
+ * Publishes a {@linkplain MapPaneEvent} of type {@code
+ * MapPaneEvent.Type.RENDERING_STOPPED} to listeners.
+ *
+ * @param renderingError
+ * The error that occured during rendering
+ *
+ * @see MapPaneListener#onRenderingStopped(org.geotools.swing.event.MapPaneEvent)
+ */
+ public void onRenderingFailed(final Exception renderingError) {
+ repaintTimer.stop();
+ LOGGER.warn("Rendering failed", renderingError);
+ updateFinalImage();
+ repaint();
+
+ }
+
+ public void onRenderingPending() {
+ // LOGGER.debug("Pending rendering updates the preview...");
+ updateFinalImage();
+ repaint();
+ }
+
+ @Override
+ protected void paintComponent(final Graphics g) {
+ if (!acceptsRepaintCalls)
+ return;
+
+ // Maybe update the cursor and maybe stop the repainting timer
+ updateCursor();
+
+ super.paintComponent(g); // candidate for removal
+
+ boolean paintedSomething = false;
+
+ if (mapImageInvalid) { /* if the map changed then redraw */
+
+ mapImageInvalid = false; // Reset for next round
+
+ // If the new mapArea and the oldMapArea intersect, we can draw some
+ // quick scaled preview to make the user feel that something is
+ // happening.
+ if (mapAreaChanged && oldMapArea != null
+ && getMapArea().intersects(oldMapArea)
+ & !getMapArea().equals(oldMapArea)) {
+
+ mapAreaChanged = false;
+
+ if (getMapArea().covers(oldMapArea)) {
+ setQuickPreviewHint(ZOOM_OUT);
+ paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
+ } else if (oldMapArea.covers(getMapArea())) {
+ setQuickPreviewHint(ZOOM_IN);
+ paintedSomething = drawScaledPreviewImage_Zoom((Graphics2D) g);
+ }
+
+ }
+
+ if (paneResized) {
+ paneResized = false;
+ disposeImages();
+ }
+
+ // Start the Threads and Timers to render the image
+ requestStartRendering();
+
+ }
+
+ if (!paintedSomething) {
+
+ g.drawImage(getFinalImage(), 0, 0, null);
+
+ g.dispose(); // cand. for removal
+
+ paintedSomething = true; // cand. for removal
+ }
+
+ }
+
+ /**
+ * Heavily works on releasing all resources related to the four
+ * {@link Image}s used to cache the results of the different renderers.<br>
+ * In November 2009 i had some memory leaking problems with {@link XMapPane}
+ * . The resources of the buffered images were never released. It seem to be
+ * important to call the GC right after flushing the images.<br>
+ * Hence this method may take a while, because it calls the GC up to four
+ * times.
+ */
+ private void disposeImages() {
+
+ // System.out.println("vorher = "
+ // + new MbDecimalFormatter().format(LangUtil.gcTotal()));
+ // bi.flush();
+ // return bi = null;
+ // System.out.println("nacher = "
+ // + new MbDecimalFormatter().format(LangUtil.gcTotal()));
+ //
+ // System.out.println("\n");
+
+ if (preFinalImage != null) {
+ preFinalImage.flush();
+ preFinalImage = null;
+ LangUtil.gc();
+ }
+ if (finalImage != null) {
+ finalImage.flush();
+ finalImage = null;
+ LangUtil.gc();
+ }
+ if (localImage != null) {
+ localImage.flush();
+ localImage = null;
+ LangUtil.gc();
+ }
+ if (bgImage != null) {
+ bgImage.flush();
+ bgImage = null;
+ LangUtil.gc();
+ }
+
+ }
+
+ /**
+ * Performs a {@value #PAN} action. During panning, the displacement is
+ * stored in {@link #imageOrigin} object. Calling {@link #performPan()} will
+ * reset the offset and call {@link #setMapArea(Envelope)}.
+ */
+ public void performPan() {
+
+ final Rectangle winBounds = getBounds();
+ winBounds.translate(-imageOrigin.x, -imageOrigin.y);
+ final Envelope newMapArea = tranformWindowToGeo(winBounds.x,
+ winBounds.y, winBounds.x + winBounds.width, winBounds.y
+ + winBounds.height);
+
+ imageOrigin.x = 0;
+ imageOrigin.y = 0;
+
+ if (!setMapArea(newMapArea)) {
+ /**
+ * If setMapArea returns true, the finalImage is updated anyways.
+ * This if-case exists to ensure that we repaint a correct image
+ * even if the new panning area has been denied.
+ */
+ updateFinalImage();
+ repaint();
+ }
+
+ if (getCursor() == SwingUtil.PANNING_CURSOR)
+ setCursor(SwingUtil.PAN_CURSOR);
+ }
+
+ //
+ // /**
+ // * Nuetzlich wenn die Componente gedruckt (z.B. wenn ein Screenshot
+ // gemacht
+ // * wird) wird. Dann werden wird der Hintergrund auf WEISS gesetzt.
+ // *
+ // * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+ // * Krüger</a>
+ // */
+ // @Override
+ // public void print(final Graphics g) {
+ // final Color orig = getBackground();
+ // setBackground(Color.WHITE);
+ //
+ // // wrap in try/finally so that we always restore the state
+ // try {
+ // super.print(g);
+ // } finally {
+ // setBackground(orig);
+ // }
+ // }
+
+ /**
+ * Entfernt einen Listener von der Map.
+ *
+ * @param l
+ * zu entfernender Listener
+ */
+ public void removeMapPaneListener(final JMapPaneListener l) {
+ mapPaneListeners.remove(l);
+ }
+
+ /**
+ * Cancels all running renderers and sets the flag to start new ones. <br>
+ *
+ * @see #startRenderThreadsTimer
+ */
+ private void requestStartRendering() {
+ if (bgExecuter != null)
+ bgExecuter.cancelTask();
+ if (localExecuter != null)
+ localExecuter.cancelTask();
+ requestStartRendering = true;
+
+ }
+
+ /**
+ * Berechnet die Transformation zwischen Fenster- und Karten-Koordinaten
+ * neu.
+ */
+ protected void resetTransforms() {
+ if (getMapArea() == null || getWidth() == 0 || getHeight() == 0)
+ return;
+
+ // We store the last Transform
+ oldScreenToWorld = screenToWorld;
+
+ this.screenToWorld = new AffineTransform(
+ // Genauso wie die Fenster-Koordinaten, werden die Longitude-Koordinaten
+ // nach rechts (Osten) hin groesser
+ // --> positive Verschiebung
+ getMapArea().getWidth() / getWidth(),
+ // keine Verzerrung
+ 0.0, 0.0,
+ // Waehrend die Fenster-Koordinaten nach unten hin groesser
+ // werden,
+ // werden Latitude-Koordinaten nach Sueden hin keiner
+ // --> negative Verschiebung
+ -getMapArea().getHeight() / getHeight(),
+ // Die Longitude-Koordinaten werden nach Osten hin groesser
+ // --> obere linke Ecke des Fensters hat also den Minimalwert
+ getMapArea().getMinX(),
+ // Die Latitude-Koordinaten werden nach Norden hin groesser
+ // --> obere linke Ecke des Fensters hat also den Maximalwert
+ getMapArea().getMaxY());
+
+ try {
+ this.worldToScreen = screenToWorld.createInverse();
+ } catch (final NoninvertibleTransformException e) {
+ LOGGER.error(e);
+ }
+ }
+
+ public void setBgContext(final MapContext context) {
+
+ // Remove the default listener from the old context
+ if (this.bgContext != null) {
+ this.bgContext.removeMapLayerListListener(bgContextListener);
+
+ // adding listener to all layers
+ for (final MapLayer mapLayer : bgContext.getLayers()) {
+ mapLayer.removeMapLayerListener(bgMapLayerListener);
+ }
+ }
+
+ this.bgContext = context;
+
+ if (context != null) {
+ setMapArea(bgContext.getAreaOfInterest());
+
+ this.bgContext.addMapLayerListListener(bgContextListener);
+
+ // adding listener to all layers
+ for (final MapLayer mapLayer : bgContext.getLayers()) {
+ mapLayer.addMapLayerListener(bgMapLayerListener);
+ }
+ }
+ mapImageInvalid = true;
+ repaint();
+ }
+
+ public void setJava2dHints(final RenderingHints java2dHints) {
+ this.java2dHints = java2dHints;
+ }
+
+ /**
+ *
+ * @param context
+ */
+ public void setLocalContext(final MapContext context) {
+ // Remove the default listener from the old context
+ if (this.localContext != null) {
+ this.localContext.removeMapLayerListListener(localContextListener);
+
+ // adding listener to all layers
+ for (final MapLayer mapLayer : localContext.getLayers()) {
+ mapLayer.removeMapLayerListener(localMapLayerListener);
+ }
+ }
+
+ this.localContext = context;
+
+ if (context != null) {
+
+ setMapArea(localContext.getAreaOfInterest());
+
+ localRenderer.setContext(localContext);
+
+ this.localContext.addMapLayerListListener(localContextListener);
+
+ // adding listener to all layers
+ for (final MapLayer mapLayer : localContext.getLayers()) {
+ mapLayer.addMapLayerListener(localMapLayerListener);
+ }
+ }
+
+ mapImageInvalid = true;
+ repaint();
+ }
+
+ /**
+ * @param newMapArea
+ * @return <code>true</code> if the mapArea has been changed and a repaint
+ * has been triggered.
+ */
+ public boolean setMapArea(final Envelope newMapArea) {
+
+ if (newMapArea == null
+ || bestAllowedMapArea(newMapArea).equals(mapArea)) {
+ // No change.. no need to repaint
+ return false;
+ }
+
+ // Testing, whether NaN or Infinity are used in the newMapArea
+ if (newMapArea.isNull() || Double.isInfinite(newMapArea.getMaxX())
+ || Double.isInfinite(newMapArea.getMaxY())
+ || Double.isInfinite(newMapArea.getMinX())
+ || Double.isInfinite(newMapArea.getMinY())) {
+ // No change.. ugly new values
+ LOGGER.warn("setMapArea has been called with newArea = "
+ + newMapArea);
+ return false;
+ }
+
+ // Testing, whether the difference if just minimal
+ if (mapArea != null) {
+ final Envelope candNew = bestAllowedMapArea(newMapArea);
+ final double tolX = mapArea.getWidth() / 1000.;
+ final double tolY = mapArea.getHeight() / 1000.;
+ if ((candNew.getMinX() - tolX < mapArea.getMinX())
+ && (mapArea.getMinX() < candNew.getMinX() + tolX)
+ && (candNew.getMaxX() - tolX < mapArea.getMaxX())
+ && (mapArea.getMaxX() < candNew.getMaxX() + tolX)
+
+ && (candNew.getMinY() - tolY < mapArea.getMinY())
+ && (mapArea.getMinY() < candNew.getMinY() + tolY)
+ && (candNew.getMaxY() - tolY < mapArea.getMaxY())
+ && (mapArea.getMaxY() < candNew.getMaxY() + tolY)
+
+ ) {
+ // The two mapAreas only differ my 1/1000th.. ignore
+
+ return false;
+ }
+ }
+
+ oldMapArea = mapArea;
+
+ this.mapArea = bestAllowedMapArea(newMapArea);
+
+ if (localContext != null) {
+ localContext.setAreaOfInterest(mapArea, localContext
+ .getCoordinateReferenceSystem());
+ }
+ if (bgContext != null) {
+ bgContext.setAreaOfInterest(mapArea, localContext
+ .getCoordinateReferenceSystem());
+ }
+ resetTransforms();
+ mapImageInvalid = true;
+ mapAreaChanged = true;
+ repaint();
+
+ // LOGGER.debug("New maparea = " + mapArea);
+ return true;
+ }
+
+ /**
+ * Set the background color of the map.
+ *
+ * @param if <code>null</code>, white is used.
+ */
+ public void setMapBackgroundColor(Color bgColor) {
+ if (bgColor == null)
+ bgColor = Color.WHITE;
+ this.mapBackgroundColor = bgColor;
+ }
+
+ /**
+ * Set the BufferedImage to use as a flaoting icon in the lower right corner
+ *
+ * @param mapImageIcon
+ * <code>null</code> is allowed and deactivates this icon.
+ */
+ public void setMapImage(final BufferedImage mapImage) {
+ this.mapImage = mapImage;
+ }
+
+ /**
+ * Sets whether a layer is regarded or ignored on {@link #SELECT_TOP},
+ * {@link #SELECT_ALL} and {@link #SELECT_ONE_FROM_TOP} actions.
+ *
+ * @param layer
+ * a layer
+ * @param selectable
+ * if {@code false} the layer is ignored during the upper
+ * mentioned actions. If <code>null</code>, the default (true)
+ * will be used.
+ */
+ public void setMapLayerSelectable(final MapLayer layer,
+ final Boolean selectable) {
+ if (selectable == null)
+ mapLayerSelectable.remove(layer);
+ else
+ mapLayerSelectable.put(layer, selectable);
+ }
+
+ /**
+ * Defines an evelope of the viwable area. The JMapPane will never show
+ * anything outside of this extend.
+ *
+ * @param maxExtend
+ * <code>null</code> to not have this restriction.
+ */
+ public void setMaxExtend(final Envelope maxExtend) {
+ this.maxExtend = maxExtend;
+ }
+
+ /**
+ * Set the maximum allowed zoom scale. This is the smaller number value of
+ * the two. If <code>null</code> is passed, Double.MINVALUE are used which
+ * mean there is no restriction.
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+ * Krüger</a>
+ */
+ public void setMaxZoomScale(final Double maxZoomScale) {
+ this.maxZoomScale = maxZoomScale == null ? Double.MIN_VALUE
+ : maxZoomScale;
+ }
+
+ // /** Stored the time used for the last real rendering in ms. **/
+ // private long lastRenderingDuration = Long.MAX_VALUE;
+
+ /**
+ * Set the minimum (nearest) allowed zoom scale. This is the bigger number
+ * value of the two. If <code>null</code> is passed, Double.MAXVALUE are
+ * used which mean there is no restriction.
+ *
+ *
+ * @author <a href="mailto:skpublic at wikisquare.de">Stefan Alfons
+ * Krüger</a>
+ */
+ public void setMinZoomScale(final Double minZoomScale) {
+ this.minZoomScale = minZoomScale == null ? Double.MAX_VALUE
+ : minZoomScale;
+ }
+
+ /**
+ *
+ * @param b
+ */
+ public void setPainting(final boolean b) {
+ acceptsRepaintCalls = b;
+ }
+
+ // /**
+ // * Returns in milli seconds the time the last rending of the
+ // * {@link SelectableXMapPane} took. #Long.MAX_VALUE if the JMapPane has
+ // not
+ // * been rendered yet.
+ // */
+ // public long getLastRenderingDuration() {
+ // return lastRenderingDuration;
+ // }
+
+ public void setQuickPreviewHint(final int quickPreviewHint) {
+ this.quickPreviewHint = quickPreviewHint;
+
+ }
+
+ private void setRendererHints(final Map<Object, Object> rendererHints) {
+ this.rendererHints = rendererHints;
+ }
+
+ /**
+ * Enables/Disables the ZOOM Mouse Listener. Upates the Cursor and stops the
+ * repaint Timer if
+ *
+ * @param state
+ */
+ public void setState(final int state) {
+ this.state = state;
+
+ zoomMapPaneMouseListener.setEnabled((state == ZOOM_IN
+ || state == ZOOM_OUT || state == PAN));
+
+ // Je nach Aktion den Cursor umsetzen
+ updateCursor();
+ }
+
+ /**
+ * Standardmaessig wird der Cursor automatisch je nach MapPane-Aktion (Zoom,
+ * Auswahl, ...) gesetzt. Mit dieser Methode kann ein statischer Cursor
+ * gesetzt werden, der unabhaengig von der aktuellen MapPanes-Aktion
+ * beibehalten wird. Um diesen statischen Cursor wieder zu entfernen, kann
+ * {@code null} als Parameter uebergeben werden
+ *
+ * @param cursor
+ * Cursor
+ */
+ public void setStaticCursor(final Cursor cursor) {
+ this.staticCursor = cursor;
+ if (cursor != null)
+ super.setCursor(cursor);
+ }
+
+ /**
+ * Starts rendering on one or two threads
+ */
+ private void startRendering() {
+
+ if (!isWellDefined())
+ return;
+
+ if (bgExecuter != null) {
+ // Stop all renderers
+ bgExecuter.cancelTask();
+ }
+
+ if (localExecuter != null) {
+ localExecuter.cancelTask();
+ }
+
+ final Rectangle curPaintArea = getVisibleRect();
+
+ /**
+ * We have to set new renderer
+ */
+
+ if (getBgContext() != null) {
+ // bgExecuter = new RenderingExecutor();
+ // LOGGER.debug("starting bg renderer:");
+ // // /* System.out.println("rendering"); */
+ // final GTRenderer createGTRenderer = GTUtil.createGTRenderer(
+ // bgContext, getRendererHints());
+ // createGTRenderer.setJava2DHints(getJava2dHints());
+ // bgExecuter.submit(getBgContext().getAreaOfInterest(),
+ // curPaintArea,
+ // (Graphics2D) getBgImage().getGraphics(), createGTRenderer);
+ }
+
+ if (getContext() != null) {
+ // localExecuter = new RenderingExecutor(this, 150l);
+ // LOGGER.debug("starting local renderer:");
+
+ // Clear label cache
+ labelCache.clear();
+ final Map<Object, Object> rh = localRenderer.getRendererHints();
+ rh.put(StreamingRenderer.LABEL_CACHE_KEY, labelCache);
+ localRenderer.setRendererHints(rh);
+
+ final boolean submitted = localExecuter.submit(getContext()
+ .getAreaOfInterest(), curPaintArea,
+ (Graphics2D) getLocalImage().getGraphics(), localRenderer,
+ getWorldToScreenTransform());
+ if (submitted)
+ repaintTimer.restart();
+ else
+ requestStartRendering = true; // Try to start rendering again in
+ // a moment
+ }
+
+ updateCursor();
+ }
+
+ /**
+ * Transformiert einen Geo-Koordinaten-Bereich in Fenster-Koordinaten.
+ *
+ * @param ox
+ * X-Koordinate der VON-Position
+ * @param oy
+ * Y-Koordinate der VON-Position
+ * @param px
+ * X-Koordinate der BIS-Position
+ * @param py
+ * Y-Koordinate der BIS-Position
+ * @param winToGeotransform
+ * Eine Window to Geo transform. If <code>null</code>,
+ * {@link #getScreenToWorld()} is used.
+ */
+ public Envelope tranformGeoToWindow(final double ox, final double oy,
+ final double px, final double py,
+ final AffineTransform winToGeotransform) {
+ final AffineTransform at = winToGeotransform == null ? getScreenToWorld()
+ : winToGeotransform;
+ Point2D geoO;
+ try {
+ geoO = at.inverseTransform(new Point2D.Double(ox, oy), null);
+ final Point2D geoP = at.inverseTransform(
+ new Point2D.Double(px, py), null);
+ return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP
+ .getY());
+ } catch (final NoninvertibleTransformException e) {
+ LOGGER.error(e);
+ return new Envelope(ox, oy, px, py);
+ }
+ }
+
+ /**
+ * Transformiert einen Fenster-Koordinaten-Bereich in Geo-Koordinaten.
+ *
+ * @param ox
+ * X-Koordinate der VON-Position
+ * @param oy
+ * Y-Koordinate der VON-Position
+ * @param px
+ * X-Koordinate der BIS-Position
+ * @param py
+ * Y-Koordinate der BIS-Position
+ */
+ public Envelope tranformWindowToGeo(final int ox, final int oy,
+ final int px, final int py) {
+ final AffineTransform at = getScreenToWorld();
+ final Point2D geoO = at.transform(new Point2D.Double(ox, oy), null);
+ final Point2D geoP = at.transform(new Point2D.Double(px, py), null);
+ return new Envelope(geoO.getX(), geoP.getX(), geoO.getY(), geoP.getY());
+ }
+
+ /**
+ * Will update the cursor. If all rendering is finished also stops the
+ * {@link #repaintTimer}
+ */
+ public void updateCursor() {
+
+ // if the renderers have stopped, also stop the timer that is updating
+ // the final image
+ if (bgExecuter != null && bgExecuter.isRunning()
+ || localExecuter != null && localExecuter.isRunning()) {
+ setCursor(WAIT_CURSOR);
+ return;
+ } else {
+ // Allow one last rendering
+ if (repaintTimer.isRunning()) {
+ repaintTimer.stop();
+ updateFinalImage();
+ repaint();
+ }
+ }
+
+ // wenn manueller Cursor gesetzt ist, dann diesen verwenden (unabhaengig
+ // von der aktuellen Aktion
+ if (this.staticCursor != null) {
+ setCursor(staticCursor);
+ return;
+ }
+ if (getCursor() == SwingUtil.PANNING_CURSOR) {
+ // This cursor will reset itself
+ return;
+ }
+
+ // Set the cursor depending on what tool is in use...
+ switch (state) {
+ case SELECT_TOP:
+ case SELECT_ONE_FROM_TOP:
+ case SELECT_ALL:
+ setCursor(SwingUtil.CROSSHAIR_CURSOR);
+ break;
+ case ZOOM_IN:
+ setCursor(SwingUtil.ZOOMIN_CURSOR);
+ break;
+ case ZOOM_OUT:
+ setCursor(SwingUtil.ZOOMOUT_CURSOR);
+ break;
+ case PAN:
+ setCursor(SwingUtil.PAN_CURSOR);
+ break;
+ default:
+ setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ break;
+ }
+ }
+
+ /**
+ * The renderers are all rendering into their own {@link Image}s. This
+ * method combines all images to one {@link #finalImage}. The
+ * {@link #repaintTimer} is calling this method regularely to update the
+ * {@link #finalImage} even if the renderers are still working.
+ */
+ synchronized protected Image updateFinalImage() {
+
+ // Render the two map images first, into the preFinalImage
+ if (bgExecuter != null) {
+ final Graphics2D preFinalG = (Graphics2D) getPreFinalImage()
+ .getGraphics();
+ preFinalG.setBackground(getMapBackgroundColor());
+
+ preFinalG.drawImage(getBgImage(), 0, 0, getMapBackgroundColor(),
+ null);
+
+ // // Draw the local layers image
+ preFinalG.drawImage(getLocalImage(), 0, 0, null);
+ preFinalG.dispose();
+
+ } else {
+ preFinalImage = getLocalImage();
+ }
+
+ final Graphics2D finalG = getFinalImage().createGraphics();
+ finalG.setBackground(getMapBackgroundColor());
+ finalG.drawImage(getPreFinalImage(), imageOrigin.x, imageOrigin.y,
+ getMapBackgroundColor(), null);
+
+ final int finalImageHeight = getFinalImage().getHeight(null);
+ final int finalImageWidth = getFinalImage().getWidth(null);
+
+ final Rectangle painedArea = new Rectangle(imageOrigin.x,
+ imageOrigin.y, finalImageWidth, finalImageHeight);
+ SwingUtil.clearAround(finalG, painedArea, getVisibleRect());
+
+ addGadgets(finalG);
+
+ finalG.dispose();
+
+ return finalImage;
+ }
+
+ /**
+ * Paints some optional stuff into the given {@link Graphics2D}. Usually
+ * called as the last layer when {@link #updateFinalImage()}
+ */
+ private void addGadgets(final Graphics2D graphics) {
+
+ // Paint a logo to the bottom right if available
+ if (mapImage != null) {
+ graphics.drawImage(mapImage, getBounds().width
+ - mapImage.getWidth() - 10, getBounds().height
+ - mapImage.getHeight() - 10, null);
+ }
+
+ // If the rendering process is still running, indicate this is the image
+ if (bgExecuter != null && bgExecuter.isRunning()
+ || localExecuter != null && localExecuter.isRunning()) {
+
+ final Color c = graphics.getColor();
+ graphics.setFont(waitFont);
+ String waitStr = "Wait..."; //i8n
+
+ graphics.setColor(Color.WHITE);
+ graphics.drawString(waitStr, 39, 69);
+ graphics.setColor(Color.BLACK);
+ graphics.drawString(waitStr, 40, 70);
+
+ graphics.setColor(c);
+ }
+
+ }
+
+ /**
+ * Sets the {@link #mapArea} to best possibly present the given features. If only one
+ * single point is given, the window is moved over the point.
+ *
+ * @param features
+ * if <code>null</code> or size==0, the function doesn nothing.
+ */
+ public void zoomTo(
+ final FeatureCollection<SimpleFeatureType, SimpleFeature> features) {
+
+ final CoordinateReferenceSystem mapCRS = getContext()
+ .getCoordinateReferenceSystem();
+ final CoordinateReferenceSystem fCRS = features.getSchema()
+ .getGeometryDescriptor().getCoordinateReferenceSystem();
+
+ double width = mapArea.getWidth();
+ double height = mapArea.getHeight();
+ final double ratio = height / width;
+
+ if (features == null || features.size() == 0) {
+ // feature count == 0 Zoom to the full extend
+ return;
+ } else if (features.size() == 1) {
+
+ // feature count == 1 Just move the window to the point and zoom 'a
+ // bit'
+ final SimpleFeature singleFeature = features.iterator().next();
+
+ if (((Geometry) singleFeature.getDefaultGeometry())
+ .getCoordinates().length > 1) {
+ // System.out.println("Zoomed to only pne poylgon");
+ // Poly
+ // TODO max width vs. height
+ width = features.getBounds().getWidth() * 3;
+ height = ratio * width;
+ } else {
+ // System.out.println("Zoomed in a bit becasue only one point");
+ // width *= .9;
+ // height *= .9;
+ }
+
+ Coordinate centre = features.getBounds().centre();
+ if (!mapCRS.equals(fCRS)) {
+ // only to calculations if the CRS differ
+ try {
+ MathTransform fToMap;
+ fToMap = CRS.findMathTransform(fCRS, mapCRS);
+ // centre is transformed to the mapCRS
+ centre = JTS.transform(centre, null, fToMap);
+ } catch (final FactoryException e) {
+ LOGGER.error("Looking for a Math transform", e);
+ } catch (final TransformException e) {
+ LOGGER.error("Looking for a Math transform", e);
+ }
+ }
+
+ final Coordinate newLeftBottom = new Coordinate(centre.x - width
+ / 2., centre.y - height / 2.);
+ final Coordinate newTopRight = new Coordinate(
+ centre.x + width / 2., centre.y + height / 2.);
+
+ final Envelope newMapArea = new Envelope(newLeftBottom, newTopRight);
+
+ setMapArea(newMapArea);
+
+ } else {
+ final ReferencedEnvelope fBounds = features.getBounds();
+
+ Envelope bounds;
+ if (!mapCRS.equals(fCRS)) {
+ bounds = JTSUtil.transformEnvelope(fBounds, fCRS, mapCRS);
+ } else {
+ bounds = fBounds;
+ }
+ // BB umrechnen von Layer-CRS in Map-CRS
+
+ // Expand a bit
+ bounds.expandBy(bounds.getWidth() / 6., bounds.getHeight() / 6.);
+
+ setMapArea(bounds);
+ }
+ }
+
+ /**
+ * Zooms towards a point.
+ *
+ * @param center
+ * position in window coordinates
+ * @param zoomFactor
+ * > 1 for zoom in, < 1 for zoom out. Default is 1.33
+ */
+ public void zoomTo(final Point center) {
+ zoomTo(center, null);
+ }
+
+ /**
+ * Zooms towards a point.
+ *
+ * @param center
+ * position in window coordinates
+ * @param zoomFaktor
+ * > 1 for zoom in, < 1 for zoom out. Default is 1.33.
+ */
+ public void zoomTo(Point center, Double zoomFaktor) {
+ if (zoomFaktor == null || zoomFaktor == 0.)
+ zoomFaktor = 2.;
+
+ final Point2D gcenter = getScreenToWorld().transform(center, null);
+ center = null;
+
+ if (Double.isNaN(gcenter.getX()) || Double.isNaN(gcenter.getY())
+ || Double.isInfinite(gcenter.getX())
+ || Double.isInfinite(gcenter.getY())
+
+ ) {
+ // Not inside valid CRS area! cancel
+ return;
+ }
+
+ final Envelope mapArea = getMapArea();
+
+ final Envelope newMapArea = new Envelope(mapArea);
+ newMapArea.expandBy((mapArea.getWidth() * zoomFaktor - mapArea
+ .getWidth()) / 2., (mapArea.getHeight() * zoomFaktor - mapArea
+ .getHeight()) / 2.);
+
+ // Move the newMapArea above the new center
+ newMapArea.translate(gcenter.getX() - mapArea.centre().x, gcenter
+ .getY()
+ - mapArea.centre().y);
+
+ setMapArea(newMapArea);
+ }
+
+}
Copied: branches/1.0-gt2-2.6/src/skrueger/geotools/ZoomXMapPaneMouseListener.java (from rev 513, branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/ZoomXMapPaneMouseListener.java)
===================================================================
--- branches/1.0-gt2-2.6/src/gtmig/org/geotools/swing/ZoomXMapPaneMouseListener.java 2009-11-09 11:17:34 UTC (rev 513)
+++ branches/1.0-gt2-2.6/src/skrueger/geotools/ZoomXMapPaneMouseListener.java 2009-11-19 09:42:59 UTC (rev 531)
@@ -0,0 +1,251 @@
+package skrueger.geotools;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+
+import org.apache.log4j.Logger;
+
+import schmitzm.swing.SwingUtil;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Envelope;
+
+public class ZoomXMapPaneMouseListener extends MouseAdapter {
+
+ private static Logger LOGGER = Logger
+ .getLogger(ZoomXMapPaneMouseListener.class);
+
+
+ protected final XMapPane xMapPane;
+
+ protected boolean enabled = true;
+
+ public ZoomXMapPaneMouseListener(XMapPane xMapPane) {
+ if (xMapPane == null) throw new IllegalArgumentException("xMapPane may not be null");
+ this.xMapPane = xMapPane;
+ }
+
+ /**
+ * Stores beginning of a drag event in window coordinates
+ */
+ protected Point startPos;
+
+ /**
+ * Stores last position of a drag event in window coordinates
+ */
+ protected Point lastPos;
+
+ /**
+ * Draws a rectangle in XOR mode from the origin at {@link #startPos} to the
+ * given point. All in screen coordinates.
+ */
+ protected void drawRectangle(final Graphics graphics, Point e) {
+ // undraw last box/draw new box
+ final int left = Math.min(startPos.x, e.x);
+ final int right = Math.max(startPos.x, e.x);
+ final int top = Math.max(startPos.y, e.y);
+ final int bottom = Math.min(startPos.y, e.y);
+ final int width = right - left;
+ final int height = top - bottom;
+
+ if (width == 0 && height == 0)
+ return;
+
+ graphics.setXORMode(Color.WHITE);
+ graphics.drawRect(left, bottom, width, height);
+ }
+
+ public void mousePressed(final MouseEvent e) {
+ if (!isEnabled())
+ return;
+ startPos = new Point(e.getPoint());
+ lastPos = new Point(e.getPoint());
+ }
+
+ public void mouseReleased(final MouseEvent e) {
+ if (!isEnabled())
+ return;
+
+ final Rectangle bounds = xMapPane.getBounds();
+
+ Envelope mapArea = xMapPane.getMapArea();
+
+ // Replace with transform and translate
+ final double mapWidth = mapArea.getWidth();
+ final double mapHeight = mapArea.getHeight();
+
+ final double startX = ((startPos.x * mapWidth) / (double) bounds.width)
+ + mapArea.getMinX();
+ final double startY = (((bounds.getHeight() - startPos.y) * mapHeight) / (double) bounds.height)
+ + mapArea.getMinY();
+ final double endX = ((e.getPoint().x * mapWidth) / (double) bounds.width)
+ + mapArea.getMinX();
+ final double endY = (((bounds.getHeight() - e.getPoint().y) * mapHeight) / (double) bounds.height)
+ + mapArea.getMinY();
+
+ if ((xMapPane.getState() == XMapPane.PAN)
+ || (e.getButton() == MouseEvent.BUTTON3)) {
+ xMapPane.performPan();
+
+ } else if (xMapPane.getState() == XMapPane.ZOOM_IN) {
+
+ drawRectangle(xMapPane.getGraphics(), e.getPoint());
+
+
+ // Don't zoom too small areas. Handle it like a click then
+ if ((Math.abs(startPos.x - e.getPoint().x) * Math.abs(e.getPoint().y - startPos.y)) < 160) {
+ xMapPane.zoomTo(e.getPoint(), .5);
+ return;
+ }
+
+ final double left = Math.min(startX, endX);
+ final double right = Math.max(startX, endX);
+ final double bottom = Math.min(startY, endY);
+ final double top = Math.max(startY, endY);
+ final Coordinate ll = new Coordinate(left, bottom);
+ final Coordinate ur = new Coordinate(right, top);
+
+ xMapPane.setMapArea(new Envelope(ll, ur));
+
+ } else if (xMapPane.getState() == XMapPane.ZOOM_OUT) {
+
+ // Remove the rectangle
+ drawRectangle(xMapPane.getGraphics(), e.getPoint());
+
+
+
+ // Don't zoom too small areas
+ if ((Math.abs(startPos.x - e.getPoint().x) * Math.abs(e.getPoint().y - startPos.y)) < 100) {
+ xMapPane.zoomTo(e.getPoint(), 2.);
+ return;
+ }
+
+ // make the dragged rectangle in screen coords the new map size?
+ final double left = Math.min(startX, endX);
+ final double right = Math.max(startX, endX);
+ final double bottom = Math.min(startY, endY);
+ final double top = Math.max(startY, endY);
+ final double nWidth = (mapWidth * mapWidth) / (right - left);
+ final double nHeight = (mapHeight * mapHeight) / (top - bottom);
+ final double deltaX1 = left - mapArea.getMinX();
+ final double nDeltaX1 = (deltaX1 * nWidth) / mapWidth;
+ final double deltaY1 = bottom - mapArea.getMinY();
+ final double nDeltaY1 = (deltaY1 * nHeight) / mapHeight;
+ final Coordinate ll = new Coordinate(mapArea.getMinX() - nDeltaX1,
+ mapArea.getMinY() - nDeltaY1);
+ final double deltaX2 = mapArea.getMaxX() - right;
+ final double nDeltaX2 = (deltaX2 * nWidth) / mapWidth;
+ final double deltaY2 = mapArea.getMaxY() - top;
+ final double nDeltaY2 = (deltaY2 * nHeight) / mapHeight;
+ final Coordinate ur = new Coordinate(mapArea.getMaxX() + nDeltaX2,
+ mapArea.getMaxY() + nDeltaY2);
+
+ xMapPane.setMapArea(new Envelope(ll, ur));
+ }
+
+
+ }
+
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Returns <code>true</code> if the xMapPane is well defined and the
+ * listener is enabled.
+ */
+ public boolean isEnabled() {
+ return enabled && xMapPane.isWellDefined();
+ }
+
+ /**
+ * Verarbeitet die Mausrad-Aktion, indem gezoomed wird.
+ *
+ * @param wheelEvt
+ * Mausrad-Event
+ */
+ public void mouseWheelMoved(MouseWheelEvent wheelEvt) {
+ if (!isEnabled()) return;
+
+ final int units = wheelEvt.getUnitsToScroll();
+ if (units > 0)
+ xMapPane.zoomTo(wheelEvt.getPoint(), 1. + .11 * units);
+ else
+ xMapPane.zoomTo(wheelEvt.getPoint(), 2. / -units);
+ }
+
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+
+ if (!isEnabled())
+ return;
+
+ int state = xMapPane.getState();
+
+ // Nothing to do for panning
+ if (state == XMapPane.PAN)
+ return;
+
+ // double zoomFactor = 1.;
+ //
+ // final Envelope mapArea = xMapPane.getMapArea();
+ // if (mapArea == null)
+ // return;
+ //
+ // final Rectangle bounds = xMapPane.getVisibleRect();
+ //
+ // final double x = (double) (e.getX());
+ // final double y = (double) (e.getY());
+ // final double width = mapArea.getWidth();
+ // final double height = mapArea.getHeight();
+ // final double width2 = width / 2.0;
+ // final double height2 = height / 2.0;
+ // final double mapX = ((x * width) / (double) bounds.width)
+ // + mapArea.getMinX();
+ // final double mapY = (((bounds.getHeight() - y) * height) / (double)
+ // bounds.height)
+ // + mapArea.getMinY();
+ // double zlevel = 1.0;
+ //
+ if (state == XMapPane.ZOOM_IN && e.getButton() == MouseEvent.BUTTON1) {
+ xMapPane.zoomTo(e.getPoint(), 1 / 2.);
+ } else if (state == XMapPane.ZOOM_IN
+ && e.getButton() == MouseEvent.BUTTON3
+ || state == XMapPane.ZOOM_OUT) {
+ xMapPane.zoomTo(e.getPoint(), 2.);
+ }
+ //
+ // final Coordinate ll = new Coordinate(mapX - (width2 / zlevel), mapY
+ // - (height2 / zlevel));
+ // final Coordinate ur = new Coordinate(mapX + (width2 / zlevel), mapY
+ // + (height2 / zlevel));
+ // final Envelope newMapArea = new Envelope(ll, ur);
+ //
+ // xMapPane.setMapArea(newMapArea);
+
+ // xMapPane.repaint();
+
+ }
+
+ /**
+ * Constantly fired while the mouse is dragging
+ */
+ public void mouseDragged(final MouseEvent event) {
+ if (!isEnabled())
+ return;
+
+ xMapPane.mouseDragged(startPos, lastPos, event);
+
+
+ lastPos = event.getPoint();
+ }
+
+
+}
More information about the Schmitzm-commits
mailing list