orthographic/photogen.py

259 lines
11 KiB
Python

import os
from PIL import Image, ImageFilter, ImageEnhance
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")
# Shadow merging
bldg_shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
if mstr_shadow_enabled == True:
for l in mstr_ortho_layers:
if l[0] == "building":
if os.path.isfile(root_filename + l[0] + "-" + l[1] + "_layer_shadow.png"):
shd = Image.open(root_filename + l[0] + "-" + l[1] + "_layer_shadow.png")
bldg_shadow.alpha_composite(shd)
# Details merging
bldg_details = Image.new("RGBA", (self._imgsize, self._imgsize))
for l in mstr_ortho_layers:
if l[0] == "building":
if os.path.isfile(root_filename + l[0] + "-" + l[1] + "_layer_details.png"):
dtl = Image.open(root_filename + l[0] + "-" + l[1] + "_layer_details.png")
dtl = dtl.filter(ImageFilter.GaussianBlur(radius=1))
bldg_details.alpha_composite(dtl)
# Building merging
bldg_main = Image.new("RGBA", (self._imgsize, self._imgsize))
for l in mstr_ortho_layers:
if l[0] == "building":
if os.path.isfile(root_filename + l[0] + "-" + l[1] + "_layer.png"):
bld = Image.open(root_filename + l[0] + "-" + l[1] + "_layer.png")
bld = bld.filter(ImageFilter.GaussianBlur(radius=0.35))
bldg_main.alpha_composite(bld)
# Merge the building layers
bldg_final = Image.new("RGBA", (self._imgsize, self._imgsize))
bldg_final.alpha_composite(bldg_details)
bldg_final.alpha_composite(bldg_shadow)
bldg_final.alpha_composite(bldg_main)
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"):
if l[0] != "building":
sn = root_filename + l[0] + "-" + l[1] + "_layer_shadow.png"
s_layer = Image.open(sn)
self._tile.alpha_composite(s_layer)
if l[0] != "building":
# Complete the file name based on the template
fn = root_filename + l[0] + "-" + l[1] + "_layer.png"
# Open the layer
layer = Image.open(fn)
if l[0] == "leisure" and l[1] == "pitch":
layer = layer.filter(ImageFilter.GaussianBlur(radius=0.2))
# Converge the layer with this image
self._tile.alpha_composite(layer)
# Drop the buildings on top
self._tile.alpha_composite(bldg_final)
# 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
tag = "landuse"
value = "meadow"
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"],
["leisure", "swimming_pool"]
)
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)
# Contrast
self._tile = ImageEnhance.Contrast(self._tile).enhance(1)
# 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