orthographic/layergen.py

1106 lines
60 KiB
Python

# -------------------------------------------------------------------
# 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, ImageEnhance
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._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:
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")
# 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 = osm_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.5))
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"):
amt = 3500
for i in range(1, amt+1):
p = randrange(1, 11)
tree = Image.open(mstr_datafolder + "textures/building/area/p" + str(p) + ".png")
lx = randrange( self._imgsize - tree.width )
ly = randrange( self._imgsize - tree.height )
layer.alpha_composite(tree, (lx, ly))
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]
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")
self.generate_adjacent_fades()
# Determine if there are any fades, and if so, fade those in first before we save the layer
fade_fn = mstr_datafolder + "_cache/" + str(self._latitude) + "-" + str(self._lat_number) + "_" + str(self._longitude) + "-" + str(self._lng_number) + "_" + self._tag + "-" + self._value
if os.path.isfile(fade_fn + "_fade_top.png") == True:
fade = Image.open(fade_fn + "_fade_top.png")
layer_comp.alpha_composite(fade)
if os.path.isfile(fade_fn + "_fade_right.png") == True:
fade = Image.open(fade_fn + "_fade_right.png")
layer_comp.alpha_composite(fade)
if os.path.isfile(fade_fn + "_fade_bottom.png") == True:
fade = Image.open(fade_fn + "_fade_bottom.png")
layer_comp.alpha_composite(fade)
if os.path.isfile(fade_fn + "_fade_left.png") == True:
fade = Image.open(fade_fn + "_fade_left.png")
layer_comp.alpha_composite(fade)
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) == True:
rsd = Image.open(fn)
layer_comp.alpha_composite(rsd)
# 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
shf_x = x + mstr_shadow_shift
if shf_x <= self._imgsize-1:
a = mask_pix[x,y][3]
st = 0
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") or (self._tag == "leisure" and self._value == "swimming_pool"):
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
# Except for buildings
if self._tag != "building":
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()
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(85, 101)
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)
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], t )
# 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":
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,11)
tree = Image.open(mstr_datafolder + "textures/building/area/p" + str(p) + ".png")
layer_comp.alpha_composite(tree, (lx, ly))
# We will do some super magic here to let houses look more realistic
if self._tag == "building":
vls = [ "detached", "hotel", "farm", "semidetached_house", "apartments", "civic", "house", "school", "kindergarten", "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, 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)
# 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")
# 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 < 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, 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 + mstr_shadow_shift
shf_y = y + (mstr_shadow_shift/2)
if shf_x < self._imgsize and shf_y < self._imgsize:
a = mask_pix[x,y][3]
st = random.uniform(0.3, mstr_shadow_strength)
ca = a * st
aa = int(ca)
shadow_pix[shf_x, shf_y] = (0,0,0,aa)
shadow = shadow.filter(ImageFilter.GaussianBlur(radius=2))
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")
# 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 = osm_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 = osm_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))
# 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(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":
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(60, 96)
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()
# 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):
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
for s in range(0, 4):
if adj_sources[s] != precedence and adj_sources[s] != -1:
src = adj_sources[s]
adj_image = Image.new("RGBA", (self._imgsize, self._imgsize))
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))
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" )
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
mask_pix = osm_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 )
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")
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