/*******************************************************************************
* Copyright (c) 2015 MITRE
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License, Version 2.0 which
* accompanies this distribution and is available at
* http://www.apache.org/licenses/LICENSE-2.0.txt
******************************************************************************/
package org.locationtech.spatial4j.io;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeCollection;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
/**
* A binary shape format. It is <em>not</em> designed to be a published standard, unlike Well Known
* Binary (WKB). The initial release is simple but it could get more optimized to use fewer bytes or
* to write & read pre-computed index structures.
* <p>
* Immutable and thread-safe.
*/
public class BinaryCodec {
//type 0; reserved for unkonwn/generic; see readCollection
protected static final byte
TYPE_POINT = 1,
TYPE_RECT = 2,
TYPE_CIRCLE = 3,
TYPE_COLL = 4,
TYPE_GEOM = 5;
//TODO support BufferedLineString
protected final SpatialContext ctx;
//This constructor is mandated by SpatialContextFactory
public BinaryCodec(SpatialContext ctx, SpatialContextFactory factory) {
this.ctx = ctx;
}
public Shape readShape(DataInput dataInput) throws IOException {
byte type = dataInput.readByte();
Shape s = readShapeByTypeIfSupported(dataInput, type);
if (s == null)
throw new IllegalArgumentException("Unsupported shape byte "+type);
return s;
}
public void writeShape(DataOutput dataOutput, Shape s) throws IOException {
boolean written = writeShapeByTypeIfSupported(dataOutput, s);
if (!written)
throw new IllegalArgumentException("Unsupported shape "+s.getClass());
}
protected Shape readShapeByTypeIfSupported(DataInput dataInput, byte type) throws IOException {
switch (type) {
case TYPE_POINT: return readPoint(dataInput);
case TYPE_RECT: return readRect(dataInput);
case TYPE_CIRCLE: return readCircle(dataInput);
case TYPE_COLL: return readCollection(dataInput);
default: return null;
}
}
/** Note: writes the type byte even if not supported */
protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s) throws IOException {
byte type = typeForShape(s);
dataOutput.writeByte(type);
return writeShapeByTypeIfSupported(dataOutput, s, type);
//dataOutput.position(dataOutput.position() - 1);//reset putting type
}
protected boolean writeShapeByTypeIfSupported(DataOutput dataOutput, Shape s, byte type) throws IOException {
switch (type) {
case TYPE_POINT: writePoint(dataOutput, (Point) s); break;
case TYPE_RECT: writeRect(dataOutput, (Rectangle) s); break;
case TYPE_CIRCLE: writeCircle(dataOutput, (Circle) s); break;
case TYPE_COLL: writeCollection(dataOutput, (ShapeCollection) s); break;
default:
return false;
}
return true;
}
protected byte typeForShape(Shape s) {
if (s instanceof Point) {
return TYPE_POINT;
} else if (s instanceof Rectangle) {
return TYPE_RECT;
} else if (s instanceof Circle) {
return TYPE_CIRCLE;
} else if (s instanceof ShapeCollection) {
return TYPE_COLL;
} else {
return 0;
}
}
protected double readDim(DataInput dataInput) throws IOException {
return dataInput.readDouble();
}
protected void writeDim(DataOutput dataOutput, double v) throws IOException {
dataOutput.writeDouble(v);
}
public Point readPoint(DataInput dataInput) throws IOException {
return ctx.makePoint(readDim(dataInput), readDim(dataInput));
}
public void writePoint(DataOutput dataOutput, Point pt) throws IOException {
writeDim(dataOutput, pt.getX());
writeDim(dataOutput, pt.getY());
}
public Rectangle readRect(DataInput dataInput) throws IOException {
return ctx.makeRectangle(readDim(dataInput), readDim(dataInput), readDim(dataInput), readDim(dataInput));
}
public void writeRect(DataOutput dataOutput, Rectangle r) throws IOException {
writeDim(dataOutput, r.getMinX());
writeDim(dataOutput, r.getMaxX());
writeDim(dataOutput, r.getMinY());
writeDim(dataOutput, r.getMaxY());
}
public Circle readCircle(DataInput dataInput) throws IOException {
return ctx.makeCircle(readPoint(dataInput), readDim(dataInput));
}
public void writeCircle(DataOutput dataOutput, Circle c) throws IOException {
writePoint(dataOutput, c.getCenter());
writeDim(dataOutput, c.getRadius());
}
public ShapeCollection readCollection(DataInput dataInput) throws IOException {
byte type = dataInput.readByte();
int size = dataInput.readInt();
ArrayList<Shape> shapes = new ArrayList<Shape>(size);
for (int i = 0; i < size; i++) {
if (type == 0) {
shapes.add(readShape(dataInput));
} else {
Shape s = readShapeByTypeIfSupported(dataInput, type);
if (s == null)
throw new InvalidShapeException("Unsupported shape byte "+type);
shapes.add(s);
}
}
return ctx.makeCollection(shapes);
}
public void writeCollection(DataOutput dataOutput, ShapeCollection col) throws IOException {
byte type = (byte) 0;//TODO add type to ShapeCollection
dataOutput.writeByte(type);
dataOutput.writeInt(col.size());
for (int i = 0; i < col.size(); i++) {
Shape s = col.get(i);
if (type == 0) {
writeShape(dataOutput, s);
} else {
boolean written = writeShapeByTypeIfSupported(dataOutput, s, type);
if (!written)
throw new IllegalArgumentException("Unsupported shape type "+s.getClass());
}
}
}
}