/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
* (C) 2009, Geomatys
*
* 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.geotoolkit.filter.binaryspatial;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.prep.PreparedGeometry;
import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory;
import java.util.logging.Level;
import org.apache.sis.feature.FeatureExt;
import org.geotoolkit.filter.DefaultLiteral;
import org.geotoolkit.filter.DefaultPropertyName;
import org.geotoolkit.geometry.jts.JTS;
import org.geotoolkit.geometry.jts.SRIDGenerator;
import org.geotoolkit.geometry.jts.SRIDGenerator.Version;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.CommonCRS;
import org.geotoolkit.util.StringUtilities;
import org.opengis.filter.FilterVisitor;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.spatial.BBOX;
import org.opengis.geometry.BoundingBox;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.util.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.logging.Logging;
import org.opengis.feature.Feature;
import org.opengis.feature.PropertyType;
import org.apache.sis.internal.feature.AttributeConvention;
/**
* Immutable "BBOX" filter.
*
* @author Johann Sorel (Geomatys).
* @module
*/
public class DefaultBBox extends AbstractBinarySpatialOperator<PropertyName,DefaultLiteral<BoundingBox>> implements BBOX {
private static final LinearRing[] EMPTY_RINGS = new LinearRing[0];
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
private static final PreparedGeometryFactory PREPARED_FACTORY = new PreparedGeometryFactory();
//cache the bbox geometry
protected transient PreparedGeometry boundingGeometry;
protected final com.vividsolutions.jts.geom.Envelope boundingEnv;
protected final CoordinateReferenceSystem crs;
protected final int srid;
public DefaultBBox(final PropertyName property, final DefaultLiteral<BoundingBox> bbox) {
super(nonNullPropertyName(property),bbox);
boundingGeometry = toGeometry(bbox.getValue());
boundingEnv = boundingGeometry.getGeometry().getEnvelopeInternal();
final CoordinateReferenceSystem crsFilter = bbox.getValue().getCoordinateReferenceSystem();
if(crsFilter != null){
this.crs = crsFilter;
this.srid = SRIDGenerator.toSRID(crs, Version.V1);
}else{
// In CQL if crs is not specified, it is EPSG:4326
this.crs = CommonCRS.WGS84.normalizedGeographic();
this.srid = 4326;
}
}
private PreparedGeometry getPreparedGeometry(){
if(boundingGeometry == null){
boundingGeometry = toGeometry(right.getValue());
}
return boundingGeometry;
}
private static PropertyName nonNullPropertyName(final PropertyName proper) {
if (proper == null)
return new DefaultPropertyName("");
return proper;
}
protected CoordinateReferenceSystem findCRS(final Object base, final Geometry candidate){
//we don't know in which crs it is, try to find it
CoordinateReferenceSystem crs = null;
try{
crs = JTS.findCoordinateReferenceSystem(candidate);
}catch(IllegalArgumentException ex){
LOGGER.log(Level.WARNING, null, ex);
}catch(NoSuchAuthorityCodeException ex){
LOGGER.log(Level.WARNING, null, ex);
}catch(FactoryException ex){
LOGGER.log(Level.WARNING, null, ex);
}
if(crs == null && base instanceof Feature){
//try to find it on the base
final Feature att = (Feature) base;
final String propertyName = left.getPropertyName();
if (propertyName.isEmpty()) {
crs = FeatureExt.getCRS(att.getType());
} else {
for(PropertyType pt : att.getType().getProperties(true)){
if(AttributeConvention.isGeometryAttribute(pt)){
crs = FeatureExt.getCRS(pt);
}
}
}
}
return crs;
}
/**
* {@inheritDoc }
*/
@Override
public String getPropertyName() {
return left.getPropertyName();
}
/**
* {@inheritDoc }
*/
@Override
public String getSRS() {
return IdentifiedObjects.getIdentifierOrName(right.getValue().getCoordinateReferenceSystem());
}
/**
* {@inheritDoc }
*/
@Override
public double getMinX() {
return right.getValue().getMinimum(0);
}
/**
* {@inheritDoc }
*/
@Override
public double getMinY() {
return right.getValue().getMinimum(1);
}
/**
* {@inheritDoc }
*/
@Override
public double getMaxX() {
return right.getValue().getMaximum(0);
}
/**
* {@inheritDoc }
*/
@Override
public double getMaxY() {
return right.getValue().getMaximum(1);
}
/**
* {@inheritDoc }
*/
@Override
public boolean evaluate(final Object object) {
Geometry candidate = toGeometry(object, left);
if(candidate == null){
return false;
}
//we don't know in which crs it is, try to find it
final CoordinateReferenceSystem candidateCrs = findCRS(object, candidate);
//if we don't know the crs, we will assume it's the objective crs already
if(candidateCrs != null){
//reproject in objective crs if needed
if (!Utilities.equalsIgnoreMetadata(this.crs,candidateCrs)) {
try {
candidate = JTS.transform(candidate, CRS.findOperation(candidateCrs, this.crs, null).getMathTransform());
} catch (MismatchedDimensionException | TransformException | FactoryException ex) {
Logging.getLogger("org.geotoolkit.filter.binaryspatial").log(Level.WARNING, null, ex);
return false;
}
}
}
final com.vividsolutions.jts.geom.Envelope candidateEnv = candidate.getEnvelopeInternal();
if(boundingEnv.contains(candidateEnv) || candidateEnv.contains(boundingEnv)) {
return true;
} else if(boundingEnv.intersects(candidateEnv)) {
return getPreparedGeometry().intersects(candidate);
} else {
return false;
}
}
/**
* {@inheritDoc }
*/
@Override
public Object accept(final FilterVisitor visitor, final Object extraData) {
return visitor.visit(this, extraData);
}
/**
* {@inheritDoc }
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("BBOX \n");
sb.append(StringUtilities.toStringTree(left,right));
return sb.toString();
}
/**
* {@inheritDoc }
*/
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final AbstractBinarySpatialOperator other = (AbstractBinarySpatialOperator) obj;
if (this.left != other.left && !this.left.equals(other.left)) {
return false;
}
if (this.right != other.right && !this.right.equals(other.right)) {
return false;
}
return true;
}
/**
* {@inheritDoc }
*/
@Override
public int hashCode() {
int hash = 15;
hash = 71 * hash + this.left.hashCode();
hash = 71 * hash + this.right.hashCode();
return hash;
}
/**
* Utility method to transform an envelope in geometry.
* @param env
* @return Geometry
*/
private static PreparedGeometry toGeometry(final Envelope env){
double minX = env.getMinimum(0);
double minY = env.getMinimum(1);
double maxX = env.getMaximum(0);
double maxY = env.getMaximum(1);
if(Double.isNaN(minX) || Double.isInfinite(minX)) minX = Double.MIN_VALUE;
if(Double.isNaN(minY) || Double.isInfinite(minY)) minY = Double.MIN_VALUE;
if(Double.isNaN(maxX) || Double.isInfinite(maxX)) maxX = Double.MAX_VALUE;
if(Double.isNaN(maxY) || Double.isInfinite(maxY)) maxY = Double.MAX_VALUE;
final Coordinate[] coords = new Coordinate[5];
coords[0] = new Coordinate(minX, minY);
coords[1] = new Coordinate(minX, maxY);
coords[2] = new Coordinate(maxX, maxY);
coords[3] = new Coordinate(maxX, minY);
coords[4] = new Coordinate(minX, minY);
final LinearRing ring = GEOMETRY_FACTORY.createLinearRing(coords);
Geometry geom = GEOMETRY_FACTORY.createPolygon(ring, EMPTY_RINGS);
return PREPARED_FACTORY.create(geom);
}
}