com.rgi.geopackage.features.BinaryHeader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swagd Show documentation
Show all versions of swagd Show documentation
SWAGD: Software to Aggregate Geospatial Data
The newest version!
/* The MIT License (MIT)
*
* Copyright (c) 2015 Reinventing Geospatial, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.rgi.geopackage.features;
import com.rgi.geopackage.features.geometry.Geometry;
import com.rgi.geopackage.features.geometry.m.EnvelopeM;
import com.rgi.geopackage.features.geometry.xy.Envelope;
import com.rgi.geopackage.features.geometry.z.EnvelopeZ;
import com.rgi.geopackage.features.geometry.zm.EnvelopeZM;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
/**
* Java implementation of the GeoPackage Binary
* Header
*
* @author Luke Lambert
*/
public class BinaryHeader
{
/**
* Constructor
*
* @param bytes
* Bytes that should comprise the GeoPackage Binary Header
*/
protected BinaryHeader(final byte[] bytes)
{
if(bytes == null)
{
throw new IllegalArgumentException("Byte buffer may not be null");
}
if(bytes.length < 8)
{
throw new IllegalArgumentException("Byte buffer must be at least 8 bytes to contain a valid GeoPackage geometry binary header");
}
if(bytes[0] != magic[0] ||
bytes[1] != magic[1])
{
throw new IllegalArgumentException("The first two bytes of a GeoPackage geometry binary header must be 'G', 'P'");
}
this.version = bytes[2];
this.flags = bytes[3];
// read flags
this.binaryType = BinaryType.type(bytes[3]);
this.contents = (bytes[3] & Contents.Empty.getBitMask()) > 0 ? Contents.Empty : Contents.NotEmpty; // TODO add a 'from bitmask' method in Contents?
this.byteOrder = ((this.flags & 1) == 0) ? ByteOrder.BIG_ENDIAN
: ByteOrder.LITTLE_ENDIAN;
final ByteBuffer srsIdByteBuffer = ByteBuffer.wrap(bytes, 4, 4); // Bytes 5->9 are int32 srs_id
srsIdByteBuffer.order(this.byteOrder);
this.spatialReferenceSystemIdentifier = srsIdByteBuffer.getInt();
this.envelopeContentsIndicator = EnvelopeContentsIndicator.fromCode((this.flags & 0b00001110) >> 1);
this.byteSize = 2 + // 2 bytes for the 'magic' header
1 + // 1 byte for version
1 + // 1 byte for flags
4 + // 4 bytes (int32) for the srs id
(8 * this.envelopeContentsIndicator.getArraySize()); // 8 bytes per double, array size number of doubles
if(bytes.length < this.byteSize)
{
throw new IllegalArgumentException("Byte array length is shorter than the envelope array size would indicate");
}
this.envelopeArray = getHeaderEnvelopeDoubles(bytes,
this.byteOrder,
this.envelopeContentsIndicator.getArraySize());
}
/**
* Constructor
*
* @param version
* 8-bit unsigned integer, 0 = version 1
* @param binaryType
* Standard or Extended
* @param contents
* Whether or not the geometry is "empty"
* @param byteOrder
* Order of the header's bytes
* @param spatialReferenceSystemIdentifier
* Spatial reference system identifier for the geometry. Should
* match the identifier in the contents table.
* @param envelopeContentsIndicator
* Indicator of the envelope array's contents (empty, not
* empty, and dimensionality)
* @param envelopeArray
* Geometry envelopeArray. Size depends on the envelopeArray
* contents indicator. Elements are listed min, max, and xyzm
* order.
*/
protected BinaryHeader(final byte version,
final BinaryType binaryType,
final Contents contents,
final ByteOrder byteOrder,
final int spatialReferenceSystemIdentifier,
final EnvelopeContentsIndicator envelopeContentsIndicator,
final double[] envelopeArray)
{
if(binaryType == null)
{
throw new IllegalArgumentException("Binary type may not be null");
}
if(contents == null)
{
throw new IllegalArgumentException("Contents enumeration may not be null");
}
if(byteOrder == null)
{
throw new IllegalArgumentException("Byte order may not be null");
}
if(envelopeContentsIndicator == null)
{
throw new IllegalArgumentException("Envelope contents indicator may not be null");
}
if(envelopeArray == null)
{
throw new IllegalArgumentException("Envelope may not be null. Use Envelope.Empty to represent an empty envelope array.");
}
if(envelopeArray.length != envelopeContentsIndicator.getArraySize())
{
throw new IllegalArgumentException("Envelope content indicator does not agree with the length of the envelope rray");
}
this.version = version;
this.binaryType = binaryType;
this.contents = contents;
this.byteOrder = byteOrder;
this.spatialReferenceSystemIdentifier = spatialReferenceSystemIdentifier;
this.envelopeContentsIndicator = envelopeContentsIndicator;
this.envelopeArray = envelopeArray.clone();
@SuppressWarnings("NumericCastThatLosesPrecision")
final int envelopeContentsMask = (byte)(this.envelopeContentsIndicator.getCode() << 1);
//noinspection NumericCastThatLosesPrecision
this.flags = (byte)(this.binaryType.getBitMask() |
this.contents.getBitMask() |
envelopeContentsMask |
(byteOrder.equals(ByteOrder.BIG_ENDIAN) ? 0 : 1));
this.byteSize = 2 + // 2 bytes for the 'magic' header
1 + // 1 byte for version
1 + // 1 byte for flags
4 + // 4 bytes (int32) for the srs id
(8 * this.envelopeContentsIndicator.getArraySize()); // 8 bytes per double, array size number of doubles
}
@Override
public boolean equals(final Object obj)
{
if(this == obj)
{
return true;
}
if(obj == null || this.getClass() != obj.getClass())
{
return false;
}
final BinaryHeader other = (BinaryHeader)obj;
return this.version == other.version &&
this.spatialReferenceSystemIdentifier == other.spatialReferenceSystemIdentifier &&
this.flags == other.flags &&
this.byteSize == other.byteSize &&
this.binaryType == other.binaryType &&
this.contents == other.contents &&
this.byteOrder .equals(other.byteOrder) &&
this.envelopeContentsIndicator == other.envelopeContentsIndicator &&
Arrays.equals(this.getEnvelopeArray(), other.getEnvelopeArray());
}
@Override
public int hashCode()
{
int result = (int) this.version;
result = 31 * result + this.binaryType.hashCode();
result = 31 * result + this.contents.hashCode();
result = 31 * result + this.byteOrder.hashCode();
result = 31 * result + this.spatialReferenceSystemIdentifier;
result = 31 * result + this.envelopeContentsIndicator.hashCode();
result = 31 * result + Arrays.hashCode(this.getEnvelopeArray());
result = 31 * result + (int)this.flags;
result = 31 * result + this.byteSize;
return result;
}
public byte getVersion()
{
return this.version;
}
public BinaryType getBinaryType()
{
return this.binaryType;
}
public Contents getContents()
{
return this.contents;
}
public ByteOrder getByteOrder()
{
return this.byteOrder;
}
public int getSpatialReferenceSystemIdentifier()
{
return this.spatialReferenceSystemIdentifier;
}
public EnvelopeContentsIndicator getEnvelopeContentsIndicator()
{
return this.envelopeContentsIndicator;
}
public double[] getEnvelopeArray()
{
return this.envelopeArray.clone();
}
public Envelope getEnvelope()
{
switch(this.envelopeContentsIndicator)
{
case NoEnvelope: return null;
case Xy: return new Envelope(this.envelopeArray[0], // min x
this.envelopeArray[2], // min y
this.envelopeArray[1], // max x
this.envelopeArray[3]); // max y
case Xyz: return new EnvelopeZ(this.envelopeArray[0], // min x
this.envelopeArray[2], // min y
this.envelopeArray[4], // min z
this.envelopeArray[1], // max y
this.envelopeArray[3], // max y
this.envelopeArray[5]); // max z
case Xym: return new EnvelopeM(this.envelopeArray[0], // min x
this.envelopeArray[2], // min y
this.envelopeArray[4], // min m
this.envelopeArray[1], // max y
this.envelopeArray[3], // max y
this.envelopeArray[5]); // max m
case Xyzm: return new EnvelopeZM(this.envelopeArray[0], // min x
this.envelopeArray[2], // min y
this.envelopeArray[4], // min z
this.envelopeArray[6], // min m
this.envelopeArray[1], // max y
this.envelopeArray[3], // max z
this.envelopeArray[5], // max z
this.envelopeArray[7]); // max m
}
return null;
}
public byte getFlags()
{
return this.flags;
}
public int getByteSize()
{
return this.byteSize;
}
/**
* Writes the binary header to the supplied byte buffer
*
* @param byteOutputStream
* Destination for the bytes of the header
*/
public void writeBytes(final ByteOutputStream byteOutputStream)
{
if(byteOutputStream == null)
{
throw new IllegalArgumentException("Byte buffer may not be null or read only");
}
// http://www.geopackage.org/spec/#gpb_spec
byteOutputStream.setByteOrder(this.byteOrder);
byteOutputStream.write(BinaryHeader.magic);
byteOutputStream.write(this.version);
byteOutputStream.write(this.flags);
byteOutputStream.write(this.spatialReferenceSystemIdentifier);
for(final double envelopeBound : this.envelopeArray)
{
byteOutputStream.write(envelopeBound);
}
}
/**
* Constructs a header from the supplied arguments, and writes it to a
* byte buffer. Currently there's no way to skip writing the envelopeArray.
*
* @param byteOutputStream
* Destination for the bytes of the header
* @param geometry
* Used to determine the envelopeArray, contents (empty or not), and
* the geometry's binary type
* @param spatialReferenceSystemIdentifier
* Spatial reference system identifier for the geometry
*/
public static void writeBytes(final ByteOutputStream byteOutputStream,
final Geometry geometry,
final int spatialReferenceSystemIdentifier)
{
if(geometry == null)
{
throw new IllegalArgumentException("Geometry may not be null");
}
final Envelope envelope = geometry.createEnvelope();
new BinaryHeader(defaultVersion,
BinaryType.fromGeometryTypeName(geometry.getGeometryTypeName()),
geometry.getContents(),
defaultByteOrder,
spatialReferenceSystemIdentifier,
envelope.getContentsIndicator(),
envelope.toArray()).writeBytes(byteOutputStream);
}
private static double[] getHeaderEnvelopeDoubles(final byte[] header,
final ByteOrder byteOrder,
final int numberOfDoubles)
{
final ByteBuffer byteBuffer = ByteBuffer.wrap(header,
8, // Envelope starts after the first 8 bytes
8*numberOfDoubles); // 8 bytes per double
byteBuffer.order(byteOrder);
final double[] envelope = new double[numberOfDoubles];
for(int x = 0; x < numberOfDoubles; ++x)
{
envelope[x] = byteBuffer.getDouble();
}
return envelope;
}
private static final byte defaultVersion = (byte)0; // Confusingly, 0 = "version 1", see: http://www.geopackage.org/spec/#gpb_spec
private static final ByteOrder defaultByteOrder = ByteOrder.BIG_ENDIAN; // Java default (?), also the network byte order
private static final byte[] magic = {(byte)71, // 'G'
(byte)80 // 'P'
};
private final byte version; // This is an *unsigned* value, regardless of Java's interpretation
private final BinaryType binaryType;
private final Contents contents;
private final ByteOrder byteOrder; // NOTE: this applies *only* to the bytes of the GeoPackage binary header. The byte order of the well known binary that follows the header is specified by the first byte of its contents: 0 - big endian, 1 - little endian
private final int spatialReferenceSystemIdentifier;
private final EnvelopeContentsIndicator envelopeContentsIndicator;
private final double[] envelopeArray;
private final byte flags;
private final int byteSize;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy