diff --git a/scripts/python/map_generator/.gitignore b/scripts/python/map_generator/.gitignore new file mode 100644 index 00000000..a9ed7789 --- /dev/null +++ b/scripts/python/map_generator/.gitignore @@ -0,0 +1,3 @@ +Syria +Test +Caucasus \ No newline at end of file diff --git a/scripts/python/map_generator/.vscode/launch.json b/scripts/python/map_generator/.vscode/launch.json index c1bbc766..8f7ecc60 100644 --- a/scripts/python/map_generator/.vscode/launch.json +++ b/scripts/python/map_generator/.vscode/launch.json @@ -5,12 +5,20 @@ "version": "0.2.0", "configurations": [ { - "name": "Python: Current File", + "name": "Main", "type": "python", "request": "launch", "program": "main.py", "console": "integratedTerminal", "args": ["./configs/Test/MediumResolution.yml"] + }, + { + "name": "Convert", + "type": "python", + "request": "launch", + "program": "convert_to_jpg.py", + "console": "integratedTerminal", + "args": ["./Syria/tiles", "./Syria/jpg"] } ] } \ No newline at end of file diff --git a/scripts/python/map_generator/convert_to_jpg.py b/scripts/python/map_generator/convert_to_jpg.py new file mode 100644 index 00000000..0d3dbefb --- /dev/null +++ b/scripts/python/map_generator/convert_to_jpg.py @@ -0,0 +1,98 @@ +import sys +import os +import numpy + +from PIL import Image +from concurrent import futures +from os import listdir +from os.path import isfile, isdir, join + +# global counters +fut_counter = 0 +tot_futs = 0 + +def printProgressBar(iteration, total, prefix = '', suffix = '', decimals = 1, length = 100, fill = '█', printEnd = "\r"): + percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total))) + filledLength = int(length * iteration // total) + bar = fill * filledLength + '-' * (length - filledLength) + print(f'\r{prefix} |{bar}| {percent}% {suffix}', end = printEnd) + # Print New Line on Complete + if iteration == total: + print() + +def done_callback(fut): + global fut_counter, tot_futs + fut_counter += 1 + printProgressBar(fut_counter, tot_futs) + +def remove_black_areas(im): + data = numpy.array(im) + red, green, blue = data.T + + # If present, remove any "black" areas + background_areas = (red < 10) & (blue < 10) & (green < 10) + data[..., :][background_areas.T] = (221, 221, 221) + + return Image.fromarray(data) + +def convert_tiles(source, dest, tile): + zoom = tile[0] + X = tile[1] + Y = tile[2] + + if not os.path.exists(os.path.join(dest, str(zoom))): + try: + os.mkdir(os.path.join(dest, str(zoom))) + except FileExistsError: + # Ignore this error, it means one other thread has already created the folder + pass + except Exception as e: + raise e + + if not os.path.exists(os.path.join(dest, str(zoom), str(X))): + try: + os.mkdir(os.path.join(dest, str(zoom), str(X))) + except FileExistsError: + # Ignore this error, it means one other thread has already created the folder + pass + except Exception as e: + raise e + + remove_black_areas(Image.open(os.path.join(source, str(zoom), str(X), f"{Y}.png")).convert('RGB')).save(os.path.join(dest, str(zoom), str(X), f"{Y}.jpg")) + +if len(sys.argv) < 3: + print("Please provide a source and a destination folder") +else: + source = sys.argv[1] + dest = sys.argv[2] + + if not os.path.exists(dest): + try: + os.mkdir(dest) + except FileExistsError: + # Ignore this error, it means one other thread has already created the folder + pass + except Exception as e: + raise e + + print(f"Listing source tiles...") + existing_tiles = [] + zooms = [int(f) for f in listdir(source) if isdir(join(source, f))] + for zoom in zooms: + Xs = [int(f) for f in listdir(join(source, str(zoom))) if isdir(join(source, str(zoom), f))] + for X in Xs: + Ys = [int(f.removesuffix(".png")) for f in listdir(os.path.join(source, str(zoom), str(X))) if isfile(join(source, str(zoom), str(X), f))] + for Y in Ys: + existing_tiles.append((zoom, X, Y)) + + print(f"{len(existing_tiles)} tiles will be converted") + + # Merge the tiles with parallel thread execution + with futures.ThreadPoolExecutor() as executor: + print(f"Converting tiles to jpg...") + print(f"Initializing exectuion pool") + futs = [executor.submit(convert_tiles, source, dest, tile) for tile in existing_tiles] + tot_futs = len(futs) + fut_counter = 0 + [fut.add_done_callback(done_callback) for fut in futs] + [fut.result() for fut in futures.as_completed(futs)] diff --git a/scripts/python/map_generator/map_generator.py b/scripts/python/map_generator/map_generator.py index 01b6b7e5..def931d5 100644 --- a/scripts/python/map_generator/map_generator.py +++ b/scripts/python/map_generator/map_generator.py @@ -86,10 +86,10 @@ def extract_tiles(n, screenshots_XY, params): os.mkdir(os.path.join(output_directory, "tiles", str(zoom), str(X))) except FileExistsError: # Ignore this error, it means one other thread has already created the folder - continue + pass except Exception as e: raise e - img.crop(box).save(os.path.join(output_directory, "tiles", str(zoom), str(X), f"{Y}.jpg")) + img.crop(box).convert('RGBA').save(os.path.join(output_directory, "tiles", str(zoom), str(X), f"{Y}.png")) n += 1 else: @@ -100,21 +100,17 @@ def merge_tiles(base_path, zoom, tile): Y = tile[1] # If the image already exists, open it so we can paste the higher quality data in it - if os.path.exists(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.jpg")): - dst = Image.open(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.jpg")) - dst.putalpha(255) - dst = make_background_transparent(dst) + if os.path.exists(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.png")): + dst = Image.open(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.png")) else: - dst = Image.new('RGBA', (256, 256), (221, 221, 221, 255)) + dst = Image.new('RGBA', (256, 256), (0, 0, 0, 0)) # Loop on all the 4 subtiles in the tile positions = [(0, 0), (0, 1), (1, 0), (1, 1)] for i in range(0, 4): # Open the subtile, if it exists, and resize it down to 128x128 - if os.path.exists(os.path.join(base_path, str(zoom), str(2*X + positions[i][0]), f"{2*Y + positions[i][1]}.jpg")): - im = Image.open(os.path.join(base_path, str(zoom), str(2*X + positions[i][0]), f"{2*Y + positions[i][1]}.jpg")).resize((128, 128)) - im.putalpha(255) - im = make_background_transparent(im) + if os.path.exists(os.path.join(base_path, str(zoom), str(2*X + positions[i][0]), f"{2*Y + positions[i][1]}.png")): + im = Image.open(os.path.join(base_path, str(zoom), str(2*X + positions[i][0]), f"{2*Y + positions[i][1]}.png")).resize((128, 128)) dst.paste(im, (positions[i][0] * 128, positions[i][1] * 128), im) # Create the output folder if it exists @@ -128,7 +124,7 @@ def merge_tiles(base_path, zoom, tile): raise e # Save the image - remove_black_areas(dst.convert('RGB')).save(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.jpg"), quality=98) + dst.save(os.path.join(base_path, str(zoom - 1), str(X), f"{Y}.png"), quality=98) def computeCorrectionFactor(XY, n_width, n_height, map_config, zoom, output_directory, port): # Take screenshots at the given position, then east and south of it @@ -216,26 +212,6 @@ def takeScreenshot(XY, n_width, n_height, map_config, zoom, output_directory, f, # Rotate, resize and save the screenshot screenshot.rotate(math.degrees(geo_data['northRotation'])).resize((int(sx * screenshot.width) + correction[0], int(sy * screenshot.height)+ correction[1] )).save(os.path.join(output_directory, "screenshots", f"{f}_{n}_{zoom}.jpg"), quality=98) -def make_background_transparent(im): - data = numpy.array(im) - red, green, blue, alpha = data.T - - # If present, remove any "background" areas - background_areas = (red > 211) & (red < 231) & (green > 211) & (green < 231) & (blue > 211) & (blue < 231) - data[..., :][background_areas.T] = (0, 0, 0, 0) # make transparent - - return Image.fromarray(data) - -def remove_black_areas(im): - data = numpy.array(im) - red, green, blue = data.T - - # If present, remove any "black" areas - background_areas = (red < 10) & (blue < 10) & (green < 10) - data[..., :][background_areas.T] = (221, 221, 221) - - return Image.fromarray(data) - def run(map_config, port): global tot_futs, fut_counter @@ -262,6 +238,12 @@ def run(map_config, port): usable_width = screen_config['width'] - 400 # Keep a margin around the center usable_height = screen_config['height'] - 400 # Keep a margin around the center + existing_zoom_levels = [int(f) for f in listdir(os.path.join(output_directory, "tiles")) if isdir(join(output_directory, "tiles", f))] + if len(existing_zoom_levels) == 0: + final_level = 1 + else: + final_level = max(existing_zoom_levels) + with open(map_config['boundary_file'], 'rt', encoding="utf-8") as bp: # Read the config file doc = bp.read() @@ -355,12 +337,15 @@ def run(map_config, port): print(f"Feature {f} of {len(features)} completed!") f += 1 + if zoom <= final_level: + final_level = int(input(f"Zoom level already exists. Starting from level {zoom}, please enter desired final zoom level: ")) + ########### Assemble tiles to get lower zoom levels - for current_zoom in range(zoom, 8, -1): + for current_zoom in range(zoom, final_level, -1): Xs = [int(d) for d in listdir(os.path.join(output_directory, "tiles", str(current_zoom))) if isdir(join(output_directory, "tiles", str(current_zoom), d))] existing_tiles = [] for X in Xs: - Ys = [int(f.removesuffix(".jpg")) for f in listdir(os.path.join(output_directory, "tiles", str(current_zoom), str(X))) if isfile(join(output_directory, "tiles", str(current_zoom), str(X), f))] + Ys = [int(f.removesuffix(".png")) for f in listdir(os.path.join(output_directory, "tiles", str(current_zoom), str(X))) if isfile(join(output_directory, "tiles", str(current_zoom), str(X), f))] for Y in Ys: existing_tiles.append((X, Y)) @@ -368,7 +353,7 @@ def run(map_config, port): for tile in existing_tiles: if (int(tile[0] / 2), int(tile[1] / 2)) not in tiles_to_produce: tiles_to_produce.append((int(tile[0] / 2), int(tile[1] / 2))) - + # Merge the tiles with parallel thread execution with futures.ThreadPoolExecutor() as executor: print(f"Merging tiles for zoom level {current_zoom - 1}...")