package mil.nga.giat.geowave.adapter.vector.util; import java.util.Map; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import com.vividsolutions.jts.geom.Geometry; import mil.nga.giat.geowave.adapter.vector.stats.FeatureBoundingBoxStatistics; import mil.nga.giat.geowave.adapter.vector.stats.FeatureTimeRangeStatistics; import mil.nga.giat.geowave.adapter.vector.utils.TimeDescriptors; import mil.nga.giat.geowave.core.geotime.GeometryUtils; import mil.nga.giat.geowave.core.geotime.GeometryUtils.GeoConstraintsWrapper; import mil.nga.giat.geowave.core.geotime.store.query.SpatialTemporalQuery; import mil.nga.giat.geowave.core.geotime.store.query.TemporalConstraints; import mil.nga.giat.geowave.core.geotime.store.query.TemporalConstraintsSet; import mil.nga.giat.geowave.core.geotime.store.query.TemporalRange; import mil.nga.giat.geowave.core.geotime.store.statistics.BoundingBoxDataStatistics; import mil.nga.giat.geowave.core.index.ByteArrayId; import mil.nga.giat.geowave.core.store.adapter.statistics.DataStatistics; import mil.nga.giat.geowave.core.store.query.BasicQuery.ConstraintSet; import mil.nga.giat.geowave.core.store.query.BasicQuery.Constraints; public class QueryIndexHelper { private static TemporalRange getStatsRange( final Map<ByteArrayId, DataStatistics<SimpleFeature>> statsMap, final AttributeDescriptor attr ) { final TemporalRange timeRange = new TemporalRange(); if (attr != null) { final FeatureTimeRangeStatistics stat = ((FeatureTimeRangeStatistics) statsMap .get(FeatureTimeRangeStatistics.composeId(attr.getLocalName()))); if (stat != null) { timeRange.setStartTime(stat.getMinTime()); timeRange.setEndTime(stat.getMaxTime()); } } return timeRange; } /** * Clip the provided constraints using the statistics, if available. * * * @param statsMap * @param timeDescriptors * @param constraintsSet * @return */ public static TemporalConstraintsSet clipIndexedTemporalConstraints( final Map<ByteArrayId, DataStatistics<SimpleFeature>> statsMap, final TimeDescriptors timeDescriptors, final TemporalConstraintsSet constraintsSet ) { // TODO: if query range doesn't intersect with the stats, it seems the // constraints are removed or empty - does this make sense? It seems // this can result in open-ended time when it should find no results. if ((timeDescriptors.getEndRange() != null) && (timeDescriptors.getStartRange() != null)) { final String ename = timeDescriptors.getEndRange().getLocalName(); final String sname = timeDescriptors.getStartRange().getLocalName(); if (constraintsSet.hasConstraintsForRange( sname, ename)) { final TemporalRange statsStartRange = getStatsRange( statsMap, timeDescriptors.getStartRange()); final TemporalRange statsEndRange = getStatsRange( statsMap, timeDescriptors.getEndRange()); final TemporalRange fullRange = new TemporalRange( statsStartRange.getStartTime(), statsEndRange.getEndTime()); final TemporalConstraints constraints = constraintsSet.getConstraintsForRange( sname, ename); constraints.replaceWithIntersections(new TemporalConstraints( fullRange, constraints.getName())); constraintsSet.removeAllConstraintsExcept(constraints.getName()); // this should be fixed to handle interwoven range. // specifically look for non-overlapping regions of time return constraintsSet; } } else if ((timeDescriptors.getTime() != null) && constraintsSet.hasConstraintsFor(timeDescriptors.getTime().getLocalName())) { final String name = timeDescriptors.getTime().getLocalName(); final FeatureTimeRangeStatistics stats = ((FeatureTimeRangeStatistics) statsMap .get(FeatureTimeRangeStatistics.composeId(name))); final TemporalConstraints constraints = constraintsSet.getConstraintsFor(name); if (stats != null) { constraints.replaceWithIntersections(new TemporalConstraints( stats.asTemporalRange(), name)); } constraintsSet.removeAllConstraintsExcept(name); return constraintsSet; } return constraintsSet; } /** * Compose temporal constraints given the constraint set and the descriptors * for the index. * * @param timeDescriptors * @param constraintsSet * @return null if the constraints does not have the fields required by the * time descriptors */ public static TemporalConstraints composeRangeTemporalConstraints( final TimeDescriptors timeDescriptors, final TemporalConstraintsSet constraintsSet ) { if ((timeDescriptors.getEndRange() != null) && (timeDescriptors.getStartRange() != null)) { final String ename = timeDescriptors.getEndRange().getLocalName(); final String sname = timeDescriptors.getStartRange().getLocalName(); if (constraintsSet.hasConstraintsForRange( sname, ename)) { return constraintsSet.getConstraintsForRange( sname, ename); } } else if ((timeDescriptors.getTime() != null) && constraintsSet.hasConstraintsFor(timeDescriptors.getTime().getLocalName())) { return constraintsSet.getConstraintsFor(timeDescriptors.getTime().getLocalName()); } return new TemporalConstraints(); } /** * Clip the provided bounded box with the statistics for the index * * @param featureType * @param bbox * @param statsMap * @return */ public static Geometry clipIndexedBBOXConstraints( final SimpleFeatureType featureType, final Geometry bbox, final Map<ByteArrayId, DataStatistics<SimpleFeature>> statsMap ) { final String geoAttrName = featureType.getGeometryDescriptor().getLocalName(); final ByteArrayId statId = FeatureBoundingBoxStatistics.composeId(geoAttrName); final FeatureBoundingBoxStatistics bboxStats = (FeatureBoundingBoxStatistics) statsMap.get(statId); if ((bboxStats != null) && (bbox != null)) { final Geometry geo = bboxStats.composeGeometry(featureType .getGeometryDescriptor() .getType() .getCoordinateReferenceSystem()); return geo.intersection(bbox); } return bbox; } public static ConstraintSet getTimeConstraintsFromIndex( final TimeDescriptors timeDescriptors, final Map<ByteArrayId, DataStatistics<SimpleFeature>> stats ) { if ((timeDescriptors.getEndRange() != null) || (timeDescriptors.getStartRange() != null)) { final FeatureTimeRangeStatistics endRange = (timeDescriptors.getEndRange() != null) ? ((FeatureTimeRangeStatistics) stats .get(FeatureTimeRangeStatistics.composeId(timeDescriptors.getEndRange().getLocalName()))) : null; final FeatureTimeRangeStatistics startRange = (timeDescriptors.getStartRange() != null) ? ((FeatureTimeRangeStatistics) stats .get(FeatureTimeRangeStatistics.composeId(timeDescriptors.getStartRange().getLocalName()))) : null; if ((endRange != null) && (startRange != null)) { return SpatialTemporalQuery.createConstraints( startRange.asTemporalRange().union( endRange.asTemporalRange()), true); } else if (endRange != null) { return SpatialTemporalQuery.createConstraints( endRange.asTemporalRange(), true); } else if (startRange != null) { return SpatialTemporalQuery.createConstraints( startRange.asTemporalRange(), true); } } else if (timeDescriptors.getTime() != null) { final FeatureTimeRangeStatistics timeStat = ((FeatureTimeRangeStatistics) stats .get(FeatureTimeRangeStatistics.composeId(timeDescriptors.getTime().getLocalName()))); if (timeStat != null) { return SpatialTemporalQuery.createConstraints( timeStat.asTemporalRange(), true); } } return new ConstraintSet(); } public static ConstraintSet getBBOXIndexConstraintsFromIndex( final SimpleFeatureType featureType, final Map<ByteArrayId, DataStatistics<SimpleFeature>> statsMap ) { final String geoAttrName = featureType.getGeometryDescriptor().getLocalName(); final ByteArrayId statId = FeatureBoundingBoxStatistics.composeId(geoAttrName); final BoundingBoxDataStatistics<SimpleFeature> bboxStats = (BoundingBoxDataStatistics<SimpleFeature>) statsMap .get(statId); return (bboxStats != null) ? bboxStats.getConstraints() : new ConstraintSet(); } public static TemporalConstraints getTemporalConstraintsForDescriptors( final TimeDescriptors timeDescriptors, final TemporalConstraintsSet timeBoundsSet ) { if ((timeBoundsSet == null) || timeBoundsSet.isEmpty()) { return new TemporalConstraints(); } if ((timeDescriptors.getStartRange() != null) && (timeDescriptors.getEndRange() != null)) { return composeRangeTemporalConstraints( timeDescriptors, timeBoundsSet); } else if ((timeDescriptors.getTime() != null) && timeBoundsSet.hasConstraintsFor(timeDescriptors.getTime().getLocalName())) { return timeBoundsSet.getConstraintsFor(timeDescriptors.getTime().getLocalName()); } return new TemporalConstraints(); } /** * Compose a time constraints. When the provided constraints do not fulfill * the indexed dimensions, compose constraints from statistics. * * * @param featureType * @param timeDescriptors * @param statsMap * @param timeBoundsSet * @return */ public static Constraints composeTimeConstraints( final SimpleFeatureType featureType, final TimeDescriptors timeDescriptors, final Map<ByteArrayId, DataStatistics<SimpleFeature>> statsMap, final TemporalConstraintsSet timeBoundsSet ) { final TemporalConstraints timeBounds = getTemporalConstraintsForDescriptors( timeDescriptors, timeBoundsSet); return (timeBounds != null) && !timeBounds.isEmpty() ? SpatialTemporalQuery.createConstraints( timeBounds, false) : new Constraints( getTimeConstraintsFromIndex( timeDescriptors, statsMap)); } /** * If composed constraints matched statistics constraints, are empty or * null, then return empty constraint set. * * * @param featureType * @param timeDescriptors * @param statsMap * @param timeBoundsSet * @return */ public static Constraints composeTimeBoundedConstraints( final SimpleFeatureType featureType, final TimeDescriptors timeDescriptors, final Map<ByteArrayId, DataStatistics<SimpleFeature>> statsMap, final TemporalConstraintsSet timeBoundsSet ) { if ((timeBoundsSet == null) || timeBoundsSet.isEmpty() || !timeDescriptors.hasTime()) { return new Constraints(); } final TemporalConstraints boundsTemporalConstraints = QueryIndexHelper.getTemporalConstraintsForDescriptors( timeDescriptors, timeBoundsSet); if (boundsTemporalConstraints.isEmpty()) { return new Constraints(); } final Constraints indexTimeConstraints = new Constraints( QueryIndexHelper.getTimeConstraintsFromIndex( timeDescriptors, statsMap)); final Constraints boundsTimeConstraints = SpatialTemporalQuery.createConstraints( boundsTemporalConstraints, false); return (boundsTimeConstraints.matches(indexTimeConstraints)) ? new Constraints() : boundsTimeConstraints; } /** * If composed constraints matched statistics constraints, are empty or * null, then return empty constraint set * * * @param featureType * @param timeDescriptors * @param statsMap * @param timeBoundsSet * @return */ public static GeoConstraintsWrapper composeGeometricConstraints( final SimpleFeatureType featureType, final Map<ByteArrayId, DataStatistics<SimpleFeature>> statsMap, final Geometry jtsBounds ) { if (jtsBounds == null) { return new GeoConstraintsWrapper( new Constraints(), true, null); } final GeoConstraintsWrapper geoConstraints = GeometryUtils.basicGeoConstraintsWrapperFromGeometry(jtsBounds); final Constraints statsConstraints = new Constraints( getBBOXIndexConstraintsFromIndex( featureType, statsMap)); if (geoConstraints.getConstraints().matches( statsConstraints)) { return new GeoConstraintsWrapper( new Constraints(), geoConstraints.isConstraintsMatchGeometry(), jtsBounds); } return geoConstraints; } /** * Compose a query from the set of constraints. When the provided * constraints do not fulfill the indexed dimensions, compose constraints * from statistics. * * @param featureType * @param timeDescriptors * @param statsMap * @param jtsBounds * @param timeBoundsSet * @return */ public static Constraints composeConstraints( final SimpleFeatureType featureType, final TimeDescriptors timeDescriptors, final Map<ByteArrayId, DataStatistics<SimpleFeature>> statsMap, final Geometry jtsBounds, final TemporalConstraintsSet timeBoundsSet ) { final Constraints timeConstraints = composeTimeConstraints( featureType, timeDescriptors, statsMap, timeBoundsSet); final GeoConstraintsWrapper geoConstraints = composeGeometricConstraints( featureType, statsMap, jtsBounds); return timeConstraints.merge(geoConstraints.getConstraints()); } }