/* Copyright 2013 The jeo project. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jeo.filter;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import io.jeo.geom.Bounds;
import java.util.Locale;
import java.util.Objects;
/**
* Filter that applies a spatial comparison operator to two geometry expression operands.
*
* @author Justin Deoliveira, OpenGeo
*
* TODO: use prepared geometries
*/
public class Spatial<T> extends BinaryFilter<T> {
/**
* Spatial operator type.
*/
public enum Type {
EQUALS, INTERSECTS, TOUCHES, DISJOINT, OVERLAPS, CROSSES, COVERS, BBOX,
WITHIN {
@Override
public Type invert() {
return CONTAINS;
}
},
CONTAINS {
@Override
public Type invert() {
return WITHIN;
}
},
DWITHIN {
@Override
public Type invert() {
return BEYOND;
}
},
BEYOND {
@Override
public Type invert() {
return DWITHIN;
}
};
public Type invert() {
return this;
}
}
final Type type;
final Expression distance;
public Spatial(Type type, Expression left, Expression right, Expression distance) {
super(left, right);
Objects.requireNonNull(type, "type must not be null");
switch (type) {
case DWITHIN: case BEYOND:
if (distance == null) {
throw new IllegalArgumentException(String.format(Locale.ROOT,"operator: %s, requires distance value", type));
}
}
this.type = type;
this.distance = distance;
}
public Type type() {
return type;
}
public Expression distance() {
return distance;
}
@Override
public Spatial<T> normalize() {
return (Spatial<T>) super.normalize();
}
@Override
public Spatial<T> invert() {
return new Spatial<>(type.invert(), right, left, distance);
}
@Override
public boolean test(T obj) {
Object o1 = left.evaluate(obj);
Object o2 = right.evaluate(obj);
Number d = (Number) (distance == null ? null : distance.evaluate(obj));
return compare(o1, o2, d);
}
protected boolean compare(Object o1, Object o2, Number d) {
if (o1 == null || o2 == null) {
return false;
}
if (type == Type.BBOX) {
// only need to compare envelopes
Envelope e1 = toEnvelope(o1);
Envelope e2 = toEnvelope(o2);
return e1.intersects(e2);
}
Geometry g1 = toGeometry(o1);
Geometry g2 = toGeometry(o2);
switch(type) {
case EQUALS:
return g1.equalsTopo(g2);
case INTERSECTS:
return g1.intersects(g2);
case TOUCHES:
return g1.touches(g2);
case OVERLAPS:
return g1.overlaps(g2);
case DISJOINT:
return g1.disjoint(g2);
case CROSSES:
return g1.crosses(g2);
case COVERS:
return g1.covers(g2);
case WITHIN:
return g1.within(g2);
case CONTAINS:
return g1.contains(g2);
case DWITHIN:
return g1.isWithinDistance(g2, d.doubleValue());
case BEYOND:
return !g1.isWithinDistance(g2, d.doubleValue());
default:
throw new IllegalStateException();
}
}
protected Envelope toEnvelope(Object o) {
if (o instanceof Envelope) {
return (Envelope) o;
}
Geometry g = toGeometry(o);
if (g == null) {
throw new IllegalArgumentException("Unable to convert " + o + " to envelope");
}
return g.getEnvelopeInternal();
}
protected Geometry toGeometry(Object o) {
if (o instanceof Geometry) {
return (Geometry) o;
}
if (o instanceof Envelope) {
return Bounds.toPolygon((Envelope) o);
}
throw new IllegalArgumentException("Unable to convert " + o + " to geometry");
}
@Override
public <R> R accept(FilterVisitor<R> v, Object obj) {
return v.visit(this, obj);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((left == null) ? 0 : left.hashCode());
result = prime * result + ((right == null) ? 0 : right.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Spatial<?> other = (Spatial<?>) obj;
if (left == null) {
if (other.left != null)
return false;
} else if (!left.equals(other.left))
return false;
if (right == null) {
if (other.right != null)
return false;
} else if (!right.equals(other.right))
return false;
if (type != other.type)
return false;
if (distance == null) {
if (other.distance != null) {
return false;
}
} else if (!distance.equals(other.distance)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder().append(left).
append(" ").append(type).append(" ").append(right);
if (distance != null) {
buf.append(" ").append(distance);
}
return buf.toString();
}
}