package mil.nga.giat.geowave.adapter.vector;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.avro.io.BinaryDecoder;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.specific.SpecificDatumReader;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import com.google.common.base.Preconditions;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKBWriter;
import mil.nga.giat.geowave.adapter.vector.avro.AttributeValues;
import mil.nga.giat.geowave.adapter.vector.avro.AvroSimpleFeature;
import mil.nga.giat.geowave.adapter.vector.avro.FeatureDefinition;
import mil.nga.giat.geowave.adapter.vector.plugin.GeoWaveGTDataStore;
import mil.nga.giat.geowave.core.store.data.field.FieldReader;
import mil.nga.giat.geowave.core.store.data.field.FieldUtils;
import mil.nga.giat.geowave.core.store.data.field.FieldWriter;
public class AvroFeatureUtils
{
private static final WKBWriter WKB_WRITER = new WKBWriter(
3);
private static final DecoderFactory DECODER_FACTORY = DecoderFactory.get();
private static final SpecificDatumReader<AvroSimpleFeature> DATUM_READER = new SpecificDatumReader<AvroSimpleFeature>();
private static final WKBReader WKB_READER = new WKBReader();
private AvroFeatureUtils() {}
/**
* Add the attributes, types and classifications for the SimpleFeatureType
* to the provided FeatureDefinition
*
* @param fd
* - existing Feature Definition (or new one if null)
* @param sft
* - SimpleFeatureType of the simpleFeature being serialized
* @param defaultClassifications
* - map of attribute names to classification
* @param defaultClassification
* - default classification if one could not be found in the map
* @return
* @throws IOException
*/
public static FeatureDefinition buildFeatureDefinition(
FeatureDefinition fd,
final SimpleFeatureType sft,
final Map<String, String> defaultClassifications,
final String defaultClassification )
throws IOException {
if (fd == null) {
fd = new FeatureDefinition();
}
fd.setFeatureTypeName(sft.getTypeName());
final List<String> attributes = new ArrayList<String>(
sft.getAttributeCount());
final List<String> types = new ArrayList<String>(
sft.getAttributeCount());
final List<String> classifications = new ArrayList<String>(
sft.getAttributeCount());
for (final AttributeDescriptor attr : sft.getAttributeDescriptors()) {
final String localName = attr.getLocalName();
attributes.add(localName);
types.add(attr.getType().getBinding().getCanonicalName());
classifications.add(getClassification(
localName,
defaultClassifications,
defaultClassification));
}
fd.setAttributeNames(attributes);
fd.setAttributeTypes(types);
fd.setAttributeDefaultClassifications(classifications);
return fd;
}
/**
* If a classification exists for this attribute name then use it If not
* then use the provided default classification
*
* @param localName
* - attribute name
* @param defaultClassifications
* - map of attribute names to classification
* @param defaultClassification
* - default classification to use if one is not mapped for the
* name provided
* @return
* @throws IOException
*/
private static String getClassification(
final String localName,
final Map<String, String> defaultClassifications,
final String defaultClassification )
throws IOException {
String classification;
if ((defaultClassifications != null) && defaultClassifications.containsKey(localName)) {
classification = defaultClassifications.get(localName);
}
else {
classification = defaultClassification;
}
if (classification == null) {
throw new IOException(
"No default classification was provided, and no classification for: '" + localName
+ "' was provided");
}
return classification;
}
/**
* Create an AttributeValue from the SimpleFeature's attributes
*
* @param sf
* @param sft
* @return
*/
synchronized public static AttributeValues buildAttributeValue(
final SimpleFeature sf,
final SimpleFeatureType sft ) {
final AttributeValues attributeValue = new AttributeValues();
final List<ByteBuffer> values = new ArrayList<ByteBuffer>(
sft.getAttributeCount());
attributeValue.setFid(sf.getID());
for (final AttributeDescriptor attr : sft.getAttributeDescriptors()) {
final Object o = sf.getAttribute(attr.getLocalName());
byte[] bytes;
if (o instanceof Geometry) {
bytes = WKB_WRITER.write((Geometry) o);
}
else {
final FieldWriter fw = FieldUtils.getDefaultWriterForClass(attr.getType().getBinding());
bytes = fw.writeField(o);
}
values.add(ByteBuffer.wrap(bytes));
}
attributeValue.setValues(values);
return attributeValue;
}
/***
* Deserialize byte array into an AvroSimpleFeature then convert to a
* SimpleFeature
*
* @param avroData
* serialized bytes of a AvroSimpleFeature
* @return Collection of GeoTools SimpleFeature instances.
* @throws IOException
* @throws ClassNotFoundException
* @throws ParseException
*/
synchronized public static SimpleFeature deserializeAvroSimpleFeature(
final byte[] avroData )
throws IOException,
ClassNotFoundException,
ParseException {
// Deserialize
final AvroSimpleFeature sfc = deserializeASF(
avroData,
null);
final FeatureDefinition featureDefinition = sfc.getFeatureType();
return avroSimpleFeatureToGTSimpleFeature(
avroFeatureDefinitionToGTSimpleFeatureType(featureDefinition),
featureDefinition.getAttributeTypes(),
sfc.getValue());
}
public static SimpleFeatureType avroFeatureDefinitionToGTSimpleFeatureType(
final FeatureDefinition featureDefinition )
throws ClassNotFoundException {
final SimpleFeatureTypeBuilder sftb = new SimpleFeatureTypeBuilder();
sftb.setCRS(GeoWaveGTDataStore.DEFAULT_CRS);
sftb.setName(featureDefinition.getFeatureTypeName());
final List<String> featureTypes = featureDefinition.getAttributeTypes();
final List<String> featureNames = featureDefinition.getAttributeNames();
for (int i = 0; i < featureDefinition.getAttributeNames().size(); i++) {
final String type = featureTypes.get(i);
final String name = featureNames.get(i);
final Class<?> c = Class.forName(type);
sftb.add(
name,
c);
}
return sftb.buildFeatureType();
}
public static SimpleFeature avroSimpleFeatureToGTSimpleFeature(
final SimpleFeatureType type,
final List<String> attributeTypes,
final AttributeValues attributeValues )
throws IOException,
ClassNotFoundException,
ParseException {
// Convert
SimpleFeature simpleFeature;
final SimpleFeatureBuilder sfb = new SimpleFeatureBuilder(
type);
// null values should still take a place in the array - check
Preconditions.checkArgument(attributeTypes.size() == attributeValues.getValues().size());
for (int i = 0; i < attributeValues.getValues().size(); i++) {
final ByteBuffer val = attributeValues.getValues().get(
i);
if (attributeTypes.get(
i).equals(
"com.vividsolutions.jts.geom.Geometry")) {
sfb.add(WKB_READER.read(val.array()));
}
else {
final FieldReader<?> fr = FieldUtils.getDefaultReaderForClass(Class.forName(attributeTypes.get(i)));
sfb.add(fr.readField(val.array()));
}
}
simpleFeature = sfb.buildFeature(attributeValues.getFid());
return simpleFeature;
}
/***
* Deserialize byte stream into an AvroSimpleFeature
*
* @param avroData
* serialized bytes of AvroSimpleFeature
* @param avroObjectToReuse
* null or AvroSimpleFeature instance to be re-used. If null a
* new object will be allocated.
* @return instance of AvroSimpleFeature with values parsed from avroData
* @throws IOException
*/
private static AvroSimpleFeature deserializeASF(
final byte[] avroData,
AvroSimpleFeature avroObjectToReuse )
throws IOException {
final BinaryDecoder decoder = DECODER_FACTORY.binaryDecoder(
avroData,
null);
if (avroObjectToReuse == null) {
avroObjectToReuse = new AvroSimpleFeature();
}
DATUM_READER.setSchema(avroObjectToReuse.getSchema());
return DATUM_READER.read(
avroObjectToReuse,
decoder);
}
}