import os from PIL import Image, ImageFilter from defines import * from layergen import * from log import * # ------------------------------------------------------------------- # 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 # ------------------------------------------------------------------- # photogen.py # The class that generates the photo tiles from previous layers, # in their correct order. # ------------------------------------------------------------------- class mstr_photogen: # Initializer doesn't need much def __init__ (self, lat, lng, ty, tx, maxlat, maxlng): self._lat = lat self._lng = lng self._ty = ty self._tx = tx self._maxlatlng = [ maxlat, maxlng ] # Define layer size depending on what is wanted self._imgsize = 0 if mstr_photores == 2048: self._imgsize = 2048 if mstr_photores == 4096: self._imgsize = 6000 # Empty image where everything goes into self._tile = Image.new("RGBA", (self._imgsize, self._imgsize)) self._latlngfld = self.latlng_folder([lat,lng]) mstr_msg("photogen", "Photogen initialized") # This puts it all together. Bonus: AND saves it. def genphoto(self): # Template for the file name which is always the same root_filename = mstr_datafolder + "/_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_" # First, we walk through all layers and blend them on top of each other, in order mstr_msg("photogen", "Merging layers") for l in mstr_ortho_layers: if os.path.isfile(root_filename + l[0] + "-" + l[1] + "_layer.png"): # Need to divert in case we have shadows if mstr_shadow_enabled == True: if os.path.isfile(root_filename + l[0] + "-" + l[1] + "_layer_shadow.png"): sn = root_filename + l[0] + "-" + l[1] + "_layer_shadow.png" s_layer = Image.open(sn) self._tile.alpha_composite(s_layer) # Complete the file name based on the template fn = root_filename + l[0] + "-" + l[1] + "_layer.png" # Open the layer layer = Image.open(fn) # Converge the layer with this image self._tile.alpha_composite(layer) # When we have run through this loop, we will end up with a sandwiched # image of all the other images, in their correct order. # However, since I have discovered that some areas in OSM simply do not # have any tag or information, it is possible that the final image will # have empty, alpha-transparent patches. # For this reason we need to check against these and fix that. # First, we will check if there is something to fix: emptyspace = self.checkForEmptySpace() mstr_msg("photogen", "Checked for empty patches") # If this check comes back as true, we need to perform # aforementioned fix: if emptyspace == True: # Choose a suitable layer type lt = [6,14,17] pick = randrange(0,len(lt)-1) ltp = lt[pick] tag = mstr_ortho_layers[ltp][0] value = mstr_ortho_layers[ltp][1] mstr_msg("photogen", "Patching empty space") self.buildCompletionMask() # Generate the layer as if it were part of the OSM data lg = mstr_layergen(tag, value, self._lat, self._ty, self._lng, self._tx, False, is_completion=True) lg.set_max_latlng_tile(self._maxlatlng) lg.set_latlng_folder(self._latlngfld) #lg.open_db() lg.open_tile_info() lg.genlayer() # Load the image completion = Image.open(root_filename + "tile-completion_layer.png") # Merge the images completion.alpha_composite(self._tile) # Make this the real one self._tile = completion # There may be some tiles that have a larger sea or even an ocean in them - these need to be # removed from the final tile ocean_pix = self._tile.load() for y in range(self._tile.width): for x in range(self._tile.height): p = ocean_pix[x,y] if p[0] == 255 and p[1] == 0 and p[2] == 255: t = (0,0,0,0) ocean_pix[x,y] = t # Now cut out inland water water_layers = ( ["natural", "water"], ["water", "lake"], ["water", "pond"], ["water", "river"] ) for l in water_layers: fn = mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_" + l[0] + "-" + l[1] + "_layer_mask.png" if os.path.isfile(fn) == True: wtr = Image.open(fn) wtr_pix = wtr.load() tilepix = self._tile.load() for y in range(wtr.height): for x in range(wtr.width): wp = wtr_pix[x,y] if wp[0] == 255 and wp[1] == 0 and wp[2] == 255 and wp[3] == 255: tilepix[x,y] = (0,0,0,0) # Alpha correction on final image corrpix = self._tile.load() for y in range(0, self._tile.height): for x in range(0, self._tile.width): c = corrpix[x,y] if c[3] > 0: nc = (c[0], c[1], c[2], 255) corrpix[x,y] = nc if c[3] == 0: corrpix[x,y] = (0,0,0,0) # We are now in posession of the final image. # Scale to correct size. #self._tile = self._tile.resize((mstr_photores, mstr_photores), Image.Resampling.BILINEAR) # This we can save accordingly. self._tile.save(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png") # Now we convert this into a DDS _tmpfn = mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) os.system(mstr_xp_ddstool + " --png2dxt1 " + _tmpfn + ".png " + _tmpfn + ".dds" ) os.remove(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png") # This checks the final image for empty patches. Should one be # found, we will generate something to fill the gap. If this is # the case, we will also note this in the database for the tile, # under the special tag and value "tile", "completion". The same # conditions apply for edge testing and so on. def checkForEmptySpace(self): empty = False # Load photo layer_pix = self._tile.load() # Scan! for y in range(self._tile.width-1): for x in range(self._tile.height-1): p = layer_pix[x,y] if p[3] < 255: # <- Check for empty or non-complete alpha empty = True break # Tell about findings return empty # This returns a mask of the empty space to cover, should there be any def buildCompletionMask(self): mask = Image.new("RGBA", (self._imgsize, self._imgsize), (0,0,0,0)) mask_pix = mask.load() # Load photo layer_pix = self._tile.load() # Scan! for y in range(self._tile.width-1): for x in range(self._tile.height-1): p = layer_pix[x,y] if p[3] < 255: # <- Check for empty or non-complete alpha mask_pix[x,y] = (0,0,0,255) # We do not apply any blur or other effects here - we only want the # exact pixel positions. mask.save( mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_tile-completion.png" ) mstr_msg("photogen", "Generated and saved empty space mask") # Construct a folder name for latitude and longitude def 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