# ------------------------------------------------------------------- # 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 import time from random import randrange import random import PIL.ImageOps from PIL import Image, ImageFilter, ImageDraw, ImageOps, ImageFile from defines import * from log import * from tileinfo import * from osmxml import * from functions import * from resourcegen import * ImageFile.LOAD_TRUNCATED_IMAGES = True 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 = [] # 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[0] == self._tag and linedata[1] == self._value: src = linedata[2].split(",") break # Should we encounter a 0 length at this point, we can choose something # It means it touches no border as it was not found in the file if len(src) == 0: while len(src) < 6: pick = randrange(1, 16) if pick not in src: src.append(pick) return src # Find layer contrast to apply, if any def findLayerContrast(self, res): contrast = 0 # The already existing source data srcfile = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/ctrdata" # Let's open the file and find our entry with open(srcfile) as file: for line in file: linedata = line.split(" ") if len(linedata) > 1: # Make sure we don't break at last line of data file if linedata[0] == self._tag and linedata[1] == self._value and linedata[2] == res: contrast = int(linedata[3]) break return contrast # Generates some random tree. # We will now move away from using pre-made trees... # they didn't look so great def generate_tree(self): sx = randrange(18, 31) sy = randrange(18, 31) treepoly = Image.new("RGBA", (sx, sy)) draw = ImageDraw.Draw(treepoly) draw.ellipse((4, 4, sx - 4, sy - 4), fill="black") tree = Image.new("RGBA", (sx, sy)) treepx = tree.load() maskpx = treepoly.load() # How many tree points do we want? treepts = 75 # How many of those have been drawn? ptsdrawn = 0 bc = [ (36, 50, 52), (30, 41, 39), (32, 45, 37), (32, 39, 49), (33, 34, 40), (44, 50, 53), (40, 46, 48), (14, 31, 38), (17, 41, 33), (39, 56, 35), (51, 51, 42), (12, 27, 31), (45, 59, 48), (37, 54, 29), (59, 50, 34), (59, 59, 35), (59, 51, 35), (70, 72, 45), (48, 59, 44), (29, 47, 23), (47, 61, 43), (29, 68, 15), (53, 77, 63), (20, 68, 40) ] bcp = randrange(0, len(bc)) treedraw = ImageDraw.Draw(tree) while ptsdrawn < treepts + 1: rx = randrange(0, sx) ry = randrange(0, sy) mp = maskpx[rx, ry] if mp[3] > 0: d = randrange(0, 51) r = bc[bcp][0] g = bc[bcp][1] + d b = bc[bcp][2] + (d // 2) a = randrange(170, 256) c = (r, g, b, a) ex = randrange(2, 5) ey = randrange(2, 5) treedraw.ellipse((rx - (ex // 2), ry - (ey // 2), rx + (ey // 2), ry + (ey // 2)), fill=c) ptsdrawn = ptsdrawn + 1 for y in range(0, tree.height): for x in range(0, tree.width): tp = treepx[x, y] diff = randrange(0, 31) nc = (tp[0] - diff, tp[1] - diff, tp[2] - diff, tp[3]) treepx[x, y] = nc tree = tree.filter(ImageFilter.GaussianBlur(radius=0.5)) for y in range(0, tree.height): for x in range(0, tree.width): tp = treepx[x, y] diff = randrange(0, 51) nc = (tp[0] - diff, tp[1] - diff, tp[2] - diff, tp[3]) treepx[x, y] = nc return tree # 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: 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 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() srcstr = "" for s in range(len(src)): srcstr = srcstr + str(src[s]) if s < len(src)-1: srcstr = srcstr + "," # Failsafe if srcstr == "0": srcstr = "" numbers = list(range(1, 16)) src = random.sample(numbers, 5) for s in range(len(src)): srcstr = srcstr + str(src[s]) if s < len(src)-1: srcstr = srcstr + "," # Patch and border sources. There can only be one for each. brd_src = None ptc_src = [] # Find this layer's predetermined contrast lyr_contrast = self.findLayerContrast(srcstr) if lyr_contrast != 0: mstr_msg("layergen", "Applying contrast value: " + str(lyr_contrast)) # Should this not exist yet, we need to create it #if os.path.isfile(gensrc_ptc) == False: rg = mstr_resourcegen(self._tag, self._value, src) rg.setLayerContrast(int(lyr_contrast)) lyr_res = rg.gensource() # Open the images ptc_src.append(lyr_res[0]) # Used to be an array, so let's keep it brd_src = lyr_res[1] 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"): amt = randrange(5, 21) masks = glob.glob(mstr_datafolder + "textures/tile/completion/*.png") patchtags = [ ["landuse", "meadow"], ["landuse", "grass"], ["natural", "heath"], ["natural", "scrub"] ] for i in range(1, amt + 1): pick = randrange(0, len(masks)) patchmask = Image.open(masks[pick]) patchmask = patchmask.rotate(randrange(0, 360), expand=True) # Make sure patch is within bounds if patchmask.width > self._imgsize or patchmask.height > self._imgsize: patchmask = patchmask.resize((mstr_photores, mstr_photores), Image.Resampling.BILINEAR) patchpix = patchmask.load() # Pick from possible tags and values for the patches numbers = list(range(1, 16)) src = random.sample(numbers, 5) patchpick = randrange(0, len(patchtags)) ctr = randrange(1, 4) rg = mstr_resourcegen(patchtags[patchpick][0], patchtags[patchpick][1], src) rg.setLayerContrast(ctr) ptch = rg.gensource() # Generate a full size of the source ptc_full = Image.new("RGBA", (mstr_photores, mstr_photores)) # Generate the source image for p in range(1, 201): rx = randrange(0 - int(ptch[0].width / 2), ptc_full.width - int(ptch[0].width / 2)) ry = randrange(0 - int(ptch[0].height / 2), ptc_full.height - int(ptch[0].height / 2)) ptc_full.alpha_composite(ptch[0], dest=(rx, ry)) rg_img = ptc_full rg_pix = rg_img.load() # The patch to be used in the layer layerpatch = Image.new("RGBA", (patchmask.width, patchmask.height)) lp_pix = layerpatch.load() for y in range(0, patchmask.height): for x in range(0, patchmask.width): ptc_msk = patchpix[x,y] if ptc_msk[3] > 0: oc = rg_pix[x,y] nc = ( oc[0], oc[1], oc[2], ptc_msk[3] ) lp_pix[x,y] = nc #layerpatch = layerpatch.rotate(randrange(0, 360), expand=True) lx = randrange(self._imgsize - layerpatch.width) ly = randrange(self._imgsize - layerpatch.height) layer.alpha_composite(layerpatch, (lx, ly)) # 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]) # Add a white-ish border around pitches if self._tag == "leisure" and self._value == "pitch": pitch_edge = osm_edge pitch_edge = pitch_edge.filter(ImageFilter.GaussianBlur(radius=0.5)) pitch_mask = pitch_edge.load() # ImageOps.invert does not like RGBA images for inversion. So I need to do it. for y in range(0, self._imgsize): for x in range(0, self._imgsize): pm = pitch_mask[x,y] if pm[3] > 0: d = randrange(0, 21) layer_comp_pix[x,y] = ( 110-pm[0]-d, 110-pm[1]-d, 110-pm[2]-d, pm[3]-(d*2) ) # Layer complete mstr_msg("layergen", "Layer image completed.") # 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") # 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 == "aeroway" and self._value == "taxiway": # Almost the same as above 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(0, 31) dr = 80+d dg = 80+d db = 85+d da = 255 layer_comp_pix[x, y] = ( dr,dg,db,da ) if self._tag == "highway" and self._value == "motorway": d = randrange(0, 46) dr = 47+d dg = 58+d db = 60+d layer_comp_pix[x, y] = ( dr,dg,db,255 ) if self._tag == "highway" and (self._value == "footway" or self._value == "track" or self._value == "path"): dr = randrange(158, 183) dg = randrange(143, 178) db = randrange(90, 161) da = a[3]-20 layer_comp_pix[x, y] = (dr, dg, db, da) 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 different for tree rows if self._tag == "natural" and self._value == "tree_row": trees = Image.new("RGBA", (self._imgsize, self._imgsize)) treespx = trees.load() for t in range(80001): lx = randrange(self._imgsize) ly = randrange(self._imgsize) a = mask_pix[lx,ly] # Just mark the hit with a black pixel. # This will be used as "target" by photogen if a[3] > 0: if lx < self._imgsize and ly < self._imgsize: c = (0,0,0,1) treespx[lx,ly] = c layer_comp.alpha_composite(trees) mstr_msg("layergen", "Layer image generated") # 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.") # Return image return layer_comp # ------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------ # As we now have a specific pool for buildings, we will need to enter a third branch # to handle all kinds of buildings in the orthos. if self._tag == "building": # Access to pixels from OSM mask mask_pix = mask.load() # Separate images for additional details tree_shadow = Image.new("RGBA", (self._imgsize, self._imgsize)) trees = Image.new("RGBA", (self._imgsize, self._imgsize)) shadow = Image.new("RGBA", (self._imgsize, self._imgsize)) bld_src = Image.new("RGBA", (self._imgsize, self._imgsize)) bld_main = Image.new("RGBA", (self._imgsize, self._imgsize)) osm_edge = mask.filter(ImageFilter.FIND_EDGES) # Find a source image to use srclist = glob.glob(mstr_datafolder + "textures/" + self._tag + "/" + self._value + "/*.png") srcnum = randrange(1, len(srclist)+1) # Patch and border sources. There can only be one for each. brd_src = None numbers = list(range(1, len(srclist))) res = random.sample(numbers, 4) ptc_src = [] for p in range(0, len(res)): ptc_src.append(Image.open(mstr_datafolder + "textures/" + self._tag + "/" + self._value + "/" + str(res[p]) + ".png")) # Set some contrast lyr_contrast = 0.85 # Open the images #ptc_src.append( ptc_img ) # Used to be an array, so let's keep it #brd_src = bldg_src[1] # 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(0, len(ptc_src)) i = ptc_src[imgid].rotate(randrange(0, 360), expand=True) l = 0 - int(i.width / 2) r = layer.width - int(i.width / 2) t = 0 - int(i.height / 2) b = layer.height - int(i.height / 2) bld_src.alpha_composite(i, (randrange(l, r), randrange(t, b))) mstr_msg("layergen", "Layer image generated") bld_src_pix = bld_src.load() bld_main_pix = bld_main.load() for y in range(0, self._imgsize): for x in range(0, self._imgsize): mp = mask_pix[x,y] bp = bld_src_pix[x,y] if mp[3] > 0: nc = ( bp[0], bp[1], bp[2], mp[3] ) bld_main_pix[x,y] = nc bld_main = bld_main.filter(ImageFilter.GaussianBlur(radius=0.5)) osm_edge = osm_edge.filter(ImageFilter.GaussianBlur(radius=1.5)) bld_edge = Image.new("RGBA", (self._imgsize, self._imgsize)) bld_edge_pix = bld_edge.load() edge_pix = osm_edge.load() for y in range(osm_edge.height): for x in range(osm_edge.width): ep = edge_pix[x,y] bp = bld_src_pix[x,y] if ep[3] == 255 and bp[3] == 255: nc = (0,0,0,60) bld_edge_pix[x,y] = nc bld_main.alpha_composite(bld_edge) 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) # Add some random trees for t in range(0, 5000): loc_x = randrange(0, self._imgsize) loc_y = randrange(0, self._imgsize) shf_val = 21 shf_x = randrange(loc_x - shf_val, loc_x + shf_val) shf_y = randrange(loc_y - shf_val, loc_y + shf_val) mp = mask_pix[loc_x, loc_y] if mp[3] == 255: shf_att = 0 shf_ok = False shf_x = randrange(loc_x - shf_val, loc_x + shf_val) shf_y = randrange(loc_y - shf_val, loc_y + shf_val) while shf_att < 51: if shf_x > 0 and shf_x < self._imgsize and shf_y > 0 and shf_y < self._imgsize: sp = mask_pix[shf_x, shf_y] if sp[3] == 0: shf_ok = True break else: shf_att = shf_att + 1 else: shf_val = shf_val + 10 shf_att = shf_att + 1 if shf_ok == True: tree = self.generate_tree() trees.alpha_composite(tree, (shf_x, shf_y)) # Perform correction for tree rendering tree_pix = trees.load() for y in range (0, self._imgsize): for x in range(0, self._imgsize): mp = mask_pix[x,y] tp = tree_pix[x,y] if mp[3] == 255 and tp[3] == 255: tree_pix[x,y] = (0,0,0,0) 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)) 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) #bld_comp = bld_comp.filter(ImageFilter.GaussianBlur(radius=1.1)) bld_main = ImageEnhance.Contrast(bld_main).enhance(lyr_contrast) bld_comp.alpha_composite(bld_main) return bld_comp # 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