/* 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.vector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import io.jeo.data.Sort;
import io.jeo.data.Transaction;
import io.jeo.filter.Filter;
import io.jeo.filter.Filters;
import io.jeo.filter.cql.CQL;
import io.jeo.filter.cql.ParseException;
import io.jeo.geom.Bounds;
import io.jeo.proj.Proj;
import io.jeo.util.Pair;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vividsolutions.jts.geom.Envelope;
import java.util.HashSet;
/**
* Describes a query against a {@link VectorDataset} dataset.
*
* @author Justin Deoliveira, OpenGeo
*/
public class VectorQuery {
/**
* Returns a vector query with no constraints.
*/
public static VectorQuery all() {
return new VectorQuery();
}
/** logger */
static Logger LOGGER = LoggerFactory.getLogger(VectorQuery.class);
/**
* Fields to include in query.
*/
Set<String> fields = new HashSet<String>(3);
/**
* Spatial bounds of the query.
*/
Bounds bounds;
/**
* Filter of the query
*/
Filter<Feature> filter;
/**
* Limit / offset
*/
Integer limit, offset;
// /**
// * sorting
// */
// List<Sort> sort;
/**
* reprojection
*/
Pair<CoordinateReferenceSystem,CoordinateReferenceSystem> reproject;
/**
* simplification
*/
Double simplify;
/**
* Transaction associated with the query
*/
Transaction transaction = Transaction.NULL;
/**
* New query instance.
*/
public VectorQuery() {
}
/**
* Set of Feature properties to query, an empty set means all properties.
* <p>
* The field list applies to {@link VectorDataset#read(VectorQuery)} and {@link VectorDataset#update(VectorQuery)}
* operations.
* </p>
*/
public Set<String> fields() {
return fields;
}
/**
* Get the fields in order of appearance in the Schema. An empty set implies
* all properties.
*
* @param schema the schema to evaluate ordering against
* @return list of fields in schema ordering or empty list
*/
public List<String> fieldsIn(Schema schema) {
List<String> ordered = new ArrayList<String>(fields.size());
if (fields.size() > 0) {
List<Field> schemaFields = schema.fields();
for (Field f: schemaFields) {
for (String s: fields) {
if (f.name().equals(s)) {
ordered.add(s);
break;
}
}
}
}
return ordered;
}
/**
* Bounds constraints on the query, may be <code>null</code> meaning no bounds constraint.
* <p>
* The bounds constraint applies to {@link VectorDataset#read(VectorQuery)} and
* {@link VectorDataset#update(VectorQuery)} operations.
* </p>
*/
public Bounds bounds() {
return bounds;
}
/**
* Constraint on the query, may be <code>null</code> meaning no constraint.
* <p>
* The filter constraint applies to {@link VectorDataset#read(VectorQuery)} and
* {@link VectorDataset#update(VectorQuery)} operations.
* </p>
*/
public Filter<Feature> filter() {
return filter;
}
/**
* Limit on the number of features to return from the query, <code>null</code> meaning no limit.
* <p>
* The feature limit applies to {@link VectorDataset#read(VectorQuery)} and
* {@link VectorDataset#update(VectorQuery)} operations.
* </p>
*/
public Integer limit() {
return limit;
}
/**
* Offset into query result set from which to start returning features, <code>null</code>
* meaning no offset.
* <p>
* The feature offset applies to {@link VectorDataset#read(VectorQuery)} and
* {@link VectorDataset#update(VectorQuery)} operations.
* </p>
*/
public Integer offset() {
return offset;
}
/**
* Coordinate reference systems to reproject feature results between, <code>null</code>
* meaning no reprojection should occur.
* <p>
* The first element in the pair may be <code>null</code> to signify that the dataset crs
* (if available) should be used
* </p>
* <p>
* The reproject field applies to {@link VectorDataset#read(VectorQuery)} and
* {@link VectorDataset#update(VectorQuery)} operations.
* </p>
*/
public Pair<CoordinateReferenceSystem, CoordinateReferenceSystem> reproject() {
return reproject;
}
/**
* Simplification tolerance to apply to feature geometries, <code>null</code> meaning no
* simplification.
* <p>
* The simplification tolerance applies to {@link VectorDataset#read(VectorQuery)} and
* {@link VectorDataset#update(VectorQuery)} operations.
* </p>
*/
public Double simplify() {
return simplify;
}
// /**
// * Sort criteria for the query, <code>null</code> meaning no sorting.
// * <p>
// * The sorting criteria applies to {@link VectorDataset#read(VectorQuery)} and
// * {@link VectorDataset#update(VectorQuery)} operations.
// * </p>
// */
// public List<Sort> sort() {
// return sort;
// }
/**
* Transaction of the query, may be <code>null</code>.
* <p>
* The transaction applies to {@link VectorDataset#update(VectorQuery)} and {@link VectorDataset#append(VectorQuery)}
* operations.
* </p>
*/
public Transaction transaction() {
return transaction;
}
/**
* Sets the field list of the query.
*
* @return This object.
*/
public VectorQuery fields(String field, String... fields) {
List<String> l = new ArrayList<>();
l.add(field);
l.addAll(Arrays.asList(fields));
return fields(l);
}
/**
* Sets the field list of the query.
*
* @return This object.
*/
public VectorQuery fields(Collection<String> fields) {
this.fields.clear();
return appendFields(fields);
}
/**
* Appends to the field list of this query.
*
* @return This object.
*/
public VectorQuery appendFields(Collection<String> fields) {
this.fields.addAll(fields);
return this;
}
/**
* Sets the filter of the query from a CQL string.
*
* @return This object.
*/
public VectorQuery filter(String cql) {
try {
return filter(CQL.parse(cql));
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* Sets the filter of the query.
*
* @return This object.
*/
public VectorQuery filter(Filter<Feature> filter) {
this.filter = filter;
return this;
}
/**
* Sets the bounds constraint of the query.
*
* @return This object.
*/
public VectorQuery bounds(Envelope env) {
return bounds(new Bounds(env));
}
/**
* Sets the bounds constraint of the query.
*
* @return This object.
*/
public VectorQuery bounds(Bounds bounds) {
this.bounds = bounds;
return this;
}
/**
* Sets the limit on the number of results to return from the query.
*
* @return This object.
*/
public VectorQuery limit(Integer limit) {
this.limit = limit;
return this;
}
/**
* Sets the number of results to skip over from the query.
*
* @return This object.
*/
public VectorQuery offset(Integer offset) {
this.offset = offset;
return this;
}
// /**
// * Sets the properties to sort results by.
// *
// * @return This object.
// */
// public VectorQuery sort(String... sort) {
// List<Sort> list = new ArrayList<Sort>();
// for (String s : sort) {
// list.add(new Sort(s));
// }
// this.sort = list;
// return this;
// }
/**
* Sets the srs to re-project query results to.
*
* @return This object.
*/
public VectorQuery reproject(String srs) {
return reproject(null, srs);
}
/**
* Sets the srs to re-project query results to.
*
* @return This object.
*/
public VectorQuery reproject(String from, String to) {
CoordinateReferenceSystem src = from != null ? Proj.crs(from) : null;
CoordinateReferenceSystem dst = to != null ? Proj.crs(to) : null;
if (from != null && src == null) {
throw new IllegalArgumentException("Unknown crs: " + from);
}
if (to == null) {
throw new IllegalArgumentException("Unknown crs: " + to);
}
return reproject(src, dst);
}
/**
* Sets the crs to re-project query results to.
*
* @return This object.
*/
public VectorQuery reproject(CoordinateReferenceSystem crs) {
return reproject(null, crs);
}
/**
* Sets the source/target crs to re-project query results from/to.
*
* @return This object.
*/
public VectorQuery reproject(CoordinateReferenceSystem from, CoordinateReferenceSystem to) {
reproject = new Pair<CoordinateReferenceSystem,CoordinateReferenceSystem>(from, to);
return this;
}
/**
* Sets the tolerance with which to simplify geometry of query results.
*
* @return This object.
*/
public VectorQuery simplify(Double tolerance) {
simplify = tolerance;
return this;
}
/**
* Sets the transaction of the query.
*
* @return This object.
*/
public VectorQuery transaction(Transaction tx) {
this.transaction = tx;
return this;
}
/**
* Determines if the query constrains results with a bounds constraint or filter.
*
* @return True if no bounds or filter constraint is applied, otherwise false.
*/
public boolean isAll() {
return Bounds.isNull(bounds) && !isFiltered();
}
/**
* Determines if the query constrains results with or filter.
*/
public boolean isFiltered() {
return !Filters.isTrueOrNull(filter);
}
/**
* Adjusts a raw count based on limit and offset of the query.
* <p>
* The adjusted count is equivalent to:
* <pre>
* min(max(0, count-offset), limit)
* </pre>
* </p>
* @return The adjusted count.
*/
public long adjustCount(long count) {
// TODO: move this to QueryPlan?
if (offset != null) {
count = Math.max(0, count - offset);
}
if (limit != null) {
count = Math.min(count, limit);
}
return count;
}
/**
* Compute missing properties based on the provided schema and the current
* filter. This allows a format to defer to CQL filtering instead of using
* native (SQL for example) encoding that may result in errors or invalid
* results.
* @param schema non-null schema to evaluate the filter against
* @return non-null Set of any missing properties
*/
public Set<String> missingProperties(Schema schema) {
Set<String> queryProperties = (Set<String>) (filter == null ?
Collections.emptySet() : Filters.properties(filter));
List<Field> f = schema.fields();
for (int i = 0, ii = f.size(); i < ii && !queryProperties.isEmpty(); i++) {
queryProperties.remove(f.get(i).name());
}
return queryProperties;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((bounds == null) ? 0 : bounds.hashCode());
result = prime * result + ((fields == null) ? 0 : fields.hashCode());
result = prime * result + ((filter == null) ? 0 : filter.hashCode());
result = prime * result + ((limit == null) ? 0 : limit.hashCode());
result = prime * result + ((offset == null) ? 0 : offset.hashCode());
result = prime * result
+ ((reproject == null) ? 0 : reproject.hashCode());
result = prime * result
+ ((simplify == null) ? 0 : simplify.hashCode());
//result = prime * result + ((sort == null) ? 0 : sort.hashCode());
result = prime * result
+ ((transaction == null) ? 0 : transaction.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;
VectorQuery other = (VectorQuery) obj;
if (bounds == null) {
if (other.bounds != null)
return false;
} else if (!bounds.equals(other.bounds))
return false;
if (fields == null) {
if (other.fields != null)
return false;
} else if (!fields.equals(other.fields))
return false;
if (filter == null) {
if (other.filter != null)
return false;
} else if (!filter.equals(other.filter))
return false;
if (limit == null) {
if (other.limit != null)
return false;
} else if (!limit.equals(other.limit))
return false;
if (offset == null) {
if (other.offset != null)
return false;
} else if (!offset.equals(other.offset))
return false;
if (reproject == null) {
if (other.reproject != null)
return false;
} else if (!reproject.equals(other.reproject))
return false;
if (simplify == null) {
if (other.simplify != null)
return false;
} else if (!simplify.equals(other.simplify))
return false;
// if (sort == null) {
// if (other.sort != null)
// return false;
// } else if (!sort.equals(other.sort))
// return false;
if (transaction == null) {
if (other.transaction != null)
return false;
} else if (!transaction.equals(other.transaction))
return false;
return true;
}
}