Tile Index Scheme
Scenery in FlightGear is composed of tiles, each associated with a tile index. The tiles are cut so that none of them is excessively large or small compared to other tiles regarding their actual size on the earth surface.
Each tile is defined by the geodetic coordinates of its southwest corner and it extends in degrees of geodetic latitude and longitude. We will refer to the latitude, longitude of the southwest corner as lat, lon. However, the index calculations should also work with any other position inside the tile.
The authoritative source for the tile index scheme is the definition of the class SGBucket
in SimGear (simgear/bucket/newbucket.{hxx, cxx}
).
Calculating the Tile Index
The tile index is composed of four parts
- base longitude index (-180...179)
- base latitude index (-90...89),
- longitude offset (0...7), and
- latitude offset (0...7).
Each degree of latitude is split up into 8 rows of tiles. Each tile spans 1/8th degree (0.125 degrees of latitude).
Therefore the base latitude index and offset are directly derived from lat using the floor
and trunc
functions:
base_y = floor(lat) y = trunc((lat - base_y) * 8)
floor & trunc explained: http://www.cplusplus.com/reference/cmath/trunc/
The tiling in east-west direction is more complex.
As the meridians converge towards the pole, the width of tile columns must be adapted based on latitude, as the following table shows (taken from sg_bucket_span
in simgear/bucket/newbucket.hxx
):
Latitude Range | Tile Width (deg) |
---|---|
89 ≤ lat < 90 | 12 |
86 ≤ lat < 89 | 4 |
83 ≤ lat < 86 | 2 |
76 ≤ lat < 83 | 1 |
62 ≤ lat < 76 | 0.5 |
22 ≤ lat < 62 | 0.25 |
-22 ≤ lat < 22 | 0.125 |
-62 ≤ lat < -22 | 0.25 |
-76 ≤ lat < -62 | 0.5 |
-83 ≤ lat < -76 | 1 |
-86 ≤ lat < -83 | 2 |
-89 ≤ lat < -86 | 4 |
-90 ≤ lat < -89 | 12 |
Note that in latitudes north of N83 or south of S83 tiles span more than a single degree of latitude. The naive code for determining the base longitude index is therefore
base_x=floor(floor(lon / tile_width) * tile_width) x=floor((lon - base_x) / tile_width)
The final tile index is found by composing the base offsets and indices into a bit-field:
index=((lon + 180) << 14) + ((lat + 90) << 6) + (y << 3) + x
So here is the final code for calculating the index (using the width from the table above for tile_width
)
base_y = floor(lat) y = trunc((lat - base_y) * 8) base_x = floor(floor(lon / tile_width) * tile_width) x = floor((lon - base_x) / tile_width)
Python script
""" Calculate a FlightGear scenery tile index from a latitude and longitude
USAGE:
$ python flightgear-tile.py LAT LON
Will print the index to standard output.
David Megginson, 2024-08-05 (Public Domain)
"""
from math import floor, trunc
import sys
# Table of tile widths for various latitudes
TILE_WIDTHS = (
(89, 90, 12,),
(86, 89, 4,),
(83, 86, 2,),
(76, 83, 1,),
(62, 76, 0.5,),
(22, 62, 0.25,),
(-22, 22, 0.125,),
(-62, -22, 0.25,),
(-76, -62, 0.5,),
(-83, -76, 1,),
(-86, -83, 2,),
(-89, -86, 4,),
(-90, -89, 12,),
)
def get_tile_width (lat):
""" Calculate the tile width for a latitude """
for entry in TILE_WIDTHS:
if lat >= entry[0]:
return entry[2]
raise Exception("Latitude out of range")
def calculate_tile_index (lat, lon):
""" Calculate the index for a lat/lon """
tile_width = get_tile_width(lat)
base_y = floor(lat)
y = trunc((lat - base_y) * 8)
base_x = floor(floor(lon / tile_width) * tile_width)
x = floor((lon - base_x) / tile_width)
index = ((base_x + 180) << 14) + ((base_y + 90) << 6) + (y << 3) + x
return index
#
# Script entry point
#
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Usage: {} LAT LON".format(sys.argv[0]), file=sys.stderr)
exit(2)
lat = float(sys.argv[1])
lon = float(sys.argv[2])
if lat < -90 or lat > 90:
print("Latitude {} out of range".format(lat))
exit(1)
if lon < -180 or lat > 180:
print("Longitude {} out of range".format(lon))
exit(1)
print(calculate_tile_index(lat, lon))
exit(0)
|