/* Copyright (c) 2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.storage.datastream;
import static org.locationtech.geogig.storage.datastream.Varint.readSignedVarInt;
import static org.locationtech.geogig.storage.datastream.Varint.readUnsignedVarInt;
import static org.locationtech.geogig.storage.datastream.Varint.writeSignedVarInt;
import static org.locationtech.geogig.storage.datastream.Varint.writeUnsignedVarInt;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.locationtech.geogig.storage.datastream.DataStreamValueSerializerV2.ValueSerializer;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory;
/**
* This is work in progress and not ready for production. It'll be a serialization format for JTS
* geometries more compact than WKB, and ideally would support different precision models.
*/
class GeometrySerializer implements ValueSerializer {
private static final int POINT = 0x01;
private static final int LINESTRING = 0x02;
private static final int POLYGON = 0x03;
private static final int MULTIPOINT = 0x04;
private static final int MULTILINESTRING = 0x05;
private static final int MULTIPOLYGON = 0x06;
private static final int GEOMETRYCOLLECTION = 0x07;
private static final double FIXED_PRECISION_FACTOR = 1e7;
private static final GeometryFactory GEOMFAC = new GeometryFactory(
new PackedCoordinateSequenceFactory());
private static abstract class GeometryEncoder {
abstract void write(DataOutput out) throws IOException;
abstract Geometry read(DataInput in) throws IOException;
}
private static GeometryEncoder[] ENCODERS = new GeometryEncoder[] {//
};
@Override
public void write(Object obj, final DataOutput out) throws IOException {
final Geometry geom = (Geometry) obj;
final int geometryType = getGeometryType(geom);
final int typeAndMasks = geometryType;
writeUnsignedVarInt(typeAndMasks, out);
geom.apply(new EncodingSequenceFilter(out, true));
}
@Override
public Geometry read(DataInput in) throws IOException {
final int typeAndMasks = readUnsignedVarInt(in);
Geometry geom;
if ((typeAndMasks & POINT) == POINT) {
geom = GEOMFAC.createPoint(EncodingSequenceFilter.readCoordinate(in));
} else if ((typeAndMasks & LINESTRING) == LINESTRING) {
CoordinateSequence cs = EncodingSequenceFilter.read(in);
geom = GEOMFAC.createLineString(cs);
} else {
throw new UnsupportedOperationException();
}
return geom;
}
private static final class EncodingSequenceFilter implements CoordinateSequenceFilter {
private DataOutput out;
private boolean writeLength;
public EncodingSequenceFilter(DataOutput out, boolean writeLength) {
this.out = out;
this.writeLength = writeLength;
}
@Override
public boolean isGeometryChanged() {
return false;
}
@Override
public boolean isDone() {
return false;
}
@Override
public void filter(CoordinateSequence seq, int index) {
double ordinate1 = seq.getOrdinate(index, 0);
double ordinate2 = seq.getOrdinate(index, 1);
int fixedO1 = toFixedPrecision(ordinate1);
int fixedO2 = toFixedPrecision(ordinate2);
try {
if (writeLength) {
writeUnsignedVarInt(seq.size(), out);
writeLength = false;
}
writeSignedVarInt(fixedO1, out);
writeSignedVarInt(fixedO2, out);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
public static CoordinateSequence read(DataInput in) throws IOException {
final int len = readUnsignedVarInt(in);
CoordinateSequence cs = GEOMFAC.getCoordinateSequenceFactory().create(len, 2);
for (int i = 0; i < len; i++) {
cs.setOrdinate(i, 0, toDoublePrecision(readSignedVarInt(in)));
cs.setOrdinate(i, 1, toDoublePrecision(readSignedVarInt(in)));
}
return cs;
}
public static CoordinateSequence readCoordinate(DataInput in) throws IOException {
CoordinateSequence cs = GEOMFAC.getCoordinateSequenceFactory().create(1, 2);
cs.setOrdinate(0, 0, toDoublePrecision(readSignedVarInt(in)));
cs.setOrdinate(0, 1, toDoublePrecision(readSignedVarInt(in)));
return cs;
}
}
private int getGeometryType(Geometry geom) {
Preconditions.checkNotNull(geom, "null geometry");
if (geom instanceof Point)
return POINT;
if (geom instanceof LineString)
return LINESTRING;
if (geom instanceof Polygon)
return POLYGON;
if (geom instanceof MultiPoint)
return MULTIPOINT;
if (geom instanceof MultiLineString)
return MULTILINESTRING;
if (geom instanceof MultiPolygon)
return MULTIPOLYGON;
if (geom instanceof GeometryCollection)
return GEOMETRYCOLLECTION;
throw new IllegalArgumentException("Unknown geometry type: " + geom.getClass());
}
/**
* Converts the requested coordinate from double to fixed precision.
*/
public static int toFixedPrecision(double ordinate) {
int fixedPrecisionOrdinate = (int) Math.round(ordinate * FIXED_PRECISION_FACTOR);
return fixedPrecisionOrdinate;
}
/**
* Converts the requested coordinate from fixed to double precision.
*/
public static double toDoublePrecision(int fixedPrecisionOrdinate) {
double ordinate = fixedPrecisionOrdinate / FIXED_PRECISION_FACTOR;
return ordinate;
}
}