# -------------------------------------------------------------------
# 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, ImagePath
from defines import *
from log import *
from tiledb import *
from osmxml import *
from functions 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._tiledb = mstr_tiledb(lat, lng)
        self._tiledb.create_tables()
        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 = 3000
        if mstr_photores == 4096: self._imgsize = 6000
        #mstr_msg("mstr_layergen", "Layer gen initialized")
    

    # 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))
        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("mstr_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
            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("mstr_layergen", "Border image generated")

        # We now need to add the seamless border
        layer.alpha_composite( brd_src )
        mstr_msg("mstr_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


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

        mstr_msg("mstr_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("mstr_layergen", "Checking for airport/s with ICAO code")
        osmxml = mstr_osmxml(0,0)
        icao = osmxml.find_icao_codes(mstr_datafolder + "_cache\\tile.xml")
        mstr_msg("mstr_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 len(icao) >= 1:
            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("mstr_layergen", "Airport/s noted in data file")
            rw_surface = osmxml.find_runway_surface(mstr_datafolder + "_cache\\tile.xml")

        # 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) 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.
            # First, we need to check for adjacent tile information. We then either
            # need to use the source of any adjacent tile, or we can choose freely.
            src = -1
            
            # Find our adjacent tiles
            adjtiles = findAdjacentTilesTo(self._lat_number, self._lng_number)

            mstr_msg("mstr_layergen", "Performing adjacency check")
            # Walk through each tile and see what we can find in relation to this
            # tile in the center
            # Since we already know the order in adjtiles, we can do this real easy
            if self._is_completion == False:
                at = self._tiledb.get_adjacency_for_source(adjtiles[0][0], adjtiles[0][1], self._tag, self._value) # Top
                ar = self._tiledb.get_adjacency_for_source(adjtiles[1][0], adjtiles[1][1], self._tag, self._value) # Right
                ab = self._tiledb.get_adjacency_for_source(adjtiles[2][0], adjtiles[2][1], self._tag, self._value) # Bottom
                al = self._tiledb.get_adjacency_for_source(adjtiles[3][0], adjtiles[3][1], self._tag, self._value) # Left
            if self._is_completion == True:
                at = self._tiledb.get_adjacency_for_completion(adjtiles[0][0], adjtiles[0][1], self._tag, self._value) # Top
                ar = self._tiledb.get_adjacency_for_completion(adjtiles[1][0], adjtiles[1][1], self._tag, self._value) # Right
                ab = self._tiledb.get_adjacency_for_completion(adjtiles[2][0], adjtiles[2][1], self._tag, self._value) # Bottom
                al = self._tiledb.get_adjacency_for_completion(adjtiles[3][0], adjtiles[3][1], self._tag, self._value) # Left

            # We are south to the top tile.
            if len(at) == 1 and src == -1:
                if "b" in at[0][5]: src = int(at[0][4])
            # We are west to the right tile.
            if len(ar) == 1 and src == -1:
                if "l" in ar[0][5]: src = int(ar[0][4])
            # We are north to the bottom tile.
            if len(ab) == 1 and src == -1:
                if "t" in ab[0][5]: src = int(ab[0][4])
            # We are east to the left tile.
            if len(al) == 1 and src == -1:
                if "r" in al[0][5]: src = int(al[0][4])
            
            mstr_msg("mstr_layergen", "Adjacency check completed")

            brd = glob.glob(root_folder + "\\brd\\b*.png")

            # If the adjacency check returned nothing (src is still -1),
            # then pick something
            if src == -1:
                if len(brd) == 1: src=1
                if len(brd) >= 2:
                    src = randrange(1, len(brd))
            
            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("mstr_layergen", "Layer sources selected")

            # OK! Load the mask
            if self._is_completion == False:
                osm_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" )
            if self._is_completion == True:
                osm_mask = Image.open( mstr_datafolder + "_cache\\" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_tile-completion.png" )
            
            # Generate an edge mask from the original
            osm_edge = osm_mask.filter(ImageFilter.FIND_EDGES)
            osm_edge = osm_edge.filter(ImageFilter.MaxFilter)
            mstr_msg("mstr_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(osm_mask)

                # Walk through a grid of 200x200 - on the edge image
                for y in range(0, osm_mask.height, int(osm_mask.height/200)):
                    for x in range(0, osm_mask.width, int(osm_mask.width/200)):
                        px = epx[x,y]
                        if px[3] == 255:
                            rx = randrange(30,60)
                            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))


            # We need to change the image in certain conditions
            if self._value == "hedge" and self._tag == "barrier":
                osm_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:
                    osm_mask = osm_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
                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("mstr_layergen", "Layer image generated")


            # 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"):
                amt = randrange(1,3)
                for i in range(1, amt):
                    ptc = randrange(1, 7)
                    img = Image.open(mstr_datafolder + "Textures\\tile\\completion\\p" + str(ptc)+".png")
                    lx = randrange( int(layer.width/20), layer.width - (int(layer.width/20)) - img.width ) 
                    ly = randrange( int(layer.width/20), layer.width - (int(layer.width/20)) - img.width )
                    layer.alpha_composite( img, (lx, ly) ) 


            # We now need to add the seamless border
            layer.alpha_composite( brd_src )
            mstr_msg("mstr_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 = osm_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]
                        if self._value == "residential":
                            layer_comp_pix[x, y] = ( rgb[0], rgb[1], rgb[2], int(a[3]/2))
                        if self._value != "residential":
                            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)


            # 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("mstr_layergen", "Layer image finalized and saved.")


            # 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")



            # Check if pixels touch the borders of the image, and if so -
            # make a not of that in the database.
            at=False
            ar=False
            ab=False
            al=False
            layer_pix = layer_comp.load() # <- Just to be safe
            
            # Top scan
            for i in range(0, self._imgsize-1):
                p = layer_pix[i,0]
                if p[3] > 0:
                    at=True
                    break
            
            # Right scan
            for i in range(0, self._imgsize-1):
                p = layer_pix[self._imgsize-1,i]
                if p[3] > 0:
                    ar=True
                    break
            
            # Bottom scan
            for i in range(0, self._imgsize-1):
                p = layer_pix[i,self._imgsize-1]
                if p[3] > 0:
                    ab=True
                    break
            
            # Left scan
            for i in range(0, self._imgsize-1):
                p = layer_pix[1,i]
                if p[3] > 0:
                    al=True
                    break
            
            # Construct DB String
            adjstr = ""
            if at==True: adjstr = adjstr + "t"
            if ar==True: adjstr = adjstr + "r"
            if ab==True: adjstr = adjstr + "b"
            if al==True: adjstr = adjstr + "l"

            # Store into DB - but only if there is something to store
            if adjstr != "":
                if self._is_completion == False:
                    self._tiledb.insert_info(self._lat_number, self._lng_number, self._tag, self._value, src, adjstr)
                if self._is_completion == True:
                    self._tiledb.insert_completion_info(self._lat_number, self._lng_number, self._tag, self._value, src, adjstr)
                self._tiledb.commit_query()
                self._tiledb.close_db()
                mstr_msg("mstr_layergen", "Adjacency info stored in database")
        

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

        if self._isline == True:

            # We will need the mask in question
            osm_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 = osm_mask.filter(ImageFilter.FIND_EDGES)
            osm_edge = osm_edge.filter(ImageFilter.MaxFilter)
            mstr_msg("mstr_layergen", "Edge mask generated")

            # As above, we will apply the blur as noted in the defines
            for i in mstr_mask_blur:
                if i[0] == self._tag and i[1] == self._value:
                    osm_mask = osm_mask.filter(ImageFilter.BoxBlur(radius=i[2]))
                    break
            osm_edge = osm_edge.filter(ImageFilter.BoxBlur(radius=1))
            
            
            # 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 = osm_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":
                            d = randrange(140,160)
                            layer_comp_pix[x, y] = ( d,d,d,a[3] )
                        if self._tag == "highway" and self._value == "motorway":
                            d = randrange(1,20)
                            r = 86-d
                            g = 97-d
                            b = 106-d
                            layer_comp_pix[x, y] = ( r,g,b,a[3] )
                        if self._tag == "waterway" and (self._value == "stream" or self._value == "river"):
                            d = randrange(1, 15)
                            layer_comp_pix[x, y] = ( 129-d, 148-d, 159-d, a[3] )
                        if self._tag == "building":
                            r = randrange(1, 20)
                            
                            if self._value == "yes":
                                d = (116-r, 117-r,135-r)
                                layer_comp_pix[x, y] = ( d[0], d[1], d[2], a[3] )
                                if e[3] > 0:
                                    b = (96-r, 97-r, 115-r)
                                    layer_comp_pix[x, y] = ( b[0],b[1],b[2],e[3] )

                            if self._value == "office" or self._value == "retail":
                                d = (100-r, 100-r, 100-r)
                                layer_comp_pix[x, y] = ( d[0], d[1], d[2], a[3] )
                                if e[3] > 0:
                                    b = (80-r, 80-r, 80-r)
                                    layer_comp_pix[x, y] = ( b[0],b[1],b[2],e[3] )

                            if self._value == "industrial":
                                d = (166-r, 170-r, 175-r)
                                layer_comp_pix[x, y] = ( d[0], d[1], d[2], a[3] )
                                if e[3] > 0:
                                    b = (146-r, 150-r, 155-r)
                                    layer_comp_pix[x, y] = ( b[0],b[1],b[2],e[3] )
                            
                        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] )
            mstr_msg("mstr_layergen", "Layer image generated")

            # Building shadow
            if mstr_shadow_enabled == True:
                if self._tag == "building":
                    mstr_msg("mstr_layergen", "Generating shadow for layer")
                    shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
                    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")
            
            # 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 == "unclassified") or (self._tag == "aeroway" and self._value == "runway"):
                # We will now add some white lines for coolness
                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(185, 215)
                            a=mask_pix[x,y]
                            layer_comp_pix[x, y] = ( w,w,w,a[3] )

                mstr_msg("mstr_layergen", "Street lines added")

            # 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("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")
'''