package mil.nga.giat.geowave.analytic.partitioner;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.measure.quantity.Length;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.mapreduce.JobContext;
import org.geotools.referencing.CRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import mil.nga.giat.geowave.analytic.GeometryCalculations;
import mil.nga.giat.geowave.analytic.PropertyManagement;
import mil.nga.giat.geowave.analytic.ScopedJobConfiguration;
import mil.nga.giat.geowave.analytic.extract.DimensionExtractor;
import mil.nga.giat.geowave.analytic.extract.SimpleFeatureGeometryExtractor;
import mil.nga.giat.geowave.analytic.param.ClusteringParameters;
import mil.nga.giat.geowave.analytic.param.ExtractParameters;
import mil.nga.giat.geowave.analytic.param.GlobalParameters;
import mil.nga.giat.geowave.analytic.param.ParameterEnum;
import mil.nga.giat.geowave.analytic.param.PartitionParameters;
import mil.nga.giat.geowave.core.geotime.index.dimension.LatitudeDefinition;
import mil.nga.giat.geowave.core.geotime.index.dimension.LongitudeDefinition;
import mil.nga.giat.geowave.core.index.dimension.NumericDimensionDefinition;
import mil.nga.giat.geowave.core.index.sfc.data.BasicNumericDataset;
import mil.nga.giat.geowave.core.index.sfc.data.MultiDimensionalNumericData;
import mil.nga.giat.geowave.core.index.sfc.data.NumericData;
import mil.nga.giat.geowave.core.index.sfc.data.NumericRange;
import mil.nga.giat.geowave.core.store.dimension.NumericDimensionField;
import mil.nga.giat.geowave.core.store.index.CommonIndexModel;
/*
* Calculates distance use orthodromic distance to calculate the bounding box around each
* point.
*
* The approach is slow and more accurate, resulting in more partitions of smaller size.
* The class requires {@link CoordinateReferenceSystem} for the distance calculation
* and {@link DimensionExtractor} to extract geometries and other dimensions.
*
* The order of distances provided must match the order or dimensions extracted from the dimension
* extractor.
*/
public class OrthodromicDistancePartitioner<T> extends
AbstractPartitioner<T> implements
Partitioner<T>,
java.io.Serializable
{
/**
*
*/
private static final long serialVersionUID = 1L;
final static Logger LOGGER = LoggerFactory.getLogger(OrthodromicDistancePartitioner.class);
private Unit<Length> geometricDistanceUnit = SI.METER;
private String crsName;
private transient CoordinateReferenceSystem crs = null;
private transient GeometryCalculations calculator;
protected DimensionExtractor<T> dimensionExtractor;
private int latDimensionPosition;
private int longDimensionPosition;
public OrthodromicDistancePartitioner() {}
public OrthodromicDistancePartitioner(
final CoordinateReferenceSystem crs,
final CommonIndexModel indexModel,
final DimensionExtractor<T> dimensionExtractor,
final double[] distancePerDimension,
final Unit<Length> geometricDistanceUnit ) {
super(
distancePerDimension);
this.crs = crs;
this.crsName = crs.getIdentifiers().iterator().next().toString();
this.geometricDistanceUnit = geometricDistanceUnit;
this.dimensionExtractor = dimensionExtractor;
initIndex(
indexModel,
distancePerDimension);
}
@Override
protected NumericDataHolder getNumericData(
final T entry ) {
final NumericDataHolder numericDataHolder = new NumericDataHolder();
final Geometry entryGeometry = dimensionExtractor.getGeometry(entry);
final double otherDimensionData[] = dimensionExtractor.getDimensions(entry);
numericDataHolder.primary = getNumericData(
entryGeometry.getEnvelope(),
otherDimensionData);
final List<Geometry> geometries = getGeometries(
entryGeometry.getCentroid().getCoordinate(),
getDistancePerDimension());
final MultiDimensionalNumericData[] values = new MultiDimensionalNumericData[geometries.size()];
int i = 0;
for (final Geometry geometry : geometries) {
values[i++] = getNumericData(
geometry.getEnvelope(),
otherDimensionData);
}
numericDataHolder.expansion = values;
return numericDataHolder;
}
private MultiDimensionalNumericData getNumericData(
final Geometry geometry,
final double[] otherDimensionData ) {
final NumericDimensionField<?>[] dimensionFields = getIndex().getIndexModel().getDimensions();
final NumericData[] numericData = new NumericData[dimensionFields.length];
final double[] distancePerDimension = getDistancePerDimension();
int otherIndex = 0;
for (int i = 0; i < dimensionFields.length; i++) {
final double minValue = (i == this.longDimensionPosition) ? geometry.getEnvelopeInternal().getMinX()
: (i == this.latDimensionPosition ? geometry.getEnvelopeInternal().getMinY()
: otherDimensionData[otherIndex] - distancePerDimension[i]);
final double maxValue = (i == this.longDimensionPosition) ? geometry.getEnvelopeInternal().getMaxX()
: (i == this.latDimensionPosition ? geometry.getEnvelopeInternal().getMaxY()
: otherDimensionData[otherIndex] + distancePerDimension[i]);
if ((i != this.longDimensionPosition) && (i != latDimensionPosition)) {
otherIndex++;
}
numericData[i] = new NumericRange(
minValue,
maxValue);
}
return new BasicNumericDataset(
numericData);
}
private static int findLongitude(
final CommonIndexModel indexModel ) {
return indexOf(
indexModel.getDimensions(),
LongitudeDefinition.class);
}
private static int findLatitude(
final CommonIndexModel indexModel ) {
return indexOf(
indexModel.getDimensions(),
LatitudeDefinition.class);
}
private static int indexOf(
final NumericDimensionField<?> fields[],
final Class<? extends NumericDimensionDefinition> clazz ) {
for (int i = 0; i < fields.length; i++) {
if (clazz.isInstance(fields[i].getBaseDefinition())) {
return i;
}
}
return -1;
}
private List<Geometry> getGeometries(
final Coordinate coordinate,
final double[] distancePerDimension ) {
return getCalculator().buildSurroundingGeometries(
new double[] {
distancePerDimension[longDimensionPosition],
distancePerDimension[latDimensionPosition]
},
geometricDistanceUnit == null ? SI.METER : geometricDistanceUnit,
coordinate);
}
private GeometryCalculations getCalculator() {
if (calculator == null) {
// this block would only occur in test or in failed initialization
if (crs == null) {
try {
crs = CRS.decode(
crsName,
true);
}
catch (final FactoryException e) {
LOGGER.error(
"CRS not providd and default EPSG:4326 cannot be instantiated",
e);
throw new RuntimeException(
e);
}
}
calculator = new GeometryCalculations(
crs);
}
return calculator;
}
@Override
protected void initIndex(
final CommonIndexModel indexModel,
final double[] distancePerDimension ) {
longDimensionPosition = findLongitude(indexModel);
latDimensionPosition = findLatitude(indexModel);
final List<Geometry> geos = getGeometries(
new Coordinate(
0,
0),
distancePerDimension);
final Envelope envelope = geos.get(
0).getEnvelopeInternal();
// set up the distances based on geometry (orthodromic distance)
final double[] distancePerDimensionForIndex = new double[distancePerDimension.length];
for (int i = 0; i < distancePerDimension.length; i++) {
distancePerDimensionForIndex[i] = (i == longDimensionPosition) ? envelope.getWidth() / 2.0
: (i == latDimensionPosition ? envelope.getHeight() / 2.0 : distancePerDimension[i]);
LOGGER.info(
"Dimension size {} is {} ",
i,
distancePerDimensionForIndex[i]);
}
super.initIndex(
indexModel,
distancePerDimensionForIndex);
}
@SuppressWarnings("unchecked")
@Override
public void initialize(
final JobContext context,
final Class<?> scope )
throws IOException {
this.initialize(
context.getConfiguration(),
scope);
}
public void initialize(
final Configuration configuration,
final Class<?> scope )
throws IOException {
initialize(new ScopedJobConfiguration(
configuration,
scope));
}
@Override
public void initialize(
ScopedJobConfiguration config )
throws IOException {
crsName = config.getString(
GlobalParameters.Global.CRS_ID,
"EPSG:4326");
try {
crs = CRS.decode(
crsName,
true);
}
catch (final FactoryException e) {
throw new IOException(
"Cannot find CRS " + crsName,
e);
}
try {
dimensionExtractor = config.getInstance(
ExtractParameters.Extract.DIMENSION_EXTRACT_CLASS,
DimensionExtractor.class,
SimpleFeatureGeometryExtractor.class);
}
catch (final Exception ex) {
throw new IOException(
"Cannot find class for " + ExtractParameters.Extract.DIMENSION_EXTRACT_CLASS.toString(),
ex);
}
final String distanceUnit = config.getString(
PartitionParameters.Partition.GEOMETRIC_DISTANCE_UNIT,
"m");
this.geometricDistanceUnit = (Unit<Length>) Unit.valueOf(distanceUnit);
super.initialize(config);
}
@Override
public Collection<ParameterEnum<?>> getParameters() {
final Set<ParameterEnum<?>> params = new HashSet<ParameterEnum<?>>();
params.addAll(super.getParameters());
params.addAll(Arrays.asList(new ParameterEnum<?>[] {
PartitionParameters.Partition.GEOMETRIC_DISTANCE_UNIT,
ExtractParameters.Extract.DIMENSION_EXTRACT_CLASS
}));
return params;
}
@Override
public void setup(
final PropertyManagement runTimeProperties,
final Class<?> scope,
final Configuration configuration ) {
super.setup(
runTimeProperties,
scope,
configuration);
final ParameterEnum[] params = new ParameterEnum[] {
GlobalParameters.Global.CRS_ID,
ExtractParameters.Extract.DIMENSION_EXTRACT_CLASS,
PartitionParameters.Partition.GEOMETRIC_DISTANCE_UNIT
};
runTimeProperties.setConfig(
params,
configuration,
scope);
}
}