Format of the Cvx BGL Files: Difference between revisions
m (→Algorithm: Fixing a minor but critical bug!) |
|||
| (42 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
= Introduction = | = Introduction = | ||
cvx*.bgl files can be read by the TmfViewer.exe application provided with the FS SDK. They contain TERRAIN_VECTOR_DB sections. These sections and subsections describe the different layers (roads, railways, water polygons, etc.) along with the geographical coordinates. | |||
<br/> | <br/> | ||
In this document, I will often take examples from the cvx2815.bgl file since that is the one I used to figure out the structure of the cxv file and it describes the area I live in. This file is located in: (....)\Microsoft Flight Simulator X\Scenery\0301\scenery. | In this document, I will often take examples from the cvx2815.bgl file since that is the one I used to figure out the structure of the cxv file and it describes the area I live in. This file is located in: (....)\Microsoft Flight Simulator X\Scenery\0301\scenery. | ||
Also refer to the document "FSX File structure" by '''Winfried Orthmann'''. It is verfy helpful. | |||
= BGL Common Format = | = BGL Common Format = | ||
All BGL files share the same generic format. A BGL file is made of a header, sections, subsections and subsection data. Subsections are children of sections. The sections are here to help us locate the subsections in the file. Data specific information is contained in the subsections data and their format is dependent on the section type. | All BGL files share the same generic format. A BGL file is made of a header, sections, subsections and subsection data. Subsections are children of sections. The sections are here to help us locate the subsections in the file. Data specific information is contained in the subsections data and their format is dependent on the section type. | ||
<br/>A BGL file always start with a header (Size = 0x38 bytes), followed by a list of section pointers. The number of section pointers is defined in the header. | <br/>A BGL file is really a big container where all kind of information can be stored (in the subsection).The meaning of the data contained in the subsections is known only by the application using it. Th only contraint is for the file to comply to the generic format described below. | ||
<br/><br/>A BGL file always start with a header (Size = 0x38 bytes), followed by a list of section pointers. The number of section pointers is defined in the header. | |||
<br/> | <br/> | ||
[[File:Cvx_BGLFormat.png]] | [[File:Cvx_BGLFormat.png]] | ||
| Line 27: | Line 30: | ||
| 0x08 | | 0x08 | ||
| 4 - DWORD | | 4 - DWORD | ||
| | | dwLowDateTime of the FILETIME structure.<br/>Date and Time the file was created<br/> | ||
The FILETIME structure represents the number of 100-nanosecond intervals since January 1, 1601<br/> | |||
See <span class="plainlinks">http://support.microsoft.com/kb/188768</span> | |||
|- | |- | ||
| 0x0C | | 0x0C | ||
| 4 - DWORD | | 4 - DWORD | ||
| | | dwHighDateTime of the FILETIME structure | ||
|- | |- | ||
| 0x10 | | 0x10 | ||
| Line 45: | Line 50: | ||
| Array[8] of unsigned integers (DWORD).<br/>Each value describes the bounding coordinates of a squared subarea.<br/> | | Array[8] of unsigned integers (DWORD).<br/>Each value describes the bounding coordinates of a squared subarea.<br/> | ||
Even if 8 slots are provided, it is not necessary to have all 8 values filled. The list stops at the first null (0x00000000) value.<br/> | Even if 8 slots are provided, it is not necessary to have all 8 values filled. The list stops at the first null (0x00000000) value.<br/> | ||
I don’t know what these subareas are used for.<br/>To compute the bounding coordinates, please see | I don’t know what these subareas are used for.<br/>To compute the bounding coordinates, please see [[#Computing the bounding coordinates from a DWORD value|Computing the bounding coordinates from a DWORD value]].<br/>To get the bounding coordinates of the square area covered by the file, just keep the minimal and maximal values of each bounding coordinates. | ||
|} | |} | ||
| Line 60: | Line 65: | ||
|- | |- | ||
| 0x00 | | 0x00 | ||
| <pre>01 02 92 19 | | <pre>01 02 92 19</pre> | ||
| Magic Number #1 | | Magic Number #1 | ||
|- | |- | ||
| Line 69: | Line 74: | ||
| 0x08 | | 0x08 | ||
| <pre>EF 82 DF E2</pre> | | <pre>EF 82 DF E2</pre> | ||
| | | Low = 3806298863 | ||
|- | |- | ||
| 0x0C | | 0x0C | ||
| <pre>E8 C7 C6 01</pre> | | <pre>E8 C7 C6 01</pre> | ||
| | | High = 29804520<br/> => February 27, 2007 | ||
|- | |- | ||
| 0x10 | | 0x10 | ||
| Line 124: | Line 129: | ||
== Sections == | == Sections == | ||
Following the header, at offset 0x38, there are as many sections as defined at offset 0x14 of the header. Each section has a size of 20 bytes and has the following structure:<br/> | Following the header, at offset 0x38, there are as many sections as defined at offset 0x14 of the header. A same file may contain sections of different type. Each section has a size of 20 bytes and has the following structure:<br/> | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
| Line 131: | Line 136: | ||
! Description | ! Description | ||
|- | |- | ||
|- valign="top" | |||
| 0x00 | | 0x00 | ||
| 4 - DWORD | | 4 - DWORD | ||
| Section type: one of the following values:<br/> | | Section type (as defined by Microsoft Flight Simulator): one of the following values:<br/> | ||
* None = 0x0 | * None = 0x0 | ||
* Copyright = 0x1 | * Copyright = 0x1 | ||
| Line 160: | Line 166: | ||
* PopulationDensity = 0x6C | * PopulationDensity = 0x6C | ||
* AutogenAnnotation = 0x6D | * AutogenAnnotation = 0x6D | ||
* TerrainIndex = 0x6E | * TerrainIndex = 0x6E (Note: According to George (Golf-HotelDelta), non-seasonal photo scenery compiled with resample also has this type. | ||
* TerrainTextureLookup = 0x6F | * TerrainTextureLookup = 0x6F | ||
* TerrainSeasonJan = 0x78 | * TerrainSeasonJan = 0x78 | ||
| Line 189: | Line 195: | ||
* FakeTypes = 0x2710 | * FakeTypes = 0x2710 | ||
* IcaoRunway = 0x2711 | * IcaoRunway = 0x2711 | ||
|- | |||
| 0x04 | |||
| 4 - DWORD | |||
| Unknown | |||
|- | |||
| 0x08 | |||
| 4 - DWORD | |||
| Number of subsections in the section. | |||
|- | |||
| 0x0C | |||
| 4 - DWORD | |||
| File offset = position in the file where the first subsection starts. | |||
|- | |||
| 0x10 | |||
| 4 - DWORD | |||
| Total Size (in bytes) of all the subsections. This value should be equal to : 16 * nbSubSections | |||
|} | |||
<br/> | |||
Note that a cvx*.bgl file have at least a section of type 101 (0x65) = TerrainVectorDb. | |||
=== Example === | |||
For example, in cvx2815.bgl, the only one section, at offset 0x38, has this: | |||
{| class="wikitable" | |||
|- | |||
! Offset | |||
! Values | |||
! Description | |||
|- | |||
| 0x38 | |||
| <pre>65 00 00 00</pre> | |||
| TerrainVectorDb = 101 = 0x65 | |||
|- | |||
| 0x3C | |||
| <pre>01 00 00 00</pre> | |||
| Unknown | |||
|- | |||
| 0x40 | |||
| <pre>8D 07 00 00</pre> | |||
| 0x078D = 1933 subsections in the section | |||
|- | |||
| 0x44 | |||
| <pre>01 CD 1F 00</pre> | |||
| 0x1FCD01 = File offset = position in the file where the first subsection starts. | |||
|- | |||
| 0x48 | |||
| <pre>D0 78 00 00</pre> | |||
| Size (in bytes) =0x78D0 = 30928 bytes | |||
|} | |} | ||
== Subsections == | == Subsections == | ||
A subsection contains information about the geographical area it covers. If also contains the file offset of the subsection’s data. All subsections of a same section are contiguous meaning that they are following each other in the file. | |||
<br/>A subsection has the following structure: | |||
{| class="wikitable" | |||
|- | |||
! Relative Offset | |||
! Number of bytes | |||
! Description | |||
|- | |||
| 0x00 | |||
| 4 - DWORD | |||
| Bounding coordinates of a squared area covered by this subsection. See Annexe A. | |||
|- | |||
| 0x04 | |||
| 4 - DWORD | |||
| Number of records contained in the subsection's data.<br/>It is 0 for TerrainVectorDb (which actually means only 1 record). | |||
|- | |||
| 0x08 | |||
| 4 - DWORD | |||
| File Offset = Position in the file of the subsection’s data | |||
|- | |||
| 0x0C | |||
| 4 - DWORD | |||
| Size of the subsection’s data | |||
|} | |||
<br/> | |||
The interpretation of the section data depends on the section type. | |||
=== Example === | |||
{| class="wikitable" | |||
|- | |||
! Offset | |||
! Values | |||
! Description | |||
|- | |||
|- valign="top" | |||
| 0x1FCD01 | |||
| <pre>00 FA 81 00</pre> | |||
| Bounding coordinates:<br/> | |||
* MinLatitude(Deg) = 47.63671875 | |||
* MaxLatitude(Deg) = 47.8125 | |||
* MinLongitude(Deg) =- 75.0 | |||
* MaxLongitude(Deg) = -74.765625 | |||
|- | |||
| 0x1FCD05 | |||
| <pre>00 00 00 00</pre> | |||
| Number of records = 0 | |||
|- | |||
| 0x1FCD09 | |||
| <pre>4C 00 00 00</pre> | |||
| 0x4C = File Offset = Position in the file of the subsection’s data. | |||
|- | |||
| 0x1FCD0D | |||
| <pre>DD 00 00 00</pre> | |||
| Size of the subsection’s data = 0xDD = 221 bytes. | |||
|} | |||
= Subsection for section of type Terrain_Vector_DB = | = Subsection for section of type Terrain_Vector_DB = | ||
This type of subsection contains values to retrieve geographical coordinates, organized as segments. These coordinates are used to draw lines (roads, railways, etc.) or polygons (lakes, airport bounds, etc.). | |||
<br/><br/> | |||
The section does not contain geographical coordinates per se but '''offsets''' to the lower left corner of the covered area (minimum latitude / longitude). The covered area is also defined in the subsection. | |||
<br/><br/> | |||
The data is organized in lists of pair values (One pair per geographical coordinate). Each pair can then be used to compute the final geographical coordinates. There are 3 ways to retrieve these lists of pairs. | |||
<br/><br/> | |||
The first value in a pair is longitude-related. The second value is latitude-related. | |||
<br/><br/> | |||
The algorithm to compute the geographical coordinates from a pair is: | |||
<pre> | |||
void convertToCoordinates (double longitude_related_Value, double latitude_related_Value) | |||
{ | |||
var deltaLongFactor = (MaxLongitudeDeg - MinLongitudeDeg) * 0.000030517578125; | |||
var deltaLatFactor = (MaxLatitudeDeg - MinLatitudeDeg) * 0.000030517578125; | |||
var LongitudeDeg = MinLongitudeDeg + (longitude_related_Value * deltaLongFactor); | |||
var LatitudeDeg = MinLongitudeDeg + (latitude_related_Value * deltaLatFactor); | |||
} | |||
</pre> | |||
== Subsection Header == | == Subsection Header == | ||
Each subsection starts with the following structure: | |||
{| class="wikitable" | |||
|- | |||
! Relative Offset | |||
! Number of bytes | |||
! Description | |||
|- | |||
| 0x00 | |||
| 4 - DWORD | |||
| 6<br/>I did not find a cvx file with a value other than 6. | |||
|- | |||
| 0x04 | |||
| 4 - DWORD | |||
| Bounding coordinates of a squared area covered by this subsection. See [[#Computing the bounding coordinates from a DWORD value|Computing the bounding coordinates from a DWORD value]]. | |||
|- | |||
| 0x08 | |||
| 4 - DWORD | |||
| 0<br/>I did not find a cvx file with a value other than 0. | |||
|- | |||
| 0x0C | |||
| 4 - DWORD | |||
| Number of entities.<br/>An entity is a list of segments. (a segment is a list of points). | |||
|- | |||
|- valign="top" | |||
| 0x10 | |||
| 4 - DWORD | |||
| Number of bytes in the signatures buffer.<br/>The signatures buffer contains some GUIDs identifying the segments of this subsection. Some GUID values (like the Texture) are not used by the TmfViewer application. | |||
<br/>It also contains some extra bytes the use of it is unknown.<br/>The GUID values are defined at: <span class="plainlinks">http://msdn.microsoft.com/en-us/library/cc707102.aspx</span> - Terrain and Scenery | |||
<br/>(See Vector Attributes for the Shp2Vec Tool) | |||
<br/>The usable GUID values are: | |||
<pre> | |||
{359C73E8-06BE-4FB2-ABCB-EC942F7761D0} Airport Bounds | |||
{91CB4A9B-9398-48E6-81DA-70AEA3295914} Parks | |||
{EA0C44F7-01DE-4D10-97EB-FB5510EB7B72} Water Polygons (GPS) | |||
{956A42AD-EC8A-41BE-B7CB-C68B5FF1727E} Water Polygons | |||
{AC39CDCB-DB78-4628-9A7C-051DA7AC864A} Exclusions | |||
{0CBC8FAD-DF73-40A1-AD2B-FE62F8004F6F} Shorelines | |||
{714BF912-F9DF-467E-80AE-28EB27374DBD} Streams | |||
{C7ACE4AE-871D-4938-8BDC-BB29C4BBF4E3} Utilities | |||
{33239EB4-D2B8-46F5-98AB-47B3D0922E2A} Railways | |||
{560FA8E6-723D-407D-B730-AE08039102A5} Roads | |||
{54B91ED8-BC02-41B7-8C3B-2B8449FF85EC} Freeway Traffic Roads | |||
</pre> | |||
|- | |||
| 0x14 | |||
| 4 - DWORD | |||
| Maximum allowed number of signature offsets.<br/>Signature offsets (see Entity Structure below) are offset into the signature buffer and point to the GUID identifying the corresponding segment. | |||
|- | |||
| 0x18 | |||
| 4 - DWORD | |||
| Maximum number of pair values per segment for this subsection.<br/>= 2 * maximum number of points per segment | |||
|- | |||
| 0x1C | |||
| 4 - DWORD | |||
| 0<br/>I did not find a cvx file with a value other than 0. | |||
|- | |||
| 0x20 | |||
| N | |||
| Signatures buffer containing <N> bytes, where N is defined at relative offset 0x10 | |||
|} | |||
== Entity Structure == | == Entity Structure == | ||
Then for each entity (the number of entities is defined at relative offset 0x0C of the subsection header (see [[#Subsection Header|Subsection Header]] above), we have the following structure: | |||
{| class="wikitable" | |||
|- | |||
! Relative Offset | |||
! Number of bytes | |||
! Description | |||
|- | |||
| 0x00 | |||
| 4 - DWORD | |||
| Number of segments in the entity. | |||
|- | |||
| 0x04 | |||
| 4 - DWORD | |||
| Segment type: | |||
* 1 – Points only | |||
* 2 – Lines | |||
* 3 - Polygons | |||
|- | |||
| 0x08 | |||
| 2 - WORD | |||
| Number of signatures offsets (Must be < 0x64). | |||
|- | |||
| 0x0A | |||
| 4 - DWORD | |||
| N = Number of signatures offsets (see above).<br/>Will contain the offsets into the signature buffer. | |||
|} | |||
== Segment Structure == | == Segment Structure == | ||
Then for each segment (the number of segments is defined at relative offset 0x00 of the entity structure (see [[#Entity Structure|Entity Structure]] above), we have the following structure: | |||
{| class="wikitable" | |||
|- | |||
! Relative Offset | |||
! Number of bytes | |||
! Description | |||
|- | |||
| 0x00 | |||
| 4 - DWORD | |||
| Number of points (geographical coordinates) in this segment. | |||
|- | |||
| 0x04 | |||
| 1 - BYTE | |||
| AddFlag : Determines how many additional bytes to read after the data buffer has been read. | |||
|- | |||
|- valign="top" | |||
| 0x05 | |||
| 1 - BYTE | |||
| Method used to build the pairs list from the data buffer.<br/>Possible values are 1,2 or 3 | |||
* 1 – Method1 | |||
* 2 – Method2 | |||
* 3 – Method3 | |||
<br/>Note: I did not find a cvx file with a value of 3 but it looks like Method3 read the pairs list directly from the data buffer without any extra processing. | |||
<br/>Methods 1 and 2 need some extra processing. See below. | |||
|- | |||
|- valign="top" | |||
| 0x06 | |||
| | |||
| Data buffer – Size may vary depending on the method used. See below. | |||
|- | |||
|- valign="top" | |||
| - | |||
| N | |||
| N depends on the AddFlag defined at relative offset 0x04. | |||
* If AddFlag = 0 then N = 0 | |||
* If AddFlag = 1 then N = 4 x NumberOfPoints (as defined at relative offset 0x00) | |||
* If AddFlag = 2 then N = 4 | |||
<br/>I don’t know what the use of these extra bytes is. | |||
|} | |||
[[File:Cvx_SectionStructure.png]] | |||
=== Method 1 === | === Method 1 === | ||
By far the most complex method. I have some code for it. But I still have to figure out the big picture. | |||
<br/><br/>The data buffer for this method has the following structure: | |||
{| class="wikitable" | |||
|- | |||
! Relative offset | |||
! Number of bytes | |||
! Description | |||
|- | |||
| 0x00 | |||
| 4 - DWORD | |||
| First Longitude Data - First value in the list of pairs. | |||
|- | |||
| 0x04 | |||
| 4 - DWORD | |||
| First Latitude Data - First value in the list of pairs. | |||
|- | |||
| 0x08 | |||
| 4 - DWORD | |||
| LongitudeData Increment | |||
|- | |||
| 0x0C | |||
| 4 - DWORD | |||
| LatitudeData Increment | |||
|- | |||
| 0x10 | |||
| 4 - DWORD | |||
| Number of bytes | |||
|- | |||
| 0x14 | |||
| N | |||
| N is the number of bytes defined above at relative offset 0x10.<br/>These N bytes are the raw data used to build the final list of pairs. | |||
|} | |||
=== Method 2 === | === Method 2 === | ||
The data buffer for this method has the following structure: | |||
{| class="wikitable" | |||
|- | |||
! Relative offset | |||
! Number of bytes | |||
! Description | |||
|- | |||
| 0x00 | |||
| 1 - BYTE | |||
| Root Mask | |||
|- | |||
|- valign="top" | |||
| 0x01 | |||
| N | |||
| N is computed using the value of the Root Mask.<br/>N = (RootMask * NbPoints * 2 + 7) >> 3<br/>where | |||
* ''RootMask'' is defined at relative offset 0x00 of this structure. | |||
* ''NbPoints'' is the number of points as defined at relative offset 0x00 of the Segment Structure. | |||
<br/>These N bytes are the raw data used to build the final list of pairs. | |||
|} | |||
==== Algorithm ==== | |||
The algorithm used by Method2 to build the list of pairs is: | |||
<pre> | |||
/* | |||
Fills an array listOfValues of DWORD | |||
*/ | |||
PositionInPair = 0; /* 0 = first value in pair, 1 = second value in pair */ | |||
PairIndex = 0 ; | |||
ShiftValue = 0 ; | |||
NbBytesLeftToRead = ((rootMask * nbPoints) * 2 + 7) >> 3; | |||
Mask = (1 << rootMask) - 1; /* 2 ^ rootMask */ | |||
/* Read first value */ | |||
if (NbBytesLeftToRead > 3) | |||
{ | |||
valueFromFile = read (4 bytes) | |||
NbBytesLeftToRead -= 4 ; | |||
} | |||
else | |||
{ | |||
valueFromFile = read (NbBytesLeftToRead bytes) | |||
NbBytesLeftToRead = 0 ; | |||
} | |||
while (PairIndex < nbPoints) | |||
{ | |||
if (ShiftValue < 0) | |||
{ | |||
/* Shift Left */ | |||
result = (uint)((valueFromFile << (-ShiftValue)) & Mask); | |||
/* Add to existing pair value */ | |||
result += listOfValues[PairIndex * 2 + PositionInPair]; | |||
listOfValues[PairIndex * 2 + PositionInPair] = result; | |||
} | |||
else | |||
{ | |||
/* Shift Right */ | |||
result = (uint)((valueFromFile >> ShiftValue) & Mask); | |||
listOfValues[PairIndex * 2 + PositionInPair] = result; | |||
} | |||
if (rootMask + ShiftValue >= 32) | |||
{ | |||
if (NbBytesLeftToRead > 3) | |||
{ | |||
valueFromFile = read (4 bytes) | |||
NbBytesLeftToRead -= 4 ; | |||
} | |||
else | |||
{ | |||
valueFromFile = read (NbBytesLeftToRead bytes) | |||
NbBytesLeftToRead = 0 ; | |||
} | |||
ShiftValue -= 32; /* now negative */ | |||
} | |||
else | |||
{ | |||
/* continue processing value from file */ | |||
ShiftValue += rootMask; | |||
PositionInPair++; | |||
} | |||
if (PositionInPair == 2) | |||
{ | |||
PairIndex++; /* Next entry in the list of pair values */ | |||
PositionInPair = 0; | |||
} | |||
} | |||
</pre> | |||
=== Example === | === Example === | ||
Still with the same example as in Subsections, we know that the first subsection data starts at offset 0x4C (as described in the first subsection starting at offset 0x1FCD01) . So at this file offset, we have the subsection header of the first subsection data. | |||
{| class="wikitable" | |||
|- | |||
! Offset | |||
! Value | |||
! Description | |||
|- | |||
| 0x4C | |||
| <pre>06 00 00 00</pre> | |||
| 6 | |||
|- | |||
| 0x50 | |||
| <pre>00 FA 81 00</pre> | |||
| Bounding coordinates: | |||
* MinLatitude(Deg) = 47.63671875 | |||
* MaxLatitude(Deg) = 47.8125 | |||
* MinLongitude(Deg) =- 75.0 | |||
* MaxLongitude(Deg) = -74.765625 | |||
|- | |||
| 0x54 | |||
| <pre>00 00 00 00</pre> | |||
| 0 | |||
|- | |||
| 0x58 | |||
| <pre>03 00 00 00</pre> | |||
| Number of entities = 3 | |||
|- | |||
| 0x5C | |||
| <pre>14 00 00 00</pre> | |||
| Number of bytes in the signatures buffer = 20 | |||
|- | |||
| 0x60 | |||
| <pre>03 00 00 00</pre> | |||
| Maximum allowed number of signature offsets = 3 | |||
|- | |||
| 0x64 | |||
| <pre>1C 00 00 00</pre> | |||
| Maximum number of pair values per segment = 28 | |||
|- | |||
| 0x68 | |||
| <pre>00 00 00 00</pre> | |||
| 0 | |||
|- | |||
| 0x6C | |||
| | |||
| Signatures buffer containing 20 bytes.<pre>F7 44 0C EA DE 01 10 4D 97 EB FB 55 10 EB 7B 72 00 00 00 00</pre> | |||
That is GUID {EA0C44F7-01DE-4D10-97EB-FB5510EB7B72} = Water Polygons (GPS), followed by 4 zeroes. | |||
|} | |||
Then follows 3 entities.<br/>The first entity (at offset 0x80 of the file) is | |||
{| class="wikitable" | |||
|- | |||
! Offset | |||
! Value | |||
! Description | |||
|- | |||
| 0x80 | |||
| <pre>01 00 00 00</pre> | |||
| Number of segments in the entity = 1 | |||
|- | |||
| 0x84 | |||
| <pre>03 00 00 00</pre> | |||
| Segment type = 3 - Polygons | |||
|- | |||
| 0x88 | |||
| <pre>01 00</pre> | |||
| Number of signatures offsets = 1 | |||
|- | |||
| 0x8A | |||
| <pre>00 00 00 00</pre> | |||
| Signatures offset = 0: points to the only one GUID | |||
|} | |||
This entity is followed by only one segment, at offset 0x8E of the file | |||
{| class="wikitable" | |||
|- | |||
! Offset | |||
! Value | |||
! Description | |||
|- | |||
| 0x8E | |||
| <pre>0E 00 00 00</pre> | |||
| Number of points = 14 | |||
|- | |||
| 0x92 | |||
| <pre>00</pre> | |||
| AddFlag = 0 | |||
|- | |||
| 0x93 | |||
| <pre>02</pre> | |||
| 2 – Method2 | |||
|- | |||
| 0x94 | |||
| <pre>0F</pre> | |||
| Root Mask = 0x0F<br/>So N = (15 * 14* 2 + 7) >> 3 = 53<br/>53 bytes to follow. | |||
|- | |||
| 0x95 | |||
| | |||
| 53 bytes:<pre> | |||
84 3E 56 37 | |||
65 10 74 1D | |||
3C 44 5B 9F | |||
11 C5 D0 05 | |||
C9 2C F3 D5 | |||
B2 3A 8D 8A | |||
8D 9D 9B 67 | |||
8B ED 52 59 | |||
B3 BC 0B 36 | |||
35 5F 54 25 | |||
B5 33 30 6D | |||
E2 64 4B EB | |||
36 A1 8F D5 | |||
0D</pre> | |||
|} | |||
The next entity starts at file offset 0xCA. | |||
<br/><br/>So the algorithm for method 2 goes like this: | |||
<pre> | |||
Mask = 0x7FFF ; | |||
NbBytesLeftToRead = 53 ; shiftValue = 0; PairIndex = 0 ; PositionInPair = 0 | |||
valueFromFile = read (4 bytes) = 0x37563E84 | |||
result = (valueFromFile >> shiftValue) & mask = 0x3E84 | |||
listOfValues[0] = 0x3E84 | |||
NbBytesLeftToRead = 49 ; shiftValue = 0x0F; PairIndex = 0 ; PositionInPair = 1 | |||
result = (valueFromFile >> shiftValue) & mask = 0x6EAC | |||
listOfValues[1] = 0x6EAC | |||
NbBytesLeftToRead = 49 ; shiftValue = 0x1E; PairIndex = 1 ; PositionInPair = 0 | |||
result = (valueFromFile >> shiftValue) & mask = 0x000 | |||
listOfValues[2] = 0x0000 | |||
rootMask + shiftValue = 0x2D > 0x20 so | |||
valueFromFile = read (4 bytes) = 0x1D741065 | |||
shiftValue = 0xFFFFFFFE | |||
NbBytesLeftToRead = 45 ; shiftValue = 0xFFFFFFFE; PairIndex = 1 ; PositionInPair = 0 | |||
result = (valueFromFile << (-shiftValue)) & mask = 0x4194 | |||
result += listOfValues[2] = 0x4194 + 0x0000 = 0x4194 | |||
listOfValues[2] = 0x4194 | |||
NbBytesLeftToRead = 45 ; shiftValue = 0x0D; PairIndex = 1 ; PositionInPair = 1 | |||
result = (valueFromFile >> shiftValue) & mask = 0x6BA0 | |||
listOfValues[3] = 0x6BA0 | |||
NbBytesLeftToRead = 45 ; shiftValue = 0x1C; PairIndex = 2 ; PositionInPair = 0 | |||
result = (valueFromFile >> shiftValue) & mask = 0x0001 | |||
listOfValues[4] = 0x0001 | |||
rootMask + shiftValue = 0x2BD > 0x20 so | |||
valueFromFile = read (4 bytes) = 0x9F5B443C | |||
shiftValue = 0xFFFFFFFC | |||
NbBytesLeftToRead = 41 ; shiftValue = 0xFFFFFFFC; PairIndex = 2 ; PositionInPair = 0 | |||
result = (valueFromFile << (-shiftValue)) & mask = 0x43C0 | |||
result += listOfValues[4] = 0x0x43C0 + 0x0001 = 0x43C1 | |||
listOfValues[4] = 0x43C1 | |||
etc | |||
</pre> | |||
Once the 53 bytes have been processed, the list contains the following 28 values, making up a list of 14 pairs. | |||
<pre> | |||
3E84 | |||
6EAC | |||
4194 | |||
6BA0 | |||
43C1 | |||
6B68 | |||
4467 | |||
6862 | |||
4905 | |||
6659 | |||
4B57 | |||
69D5 | |||
58A8 | |||
73B1 | |||
59E6 | |||
76C5 | |||
5952 | |||
7966 | |||
582E | |||
79A9 | |||
5545 | |||
76A4 | |||
4C0C | |||
7136 | |||
4B64 | |||
6DD6 | |||
3E84 | |||
6EAC | |||
</pre> | |||
To get the coordinates of the first point, we take the first pair {0x3E84, 0x6EAC} and use the convertToCoordinates method described at [[#Subsection for section of type Terrain_Vector_DB|Subsection for section of type Terrain_Vector_DB]]. | |||
<br/>The first value in the pair is longitude-related. The second value in the pair is latitude-related. | |||
<pre> | |||
doubledeltaLongFactor = ((-74.765625) - (- 75.0)) * 0.000030517578125; // = 0.000007152557373046875 | |||
double deltaLatFactor = (47.8125 - 47.63671875) * 0.000030517578125; // = 0.0000053644180297851563 | |||
double LongitudeDeg = (- 75.0) + (0x3E84 * deltaLongFactor); // = -74.885530471801758 | |||
double LatitudeDeg = 47.63671875 + (0x6EAC * deltaLatFactor); // = 47.788703441619873 | |||
</pre> | |||
= Annexe A = | = Annexe A = | ||
== Computing the bounding coordinates from a DWORD value == | |||
<pre> | |||
public static List<double> GetBoundingCoordinates(uint boundingValue) | |||
{ | |||
var list = new List<double>(); | |||
var shiftValue = 15; | |||
var work = boundingValue; | |||
var latitudeData = (uint)0; | |||
var longitudeData = (uint)0; | |||
while (work < 0x80000000 && shiftValue >= 0) | |||
{ | |||
shiftValue--; | |||
work *= 4; | |||
} | |||
work &= 0x7FFFFFFF; // Remove negative flag, if any | |||
var powerOfTwo = shiftValue; | |||
while (shiftValue >= 0) | |||
{ | |||
if (work >= 0x80000000) | |||
{ | |||
latitudeData += (uint)(1 << shiftValue); | |||
} | |||
if ((work & 0x40000000) != 0) | |||
{ | |||
longitudeData += (uint)(1 << shiftValue); | |||
} | |||
work *= 4; | |||
shiftValue--; | |||
} | |||
// factor = 1.0 / (2^i) | |||
var factor = 1.0 / (1 << powerOfTwo); | |||
// Calc bounding coordinates | |||
var minLatitudeDeg = 90.0 - ((latitudeData + 1.0) * factor * 360.0); | |||
var maxLatitudeDeg = 90.0 - (latitudeData * factor * 360.0); | |||
var minLongitude = (longitudeData * factor * 480.0) - 180.0; | |||
var maxLongitude = ((longitudeData + 1.0) * factor * 480.0) - 180.0; | |||
list.Add(minLatitudeDeg); | |||
list.Add(maxLatitudeDeg); | |||
list.Add(minLongitude); | |||
list.Add(maxLongitude); | |||
return list; | |||
} | |||
</pre> | |||
Latest revision as of 08:45, 14 October 2020
Introduction
cvx*.bgl files can be read by the TmfViewer.exe application provided with the FS SDK. They contain TERRAIN_VECTOR_DB sections. These sections and subsections describe the different layers (roads, railways, water polygons, etc.) along with the geographical coordinates.
In this document, I will often take examples from the cvx2815.bgl file since that is the one I used to figure out the structure of the cxv file and it describes the area I live in. This file is located in: (....)\Microsoft Flight Simulator X\Scenery\0301\scenery.
Also refer to the document "FSX File structure" by Winfried Orthmann. It is verfy helpful.
BGL Common Format
All BGL files share the same generic format. A BGL file is made of a header, sections, subsections and subsection data. Subsections are children of sections. The sections are here to help us locate the subsections in the file. Data specific information is contained in the subsections data and their format is dependent on the section type.
A BGL file is really a big container where all kind of information can be stored (in the subsection).The meaning of the data contained in the subsections is known only by the application using it. Th only contraint is for the file to comply to the generic format described below.
A BGL file always start with a header (Size = 0x38 bytes), followed by a list of section pointers. The number of section pointers is defined in the header.
File Header
The header consists of 0x38 (56) bytes. It contains the number of sections defined in the file as well as the bounding geographical coordinates of the covered squared area.
| Offset | Number of bytes | Description |
|---|---|---|
| 0x00 | 4 - DWORD | Magic Number #1 – Must be 0x01, 0x02, 0x92, 0x19 |
| 0x04 | 4 - DWORD | Header size : 0x38 |
| 0x08 | 4 - DWORD | dwLowDateTime of the FILETIME structure. Date and Time the file was created The FILETIME structure represents the number of 100-nanosecond intervals since January 1, 1601 |
| 0x0C | 4 - DWORD | dwHighDateTime of the FILETIME structure |
| 0x10 | 4 - DWORD | Magic Number #2 – Must be 0x03, 0x18, 0x05, 0x08 Maybe to identify the FS version |
| 0x14 | 4 - DWORD | The number of sections following this header. |
| 0x18 | 32 | Array[8] of unsigned integers (DWORD). Each value describes the bounding coordinates of a squared subarea. Even if 8 slots are provided, it is not necessary to have all 8 values filled. The list stops at the first null (0x00000000) value. |
Example
For example, in cvx2815.bgl :
| Offset | Values | Description |
|---|---|---|
| 0x00 | 01 02 92 19 |
Magic Number #1 |
| 0x04 | 38 00 00 00 |
Header size |
| 0x08 | EF 82 DF E2 |
Low = 3806298863 |
| 0x0C | E8 C7 C6 01 |
High = 29804520 => February 27, 2007 |
| 0x10 | 03 18 15 08 |
Magic Number #2 |
| 0x14 | 01 00 00 00 |
1 section following this header |
| 0x18 | E8 07 02 00 |
MinLatitude(Deg) = 46.40625 MaxLatitude(Deg) = 47.8125 MinLongitude(Deg) = -75.0 MaxLongitude(Deg) = -73.125 |
| 0x1C | E9 07 02 00 |
MinLatitude(Deg) = 46.40625 MaxLatitude(Deg) = 47.8125 MinLongitude(Deg) = - 73.125 MaxLongitude(Deg) = -71.25 |
| 0x20 | EA 07 02 00 |
MinLatitude(Deg) = 45.0 MaxLatitude(Deg) = 46.40625 MinLongitude(Deg) = -75.0 MaxLongitude(Deg) = -73.125 |
| 0x24 | EB 07 02 00 |
MinLatitude(Deg) = 45.0 MaxLatitude(Deg) = 46.40625 MinLongitude(Deg) = -73.124 MaxLongitude(Deg) = -71.25 |
| 0x28 | 00 00 00 00 |
|
| 0x2C | 00 00 00 00 |
|
| 0x30 | 00 00 00 00 |
|
| 0x34 | 00 00 00 00 |
You’ll notice that only 4 subareas are defined (on a possibility of 8) and the last 4 available slots are empty (Value = 0)
So the bounding coordinates of the area covered by cvx2815.bgl are:
- MinLatitude(Deg) = 45.0
- MaxLatitude(Deg) = 47.8125
- MinLongitude(Deg) = -75.0
- MaxLongitude(Deg) = -71.25
Sections
Following the header, at offset 0x38, there are as many sections as defined at offset 0x14 of the header. A same file may contain sections of different type. Each section has a size of 20 bytes and has the following structure:
| Relative Offset | Number of bytes | Description |
|---|---|---|
| 0x00 | 4 - DWORD | Section type (as defined by Microsoft Flight Simulator): one of the following values:
|
| 0x04 | 4 - DWORD | Unknown |
| 0x08 | 4 - DWORD | Number of subsections in the section. |
| 0x0C | 4 - DWORD | File offset = position in the file where the first subsection starts. |
| 0x10 | 4 - DWORD | Total Size (in bytes) of all the subsections. This value should be equal to : 16 * nbSubSections |
Note that a cvx*.bgl file have at least a section of type 101 (0x65) = TerrainVectorDb.
Example
For example, in cvx2815.bgl, the only one section, at offset 0x38, has this:
| Offset | Values | Description |
|---|---|---|
| 0x38 | 65 00 00 00 |
TerrainVectorDb = 101 = 0x65 |
| 0x3C | 01 00 00 00 |
Unknown |
| 0x40 | 8D 07 00 00 |
0x078D = 1933 subsections in the section |
| 0x44 | 01 CD 1F 00 |
0x1FCD01 = File offset = position in the file where the first subsection starts. |
| 0x48 | D0 78 00 00 |
Size (in bytes) =0x78D0 = 30928 bytes |
Subsections
A subsection contains information about the geographical area it covers. If also contains the file offset of the subsection’s data. All subsections of a same section are contiguous meaning that they are following each other in the file.
A subsection has the following structure:
| Relative Offset | Number of bytes | Description |
|---|---|---|
| 0x00 | 4 - DWORD | Bounding coordinates of a squared area covered by this subsection. See Annexe A. |
| 0x04 | 4 - DWORD | Number of records contained in the subsection's data. It is 0 for TerrainVectorDb (which actually means only 1 record). |
| 0x08 | 4 - DWORD | File Offset = Position in the file of the subsection’s data |
| 0x0C | 4 - DWORD | Size of the subsection’s data |
The interpretation of the section data depends on the section type.
Example
| Offset | Values | Description |
|---|---|---|
| 0x1FCD01 | 00 FA 81 00 |
Bounding coordinates:
|
| 0x1FCD05 | 00 00 00 00 |
Number of records = 0 |
| 0x1FCD09 | 4C 00 00 00 |
0x4C = File Offset = Position in the file of the subsection’s data. |
| 0x1FCD0D | DD 00 00 00 |
Size of the subsection’s data = 0xDD = 221 bytes. |
Subsection for section of type Terrain_Vector_DB
This type of subsection contains values to retrieve geographical coordinates, organized as segments. These coordinates are used to draw lines (roads, railways, etc.) or polygons (lakes, airport bounds, etc.).
The section does not contain geographical coordinates per se but offsets to the lower left corner of the covered area (minimum latitude / longitude). The covered area is also defined in the subsection.
The data is organized in lists of pair values (One pair per geographical coordinate). Each pair can then be used to compute the final geographical coordinates. There are 3 ways to retrieve these lists of pairs.
The first value in a pair is longitude-related. The second value is latitude-related.
The algorithm to compute the geographical coordinates from a pair is:
void convertToCoordinates (double longitude_related_Value, double latitude_related_Value)
{
var deltaLongFactor = (MaxLongitudeDeg - MinLongitudeDeg) * 0.000030517578125;
var deltaLatFactor = (MaxLatitudeDeg - MinLatitudeDeg) * 0.000030517578125;
var LongitudeDeg = MinLongitudeDeg + (longitude_related_Value * deltaLongFactor);
var LatitudeDeg = MinLongitudeDeg + (latitude_related_Value * deltaLatFactor);
}
Subsection Header
Each subsection starts with the following structure:
| Relative Offset | Number of bytes | Description |
|---|---|---|
| 0x00 | 4 - DWORD | 6 I did not find a cvx file with a value other than 6. |
| 0x04 | 4 - DWORD | Bounding coordinates of a squared area covered by this subsection. See Computing the bounding coordinates from a DWORD value. |
| 0x08 | 4 - DWORD | 0 I did not find a cvx file with a value other than 0. |
| 0x0C | 4 - DWORD | Number of entities. An entity is a list of segments. (a segment is a list of points). |
| 0x10 | 4 - DWORD | Number of bytes in the signatures buffer. The signatures buffer contains some GUIDs identifying the segments of this subsection. Some GUID values (like the Texture) are not used by the TmfViewer application.
{359C73E8-06BE-4FB2-ABCB-EC942F7761D0} Airport Bounds
{91CB4A9B-9398-48E6-81DA-70AEA3295914} Parks
{EA0C44F7-01DE-4D10-97EB-FB5510EB7B72} Water Polygons (GPS)
{956A42AD-EC8A-41BE-B7CB-C68B5FF1727E} Water Polygons
{AC39CDCB-DB78-4628-9A7C-051DA7AC864A} Exclusions
{0CBC8FAD-DF73-40A1-AD2B-FE62F8004F6F} Shorelines
{714BF912-F9DF-467E-80AE-28EB27374DBD} Streams
{C7ACE4AE-871D-4938-8BDC-BB29C4BBF4E3} Utilities
{33239EB4-D2B8-46F5-98AB-47B3D0922E2A} Railways
{560FA8E6-723D-407D-B730-AE08039102A5} Roads
{54B91ED8-BC02-41B7-8C3B-2B8449FF85EC} Freeway Traffic Roads
|
| 0x14 | 4 - DWORD | Maximum allowed number of signature offsets. Signature offsets (see Entity Structure below) are offset into the signature buffer and point to the GUID identifying the corresponding segment. |
| 0x18 | 4 - DWORD | Maximum number of pair values per segment for this subsection. = 2 * maximum number of points per segment |
| 0x1C | 4 - DWORD | 0 I did not find a cvx file with a value other than 0. |
| 0x20 | N | Signatures buffer containing <N> bytes, where N is defined at relative offset 0x10 |
Entity Structure
Then for each entity (the number of entities is defined at relative offset 0x0C of the subsection header (see Subsection Header above), we have the following structure:
| Relative Offset | Number of bytes | Description |
|---|---|---|
| 0x00 | 4 - DWORD | Number of segments in the entity. |
| 0x04 | 4 - DWORD | Segment type:
|
| 0x08 | 2 - WORD | Number of signatures offsets (Must be < 0x64). |
| 0x0A | 4 - DWORD | N = Number of signatures offsets (see above). Will contain the offsets into the signature buffer. |
Segment Structure
Then for each segment (the number of segments is defined at relative offset 0x00 of the entity structure (see Entity Structure above), we have the following structure:
| Relative Offset | Number of bytes | Description |
|---|---|---|
| 0x00 | 4 - DWORD | Number of points (geographical coordinates) in this segment. |
| 0x04 | 1 - BYTE | AddFlag : Determines how many additional bytes to read after the data buffer has been read. |
| 0x05 | 1 - BYTE | Method used to build the pairs list from the data buffer. Possible values are 1,2 or 3
|
| 0x06 | Data buffer – Size may vary depending on the method used. See below. | |
| - | N | N depends on the AddFlag defined at relative offset 0x04.
|
Method 1
By far the most complex method. I have some code for it. But I still have to figure out the big picture.
The data buffer for this method has the following structure:
| Relative offset | Number of bytes | Description |
|---|---|---|
| 0x00 | 4 - DWORD | First Longitude Data - First value in the list of pairs. |
| 0x04 | 4 - DWORD | First Latitude Data - First value in the list of pairs. |
| 0x08 | 4 - DWORD | LongitudeData Increment |
| 0x0C | 4 - DWORD | LatitudeData Increment |
| 0x10 | 4 - DWORD | Number of bytes |
| 0x14 | N | N is the number of bytes defined above at relative offset 0x10. These N bytes are the raw data used to build the final list of pairs. |
Method 2
The data buffer for this method has the following structure:
| Relative offset | Number of bytes | Description |
|---|---|---|
| 0x00 | 1 - BYTE | Root Mask |
| 0x01 | N | N is computed using the value of the Root Mask. N = (RootMask * NbPoints * 2 + 7) >> 3 where
|
Algorithm
The algorithm used by Method2 to build the list of pairs is:
/*
Fills an array listOfValues of DWORD
*/
PositionInPair = 0; /* 0 = first value in pair, 1 = second value in pair */
PairIndex = 0 ;
ShiftValue = 0 ;
NbBytesLeftToRead = ((rootMask * nbPoints) * 2 + 7) >> 3;
Mask = (1 << rootMask) - 1; /* 2 ^ rootMask */
/* Read first value */
if (NbBytesLeftToRead > 3)
{
valueFromFile = read (4 bytes)
NbBytesLeftToRead -= 4 ;
}
else
{
valueFromFile = read (NbBytesLeftToRead bytes)
NbBytesLeftToRead = 0 ;
}
while (PairIndex < nbPoints)
{
if (ShiftValue < 0)
{
/* Shift Left */
result = (uint)((valueFromFile << (-ShiftValue)) & Mask);
/* Add to existing pair value */
result += listOfValues[PairIndex * 2 + PositionInPair];
listOfValues[PairIndex * 2 + PositionInPair] = result;
}
else
{
/* Shift Right */
result = (uint)((valueFromFile >> ShiftValue) & Mask);
listOfValues[PairIndex * 2 + PositionInPair] = result;
}
if (rootMask + ShiftValue >= 32)
{
if (NbBytesLeftToRead > 3)
{
valueFromFile = read (4 bytes)
NbBytesLeftToRead -= 4 ;
}
else
{
valueFromFile = read (NbBytesLeftToRead bytes)
NbBytesLeftToRead = 0 ;
}
ShiftValue -= 32; /* now negative */
}
else
{
/* continue processing value from file */
ShiftValue += rootMask;
PositionInPair++;
}
if (PositionInPair == 2)
{
PairIndex++; /* Next entry in the list of pair values */
PositionInPair = 0;
}
}
Example
Still with the same example as in Subsections, we know that the first subsection data starts at offset 0x4C (as described in the first subsection starting at offset 0x1FCD01) . So at this file offset, we have the subsection header of the first subsection data.
| Offset | Value | Description |
|---|---|---|
| 0x4C | 06 00 00 00 |
6 |
| 0x50 | 00 FA 81 00 |
Bounding coordinates:
|
| 0x54 | 00 00 00 00 |
0 |
| 0x58 | 03 00 00 00 |
Number of entities = 3 |
| 0x5C | 14 00 00 00 |
Number of bytes in the signatures buffer = 20 |
| 0x60 | 03 00 00 00 |
Maximum allowed number of signature offsets = 3 |
| 0x64 | 1C 00 00 00 |
Maximum number of pair values per segment = 28 |
| 0x68 | 00 00 00 00 |
0 |
| 0x6C | Signatures buffer containing 20 bytes.F7 44 0C EA DE 01 10 4D 97 EB FB 55 10 EB 7B 72 00 00 00 00 That is GUID {EA0C44F7-01DE-4D10-97EB-FB5510EB7B72} = Water Polygons (GPS), followed by 4 zeroes. |
Then follows 3 entities.
The first entity (at offset 0x80 of the file) is
| Offset | Value | Description |
|---|---|---|
| 0x80 | 01 00 00 00 |
Number of segments in the entity = 1 |
| 0x84 | 03 00 00 00 |
Segment type = 3 - Polygons |
| 0x88 | 01 00 |
Number of signatures offsets = 1 |
| 0x8A | 00 00 00 00 |
Signatures offset = 0: points to the only one GUID |
This entity is followed by only one segment, at offset 0x8E of the file
| Offset | Value | Description |
|---|---|---|
| 0x8E | 0E 00 00 00 |
Number of points = 14 |
| 0x92 | 00 |
AddFlag = 0 |
| 0x93 | 02 |
2 – Method2 |
| 0x94 | 0F |
Root Mask = 0x0F So N = (15 * 14* 2 + 7) >> 3 = 53 53 bytes to follow. |
| 0x95 | 53 bytes:84 3E 56 37 65 10 74 1D 3C 44 5B 9F 11 C5 D0 05 C9 2C F3 D5 B2 3A 8D 8A 8D 9D 9B 67 8B ED 52 59 B3 BC 0B 36 35 5F 54 25 B5 33 30 6D E2 64 4B EB 36 A1 8F D5 0D |
The next entity starts at file offset 0xCA.
So the algorithm for method 2 goes like this:
Mask = 0x7FFF ; NbBytesLeftToRead = 53 ; shiftValue = 0; PairIndex = 0 ; PositionInPair = 0 valueFromFile = read (4 bytes) = 0x37563E84 result = (valueFromFile >> shiftValue) & mask = 0x3E84 listOfValues[0] = 0x3E84 NbBytesLeftToRead = 49 ; shiftValue = 0x0F; PairIndex = 0 ; PositionInPair = 1 result = (valueFromFile >> shiftValue) & mask = 0x6EAC listOfValues[1] = 0x6EAC NbBytesLeftToRead = 49 ; shiftValue = 0x1E; PairIndex = 1 ; PositionInPair = 0 result = (valueFromFile >> shiftValue) & mask = 0x000 listOfValues[2] = 0x0000 rootMask + shiftValue = 0x2D > 0x20 so valueFromFile = read (4 bytes) = 0x1D741065 shiftValue = 0xFFFFFFFE NbBytesLeftToRead = 45 ; shiftValue = 0xFFFFFFFE; PairIndex = 1 ; PositionInPair = 0 result = (valueFromFile << (-shiftValue)) & mask = 0x4194 result += listOfValues[2] = 0x4194 + 0x0000 = 0x4194 listOfValues[2] = 0x4194 NbBytesLeftToRead = 45 ; shiftValue = 0x0D; PairIndex = 1 ; PositionInPair = 1 result = (valueFromFile >> shiftValue) & mask = 0x6BA0 listOfValues[3] = 0x6BA0 NbBytesLeftToRead = 45 ; shiftValue = 0x1C; PairIndex = 2 ; PositionInPair = 0 result = (valueFromFile >> shiftValue) & mask = 0x0001 listOfValues[4] = 0x0001 rootMask + shiftValue = 0x2BD > 0x20 so valueFromFile = read (4 bytes) = 0x9F5B443C shiftValue = 0xFFFFFFFC NbBytesLeftToRead = 41 ; shiftValue = 0xFFFFFFFC; PairIndex = 2 ; PositionInPair = 0 result = (valueFromFile << (-shiftValue)) & mask = 0x43C0 result += listOfValues[4] = 0x0x43C0 + 0x0001 = 0x43C1 listOfValues[4] = 0x43C1 etc
Once the 53 bytes have been processed, the list contains the following 28 values, making up a list of 14 pairs.
3E84 6EAC 4194 6BA0 43C1 6B68 4467 6862 4905 6659 4B57 69D5 58A8 73B1 59E6 76C5 5952 7966 582E 79A9 5545 76A4 4C0C 7136 4B64 6DD6 3E84 6EAC
To get the coordinates of the first point, we take the first pair {0x3E84, 0x6EAC} and use the convertToCoordinates method described at Subsection for section of type Terrain_Vector_DB.
The first value in the pair is longitude-related. The second value in the pair is latitude-related.
doubledeltaLongFactor = ((-74.765625) - (- 75.0)) * 0.000030517578125; // = 0.000007152557373046875 double deltaLatFactor = (47.8125 - 47.63671875) * 0.000030517578125; // = 0.0000053644180297851563 double LongitudeDeg = (- 75.0) + (0x3E84 * deltaLongFactor); // = -74.885530471801758 double LatitudeDeg = 47.63671875 + (0x6EAC * deltaLatFactor); // = 47.788703441619873
Annexe A
Computing the bounding coordinates from a DWORD value
public static List<double> GetBoundingCoordinates(uint boundingValue)
{
var list = new List<double>();
var shiftValue = 15;
var work = boundingValue;
var latitudeData = (uint)0;
var longitudeData = (uint)0;
while (work < 0x80000000 && shiftValue >= 0)
{
shiftValue--;
work *= 4;
}
work &= 0x7FFFFFFF; // Remove negative flag, if any
var powerOfTwo = shiftValue;
while (shiftValue >= 0)
{
if (work >= 0x80000000)
{
latitudeData += (uint)(1 << shiftValue);
}
if ((work & 0x40000000) != 0)
{
longitudeData += (uint)(1 << shiftValue);
}
work *= 4;
shiftValue--;
}
// factor = 1.0 / (2^i)
var factor = 1.0 / (1 << powerOfTwo);
// Calc bounding coordinates
var minLatitudeDeg = 90.0 - ((latitudeData + 1.0) * factor * 360.0);
var maxLatitudeDeg = 90.0 - (latitudeData * factor * 360.0);
var minLongitude = (longitudeData * factor * 480.0) - 180.0;
var maxLongitude = ((longitudeData + 1.0) * factor * 480.0) - 180.0;
list.Add(minLatitudeDeg);
list.Add(maxLatitudeDeg);
list.Add(minLongitude);
list.Add(maxLongitude);
return list;
}
