# ------------------------------------------------------------------- # 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 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 # Open DB def open_db(self): self._tiledb = mstr_tiledb(self._latitude, self._longitude, self._latlngfld) self._tiledb.create_tables() # Tile info object def open_tile_info(self): self._tileinfo = mstr_tileinfo(self._latitude, self._longitude, 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: ptc_src.append(Image.open(p)) 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 # This generates the layer from the defined mask def genlayer(self): 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") osmxml = mstr_osmxml(0,0) icao = osmxml.find_icao_codes(mstr_datafolder + "_cache/tile.xml") 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 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("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 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. # 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("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._tileinfo.get_adjacency_for_tag_and_value(adjtiles[0][0], adjtiles[0][1], self._tag, self._value) # Top ar = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[1][0], adjtiles[1][1], self._tag, self._value) # Right ab = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[2][0], adjtiles[2][1], self._tag, self._value) # Bottom al = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[3][0], adjtiles[3][1], self._tag, self._value) # Left #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._tileinfo.get_adjacency_for_completion(adjtiles[0][0], adjtiles[0][1]) # Top ar = self._tileinfo.get_adjacency_for_completion(adjtiles[1][0], adjtiles[1][1]) # Right ab = self._tileinfo.get_adjacency_for_completion(adjtiles[2][0], adjtiles[2][1]) # Bottom al = self._tileinfo.get_adjacency_for_completion(adjtiles[3][0], adjtiles[3][1]) # Left #at = self._tiledb.get_adjacency_for_completion(adjtiles[0][0], adjtiles[0][1]) # Top #ar = self._tiledb.get_adjacency_for_completion(adjtiles[1][0], adjtiles[1][1]) # Right #ab = self._tiledb.get_adjacency_for_completion(adjtiles[2][0], adjtiles[2][1]) # Bottom #al = self._tiledb.get_adjacency_for_completion(adjtiles[3][0], adjtiles[3][1]) # Left if len(at) == 4: self._tag = at[0] self._value = at[1] if len(ar) == 4: self._tag = ar[0] self._value = ar[1] if len(ab) == 4: self._tag = ab[0] self._value = ab[1] if len(al) == 4: self._tag = al[0] self._value = al[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] # We are south to the top tile. if len(at) == 2 and src == -1: if "b" in at[1]: src = at[0] # We are west to the right tile. if len(ar) == 2 and src == -1: if "l" in ar[1]: src = ar[0] # We are north to the bottom tile. if len(ab) == 2 and src == -1: if "t" in ab[1]: src = ab[0] # We are east to the left tile. if len(al) == 2 and src == -1: if "r" in al[1]: src = al[0] # Should we be at the border of the degree for latitude and longitude, we need to perform # additional checks is_deg_brd_t = False is_deg_brd_r = False is_deg_brd_b = False is_deg_brd_l = False if self._lat_number == 1: is_deg_brd_b = True if self._lat_number == self._maxlat: is_deg_brd_t = True if self._lng_number == 1: is_deg_brd_l = True if self._lng_number == self._maxlng: is_deg_brd_r = True # Adjacent latitude and longitude tiles deg_tiles = [] deg_tiles.append( ( self._latitude+1, self._longitude ) ) # Top deg_tiles.append( ( self._latitude, self._longitude+1 ) ) # Right deg_tiles.append( ( self._latitude-1, self._longitude ) ) # Bottom deg_tiles.append( ( self._latitude, self._longitude-1 ) ) # Left # Perform degree border checks # - and make sure we do not run into errors - this drove me crazy in testing atd = [] ard = [] abd = [] ald = [] if is_deg_brd_t == True: if self._is_completion == False: atd = self._tileinfo.get_adjacency_for_tag_and_value_in_lat_lng(deg_tiles[0][0], deg_tiles[0][1], self._tag, self._value, 1, self._lng_number, "b") # Top #atd = self._tiledb.get_adjacency_for_source_in_lat_lng(deg_tiles[0][0], deg_tiles[0][1], 1, self._lng_number, self._tag, self._value) # Top if self._is_completion == True: atd = self._tileinfo.get_completion_in_lat_lng(deg_tiles[0][0], deg_tiles[0][1], 1, self._lng_number, "b") #atd = self._tiledb.get_adjacency_for_completion_in_lat_lng(deg_tiles[0][0], deg_tiles[0][1], 1, self._lng_number) # Top if is_deg_brd_r == True: if self._is_completion == False: ard = self._tileinfo.get_adjacency_for_tag_and_value_in_lat_lng(deg_tiles[1][0], deg_tiles[1][1], self._tag, self._value, self._lat_number, 1, "l") # Right #ard = self._tiledb.get_adjacency_for_source_in_lat_lng(deg_tiles[1][0], deg_tiles[1][1], self._lat_number, 1, self._tag, self._value) # Right if self._is_completion == True: ard = self._tileinfo.get_completion_in_lat_lng(deg_tiles[1][0], deg_tiles[1][1], self._lat_number, 1, "l") #ard = self._tiledb.get_adjacency_for_completion_in_lat_lng(deg_tiles[1][0], deg_tiles[1][1], self._lat_number, 1) # Right if is_deg_brd_b == True: #maxlatlng = self._tiledb.get_highest_latlong_from_tile(self._latitude-1, self._longitude) if self._is_completion == False: abd = self._tileinfo.get_adjacency_for_tag_and_value_in_lat_lng(deg_tiles[2][0], deg_tiles[2][1], self._tag, self._value, self._lat_number, self._lng_number, "t") #abd = self._tiledb.get_adjacency_for_source_in_lat_lng(deg_tiles[2][0], deg_tiles[2][1], maxlatlng[0], self._lng_number, self._tag, self._value) # Bottom if self._is_completion == True: abd = self._tileinfo.get_completion_in_lat_lng(deg_tiles[2][0], deg_tiles[2][1], self._lat_number, self._lng_number, "t") #abd = self._tiledb.get_adjacency_for_completion_in_lat_lng(deg_tiles[2][0], deg_tiles[2][1], maxlatlng[0], self._lng_number) # Bottom if is_deg_brd_l == True: #maxlatlng = self._tiledb.get_highest_latlong_from_tile(self._latitude, self._longitude-1) if self._is_completion == False: ald = self._tileinfo.get_adjacency_for_tag_and_value_in_lat_lng(deg_tiles[2][0], deg_tiles[2][1], self._tag, self._value, self._lat_number, self._lng_number, "r") #ald = self._tiledb.get_adjacency_for_source_in_lat_lng(deg_tiles[2][0], deg_tiles[2][1], self._lat_number, maxlatlng[1], self._tag, self._value) # Left if self._is_completion == True: ald = self._tileinfo.get_completion_in_lat_lng(deg_tiles[2][0], deg_tiles[2][1], self._lat_number, self._lng_number, "r") #ald = self._tiledb.get_adjacency_for_completion_in_lat_lng(deg_tiles[2][0], deg_tiles[2][1], self._lat_number, maxlatlng[1]) # Left if (is_deg_brd_t == True or is_deg_brd_r == True or is_deg_brd_b == True or is_deg_brd_l == True): if src == -1 and self._is_completion == True: if len(atd) == 4: self._tag = atd[0] self._value = atd[1] if len(ard) == 4: self._tag = ard[0] self._value = ard[1] if len(abd) == 4: self._tag = abd[0] self._value = abd[1] if len(ald) == 4: self._tag = ald[0] self._value = ald[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] # Should we get here and one of the degree border checks turns out true, # we need to make sure that we select the source we found. This should # enable seamless tiling... around the entire planet if is_deg_brd_t == True and len(at) == 0 and src == -1: if len(atd) == 2: if "b" in atd[3]: src = int(atd[2]) if is_deg_brd_r == True and len(ar) == 0 and src == -1: if len(ard) == 2: if "l" in ard[3]: src = int(ard[2]) if is_deg_brd_b == True and len(ab) == 0 and src == -1: if len(abd) == 2: if "t" in abd[3]: src = int(abd[2]) if is_deg_brd_l == True and len(al) == 0 and src == -1: if len(ald) == 2: if "r" in ald[3]: src = int(ald[2]) mstr_msg("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)+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: ptc_src.append(Image.open(p)) mstr_msg("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("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(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 < osm_mask.width and y > 0 and y < osm_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": 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: if self._tag != "place" and (self._value != "sea" or self._value != "ocean"): 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) - 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") # 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,5) for i in range(1, amt+1): ptc = randrange(1, 14) 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.height ) layer.alpha_composite( img, (lx, ly) ) # 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_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("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() # 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 = osm_mask.load() for y in range(self._imgsize-1): for x in range(self._imgsize-1): m = mask_pix[x,y] shf_x = 0 # Buildings get slightly closer shadows if self._tag == "building": shf_x = x + int(mstr_shadow_shift/2) if self._tag != "building": shf_x = x + mstr_shadow_shift if shf_x <= self._imgsize-1: a = mask_pix[x,y][3] st = 0 if self._tag == "building": st = random.uniform(0.25, mstr_shadow_strength/2) if self._tag != "building": st = random.uniform(0.45, mstr_shadow_strength) ca = a * st aa = int(ca) shadow_pix[shf_x, y] = (0,0,0,aa) shadow.save(mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value + "_layer_shadow.png") mstr_msg("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: r = self._tileinfo.get_adjacency_for_tag_and_value(self._lat_number, self._lng_number, self._tag, self._value) #r = self._tiledb.get_adjacency_for_source(self._lat_number, self._lng_number, self._tag, self._value) if len(r) == 0: self._tileinfo.add_adjacency_data(self._lat_number, self._lng_number, self._tag, self._value, src, adjstr) mstr_msg("layergen", "Adjacency info stored in database") if self._is_completion == True: r = self._tileinfo.get_adjacency_for_completion(self._lat_number, self._lng_number) #r = self._tiledb.get_adjacency_for_completion(self._lat_number, self._lng_number) if len(r) == 0: self._tileinfo.add_completion_data(self._lat_number, self._lng_number, self._tag, self._value, src, adjstr) mstr_msg("layergen", "Adjacency info for completion stored in database") #self._tiledb.commit_query() #self._tiledb.close_db() # 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"): 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") # --------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------- # 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 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("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() # Let's define some base color ranges for different types of buildings bld_clr = [ ("detached", 190, 192, 195), ("church", 134, 134, 136), ("hotel", 153, 147, 138), ("farm", 145, 124, 121), ("semidetached_house", 167, 163, 152), ("apartments", 129, 134, 127), ("civic", 134, 134, 136), ("garage", 101, 109, 111), ("office", 139, 152, 156), ("retail", 121, 122, 108), ("industrial", 191, 192, 187), ("house", 145, 124, 121), ("terrace", 191, 192, 187), ("hangar", 137, 162, 195), ("school", 111, 117, 115), ("yes", 152, 144, 141) ] # Find the color index to work with cidx = 0 if self._tag == "building": for c in bld_clr: if c[0] == self._value: break cidx = cidx+1 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) # 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) t = a[3]-d if t < 0: t = 0 layer_comp_pix[x, y] = ( mats[pick-1][0], mats[pick-1][1], mats[pick-1][2], t ) # A bit special here if self._tag == "building": # Find a color range d = randrange(1,21) # Bring in some variety by making the one or other pixel darker dp = randrange(1, 3) if dp == 2: d = d + 20 # Adjust this pixel c = (bld_clr[cidx][1]-d, bld_clr[cidx][2]-d, bld_clr[cidx][3]-d, 255) # Set pixel layer_comp_pix[x, y] = c 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] ) # We will do some super magic here to let houses look more realistic if self._tag == "building" or self._value == "cemetery": vls = [ "detached", "hotel", "farm", "semidetached_house", "apartments", "civic", "office", "retail", "industrial", "house", "school", "yes" ] if self._value in vls: # Generate a new image details = Image.new("RGBA", (self._imgsize, self._imgsize)) details_pix = details.load() layer_pix = layer_comp.load() for y in range(self._imgsize-1): for x in range(self._imgsize-1): p = layer_pix[x,y] if p[3] > 0: shf_x = x+randrange(1, 21) shf_y = y+randrange(1, 21) shf_x2 = x-randrange(1, 21) shf_y2 = y-randrange(1, 21) if shf_x <= self._imgsize-1 and shf_x >= 0 and shf_y <= self._imgsize-1 and shf_y >= 0: 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) # Merge the details BELOW the houses details.alpha_composite(layer_comp) layer_comp = details # New edge osm_edge = osm_mask.filter(ImageFilter.FIND_EDGES) osm_edge = osm_edge.filter(ImageFilter.MaxFilter) osm_edge = osm_edge.filter(ImageFilter.GaussianBlur(radius=2)) # Blur the image layer_comp = layer_comp.filter(ImageFilter.GaussianBlur(radius=1)) osm_edge.alpha_composite(layer_comp) layer_comp = osm_edge # Add some random trees div = int(self._imgsize/200) trees = Image.new("RGBA", (self._imgsize, self._imgsize)) 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 > 0 and shf_x < self._imgsize and shf_y > 0 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, 11)) 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)) trees.alpha_composite(layer_comp) layer_comp = trees mstr_msg("layergen", "Layer image generated") # Building shadow if mstr_shadow_enabled == True: if self._tag == "building": mstr_msg("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 + randrange(1, mstr_shadow_shift + 1) shf_x2 = x + randrange(1, mstr_shadow_shift + 1) if shf_x <= self._imgsize-1 and shf_x >= 0 and shf_x2 <= self._imgsize-1 and shf_x2 >= 0: a = mask_pix[x,y][3] st = random.uniform(0.6, mstr_shadow_strength) ca = a * st aa = int(ca) shadow_pix[shf_x, y] = (0,0,0,aa) shadow_pix[shf_x2, 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("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 == "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 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("layergen", "Street lines added") if self._tag == "waterway" and (self._value == "river" or self._value == "stream"): layer_comp = layer_comp.filter(ImageFilter.GaussianBlur(radius=4)) # 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()