]> marstr Code Repo - orthographic/commitdiff
First public release - corrected issue with forest rendering. Implemented file-in...
authormarstr <marcus@marstr.online>
Sat, 31 Aug 2024 19:47:15 +0000 (21:47 +0200)
committermarstr <marcus@marstr.online>
Sat, 31 Aug 2024 19:55:14 +0000 (21:55 +0200)
defines.py
functions.py
layergen.py
maskgen.py
orthographic.py
repoinfo
tiledb.py
tilegen.py

index d7dc54df3b1a6fb9498ef9535b3e7a6c194add6f..0a78fe8adf6fe5b9331fbfbd238ff06257478c9a 100644 (file)
@@ -22,7 +22,17 @@ mstr_osm_endpoint = "https://marstr.online/osm/v1/"
 mstr_photores = 2048 # <- Change this to 4096 for 16k resolution
 
 # 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?
 mstr_clear_cache = True
@@ -166,8 +176,8 @@ mstr_mask_blur = [
     ("landuse", "farmland", 15),
     ("landuse", "farmyard", 15),
     # Z-Order 2
-    ("landuse", "forest", 15),
-    ("leisure", "nature_reserve", 15),
+    ("landuse", "forest", 20),
+    ("leisure", "nature_reserve", 20),
     ("landuse", "military", 30),
     # Z-Order 3
     ("natural", "bare_rock", 25),
@@ -205,9 +215,3 @@ mstr_mask_blur = [
     ("building", "industrial", 1),
     ("building", "yes", 1)
 ]
-
-
-# Define tile main colors by latitude-longitude region
-mstr_base_colors = [
-    ((50,0), 100, 106, 77)
-]
index b731d4ed62c3b8aadbe24591119c1ca293c841ac..580ebce336c17d908da46e0f07fb1685541adfc5 100644 (file)
@@ -61,6 +61,24 @@ def findZL16tiles(v, h):
     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
 def in_circle(center_x, center_y, radius, x, y):
index db1774d4502b3f2af11b860bace3a7f4a4d9417d..39cc85c91c50d690a8d667d04b0061f3d56bd175 100644 (file)
@@ -219,11 +219,12 @@ class mstr_layergen:
                             ry = randrange(30,60)
                             f = randrange(1,10)
                             
-                            # Do some magic
-                            if f != 5:
-                                imgd.ellipse((x-int(rx/2), y-int(ry/2), x+rx, y+ry), fill="black")
-                            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))
+                            # Do some magic - but not on edges
+                            if x > 0 and x < osm_mask.width and y > 0 and y < osm_mask.height:
+                                if f != 5:
+                                    imgd.ellipse((x-int(rx/2), y-int(ry/2), x+rx, y+ry), fill="black")
+                                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
@@ -305,24 +306,34 @@ class mstr_layergen:
 
             # Let's try our hand at pseudo shadows
             if mstr_shadow_enabled == True:
-                shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
-                for sh in mstr_shadow_casters:
-                    if self._tag == sh[0] and self._value == sh[1]:
-                        mstr_msg("mstr_layergen", "Generating shadow for layer")
-                        shadow_pix = shadow.load()
-                        mask_pix = osm_mask.load()
-                        for y in range(self._imgsize-1):
-                            for x in range(self._imgsize-1):
-                                m = mask_pix[x,y]
-                                shf_x = x + mstr_shadow_shift
-                                if shf_x <= self._imgsize-1:
-                                    a = mask_pix[x,y][3]
-                                    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")
+                if mstr_shadow_shift >= 2:
+                    shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
+                    for sh in mstr_shadow_casters:
+                        if self._tag == sh[0] and self._value == sh[1]:
+                            mstr_msg("mstr_layergen", "Generating shadow for layer")
+                            shadow_pix = shadow.load()
+                            mask_pix = osm_mask.load()
+                            for y in range(self._imgsize-1):
+                                for x in range(self._imgsize-1):
+                                    m = mask_pix[x,y]
+                                    shf_x = 0
+                                    # Buildings get slightly closer shadows
+                                    if self._tag == "building":
+                                        shf_x = x + int(mstr_shadow_shift/2)
+                                    if self._tag != "building":
+                                        shf_x = x + mstr_shadow_shift
+                                    if shf_x <= self._imgsize-1:
+                                        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.")
                 
 
-
-
-'''
-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")
-'''
index 8ee96802ac7ded53285b4e01f0c5ee3face53c54..5375d77e52126bb8734abbd7a025fab77d8c22ea 100644 (file)
@@ -182,13 +182,4 @@ class mstr_maskgen:
         mask_img.save(mstr_datafolder + "_cache\\" + fstr + "_" + self._tag + "-" + self._value + ".png")
         # Inform
         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()
\ No newline at end of file
+    
\ No newline at end of file
index 65b1959d42a21a5fe7785d2aba119115949201b5..8434e755e02686135b6f92ece96efd4637a470c8 100644 (file)
@@ -19,11 +19,43 @@ from maskgen import *
 from layergen import *
 from photogen import *
 from osmxml import *
+from tilegen import *
 
 
 # The main class which handles the rest
 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
     # 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
@@ -47,6 +79,9 @@ class mstr_orthographic:
     def _buildTile(self):
         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.
         # Temporary images for the ortho tile generation go here
         if not os.path.exists(self._output + "/_cache"):
@@ -80,6 +115,11 @@ class mstr_orthographic:
         osmxml = mstr_osmxml(0,0)
         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
         # stress on OSM servers and my server, we will do acquire the data
         # only for the current processed part of the tile.
@@ -142,34 +182,25 @@ class mstr_orthographic:
                     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
                 cur_tile_x = cur_tile_x+1
                 bb_lng = bb_lng + mstr_zl_18
                 bb_lng_edge = bb_lng_edge + mstr_zl_18
                 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
                 if mstr_clear_cache == True:
                     ch = glob.glob(mstr_datafolder + "_cache\\*")
                     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")
             
 
@@ -181,6 +212,27 @@ NO_ALPHA"""
             bb_lat = bb_lat + self._vstep
             bb_lat_edge = bb_lat_edge + self._vstep
             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("")
+
+
 
 
 
index c97df70b527c4e9cb6d49e1d3fe7afd094ed9457..e50ef2fd28c0df9e0fd259f73969790890eaf65e 100644 (file)
--- a/repoinfo
+++ b/repoinfo
@@ -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
 - 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.
 
+NOTE! 4096 not yet implemented, but planned.
+
 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.
@@ -85,7 +87,7 @@ Just a note: 4096 also uses 4x more hard drive space. 4k uses about 2MB per imag
 
 Very simple.
 
-[codebox]python main.py LATITUDE LONGITUDE[/codebox]
+[codebox]python og.py LATITUDE LONGITUDE[/codebox]
 
 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 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 :)
+
index 5109484c5780e72b49b1b64f3bb6b5194eb95595..c8b73638147ea76a4c7ee850d597aa073c090894 100644 (file)
--- a/tiledb.py
+++ b/tiledb.py
@@ -73,6 +73,12 @@ class mstr_tiledb:
         rws = r.fetchall()
         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
     def perform_query(self, qry):
index a1fcb52b17ee787329d99023c4caa995f8c77b18..e1fbc0f63603a54fe1d48e33dbc47a2d9f25c613 100644 (file)
@@ -18,18 +18,180 @@ from PIL import Image, ImageFilter
 from log import *
 from functions import *
 from tiledb import *
+import math
+import os
+import glob
 
 class mstr_tilegen:
     # We only need some values. Also sets up connection to DB
-    def __init__(self, lat, lng, lngw, vstep):
-        self._lat = lat
-        self._lng = lng
-        self._lngw = lngw
-        self._vstep = vstep
+    def __init__(self, lat, lng, vstep, max_lat, max_lng):
+        self._lat    = lat
+        self._lng    = lng
+        self._vstep  = vstep
+        self._maxlat = max_lat
+        self._maxlng = max_lng
         # Connection to DB
         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):
-        pass
\ No newline at end of file
+        # 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
+'''