All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.locationtech.jts.io.twkb.TWKBWriter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2018 James Hughes, 2019 Gabriel Roldan, 2022 Aurélien Mino
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package org.locationtech.jts.io.twkb;

import static org.locationtech.jts.io.twkb.Varint.writeSignedVarLong;
import static org.locationtech.jts.io.twkb.Varint.writeUnsignedVarInt;

import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;

import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.io.twkb.TWKBHeader.GeometryType;

/**
 * Writes {@link Geometry}s in TWKB (Tiny Well-known Binary) format.
 * 

* The current TWKB specification is * https://github.com/TWKB/Specification/blob/master/twkb.md. *

*/ public class TWKBWriter { private TWKBHeader paramsHeader = new TWKBHeader() .setXyPrecision(7) .setZPrecision(0) .setMPrecision(0) .setHasBBOX(false) .setHasSize(false); /** * Number of base-10 decimal places stored for X and Y dimensions. *

* A positive retaining information to the right of the decimal place, negative rounding up to * the left of the decimal place). *

* Defaults to {@code 7} */ public TWKBWriter setXYPrecision(int xyprecision) { if (xyprecision < -7 || xyprecision > 7) { throw new IllegalArgumentException( "X/Z precision cannot be greater than 7 or less than -7"); } paramsHeader = paramsHeader.setXyPrecision(xyprecision); return this; } public TWKBWriter setEncodeZ(boolean includeZDimension) { paramsHeader = paramsHeader.setHasZ(includeZDimension); return this; } public TWKBWriter setEncodeM(boolean includeMDimension) { paramsHeader = paramsHeader.setHasM(includeMDimension); return this; } /** * Number of base-10 decimal places stored for Z dimension. *

* A positive retaining information to the right of the decimal place, negative rounding up to * the left of the decimal place). *

* Defaults to {@code 0} */ public TWKBWriter setZPrecision(int zprecision) { if (zprecision < 0 || zprecision > 7) { throw new IllegalArgumentException("Z precision cannot be negative or greater than 7"); } paramsHeader = paramsHeader.setZPrecision(zprecision); return this; } /** * Number of base-10 decimal places stored for M dimension. *

* A positive retaining information to the right of the decimal place, negative rounding up to * the left of the decimal place). *

* Defaults to {@code 0} */ public TWKBWriter setMPrecision(int mprecision) { if (mprecision < 0 || mprecision > 7) { throw new IllegalArgumentException("M precision cannot be negative or greater than 7"); } paramsHeader = paramsHeader.setMPrecision(mprecision); return this; } /** * Whether the generated TWKB should include the size in bytes of the geometry. */ public TWKBWriter setIncludeSize(boolean includeSize) { paramsHeader = paramsHeader.setHasSize(includeSize); return this; } /** * Whether the generated TWKB should include a Bounding Box for the geometry. */ public TWKBWriter setIncludeBbox(boolean includeBbox) { paramsHeader = paramsHeader.setHasBBOX(includeBbox); return this; } public byte[] write(Geometry geom) { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { write(geom, out); } catch (IOException ex) { throw new RuntimeException("Unexpected IOException caught: " + ex.getMessage(), ex); } return out.toByteArray(); } public void write(Geometry geom, OutputStream out) throws IOException { write(geom, (DataOutput) new DataOutputStream(out)); } public void write(Geometry geom, DataOutput out) throws IOException { Objects.requireNonNull(geom, "geometry is null"); write(geom, out, paramsHeader, false); } private TWKBHeader write(Geometry geometry, DataOutput out, TWKBHeader params, boolean forcePreserveHeaderDimensions) throws IOException { Objects.requireNonNull(geometry, "Geometry is null"); Objects.requireNonNull(out, "DataOutput is null"); Objects.requireNonNull(params, "TWKBHeader is null"); TWKBHeader header = prepareHeader(geometry, new TWKBHeader(params), forcePreserveHeaderDimensions); if (header.hasSize()) { BufferedDataOutput bufferedBody = new BufferedDataOutput(); writeGeometryBody(geometry, bufferedBody, header); int bodySize = bufferedBody.size(); header = header.setGeometryBodySize(bodySize); writeHeaderTo(header, out); out.write(bufferedBody.content()); } else { writeHeaderTo(header, out); writeGeometryBody(geometry, out, header); } return header; } private static void writeHeaderTo(TWKBHeader header, DataOutput out) throws IOException { Objects.requireNonNull(out); final int typeAndPrecisionHeader; final int metadataHeader; { final int geometryType = header.geometryType().getValue(); final int precisionHeader = Varint.zigZagEncode(header.xyPrecision()) << 4; typeAndPrecisionHeader = precisionHeader | geometryType; metadataHeader = (header.hasBBOX() ? 0b00000001 : 0) // | (header.hasSize() ? 0b00000010 : 0)// | (header.hasIdList() ? 0b00000100 : 0)// | (header.hasExtendedPrecision() ? 0b00001000 : 0)// | (header.isEmpty() ? 0b00010000 : 0); } out.writeByte(typeAndPrecisionHeader); out.writeByte(metadataHeader); if (header.hasExtendedPrecision()) { int extendedDimsHeader = (header.hasZ() ? 0b00000001 : 0) | (header.hasM() ? 0b00000010 : 0); extendedDimsHeader |= header.zPrecision() << 2; extendedDimsHeader |= header.mPrecision() << 5; out.writeByte(extendedDimsHeader); } if (header.hasSize()) { writeUnsignedVarInt(header.geometryBodySize(), out); } } private TWKBHeader prepareHeader(Geometry geometry, TWKBHeader params, boolean forcePreserveHeaderDimensions) { final boolean isEmpty = geometry.isEmpty(); final GeometryType geometryType = GeometryType.valueOf(geometry.getClass()); TWKBHeader header = forcePreserveHeaderDimensions ? params : setDimensions(geometry, params); header.setEmpty(isEmpty); header.setGeometryType(geometryType); if (isEmpty && header.hasBBOX()) { header = header.setHasBBOX(false); } return header; } private void writeGeometryBody(Geometry geom, DataOutput out, TWKBHeader header) throws IOException { if (header.isEmpty()) { return; } if (header.hasBBOX()) { writeBbox(geom, out, header); } final GeometryType geometryType = GeometryType.valueOf(geom.getClass()); switch (geometryType) { case POINT: writePoint((Point) geom, out, header); return; case LINESTRING: writeLineString((LineString) geom, out, header, new long[header.getDimensions()]); return; case POLYGON: writePolygon((Polygon) geom, out, header, new long[header.getDimensions()]); return; case MULTIPOINT: writeMultiPoint((MultiPoint) geom, out, header); return; case MULTILINESTRING: writeMultiLineString((MultiLineString) geom, out, header); return; case MULTIPOLYGON: writeMultiPolygon((MultiPolygon) geom, out, header); return; case GEOMETRYCOLLECTION: writeGeometryCollection((GeometryCollection) geom, out, header); return; default: break; } } private void writePoint(Point geom, DataOutput out, TWKBHeader header) throws IOException { assert !geom.isEmpty(); CoordinateSequence seq = geom.getCoordinateSequence(); int dimensions = header.getDimensions(); for (int d = 0; d < dimensions; d++) { writeOrdinate(seq.getOrdinate(0, d), 0L, header.getPrecision(d), out); } } private void writeCoordinateSequence(CoordinateSequence coordinateSequence, DataOutput out, TWKBHeader header, long[] prev, int minNPoints) throws IOException { final int dimensions = header.getDimensions(); long[] delta = new long[dimensions]; int nPoints = 0; int nPointsRemaining = coordinateSequence.size(); // Real number of points can't be determined beforehand, since duplicated points may be removed, so buffering is required BufferedDataOutput bufferedOut = new BufferedDataOutput(); for (int coordIndex = 0; coordIndex < coordinateSequence.size(); coordIndex++) { long diff = 0; nPointsRemaining--; for (int ordinateIndex = 0; ordinateIndex < dimensions; ordinateIndex++) { int precision = header.getPrecision(ordinateIndex); double ordinate = coordinateSequence.getOrdinate(coordIndex, ordinateIndex); long preciseOrdinate = makePrecise(ordinate, precision); delta[ordinateIndex] = preciseOrdinate - prev[ordinateIndex]; prev[ordinateIndex] = preciseOrdinate; diff += Math.abs(delta[ordinateIndex]); } if (coordIndex != 0 && diff == 0 && (nPoints + nPointsRemaining) > minNPoints) { // Skip this point continue; } for (int ordinateIndex = 0; ordinateIndex < header.getDimensions(); ordinateIndex++) { writeSignedVarLong(delta[ordinateIndex], bufferedOut); } nPoints++; } writeUnsignedVarInt(nPoints, out); out.write(bufferedOut.content()); } private long writeOrdinate(double ordinate, long previousOrdinateValue, int precision, DataOutput out) throws IOException { long preciseOrdinate = makePrecise(ordinate, precision); long delta = preciseOrdinate - previousOrdinateValue; writeSignedVarLong(delta, out); return preciseOrdinate; } private long makePrecise(double value, int precision) { return Math.round(value * Math.pow(10, precision)); } private void writeLineString(LineString geom, DataOutput out, TWKBHeader header, long[] prev) throws IOException { writeCoordinateSequence(geom.getCoordinateSequence(), out, header, prev, 3); } private void writePolygon(Polygon geom, DataOutput out, TWKBHeader header, long[] prev) throws IOException { if (geom.isEmpty()) { writeUnsignedVarInt(0, out); return; } final int numInteriorRing = geom.getNumInteriorRing(); final int nrings = 1 + numInteriorRing; writeUnsignedVarInt(nrings, out); writeLinearRing(geom.getExteriorRing(), out, header, prev); for (int r = 0; r < numInteriorRing; r++) { writeLinearRing(geom.getInteriorRingN(r), out, header, prev); } } private void writeLinearRing(LinearRing geom, DataOutput out, TWKBHeader header, long[] prev) throws IOException { if (geom.isEmpty()) { writeUnsignedVarInt(0, out); return; } writeCoordinateSequence(geom.getCoordinateSequence(), out, header, prev, 3); } private void writeMultiPoint(MultiPoint geom, DataOutput out, TWKBHeader header) throws IOException { assert !geom.isEmpty(); CoordinateSequence seq = geom.getFactory().getCoordinateSequenceFactory() .create(geom.getCoordinates()); writeCoordinateSequence(seq, out, header, new long[header.getDimensions()], 2); } private void writeMultiLineString(MultiLineString geom, DataOutput out, TWKBHeader header) throws IOException { final int size = writeNumGeometries(geom, out); long[] prev = new long[header.getDimensions()]; for (int i = 0; i < size; i++) { writeLineString((LineString) geom.getGeometryN(i), out, header, prev); } } private void writeMultiPolygon(MultiPolygon geom, DataOutput out, TWKBHeader header) throws IOException { final int size = writeNumGeometries(geom, out); long[] prev = new long[header.getDimensions()]; for (int i = 0; i < size; i++) { writePolygon((Polygon) geom.getGeometryN(i), out, header, prev); } } private void writeGeometryCollection(GeometryCollection geom, DataOutput out, TWKBHeader header) throws IOException { final int size = writeNumGeometries(geom, out); for (int i = 0; i < size; i++) { Geometry geometryN = geom.getGeometryN(i); boolean forcePreserveDimensions = geometryN.isEmpty(); write(geometryN, out, header, forcePreserveDimensions); } } private int writeNumGeometries(GeometryCollection geom, DataOutput out) throws IOException { int size = geom.getNumGeometries(); writeUnsignedVarInt(size, out); return size; } private void writeBbox(Geometry geom, DataOutput out, TWKBHeader header) throws IOException { final int dimensions = header.getDimensions(); final double[] boundsCoordinates = computeEnvelope(geom, dimensions); for (int d = 0; d < dimensions; d++) { final int precision = header.getPrecision(d); double min = boundsCoordinates[2 * d]; double max = boundsCoordinates[2 * d + 1]; long preciseMin = writeOrdinate(min, 0, precision, out); writeOrdinate(max, preciseMin, precision, out); } } private static double[] computeEnvelope(Geometry geom, int dimensions) { BoundsExtractor extractor = new BoundsExtractor(dimensions); geom.apply(extractor); return extractor.ordinates; } private static TWKBHeader setDimensions(Geometry g, TWKBHeader header) { if (g.isEmpty()) { return header.setHasZ(false).setHasM(false); } if (g instanceof Point) { return setDimensions(((Point) g).getCoordinateSequence(), header); } if (g instanceof LineString) { return setDimensions(((LineString) g).getCoordinateSequence(), header); } if (g instanceof Polygon) { return setDimensions(((Polygon) g).getExteriorRing().getCoordinateSequence(), header); } return setDimensions(g.getGeometryN(0), header); } private static TWKBHeader setDimensions(CoordinateSequence seq, TWKBHeader header) { boolean hasZ = seq.hasZ(); boolean hasM = seq.hasM(); return header.setHasZ(hasZ).setHasM(hasM); } private static class BufferedDataOutput extends DataOutputStream { public BufferedDataOutput() { super(new ByteArrayOutputStream()); } public byte[] content() { return ((ByteArrayOutputStream) out).toByteArray(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy