# -------------------------------------------------------------------
# ORTHOGRAPHIC
# Your personal aerial satellite. Always on. At any altitude.*
# Developed by MarStrMind
# License: Open Software License 3.0
# Up to date version always on marstr.online
# -------------------------------------------------------------------
# layergen.py
# Generates a full-sized geo layer image, based on the required layer
# type. We use a simple randomization method to generate such an
# image, which is then used for the final photo in photogen.
# -------------------------------------------------------------------

import glob
import os
from random import randrange
import random
from PIL import Image, ImageFilter, ImageDraw
from defines import *
from log import *
from tileinfo import *
from osmxml import *
from functions import *
from xp_normalmap import *

class mstr_layergen:

    # Initializes the layer generator. can_choose will go false if we need
    # a pre-determined layer from another tile, should this be adjacent to it.
    # In this case layer_needed will be populated with the appropriate number.
    # You also need the zoom level so that we can generate a scaled version.
    def __init__(self, tag, value, lat, latnum, lng, lngnum, is_line, is_completion=False):
        self._tag = tag
        self._value = value
        self._latitude = lat
        self._lat_number = latnum
        self._longitude = lng
        self._lng_number = lngnum
        self._layerborder = -1
        self._is_completion = is_completion
        # Define layer size depending on what is wanted
        self._imgsize = 0
        self._isline = is_line
        if mstr_photores == 2048: self._imgsize = 2048
        #if mstr_photores == 4096: self._imgsize = 6000
        #mstr_msg("layergen", "Layer gen initialized")
    
    # Define maximum latitude and longitude tile numbers
    def set_max_latlng_tile(self, maxlatlng):
        self._maxlat = maxlatlng[0]
        self._maxlng = maxlatlng[1]
        mstr_msg("layergen", "Maximum latitude and longitude tile numbers received")

    # Set latlng folder
    def set_latlng_folder(self, latlngfld):
        self._latlngfld = latlngfld

    # Tile info object
    def open_tile_info(self):
        self._tileinfo = mstr_tileinfo(self._latitude, self._longitude, self._lat_number, self._lng_number, self._latlngfld)

    # This generates a "border" image, for example farmland usually has a small space of grass
    # before the actual crop of farm field itself. This generates this "border" layer,
    # and returns it.
    # Needs the actual edge mask, and the tag and value to be used as border.
    # Perform necessary adjustments on the mask prior to this call, for example blurring or
    # other effects.
    def genborder(self, edgemask, tag, value):
        layer = Image.new("RGBA", (self._imgsize, self._imgsize))
        root_folder = mstr_datafolder + "textures/" + tag + "/" + value

        # Determine which sources we use
        brd = glob.glob(root_folder + "/brd/b*.png")
        src = -1
        if len(brd) == 1: src=1
        if len(brd) >= 2:
            src = randrange(1, len(brd)+1)
        ptc = glob.glob(root_folder + "/ptc/b" + str(src) + "_p*.png")
        
        # Load in the sources to work with
        brd_src = Image.open(root_folder + "/brd/b" + str(src) + ".png")
        ptc_src = []
        for p in ptc:
            pimg = Image.open(p)
            pimg = pimg.rotate(randrange(0, 360), expand=True)
            ptc_src.append(pimg)
        mstr_msg("layergen", "Border sources selected")

        # Begin producing a largely random image
        samples = 250   # <- We need this in a moment
        for i in range(samples):
            imgid = 0
            if len(ptc_src) == 1: imgid = 0
            if len(ptc_src) >= 2:
                imgid = randrange(1, len(ptc_src)+1) - 1
            l = 0 - int(ptc_src[imgid].width / 2)
            r = layer.width - int(ptc_src[imgid].width / 2)
            t = 0 - int(ptc_src[imgid].height / 2)
            b = layer.height - int(ptc_src[imgid].height / 2)
            layer.alpha_composite( ptc_src[imgid], ( randrange(l, r), randrange(t, b) ) )
        mstr_msg("layergen", "Border image generated")

        # We now need to add the seamless border
        layer.alpha_composite( brd_src )
        mstr_msg("layergen", "Layer image completed")

        # And now for the Big Mac.
        # Generate the layer from the mask.
        layer_comp = Image.new("RGBA", (self._imgsize, self._imgsize))
        layer_final = Image.composite(layer, layer_comp, edgemask)

        # Provide the image
        return layer_final


    # Find the source to use pre-determined in phase one
    def findLayerSource(self):
        # The source number
        src = -1

        # The already existing source data
        srcfile = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/" + str(self._lat_number) + "_" + str(self._lng_number)

        # Let's open the file and find our entry
        with open(srcfile) as file:
            for line in file:
                linedata = line.split(" ")
                if linedata[2] == self._tag and linedata[3] == self._value:
                    src = int(linedata[4])
                    break
        
        # Should we encounter a -1 at this point, we can choose something
        # It means it touches no border as it was not found in the file
        if src == -1:
            root_folder = mstr_datafolder + "textures/"
            for s in mstr_ortho_layers:
                if s[0] == self._tag and s[1] == self._value:
                    fld_main = len(s)-2
                    fld_sub  = len(s)-1
                    root_folder = root_folder + s[fld_main] + "/" + s[fld_sub]
            
            brd = glob.glob(root_folder + "/brd/b*.png")
            src = randrange(1, len(brd)+1)

        return src



    # This generates the layer from the defined mask
    def genlayer(self, mask, xml):

        mstr_msg("layergen", "Layer to be generated: " + str(self._latitude) + "-" + str(self._lat_number) + ":" + str(self._longitude) + "-" + str(self._lng_number) + " -- tag: " + self._tag + " - value: " + self._value )

        # Before we generate the layer, let's check for airports in this chunk
        mstr_msg("layergen", "Checking for airport/s with ICAO code")
        
        icao = None
        if xml != None:
            icao = xml.find_icao_codes()
            mstr_msg("layergen", "Found " + str(len(icao)) + " airport/s")
        # Runway surface, if any other than concrete/asphalt
        rw_surface = ""
        # If we find an airport, make a note ...
        if icao != None:
            if len(icao) >= 1 and self._is_completion == False:
                #for i in icao:
                    # ... but only, if this airport is not already noted
                    #iccheck = self._tiledb.perform_query("SELECT * FROM airports WHERE icao='" + i +"';")
                    #if len(iccheck) == 0:
                        #self._tiledb.insert_icao(i, self._lat_number, self._lng_number, self._latitude, self._longitude)
                    #    mstr_msg("layergen", "Airport/s noted in data file")
                rw_surface = xml.find_runway_surface()

        # The image for the layer itself
        layer = Image.new("RGBA", (self._imgsize, self._imgsize))
        layer_pix = layer.load()

        # There are some things we need to use sources for, and some things, we do not.
        # We need to differentiate that.

        if (self._isline == False and self._tag != "building") or (self._is_completion == True):
            # Determine where we get the our source material from
            root_folder = mstr_datafolder + "textures/"
            for s in mstr_ortho_layers:
                if s[0] == self._tag and s[1] == self._value:
                    fld_main = len(s)-2
                    fld_sub  = len(s)-1
                    root_folder = root_folder + s[fld_main] + "/" + s[fld_sub]

            # Determine which sources to use.
            src = self.findLayerSource()
            
            ptc = glob.glob(root_folder + "/ptc/b" + str(src) + "_p*.png")
            
            # Load in the sources to work with
            brd_src = Image.open(root_folder + "/brd/b" + str(src) + ".png")
            ptc_src = []
            for p in ptc:
                ptc_src.append(Image.open(p))
            mstr_msg("layergen", "Layer sources selected")
            
            # Generate an edge mask from the original
            osm_edge = mask.filter(ImageFilter.FIND_EDGES)
            osm_edge = osm_edge.filter(ImageFilter.MaxFilter)
            mstr_msg("layergen", "Edge mask generated")

            # This adds some natural looking shapes to these types of features
            if self._value == "forest" or self._value == "nature_reserve":
                epx = osm_edge.load()
                imgd = ImageDraw.Draw(mask)

                # Walk through a grid of 100x100 - on the edge image
                for y in range(0, mask.height, int(mask.height/100)):
                    for x in range(0, mask.width, int(mask.width/100)):
                        px = epx[x,y]
                        if px[3] == 255:
                            rx = randrange(24,60)
                            ry = randrange(24,60)
                            f = randrange(1,10)

                            # Randomize the found locations a little
                            psx = randrange(x-11, x+11)
                            psy = randrange(y-11, y+11)
                            
                            # Do some magic - but not on edges
                            if x > 0 and x < mask.width and y > 0 and y < mask.height:
                                if f != 5:
                                    imgd.ellipse((psx-int(rx/2), psy-int(ry/2), psx+rx, psy+ry), fill="black")
                                if f == 3 or f == 7:
                                    imgd.ellipse((psx-int(rx/2), psy-int(ry/2), psx+rx, psy+ry), fill=(0,0,0,0))


            # We need to change the image in certain conditions
            if self._value == "hedge" and self._tag == "barrier":
                mask = osm_edge

            # From here on in we will need to perform some adjustments on the masks, depending
            # on what they are.
            for i in mstr_mask_blur:
                if i[0] == self._tag and i[1] == self._value:
                    if self._tag != "place" and (self._value != "sea" or self._value != "ocean"): 
                        mask = mask.filter(ImageFilter.BoxBlur(radius=i[2]))
                        break
            
            # Begin producing a largely random image
            samples = 250   # <- We need this in a moment
            for i in range(samples):
                imgid = 0
                if len(ptc_src) == 1: imgid = 0
                if len(ptc_src) >= 2:
                    imgid = randrange(1, len(ptc_src)+1) - 1
                l = 0 - int(ptc_src[imgid].width / 2)
                r = layer.width - int(ptc_src[imgid].width / 2)
                t = 0 - int(ptc_src[imgid].height / 2)
                b = layer.height - int(ptc_src[imgid].height / 2)
                layer.alpha_composite( ptc_src[imgid], ( randrange(l, r), randrange(t, b) ) )
            mstr_msg("layergen", "Layer image generated")

            # We now need to add the seamless border
            layer.alpha_composite( brd_src )

            # Here we need to do some magic to make some features look more natural
            if (self._tag == "landuse" and self._value == "meadow") or (self._tag == "natural" and self._value == "grassland") or (self._tag == "natural" and self._value == "heath") or (self._tag == "landuse" and self._value == "cemetery") or (self._tag == "landuse" and self._value == "residential"):
                if self._is_completion == False:
                    amt = randrange(2, 9)
                    for i in range(1, amt+1):
                        ptc = randrange(1, 14)
                        img = Image.open(mstr_datafolder + "textures/tile/completion/p" + str(ptc)+".png")
                        img = img.rotate(randrange(0, 360), expand=True)
                        a = img.getchannel("A")
                        bbox = a.getbbox()
                        img = img.crop(bbox)
                        lx = randrange( self._imgsize - img.width ) 
                        ly = randrange( self._imgsize - img.height )
                        layer.alpha_composite( img, (lx, ly) )
                if self._is_completion == True:
                    mp = mask.load()
                    edn = self.xplane_latlng_folder(self.find_earthnavdata_number())
                    idx = 0
                    for r in mstr_completion_colors:
                        if r[0] == edn:
                            break
                        else:
                            idx = idx+1
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            if mp[x,y][3] > 0:
                                # Pick a color
                                a = mp[x,y]
                                cidx = randrange(len(mstr_completion_colors[idx][1]))
                                clr = mstr_completion_colors[idx][1][cidx]
                                layer_pix[x,y] = (clr[0], clr[1], clr[2], a[3])
                    amt = randrange(1,51)
                    for i in range(1, amt+1):
                        ptc = randrange(1, 14)
                        img = Image.open(mstr_datafolder + "textures/tile/completion/p" + str(ptc)+".png")
                        img = img.rotate(randrange(0, 360), expand=True)
                        a = img.getchannel("A")
                        bbox = a.getbbox()
                        img = img.crop(bbox)
                        imgp = img.load()
                        for y in range(img.height):
                            for x in range(img.width):
                                c = imgp[x,y]
                                nc = (c[0], c[1], c[2], int(imgp[x,y][3]*0.4))
                                imgp[x,y] = nc
                        lx = randrange( self._imgsize - img.width ) 
                        ly = randrange( self._imgsize - img.height )
                        layer.alpha_composite( img, (lx, ly))
                    layer = layer.filter(ImageFilter.GaussianBlur(radius=1))
                    

            # Add trees only in some features
            if (self._tag == "landuse" and self._value == "cemetery") or (self._tag == "landuse" and self._value == "residential") or (self._tag == "leisure" and self._value == "park"):
                trees = Image.new("RGBA", (self._imgsize, self._imgsize))
                amt = 3500
                for i in range(1, amt+1):
                    p = randrange(1, 16)
                    tree = Image.open(mstr_datafolder + "textures/building/area/p" + str(p) + ".png")
                    lx = randrange( self._imgsize - tree.width ) 
                    ly = randrange( self._imgsize - tree.height )
                    trees.alpha_composite(tree, (lx, ly))
                    
                tree_shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
                tree_pix = trees.load()
                shadow_pix = tree_shadow.load()
                for y in range(self._imgsize):
                    for x in range(self._imgsize):
                        tp = tree_pix[x,y]
                        if tp[3] > 0:
                            rndshd = randrange(5, 210)
                            sc = (0,0,0,rndshd)
                            if x+8 < self._imgsize and y+5 < self._imgsize:
                                shadow_pix[x+8,y+5] = sc
                tree_shadow = tree_shadow.filter(ImageFilter.GaussianBlur(radius=2))
                tree_shadow.alpha_composite(trees)
                layer.alpha_composite(tree_shadow)

            mstr_msg("layergen", "Layer image completed")


            # And now for the Big Mac.
            # Generate the layer from the mask.
            layer_comp = Image.new("RGBA", (self._imgsize, self._imgsize))
            layer_pix = layer.load()
            mask_pix = mask.load()
            layer_comp_pix = layer_comp.load()
            for y in range(self._imgsize):
                for x in range(self._imgsize):
                    if mask_pix[x, y][3] > 0:
                        rgb=layer_pix[x,y]
                        a=mask_pix[x,y]
                        layer_comp_pix[x, y] = ( rgb[0], rgb[1], rgb[2], a[3])

            # For some things, we will need to add a border and then add this to the layer.
            layer_border = None
            if self._tag == "landuse":
                if self._value == "forest" or self._value == "farmland":
                    osm_edge = osm_edge.filter(ImageFilter.ModeFilter(size=15))
                    osm_edge = osm_edge.filter(ImageFilter.BoxBlur(radius=2))
                    layer_border = self.genborder(osm_edge, "landuse", "meadow")
                    layer_comp.alpha_composite(layer_border)


            # Here we want to make sure that the generated image fits well with others, so
            # let's do that.
            mstr_msg("layergen", "Generating adjacent fades")
            adjfade = self.generate_adjacent_fades(mask)

            layer_comp.alpha_composite(adjfade)
            mstr_msg("layergen", "Adjacent fading completed")


            # Add a white-ish border around pitches
            if self._tag == "leisure" and self._value == "pitch":
                epx = osm_edge.load()
                for y in range(self._imgsize):
                    for x in range(self._imgsize):
                        ep = epx[x,y]
                        if ep[3] > 0:
                            d = randrange(10,101)
                            nw = (200-d,200-d,200-d,255)
                            layer_comp_pix[x,y] = nw

            # I need to put this special sub-call here to solve an otherwise unsolvable
            # conflict with layer order
            if self._tag == "landuse" and self._value == "forest":
                # The residential layer MUST exist before we reach the forest part.
                fn = mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_landuse-residential_layer.png"
                if os.path.isfile(fn):
                    rsd = Image.open(fn)
                    rsd_pix = rsd.load()
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            rpix = rsd_pix[x,y]
                            lpix = layer_comp_pix[x,y]
                            if rpix[3] > 0 and lpix[3] > 0:
                                layer_comp_pix[x,y] = (lpix[0], lpix[1], lpix[2], 255-rpix[3])

            # Store layer
            #if self._is_completion == False:
            #    layer_comp.save( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer.png" )
            #if self._is_completion == True:
            #   layer_comp.save( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_tile-completion_layer.png" )
            #layer_final.save( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer.png" )
            mstr_msg("layergen", "Layer image finalized and saved.")


            # Depending on if scenery for XP should be made, AND if normal maps should be made, we would
            # need to make them at this exact point
            if mstr_xp_genscenery == True:
                if mstr_xp_scn_normalmaps == True and self._is_completion == False:
                    nm = False
                    for n in mstr_xp_normal_maps:
                        if n[0] == self._tag and (n[1] == self._value or n[1] == "*"):
                            nm = True
                            break
                    if nm == True:
                        nrm = mstr_xp_normalmap(self._latitude, self._longitude, self._tag, self._value, self._lat_number, self._lng_number, self._latlngfld)
                        nrm.build_normalmap(layer_comp)


            # Let's try our hand at pseudo shadows
            if mstr_shadow_enabled == True:
                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("layergen", "Generating shadow for layer")
                            shadow_pix = shadow.load()
                            mask_pix = mask.load()
                            shf = 1
                            while shf < mstr_shadow_shift:
                                for y in range(self._imgsize):
                                    for x in range(self._imgsize):
                                        mp = layer_comp_pix[x,y]
                                        if mp[3] == 255:
                                            if x+(shf*2) < self._imgsize and y+shf < self._imgsize:
                                                rndshd = randrange(5, 210)
                                                shadow_pix[x+(shf*2), y+shf] = (0,0,0,rndshd)
                                shf = shf+1
                            
                            # Tree removal
                            for y in range(self._imgsize):
                                for x in range(self._imgsize):
                                    lp = layer_comp_pix[x,y]
                                    if lp[3] >= 250:
                                        shadow_pix[x,y] = (0,0,0,0)

                            shadow = shadow.filter(ImageFilter.GaussianBlur(radius=1.5))

                            #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")
                            shadow.alpha_composite(layer_comp)
                            layer_comp = shadow
                            mstr_msg("layergen", "Shadow layer completed")


            # Create a water mask we need to remove from the DDS later
            if (self._tag == "natural" and self._value == "water") or (self._tag == "water" and self._value == "lake") or (self._tag == "water" and self._value == "pond") or (self._tag == "water" and self._value == "river") or (self._tag == "leisure" and self._value == "swimming_pool"):
                mstr_msg("layergen", "Generating inland water mask")
                inl_mask = Image.new("RGBA", (self._imgsize, self._imgsize), (0,0,0,0))
                lyr_pix = layer_comp.load()
                inl_pix = inl_mask.load()
                for y in range(self._imgsize):
                    for x in range(self._imgsize):
                        l = lyr_pix[x,y]
                        if l[3] > 65:
                            b = 255 - l[3]
                            inl_pix[x,y] = (255,0,255,255)
                #inl_mask.save(mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer_mask.png")
                layer_comp = inl_mask
                mstr_msg("layergen", "Inland water mask generated and saved")

            # Return the completed image
            return layer_comp
        

        # ---------------------------------------------------------------------------------------
        # ---------------------------------------------------------------------------------------
        # ---------------------------------------------------------------------------------------

        # If we encounter one of these road-specific tags, we need to proceed differently.

        if self._isline == True or self._tag == "building":

            # We will need the mask in question
            #mask = Image.open( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + ".png" )

            # Generate an edge mask from the original
            osm_edge = mask.filter(ImageFilter.FIND_EDGES)
            osm_edge = osm_edge.filter(ImageFilter.MaxFilter)
            mstr_msg("layergen", "Edge mask generated")

            # As above, we will apply the blur as noted in the defines
            # Except for buildings
            if self._tag != "building":
                for i in mstr_mask_blur:
                    if i[0] == self._tag and i[1] == self._value:
                        mask = mask.filter(ImageFilter.BoxBlur(radius=i[2]))
                        break
            
            
            # And now for the Big Mac.
            # Generate the layer from the mask. Same as above - except!
            # This time we have no source material - instead we will fill the
            # mask with a color that is appropriate for this street type.
            layer_comp = Image.new("RGBA", (self._imgsize, self._imgsize))
            mask_pix = mask.load()
            edge_pix = osm_edge.load()
            layer_comp_pix = layer_comp.load()

            for y in range(self._imgsize):
                for x in range(self._imgsize):
                    if mask_pix[x, y][3] > 0:
                        a=mask_pix[x,y]
                        e=edge_pix[x,y]
                        # Find a suitable color
                        d = 0
                        if self._tag == "aeroway" and self._value == "runway":
                            # It seems only runways with any other surface than concrete
                            # are mentioned in OSM. So we need to make sure when to render
                            # "concrete" and when to leave it. Only sometimes the word
                            # "asphalt" is mentioned
                            if rw_surface == "" or rw_surface == "asphalt":
                                d = randrange(81, 101)
                                layer_comp_pix[x, y] = ( d,d,d,a[3] )
                        if self._tag == "railway":
                            d = randrange(41, 61)
                            layer_comp_pix[x, y] = ( d,d,d,a[3] )
                        if self._tag == "highway" and self._value != "motorway":
                            dr = randrange(110,121)
                            dg = randrange(110,121)
                            db = randrange(115,130)
                            layer_comp_pix[x, y] = ( dr,dg,db,a[3] )
                        if self._tag == "highway" and self._value == "motorway":
                            dr = randrange(77,89)
                            dg = randrange(88,96)
                            db = randrange(90,101)
                            layer_comp_pix[x, y] = ( dr,dg,db,a[3] )
                        if self._tag == "waterway" and (self._value == "stream" or self._value == "river"):
                            d = randrange(1, 15)
                            # Rock, grass, water
                            mats = [ (48-d, 45-d, 42-d), (58-d, 81-d, 41-d), (129-d, 148-d, 159-d) ]
                            # Pick one of those
                            #pick = randrange(1,4)
                            pick = 2
                            t = a[3]-d
                            if t < 0: t = 0
                            if e[3] > 0:
                                layer_comp_pix[x, y] = ( mats[pick-1][0], mats[pick-1][1], mats[pick-1][2], 35 )
                        
                        # A bit special here
                        if self._tag == "building":
                            # Find a color range for the pixel
                            d = randrange(1,21)
                            nr = a[0]+40 - d
                            ng = a[1]+40 - d
                            nb = a[2]+40 - d
                            if nr < 0: nr = 0
                            if ng < 0: ng = 0
                            if nb < 0: nb = 0
                            if nr > 255: nr = 255
                            if ng > 255: ng = 255
                            if nb > 255: nb = 255
                            nc = (nr, ng, nb, 255)
                            layer_comp_pix[x,y] = (nr,ng,nb,255)
                            
                        if self._value == "track" or self._value == "path":
                            d = randrange(1,20)
                            r = 164 - d
                            g = 159 - d
                            b = 138  - d
                            layer_comp_pix[x, y] = ( r,g,b,a[3] )

            # A bit different for tree rows
            if self._tag == "natural" and self._value == "tree_row":
                trees = Image.new("RGBA", (self._imgsize, self._imgsize))
                for t in range(20001):
                    lx = randrange(self._imgsize)
                    ly = randrange(self._imgsize)
                    a = mask_pix[lx,ly]
                    if a[3] > 0:
                        if lx < self._imgsize and ly < self._imgsize:
                            p = randrange(1,16)
                            tree = Image.open(mstr_datafolder + "textures/building/area/p" + str(p) + ".png")
                            trees.alpha_composite(tree, (lx, ly))
                if mstr_shadow_enabled == True:
                    tree_shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
                    tree_pix = trees.load()
                    shadow_pix = tree_shadow.load()
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            tp = tree_pix[x,y]
                            if tp[3] > 0:
                                rndshd = randrange(5, 210)
                                sc = (0,0,0,rndshd)
                                if x+8 < self._imgsize and y+5 < self._imgsize:
                                    shadow_pix[x+8,y+5] = sc
                    tree_shadow = tree_shadow.filter(ImageFilter.GaussianBlur(radius=2))
                    tree_shadow.alpha_composite(trees)
                    layer_comp.alpha_composite(tree_shadow)

            # We will do some super magic here to let houses look more realistic
            if self._tag == "building":
                
                details = Image.new("RGBA", (self._imgsize, self._imgsize))
                tree_shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
                trees = Image.new("RGBA", (self._imgsize, self._imgsize))
                roof_details = Image.new("RGBA", (self._imgsize, self._imgsize))
                shadow = Image.new("RGBA", (self._imgsize, self._imgsize))

                if mstr_shadow_enabled == True:
                    fn = mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_building-" + self._value + "_layer_shadow.png"
                    if os.path.isfile(fn):
                        shadow = Image.open(fn)

                vls = [ "detached", "hotel", "farm", "semidetached_house", "apartments", "civic", "house", "school", "kindergarten", "yes" ]
                if self._value in vls:
                    # Generate a new image
                    details_pix = details.load()
                    layer_pix = layer_comp.load()
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            p = layer_pix[x,y]
                            if p[3] > 0:
                                shf_x = x+randrange(1, 16)
                                shf_y = y+randrange(1, 16)
                                shf_x2 = x-randrange(1, 16)
                                shf_y2 = y-randrange(1, 16)
                                if shf_x < self._imgsize and shf_y < self._imgsize and shf_x2 < self._imgsize and shf_y2 < self._imgsize:
                                    st = random.uniform(0.65, 0.85)
                                    ca = 255 * st
                                    aa = int(ca)
                                    d = randrange(1,26)
                                    d2 = randrange(1,26)
                                    details_pix[shf_x, shf_y] = (187-d, 179-d, 176-d, aa)
                                    details_pix[shf_x2, shf_y2] = (187-d2, 179-d2, 176-d2, aa)

                    # Image for roof details
                    roof_det_pix = roof_details.load()
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            mp = mask_pix[x,y]
                            if mp[3] == 255:
                                # Determine if we render some pixel
                                rnd = randrange(1, 3)
                                if rnd == 2:
                                    # Find a range for the base color of the pixel
                                    d = randrange(21)
                                    # Find a random alpha value
                                    a = randrange(1, 151)
                                    nc = (mstr_building_detail_colors[0][0]-d, mstr_building_detail_colors[0][1]-d, mstr_building_detail_colors[0][2]-d, a)
                                    roof_det_pix[x,y] = nc


                    # Let's see how it works with this method
                    #details.save(mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer_details.png")
                    #layer_comp.alpha_composite(details)

                    # Add some random trees
                    div = int(self._imgsize/200)
                    for y in range(0, self._imgsize, div):
                        for x in range(0, self._imgsize, div):
                            if x > 0 and x < self._imgsize and y > 0 and y < self._imgsize:
                                p = mask_pix[x, y]
                                if p[3] != 0:
                                    # We found something...
                                    # Determine if we put something somewhere
                                    placement = randrange(0, 5)
                                    if placement == 1:
                                        # Do some random shift away from this location
                                        shf_x = randrange(x-11, x+11)
                                        shf_y = randrange(y-11, y+11)
                                        if shf_x < self._imgsize and shf_y < self._imgsize:
                                            # Pick a number of trees to place
                                            numtrees = randrange(1, 16)
                                            for i in range(1, numtrees+1):
                                                # Pick some file
                                                pick = str(randrange(1, 16))
                                                tree = Image.open(mstr_datafolder + "textures/building/area/p" + pick + ".png")
                                                # Do a correction for the location if needed
                                                if shf_x < 1: shf_x = 1
                                                if shf_y < 1: shf_y = 1
                                                if shf_x > self._imgsize - tree.width: shf_x = self._imgsize - tree.width - 1
                                                if shf_y > self._imgsize - tree.height: shf_y = self._imgsize - tree.height - 1
                                                trees.alpha_composite(tree, (shf_x, shf_y))
                    
                    
                    if mstr_shadow_enabled == True:
                        tree_pix = trees.load()
                        shadow_pix = tree_shadow.load()
                        for y in range(self._imgsize):
                            for x in range(self._imgsize):
                                tp = tree_pix[x,y]
                                if tp[3] > 0:
                                    rndshd = randrange(5, 210)
                                    sc = (0,0,0,rndshd)
                                    if x+8 < self._imgsize and y+5 < self._imgsize:
                                        shadow_pix[x+8,y+5] = sc
                        tree_shadow = tree_shadow.filter(ImageFilter.GaussianBlur(radius=2))
                        tree_shadow.alpha_composite(trees)

                # Let's try this one on for size
                bld_comp = Image.new("RGBA", (self._imgsize, self._imgsize))
                details = details.filter(ImageFilter.GaussianBlur(radius=1))
                bld_comp.alpha_composite(details)
                bld_comp.alpha_composite(tree_shadow)
                bld_comp.alpha_composite(trees)
                shd_p  = shadow.load()
                for y in range(self._imgsize):
                    for x in range(self._imgsize):
                        c = shd_p[x,y]
                        if c[3] > 0:
                            s = (0,0,0,120-(randrange(0,21)))
                            shd_p[x,y] = s
                shadow = shadow.filter(ImageFilter.GaussianBlur(radius=1))
                bld_comp.alpha_composite(shadow)
                layer_comp = layer_comp.filter(ImageFilter.GaussianBlur(radius=1.1))
                bld_comp.alpha_composite(layer_comp)
                layer_comp = bld_comp
                layer_comp.alpha_composite(roof_details)

            mstr_msg("layergen", "Layer image generated")

            # Building shadow
            if mstr_shadow_enabled == True:
                    
                # Some funnies with shadows
                if self._tag == "building" and (self._value == "detached" or self._value == "semidetached_house" or self._value == "apartments" or self._value == "civic" or self._value == "house" or self._value == "terrace"):
                    mask_pix = mask.load()
                    roofshadow = Image.new("RGBA", (self._imgsize, self._imgsize))
                    roofpix = roofshadow.load()
                    # Generate a pseudo shifted roof shadow
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            mp = mask_pix[x,y]
                            if mp[3] == 255:
                                nx = x+8
                                ny = y+4
                                if nx < self._imgsize and ny < self._imgsize:
                                    roofpix[nx,ny] = (0,0,0,255)

                    # Now apply the shift where necessary
                    roofpix = roofshadow.load()
                    mask_pix = mask.load()
                    layer_comp_pix = layer_comp.load()
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            rp = roofpix[x,y]
                            mp = mask_pix[x,y]
                            if rp[3] == 255 and mp[3] == 255:
                                c = layer_comp_pix[x,y]
                                dim = randrange(30,61)
                                nr = c[0] - dim
                                ng = c[1] - dim
                                nb = c[2] - dim
                                if nr < 0: nr = 0
                                if ng < 0: ng = 0
                                if nb < 0: nb = 0
                                layer_comp_pix[x,y] = (nr, ng, nb, c[3])
                    #layer_comp = layer_comp.filter(ImageFilter.GaussianBlur(radius=1))


            # Let's add some details to the roofs
            if self._tag == "building":
                vls = [ "detached", "hotel", "farm", "semidetached_house", "apartments", "civic", "house", "school", "kindergarten", "yes" ]
                if self._value in vls:
                    roof_additional_detail = Image.new("RGBA", (self._imgsize, self._imgsize))
                    rad_pix = roof_additional_detail.load()
                    for r in range(30001):
                        lx = randrange(self._imgsize)
                        ly = randrange(self._imgsize)
                        mp = mask_pix[lx,ly]
                        if mp[3] == 255:
                            # Brighter or darker pixel
                            bod = randrange(1,3)
                            c = 0
                            if bod == 2:
                                c = 40
                            else:
                                c = 200
                            dt = (c, c, c, 130)
                            rad_pix[lx,ly] = dt
                            if lx+1 < self._imgsize:
                                rad_pix[lx+1, ly] = dt
                            if lx+1 < self._imgsize and ly+1 < self._imgsize:
                                rad_pix[lx+1, ly+1] = dt
                            if ly+1 < self._imgsize:
                                rad_pix[lx, ly+1] = dt
                    layer_comp.alpha_composite(roof_additional_detail)
            
            # Let's put some other details on commercial buildings
            if self._tag == "building":
                vls = [ "office", "retail", "industrial" ]
                if self._value in vls:

                    # Find a suitable location to render something
                    for r in range(15001):
                        lx = randrange(self._imgsize)
                        ly = randrange(self._imgsize)
                        mp = mask_pix[lx,ly]

                        # Think of some random shape
                        if mp[3] == 255:
                            rw = randrange(3,8)
                            rh = randrange(3,8)
                            sh = Image.new("RGBA", (rw, rh), (30,30,30,130))
                            shp = sh.load()
                            for sy in range(rh):
                                for sx in range(rw):
                                    if sx > 0 and sx < rw and sy > 0 and sy < rh: shp[sx, sy] = (180,180,180,160)
                            rt = randrange(1, 3)
                            if rt == 2:
                                sh = sh.rotate(45, expand=True)

                            layer_comp.alpha_composite(sh, (lx, ly))

            
            # Highways and runways of any kind get some special treatment
            if (self._tag == "highway" and self._value == "motorway") or (self._tag == "highway" and self._value == "primary") or (self._tag == "highway" and self._value == "secondary") or (self._tag == "highway" and self._value == "tertiary") or (self._tag == "aeroway" and self._value == "runway"):
                # We will now add some white lines for coolness
                osm_edge = mask.filter(ImageFilter.FIND_EDGES)
                mask_pix = osm_edge.load()
                layer_comp_pix = layer_comp.load()
                for y in range(self._imgsize):
                    for x in range(self._imgsize):
                        if mask_pix[x, y][3] > 0:
                            # Find a suitable color
                            w = randrange(125, 156)
                            a=mask_pix[x,y]
                            layer_comp_pix[x, y] = ( w,w,w,a[3] )

            if self._tag == "highway" and self._value == "residential":
                osm_edge = mask.filter(ImageFilter.FIND_EDGES)
                mask_pix = osm_edge.load()
                layer_comp_pix = layer_comp.load()
                for y in range(self._imgsize):
                    for x in range(self._imgsize):
                        if mask_pix[x, y][3] > 0:
                            # Find a suitable color
                            w = randrange(150,181)
                            a=mask_pix[x,y]
                            layer_comp_pix[x, y] = ( w,w,w,a[3] )
                mstr_msg("layergen", "Street lines added")


            # Same as above, except that streams are lines and are not drawn as polygons.
            # Therefore this part needs to be in here as well.
            if self._tag == "waterway" and self._value == "stream":
                mstr_msg("layergen", "Generating inland water mask")
                inl_mask = Image.new("RGBA", (self._imgsize, self._imgsize), (0,0,0,0))
                lyr_pix = layer_comp.load()
                inl_pix = inl_mask.load()
                for y in range(self._imgsize):
                    for x in range(self._imgsize):
                        l = lyr_pix[x,y]
                        if l[3] > 65:
                            b = 255 - l[3]
                            inl_pix[x,y] = (255,0,255,255)
                #inl_mask.save(mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer_mask.png")
                mstr_msg("layergen", "Inland water mask generated and saved")

            
            # Blur roads a bit
            if self._tag == "highway":
                layer_comp = layer_comp.filter(ImageFilter.GaussianBlur(radius=1))


            # Store layer
            #layer_comp.save( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer.png" )
            mstr_msg("layergen", "Layer image finalized and saved.")


            # Depending on if scenery for XP should be made, AND if normal maps should be made, we would
            # need to make them at this exact point
            if mstr_xp_genscenery == True:
                if mstr_xp_scn_normalmaps == True and self._is_completion == False:
                    nm = False
                    for n in mstr_xp_normal_maps:
                        if n[0] == self._tag and (n[1] == self._value or n[1] == "*"):
                            nm = True
                            break
                    if nm == True:
                        nrm = mstr_xp_normalmap(self._latitude, self._longitude, self._tag, self._value, self._lat_number, self._lng_number, self._latlngfld)
                        nrm.build_normalmap(layer_comp)


            # Return image
            return layer_comp


    # Should we find more than one source, the first one found will take precedence.
    # For the others, we will need to generate fading images, so that the final layer 
    # image works with other tiles
    def generate_adjacent_fades(self, mask):
        adj_sources = self.find_all_adjacent_sources()
        precedence = -1

        # Be prepared for every border
        brd_t = Image.open(mstr_datafolder + "textures/multi_source/brd_t.png")
        brd_r = Image.open(mstr_datafolder + "textures/multi_source/brd_r.png")
        brd_b = Image.open(mstr_datafolder + "textures/multi_source/brd_b.png")
        brd_l = Image.open(mstr_datafolder + "textures/multi_source/brd_l.png")

        brd_t_pix = brd_t.load()
        brd_r_pix = brd_r.load()
        brd_b_pix = brd_b.load()
        brd_l_pix = brd_l.load()

        for s in range(0, 4):
            if adj_sources[s] != -1:
                precedence = adj_sources[s]
                break

        # Generate required images
        # Basically a shortened version of the main layergen call
        adj_image = Image.new("RGBA", (self._imgsize, self._imgsize))
        for s in range(0, 4):
            if adj_sources[s] != precedence and adj_sources[s] != -1:
                src = adj_sources[s]

                adj_pix = adj_image.load()

                # Root folder
                root_folder = mstr_datafolder + "textures/" + self._tag + "/" + self._value

                # Load in the sources to work with
                ptc = glob.glob(root_folder + "/ptc/b" + str(src) + "_p*.png")
                brd_src = Image.open(root_folder + "/brd/b" + str(src) + ".png")
                ptc_src = []
                for p in ptc:
                    ptc_src.append(Image.open(p))

                #mask = Image.open( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + ".png" )
                #lyr_mask = Image.open( mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer.png" )

                for i in mstr_mask_blur:
                    if i[0] == self._tag and i[1] == self._value:
                        if self._tag != "place" and (self._value != "sea" or self._value != "ocean"): 
                            mask = mask.filter(ImageFilter.BoxBlur(radius=i[2]))
                            break
                mask_pix = mask.load()
            
                # Begin producing a largely random image
                samples = 250   # <- We need this in a moment
                for i in range(samples):
                    imgid = 0
                    if len(ptc_src) == 1: imgid = 0
                    if len(ptc_src) >= 2:
                        imgid = randrange(1, len(ptc_src)+1) - 1
                    l = 0 - int(ptc_src[imgid].width / 2)
                    r = adj_image.width - int(ptc_src[imgid].width / 2)
                    t = 0 - int(ptc_src[imgid].height / 2)
                    b = adj_image.height - int(ptc_src[imgid].height / 2)
                    adj_image.alpha_composite( ptc_src[imgid], ( randrange(l, r), randrange(t, b) ) )

                adj_image.alpha_composite( brd_src )

                #lyr_pix = lyr_mask.load()
                for y in range(self._imgsize):
                    for x in range(self._imgsize):
                        if mask_pix[x, y][3] > 0:
                            rgb=adj_pix[x,y]
                            a=mask_pix[x,y]
                            adj_pix[x, y] = ( rgb[0], rgb[1], rgb[2], a[3])

                # Up until here we mimiced the exact same behavior as layergen. However, now
                # we need to adjust the alpha to make this layer fade.
                # Then, we save the image
                if s == 0:
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            fade_a = brd_t_pix[0, y]
                            if mask_pix[x, y][3] > 0:
                                c = adj_pix[x,y]
                                adj_pix[x,y] = (c[0], c[1], c[2], fade_a[3])
                            else:
                                adj_pix[x,y] = (0,0,0,0)
                    #adj_image.save(mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_fade_top.png")
                
                if s == 1:
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            fade_a = brd_r_pix[x, 0]
                            if mask_pix[x, y][3] > 0:
                                c = adj_pix[x,y]
                                adj_pix[x,y] = (c[0], c[1], c[2], fade_a[3])
                            else:
                                adj_pix[x,y] = (0,0,0,0)
                    #adj_image.save(mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_fade_right.png")
                
                if s == 2:
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            fade_a = brd_b_pix[0, y]
                            if mask_pix[x, y][3] > 0:
                                c = adj_pix[x,y]
                                adj_pix[x,y] = (c[0], c[1], c[2], fade_a[3])
                            else:
                                adj_pix[x,y] = (0,0,0,0)
                    #adj_image.save(mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_fade_bottom.png")

                if s == 3:
                    for y in range(self._imgsize):
                        for x in range(self._imgsize):
                            fade_a = brd_l_pix[x, 0]
                            if mask_pix[x, y][3] > 0:
                                c = adj_pix[x,y]
                                adj_pix[x,y] = (c[0], c[1], c[2], fade_a[3])
                            else:
                                adj_pix[x,y] = (0,0,0,0)
                    #adj_image.save(mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_fade_left.png")

        # Return the image
        return adj_image



    def find_all_adjacent_sources(self):
        # Sources for this tag and value - top, right, bottom, left
        sources = [-1,-1,-1,-1] 

        # Perform query for each neighboring tile
        src_top = self._tileinfo.get_adjacency_for_tag_and_value(self._lat_number+1, self._lng_number, self._tag, self._value)
        src_rgt = self._tileinfo.get_adjacency_for_tag_and_value(self._lat_number, self._lng_number+1, self._tag, self._value)
        src_btm = self._tileinfo.get_adjacency_for_tag_and_value(self._lat_number-1, self._lng_number, self._tag, self._value)
        src_lft = self._tileinfo.get_adjacency_for_tag_and_value(self._lat_number, self._lng_number-1, self._tag, self._value)

        if len(src_top) == 2:
            if "b" in src_top[1]: sources[0] = src_top[0]
        if len(src_rgt) == 2:
            if "l" in src_rgt[1]: sources[1] = src_rgt[0]
        if len(src_btm) == 2:
            if "t" in src_btm[1]: sources[2] = src_btm[0]
        if len(src_lft) == 2:
            if "r" in src_lft[1]: sources[3] = src_lft[0]

        # Report our findings
        return sources

    # Find the next "by-ten" numbers for the current latitude and longitude
    def find_earthnavdata_number(self):
        earthnavdata = []
        lat = abs(int(self._latitude / 10) * 10)
        lng = abs(int(self._longitude / 10) * 10)
        earthnavdata.append(lat)
        earthnavdata.append(lng)
        return earthnavdata

    # Construct an X-Plane compatible folder name for latitude and longitude
    def xplane_latlng_folder(self, numbers):
        fstr = ""
        if numbers[0] >= 0: fstr = "+"
        if numbers[0] <  0: fstr = "-"
        if abs(numbers[0]) < 10: fstr = fstr + "0" + str(numbers[0])
        if abs(numbers[0]) >= 10 and numbers[0] <= 90: fstr = fstr + str(numbers[0])

        if numbers[1] >= 0: fstr = fstr + "+"
        if numbers[1] <  0: fstr = fstr + "-"
        if abs(numbers[1]) < 10: fstr = fstr + "00" + str(numbers[1])
        if abs(numbers[1]) >= 10 and numbers[0] <= 99: fstr = fstr + "0" + str(numbers[1])
        if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])

        return fstr