/*
* Copyright (C) 2017.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 or
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
package uk.me.parabola.imgfmt.app.dem;
import java.io.ByteArrayOutputStream;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.mkgmap.reader.hgt.HGTReader;
/**
* This keeps the bit stream data and header info for a single DEM tile.
* A tile contains the height values for a number of points organised in a width * height matrix.
* The values of the matrix are not stored directly. Instead, a compression algorithm is used to reduce
* the size of the needed bit stream. The algorithm uses different methods, mainly a delta encoding, run length encoding
* and a variable number of bits and encoding methods to store the actual value.
* The actually used methods are not stored, instead they are predicted from previously stored values (if any) and header data.
* Both the encoder and decoder have to implement exactly the same prediction algorithm.
* As of 2017-12-13 only a subset of the algorithms used by Garmin is understood, but it seems to be enough to store valid data.
*
* This code is based on the program and documentation created by Frank Stinner.
*
* @author Gerd Petermann
*
*/
public class DEMTile
{
private ByteArrayOutputStream bits
;
private int[] heights
;
private final int height
;
private final int width
;
private int offset
; // offset from section.dataOffset2
private final int baseHeight
; // base or minimum height in this tile
private final int maxDeltaHeight
; // delta between max height and base height
private final int encodingType
; // determines how the highest values are displayed
private final boolean hasData
; // not all voids
private int bitPos
;
private byte currByte
;
private int currPlateauTablePos
; // current position in plateau tables
private CalcType currCalcType
;
private final static boolean DEBUG =
false;
private StringBuilder bs
;
// fields used for debugging
private final int tileNumberLat
;
private final int tileNumberLon
;
private enum EncType
{
HYBRID, LEN
}
private enum WrapType
{
WRAP_0, WRAP_1, WRAP_2
}
private enum CalcType
{
CALC_P_LEN, CALC_STD, CALC_PLATEAU_ZERO, CALC_PLATEAU_NON_ZERO
}
static final int[] plateauUnit =
{ 1,
1,
1,
1,
2,
2,
2,
2,
4,
4,
4,
4,
8,
8,
8,
8,
16,
16,
32,
32,
64,
64,
128 };
static final int[] plateauBinBits =
{ 0,
0,
0,
1,
1,
1,
1,
2,
2,
2,
2,
3,
3,
3,
3,
4,
4,
5,
5,
6,
6,
7,
8 };
public DEMTile
(int col,
int row,
int width,
int height,
short[] realHeights
) {
this.
width = width
;
this.
height = height
;
this.
tileNumberLon = col
;
this.
tileNumberLat = row
;
// check values in matrix
int min =
Integer.
MAX_VALUE;
int max =
Integer.
MIN_VALUE;
int countInvalid =
0;
for (int h : realHeights
) {
if (h == HGTReader.
UNDEF)
++countInvalid
;
else {
if (h
> max
)
max = h
;
if (h
< min
)
min = h
;
}
}
if (min ==
Integer.
MAX_VALUE) {
// all values are invalid
hasData =
false;
encodingType =
2;
min =
0;
max =
0;
} else if (countInvalid
> 0) {
// some values are invalid
hasData =
true;
encodingType =
2; // don't display highest value
max++
;
} else {
// all height values are valid
hasData =
true;
encodingType =
0;
}
this.
baseHeight = min
;
this.
maxDeltaHeight = max - min
;
if (min == max
) {
return; // all heights equal
}
createBitStream
(realHeights
);
}
public boolean hasValidHeights
() {
return hasData
;
}
public int getBaseHeight
() {
return baseHeight
;
}
public int getMaxHeight
() {
return baseHeight + maxDeltaHeight -
(encodingType ==
0 ? 0 :
1);
}
private void createBitStream
(short[] realHeights
) {
bits =
new ByteArrayOutputStream(1024);
heights =
new int[realHeights.
length];
// normalise the height matrix
for (int i =
0; i
< realHeights.
length; i++
) {
if (realHeights
[i
] == HGTReader.
UNDEF)
heights
[i
] = maxDeltaHeight
;
else
heights
[i
] =
(realHeights
[i
] - baseHeight
);
}
// all values in heights are now expected to be between 0 .. maxDeltaHeight
if (DEBUG
) {
bs =
new StringBuilder();
}
encodeDeltas
();
// cleanup
bs =
null;
heights =
null;
}
private void addBit
(boolean bit
) {
if (DEBUG
) {
bs.
append(bit
? '1':
'0');
}
if (bit
) {
currByte |=
1 << (7-bitPos
);
}
bitPos++
;
if (bitPos
> 7) {
bitPos =
0;
bits.
write(currByte
);
currByte =
0;
}
}
/**
* The main loop to calculate the bit stream data.
*/
private void encodeDeltas
() {
int pos =
0;
currCalcType =
null;
ValPredicter encStandard =
new ValPredicter
(CalcType.
CALC_STD, maxDeltaHeight
);
ValPredicter encPlateauF0 =
new ValPredicter
(CalcType.
CALC_PLATEAU_ZERO, maxDeltaHeight
);
ValPredicter encPlateauF1 =
new ValPredicter
(CalcType.
CALC_PLATEAU_NON_ZERO, maxDeltaHeight
);
ValPredicter encoder =
null;
boolean writeFollower =
false;
while (pos
< heights.
length) {
if (DEBUG
) {
bs.
setLength(0);
}
int n = pos
% width
;
int m = pos / width
;
int hUpper = getHeight
(n, m -
1);
int hLeft = getHeight
(n -
1, m
);
int dDiff = hUpper - hLeft
;
if (writeFollower
) {
encoder =
(dDiff ==
0) ? encPlateauF0 : encPlateauF1
;
writeFollower =
false;
} else if (dDiff ==
0) {
currCalcType = CalcType.
CALC_P_LEN;
int pLen = calcPlateauLen
(n, m
);
writePlateauLen
(pLen, n
);
pos += pLen
;
writeFollower =
(pos
% width
!=
0 || pLen ==
0);
continue;
} else {
encoder = encStandard
;
}
currCalcType = encoder.
type;
encoder.
setDDiff(dDiff
);
int v
;
int h = getHeight
(n, m
);
if (currCalcType == CalcType.
CALC_STD) {
int predict
;
int hUpLeft = getHeight
(n-
1, m-
1);
int hdiffUp = hUpper- hUpLeft
;
if (hdiffUp
>= maxDeltaHeight - hLeft
) {
predict = -
1;
} else if (hdiffUp
<= -hLeft
) {
predict =
0;
} else {
predict = hLeft + hdiffUp
;
}
if (dDiff
> 0)
v = -h + predict
;
else
v = h -predict
;
} else {
// plateau follower: predicted value is upper height
v = h - hUpper
;
}
if (DEBUG
) {
bs.
setLength(0);
}
encoder.
write(v
);
pos++
;
}
if (bitPos
> 0)
bits.
write(currByte
);
}
/**
* Add the bits for a given plateau length using the current value of lastTablePos
* @param pLen
*/
private void writePlateauLen
(int pLen,
int col
) {
int len = pLen
;
int x = col
;
if (col + len
>= width
) {
// this is not really needed but sometimes produces fewer bits
// compared to the loop in the else branch
while (x
< width
) {
int unit = plateauUnit
[currPlateauTablePos++
];
len -= unit
;
x += unit
;
addBit
(true);
}
if (x
!= width
) {
currPlateauTablePos--
;
}
} else {
while (true) {
int unit = plateauUnit
[currPlateauTablePos
];
if (len
< unit
)
break;
currPlateauTablePos++
;
len -= unit
;
addBit
(true);
x += unit
;
if (x
> width
)
currPlateauTablePos--
;
if (x
>= width
) {
return;
}
}
if (currPlateauTablePos
> 0)
currPlateauTablePos--
;
addBit
(false); // separator bit
int binBits = plateauBinBits
[currPlateauTablePos
];
if (binBits
> 0) {
writeValAsBin
(Math.
abs(len
), binBits
);
}
}
}
/**
* Write an unsigned binary value with the given number of bits, MSB first.
* @param val
* @param numBits
*/
private void writeValAsBin
(int val,
int numBits
) {
if (numBits ==
0 && val ==
0)
return;
int t =
1 << (numBits -
1);
if (val
>= t
<< 1)
throw new MapFailedException
("Number too big for binary encoding with " + numBits +
" bits:" + val
);
while (t
> 0) {
addBit
((val
& t
) !=
0);
t
>>=
1;
}
}
/**
* Write a length encoded value as a sequence of 0-bits followed by a 1-bit.
* @param val
*/
private void writeNumberOfZeroBits
(int val
) {
for (int i =
0; i
< val
; i++
)
addBit
(false);
addBit
(true); // terminating 1-bit
}
/**
* Write an unsigned binary value with the given number of bits, MSB first.
* @param val
* @param hunit
* @param maxZeroBits
* @return
*/
private boolean writeValHybrid
(int val,
int hunit,
int maxZeroBits
) {
assert hunit
> 0;
assert Integer.
bitCount(hunit
) ==
1;
int numBits =
Integer.
numberOfTrailingZeros(hunit
);
int binPart
;
int lenPart
;
if (val
> 0) {
binPart =
(val -
1) % hunit
;
lenPart =
(val -
1 - binPart
) / hunit
;
} else {
binPart = -val
% hunit
;
lenPart =
(-val - binPart
) / hunit
;
}
if (lenPart
<= maxZeroBits
) {
writeNumberOfZeroBits
(lenPart
); // write length encoded part
writeValAsBin
(binPart, numBits
); // write binary encoded part
addBit
(val
> 0); // sign bit, 1 means positive
return true;
}
return false;
}
/**
* Write a signed binary value with the given number of bits, MSB first, sign bit last
* @param val
* @param numZeroBits number of zero bits that is considered valid
*/
private void writeValBigBin
(int val,
int numZeroBits
) {
// signal big bin by writing an invalid number of zero bits
writeNumberOfZeroBits
(numZeroBits +
1);
int bits = getBigBinBits
(maxDeltaHeight
);
if (val
< 0)
writeValAsBin
(-val -
1, bits -
1);
else
writeValAsBin
(val -
1, bits -
1);
addBit
(val
<=
0); // sign bit, 0 means positive
}
/**
* A plateau is a sequence of equal delta values. Calculate the length.
* @param col current column
* @param row current row
* @return the length of the plateau, which might be zero.
*/
private int calcPlateauLen
(int col,
int row
) {
int len =
0;
int v = getHeight
(col-
1, row
);
while (col + len
< width
) {
if (v == getHeight
(col + len, row
)) {
++len
;
} else
break;
}
return len
;
}
private int getHeight
(int col,
int row
) {
if (heights ==
null)
return 0;
if (row
< 0) {
// virtual 1st row
return 0;
}
if (col
< 0) {
return row ==
0 ? 0 : heights
[(row -
1) * width
];
}
return heights
[col + row
* width
];
}
/**
* Write tile header with the given field sizes
* @param writer
* @param recordDesc gives the field sizes
*/
public void writeHeader
(ImgFileWriter writer,
int recordDesc
) {
if (maxDeltaHeight ==
0)
offset =
0;
int offsetSize =
(recordDesc
& 0x3
) +
1;
int baseSize =
((recordDesc
& 0x4
) >> 2) +
1;
int deltaSize =
((recordDesc
& 0x8
) >> 3) +
1;
boolean hasExtra =
(recordDesc
& 0x10
) !=
0;
writer.
putNu(offsetSize, offset
);
if (baseSize ==
1)
writer.
put1s(baseHeight
);
else
writer.
put2s(baseHeight
);
writer.
putNu(deltaSize, maxDeltaHeight
);
if (hasExtra
)
writer.
put1u(encodingType
);
}
public void writeBitStreamData
(ImgFileWriter writer
) {
if (bits
!=
null) {
writer.
put(bits.
toByteArray());
}
}
private static int evaluateData
(int oldsum,
int elemcount,
int newdata,
int region
) {
switch (region
) {
case 0:
return -
1 - oldsum - elemcount
;
case 1:
return 2 * (newdata + elemcount
) +
3;
case 2:
return 2 * newdata -
1;
case 3:
return 2 * (newdata - elemcount
) -
5;
default:
return 1 - oldsum + elemcount
;
}
}
private static int getEvaluateDataRegion
(int oldsum,
int elemcount,
int newdata
) {
if (elemcount
< 63) {
if (newdata
< -
2 -
((oldsum +
3 * elemcount
) >> 1)) {
return 0;
} else if (newdata
< -
((oldsum + elemcount
) >> 1)) {
return 1;
} else if (newdata
< 2 -
((oldsum - elemcount
) >> 1)) {
return 2;
} else if (newdata
< 4 -
((oldsum -
3 * elemcount
) >> 1)) {
return 3;
} else {
return 4;
}
} else {
if (newdata
< -
2 -
((oldsum +
3 * elemcount
) >> 1)) {
return 0;
} else if (newdata
< -
((oldsum + elemcount
) >> 1) -
1) {
// special case in if !
return 1;
} else if (newdata
< 2 -
((oldsum - elemcount
) >> 1)) {
return 2;
} else if (newdata
< 4 -
((oldsum -
3 * elemcount
) >> 1)) {
return 3;
} else {
return 4;
}
}
}
/**
* This class keeps statistics about the previously encoded values and tries to predict the next value.
* It also calculates the encoding method to use.
* Based on findings of Frank Stinner.
*/
private class ValPredicter
{
private EncType encType
;
private WrapType wrapType
;
private int sumH
;
private int sumL
;
private int elemCount
;
private int hunit
;
private final CalcType type
;
private final int unitDelta
;
private int dDiff
;
private final int maxZeroBits
;
final int l0WrapUp, l0WrapDown, l1WrapUp, l1WrapDown, l2WrapUp, l2WrapDown,hWrapUp, hWrapDown
;
public ValPredicter
(CalcType type,
int maxHeight
) {
super();
this.
type = type
;
int numZeroBits = getMaxLengthZeroBits
(maxHeight
);
if (type == CalcType.
CALC_PLATEAU_NON_ZERO || type == CalcType.
CALC_PLATEAU_ZERO)
--numZeroBits
;
maxZeroBits = numZeroBits
;
unitDelta =
Math.
max(0, maxHeight - 0x5f
) / 0x40
; // TODO what does it mean?
encType = EncType.
HYBRID;
wrapType = WrapType.
WRAP_0;
hunit = getStartHUnit
(maxHeight
);
// calculate threshold values for wrapping
if (maxHeight
% 2 ==
0) {
l0WrapDown = maxHeight /
2;
l0WrapUp = -maxHeight /
2;
l1WrapDown =
(maxHeight +
2) /
2;
l1WrapUp = -maxHeight /
2;
l2WrapDown = maxHeight /
2;
l2WrapUp = -maxHeight /
2;
} else {
l0WrapDown =
(maxHeight +
1) /
2;
l0WrapUp = -
(maxHeight -
1) /
2;
l1WrapDown =
(maxHeight +
1) /
2;
l1WrapUp = -
(maxHeight -
1) /
2;
l2WrapDown =
(maxHeight -
1) /
2;
l2WrapUp = -
(maxHeight +
1) /
2;
}
hWrapDown =
(maxHeight +
1) /
2;
hWrapUp = -
(maxHeight -
1) /
2;
}
/**
* This
* @param data
* @return
*/
private int wrap
(int data
) {
int v = data
;
int down,up
;
if (encType == EncType.
HYBRID) {
down = hWrapDown
;
up = hWrapUp
;
} else {
if (wrapType == WrapType.
WRAP_0) {
down = l0WrapDown
;
up = l0WrapUp
;
} else if (wrapType == WrapType.
WRAP_1) {
down = l1WrapDown
;
up = l1WrapUp
;
} else {
down = l2WrapDown
;
up = l2WrapUp
;
}
}
if (v
> down
)
v = v -
(maxDeltaHeight +
1);
if (v
< up
)
v = v + maxDeltaHeight +
1;
return v
;
}
public void write
(int val
) {
int wrapped = wrap
(val
);
int delta1 = wrapped
;
if (type == CalcType.
CALC_PLATEAU_ZERO) {
if (delta1
<=
0)
delta1++
;
} else if (type == CalcType.
CALC_PLATEAU_NON_ZERO) {
if (dDiff
> 0) {
delta1 = -delta1
;
}
}
int delta2
;
if (wrapType == WrapType.
WRAP_0)
delta2 = delta1
;
else if (wrapType == WrapType.
WRAP_1)
delta2 =
1 - delta1
;
else delta2 = -delta1
;
boolean written =
false;
if (encType == EncType.
HYBRID) {
written = writeValHybrid
(delta2, hunit, getCurrentMaxZeroBits
());
} else {
// EncType.LEN
// 2 * Math.Abs(data) - (Math.Sign(data) + 1) / 2
int n0
;
if (delta2
< 0) {
n0 = -delta2
* 2;
} else if (delta2
> 0){
n0 =
(delta2 -
1) * 2 +
1;
} else {
n0 =
0;
}
if (n0
<= getCurrentMaxZeroBits
()) {
writeNumberOfZeroBits
(n0
);
written =
true;
}
}
if (!written
)
writeValBigBin
(delta2, getCurrentMaxZeroBits
());
processVal
(delta1
);
}
/**
* This looks wrong but seems to be needed. For a plateau follower we reduce the max value.
* The effect seems to be that a Big Bin is written although it would fit in the normal number.
* Maybe a BigBin signals that a flag should be reset or currPlateauTablePos is too high?
* @return number of 0 bits that are considered okay.
*/
int getCurrentMaxZeroBits
() {
if (currCalcType == CalcType.
CALC_PLATEAU_NON_ZERO || currCalcType == CalcType.
CALC_PLATEAU_ZERO)
return maxZeroBits - plateauBinBits
[currPlateauTablePos
];
return maxZeroBits
;
}
private void processVal
(int delta1
) {
if (type == CalcType.
CALC_STD) {
// calculate threshold sum hybrid
sumH += delta1
> 0 ? delta1 : -delta1
;
if (sumH + unitDelta +
1 >= 0xffff
)
sumH -= 0x10000
;
// calculate threshold sum for length encoding
int evalRegion = -
1;
int workData = delta1
;
if (elemCount ==
63) {
evalRegion = getEvaluateDataRegion
(sumL, elemCount, delta1
);
boolean datagerade = delta1
% 2 ==
0;
boolean sumL1 =
(sumL -
1) % 4 ==
0;
switch (evalRegion
) {
case 0:
case 2:
case 4:
if ((sumL1
&& !datagerade
) ||
(!sumL1
&& datagerade
)) {
workData++
;
}
break;
case 1:
workData++
;
if ((sumL1
&& !datagerade
) ||
(!sumL1
&& datagerade
)) {
workData++
;
}
break;
case 3:
if ((sumL1
&& datagerade
) ||
(!sumL1
&& !datagerade
)) {
workData--
;
}
break;
}
}
if (evalRegion
< 0)
evalRegion = getEvaluateDataRegion
(sumL, elemCount, workData
);
int eval = evaluateData
(sumL, elemCount, workData, evalRegion
);
sumL += eval
;
// now update elem counter
elemCount++
;
if (elemCount ==
64) {
elemCount =
32;
sumH =
((sumH - unitDelta
) >> 1) -
1;
sumL /=
2;
}
// calculate new hunit
hunit = normalizeHUnit
((unitDelta + sumH +
1) /
(elemCount +
1));
// finally determine encode type for next value
wrapType = WrapType.
WRAP_0;
if (hunit
> 0) {
encType = EncType.
HYBRID;
} else {
encType = EncType.
LEN;
if (sumL
> 0)
wrapType = WrapType.
WRAP_1;
}
} else if (type == CalcType.
CALC_PLATEAU_ZERO) {
// calculate threshold sum hybrid
sumH += delta1
> 0 ? delta1 :
1 - delta1
; // different to standard
if (sumH + unitDelta +
1 >= 0xffff
)
sumH -= 0x10000
;
// calculate threshold sum for length encoding
sumL += delta1
<=
0 ? -
1 :
1;
elemCount++
;
if (elemCount ==
64) {
elemCount =
32;
sumH =
((sumH - unitDelta
) >> 1) -
1;
sumL /=
2;
if (sumL
% 2 !=
0) {
sumL++
;
}
}
// calculate new hunit
hunit = normalizeHUnit
((unitDelta + sumH +
1 - elemCount /
2) /
(elemCount +
1));
// finally determine encode type for next value
wrapType = WrapType.
WRAP_0;
if (hunit
> 0) {
encType = EncType.
HYBRID;
} else {
encType = EncType.
LEN;
if (sumL
>=
0)
wrapType = WrapType.
WRAP_1;
}
} else {
assert type == CalcType.
CALC_PLATEAU_NON_ZERO;
// calculate threshold sum hybrid
sumH += delta1
< 0 ? -delta1 : delta1
; // simple absolute sum
if (sumH + unitDelta +
1 >= 0xffff
)
sumH -= 0x10000
;
// calculate threshold sum for length encoding
sumL += delta1
<=
0 ? -
1 :
1;
elemCount++
;
if (elemCount ==
64) {
elemCount =
32;
sumH =
((sumH - unitDelta
) >> 1) -
1;
sumL /=
2;
if (sumL
% 2 !=
0) {
sumL--
; // different to CALC_PLATEAU_ZERO !
}
}
// calculate new hunit
hunit = normalizeHUnit
((unitDelta + sumH +
1) /
(elemCount +
1));
// finally determine encode type for next value
wrapType = WrapType.
WRAP_0;
if (hunit
> 0) {
encType = EncType.
HYBRID;
} else {
encType = EncType.
LEN;
if (sumL
<=
0)
wrapType = WrapType.
WRAP_2;
}
}
}
public void setDDiff
(int dDiff
) {
this.
dDiff = dDiff
;
}
}
/**
* Calculate the longest sequence of zero bits that is considered as a valid number.
* The number depends on the field maxDeltaHeight in the header structure.
* @param maxHeight
* @return the number of bits
*/
private static int getMaxLengthZeroBits
(int maxHeight
) {
if (maxHeight
< 2)
return 15;
if (maxHeight
< 4)
return 16;
if (maxHeight
< 8)
return 17;
if (maxHeight
< 16)
return 18;
if (maxHeight
< 32)
return 19;
if (maxHeight
< 64)
return 20;
if (maxHeight
< 128)
return 21;
if (maxHeight
< 256)
return 22;
if (maxHeight
< 512)
return 25;
if (maxHeight
< 1024)
return 28;
if (maxHeight
< 2048)
return 31;
if (maxHeight
< 4096)
return 34;
if (maxHeight
< 8192)
return 37;
if (maxHeight
< 16384)
return 40;
return 43;
}
/**
* Calculate the start hunit value.
* The number depends on the field maxDeltaHeight in the header structure.
* @param maxHeight
* @return the hunit value
*/
private static int getStartHUnit
(int maxHeight
) {
if (maxHeight
< 0x9f
)
return 1;
else if (maxHeight
< 0x11f
)
return 2;
else if (maxHeight
< 0x21f
)
return 4;
else if (maxHeight
< 0x41f
)
return 8;
else if (maxHeight
< 0x81f
)
return 16;
else if (maxHeight
< 0x101f
)
return 32;
else if (maxHeight
< 0x201f
)
return 64;
else if (maxHeight
< 0x401f
)
return 128;
return 256;
}
static int getBigBinBits
(int maxHeight
) {
int bits
;
if (maxHeight
<16384) {
int n =
Integer.
highestOneBit(maxHeight
);
bits =
Integer.
numberOfTrailingZeros(n
) +
1;
} else
bits =
15;
return bits
;
// if (maxHeight < 2)
// return 1;
// else if (maxHeight < 4)
// return 2;
// else if (maxHeight < 8)
// return 3;
// else if (maxHeight < 16)
// return 4;
// else if (maxHeight < 32)
// return 5;
// else if (maxHeight < 64)
// return 6;
// else if (maxHeight < 128)
// return 7;
// else if (maxHeight < 256)
// return 8;
// else if (maxHeight < 512)
// return 9;
// else if (maxHeight < 1024)
// return 10;
// else if (maxHeight < 2048)
// return 11;
// else if (maxHeight < 4096)
// return 12;
// else if (maxHeight < 8192)
// return 13;
// else if (maxHeight < 16384)
// return 14;
// else
// return 15;
}
/**
* The hunit value must be a positive multiple of 2 (including 0).
* @param hu the result of a division to calculate the new hunit
* @return the normalised value
*/
private static int normalizeHUnit
(int hu
) {
if (hu
> 0) {
return Integer.
highestOneBit(hu
);
}
return 0;
}
public int getBitStreamLen
() {
if (bits ==
null)
return 0;
else
return bits.
size();
}
public void setOffset
(int off
) {
this.
offset = off
;
}
@
Override
public String toString
() {
return tileNumberLat +
" " + tileNumberLon +
" w=" + width +
" h=" + height
;
}
public int getMaxDeltaHeight
() {
return maxDeltaHeight
;
}
public int getEncodingType
() {
return encodingType
;
}
public byte[] getBitStream
() {
return bits.
toByteArray();
}
}