TWKB编码
"Tiny Well-known Binary" or "TWKB"
Version | Release |
0.23 | May 1, 2015 |
Abstract
TWKB is a multi-purpose format for serializing vector geometry data into a byte buffer, with an emphasis on minimizing size of the buffer.
Why not WKB?
The original OGC "well-known binary" format is a simple format, and is capable of easily representing complex OGC geometries like nested collections, but it has two important drawbacks for use as a production serialization:
- it is not aligned, so it doesn't support efficient direct memory access; and,
- it uses IEEE doubles as the coordinate storage format, so for data with lots of spatially adjacent coordinates (basically, all GIS data) it wastes a lot of space on redundant specification of coordinate information.
A new serialization format can address the problem of alignment, or the problem of size, but not both. Given that most current client/server perfomance issues are bottlenecked on network transport times, TWKB concentrates on solving the problem of serialization size.
Basic Principles
TWKB applies the following principles:
- Only store the absolute position once, and store all other positions as delta values relative to the preceding position.
- Only use as much address space as is necessary for any given value. Practically this means that "variable length integers" or "varints" are used throughout the specification for storing values in any situation where numbers greater than 128 might be encountered.
Structure
Standard Attributes
Every TWKB geometry contains standard attributes at the top of the object.
- A type number and precision byte to describe the OGC geometry type.
- A metadata header to indicate which optional attributes to expect, and the storage precision of all coordinates in the geometry.
- An optional extended dimension byte with information about existence and precision of Z & M dimensions.
- An optional size in bytes of the object.
- An optional bounding box of the geometry.
- An optional unique identifier array of sub-components for multi-geometries.
Type & Precision
Size: 1 byte, holding geometry type and number of dimensions
The type-byte stores both the geometry type, and the dimensionality of the coordinates of the geometry.
Bits | Role | Purpose |
1-4 | Unsigned Integer | Geometry type |
5-8 | Signed Integer | Geometry precision |
- Bits 1-4 store the geometry type (there is space for 16 and we currently use 7):
- 1 Point
- 2 Linestring
- 3 Polygon
- 4 MultiPoint
- 5 MultiLinestring
- 6 MultiPolygon
- 7 GeometryCollection
- Bits 5-8 store the "precision", which refers to the number of base-10 decimal places stored.
- A positive precision implies retaining information to the right of the decimal place
- 41231.1231 at precision=2 would store 41231.12
- 41231.1231 at precision=1 would store 41231.1
- 41231.1231 at precision=0 would store 41231
- A negative precision implies rounding up to the left of the decimal place
- 41231.1231 at precision=-1 would store 41230
- 41231.1231 at precision=-2 would store 41200
- A positive precision implies retaining information to the right of the decimal place
In order to support negative precisions, the precision number should be stored using zig-zag encoding (see "ZigZag Encode" below).
- The geometry type can be read by masking out the lower four bits (type & 0x0F).
- The precision can be read by masking out the top four bits ((type & 0xF0) >> 4).
Metadata Header
Size: 1 byte
The metadata byte of TWKB is mandatory, and encodes the following information:
Bits | Role | Purpose |
1 | Boolean | Is there a bounding box? |
2 | Boolean | Is there a size attribute? |
3 | Boolean | Is there an ID list? |
4 | Boolean | Is there extended precision information? |
5 | Boolean | Is this an empty geometry? |
6-8 | Boolean | Unused |
Extended Dimensions [Optional]
Size: 1 byte
For some coordinate reference systems, and dimension combinations, it makes no sense to store all dimensions using the same precision.
For example, for data with X/Y/Z/T dimensions, using a geographic coordinate system, it might make sense to store the X/Y (longitude/latitude) dimensions with precision of 6 (about 10 cm), the Z with a precision of 1 (also 10 cm), and the T with a precision of 0 (whole seconds). A single precision number cannot handle this case, so the extended precision slot is optionally available for it.
The extended precision byte holds:
Bits | Role | Purpose |
1 | Boolean | Geometry has Z coordinates? |
2 | Boolean | Geometry has M coordinates? |
3-5 | Unsigned Integer | Precision for Z coordinates. |
6-8 | Unsigned Integer | Precision for M coordinates. |
The extended precision values are always positive (only deal with digits to the left of the decimal point)
- The presence of Z can be read by masking out bit 1: (extended & 0x01)
- The presence of M can be read by masking out bit 2: (extended & 0x02)
- The value of Z precision can be read by masking and shifting bits 3-5: (extended & 0x1C) >> 2
- The value of M precision can be read by masking and shifting bits 6-8: (extended & 0xE0) >> 5
Size [Optional]
Size: 1 unsigned varint (so, variable size)
If the size attribute bit is set in the metadata header, a varInt with size information comes next. The value is the size in bytes of the remainder of the geometry after the size attribute.
When encountered in collections, an application can use the size attribute to advance the read pointer the the start of the next geometry. This can be used for a quick scan through a set of geometries, for reading just bounding boxes, or to distibute the read process to different threads.
Bounding Box [Optional]
Size: 2 signed varints per dimension (also variable size)
Each dimension of a bounding box is represented by a pair of varints:
- the minimum of the dimension
- the relative maximum of the dimension (relative to the minimum)
So, for example:
- [xmin, deltax, ymin, deltay, zmin, deltaz]
ID List [Optional]
Size: N signed varints, one per sub-geometry
The TWKB collection types (multipoint, multilinestring, multipolygon, geometrycollection)
- can be used as single values (a given row contains a single collection object), or
- can be used as aggregate wrappers for values from multiple rows (a single object contains geometries read from multiple rows).
In the latter case, it makes sense to include a unique identifier for each sub-geometry that is being wrapped into the collection. The "idlist" attribute is an array of varint that has one varint for each sub-geometry in the collection.
Description of Types
PointArrays
Every object contains a point array, which is an array of coordinates, stored as varints. The final values in the point arrays are a result of four steps:
- convert doubles to integers
- calculate integer delta values between successive points
- zig-zag encode delta values to move negative values into positive range
- varint encode the final value
The result is a very compact representation of the coordinates.
Convert to Integers
All storage using varints, which are integers. However, before being encoded most coordinate are doubles. How are the double coordinates converted into integers? And how are the integers converted ino the final array of varints?
Each coordinate is multiplied by the geometry precision value (from the metadata header), and then rounded to the nearest integer value (round(doublecoord * 10^precision)). When converting from TWKB back to double precision, the reverse process is applied.
Calculate Delta Values
Rather than storing the absolute position of every vertex, we store the difference between each coordinate and the previous coordinate of that dimension. So the coordinate values of a point array are:
x1, y1
(x2 - x1), (y2 - y1)
(x3 - x2), (y3 - y2)
(x4 - x3), (y4 - y3)
...
(xn - xn-1), (yn - yn-1)
Delta values of integer coordinates are nice and small, and they store very compactly as varints.
Every coordinate value in a geometry except the very first one is a delta value relative to the previous value processed. Examples:
- The second coordinate of a line string should be a delta relative to the first.
- The first coordinate of the second ring of a polygon should be a delta relative to the last coordinate of the first ring.
- The first coordinate of the third linestring in a multilinestring should be a delta relative to the last coordinate of the second linestring.
Basically, implementors should always keep the value of the previous coordinate in hand while processing a geometry to use to difference against the incoming coordinate. By setting the very first "previous coordinate" value to [0,0] it is possible to use the same differencing code throughout, since the first value processed will thus receive a "delta" value of itself.
ZigZag Encode
The varint scheme for indicating a value larger than one byte involves setting the "high bit", which is the same thing that standard integers use for storing negative numbers.
So negative numbers need to first be encoded using a "zig zag" encoding, which keep absolute values small, but do not set the high bit as part of encoding negative numbers.
The effect is to convert small negative numbers into small positive numbers, which is exactly what we want, since delta values will tend to be small negative and positive numbers:
-1 => 1
1 => 2
-2 => 3
2 => 4
A computationally fast formula to generate the zig-zag value is
/* for 32-bit signed integer n */
unsigned int zz = (n << 1) ^ (n >> 31)
/* for 64-bit signed integer n */
unsigned long zz = (n << 1) ^ (n >> 63)
VarInt Encode
Variable length integers are a clever way of only using as many bytes as necessary to store a value. The method is described here:
https://developers.google.com/protocol-buffers/docs/encoding#varints
The varint scheme sets the high bit of a byte as a flag to indicate when more bytes are needed to fully represent a number. Decoding a varint accumulates the information in each flagged byte until an unflagged byte is found and the integer is complete and ready to return.
Primitive Types
Point [Type 1]
Because points only have one coordinate, the coordinates will be the true values (not relative) except in cases where the point is embedded in a collection.
Bounding boxes are permitted on points, but discouraged since they just duplicate the values already available in the point coordinate. Similarly, unless the point is part of a collection (where random access is a possible use case), the size should also be omitted.
type_and_prec byte
metadata_header byte
[extended_dims] byte
[size] uvarint
[bounds] bbox
pointarray varint[]
Linestring [Type 2]
A linestring has, in addition to the standard metadata:
- an npoints unsigned varint giving the number of points in the linestring
- if npoints is zero, the linestring is "empty", and there is no further content
The layout is:
type_and_prec byte
metadata_header byte
[extended_dims] byte
[size] uvarint
[bounds] bbox
npoints uvarint
pointarray varint[]
Polygon [Type 3]
A polygon has, in addition to the standard metadata:
- an nrings unsigned varint giving the number of rings in the polygon
- if nrings is zero, the polygon is "empty", and there is no further content
- for each ring there will be
- an npoints unsigned varint giving the number of points in the ring
- a pointarray of varints
- rings are assumed to be implicitly closed, so the first and last point should not be the same
The layout is:
type_and_prec byte
metadata_header byte
[extended_dims] byte
[size] uvarint
[bounds] bbox
nrings uvarint
npoints[0] uvarint
pointarray[0] varint[]
...
npoints[n] uvarint
pointarray[n] varint[]
MultiPoint [Type 4]
A multipoint has, in addition to the standard metadata:
- an optional "idlist" (if indicated in the metadata header)
- an npoints unsigned varint giving the number of points in the multipoint
- if npoints is zero, the multipoint is "empty", and there is no further content
The layout is:
type_and_prec byte
metadata_header byte
[extended_dims] byte
[size] uvarint
[bounds] bbox
npoints uvarint
[idlist] varint[]
pointarray varint[]
MultiLineString [Type 5]
A multilinestring has, in addition to the standard metadata:
- an optional "idlist" (if indicated in the metadata header)
- an nlinestrings unsigned varint giving the number of linestrings in the collection
- if nlinestrings is zero, the collection is "empty", and there is no further content
- for each linestring there will be
- an npoints unsigned varint giving the number of points in the linestring
- a pointarray of varints
The layout is:
type_and_prec byte
metadata_header byte
[extended_dims] byte
[size] uvarint
[bounds] bbox
nlinestrings uvarint
[idlist] varint[]
npoints[0] uvarint
pointarray[0] varint[]
...
npoints[n] uvarint
pointarray[n] varint[]
MultiPolygon [Type 6]
A multipolygon has, in addition to the standard metadata:
- an optional "idlist" (if indicated in the metadata header)
- an npolygons unsigned varint giving the number of polygons in the collection
- if npolygons is zero, the collection is "empty", and there is no further content
- for each polygon there will be
- an nrings unsigned varint giving the number of rings in the linestring
- for each ring there will be
- an npoints unsigned varint giving the number of points in the ring
- a pointarray of varints
- rings are assumed to be implicitly closed, so the first and last point should not be the same
The layout is:
type_and_prec byte
metadata_header byte
[extended_dims] byte
[size] uvarint
[bounds] bbox
npolygons uvarint
[idlist] varint[]
nrings[0] uvarint
npoints[0][0] uvarint
pointarray[0][0] varint[]
...
nrings[n] uvarint
npoints[n][m] uvarint
pointarray[n][m] varint[]
GeometryCollection [Type 7]
A geometrycollection has, in addition to the standard metadata:
- an optional "idlist" (if indicated in the metadata header)
- an ngeometries unsigned varint giving the number of geometries in the collection
- for each geometry there will be a complete TWKB geometry (with it's own first absolute coordinate), readable using the rules set out above
The layout is:
type_and_prec byte
metadata_header byte
[extended_dims] byte
[size] varint
[bounds] bbox
ngeometries varint
[idlist] varint[]
geom twkb[]
package org.locationtech.geomesa.features.serialization import com.typesafe.scalalogging.LazyLogging import org.locationtech.geomesa.utils.geometry.GeometryPrecision.TwkbPrecision import org.locationtech.jts.geom._ import scala.util.control.NonFatal /** * Based on the TWKB standard: https://github.com/TWKB/Specification/blob/master/twkb.md * * For backwards compatibility, also reads original serialization, with the `LegacyGeometrySerialization` trait */ // noinspection LanguageFeature trait TwkbSerialization[T <: NumericWriter, V <: NumericReader] extends VarIntEncoding[T, V] with WkbSerialization[T, V] with LazyLogging { import DimensionalBounds._ import TwkbSerialization.FlagBytes._ import TwkbSerialization.GeometryBytes._ import TwkbSerialization.ZeroByte private val factory = new GeometryFactory() private val csFactory = factory.getCoordinateSequenceFactory /** * Serialize a geometry * * For explanation of precisions, see `org.locationtech.geomesa.utils.geometry.GeometryPrecision` * * @param out output * @param geometry geometry * @param precision precision for encoding x, y, z, m */ def serialize(out: T, geometry: Geometry, precision: TwkbPrecision = TwkbPrecision()): Unit = { if (geometry == null) { out.writeByte(ZeroByte) } else { // choose our state to correspond with the dimensions in the geometry implicit val state: DeltaState = { // note that we only check the first coordinate - if a geometry is written with different // dimensions in each coordinate, some information may be lost val coord = geometry.getCoordinate // check for dimensions - use NaN != NaN to verify z coordinate // TODO check for M coordinate when added to JTS if (coord == null || java.lang.Double.isNaN(coord.getZ)) { new XYState(precision.xy) } else { new XYZState(precision.xy, precision.z) } } geometry match { case g: Point => if (g.isEmpty) { state.writeMetadata(out, TwkbPoint, empty = true, bbox = false) } else { state.writeMetadata(out, TwkbPoint, empty = false, bbox = false) state.writeCoordinate(out, g.getCoordinate) } case g: LineString => if (g.isEmpty) { state.writeMetadata(out, TwkbLineString, empty = true, bbox = false) } else { state.writeMetadata(out, TwkbLineString, empty = false, bbox = true) state.writeBoundingBox(out, g) } writeLineString(out, g) case g: Polygon => if (g.isEmpty) { state.writeMetadata(out, TwkbPolygon, empty = true, bbox = false) } else { state.writeMetadata(out, TwkbPolygon, empty = false, bbox = true) state.writeBoundingBox(out, g) } writePolygon(out, g) case g: MultiPoint => if (g.isEmpty) { state.writeMetadata(out, TwkbMultiPoint, empty = true, bbox = false) } else { state.writeMetadata(out, TwkbMultiPoint, empty = false, bbox = true) state.writeBoundingBox(out, g) } writeMultiPoint(out, g) case g: MultiLineString => if (g.isEmpty) { state.writeMetadata(out, TwkbMultiLineString, empty = true, bbox = false) } else { state.writeMetadata(out, TwkbMultiLineString, empty = false, bbox = true) state.writeBoundingBox(out, g) } writeMultiLineString(out, g) case g: MultiPolygon => if (g.isEmpty) { state.writeMetadata(out, TwkbMultiPolygon, empty = true, bbox = false) } else { state.writeMetadata(out, TwkbMultiPolygon, empty = false, bbox = true) state.writeBoundingBox(out, g) } writeMultiPolygon(out, g) case g: GeometryCollection => if (g.isEmpty) { state.writeMetadata(out, TwkbCollection, empty = true, bbox = false) } else { state.writeMetadata(out, TwkbCollection, empty = false, bbox = true) state.writeBoundingBox(out, g) } writeCollection(out, g) } } } /** * Deserialize a geometry * * @param in input * @return */ def deserialize(in: V): Geometry = { try { val precisionAndType = in.readByte() if (precisionAndType == ZeroByte) { null } else if (precisionAndType == NOT_NULL_BYTE) { // TODO this overlaps with twkb point type with precision 0 deserializeWkb(in) } else { // first byte contains the geometry type in the first 4 bits and the x-y precision in the second 4 bits val geomType = (precisionAndType & 0x0F).toByte val precision = VarIntEncoding.zigzagDecode((precisionAndType & 0xF0) >>> 4) // second byte contains flags for optional elements val flags = in.readByte() val hasBoundingBox = (flags & BoundingBoxFlag) != 0 val hasExtendedDims = (flags & ExtendedDimsFlag) != 0 val isEmpty = (flags & EmptyFlag) != 0 // extended dims indicates the presence of z and/or m // we create our state tracker based on the dimensions that are present implicit val state: DeltaState = if (hasExtendedDims) { // z and m precisions are indicated in the next byte, where (from right to left): // bit 0 indicates presence of z dimension // bit 1 indicates presence of m dimension // bits 2-5 indicate z precision // bits 6-8 indicate m precision val extendedDims = in.readByte() if ((extendedDims & 0x01) != 0) { // indicates z dimension if ((extendedDims & 0x02) != 0) { // indicates m dimension new XYZMState(precision, (extendedDims & 0x1C) >> 2, (extendedDims & 0xE0) >>> 5) } else { new XYZState(precision, (extendedDims & 0x1C) >> 2) } } else if ((extendedDims & 0x02) != 0) { // indicates m dimension new XYMState(precision, (extendedDims & 0xE0) >>> 5) } else { // not sure why anyone would indicate extended dims but set them all false... new XYState(precision) } } else { new XYState(precision) } // size is the length of the remainder of the geometry, after the size attribute // we don't currently use size - parsing will fail if size is actually present // val hasSize = (flags & FlagBytes.SizeFlag) != 0 // if (hasSize) { // val size = readUnsignedVarInt(in) // } // bounding box is not currently used, but we write it in anticipation of future filter optimizations if (hasBoundingBox) { state.skipBoundingBox(in) } // children geometries can be written with an id list // we don't currently use ids - parsing will fail if ids are actually present // val hasIds = (flags & FlagBytes.IdsFlag) != 0 geomType match { case TwkbPoint => factory.createPoint(if (isEmpty) { null } else { csFactory.create(readPointArray(in, 1)) }) case TwkbLineString => readLineString(in) case TwkbPolygon => readPolygon(in) case TwkbMultiPoint => readMultiPoint(in) case TwkbMultiLineString => readMultiLineString(in) case TwkbMultiPolygon => readMultiPolygon(in) case TwkbCollection => readCollection(in) case _ => throw new IllegalArgumentException(s"Invalid TWKB geometry type $geomType") } } } catch { case NonFatal(e) => logger.error(s"Error reading serialized kryo geometry:", e); null } } private def writeLineString(out: T, g: LineString)(implicit state: DeltaState): Unit = writePointArray(out, g.getCoordinateSequence, g.getNumPoints) private def readLineString(in: V)(implicit state: DeltaState): LineString = factory.createLineString(csFactory.create(readPointArray(in, readUnsignedVarInt(in)))) private def writePolygon(out: T, g: Polygon)(implicit state: DeltaState): Unit = { if (g.isEmpty) { writeUnsignedVarInt(out, 0) } else { val numRings = g.getNumInteriorRing writeUnsignedVarInt(out, numRings + 1) // include exterior ring in count // note: don't write final point for each ring, as they should duplicate the first point var ring = g.getExteriorRing.getCoordinateSequence writePointArray(out, ring, ring.size() - 1) var j = 0 while (j < numRings) { ring = g.getInteriorRingN(j).getCoordinateSequence writePointArray(out, ring, ring.size() - 1) j += 1 } } } private def readPolygon(in: V)(implicit state: DeltaState): Polygon = { val numRings = readUnsignedVarInt(in) if (numRings == 0) { factory.createPolygon(null, null) } else { val exteriorRing = readLinearRing(in, readUnsignedVarInt(in)) val interiorRings = Array.ofDim[LinearRing](numRings - 1) var i = 1 while (i < numRings) { interiorRings(i - 1) = readLinearRing(in, readUnsignedVarInt(in)) i += 1 } factory.createPolygon(exteriorRing, interiorRings) } } private def writeMultiPoint(out: T, g: MultiPoint)(implicit state: DeltaState): Unit = { val length = g.getNumPoints writeUnsignedVarInt(out, length) var i = 0 while (i < length) { state.writeCoordinate(out, g.getGeometryN(i).asInstanceOf[Point].getCoordinate) i += 1 } } private def readMultiPoint(in: V)(implicit state: DeltaState): MultiPoint = { val numPoints = readUnsignedVarInt(in) if (numPoints == 0) { factory.createMultiPoint(null: CoordinateSequence) } else { // note: id list would go here, with one ID per point factory.createMultiPoint(readPointArray(in, numPoints).map(factory.createPoint)) } } private def writeMultiLineString(out: T, g: MultiLineString)(implicit state: DeltaState): Unit = { val length = g.getNumGeometries writeUnsignedVarInt(out, length) var i = 0 while (i < length) { val line = g.getGeometryN(i).asInstanceOf[LineString].getCoordinateSequence writePointArray(out, line, line.size()) i += 1 } } private def readMultiLineString(in: V)(implicit state: DeltaState): MultiLineString = { val numLineStrings = readUnsignedVarInt(in) if (numLineStrings == 0) { factory.createMultiLineString(null) } else { // note: id list would go here, with one ID per line string val lineStrings = Array.ofDim[LineString](numLineStrings) var i = 0 while (i < numLineStrings) { lineStrings(i) = readLineString(in) i += 1 } factory.createMultiLineString(lineStrings) } } private def writeMultiPolygon(out: T, g: MultiPolygon)(implicit state: DeltaState): Unit = { val length = g.getNumGeometries writeUnsignedVarInt(out, length) var i = 0 while (i < length) { writePolygon(out, g.getGeometryN(i).asInstanceOf[Polygon]) i += 1 } } private def readMultiPolygon(in: V)(implicit state: DeltaState): MultiPolygon = { val numPolygons = readUnsignedVarInt(in) if (numPolygons == 0) { factory.createMultiPolygon(null) } else { // note: id list would go here, with one ID per polygon val polygons = Array.ofDim[Polygon](numPolygons) var i = 0 while (i < numPolygons) { polygons(i) = readPolygon(in) i += 1 } factory.createMultiPolygon(polygons) } } private def writeCollection(out: T, g: GeometryCollection)(implicit state: DeltaState): Unit = { val length = g.getNumGeometries writeUnsignedVarInt(out, length) var i = 0 while (i < length) { serialize(out, g.getGeometryN(i)) i += 1 } } private def readCollection(in: V): GeometryCollection = { val numGeoms = readUnsignedVarInt(in) if (numGeoms == 0) { factory.createGeometryCollection(null) } else { // note: id list would go here, with one ID per sub geometry val geoms = Array.ofDim[Geometry](numGeoms) var i = 0 while (i < numGeoms) { geoms(i) = deserialize(in) i += 1 } factory.createGeometryCollection(geoms) } } private def writePointArray(out: T, coords: CoordinateSequence, length: Int)(implicit state: DeltaState): Unit = { writeUnsignedVarInt(out, length) var i = 0 while (i < length) { state.writeCoordinate(out, coords.getCoordinate(i)) i += 1 } } private def readPointArray(in: V, length: Int)(implicit state: DeltaState): Array[Coordinate] = { val result = Array.ofDim[Coordinate](length) var i = 0 while (i < length) { result(i) = state.readCoordinate(in) i += 1 } result } private def readLinearRing(in: V, length: Int)(implicit state: DeltaState): LinearRing = { if (length == 0) { factory.createLinearRing(null: CoordinateSequence) } else { val result = Array.ofDim[Coordinate](length + 1) var i = 0 while (i < length) { result(i) = state.readCoordinate(in) i += 1 } // linear rings should not store the final, duplicate point, but still need it for the geometry result(length) = result(0) factory.createLinearRing(csFactory.create(result)) } } /** * TWKB only reads and writes the delta from one coordinate to the next, which generally saves space * over absolute values. This trait tracks the values used for delta calculations over a single * read or write operation */ private sealed trait DeltaState { /** * Write metadata, which includes the geometry and precision byte, the flag byte, and optionally * an extended precision byte * * @param out output * @param geometryType geometry type * @param empty indicate that the geometry is empty * @param bbox indicate that a bbox will be written */ def writeMetadata(out: T, geometryType: Byte, empty: Boolean, bbox: Boolean): Unit /** * Writes out a bounding box. Each dimension stores a min value and a delta to the max value * * @param out output * @param geometry geometry * @param bounds bounds operation * @tparam G geometry type */ def writeBoundingBox[G <: Geometry](out: T, geometry: G)(implicit bounds: DimensionalBounds[G]): Unit /** * Skips over a bounding box. We don't currently use the bounding box when reading * * @param in in */ def skipBoundingBox(in: V): Unit /** * Write a coordinate * * @param out output * @param coordinate coordinate */ def writeCoordinate(out: T, coordinate: Coordinate): Unit /** * Read a coordinate * * @param in input * @return */ def readCoordinate(in: V): Coordinate /** * Reset the state back to its original state, suitable for re-use */ def reset(): Unit } private class XYState(precision: Int) extends DeltaState { private val p: Double = math.pow(10, precision) private var x: Int = 0 private var y: Int = 0 protected val boundingBoxFlag: Byte = BoundingBoxFlag protected val emptyFlag: Byte = EmptyFlag protected val dimensionsFlag: Byte = ZeroByte override def writeMetadata(out: T, geometryType: Byte, empty: Boolean, bbox: Boolean): Unit = { // write the geometry type and the main precision out.writeByte(((VarIntEncoding.zigzagEncode(precision) << 4) | geometryType).toByte) // write the flag byte out.writeByte(if (bbox) { boundingBoxFlag } else if (empty) { emptyFlag } else { dimensionsFlag }) } override def writeCoordinate(out: T, coordinate: Coordinate): Unit = { val cx = math.round(coordinate.x * p).toInt val cy = math.round(coordinate.y * p).toInt writeVarInt(out, cx - x) writeVarInt(out, cy - y) x = cx y = cy } override def readCoordinate(in: V): Coordinate = { x = x + readVarInt(in) y = y + readVarInt(in) new Coordinate(x / p, y / p) } override def writeBoundingBox[G <: Geometry](out: T, geometry: G)(implicit bounds: DimensionalBounds[G]): Unit = { val (minX, maxX) = bounds.x(geometry) val intX = math.round(minX * p).toInt writeVarInt(out, intX) writeVarInt(out, math.round(maxX * p).toInt - intX) val (minY, maxY) = bounds.y(geometry) val intY = math.round(minY * p).toInt writeVarInt(out, intY) writeVarInt(out, math.round(maxY * p).toInt - intY) } override def skipBoundingBox(in: V): Unit = { skipVarInt(in) skipVarInt(in) skipVarInt(in) skipVarInt(in) } override def reset(): Unit = { x = 0 y = 0 } } private abstract class ExtendedState(precision: Int) extends XYState(precision) { protected def extendedDims: Byte override protected val boundingBoxFlag: Byte = (ExtendedDimsFlag | BoundingBoxFlag).toByte override protected val emptyFlag: Byte = (ExtendedDimsFlag | EmptyFlag).toByte override protected val dimensionsFlag: Byte = ExtendedDimsFlag override def writeMetadata(out: T, geometryType: Byte, empty: Boolean, bbox: Boolean): Unit = { super.writeMetadata(out, geometryType, empty, bbox) // write the extended precision values out.writeByte(extendedDims) } } private class XYZState(precision: Int, zPrecision: Int) extends ExtendedState(precision) { private val pz: Double = math.pow(10, zPrecision) private var z: Int = 0 // sets bits for z dim, and its precisions override protected val extendedDims: Byte = (0x01 | ((zPrecision & 0x03) << 2)).toByte override def writeBoundingBox[G <: Geometry](out: T, geometry: G)(implicit bounds: DimensionalBounds[G]): Unit = { super.writeBoundingBox(out, geometry) val (minZ, maxZ) = bounds.z(geometry) val intZ = math.round(minZ * pz).toInt writeVarInt(out, intZ) writeVarInt(out, math.round(maxZ * pz).toInt - intZ) } override def writeCoordinate(out: T, coordinate: Coordinate): Unit = { super.writeCoordinate(out, coordinate) val cz = math.round(coordinate.getZ * pz).toInt writeVarInt(out, cz - z) z = cz } override def readCoordinate(in: V): Coordinate = { val coord = super.readCoordinate(in) z = z + readVarInt(in) coord.setZ(z / pz) coord } override def skipBoundingBox(in: V): Unit = { super.skipBoundingBox(in) skipVarInt(in) skipVarInt(in) } override def reset(): Unit = { super.reset() z = 0 } } private class XYMState(precision: Int, mPrecision: Int) extends ExtendedState(precision) { private val pm: Double = math.pow(10, mPrecision) private var m: Int = 0 // sets bit for m dim, and its precisions override protected val extendedDims: Byte = (0x02 | ((mPrecision & 0x03) << 5)).toByte override def writeBoundingBox[G <: Geometry](out: T, geometry: G)(implicit bounds: DimensionalBounds[G]): Unit = { super.writeBoundingBox(out, geometry) val (minM, maxM) = bounds.m(geometry) val intM = math.round(minM * pm).toInt writeVarInt(out, intM) writeVarInt(out, math.round(maxM * pm).toInt - intM) } override def writeCoordinate(out: T, coordinate: Coordinate): Unit = { super.writeCoordinate(out, coordinate) val cm = 0 // TODO math.round(coordinate.m * pm).toInt writeVarInt(out, cm - m) m = cm } override def readCoordinate(in: V): Coordinate = { val coord = super.readCoordinate(in) m = m + readVarInt(in) // TODO set m as 4th ordinate when supported by jts coord } override def skipBoundingBox(in: V): Unit = { super.skipBoundingBox(in) skipVarInt(in) skipVarInt(in) } override def reset(): Unit = { super.reset() m = 0 } } private class XYZMState(precision: Int, zPrecision: Int, mPrecision: Int) extends XYZState(precision, zPrecision) { private val pm: Double = math.pow(10, mPrecision) private var m: Int = 0 // sets bits for both z and m dims, and their precisions override protected val extendedDims: Byte = (0x03 | ((zPrecision & 0x03) << 2) | ((mPrecision & 0x03) << 5)).toByte override def writeBoundingBox[G <: Geometry](out: T, geometry: G)(implicit bounds: DimensionalBounds[G]): Unit = { super.writeBoundingBox(out, geometry) val (minM, maxM) = bounds.m(geometry) val intM = math.round(minM * pm).toInt writeVarInt(out, intM) writeVarInt(out, math.round(maxM * pm).toInt - intM) } override def writeCoordinate(out: T, coordinate: Coordinate): Unit = { super.writeCoordinate(out, coordinate) val cm = 0 // TODO math.round(coordinate.m * pm).toInt writeVarInt(out, cm - m) m = cm } override def readCoordinate(in: V): Coordinate = { val coord = super.readCoordinate(in) m = m + readVarInt(in) // TODO set m as 4th ordinate when supported by jts coord } override def skipBoundingBox(in: V): Unit = { super.skipBoundingBox(in) skipVarInt(in) skipVarInt(in) } override def reset(): Unit = { super.reset() m = 0 } } } object TwkbSerialization { val MaxPrecision: Byte = 7 private val ZeroByte: Byte = 0 // twkb constants object GeometryBytes { val TwkbPoint :Byte = 1 val TwkbLineString :Byte = 2 val TwkbPolygon :Byte = 3 val TwkbMultiPoint :Byte = 4 val TwkbMultiLineString :Byte = 5 val TwkbMultiPolygon :Byte = 6 val TwkbCollection :Byte = 7 } object FlagBytes { val BoundingBoxFlag :Byte = 0x01 val SizeFlag :Byte = 0x02 val IdsFlag :Byte = 0x04 val ExtendedDimsFlag :Byte = 0x08 val EmptyFlag :Byte = 0x10 } }
-------------------------------------------- C ------------------------------------------
#include "lwout_twkb.h" /* * GeometryType, and dimensions */ static uint8_t lwgeom_twkb_type(const LWGEOM *geom) { uint8_t twkb_type = 0; LWDEBUGF(2, "Entered lwgeom_twkb_type",0); switch ( geom->type ) { case POINTTYPE: twkb_type = WKB_POINT_TYPE; break; case LINETYPE: twkb_type = WKB_LINESTRING_TYPE; break; case TRIANGLETYPE: case POLYGONTYPE: twkb_type = WKB_POLYGON_TYPE; break; case MULTIPOINTTYPE: twkb_type = WKB_MULTIPOINT_TYPE; break; case MULTILINETYPE: twkb_type = WKB_MULTILINESTRING_TYPE; break; case MULTIPOLYGONTYPE: twkb_type = WKB_MULTIPOLYGON_TYPE; break; case TINTYPE: case COLLECTIONTYPE: twkb_type = WKB_GEOMETRYCOLLECTION_TYPE; break; default: lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); } return twkb_type; } /** * Calculates the size of the bbox in varints in the form: * xmin, xdelta, ymin, ydelta */ static size_t sizeof_bbox(TWKB_STATE *ts, int ndims) { int i; uint8_t buf[16]; size_t size = 0; LWDEBUGF(2, "Entered %s", __func__); for ( i = 0; i < ndims; i++ ) { size += varint_s64_encode_buf(ts->bbox_min[i], buf); size += varint_s64_encode_buf((ts->bbox_max[i] - ts->bbox_min[i]), buf); } return size; } /** * Writes the bbox in varints in the form: * xmin, xdelta, ymin, ydelta */ static void write_bbox(TWKB_STATE *ts, int ndims) { int i; LWDEBUGF(2, "Entered %s", __func__); for ( i = 0; i < ndims; i++ ) { bytebuffer_append_varint(ts->header_buf, ts->bbox_min[i]); bytebuffer_append_varint(ts->header_buf, (ts->bbox_max[i] - ts->bbox_min[i])); } } /** * Stores a pointarray as varints in the buffer * @register_npoints, controls whether an npoints entry is added to the buffer (used to skip npoints for point types) * @dimension, states the dimensionality of object this array is part of (0 = point, 1 = linear, 2 = areal) */ static int ptarray_to_twkb_buf(const POINTARRAY *pa, TWKB_GLOBALS *globals, TWKB_STATE *ts, int register_npoints, uint32_t minpoints) { uint32_t ndims = FLAGS_NDIMS(pa->flags); uint32_t i, j; bytebuffer_t b; bytebuffer_t *b_p; int64_t nextdelta[MAX_N_DIMS]; int npoints = 0; size_t npoints_offset = 0; uint32_t max_points_left = pa->npoints; LWDEBUGF(2, "Entered %s", __func__); /* Dispense with the empty case right away */ if ( pa->npoints == 0 && register_npoints ) { LWDEBUGF(4, "Register npoints:%d", pa->npoints); bytebuffer_append_uvarint(ts->geom_buf, pa->npoints); return 0; } /* If npoints is more than 127 it is unpredictable how many bytes npoints will need */ /* Then we have to store the deltas in a temp buffer to later add them after npoints */ /* If noints is below 128 we know 1 byte will be needed */ /* Then we can make room for that 1 byte at once and write to */ /* ordinary buffer */ if( pa->npoints > 127 ) { /* Independent buffer to hold the coordinates, so we can put the npoints */ /* into the stream once we know how many points we actually have */ bytebuffer_init_with_size(&b, 3 * ndims * pa->npoints); b_p = &b; } else { /* We give an alias to our ordinary buffer */ b_p = ts->geom_buf; if ( register_npoints ) { /* We do not store a pointer to the place where we want the npoints value */ /* Instead we store how far from the beginning of the buffer we want the value */ /* That is because we otherwise will get in trouble if the buffer is reallocated */ npoints_offset = b_p->writecursor - b_p->buf_start; /* We just move the cursor 1 step to make room for npoints byte */ /* We use the function append_byte even if we have no value yet, */ /* since that gives us the check for big enough buffer and moves the cursor */ bytebuffer_append_byte(b_p, 0); } } for ( i = 0; i < pa->npoints; i++ ) { double *dbl_ptr = (double*)getPoint_internal(pa, i); int64_t diff = 0; /* Write this coordinate to the buffer as a varint */ for ( j = 0; j < ndims; j++ ) { /* To get the relative coordinate we don't get the distance */ /* from the last point but instead the distance from our */ /* last accumulated point. This is important to not build up an */ /* accumulated error when rounding the coordinates */ nextdelta[j] = (int64_t) llround(globals->factor[j] * dbl_ptr[j]) - ts->accum_rels[j]; LWDEBUGF(4, "deltavalue: %d, ", nextdelta[j]); diff += llabs(nextdelta[j]); } /* Skipping the first point is not allowed */ /* If the sum(abs()) of all the deltas was zero, */ /* then this was a duplicate point, so we can ignore it */ if ( i > 0 && diff == 0 && max_points_left > minpoints ) { max_points_left--; continue; } /* We really added a point, so... */ npoints++; /* Write this vertex to the temporary buffer as varints */ for ( j = 0; j < ndims; j++ ) { ts->accum_rels[j] += nextdelta[j]; bytebuffer_append_varint(b_p, nextdelta[j]); } /* See if this coordinate expands the bounding box */ if( globals->variant & TWKB_BBOX ) { for ( j = 0; j < ndims; j++ ) { if( ts->accum_rels[j] > ts->bbox_max[j] ) ts->bbox_max[j] = ts->accum_rels[j]; if( ts->accum_rels[j] < ts->bbox_min[j] ) ts->bbox_min[j] = ts->accum_rels[j]; } } } if ( pa->npoints > 127 ) { /* Now write the temporary results into the main buffer */ /* First the npoints */ if ( register_npoints ) bytebuffer_append_uvarint(ts->geom_buf, npoints); /* Now the coordinates */ bytebuffer_append_bytebuffer(ts->geom_buf, b_p); /* Clear our temporary buffer */ bytebuffer_destroy_buffer(&b); } else { /* If we didn't use a temp buffer, we just write that npoints value */ /* to where it belongs*/ if ( register_npoints ) varint_u64_encode_buf(npoints, b_p->buf_start + npoints_offset); } return 0; } /****************************************************************** * POINTS *******************************************************************/ static int lwpoint_to_twkb_buf(const LWPOINT *pt, TWKB_GLOBALS *globals, TWKB_STATE *ts) { LWDEBUGF(2, "Entered %s", __func__); /* Set the coordinates (don't write npoints) */ ptarray_to_twkb_buf(pt->point, globals, ts, 0, 1); return 0; } /****************************************************************** * LINESTRINGS *******************************************************************/ static int lwline_to_twkb_buf(const LWLINE *line, TWKB_GLOBALS *globals, TWKB_STATE *ts) { LWDEBUGF(2, "Entered %s", __func__); /* Set the coordinates (do write npoints) */ ptarray_to_twkb_buf(line->points, globals, ts, 1, 2); return 0; } static int lwtriangle_to_twkb_buf(const LWTRIANGLE *tri, TWKB_GLOBALS *globals, TWKB_STATE *ts) { LWDEBUGF(2, "Entered %s", __func__); bytebuffer_append_uvarint(ts->geom_buf, (uint64_t)1); /* Set the coordinates (do write npoints) */ ptarray_to_twkb_buf(tri->points, globals, ts, 1, 2); return 0; } /****************************************************************** * POLYGONS *******************************************************************/ static int lwpoly_to_twkb_buf(const LWPOLY *poly, TWKB_GLOBALS *globals, TWKB_STATE *ts) { uint32_t i; /* Set the number of rings */ bytebuffer_append_uvarint(ts->geom_buf, (uint64_t) poly->nrings); for ( i = 0; i < poly->nrings; i++ ) { /* Set the coordinates (do write npoints) */ ptarray_to_twkb_buf(poly->rings[i], globals, ts, 1, 4); } return 0; } /****************************************************************** * MULTI-GEOMETRYS (MultiPoint, MultiLinestring, MultiPolygon) *******************************************************************/ static int lwmulti_to_twkb_buf(const LWCOLLECTION *col, TWKB_GLOBALS *globals, TWKB_STATE *ts) { uint32_t i; int nempty = 0; LWDEBUGF(2, "Entered %s", __func__); LWDEBUGF(4, "Number of geometries in multi is %d", col->ngeoms); /* Deal with special case for MULTIPOINT: skip any empty points */ if ( col->type == MULTIPOINTTYPE ) { for ( i = 0; i < col->ngeoms; i++ ) if ( lwgeom_is_empty(col->geoms[i]) ) nempty++; } /* Set the number of geometries */ bytebuffer_append_uvarint(ts->geom_buf, (uint64_t) (col->ngeoms - nempty)); /* We've been handed an idlist, so write it in */ if ( ts->idlist ) { for ( i = 0; i < col->ngeoms; i++ ) { /* Skip empty points in multipoints, we can't represent them */ if ( col->type == MULTIPOINTTYPE && lwgeom_is_empty(col->geoms[i]) ) continue; bytebuffer_append_varint(ts->geom_buf, ts->idlist[i]); } /* Empty it out to nobody else uses it now */ ts->idlist = NULL; } for ( i = 0; i < col->ngeoms; i++ ) { /* Skip empty points in multipoints, we can't represent them */ if ( col->type == MULTIPOINTTYPE && lwgeom_is_empty(col->geoms[i]) ) continue; lwgeom_to_twkb_buf(col->geoms[i], globals, ts); } return 0; } /****************************************************************** * GEOMETRYCOLLECTIONS *******************************************************************/ static int lwcollection_to_twkb_buf(const LWCOLLECTION *col, TWKB_GLOBALS *globals, TWKB_STATE *ts) { uint32_t i; LWDEBUGF(2, "Entered %s", __func__); LWDEBUGF(4, "Number of geometries in collection is %d", col->ngeoms); /* Set the number of geometries */ bytebuffer_append_uvarint(ts->geom_buf, (uint64_t) col->ngeoms); /* We've been handed an idlist, so write it in */ if ( ts->idlist ) { for ( i = 0; i < col->ngeoms; i++ ) bytebuffer_append_varint(ts->geom_buf, ts->idlist[i]); /* Empty it out to nobody else uses it now */ ts->idlist = NULL; } /* Write in the sub-geometries */ for ( i = 0; i < col->ngeoms; i++ ) { lwgeom_write_to_buffer(col->geoms[i], globals, ts); } return 0; } /****************************************************************** * Handle whole TWKB *******************************************************************/ static int lwgeom_to_twkb_buf(const LWGEOM *geom, TWKB_GLOBALS *globals, TWKB_STATE *ts) { LWDEBUGF(2, "Entered %s", __func__); switch ( geom->type ) { case POINTTYPE: { LWDEBUGF(4,"Type found is Point, %d", geom->type); return lwpoint_to_twkb_buf((LWPOINT*) geom, globals, ts); } case LINETYPE: { LWDEBUGF(4,"Type found is Linestring, %d", geom->type); return lwline_to_twkb_buf((LWLINE*) geom, globals, ts); } case TRIANGLETYPE: { LWDEBUGF(4, "Type found is Triangle, %d", geom->type); return lwtriangle_to_twkb_buf((LWTRIANGLE *)geom, globals, ts); } /* Polygon has 'nrings' and 'rings' elements */ case POLYGONTYPE: { LWDEBUGF(4,"Type found is Polygon, %d", geom->type); return lwpoly_to_twkb_buf((LWPOLY*)geom, globals, ts); } /* All these Collection types have 'ngeoms' and 'geoms' elements */ case MULTIPOINTTYPE: case MULTILINETYPE: case MULTIPOLYGONTYPE: { LWDEBUGF(4,"Type found is Multi, %d", geom->type); return lwmulti_to_twkb_buf((LWCOLLECTION*)geom, globals, ts); } case COLLECTIONTYPE: case TINTYPE: { LWDEBUGF(4,"Type found is collection, %d", geom->type); return lwcollection_to_twkb_buf((LWCOLLECTION*) geom, globals, ts); } /* Unknown type! */ default: lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); } return 0; } static int lwgeom_write_to_buffer(const LWGEOM *geom, TWKB_GLOBALS *globals, TWKB_STATE *parent_state) { int i, is_empty, has_z = 0, has_m = 0, ndims; size_t bbox_size = 0, optional_precision_byte = 0; uint8_t flag = 0, type_prec = 0; bytebuffer_t header_bytebuffer, geom_bytebuffer; TWKB_STATE child_state; memset(&child_state, 0, sizeof(TWKB_STATE)); child_state.header_buf = &header_bytebuffer; child_state.geom_buf = &geom_bytebuffer; child_state.idlist = parent_state->idlist; bytebuffer_init_with_size(child_state.header_buf, 16); bytebuffer_init_with_size(child_state.geom_buf, 64); /* Read dimensionality from input */ ndims = lwgeom_ndims(geom); is_empty = lwgeom_is_empty(geom); if ( ndims > 2 ) { has_z = lwgeom_has_z(geom); has_m = lwgeom_has_m(geom); } /* Do we need extended precision? If we have a Z or M we do. */ optional_precision_byte = (has_z || has_m); /* Both X and Y dimension use the same precision */ globals->factor[0] = pow(10, globals->prec_xy); globals->factor[1] = globals->factor[0]; /* Z and M dimensions have their own precisions */ if ( has_z ) globals->factor[2] = pow(10, globals->prec_z); if ( has_m ) globals->factor[2 + has_z] = pow(10, globals->prec_m); /* Reset stats */ for ( i = 0; i < MAX_N_DIMS; i++ ) { /* Reset bbox calculation */ child_state.bbox_max[i] = INT64_MIN; child_state.bbox_min[i] = INT64_MAX; /* Reset acumulated delta values to get absolute values on next point */ child_state.accum_rels[i] = 0; } /* TYPE/PRECISION BYTE */ if ( abs(globals->prec_xy) > 7 ) lwerror("%s: X/Z precision cannot be greater than 7 or less than -7", __func__); /* Read the TWKB type number from the geometry */ TYPE_PREC_SET_TYPE(type_prec, lwgeom_twkb_type(geom)); /* Zig-zag the precision value before encoding it since it is a signed value */ TYPE_PREC_SET_PREC(type_prec, zigzag8(globals->prec_xy)); /* Write the type and precision byte */ bytebuffer_append_byte(child_state.header_buf, type_prec); /* METADATA BYTE */ /* Set first bit if we are going to store bboxes */ FIRST_BYTE_SET_BBOXES(flag, (globals->variant & TWKB_BBOX) && ! is_empty); /* Set second bit if we are going to store resulting size */ FIRST_BYTE_SET_SIZES(flag, globals->variant & TWKB_SIZE); /* There will be no ID-list (for now) */ FIRST_BYTE_SET_IDLIST(flag, parent_state->idlist && ! is_empty); /* Are there higher dimensions */ FIRST_BYTE_SET_EXTENDED(flag, optional_precision_byte); /* Empty? */ FIRST_BYTE_SET_EMPTY(flag, is_empty); /* Write the header byte */ bytebuffer_append_byte(child_state.header_buf, flag); /* EXTENDED PRECISION BYTE (OPTIONAL) */ /* If needed, write the extended dim byte */ if( optional_precision_byte ) { uint8_t flag = 0; if ( has_z && ( globals->prec_z > 7 || globals->prec_z < 0 ) ) lwerror("%s: Z precision cannot be negative or greater than 7", __func__); if ( has_m && ( globals->prec_m > 7 || globals->prec_m < 0 ) ) lwerror("%s: M precision cannot be negative or greater than 7", __func__); HIGHER_DIM_SET_HASZ(flag, has_z); HIGHER_DIM_SET_HASM(flag, has_m); HIGHER_DIM_SET_PRECZ(flag, globals->prec_z); HIGHER_DIM_SET_PRECM(flag, globals->prec_m); bytebuffer_append_byte(child_state.header_buf, flag); } /* It the geometry is empty, we're almost done */ if ( is_empty ) { /* If this output is sized, write the size of */ /* all following content, which is zero because */ /* there is none */ if ( globals->variant & TWKB_SIZE ) bytebuffer_append_byte(child_state.header_buf, 0); bytebuffer_append_bytebuffer(parent_state->geom_buf, child_state.header_buf); bytebuffer_destroy_buffer(child_state.header_buf); bytebuffer_destroy_buffer(child_state.geom_buf); return 0; } /* Write the TWKB into the output buffer */ lwgeom_to_twkb_buf(geom, globals, &child_state); /*If we have a header_buf, we know that this function is called inside a collection*/ /*and then we have to merge the bboxes of the included geometries*/ /*and put the result to the parent (the collection)*/ if( (globals->variant & TWKB_BBOX) && parent_state->header_buf ) { LWDEBUG(4,"Merge bboxes"); for ( i = 0; i < ndims; i++ ) { if(child_state.bbox_min[i]<parent_state->bbox_min[i]) parent_state->bbox_min[i] = child_state.bbox_min[i]; if(child_state.bbox_max[i]>parent_state->bbox_max[i]) parent_state->bbox_max[i] = child_state.bbox_max[i]; } } /* Did we have a box? If so, how big? */ bbox_size = 0; if( globals->variant & TWKB_BBOX ) { LWDEBUG(4,"We want boxes and will calculate required size"); bbox_size = sizeof_bbox(&child_state, ndims); } /* Write the size if wanted */ if( globals->variant & TWKB_SIZE ) { /* Here we have to add what we know will be written to header */ /* buffer after size value is written */ size_t size_to_register = bytebuffer_getlength(child_state.geom_buf); size_to_register += bbox_size; bytebuffer_append_uvarint(child_state.header_buf, size_to_register); } if( globals->variant & TWKB_BBOX ) write_bbox(&child_state, ndims); bytebuffer_append_bytebuffer(parent_state->geom_buf,child_state.header_buf); bytebuffer_append_bytebuffer(parent_state->geom_buf,child_state.geom_buf); bytebuffer_destroy_buffer(child_state.header_buf); bytebuffer_destroy_buffer(child_state.geom_buf); return 0; } /** * Convert LWGEOM to a char* in TWKB format. Caller is responsible for freeing * the returned array. */ lwvarlena_t * lwgeom_to_twkb_with_idlist(const LWGEOM *geom, int64_t *idlist, uint8_t variant, int8_t precision_xy, int8_t precision_z, int8_t precision_m) { LWDEBUGF(2, "Entered %s", __func__); LWDEBUGF(2, "variant value %x", variant); TWKB_GLOBALS tg; TWKB_STATE ts; bytebuffer_t geom_bytebuffer; memset(&ts, 0, sizeof(TWKB_STATE)); memset(&tg, 0, sizeof(TWKB_GLOBALS)); tg.variant = variant; tg.prec_xy = precision_xy; tg.prec_z = precision_z; tg.prec_m = precision_m; if ( idlist && ! lwgeom_is_collection(geom) ) { lwerror("Only collections can support ID lists"); return NULL; } if ( ! geom ) { LWDEBUG(4,"Cannot convert NULL into TWKB."); lwerror("Cannot convert NULL into TWKB"); return NULL; } ts.idlist = idlist; ts.header_buf = NULL; ts.geom_buf = &geom_bytebuffer; bytebuffer_init_with_size(ts.geom_buf, 512); lwgeom_write_to_buffer(geom, &tg, &ts); lwvarlena_t *v = bytebuffer_get_buffer_varlena(ts.geom_buf); bytebuffer_destroy_buffer(ts.geom_buf); return v; } lwvarlena_t * lwgeom_to_twkb(const LWGEOM *geom, uint8_t variant, int8_t precision_xy, int8_t precision_z, int8_t precision_m) { return lwgeom_to_twkb_with_idlist(geom, NULL, variant, precision_xy, precision_z, precision_m); }