# ------------------------------------------------------------------- # 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") water_file = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._lat_number) + "_" + str(self._lng_number) + "_water.png" inl_mask = None if os.path.isfile(water_file): inl_mask = Image.open(water_file) else: inl_mask = Image.new("RGBA", (self._imgsize, self._imgsize), (255,255,255,255)) 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] > 50: clr = 255-l[3] c = (clr,clr,clr,255) inl_pix[x,y] = c inl_mask.save(water_file) #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