/* ---------------------------------------------------------------------- */
/* tileset.h : Header with cross-platform "tgxm" tileset file definitions */
/* ---------------------------------------------------------------------- */
/* The file format and content specifications in this file were developed
by Geodyssey Limited of Calgary, Alberta, Canada. They are provided
to application developers free of charge and free of copyright
restriction in the hope that they will enable the development of
robust cross-platform geographical applications. Attribution of this
design to Geodyssey Limited at www.geodyssey.com would be appreciated.
*/
#ifndef TILESET_H
#define TILESET_H 1
/* The suffix 'tgxm' suggests tiled, geo-referenced, compressed bitmaps.
To facilitate cross-platform use, file names must be less than 32
characters, no blanks, first character alpha, the rest may be
alphabetics, numerals, and the underscore '_' only. */
#define TS_FILE_BIG_ENDIAN "tgxm"
#define TS_FILE_LITTLE_ENDIAN "mxgt"
#define TS_FILE_SUFFIX ".tgxm" /* always tgxm, all lowercase */
#define TS_IMAGE_ZLIB 1
#define TS_IMAGE_JPEG 2
#define TS_IMAGE_ZLIB_JPEG 3
#define TS_PROJ_RECTANGULAR 1
#define TS_PROJ_AZIMUTAL 2
#define TS_COLOR_BLACKWHITE 1
#define TS_COLOR_PALETTE256 2
#define TS_COLOR_RGB 3
/* All integers stored in the file are 4 bytes wide; unsigned characters
one byte. Headers (where integers occur, as opposed to the "body" of
the compressed map image) are stored twice - in both big and little
endian "mirrors"; so that the file can be easily processed in read-only
memory-mapped mode on either platform. The order of headers is not
prescribed; only that both MUST be present. Applications using the file
can locate the preferred header block by checking the file "stamp":
it must be "tgxm" for big-endian, "mxgt" for little-endian. If the
first header block is not of the required endianess, the length to be
skipped can be computed using the data in the first block and the
TS_I4_RVRS(i) macro.
All structures are tightly packed.
To avoid storing floating point numbers in the file, angles in radians
in the range of -PI < a < +PI are packed into 4-byte integers, with
a resolution of about 1 cm on the Earth's surface. Thus a program on
any platform, with any internal floating point format, can derive
all angular (latitude/longitude) measurements from/to the integers
stored in the file using TS_PI and the macros following it.
It is assumed that an application working with "rectangular projection"
tilesets will keep all longitudes relative to the central meridian of
the tileset, and convert the values on input or output to the meridian
of the user's choice (Greenwich, Paris, Pulkovo, Mecca or whatever)
using the tileset header value in tsRect.centerLng - which is the
tileset central meridian relative (-PI +PI) to the Greenwich
meridian. This will get around the necessity to constantly deal with
the cyclic nature of longitude measure: trivial when computations are
performed using real numbers, but awkward when using integer measure
that employs nearly the full internal representation range.
*/
#define TS_PI 3.14159265358979323846
#define TS_PIHALF 1.57079632679489661923
#define TS_I4A_UNDEF -2147483647
#define TS_I4A_SCALE_INT 2147483640
#define TS_I4A_SCALE_DBL 2147483640.0
#define TS_AngOfI4(i) ((((double)(i)) / TS_I4A_SCALE_DBL) * TS_PI)
#define TS_I4OfAng(r) ((int)(((r) / TS_PI) * TS_I4A_SCALE_DBL))
/* Following is only for reporting convenience (no file items use this): */
#define TS_DEG2RAD (TS_PI / 180.0)
#define TS_RAD2DEG (180.0 / TS_PI)
#define TS_DRN(l) (1.000000001 * TS_RAD2DEG * TS_AngOfI4(l)) /* degrees */
#define TS_DAB(l) (TS_DRN(abs(l))) /* absolute */
#define TS_DAW(l) ((double)((int)TS_DAB(l))) /* absolute whole deg */
#define TS_SIGN_LAT(l) ((l < 0) ? 's' : 'n')
#define TS_SIGN_LNG(l) ((l < 0) ? 'w' : 'e')
#define TS_INTDEG_LL(l) ((int)(TS_DAB(l))) /* unsigned integer degrees */
#define TS_DECMIN_LL(l) (60.0 * ((TS_DAB(l) - TS_DAW(l)))) /* decimal minutes, to follow */
/* For GPS stream manipulations */
#define TS_MIN2RAD (TS_PI / 10800.0)
#define TS_FUZZA 4.e-11 /* in radians, corresponding to Earth surface millimeter */
/* Defined here for convenience, as it might be common to different
programs passing a location obtained from a GPS device in a common,
concise form: */
struct tsGpsLocation { /* location/elevation/time */
int lat; /* scaled integer... */
int lng; /* ...cf. TS_I4OfAng(r) */
int elevation; /* centimeters above/below ellipsoid */
unsigned time; /* seconds after TS_EPOCH (up to AD. 2136) */
};
/* For reversing order of bytes in a 4-byte integer */
#define TS_I4_RVRS(i) (((i) << 24) | (((unsigned)(i)) >> 24) | (((i) & 0x0000ff00) << 8) | (((i) & 0x00ff0000) >> 8))
/* Macro to swap red and green byte in a 24-bit pixel. (Win32 is BGR) */
#define TS_SWAP_RB(p) {(*p)^=(*(p+2)); (*(p+2))^=(*p); (*p)^= *(p+2);}
/* Macros to produce a simple "human-readable" decimal degree latitude
and longitude, given scaled integers in radian measure */
#define TS_DECDEG_LAT(l) (TS_RAD2DEG * TS_AngOfI4(l)) /* always global */
#define TS_DECDEG_LNG1(l) (TS_RAD2DEG * TS_AngOfI4(l)) /* global or... */
#define TS_DECDEG_LNG2(l0,ld) (TS_RAD2DEG * TS_AngOfI4(ts_LongitudeGlobal(l0, ld))) /* ...tileset center and delta */
/* Various utility macros: */
/* Tileset width, height in pixels, given pointer to tileset header */
#define TS_PX_WIDTH(p) ((p)->tileColumns * (p)->tileWidth)
#define TS_PX_HEIGHT(p) ((p)->tileRows * (p)->tileHeight)
/* Undefined pixel coordinate value flag */
#define TS_PIX_UNDEF -2147483647
/* File Structure and Geometry:
----------------------------
In brief, a tgxm file is composed of:
File header (one tsHdr structure)
Geometry header (one tsRect structure)
Row table (tileRows + 1 integers)
Tile table (tileTableSize * tsTile1 or tsTile2 structures)
Mirror of all of the above, in the opposite endianess.
Palette (optional, 256 tsColor structures)
Transparency color (optional, only for IMAGE_ZLIB_JPEG images)
Descriptions
Pixel data
A file starts with the tsHdr file header structure. Next (in the case of
the "rectangular" projection, the only one implemented so far) follows
one tsRect structure, immediately followed by tileRows + 1 four-byte
integers, ordered from South to North ('up'), defining the South limit
of each row in latitude on the ground and, as the last value in the
table, the North limit of the northernmost row.
Next are tsTile1 (for TS_IMAGE_ZLIB or TS_IMAGE_JPEG types) or
tsTile2 (for TS_IMAGE_ZLIB_JPEG type) structures, in "rectangular
Morton" order.
Next (in the case of colorDepth == TS_COLOR_PALETTE256) are
256 tsPalette entries.
Next (in the case of imageType== TS_IMAGE_ZLIB_JPEG) is one
tsPalette entry, defining the transparent color of the lossless
layer.
Next are two short, fixed length description lines.
Following are tileColumns x tileRows number of compressed tile
pixel blocks; their individual sizes and offsets from the file
start are given in the respective tsTile structure.
Pixels lines are ordered opposite than tile rows: tile rows start
at the South boundary of the set whereas pixel lines start at the
North boundary of the tile.
"Rectangular" projection geometry specifications:
Given the number of tile columns, together with the tileset range
of longitude, determine the tile longitude width, which will be the
same for all tiles in the set.
Each set has at least one row of tiles immediately above and
one immediately below the mid-latitude parallel. Each tile North
of the set mid-latitude has an extent in latitude such that its
"height" (North/South extent) on the ground is equal to the ground
length of its south bounding parallel, and each tile South of the
set mid-latitude has an extent in latitude such that its latitude
extent is equal to the length of its North bounding parallel.
As many rows North and South of the set mid-latitude will be
required as are necessary to reach the North and South limits
of the tileset. Note that these two numbers might not be equal.
This method will produce a "near-conformal" map image; yet it
is very simple to implement. All the geodetic computations required
consist of the determination of the circumference of a given
parallel of latitude and the length of the meridian arc between two
given latitudes. This in turn makes it easy for various programs
to produce tilesets with the same geometry, given the same
longitude range, number of tile columns and mid-latitude.
(If the precision requirements are only moderate, even spherical
Earth computations might yield tilesets with servicable geometry.
However, such tilesets could not be considered to conform to the
TileShare "Rectangular Projection" standard).
Tile order:
-----------
The tile row table is in South to North order. If the number of rows
or columns is not an even power of two, the tile index table will
be somewhat sparse, but can be entered directly using tile column
and row as arguments. Tiles in the file are stored in neither row-
nor column-major order, but rather in a "rectangular Morton" order.
(cf. ts_Morton()) - as the spatial clustering of tile pixel blocks
in the file is the most important design parameter. The offset and
size values in the unused index table slots are both set to xffffffff
and can be ignored. The total size of the table is given explicitly
in the tileTableSize header structure item.
Tile size:
----------
The present implementation of the utility functions assumes that the
tile will have less than 64K pixels in either dimension. While there
is no such limitation built into the file definition, it is probably
reasonable to stick to this limit; especially since it is unlikely
that tiles larger than this would be of much practical use.
Color and tileset levels:
-------------------------
Lossless compression is based on the zlib library which is well suited
for maps. Lossy compression is based on the JPEG library which is best
avoided as a single-layer (Level 2, see below) tileset, but is a
good coice for maps with shaded background in level 3 tilesets.
For efficient lossless compression it is probably worth reducing the
number of colors of the original scans.
Tilesets can be of three levels, in increased order of sophistication
and programming complexity:
Level 1: Single layer, lossless (zlib compressed)
Level 2: Single layer, lossy (JPEG library compressed)
Level 3: Combined background and linework layers.
The first level is the most common one and is the only that should be
assumed to be supported by all applications working with tgxm files.
The second level is similar, except that it is using JPEG library
compression. The third type consists of two layers: one for the map
background with a continuously changing color, the other for the
linework and labels. A linework layer is assumed to have a transparent
color. Such bitmaps could only be created from the scanned material
with some rather involved image processing. The combined layer type
is therefore to be considered as 'experimental'.
colorDepth applies only to the lossless layer; JPEG layer is always RGB.
File Stamp:
-----------
If the file stamp is created as:
tsHdr.fileStamp = 16777216 * 't' + 65536 * 'g' + 256 * 'x' + 'm'
it will be "tgxm" for the big-endian header, "mxgt" for the
little-endian preamble.
Description:
------------
All creators are required to place two short lines of descriptive
data in the file. Note however that the access to all data following
the description is based on the offsets in the tile table; thus
any extraneous data between the description and first tile (always
tile 0 in the tile table) has no negative impact on the file
integrity, only on its size.
*/
struct tsHdr { /* Tile-set file header */
int fileStamp; /* filestamp, "tgxm" or "mxgt" */
int reserved; /* always 0 */
int id; /* creator or user controlled identifier */
int projection; /* TS_PROJ_RECTANGULAR only for now */
int tileWidth, tileHeight; /* in pixels */
int tileColumns, tileRows; /* tile rows and columns */
int colorDepth; /* TS_COLOR_... (1, 2, or 3) */
int imageType; /* TS_IMAGE_ZLIB only for now */
}; /* 10 ints, 40 bytes */
struct tsRect { /* rectangular set sub-header, row table must follow */
int centerLat; /* set center latitude, -PI/2 +PI/2 from Equator */
int centerLng; /* longitude, Greenwich meridian relative, -PI +PI */
int rangeLat, rangeLng; /* set range in latitude and longitude */
int lowLatY; /* tileset coordinate corresponding to low latitude */
int highLatY; /* as above, tileset high latitude */
}; /* 6 ints, 24 bytes, followed immediately by row table */
struct tsTile1 { /* Tile header */
int dataStart; /* compressed block offset from file start or ffffffff */
int dataLength; /* size in bytes, negative: uncompressed, 0: no tile */
}; /* 2 ints, 8 bytes */
struct tsTile2 { /* Tile header */
int dataStartJ; /* JPEG compressed block offset from file start */
int dataLengthJ; /* size of the above in bytes, negative: uncompressed */
int dataStartZ; /* zlib compressed block offset from file start */
int dataLengthZ; /* size of the above in bytes, negative: uncompressed */
}; /* 4 ints, 8 bytes */
struct tsColor { /* palette (always 256 color) table entry */
unsigned char red;
unsigned char green;
unsigned char blue;
unsigned char reserved; /* always 0 */
}; /* 4 bytes */
struct tsTrans { /* Transparency color */
unsigned char redTransparent; /* for IMAGE_ZLIB_JPEG... */
unsigned char greenTransparent; /* lossless layer */
unsigned char blueTransparent; /* transparent color */
unsigned char reserved; /* always 0 */
}; /* 4 bytes */
struct tsDescr { /* description */
char descr_l1[32]; /* ascii text description, line 1 */
char descr_l2[32]; /* ascii text description, line 2 */
}; /* 64 (at least) bytes */
/* Structures representing global point and tileset tile/pixel location: */
struct tsLatLng { /* Global (or tileset) latitude/longitude: */
int lat; /* integer-scaled radian, -PI/2 +PI/2 range */
int lng; /* as above, usually "normalized" to -PI +PI range */
}; /* 2 ints, 8 bytes */
/* All values in the following are non-negative, but signed integers are
used as an application is likely to use them in integer additions and
subtractions.
*/
struct tsPixel { /* tileset pixel x,y from South-West corner */
int pxl_x; /* West to East */
int pxl_y; /* South to North */
}; /* 4 ints, 16 bytes */
struct tsTilePixel { /* tileset "coordinates" (counts 0-relative): */
int tileColumn; /* West to East */
int tileRow; /* South to North */
int pxl_x; /* West to East */
int pxl_y; /* South to North */
}; /* 4 ints, 16 bytes */
/* Tileset mapping and utility functions, in tileset.c: */
int ts_PixelToTilePixel(const struct tsHdr *, const struct tsPixel *, struct tsTilePixel *);
int ts_MapRect(const struct tsHdr *, const struct tsRect *, const struct tsLatLng *, struct tsTilePixel *);
int ts_UnMapRect(const struct tsHdr *, const struct tsRect *, const struct tsTilePixel *, struct tsLatLng *);
int ts_PixelAdd(const struct tsHdr *, const struct tsRect *, const struct tsTilePixel *, int, int, struct tsTilePixel *);
int ts_LongitudeGlobal(int, int);
int ts_LongitudeTileset(int, int);
int ts_IntRat2(int, int, int);
int ts_IntRat3(int, int, int, int);
int ts_Morton(int, int, int, int);
#endif