orthographic/layergen.py

893 lines
41 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
import time
from random import randrange
import random
import PIL.ImageOps
from PIL import Image, ImageFilter, ImageDraw, ImageOps, ImageFile
from defines import *
from log import *
from tileinfo import *
from osmxml import *
from functions import *
from resourcegen import *
from colorizer import *
ImageFile.LOAD_TRUNCATED_IMAGES = True
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 zoom level
def set_zoomlevel(self, zl):
self._zoomlevel = zl
# 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 = []
# 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[0] == self._tag and linedata[1] == self._value:
src = linedata[2].split(",")
break
# Should we encounter a 0 length at this point, we can choose something
# It means it touches no border as it was not found in the file
if len(src) == 0:
while len(src) < 6:
pick = randrange(1, 16)
if pick not in src: src.append(pick)
return src
# Find layer contrast to apply, if any
def findLayerContrast(self, res):
contrast = 0
# The already existing source data
srcfile = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/ctrdata"
# Let's open the file and find our entry
with open(srcfile) as file:
for line in file:
linedata = line.split(" ")
if len(linedata) > 1: # Make sure we don't break at last line of data file
if linedata[0] == self._tag and linedata[1] == self._value and linedata[2] == res:
contrast = int(linedata[3])
break
return contrast
# Generates some random tree.
# We will now move away from using pre-made trees...
# they didn't look so great
def generate_tree(self):
sx = randrange(18, 31)
sy = randrange(18, 31)
treepoly = Image.new("RGBA", (sx, sy))
draw = ImageDraw.Draw(treepoly)
draw.ellipse((4, 4, sx - 4, sy - 4), fill="black")
tree = Image.new("RGBA", (sx, sy))
treepx = tree.load()
maskpx = treepoly.load()
# How many tree points do we want?
treepts = 75
# How many of those have been drawn?
ptsdrawn = 0
bc = [
(36, 50, 52),
(30, 41, 39),
(32, 45, 37),
(32, 39, 49),
(33, 34, 40),
(44, 50, 53),
(40, 46, 48),
(14, 31, 38),
(17, 41, 33),
(39, 56, 35),
(51, 51, 42),
(12, 27, 31),
(45, 59, 48),
(37, 54, 29),
(59, 50, 34),
(59, 59, 35),
(59, 51, 35),
(70, 72, 45),
(48, 59, 44),
(29, 47, 23),
(47, 61, 43),
(29, 68, 15),
(53, 77, 63),
(20, 68, 40)
]
bcp = randrange(0, len(bc))
treedraw = ImageDraw.Draw(tree)
while ptsdrawn < treepts + 1:
rx = randrange(0, sx)
ry = randrange(0, sy)
mp = maskpx[rx, ry]
if mp[3] > 0:
d = randrange(0, 51)
r = bc[bcp][0]
g = bc[bcp][1] + d
b = bc[bcp][2] + (d // 2)
a = randrange(170, 256)
c = (r, g, b, a)
ex = randrange(2, 5)
ey = randrange(2, 5)
treedraw.ellipse((rx - (ex // 2), ry - (ey // 2), rx + (ey // 2), ry + (ey // 2)), fill=c)
ptsdrawn = ptsdrawn + 1
for y in range(0, tree.height):
for x in range(0, tree.width):
tp = treepx[x, y]
diff = randrange(0, 31)
nc = (tp[0] - diff, tp[1] - diff, tp[2] - diff, tp[3])
treepx[x, y] = nc
tree = tree.filter(ImageFilter.GaussianBlur(radius=0.5))
for y in range(0, tree.height):
for x in range(0, tree.width):
tp = treepx[x, y]
diff = randrange(0, 51)
nc = (tp[0] - diff, tp[1] - diff, tp[2] - diff, tp[3])
treepx[x, y] = nc
if self._zoomlevel == 16:
tree = tree.resize((int(tree.width/4), int(tree.height/4)), resample=Image.Resampling.BILINEAR)
return tree
# 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:
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):
# The Perlin map
perlin_map = Image.open(mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/perlin/" + self._tag + "_" + self._value + ".png")
perlin_pix = perlin_map.load()
# Determine where we get the 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]
# 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
fordiv = 100
if self._zoomlevel == 16: fordiv = 25
for y in range(0, mask.height, int(mask.height/fordiv)):
for x in range(0, mask.width, int(mask.width/fordiv)):
px = epx[x,y]
if px[3] == 255:
rx = randrange(24,60)
ry = randrange(24,60)
if self._zoomlevel == 16:
rx = randrange(6, 15)
ry = randrange(6, 15)
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
# Find starting point in the Perlin map
prln_step = 0
if self._zoomlevel == 16: prln_step = 80
if self._zoomlevel == 18: prln_step = 20
prln_x = (self._lng_number-1) * prln_step
prln_y = perlin_map.height - (self._lat_number * prln_step) - 1
# First, fill the image with the base colors from the Perlin map,
# plus some variation
prln_pix_step = int(layer.width / prln_step)
for y in range(0, layer.height):
for x in range(0, layer.width):
xp = prln_x + int(x/prln_pix_step)
yp = prln_y + int(y/prln_pix_step)
if xp >= perlin_map.width: xp = perlin_map.width - 1
if yp >= perlin_map.height: yp = perlin_map.height - 1
pc = perlin_pix[xp,yp]
df = randrange(0, 6)
lc = (pc[0]+df, pc[1]+df, pc[2]+df, 255)
#lc = (pc[0], pc[1], pc[2], 255)
layer_pix[x,y] = lc
clrz_layer = layer.copy()
# ------------------------------------------
# Begin producing a largely random image
samples = 0
if self._zoomlevel == 18: samples = 250
if self._zoomlevel == 16: samples = 2000
txts = glob.glob(mstr_datafolder + "textures/" + self._tag + "/" + self._value + "/*.png")
ptc_name = txts[randrange(0, len(txts))]
while "brd" in ptc_name:
ptc_name = txts[randrange(0, len(txts))]
ptc_src = Image.open(ptc_name)
if self._zoomlevel == 16: ptc_src = ptc_src.resize((250,250), resample=Image.Resampling.BILINEAR)
clrz = mstr_colorizer(ptc_src)
tmp_layer = Image.new("RGBA", (self._imgsize, self._imgsize))
for i in range(samples):
xp = randrange(-125, 1924)
yp = randrange(-125, 1924)
ptc = clrz._grs.rotate(randrange(0, 360), expand=True)
if self._value == "golf_course":
ptc = clrz._baseimg.rotate(randrange(0, 360), expand=True)
tmp_layer.alpha_composite(ptc, (xp,yp))
# Add the seamless border
brd = Image.open(mstr_datafolder + "textures/" + self._tag + "/" + self._value + "/brd.png")
brd_clrz = mstr_colorizer(brd)
if self._value != "golf_course":
tmp_layer.alpha_composite(brd_clrz._baseimg)
else:
tmp_layer.alpha_composite(brd_clrz._grs)
if self._value != "golf_course":
tmp_layer.putalpha(51)
layer.alpha_composite(tmp_layer)
# Let's make some noise to give forests some better look
if (self._tag == "landuse" and self._value == "forest") or (self._tag == "leisure" and self._value == "nature_reserve"):
frst_noise = Image.new("RGBA", (self._imgsize, self._imgsize))
frst_pix = frst_noise.load()
for n in range(0, 1500000):
nx = randrange(0, self._imgsize)
ny = randrange(0, self._imgsize)
na = randrange(65, 241)
nc = (0,0,0,na)
frst_pix[nx,ny] = nc
frst_noise = frst_noise.filter(ImageFilter.GaussianBlur(radius=1))
layer.alpha_composite(frst_noise)
# Same for farmlands... but not as intensive
if (self._tag == "landuse" and self._value == "farmland") or (self._tag == "landuse" and self._value == "farmyard"):
frst_noise = Image.new("RGBA", (self._imgsize, self._imgsize))
frst_pix = frst_noise.load()
for n in range(0, 1500000):
nx = randrange(0, self._imgsize)
ny = randrange(0, self._imgsize)
na = randrange(25, 65)
nc = (0,0,0,na)
frst_pix[nx,ny] = nc
frst_noise = frst_noise.filter(ImageFilter.GaussianBlur(radius=1))
layer.alpha_composite(frst_noise)
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") or (
self._tag == "landuse" and self._value == "cemetery") or (
self._tag == "landuse" and self._value == "residential"):
amt = randrange(50,101)
masks = glob.glob(mstr_datafolder + "textures/tile/completion/*.png")
patchtags = [
["landuse", "meadow"],
["landuse", "grass"],
["natural", "heath"],
["natural", "scrub"]
]
for i in range(1, amt + 1):
layerpatch = Image.open(mstr_datafolder + "textures/tile/completion_color/p" + str(randrange(1,14)) + ".png")
if self._zoomlevel == 16:
lpw = int(layerpatch.width/3)
lph = int(layerpatch.height/3)
layerpatch = layerpatch.resize((lpw,lph), resample=Image.Resampling.BILINEAR)
layerpatch = layerpatch.rotate(randrange(0, 360), expand=True)
lx = randrange(0, mstr_photores-layerpatch.width)
ly = randrange(0, mstr_photores-layerpatch.height)
layer.alpha_composite(layerpatch, (lx, ly))
# 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])
# Add a white-ish border around pitches
if self._tag == "leisure" and self._value == "pitch":
pitch_edge = mask.filter(ImageFilter.FIND_EDGES)
pitch_mask = pitch_edge.load()
# ImageOps.invert does not like RGBA images for inversion. So I need to do it.
for y in range(0, self._imgsize):
for x in range(0, self._imgsize):
pm = pitch_mask[x,y]
if pm[3] > 0:
d = randrange(0, 21)
layer_comp_pix[x,y] = ( 110-pm[0]-d, 110-pm[1]-d, 110-pm[2]-d, pm[3]-(d*2) )
# Layer complete
mstr_msg("layergen", "Layer image completed.")
# 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")
# 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 == "aeroway" and self._value == "taxiway":
# Almost the same as above
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(0, 31)
dr = 80+d
dg = 80+d
db = 85+d
da = 255
layer_comp_pix[x, y] = ( dr,dg,db,da )
if self._tag == "highway" and self._value == "motorway":
d = randrange(0, 46)
dr = 47+d
dg = 58+d
db = 60+d
layer_comp_pix[x, y] = ( dr,dg,db,255 )
if self._tag == "highway" and (self._value == "footway" or self._value == "track" or self._value == "path"):
dr = randrange(158, 183)
dg = randrange(143, 178)
db = randrange(90, 161)
da = a[3]-20
layer_comp_pix[x, y] = (dr, dg, db, da)
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 different for tree rows
if self._tag == "natural" and self._value == "tree_row":
trees = Image.new("RGBA", (self._imgsize, self._imgsize))
treespx = trees.load()
for t in range(450001):
lx = randrange(self._imgsize)
ly = randrange(self._imgsize)
a = mask_pix[lx,ly]
# Just mark the hit with a black pixel.
# This will be used as "target" by photogen
if a[3] > 0:
if lx < self._imgsize and ly < self._imgsize:
c = (0,0,0,1)
treespx[lx,ly] = c
layer_comp.alpha_composite(trees)
mstr_msg("layergen", "Layer image generated")
# 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,int(a[3]/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.")
# Return image
return layer_comp
# ------------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------
# ------------------------------------------------------------------------------------------
# As we now have a specific pool for buildings, we will need to enter a third branch
# to handle all kinds of buildings in the orthos.
if self._tag == "building":
# Access to pixels from OSM mask
mask_pix = mask.load()
# Separate images for additional details
tree_shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
trees = Image.new("RGBA", (self._imgsize, self._imgsize))
shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
bld_src = Image.new("RGBA", (self._imgsize, self._imgsize))
bld_main = Image.new("RGBA", (self._imgsize, self._imgsize))
osm_edge = mask.filter(ImageFilter.FIND_EDGES)
# Find a source image to use
srclist = glob.glob(mstr_datafolder + "textures/" + self._tag + "/" + self._value + "/*.png")
srcnum = randrange(1, len(srclist)+1)
# Patch and border sources. There can only be one for each.
brd_src = None
numbers = list(range(1, len(srclist)))
res = random.sample(numbers, 4)
ptc_src = []
for p in range(0, len(res)):
ptc_img = Image.open(mstr_datafolder + "textures/" + self._tag + "/" + self._value + "/" + str(res[p]) + ".png")
if self._zoomlevel == 16:
ptc_img = ptc_img.resize((250,250), resample=Image.Resampling.BILINEAR)
ptc_src.append(ptc_img)
# Set some contrast
lyr_contrast = 0.85
# Open the images
#ptc_src.append( ptc_img ) # Used to be an array, so let's keep it
#brd_src = bldg_src[1]
# Begin producing a largely random image
samples = 0
if self._zoomlevel == 18: samples = 250
if self._zoomlevel == 16: samples = 2000 # <- 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(0, len(ptc_src))
i = ptc_src[imgid].rotate(randrange(0, 360), expand=True)
l = 0 - int(i.width / 2)
r = layer.width - int(i.width / 2)
t = 0 - int(i.height / 2)
b = layer.height - int(i.height / 2)
bld_src.alpha_composite(i, (randrange(l, r), randrange(t, b)))
mstr_msg("layergen", "Layer image generated")
bld_src_pix = bld_src.load()
bld_main_pix = bld_main.load()
for y in range(0, self._imgsize):
for x in range(0, self._imgsize):
mp = mask_pix[x,y]
bp = bld_src_pix[x,y]
if mp[3] > 0:
nc = ( bp[0], bp[1], bp[2], mp[3] )
bld_main_pix[x,y] = nc
bld_main = bld_main.filter(ImageFilter.GaussianBlur(radius=0.5))
osm_edge = osm_edge.filter(ImageFilter.GaussianBlur(radius=1.5))
bld_edge = Image.new("RGBA", (self._imgsize, self._imgsize))
bld_edge_pix = bld_edge.load()
edge_pix = osm_edge.load()
for y in range(osm_edge.height):
for x in range(osm_edge.width):
ep = edge_pix[x,y]
bp = bld_src_pix[x,y]
if ep[3] == 255 and bp[3] == 255:
nc = (0,0,0,60)
bld_edge_pix[x,y] = nc
bld_main.alpha_composite(bld_edge)
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)
# Add some random trees
treeamt = 5000
if self._zoomlevel == 18: treeamt = 35000
for t in range(0, treeamt+1):
loc_x = randrange(0, self._imgsize)
loc_y = randrange(0, self._imgsize)
shf_val = 21
shf_x = randrange(loc_x - shf_val, loc_x + shf_val)
shf_y = randrange(loc_y - shf_val, loc_y + shf_val)
mp = mask_pix[loc_x, loc_y]
if mp[3] == 255:
shf_att = 0
shf_ok = False
shf_x = randrange(loc_x - shf_val, loc_x + shf_val)
shf_y = randrange(loc_y - shf_val, loc_y + shf_val)
while shf_att < 51:
if shf_x > 0 and shf_x < self._imgsize and shf_y > 0 and shf_y < self._imgsize:
sp = mask_pix[shf_x, shf_y]
if sp[3] == 0:
shf_ok = True
break
else:
shf_att = shf_att + 1
else:
shf_val = shf_val + 10
shf_att = shf_att + 1
if shf_ok == True:
tree = self.generate_tree()
trees.alpha_composite(tree, (shf_x, shf_y))
# Perform correction for tree rendering
tree_pix = trees.load()
for y in range (0, self._imgsize):
for x in range(0, self._imgsize):
mp = mask_pix[x,y]
tp = tree_pix[x,y]
if mp[3] == 255 and tp[3] == 255:
tree_pix[x,y] = (0,0,0,0)
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)
spx = 8
spy = 5
if self._zoomlevel == 16:
spx = 2
spy = 1
if x + spx < self._imgsize and y + spy < self._imgsize:
shadow_pix[x + spx, y + spy] = 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))
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)
#bld_comp = bld_comp.filter(ImageFilter.GaussianBlur(radius=1.1))
bld_main = ImageEnhance.Contrast(bld_main).enhance(lyr_contrast)
bld_comp.alpha_composite(bld_main)
return bld_comp
# 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()