First public release - corrected issue with forest rendering. Implemented file-in-use check when clearing cache. Zoom level 16 now being built in tilegen.py . Tiles around airports will be kept - the rest will be removed after all tile creation.

This commit is contained in:
marstr 2024-08-31 21:47:15 +02:00
parent 949e305711
commit f9597fbb79
8 changed files with 316 additions and 88 deletions

View File

@ -22,7 +22,17 @@ mstr_osm_endpoint = "https://marstr.online/osm/v1/"
mstr_photores = 2048 # <- Change this to 4096 for 16k resolution mstr_photores = 2048 # <- Change this to 4096 for 16k resolution
# Radius of zoom level 18 aerials around airports with ICAO code # Radius of zoom level 18 aerials around airports with ICAO code
mstr_airport_radius = 2 # Value is in tiles - not in km
#
# Value = 5:
# #####
# #####
# ##X##
# #####
# #####
#
# The "X" is the tile with the airport
mstr_airport_radius = 5
# Clear cache after generating a complete tile? # Clear cache after generating a complete tile?
mstr_clear_cache = True mstr_clear_cache = True
@ -166,8 +176,8 @@ mstr_mask_blur = [
("landuse", "farmland", 15), ("landuse", "farmland", 15),
("landuse", "farmyard", 15), ("landuse", "farmyard", 15),
# Z-Order 2 # Z-Order 2
("landuse", "forest", 15), ("landuse", "forest", 20),
("leisure", "nature_reserve", 15), ("leisure", "nature_reserve", 20),
("landuse", "military", 30), ("landuse", "military", 30),
# Z-Order 3 # Z-Order 3
("natural", "bare_rock", 25), ("natural", "bare_rock", 25),
@ -205,9 +215,3 @@ mstr_mask_blur = [
("building", "industrial", 1), ("building", "industrial", 1),
("building", "yes", 1) ("building", "yes", 1)
] ]
# Define tile main colors by latitude-longitude region
mstr_base_colors = [
((50,0), 100, 106, 77)
]

View File

@ -61,6 +61,24 @@ def findZL16tiles(v, h):
return tiles return tiles
# Find the tiles to keep around an airport, using the defined tile
# radius amount in defines.py
def findAirportTiles(av, ah):
# The tiles
tiles=[]
# Starting points
sty = av - int(mstr_airport_radius/2)
stx = ah - int(mstr_airport_radius/2)
for y in range(mstr_airport_radius):
for x in range(mstr_airport_radius):
a = ( sty+y, stx+x )
tiles.append(a)
# Return the tiles
return tiles
# Testing # Testing
def in_circle(center_x, center_y, radius, x, y): def in_circle(center_x, center_y, radius, x, y):

View File

@ -219,11 +219,12 @@ class mstr_layergen:
ry = randrange(30,60) ry = randrange(30,60)
f = randrange(1,10) f = randrange(1,10)
# Do some magic # Do some magic - but not on edges
if f != 5: if x > 0 and x < osm_mask.width and y > 0 and y < osm_mask.height:
imgd.ellipse((x-int(rx/2), y-int(ry/2), x+rx, y+ry), fill="black") if f != 5:
if f == 3 or f == 7: imgd.ellipse((x-int(rx/2), y-int(ry/2), x+rx, y+ry), fill="black")
imgd.ellipse((x-int(rx/2), y-int(ry/2), x+rx, y+ry), fill=(0,0,0,0)) if f == 3 or f == 7:
imgd.ellipse((x-int(rx/2), y-int(ry/2), x+rx, y+ry), fill=(0,0,0,0))
# We need to change the image in certain conditions # We need to change the image in certain conditions
@ -305,24 +306,34 @@ class mstr_layergen:
# Let's try our hand at pseudo shadows # Let's try our hand at pseudo shadows
if mstr_shadow_enabled == True: if mstr_shadow_enabled == True:
shadow = Image.new("RGBA", (self._imgsize, self._imgsize)) if mstr_shadow_shift >= 2:
for sh in mstr_shadow_casters: shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
if self._tag == sh[0] and self._value == sh[1]: for sh in mstr_shadow_casters:
mstr_msg("mstr_layergen", "Generating shadow for layer") if self._tag == sh[0] and self._value == sh[1]:
shadow_pix = shadow.load() mstr_msg("mstr_layergen", "Generating shadow for layer")
mask_pix = osm_mask.load() shadow_pix = shadow.load()
for y in range(self._imgsize-1): mask_pix = osm_mask.load()
for x in range(self._imgsize-1): for y in range(self._imgsize-1):
m = mask_pix[x,y] for x in range(self._imgsize-1):
shf_x = x + mstr_shadow_shift m = mask_pix[x,y]
if shf_x <= self._imgsize-1: shf_x = 0
a = mask_pix[x,y][3] # Buildings get slightly closer shadows
st = random.uniform(0.45, mstr_shadow_strength) if self._tag == "building":
ca = a * st shf_x = x + int(mstr_shadow_shift/2)
aa = int(ca) if self._tag != "building":
shadow_pix[shf_x, y] = (0,0,0,aa) shf_x = x + mstr_shadow_shift
shadow.save(mstr_datafolder + "_cache\\" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer_shadow.png") if shf_x <= self._imgsize-1:
mstr_msg("mstr_layergen", "Shadow layer completed") a = mask_pix[x,y][3]
st = 0
if self._tag == "building":
st = random.uniform(0.25, mstr_shadow_strength/2)
if self._tag != "building":
st = random.uniform(0.45, mstr_shadow_strength)
ca = a * st
aa = int(ca)
shadow_pix[shf_x, y] = (0,0,0,aa)
shadow.save(mstr_datafolder + "_cache\\" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer_shadow.png")
mstr_msg("mstr_layergen", "Shadow layer completed")
@ -510,22 +521,3 @@ class mstr_layergen:
mstr_msg("mstr_layergen", "Layer image finalized and saved.") mstr_msg("mstr_layergen", "Layer image finalized and saved.")
'''
lg1 = mstr_layergen("landuse", "forest", 51, 1, 7, 1)
lg1.genlayer()
lg2 = mstr_layergen("landuse", "farmland", 51, 1, 7, 1)
lg2.genlayer()
lg3 = mstr_layergen("leisure", "golf_course", 51, 1, 7, 1)
lg3.genlayer()
l = Image.new("RGBA", (3000, 3000))
l1 = Image.open("M:\\Developer\\Projects\\orthographic\\_cache\\51-1_7-1_landuse-forest_layer.png")
l2 = Image.open("M:\\Developer\\Projects\\orthographic\\_cache\\51-1_7-1_landuse-farmland_layer.png")
l3 = Image.open("M:\\Developer\\Projects\\orthographic\\_cache\\51-1_7-1_leisure-golf_course_layer.png")
l.alpha_composite(l3)
l.alpha_composite(l2)
l.alpha_composite(l1)
l.save("M:\\layer.png")
'''

View File

@ -182,13 +182,4 @@ class mstr_maskgen:
mask_img.save(mstr_datafolder + "_cache\\" + fstr + "_" + self._tag + "-" + self._value + ".png") mask_img.save(mstr_datafolder + "_cache\\" + fstr + "_" + self._tag + "-" + self._value + ".png")
# Inform # Inform
mstr_msg("mstr_maskgen", "Mask built.") mstr_msg("mstr_maskgen", "Mask built.")
#mg = mstr_maskgen([51, 1, 7, 1], 0.0100691262567974, "building", "yes", False)
#mg = mstr_maskgen([51, 1, 7, 1], 0.0100691262567974, "natural", "bare_rock")
#mg = mstr_maskgen([51, 1, 7, 1], 0.0100691262567974, "highway", "track")
#mg = mstr_maskgen([51, 1, 7, 1], 0.0100691262567974, "landuse", "forest", False)
#mg = mstr_maskgen([51, 1, 7, 1], 0.0100691262567974, "natural", "water")
#mg = mstr_maskgen([51, 1, 7, 1], 0.0100691262567974, "leisure", "golf_course")
#mg = mstr_maskgen([51, 2, 7, 5], 0.0100691262567974, "boundary", "administrative", "admin_level", ["6"])
#mg._build_mask()

View File

@ -19,11 +19,43 @@ from maskgen import *
from layergen import * from layergen import *
from photogen import * from photogen import *
from osmxml import * from osmxml import *
from tilegen import *
# The main class which handles the rest # The main class which handles the rest
class mstr_orthographic: class mstr_orthographic:
# It did happen that the generation of photos crashed as, for some reason,
# a file in _cache was apparently used by another process (hint: it was
# not). I therefore need this test before deleting a file in _cache, so
# that generation of the orthos can move forward.
def _isFileAccessibleWin(self, src):
a = False
if os.path.isfile(src) == True:
try:
os.rename(src, src)
a = True
except OSError as e:
a = False
return a
# Need a same call for POSIX
def _isFileAccessiblePosix(self, src):
wildcard = "/proc/*/fd/*"
lfds = glob.glob(wildcard)
for fds in lfds:
try:
file = os.readlink(fds)
if file == src:
return True
except OSError as err:
if err.errno == 2:
file = None
else:
raise(err)
return False
# This will determine the vertical stepping in degrees in order to generate # This will determine the vertical stepping in degrees in order to generate
# masks with a 1:1 square ratio. This is important as X-Plane textures for # masks with a 1:1 square ratio. This is important as X-Plane textures for
# orthos can only be a power of 2, such as 2048x2048 # orthos can only be a power of 2, such as 2048x2048
@ -47,6 +79,9 @@ class mstr_orthographic:
def _buildTile(self): def _buildTile(self):
mstr_msg("mstr_orthographic", "Beginning construction of tile") mstr_msg("mstr_orthographic", "Beginning construction of tile")
# We need to know which platform we are on
os_platform = os.name
# Create the _cache folder, should it not exist. # Create the _cache folder, should it not exist.
# Temporary images for the ortho tile generation go here # Temporary images for the ortho tile generation go here
if not os.path.exists(self._output + "/_cache"): if not os.path.exists(self._output + "/_cache"):
@ -80,6 +115,11 @@ class mstr_orthographic:
osmxml = mstr_osmxml(0,0) osmxml = mstr_osmxml(0,0)
mstr_msg("mstr_orthographic", "Set initial coordinates and bounding box for OSM acquisition") mstr_msg("mstr_orthographic", "Set initial coordinates and bounding box for OSM acquisition")
# The highest encountered tile numbers
# This is needed to produce the zoom level 16 images
top_lat = 1
top_lng = 1
# Previously, I downloaded all XML files in one go - but to ease the # Previously, I downloaded all XML files in one go - but to ease the
# stress on OSM servers and my server, we will do acquire the data # stress on OSM servers and my server, we will do acquire the data
# only for the current processed part of the tile. # only for the current processed part of the tile.
@ -142,34 +182,25 @@ class mstr_orthographic:
print("") print("")
print("") print("")
# Store a terrain file
'''
dmt = self._findWidthOfLongitude(bb_lat) * mstr_zl_18
sy = bb_lat + (self._vstep / 2)
sx = bb_lng + (mstr_zl_18 / 2)
ter_content = """A
800
TERRAIN
LOAD_CENTER """ + str(sy) + " " + str(sx) + " " + str(dmt) + " " + str(mstr_photores) + """
BASE_TEX_NOWRAP ../Textures/"""+str(cur_tile_y)+"_"+str(cur_tile_x)+".jpg"+"""
NO_ALPHA"""
with open(self._output + "\\Tiles\\"+str(bb_lat)+"_"+str(bb_lng)+"\\terrain\\"+str(cur_tile_y)+"_"+str(cur_tile_x)+".ter", 'w') as textfile:
textfile.write(ter_content)
mstr_msg("mstr_orthographic", "Wrote .ter file")
'''
# Adjust longitude coordinates # Adjust longitude coordinates
cur_tile_x = cur_tile_x+1 cur_tile_x = cur_tile_x+1
bb_lng = bb_lng + mstr_zl_18 bb_lng = bb_lng + mstr_zl_18
bb_lng_edge = bb_lng_edge + mstr_zl_18 bb_lng_edge = bb_lng_edge + mstr_zl_18
mstr_msg("mstr_orthographic", "Adjustment of longitude performed") mstr_msg("mstr_orthographic", "Adjustment of longitude performed")
# Adjust peak longitude tile number
if cur_tile_x > top_lng:
top_lng = cur_tile_x
# Clear out cache # Clear out cache
if mstr_clear_cache == True: if mstr_clear_cache == True:
ch = glob.glob(mstr_datafolder + "_cache\\*") ch = glob.glob(mstr_datafolder + "_cache\\*")
for f in ch: for f in ch:
os.remove(f) if os_platform == "nt":
if self._isFileAccessibleWin(f) == True:
os.remove(f)
if os_platform == "posix":
if self._isFileAccessiblePosix(f) == True:
os.remove(f)
mstr_msg("mstr_orthographic", "Cleared cache") mstr_msg("mstr_orthographic", "Cleared cache")
@ -181,6 +212,27 @@ NO_ALPHA"""
bb_lat = bb_lat + self._vstep bb_lat = bb_lat + self._vstep
bb_lat_edge = bb_lat_edge + self._vstep bb_lat_edge = bb_lat_edge + self._vstep
mstr_msg("mstr_orthographic", "Adjustment of latitude performed") mstr_msg("mstr_orthographic", "Adjustment of latitude performed")
# Adjust peak latitude number
if cur_tile_y > top_lat:
top_lat = cur_tile_y
mstr_msg("mstr_orthographic", "Generation of all tiles completed!")
mstr_msg("mstr_orthographic", "Generating ZL16 tiles and keeping airport tiles")
tg = mstr_tilegen(self._lat, self._lng, self._vstep, top_lat, top_lng)
tg.genTiles()
mstr_msg("mstr_orthographic", "Final step completed.")
print("")
print("")
mstr_msg("mstr_orthographic", "Tile data in: " + mstr_datafolder + "\\Tiles\\" + str(self._lat) + "_" + self._lng)
mstr_msg("mstr_orthographic", "Orthos are in the Textures subfolder")
mstr_msg("mstr_orthographic", "X-Plane .ter's are in the terrain subfolder")
print("")
print("")
mstr_msg("mstr_orthographic", "Thanks for using Orthographic! -- Best, Marcus")
print("")

View File

@ -72,10 +72,12 @@ Apart from that I am aware that the code is most likely not the best and can be
- Open the file defines.py - Open the file defines.py
- Change mstr_datafolder to where you want the data of Orthographic to be stored and placed - Change mstr_datafolder to where you want the data of Orthographic to be stored and placed
- Change mstr_airport_radius to an amount in kilometers. Areas around airports with ICAO code will get aerials with zoom level 18 - Change mstr_airport_radius to an amount in zoom level 18 tiles. Areas around airports with ICAO code will get aerials with zoom level 18. I recommend to leave the default at 5
In this file you can also define how large you want the final products to be - you can choose between 4k resolution (2048px) or 16k resolution (4096). The choice you make here should be based on 1) how much detail you want, and 2) how much VRAM your GPU has. The larger a texture, the more VRAM is needed, and the more processing is required by the GPU. I personally go with 2048, as I have a RTX2060, so my VRAM is limited. When at altitudes of 10,000 feet and above, you will most definitely not notice the difference between 4096 and 2048. In this file you can also define how large you want the final products to be - you can choose between 4k resolution (2048px) or 16k resolution (4096). The choice you make here should be based on 1) how much detail you want, and 2) how much VRAM your GPU has. The larger a texture, the more VRAM is needed, and the more processing is required by the GPU. I personally go with 2048, as I have a RTX2060, so my VRAM is limited. When at altitudes of 10,000 feet and above, you will most definitely not notice the difference between 4096 and 2048.
NOTE! 4096 not yet implemented, but planned.
Change the mstr_photores variable to 4096 if you want the maximum possible. Change the mstr_photores variable to 4096 if you want the maximum possible.
Just a note: 4096 also uses 4x more hard drive space. 4k uses about 2MB per image, where as 16k uses about 8-10MB per image. This may not seem much for one image, but keep in mind we are talking about quite a considerable number of images. To get an idea - if you have it, look into any folder of an Ortho4XP tile in your X-Plane folder, and check the size of the "textures" folder. Just a note: 4096 also uses 4x more hard drive space. 4k uses about 2MB per image, where as 16k uses about 8-10MB per image. This may not seem much for one image, but keep in mind we are talking about quite a considerable number of images. To get an idea - if you have it, look into any folder of an Ortho4XP tile in your X-Plane folder, and check the size of the "textures" folder.
@ -85,7 +87,7 @@ Just a note: 4096 also uses 4x more hard drive space. 4k uses about 2MB per imag
Very simple. Very simple.
[codebox]python main.py LATITUDE LONGITUDE[/codebox] [codebox]python og.py LATITUDE LONGITUDE[/codebox]
So for example So for example
@ -119,3 +121,4 @@ Leverkusen, Germany
As I have hosted this on my private but publicly accessible gitweb (which is the same tool kernel.org uses btw), it should be clear that I am making the code available to everyone. Of course, you are free to improve (I am sure the code needs some optimisation). If you changed or improved something, and you publish it (no matter where), you MUST adhere to the license this software ships with, which is OSL 3.0. As I have hosted this on my private but publicly accessible gitweb (which is the same tool kernel.org uses btw), it should be clear that I am making the code available to everyone. Of course, you are free to improve (I am sure the code needs some optimisation). If you changed or improved something, and you publish it (no matter where), you MUST adhere to the license this software ships with, which is OSL 3.0.
As it is hosted on my private gitweb, and not in a public instance like GitHub, I need to be very careful and clear about this. In short it means, also legally, that you cannot download the current snapshot, post the content somewhere else, and claim you made it. Respect the time, work and energy I have invested into this project :) As it is hosted on my private gitweb, and not in a public instance like GitHub, I need to be very careful and clear about this. In short it means, also legally, that you cannot download the current snapshot, post the content somewhere else, and claim you made it. Respect the time, work and energy I have invested into this project :)

View File

@ -73,6 +73,12 @@ class mstr_tiledb:
rws = r.fetchall() rws = r.fetchall()
return rws return rws
# Get all tiles with detected airports (ICAO codes)
def get_tiles_with_airports(self):
r = self._crs.execute("SELECT * FROM airports")
rws = r.fetchall
return rws
# Perform a custom query and retrieve results # Perform a custom query and retrieve results
def perform_query(self, qry): def perform_query(self, qry):

View File

@ -18,18 +18,180 @@ from PIL import Image, ImageFilter
from log import * from log import *
from functions import * from functions import *
from tiledb import * from tiledb import *
import math
import os
import glob
class mstr_tilegen: class mstr_tilegen:
# We only need some values. Also sets up connection to DB # We only need some values. Also sets up connection to DB
def __init__(self, lat, lng, lngw, vstep): def __init__(self, lat, lng, vstep, max_lat, max_lng):
self._lat = lat self._lat = lat
self._lng = lng self._lng = lng
self._lngw = lngw self._vstep = vstep
self._vstep = vstep self._maxlat = max_lat
self._maxlng = max_lng
# Connection to DB # Connection to DB
self._tiledb = mstr_tiledb(lat, lng) self._tiledb = mstr_tiledb(lat, lng)
mstr_msg("mstr_tilegen", "Tilegen initialized")
# Generates the ZL16 tiles and stores them # To write down X-Plane .ter files, we will need to know the exact size
# of the particular longitude we are in, as this value varies depending
# on where you are on a sphere.
# Returned values is in meters.
# The current latitude is needed.
def _findWidthOfLongitude(self, lat):
dm = math.cos(math.radians(lat)) * 111.321 # <- 1 deg width at equator in km
return round(dm * 1000, 3)
# Generates the ZL16 tiles and stores them.
# We generate ZL16 tiles first, then we check which tiles to keep near airports
def genTiles(self): def genTiles(self):
pass # The current lat and lng tile numbers
cur_lat = 1
cur_lng = 1
# Actual starting coordinates for ZL16
a_lat = self._lat + self._vstep * 2
a_lng = self._lng + mstr_zl_18 * 2
# Scaled res
scaled_res = int(mstr_photores/4) # For example, 512 for a photo res of 2048
# Find out how many steps we can walk in every direction
steps_lat = int(math.ceil(self._maxlat/4))
steps_lng = int(math.ceil(self._maxlng/4))
mstr_msg("mstr_tilegen", "Latitude and longitude steps determined")
# OK... so. Let's finish this.
for lt in range(1, steps_lat):
for ln in range(1, steps_lng):
# Find out which tiles to process
tiles = findZL16tiles(cur_lat, cur_lng)
# Generate the ZL16 image
zl16 = Image.new("RGB", (mstr_photores, mstr_photores))
# Walk through this array
xpos = 0
ypos = int(scaled_res*3)
for i in range(0, 3):
for j in range(0, 3):
# We may run into situations where ask for tiles that don't exist...
# Let's make sure we can continue
fpath = mstr_datafolder + "Tiles\\" + str(self._lat) + "_" + str(self._lng) + "\\Textures\\" + str(tiles[i][j][0]) + "_" + str(tiles[i][j][1]) + ".jpg"
if os.path.isfile( fpath ):
tlimg = Image.open(fpath)
tlimg = tlimg.resize((scaled_res,scaled_res), Image.Resampling.BILINEAR)
zl16.paste(tlimg, (xpos, ypos))
xpos = xpos + scaled_res
xpos = 0
ypos = ypos - scaled_res
# Now save this image
zl16.save(mstr_datafolder + "Tiles\\" + str(self._lat) + "_" + str(self._lng) + "\\Textures\\" + str(self._lat) + "-" + str(ln) + "_" + str(self._lng) + "-" + str(lt) + "_OG16.jpg", format='JPEG', subsampling=0, quality=100)
# Store a terrain file
dmt = self._findWidthOfLongitude(a_lat) * mstr_zl_16
ter_content = """A
800
TERRAIN
LOAD_CENTER """ + str(a_lat) + " " + str(a_lng) + " " + str(dmt) + " " + "../Textures/" + str(self._lat) + "-" + str(ln) + "_" + str(self._lng) + "-" + str(lt) + "_OG_16.jpg"+"""
NO_ALPHA"""
with open(mstr_datafolder + "\\Tiles\\"+str(self._lat)+"_"+str(self._lng)+"\\terrain\\"+str(self._lat)+"_"+str(lt)+"-"+str(self._lng)+"-"+str(ln)+"_OG16.ter", 'w') as textfile:
textfile.write(ter_content)
mstr_msg("mstr_tilegen", "Wrote .ter file")
# Adjust
a_lng = a_lng + (mstr_zl_16 * 4)
cur_lng = cur_lng + 4
mstr_msg("mstr_tilegen", "Adjusted coordinate values")
# Adjust
a_lng = self._lat + (mstr_zl_16 * 2)
a_lat = a_lat + (self._vstep * 4)
cur_lat = cur_lat + 4
cur_lng = self._lng
mstr_msg("mstr_tilegen", "Adjusted coordinate values for next tile loop")
mstr_msg("mstr_tilegen", "Tile generation... completed (wow.jpg)")
# BUT! This is not the end. Yet.
# Make sure we keep tiles around airports.
airports = self._tiledb.get_tiles_with_airports()
mstr_msg("mstr_tilegen", "Filtering ZL18 tiles for airports")
# The ZL 18 tiles to keep in the end
tiles = []
mstr_msg("mstr_tilegen", "Finding ZL18 tiles to keep")
for a in airports:
tiles.append(findAirportTiles(int(a[1]), int(a[2])))
mstr_msg("mstr_tilegen", "Determined ZL18 tiles")
# Create a final array to make life easier
mstr_msg("mstr_tilegen", "Generating arrays for tiles to keep")
keeping = []
for t in tiles:
for i in t:
keeping.append(i)
# Perform the cleanup
mstr_msg("mstr_tilegen", "Cleaning up non-needed tiles")
for y in range(1, self._maxlat):
for x in range(1, self._maxlng):
fn = str(y) + "_" + str(x) + ".jpg"
found = False
for k in keeping:
kfn = str(k[0]) + "_" + str(k[1]) + ".jpg"
if fn == kfn:
found = True
break
if found == False:
os.remove(mstr_datafolder + "\\Tiles\\" + str(self._lat) + "_" + str(self._lng) + "\\Textures\\" + fn)
mstr_msg("mstr_tilegen", "Cleanup completed")
# And now for the final act of tonight's entertainment
mstr_msg("mstr_tilegen", "Writing .ter files for ZL18 tiles")
for k in keeping:
k_lat = self._lat + (k[0] * self._vstep) + (self._vstep * 0.5)
k_lng = self._lat + (k[1] * mstr_zl_18) + (mstr_zl_18 * 0.5)
k_dmt = self._findWidthOfLongitude(self._lng * mstr_zl_18)
k_fln = mstr_datafolder + "\\Tiles\\" + str(self._lat) + "_" + str(self._lng) + "\\terrain\\" + str(k[0]) + "_" + str(k[1]) + ".ter"
ter_content = """A
800
TERRAIN
LOAD_CENTER """ + str(k_lat) + " " + str(k_lng) + " " + str(k_dmt) + " " + "../Textures/" + str(k[0]) + "_" + str(k[1]) + ".jpg"+"""
NO_ALPHA"""
with open(k_fln, 'w') as textfile:
textfile.write(ter_content)
mstr_msg("mstr_tilegen", "Wrote all .ter files for ZL18 tiles.")
mstr_msg("mstr_tilegen", "Work complete.")
'''
Did we fly to the moon too soon?
Did we squander the chance?
In the rush of the race
The reason we chase is lost in romance
And still we try
To justify the waste
For a taste of man's greatest adventure
I blame you for the moonlit sky
And the dream that died
With the eagles' flights
I blame you for the moonlit nights
When I wonder why
Are the seas still dry
Don't blame this sleeping satellite
'''