Massive commit. Raytracer for tile preparation. Resource generator. Everything moved into RAM for processing.

This commit is contained in:
Marcus Str. 2024-12-17 17:18:05 +01:00
parent aae81c58e9
commit 2278fe3b0a
11 changed files with 934 additions and 791 deletions

View File

@ -132,6 +132,7 @@ mstr_ortho_layers = [
("natural", "bare_rock", "natural", "bare_rock"), ("natural", "bare_rock", "natural", "bare_rock"),
("highway", "track", 3), ("highway", "track", 3),
("highway", "path", 3), ("highway", "path", 3),
("highway", "footway", 4),
("leisure", "park", "leisure", "green"), ("leisure", "park", "leisure", "green"),
("leisure", "dog_park", "leisure", "green"), ("leisure", "dog_park", "leisure", "green"),
("leisure", "garden", "leisure", "green"), ("leisure", "garden", "leisure", "green"),
@ -144,7 +145,6 @@ mstr_ortho_layers = [
# Z-Order 2 # Z-Order 2
("highway", "service", 6), ("highway", "service", 6),
("highway", "residential", 12), ("highway", "residential", 12),
("highway", "footway", 4),
("highway", "primary", 25), ("highway", "primary", 25),
("highway", "secondary", 13), ("highway", "secondary", 13),
("highway", "tertiary", 20), ("highway", "tertiary", 20),
@ -191,6 +191,7 @@ mstr_ortho_layers = [
("building", "kindergarten", "building", "kindergarten"), ("building", "kindergarten", "building", "kindergarten"),
("building", "public", "building", "public"), ("building", "public", "building", "public"),
("building", "commercial", "building", "commercial"), ("building", "commercial", "building", "commercial"),
("building", "warehouse", "building", "warehouse"),
("building", "yes", "building", "common"), ("building", "yes", "building", "common"),
("water", "lake", "natural", "water"), ("water", "lake", "natural", "water"),
("water", "pond", "natural", "water"), ("water", "pond", "natural", "water"),
@ -281,9 +282,10 @@ mstr_mask_blur = [
("building", "terrace", 1), ("building", "terrace", 1),
("building", "hangar", 1), ("building", "hangar", 1),
("building", "school", 1), ("building", "school", 1),
("building", "kindergarten", 1), ("building", "kindergarten", 1),
("building", "public", 1), ("building", "public", 1),
("building", "commercial", 1), ("building", "commercial", 1),
("building", "warehouse", 1),
("building", "yes", 0), ("building", "yes", 0),
("place", "sea", 1), ("place", "sea", 1),
("place", "ocean", 1) ("place", "ocean", 1)
@ -396,7 +398,13 @@ mstr_building_base_colors = [
"#373942", "#40424a", "#363b4f", "#2c2d32", "#444651", "#373942", "#40424a", "#363b4f", "#2c2d32", "#444651",
"#454545", "#39393b", "#4b4b4c", "#363638", "#525252" "#454545", "#39393b", "#4b4b4c", "#363638", "#525252"
] ]
) ),
("warehouse", [
"#403a33", "#4f4b46", "#413629", "#574c3f", "#3a2e21",
"#aaaeb6", "#939cac", "#8a919d", "#a0b9bf", "#8d999b",
"#49575a", "#273d43", "#313a3c", "#484f50", "#212d30"
]
),
] ]

View File

@ -107,4 +107,5 @@ def xplane_latlng_folder(numbers):
if abs(numbers[1]) >= 10 and numbers[0] <= 99: fstr = fstr + "0" + 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]) if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])
return fstr return fstr

File diff suppressed because it is too large Load Diff

8
log.py
View File

@ -11,9 +11,15 @@
# ------------------------------------------------------------------- # -------------------------------------------------------------------
import datetime import datetime
from colorama import init as colorama_init
from colorama import Fore
from colorama import Style
from defines import * from defines import *
def mstr_msg(fnc, msg): def mstr_msg(fnc, msg):
if mstr_show_log == True: if mstr_show_log == True:
colorama_init()
now = datetime.datetime.now() now = datetime.datetime.now()
print(now.strftime(" %H:%M:%S" + " | ["+fnc+"] | " + msg))
print(f' {Fore.GREEN}'+now.strftime("%H:%M:%S")+f'{Style.RESET_ALL} | {Fore.YELLOW}[' + fnc + f']{Style.RESET_ALL} | {Fore.CYAN}'+ msg + f'{Style.RESET_ALL}')
#print(f"{Fore.GREEN}" + now.strftime(" %H:%M:%S" + " | ["+fnc+"] | " + msg))

View File

@ -252,6 +252,14 @@ class mstr_maskgen:
if sp[3] != 0: if sp[3] != 0:
bld_shadow_pix[x,y] = (0,0,0,120) bld_shadow_pix[x,y] = (0,0,0,120)
# Mark buildings in red
for y in range(mstr_photores):
for x in range(mstr_photores):
mp = mask_pix[x,y]
if mp[3] != 0:
bld_shadow_pix[x,y] = (255,0,0,255)
# Store # Store
if os.path.isfile(fn) == True: if os.path.isfile(fn) == True:
lyr = Image.open(fn) lyr = Image.open(fn)

32
og.py
View File

@ -35,13 +35,9 @@ prep = False
if len(sys.argv) == 4: if len(sys.argv) == 4:
cli = True cli = True
if sys.argv[3] == "true": prep = True
#if len(sys.argv) == 4:
# pbf = True
# Only if we find enough arguments, proceed. # Only if we find enough arguments, proceed.
if cli == True: if cli:
lat = int(sys.argv[1]) lat = int(sys.argv[1])
lng = int(sys.argv[2]) lng = int(sys.argv[2])
@ -50,28 +46,18 @@ if cli == True:
# Create the class and init values # Create the class and init values
og = mstr_orthographic(lat, lng, mstr_datafolder, os.getcwd(), prep) og = mstr_orthographic(lat, lng, mstr_datafolder, os.getcwd(), prep)
if prep == True: # Prepare a tile
if sys.argv[3] == "prepare":
og._prepareTile() og._prepareTile()
if prep == False: # Generate orthos
if sys.argv[3] != "xpscenery": if sys.argv[3] != "prepare" and sys.argv[3] != "xpscenery":
og._generateOrthos_mt(int(sys.argv[3])) og._generateOrthos_mt(int(sys.argv[3]))
# Build the terrain mesh and assign ground textures
if sys.argv[3] == "xpscenery":
og.generate_xp_scenery()
# Build the terrain mesh and assign ground textures
if sys.argv[3] == "xpscenery":
og.generate_xp_scenery()
# Only if we find enough arguments, proceed.
if pbf == True:
lat = int(sys.argv[1])
lng = int(sys.argv[2])
pbf = sys.argv[3]
if pbf == "pbf":
# Create the class and init values
og = mstr_orthographic(lat, lng, mstr_datafolder, os.getcwd())
og._generateData()
if cli == False and pbf == False: if cli == False and pbf == False:

View File

@ -170,9 +170,13 @@ class mstr_orthographic:
mstr_msg("orthographic", "Max lat tile: " + str(mlat) + " - max lng tile: " + str(mlng)) mstr_msg("orthographic", "Max lat tile: " + str(mlat) + " - max lng tile: " + str(mlng))
maxlatlng = [ mlat, mlng ] maxlatlng = [ mlat, mlng ]
# For completion layers
numbers = list(range(1, 16))
res = random.sample(numbers, 5)
procs = [] procs = []
for p in range(1, amtsmt+1): for p in range(1, amtsmt+1):
proc = Process(target=self._buildOrtho, args=[1, p, amtsmt]) proc = Process(target=self._buildOrtho, args=[1, p, amtsmt, res])
procs.append(proc) procs.append(proc)
proc.start() proc.start()
mstr_msg("orthographic", "Ortho threads started") mstr_msg("orthographic", "Ortho threads started")
@ -181,7 +185,7 @@ class mstr_orthographic:
# Starts a threading loop to build orthos, with the defined starting point in # Starts a threading loop to build orthos, with the defined starting point in
# the lat-lng grid. You will also need to provide the horizontal stepping so # the lat-lng grid. You will also need to provide the horizontal stepping so
# that the thread keeps running. # that the thread keeps running.
def _buildOrtho(self, v, h, step): def _buildOrtho(self, v, h, step, cpl):
# Starting point # Starting point
grid_lat = v grid_lat = v
@ -256,7 +260,6 @@ class mstr_orthographic:
lg = mstr_layergen(layer[0], layer[1], self._lat, grid_lat, self._long, grid_lng, layer[2]) lg = mstr_layergen(layer[0], layer[1], self._lat, grid_lat, self._long, grid_lng, layer[2])
lg.set_max_latlng_tile(maxlatlng) lg.set_max_latlng_tile(maxlatlng)
lg.set_latlng_folder(self._latlngfld) lg.set_latlng_folder(self._latlngfld)
#lg.open_db()
lg.open_tile_info() lg.open_tile_info()
lyr = lg.genlayer(mask, osmxml) lyr = lg.genlayer(mask, osmxml)
photolayers.append(lyr) photolayers.append(lyr)
@ -274,7 +277,8 @@ class mstr_orthographic:
# Snap a photo with our satellite :) # Snap a photo with our satellite :)
mstr_msg("orthographic", "Generating ortho photo") mstr_msg("orthographic", "Generating ortho photo")
pg = mstr_photogen(self._lat, self._long, grid_lat, grid_lng, maxlatlng[0], maxlatlng[1]) pg = mstr_photogen(self._lat, self._long, grid_lat, grid_lng, maxlatlng[0], maxlatlng[1])
pg.genphoto(photolayers, waterlayers) pg.setLayerNames(layers)
pg.genphoto(photolayers, waterlayers, cpl)
mstr_msg("orthographic", " -- Ortho photo generated -- ") mstr_msg("orthographic", " -- Ortho photo generated -- ")
print("") print("")
print("") print("")
@ -375,12 +379,12 @@ class mstr_orthographic:
bb_lat = self._lat bb_lat = self._lat
bb_lng = self._long bb_lng = self._long
# We will now prepare the graphic tile generation. We do this by only generating # We will now prepare the graphic tile generation. We do this by only generating
# the masks and determine which sources to use in the actual images. # the masks and determine which sources to use in the actual images.
# Previously, I downloaded all XML files in one go - but to ease the # Previously, I downloaded all XML files in one go - but to ease the
# stress on OSM servers and my server, we will do acquire the data # stress on OSM servers and my server, we will do acquire the data
# only for the current processed part of the tile. # only for the current processed part of the tile.
"""
for lat_grid in range(1, maxlatlng[0]+1): for lat_grid in range(1, maxlatlng[0]+1):
for lng_grid in range(1, maxlatlng[1]+1): for lng_grid in range(1, maxlatlng[1]+1):
# Adjust bounding box # Adjust bounding box
@ -403,7 +407,7 @@ class mstr_orthographic:
mask = mg._build_mask(osmxml, is_prep=True) # We need an object here mask = mg._build_mask(osmxml, is_prep=True) # We need an object here
tp = mstr_tileprep(self._lat, self._long, lat_grid, lng_grid, layer[0], layer[1], mask, False) tp = mstr_tileprep(self._lat, self._long, lat_grid, lng_grid, layer[0], layer[1], mask, False)
tp._prepareTile() tp._determineEdges()
curlyr = curlyr+1 curlyr = curlyr+1
@ -428,6 +432,24 @@ class mstr_orthographic:
if cur_tile_y > top_lat: if cur_tile_y > top_lat:
top_lat = cur_tile_y top_lat = cur_tile_y
exit(1)
"""
# We now need to "raytrace" the resources for correct placement
mstr_msg("orthographic", "Performing resource plamement tracing for tile")
for lat_grid in range(1, maxlatlng[0]+1):
for lng_grid in range(1, maxlatlng[1]+1):
mstr_msg("orthographic", "Placement tracing for " + str(lat_grid) + ":" + str(lng_grid))
df = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/" + str(lat_grid) + "_" + str(lng_grid)
fnlines = []
with open(df) as textfile:
fnlines = textfile.readlines()
for l in range(0, len(fnlines)):
lyr = fnlines[l].split(" ")
tp = mstr_tileprep(self._lat, self._long, lat_grid, lng_grid, lyr[0], lyr[1], None, False)
tp._setLatLngFold(self._latlngfld)
tp._placeTileSources(lat_grid, lng_grid)
# Generates X-Plane 11/12 scenery with # Generates X-Plane 11/12 scenery with

View File

@ -1,12 +1,14 @@
import os import os
from PIL import Image, ImageFilter, ImageEnhance from PIL import Image, ImageFilter, ImageEnhance, ImageFile
from defines import * from defines import *
from layergen import * from layergen import *
from log import * from log import *
from functions import * from functions import *
from xp_normalmap import * from xp_normalmap import *
ImageFile.LOAD_TRUNCATED_IMAGES = True
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# ORTHOGRAPHIC # ORTHOGRAPHIC
# Your personal aerial satellite. Always on. At any altitude.* # Your personal aerial satellite. Always on. At any altitude.*
@ -36,17 +38,30 @@ class mstr_photogen:
self._latlngfld = self.latlng_folder([lat,lng]) self._latlngfld = self.latlng_folder([lat,lng])
mstr_msg("photogen", "Photogen initialized") mstr_msg("photogen", "Photogen initialized")
# Defines the order of layer names that were processed
# Called by orthographic prior to starting the photo gen process
def setLayerNames(self, names):
self._lyrnames = names
# This puts it all together. Bonus: AND saves it. # This puts it all together. Bonus: AND saves it.
def genphoto(self, layers, waterlayers): def genphoto(self, layers, waterlayers, cpl):
# Template for the file name which is always the same # 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) + "_" #root_filename = mstr_datafolder + "/_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_"
# Correct layers
#mstr_msg("photogen", "Correcting layer order issues")
#layers = self.correctLayerIssues(layers)
# First, we walk through all layers and blend them on top of each other, in order # First, we walk through all layers and blend them on top of each other, in order
mstr_msg("photogen", "Merging layers") mstr_msg("photogen", "Merging layers")
lyr=0
for l in layers: for l in layers:
self._tile.alpha_composite(l) if self._lyrnames[lyr] != "building":
self._tile.alpha_composite(l)
lyr=lyr+1
# When we have run through this loop, we will end up with a sandwiched # When we have run through this loop, we will end up with a sandwiched
@ -65,65 +80,80 @@ class mstr_photogen:
if emptyspace == True: if emptyspace == True:
mstr_msg("photogen", "Patching empty space") mstr_msg("photogen", "Patching empty space")
mask = self.buildCompletionMask()
# Load the mask
mask_px = mask.load()
cmpl = Image.new("RGBA", (self._imgsize, self._imgsize)) cmpl = Image.new("RGBA", (self._imgsize, self._imgsize))
cmp_px = cmpl.load()
edn = self.find_earthnavdata_number() edn = self.find_earthnavdata_number()
edns = self.latlng_folder(edn) edns = self.latlng_folder(edn)
idx = 0
for r in mstr_completion_colors:
if r[0] == edns:
break
else:
idx = idx+1
for y in range(self._imgsize):
for x in range(self._imgsize):
p = mask_px[x,y]
if p[3] > 0:
cidx = randrange(0, len(mstr_completion_colors[idx][1])-1)
clr = mstr_completion_colors[idx][1][cidx]
cmp_px[x,y] = (clr[0], clr[1], clr[2], 255)
# Some features cplstr = ""
patches = glob.glob(mstr_datafolder + "textures/tile/completion/*.png") for c in range(0, len(cpl)):
cplstr = cplstr + str(cpl[c])
if c < len(cpl)-1:
cplstr = cplstr + "_"
# Pick an amount of features to add # Should this not exist yet, we need to create it
patch_amt = randrange(1, 7) rg = mstr_resourcegen("landuse", "meadow", cpl)
rg.setLayerContrast(randrange(1,4))
ptcimg = rg.gensource()
# Add those somewhere ptc_src = [ptcimg[0]]
for p in range(1, patch_amt+1): samples = 250 # <- We need this in a moment
# Load some patch for i in range(samples):
ptc = Image.open(mstr_datafolder + "textures/tile/completion/p" + str(randrange(1, len(patches)+1)) + ".png") imgid = 0
# Rotate it if len(ptc_src) == 1: imgid = 0
ptc = ptc.rotate(randrange(0, 360), expand=True) l = 0 - int(ptc_src[imgid].width / 2)
r = cmpl.width - int(ptc_src[imgid].width / 2)
t = 0 - int(ptc_src[imgid].height / 2)
b = cmpl.height - int(ptc_src[imgid].height / 2)
cmpl.alpha_composite(ptc_src[imgid], (randrange(l, r), randrange(t, b)))
# Make sure ortho generation does not crash brd_img = ptcimg[1]
if ptc.width >= mstr_photores: cmpl.alpha_composite(brd_img)
ptc = ptc.resize((1536, 1536), Image.Resampling.BILINEAR)
# Adjust alpha on this image # Patches to add from other sources. If they don't exist, we also need to make them
ptc_p = ptc.load() masks = glob.glob(mstr_datafolder + "textures/tile/completion/*.png")
for y in range(ptc.height): amt = randrange(5, 16)
for x in range(ptc.width): for i in range(1, amt + 1):
c = ptc_p[x,y] pick = randrange(0, len(masks))
if c[3] > 0: patchmask = Image.open(masks[pick])
na = c[3] - 160 patchpix = patchmask.load()
if na < 0: na = 0 # Pick from possible tags and values for the patches
nc = (c[0], c[1], c[2], na) patchtags = [
ptc_p[x,y] = nc ["landuse", "meadow"],
["landuse", "grass"],
["natural", "heath"],
["natural", "scrub"]
]
# Find a location INSIDE the image! numbers = list(range(1, 16))
px = randrange(1, randrange(self._imgsize - ptc.width - 1)) src = random.sample(numbers, 5)
py = randrange(1, randrange(self._imgsize - ptc.height - 1))
# Add it to the completion image patchpick = randrange(0, len(patchtags))
cmpl.alpha_composite(ptc, dest=(px,py))
rg = mstr_resourcegen(patchtags[patchpick][0], patchtags[patchpick][1], src)
rg.setLayerContrast(randrange(1, 4))
ptch = rg.gensource()
rg_img = ptch[0]
rg_pix = rg_img.load()
# The patch to be used in the layer
layerpatch = Image.new("RGBA", (patchmask.width, patchmask.height))
lp_pix = layerpatch.load()
for y in range(0, patchmask.height):
for x in range(0, patchmask.width):
ptc_msk = patchpix[x, y]
if ptc_msk[3] > 0:
oc = rg_pix[x, y]
nc = (oc[0], oc[1], oc[2], ptc_msk[3])
lp_pix[x, y] = nc
layerpatch = layerpatch.rotate(randrange(0, 360), expand=True)
lx = randrange(self._imgsize - layerpatch.width)
ly = randrange(self._imgsize - layerpatch.height)
cmpl.alpha_composite(layerpatch, (lx, ly))
# Merge the images # Merge the images
cmpl.alpha_composite(self._tile) cmpl.alpha_composite(self._tile)
@ -152,11 +182,22 @@ class mstr_photogen:
corrpix[x,y] = nc corrpix[x,y] = nc
if c[3] == 0: if c[3] == 0:
corrpix[x,y] = (0,0,0,0) corrpix[x,y] = (0,0,0,0)
# One more thing...
mstr_msg("photogen", "Adding features to layers")
self.addTreesToFeatures(layers)
# Throw missing buildings on top
lyr = 0
for l in layers:
if self._lyrnames[lyr] == "building":
self._tile.alpha_composite(l)
lyr = lyr + 1
# We are now in posession of the final image. # We are now in posession of the final image.
# Contrast # Contrast
self._tile = ImageEnhance.Contrast(self._tile).enhance(1) #self._tile = ImageEnhance.Contrast(self._tile).enhance(0.1)
# This we can save accordingly. # This we can save accordingly.
self._tile.save(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png") self._tile.save(mstr_datafolder + "z_orthographic/orthos/" + self._latlngfld + "/" + str(self._ty) + "_" + str(self._tx) + ".png")
@ -191,6 +232,172 @@ class mstr_photogen:
nrmimg.save(nrmfln) nrmimg.save(nrmfln)
# 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
return tree
# This used to be in layergen and solves the problem of roads being rendered above trees.
# It is the only solution that worked, after some research.
def addTreesToFeatures(self, layers):
# Walk through list of layers to decide where to add the trees
curlyr = 0
for lyr in self._lyrnames:
# Add trees only in some features
if (lyr[0] == "landuse" and lyr[1] == "cemetery") or (
lyr[0] == "landuse" and lyr[1] == "residential") or (
lyr[0] == "leisure" and lyr[1] == "park"):
trees = Image.new("RGBA", (self._imgsize, self._imgsize))
amt = 4000
lyrmask = layers[curlyr].load() # We can use the layer image as alpha mask
for i in range(1, amt + 1):
lx = randrange(0, self._imgsize)
ly = randrange(0, self._imgsize)
lp = lyrmask[lx,ly]
if lp[3] > 0:
tree = self.generate_tree()
trees.alpha_composite(tree, (lx, ly))
tree_shadow = Image.new("RGBA", (self._imgsize, self._imgsize))
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)
if x + 8 < self._imgsize and y + 5 < self._imgsize:
shadow_pix[x + 8, y + 5] = sc
tree_shadow = tree_shadow.filter(ImageFilter.GaussianBlur(radius=2))
tree_shadow.alpha_composite(trees)
self._tile.alpha_composite(tree_shadow)
curlyr = curlyr + 1
# Reset
curlyr = 0
bldg = []
tilepix = self._tile.load()
for lyr in self._lyrnames:
if lyr[0] == "building":
bldg.append(curlyr)
curlyr = curlyr + 1
for b in range(0, len(bldg)):
bldg_lyr = layers[bldg[b]].load()
shdw = Image.open(mstr_datafolder + "_cache/" + str(self._lat) + "-" + str(self._ty) + "_" + str(self._lng) + "-" + str(self._tx) + "_" + self._lyrnames[bldg[b]][0] + "-" + self._lyrnames[bldg[b]][1] + "_layer_shadow.png")
shdw_pix = shdw.load()
for y in range(0, self._imgsize):
for x in range(0, self._imgsize):
bpix = bldg_lyr[x,y]
spix = shdw_pix[x,y]
if bpix[3] > 0 and spix[0] == 255:
tilepix[x,y] = ( bpix[0], bpix[1], bpix[2], bpix[3] )
# Correct some layer issues
def correctLayerIssues(self, layers):
# First the residential/forest dilemma
residential = 0
forest = 0
curlyr = 0
for lyr in self._lyrnames:
if lyr[0] == "landuse" and lyr[1] == "residential":
residential=curlyr
if lyr[0] == "landuse" and lyr[1] == "forest":
forest = curlyr
curlyr = curlyr+1
layers[forest].alpha_composite(layers[residential])
return layers
# This checks the final image for empty patches. Should one be # This checks the final image for empty patches. Should one be

View File

@ -29,7 +29,6 @@ class mstr_tileinfo:
#self._adjfile = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/adjinfo" #self._adjfile = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/adjinfo"
self._cplfile = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/cplinfo" self._cplfile = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/cplinfo"
self.createDataFile() self.createDataFile()
self.createCompletionFile()
def createDataFile(self): def createDataFile(self):
@ -37,15 +36,8 @@ class mstr_tileinfo:
open(self._adjfile, 'a').close() open(self._adjfile, 'a').close()
def createCompletionFile(self): def add_adjacency_data(self, tag, value, source, adj):
if os.path.isfile(self._cplfile) == False:
open(self._cplfile, 'a').close()
def add_adjacency_data(self, tv, th, tag, value, source, adj):
line = "" line = ""
line = line + str(tv) + " "
line = line + str(th) + " "
line = line + tag + " " line = line + tag + " "
line = line + value + " " line = line + value + " "
line = line + str(source) + " " line = line + str(source) + " "
@ -55,19 +47,6 @@ class mstr_tileinfo:
textfile.write(line) textfile.write(line)
def add_completion_data(self, tv, th, tag, value, source, adj):
line = ""
line = line + str(tv) + " "
line = line + str(th) + " "
line = line + tag + " "
line = line + value + " "
line = line + str(source) + " "
line = line + str(adj) + "\n"
with open(self._cplfile, 'a') as textfile:
textfile.write(line)
def get_adjacency_for_tag_and_value(self, tv, th, tag, value): def get_adjacency_for_tag_and_value(self, tv, th, tag, value):
adj = [] adj = []
fnlines = [] fnlines = []
@ -78,12 +57,11 @@ class mstr_tileinfo:
for ln in fnlines: for ln in fnlines:
l = ln.split(" ") l = ln.split(" ")
if int(l[0]) == tv and int(l[1]) == th: if l[0] == tag and l[1] == value:
if l[2] == tag and l[3] == value: l[3] = l[3].replace("\n", "")
l[5] = l[5].replace("\n", "") adj.append(l[2])
adj.append(int(l[4])) adj.append(l[3])
adj.append(l[5]) break
break
return adj return adj
@ -243,3 +221,4 @@ class mstr_tileinfo:
if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1]) if abs(numbers[1]) >= 100 : fstr = fstr + str(numbers[1])
return fstr return fstr

View File

@ -14,6 +14,7 @@
# ------------------------------------------------------------------- # -------------------------------------------------------------------
import glob import glob
from os import MFD_ALLOW_SEALING
from random import randrange from random import randrange
from PIL import Image from PIL import Image
from osmxml import * from osmxml import *
@ -34,7 +35,7 @@ class mstr_tileprep:
self._tag = tag self._tag = tag
self._value = value self._value = value
self._edges = "" # To be filled by _edgeDetect call self._edges = "" # To be filled by _edgeDetect call
self._source = -1 self._source = ""
self._mask = mask self._mask = mask
latlngfld = xplane_latlng_folder([lat, lng]) latlngfld = xplane_latlng_folder([lat, lng])
self._tileinfo = mstr_tileinfo(lat, lng, v, h, latlngfld) self._tileinfo = mstr_tileinfo(lat, lng, v, h, latlngfld)
@ -53,8 +54,8 @@ class mstr_tileprep:
return srcfld return srcfld
# Prepare the tile accordingly # Use the mask to determine the edges
def _prepareTile(self): def _determineEdges(self):
# Load the mask pixels # Load the mask pixels
mp = self._mask.load() mp = self._mask.load()
imgsize = self._mask.width imgsize = self._mask.width
@ -69,29 +70,29 @@ class mstr_tileprep:
al=False al=False
# Top scan # Top scan
for i in range(0, imgsize-1): for i in range(0, imgsize):
p = mp[i,0] p = mp[i,0]
if p[3] > 0: if p[3] > 0:
at=True at=True
break break
# Right scan # Right scan
for i in range(0, imgsize-1): for i in range(0, imgsize):
p = mp[imgsize-1,i] p = mp[imgsize-1,i]
if p[3] > 0: if p[3] > 0:
ar=True ar=True
break break
# Bottom scan # Bottom scan
for i in range(0, imgsize-1): for i in range(0, imgsize):
p = mp[i,imgsize-1] p = mp[i,imgsize-1]
if p[3] > 0: if p[3] > 0:
ab=True ab=True
break break
# Left scan # Left scan
for i in range(0, imgsize-1): for i in range(0, imgsize):
p = mp[1,i] p = mp[0,i]
if p[3] > 0: if p[3] > 0:
al=True al=True
break break
@ -103,48 +104,187 @@ class mstr_tileprep:
if ab==True: adjstr = adjstr + "b" if ab==True: adjstr = adjstr + "b"
if al==True: adjstr = adjstr + "l" if al==True: adjstr = adjstr + "l"
# Now find out of there is a source from any adjacent tile # We will now write this down first, without a source being selected
adjtiles = findAdjacentTilesTo(self._tile_v, self._tile_h)
sat = []
sar = []
sab = []
sal = []
if self._is_completion == False:
if at == True: sat = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[0][0], adjtiles[0][1], self._tag, self._value) # Top
if ar == True: sar = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[1][0], adjtiles[1][1], self._tag, self._value) # Right
if ab == True: sab = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[2][0], adjtiles[2][1], self._tag, self._value) # Bottom
if al == True: sal = self._tileinfo.get_adjacency_for_tag_and_value(adjtiles[3][0], adjtiles[3][1], self._tag, self._value) # Left
if self._is_completion == True:
if at == True: sat = self._tileinfo.get_adjacency_for_completion(adjtiles[0][0], adjtiles[0][1]) # Top
if ar == True: sar = self._tileinfo.get_adjacency_for_completion(adjtiles[1][0], adjtiles[1][1]) # Right
if ab == True: sab = self._tileinfo.get_adjacency_for_completion(adjtiles[2][0], adjtiles[2][1]) # Bottom
if al == True: sal = self._tileinfo.get_adjacency_for_completion(adjtiles[3][0], adjtiles[3][1]) # Left
if self._source == -1 and len(sat) == 2: self._source = sat[0]
if self._source == -1 and len(sar) == 2: self._source = sar[0]
if self._source == -1 and len(sab) == 2: self._source = sab[0]
if self._source == -1 and len(sal) == 2: self._source = sal[0]
# If there was nothing in the info still, we need to select some source
if self._source == -1:
srcfld = self._findCorrectTextureFolder()
tx = mstr_datafolder + "textures/" + srcfld[0] + "/" + srcfld[1] + "/brd/b*.png"
lst = glob.glob(tx)
if len(lst) == 1: self._source = 1
if len(lst) >= 2: self._source = randrange(1, len(lst)+1)
# Store into DB - but only if there is something to store
if adjstr != "": if adjstr != "":
if self._is_completion == False: self._tileinfo.add_adjacency_data(self._tag, self._value, "0", adjstr)
r = self._tileinfo.get_adjacency_for_tag_and_value(self._tile_v, self._tile_h, self._tag, self._value)
if len(r) == 0:
self._tileinfo.add_adjacency_data(self._tile_v, self._tile_h, self._tag, self._value, self._source, adjstr)
mstr_msg("tileprep", "Adjacency info stored in database")
if self._is_completion == True:
r = self._tileinfo.get_adjacency_for_completion(self._tile_v, self._tile_h)
if len(r) == 0:
self._tileinfo.add_completion_data(self._tile_v, self._tile_h, self._tag, self._value, self._source, adjstr)
mstr_msg("tileprep", "Adjacency info for completion stored in database")
# Set the latlng folder
def _setLatLngFold(self, latlngfld):
self._latlngfld = latlngfld
# Find out if there is already something in place for this tag of this tile
def _getResourceInfo(self, tv, th):
# This either remains 0 or a string different to "0" in the end
src = "0"
df = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/" + str(tv) + "_" + str(th)
fnlines = []
if os.path.isfile(df) == True: # It is possible that the requested file does not yet exist
with open(df) as textfile:
fnlines = textfile.readlines()
for ln in fnlines:
l = ln.split(" ")
if l[0] == self._tag and l[1] == self._value:
l[3] = l[3].replace("\n", "")
src = l[2]
return src
# Find the edge touch info
def _getResourceTouch(self, tv, th):
touch = ""
df = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/" + str(tv) + "_" + str(th)
fnlines = []
if os.path.isfile(df) == True: # It is possible that the requested file does not yet exist
with open(df) as textfile:
fnlines = textfile.readlines()
for ln in fnlines:
l = ln.split(" ")
if l[0] == self._tag and l[1] == self._value:
l[3] = l[3].replace("\n", "")
touch = l[3]
return touch
# Select a combination of resources
def _selectResources(self):
numbers = list(range(1, 16))
res = random.sample(numbers, 5)
# Construct a string of the array
resstr = ""
for r in range(len(res)):
resstr = resstr + str(res[r])
if r < len(res)-1:
resstr = resstr + ","
return resstr
# Store the required resource information into the appropriate tile
def _storeResourceInfo(self, tile_v, tile_h, res):
df = mstr_datafolder + "z_orthographic/data/" + self._latlngfld + "/" + str(tile_v) + "_" + str(tile_h)
fnlines = []
contrast = 0
if os.path.isfile(df) == True: # It is possible that the requested file does not yet exist
with open(df) as textfile:
fnlines = textfile.readlines()
curline = 0
for ln in fnlines:
l = ln.split(" ")
if l[0] == self._tag and l[1] == self._value:
l[2] = res
contrast = int(l[4])
# Find contrast values for some tags
if (
(l[0] == "landuse" and l[1] == "forest") or
(l[0] == "landuse" and l[1] == "meadow") or
(l[0] == "landuse" and l[1] == "grass") or
(l[0] == "leisure" and l[1] == "nature_reserve") or
(l[0] == "natural" and l[1] == "grassland") or
(l[0] == "landuse" and l[1] == "greenfield") or
(l[0] == "natural" and l[1] == "heath") or
(l[0] == "natural" and l[1] == "wetland") or
(l[0] == "leisure" and l[1] == "park") or
(l[0] == "building")
):
if int(l[4]) == 0: contrast = randrange(1, 4)
l[3] = l[3].replace("\n", "")
fnlines[curline] = l[0] + " " + l[1] + " " + l[2] + " " + l[3] + " " + str(contrast) + "\n"
curline = curline+1
lines = ""
for l in range(len(fnlines)):
lines = lines + fnlines[l]
with open(df, 'w') as textfile:
textfile.write(lines)
# Walk through the now existing data files and make sure we always pick the correct
# sources for every tile, thus evading previous edge detection errors
def _placeTileSources(self, mlat, mlng):
# The first tile gets to choose something for itself
if self._tile_v == 1 and self._tile_h == 1:
resstr = self._selectResources()
self._storeResourceInfo(1, 1, resstr)
# Start "raytracing"
# Initial reset
tv = self._tile_v
th = self._tile_h
# Start marching north
while tv < mlat+1:
restch = self._getResourceTouch(tv, th)
if "t" in restch:
resstr = self._getResourceInfo(tv, th)
if resstr == "0":
resstr = self._selectResources()
self._storeResourceInfo(tv, th, resstr)
resd = self._getResourceInfo(tv+1, th)
if resd=="0":
self._storeResourceInfo(tv + 1, th, resstr)
else:
break
tv = tv + 1
# Start marching east
tv = self._tile_v
th = self._tile_h
while th < mlng + 1:
restch = self._getResourceTouch(tv, th)
if "r" in restch:
resstr = self._getResourceInfo(tv, th)
if resstr == "0":
resstr = self._selectResources()
self._storeResourceInfo(tv, th, resstr)
resd = self._getResourceInfo(tv, th+1)
if resd == "0":
self._storeResourceInfo(tv, th + 1, resstr)
else:
break
th = th + 1
# Start marching south
tv = self._tile_v
th = self._tile_h
while tv > 0:
restch = self._getResourceTouch(tv, th)
if "b" in restch:
resstr = self._getResourceInfo(tv, th)
if resstr == "0":
resstr = self._selectResources()
self._storeResourceInfo(tv, th, resstr)
resd = self._getResourceInfo(tv - 1, th)
if resd == "0":
self._storeResourceInfo(tv - 1, th, resstr)
else:
break
tv = tv - 1
# Start marching west
tv = self._tile_v
th = self._tile_h
while th > 0:
restch = self._getResourceTouch(tv, th)
if "l" in restch:
resstr = self._getResourceInfo(tv, th)
if resstr == "0":
resstr = self._selectResources()
self._storeResourceInfo(tv, th, resstr)
resd = self._getResourceInfo(tv, th-1)
if resd == "0":
self._storeResourceInfo(tv, th - 1, resstr)
else:
break
th = th - 1

View File

@ -1,3 +1,4 @@
from random import randrange
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# ORTHOGRAPHIC # ORTHOGRAPHIC
@ -73,7 +74,7 @@ class mstr_xp_normalmap:
# Resize original # Resize original
image = image.resize((int(mstr_photores/4), int(mstr_photores/4)), Image.Resampling.BILINEAR) image = image.resize((int(mstr_photores/4), int(mstr_photores/4)), Image.Resampling.BILINEAR)
nmp = Image.new("RGBA", (image.width, image.height), (128,128,1,1)) nmp = Image.new("RGBA", (image.width, image.height), (128,128,0,0))
if water: nmp = Image.new("RGBA", (image.width, image.height), (128, 128, 255, 0)) if water: nmp = Image.new("RGBA", (image.width, image.height), (128, 128, 255, 0))
@ -83,48 +84,62 @@ class mstr_xp_normalmap:
# Let's try some shenanigans # Let's try some shenanigans
w = image.width w = image.width
h = image.height h = image.height
for y in range(h): if not water:
for x in range(w): for y in range(h):
p = org[x,y] for x in range(w):
if p[3] > 0: # Only do something if there is something to do in layer p = org[x,y]
# Neighboring pixels if p[3] > 0: # Only do something if there is something to do in layer
px_t = org[ self.clamp(x, w), self.clamp(y+1, h) ] # Neighboring pixels
px_tr = org[ self.clamp(x+1, w), self.clamp(y+1, h) ] px_t = org[ self.clamp(x, w), self.clamp(y+1, h) ]
px_r = org[ self.clamp(x+1, w), self.clamp(y, h) ] px_tr = org[ self.clamp(x+1, w), self.clamp(y+1, h) ]
px_br = org[ self.clamp(x+1, w), self.clamp(y+1, h) ] px_r = org[ self.clamp(x+1, w), self.clamp(y, h) ]
px_b = org[ self.clamp(x, w), self.clamp(y-1, h) ] px_br = org[ self.clamp(x+1, w), self.clamp(y+1, h) ]
px_bl = org[ self.clamp(x-1, w), self.clamp(y-1, h) ] px_b = org[ self.clamp(x, w), self.clamp(y-1, h) ]
px_l = org[ self.clamp(x-1, w), self.clamp(y, h) ] px_bl = org[ self.clamp(x-1, w), self.clamp(y-1, h) ]
px_tl = org[ self.clamp(x-1, w), self.clamp(y+1, h) ] px_l = org[ self.clamp(x-1, w), self.clamp(y, h) ]
px_tl = org[ self.clamp(x-1, w), self.clamp(y+1, h) ]
# Intensities of pixels # Intensities of pixels
it_t = self.intensity(px_t) it_t = self.intensity(px_t)
it_tr = self.intensity(px_tr) it_tr = self.intensity(px_tr)
it_r = self.intensity(px_r) it_r = self.intensity(px_r)
it_br = self.intensity(px_br) it_br = self.intensity(px_br)
it_b = self.intensity(px_b) it_b = self.intensity(px_b)
it_bl = self.intensity(px_bl) it_bl = self.intensity(px_bl)
it_l = self.intensity(px_l) it_l = self.intensity(px_l)
it_tl = self.intensity(px_tl) it_tl = self.intensity(px_tl)
# Sobel filter # Sobel filter
dx = (it_tr + 2.0 * it_r + it_br) - (it_tl + 2.0 * it_l + it_bl) dx = (it_tr + 2.0 * it_r + it_br) - (it_tl + 2.0 * it_l + it_bl)
dy = (it_bl + 2.0 * it_b + it_br) - (it_tl + 2.0 * it_t + it_tr) dy = (it_bl + 2.0 * it_b + it_br) - (it_tl + 2.0 * it_t + it_tr)
dz = 10 # This is usually a good value for strength dz = 10 # This is usually a good value for strength
v = (dx, dy, dz) v = (dx, dy, dz)
nrm = self.normalize_vector(v) nrm = self.normalize_vector(v)
if nrm[1] > 0:
nrm[1] = 0 - (abs(nrm[1]))
else:
nrm[1] = abs(nrm[1])
# Set pixel if nrm[1] > 0:
if water: nrm[1] = 0 - (abs(nrm[1]))
nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), int(self.map_component(nrm[2])), int(self.map_component(nrm[2]))) else:
if not water: nrm[1] = abs(nrm[1])
# Set pixel
nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), 255 - int(self.map_component(nrm[2])), 1) nmp_pix[x,y] = (int(self.map_component(nrm[0])), int(self.map_component(nrm[1])), 255 - int(self.map_component(nrm[2])), 1)
# Previous code produced bad and eerie looking lines at the border of the image. Let's have a go with this.
if water:
for y in range(h):
for x in range(w):
p = org[x,y]
if p[3] > 0:
wr = randrange(116, 141)
wg = randrange(111, 139)
wb = 255
if x == 0 or x == w-1: wb = 0
if y == 0 or y == h-1: wb = 0
wa = p[3]
c = (wr, wg, wb, wa)
nmp_pix[x,y] = c
mstr_msg("xp_normalmap", "[X-Plane] Normal map generated") mstr_msg("xp_normalmap", "[X-Plane] Normal map generated")
return nmp return nmp