/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2008-2011, Open Source Geospatial Foundation (OSGeo)
*
* 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;
* version 2.1 of the License.
*
* 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 org.geotools.swing.tool;
import java.awt.geom.AffineTransform;
import java.lang.ref.WeakReference;
import java.util.logging.Logger;
import org.geotools.geometry.DirectPosition2D;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.map.event.MapBoundsEvent;
import org.geotools.map.event.MapBoundsListener;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.util.logging.Logging;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
/**
* Abstract base class for helper classes used by {@linkplain InfoTool} to query
* features in map layers.
*
* @author Michael Bedward
* @since 2.6
*
* @source $URL$
* @version $URL$
*/
public abstract class InfoToolHelper implements MapBoundsListener {
private static final Logger LOGGER = Logging.getLogger("org.geotools.swing");
/**
* String key used for the position element in the {@code Map} passed to
* {@linkplain #getInfo( org.geotools.util.KVP )}.
*/
public static final String KEY_POSITION = "pos";
protected WeakReference<MapContent> contentRef;
protected WeakReference<Layer> layerRef;
private boolean transformFailed;
private MathTransform transform;
/**
* CAlled by the helper lookup system when selecting a helper
* for a given layer.
*
* @param layer the layer
*
* @return {@code true} is this helper can handle the layer
*
* @throws IllegalArgumentException if {@code layer} is {@code null}
*/
public abstract boolean isSupportedLayer(Layer layer);
/**
* Gets layer data at the specified position. If there are no feature
* data at the position, an empty {@code InfoToolResult} object is
* returned.
*
* @param pos query position
* @return layer data
*
* @throws Exception on error querying the layer
*/
public abstract InfoToolResult getInfo(DirectPosition2D pos) throws Exception;
/**
* Checks if this helper is holding a reference to a {@code MapContent} and
* a {@code Layer}.Helpers only hold a {@code WeakReference} to both the
* map content and layer to avoid blocking garbage collection when layers
* are discarded.
*
* @return {@code true} if both map content and layer references are valid
*/
public boolean isValid() {
return contentRef != null && contentRef.get() != null
&& layerRef != null && layerRef.get() != null;
}
/**
* Sets the map content for this helper.
*
* @param layer the map content
*
* @throws IllegalArgumentException if {@code content} is {@code null}
*/
public void setMapContent(MapContent content) {
if (content == null) {
throw new IllegalArgumentException("content must not be null");
}
contentRef = new WeakReference<MapContent>(content);
clearTransform();
}
/**
* Gets the map content associated with this helper.
*
* @return the map content
*/
public MapContent getMapContent() {
return contentRef != null ? contentRef.get() : null;
}
/**
* Sets the map layer for this helper.
*
* @param layer the map layer
*
* @throws IllegalArgumentException if {@code layer} is {@code null}
*/
public void setLayer(Layer layer) {
if (layer == null) {
throw new IllegalArgumentException("layer must not be null");
}
layerRef = new WeakReference<Layer>(layer);
clearTransform();
}
/**
* Gets the map layer associated with this helper.
* @return
*/
public Layer getLayer() {
return layerRef != null ? layerRef.get() : null;
}
/**
* A method from the {@code MapBoundsListener} interface used to listen
* for a change to the map content's coordinate reference system.
*/
@Override
public void mapBoundsChanged(MapBoundsEvent event) {
clearTransform();
}
/**
* Gets the {@code MathTransform} used to convert coordinates from the
* projection being used by the {@code MapContent} to that of the
* {@code Layer}.
*
* @return the transform or {@code null} if the layer's CRS is the same
* as that of the map content, or if either has no CRS defined
*/
protected MathTransform getContentToLayerTransform() {
if (transform == null && !transformFailed) {
MapContent content = getMapContent();
Layer layer = getLayer();
if (content != null && layer != null) {
CoordinateReferenceSystem contentCRS = content.getCoordinateReferenceSystem();
CoordinateReferenceSystem layerCRS =
layer.getFeatureSource().getSchema().getCoordinateReferenceSystem();
if (contentCRS != null && layerCRS != null) {
if (CRS.equalsIgnoreMetadata(contentCRS, layerCRS)) {
transform = new AffineTransform2D(new AffineTransform());
} else {
try {
transform = CRS.findMathTransform(contentCRS, layerCRS, true);
} catch (Exception ex) {
LOGGER.warning("Can't transform map content CRS to layer CRS");
transformFailed = true;
}
}
}
} else {
// one or both of content and layer CRS is null
transform = new AffineTransform2D(new AffineTransform());
}
}
return transform;
}
protected boolean isTransformRequired() {
return !getContentToLayerTransform().isIdentity();
}
protected void clearTransform() {
transform = null;
transformFailed = false;
}
}