Merge pull request #4 from NichiHachi/main

Highway map and skeleton
This commit is contained in:
Xeon0X
2024-06-13 16:42:43 +02:00
committed by GitHub
16 changed files with 499 additions and 102 deletions

View File

@@ -1,4 +1,7 @@
gdpc==7.1.0 gdpc==7.1.0
numpy==1.26.4 numpy==1.26.4
Pillow==10.3.0
pygame==2.5.2 pygame==2.5.2
scipy==1.13.0 scipy==1.13.1
skan==0.11.1
skimage==0.0

View File

@@ -1,4 +1,4 @@
from District import District, CustomDistrict, VoronoiDistrict from District import District
from Position import Position from Position import Position
from PIL import Image from PIL import Image
import random import random
@@ -33,13 +33,14 @@ class City:
watermap.close() watermap.close()
heightmap.close() heightmap.close()
def add_district(self, center: Position): def add_district(self, center: Position, district_type: str = ""):
""" """
Add a new district to the city. Add a new district to the city.
:param district_type:
:param center: The center position of the new district. :param center: The center position of the new district.
""" """
self.districts.append(CustomDistrict(len(self.districts) + 1, center)) self.districts.append(District(len(self.districts) + 1, center, district_type))
self.map_data[center.y][center.x] = len(self.districts) self.map_data[center.y][center.x] = len(self.districts)
def is_expend_finished(self): def is_expend_finished(self):
@@ -62,15 +63,15 @@ class City:
""" """
min_distance = point.distance_to(self.districts[index_district].center_expend) min_distance = point.distance_to(self.districts[index_district].center_expend)
index_district_chosen = index_district index_district_chosen = index_district
for i in range(index_district + 1, len(self.districts)): for index in range(index_district + 1, len(self.districts)):
if point in self.districts[i].area_expend: if point in self.districts[index].area_expend:
distance = point.distance_to(self.districts[i].center_expend) distance = point.distance_to(self.districts[index].center_expend)
if distance < min_distance: if distance < min_distance:
min_distance = distance min_distance = distance
self.districts[index_district_chosen].area_expend.remove(point) self.districts[index_district_chosen].area_expend.remove(point)
index_district_chosen = i index_district_chosen = index
else: else:
self.districts[i].area_expend.remove(point) self.districts[index].area_expend.remove(point)
self.districts[index_district_chosen].area.append(point) self.districts[index_district_chosen].area.append(point)
self.districts[index_district_chosen].area_expend_from_point.append(point) self.districts[index_district_chosen].area_expend_from_point.append(point)
self.districts[index_district_chosen].area_expend.remove(point) self.districts[index_district_chosen].area_expend.remove(point)
@@ -92,21 +93,19 @@ class City:
""" """
Loop the expansion of all districts in the city until all districts are fully expanded. Loop the expansion of all districts in the city until all districts are fully expanded.
""" """
loop_count = 0 print("[City] Start expanding districts...")
while not self.is_expend_finished(): while not self.is_expend_finished():
self.update_expend_district() self.update_expend_district()
loop_count += 1 print("[City] Finished expanding districts.")
if loop_count % 100 == 0:
print("[City] Loop count: ", loop_count)
def custom_district_draw_map(self): def district_draw_map(self):
""" """
Draw the map of the city with different colors for each district. Draw the map of the city with different colors for each district.
""" """
width, height = len(self.map_data[0]), len(self.map_data) width, height = len(self.map_data[0]), len(self.map_data)
img = Image.new('RGB', (width, height)) img = Image.new('RGB', (width, height))
colors = {i: (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) colors = {id_district: (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
for i in range(1, len(self.districts) + 1)} for id_district in range(1, len(self.districts) + 1)}
for y in range(height): for y in range(height):
for x in range(width): for x in range(width):
@@ -115,12 +114,13 @@ class City:
else: else:
img.putpixel((x, y), colors[self.map_data[y][x]]) img.putpixel((x, y), colors[self.map_data[y][x]])
img.save('./data/custom_district.png') img.save('./data/district.png')
print("[City] District map created.")
if __name__ == '__main__': if __name__ == '__main__':
city = City() city = City()
for i in range(10): for i in range(10):
city.add_district(Position(random.randint(0, 600), random.randint(0, 600))) city.add_district(Position(random.randint(0, 800), random.randint(0, 800)))
city.loop_expend_district() city.loop_expend_district()
city.custom_district_draw_map() city.district_draw_map()

View File

@@ -1,45 +1,6 @@
from Position import Position from Position import Position
class District: class District:
"""
The District class represents a district in the world.
A district can be characterized by its type and its unique id.
Attributes:
tile_id (int): The unique id of the district.
type (str): The type of the district. Can be "Forest", "City", "Mountain" or "Villa".
"""
def __init__(self, tile_id: int):
"""
The constructor for the District class.
:param tile_id: Unique id of the district (Must be greater than 0)
"""
if tile_id <= 0:
raise ValueError("Tile id must be greater than 0")
self.tile_id = tile_id
self.type = "" #Forest, City, Montain, Villa
def verify_point(point: Position, point_new: Position, map_data: list[list[int]], height_map: list[list[int]]):
"""
Function to verify if a new point can be added to a district extend area list.
:param point: The current point.
:param point_new: The new point to be verified.
:param map_data: The 2D list representing the map.
:param height_map: The 2D list representing the height map.
:return: True if the new point can be added, False otherwise.
"""
return (0 <= point_new.x < len(map_data[0]) and
0 <= point_new.y < len(map_data) and
map_data[point_new.y][point_new.x] == 0 and
abs(height_map[point_new.y][point_new.x] - height_map[point.y][point.x]) < 2)
class CustomDistrict(District):
""" """
The CustomDistrict class represents a district that can be expanded. The CustomDistrict class represents a district that can be expanded.
@@ -49,19 +10,39 @@ class CustomDistrict(District):
area_expend_from_point (list): The list of positions from which the district can expand. area_expend_from_point (list): The list of positions from which the district can expand.
area_expend (list): The list of positions to which the district will maybe expand. area_expend (list): The list of positions to which the district will maybe expand.
""" """
def __init__(self, tile_id: int, center: Position): def __init__(self, tile_id: int, center: Position, district_type: str = ""):
""" """
The constructor for the CustomDistrict class. The constructor for the District class.
:param tile_id: Unique id of the district (Must be greater than 0) :param tile_id: Unique id of the district (Must be greater than 0)
:param center: The center position from which the district expands. :param center: The center position from which the district expands.
:param district_type: The type of the district (Forest, City, Mountain, Villa)
""" """
super().__init__(tile_id) if tile_id <= 0:
raise ValueError("Tile id must be greater than 0")
self.tile_id = tile_id
self.type = district_type
self.center_expend = center self.center_expend = center
self.area = [center] self.area = [center]
self.area_expend_from_point = [center] self.area_expend_from_point = [center]
self.area_expend = [] self.area_expend = []
def verify_point(self, point: Position, point_new: Position, map_data: list[list[int]], height_map: list[list[int]]):
"""
Verify if a new point can be added to a district extend area list.
:param point: The current point.
:param point_new: The new point to be verified.
:param map_data: The 2D list representing the map.
:param height_map: The 2D list representing the height map.
:return: True if the new point can be added, False otherwise.
"""
return (0 <= point_new.x < len(map_data[0]) and
0 <= point_new.y < len(map_data) and
map_data[point_new.y][point_new.x] == 0 and
(self.type == "Mountain" or
abs(height_map[point_new.y][point_new.x] - height_map[point.y][point.x]) < 2))
def update_expend_points(self, point: Position, map_data: list[list[int]], height_map: list[list[int]]): def update_expend_points(self, point: Position, map_data: list[list[int]], height_map: list[list[int]]):
""" """
Update the points to which the district can expand. Update the points to which the district can expand.
@@ -71,20 +52,7 @@ class CustomDistrict(District):
:param height_map: The 2D list representing the height map. :param height_map: The 2D list representing the height map.
""" """
for pos in [Position(1, 0), Position(-1, 0), Position(0, 1), Position(0, -1)]: for pos in [Position(1, 0), Position(-1, 0), Position(0, 1), Position(0, -1)]:
if verify_point(point, point + pos, map_data, height_map): if self.verify_point(point, point + pos, map_data, height_map):
if point + pos not in self.area_expend: if point + pos not in self.area_expend:
self.area_expend.append(point + pos) self.area_expend.append(point + pos)
self.area_expend_from_point.remove(point) self.area_expend_from_point.remove(point)
class Edge: #I'm Edging rn
def __init__(self, point1, point2):
self.point1 = point1
self.point2 = point2
class VoronoiDistrict(District):
def __init__(self, tile_id: int, center: Position):
super().__init__(tile_id)
self.center = center
self.edges = []

215
world_maker/Skeleton.py Normal file
View File

@@ -0,0 +1,215 @@
import numpy as np
#import skan
from skimage.morphology import skeletonize
from skan.csr import skeleton_to_csgraph
from collections import Counter
from PIL import Image
import random
class Skeleton:
def __init__(self, data: np.ndarray = None):
self.lines = []
self.intersections = []
self.centers = []
self.coordinates = []
self.graph = None
if data is not None:
self.set_skeleton(data)
def set_skeleton(self, data: np.ndarray):
print("[Skeleton] Start skeletonization...")
binary_skeleton = skeletonize(data, method="lee")
graph, coordinates = skeleton_to_csgraph(binary_skeleton)
self.graph = graph.tocoo()
# List of lists. Inverted coordinates.
coordinates = list(coordinates)
# print(coordinates)
for i in range(len(coordinates)):
coordinates[i] = list(coordinates[i])
# print(coordinates)
for i in range(len(coordinates[0])):
# print((coordinates[0][i], coordinates[1][i], coordinates[2][i]))
self.coordinates.append((coordinates[0][i], coordinates[1][i], coordinates[2][i]))
print("[Skeleton] Skeletonization completed.")
def find_next_elements(self, key: str) -> list:
"""Find the very nearest elements"""
line = []
values = np.array(self.graph.row)
indices = np.where(values == key)[0]
for i in range(len(indices)):
if self.graph.row[indices[i]] == key:
line.append(self.graph.col[indices[i]])
return line
def find_line(self, key: str):
next_keys = self.find_next_elements(key)
if len(next_keys) >= 3: # Intersections.
return next_keys
if len(next_keys) == 2 or len(next_keys) == 1: # In line or endpoints.
line = [key]
line.insert(0, next_keys[0])
if len(next_keys) == 2:
line.insert(len(line), next_keys[1])
next_keys = line[0], line[-1]
while len(next_keys) == 2 or len(next_keys) == 1:
extremity = []
for key in next_keys:
next_keys = self.find_next_elements(key)
if len(next_keys) <= 2:
# Add the neighbors that is not already in the line.
for element in next_keys:
if element not in line:
extremity.append(element)
line.append(element)
if len(next_keys) >= 3:
# Add the intersection only.
extremity.append(key)
next_keys = []
for key in extremity:
ends = self.find_next_elements(key)
if len(ends) == 2:
next_keys.append(key)
return line
def parse_graph(self, parse_orphan: bool = False):
print("[Skeleton] Start parsing the graph", ("with orphans" if parse_orphan else "") + "...")
for key, value in sorted(
Counter(self.graph.row).items(), key=lambda kv: kv[1], reverse=True
):
# Start from the biggest intersections.
if value != 2: # We don't want to be in the middle of a line.
line = self.find_line(key)
# We have now all the connected points if it's an
# intersection. We need to find the line.
if value != 1:
# It's not an endpoint.
self.centers.append(key)
self.intersections.append(line)
for i in line:
line = self.find_line(i)
if i in line:
# The key is inside the result : it's a line.
already_inside = False
for l in self.lines:
# Verification if not already inside.
if Counter(l) == Counter(line):
already_inside = True
# print(line, "inside", lines)
if not already_inside:
self.lines.append(line)
else:
# The key is not inside the result, it's an
# intersection directly connected to the key.
line = [key, i]
already_inside = False
for l in self.lines:
# Verification if not already inside.
if Counter(l) == Counter(line):
already_inside = True
# print(line, "inside", lines)
if not already_inside:
self.lines.append(line)
elif value == 2 and parse_orphan:
line = self.find_line(key)
already_inside = False
for l in self.lines:
# Verification if not already inside.
if Counter(l) == Counter(line):
already_inside = True
if not already_inside:
self.lines.append(line)
print("[Skeleton] Graph parsing completed.")
def map(self) -> Image:
"""
Generate an image to visualize 2D path of the skeleton.
Returns:
image: 2D path of the skeleton on top of the heightmap.
"""
print("[Skeleton] Start mapping the skeleton...")
# editor = Editor()
# buildArea = editor.getBuildArea()
# buildRect = buildArea.toRect()
# xzStart = buildRect.begin
# xzDistance = (max(buildRect.end[0], buildRect.begin[0]) - min(buildRect.end[0], buildRect.begin[0]),
# max(buildRect.end[1], buildRect.begin[1]) - min(buildRect.end[1], buildRect.begin[1]))
heightmap = Image.open("data/heightmap.png").convert('RGB')
# roadsArea = Image.new("L", xzDistance, 0)
# width, height = heightmap.size
# Lines
for i in range(len(self.lines)):
r, g, b = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
for j in range(len(self.lines[i])):
z = self.coordinates[self.lines[i][j]][0]
# y = self.coordinates[self.lines[i][j]][1]
x = self.coordinates[self.lines[i][j]][2]
heightmap.putpixel(
(
int(z),
int(x),
),
(r + j, g + j, b + j),
)
# roadsArea.putpixel(
# (
# int(z),
# int(x),
# ),
# (255),
# )
# Centers
for i in range(len(self.centers)):
# print(self.coordinates[self.centers[i]])
heightmap.putpixel(
(int(self.coordinates[self.centers[i]][0]), int(self.coordinates[self.centers[i]][2])),
(255, 255, 0),
)
# roadsArea.putpixel(
# (int(self.coordinates[self.centers[i]][0]), int(self.coordinates[self.centers[i]][2])),
# (255),
# )
# # Intersections
# for i in range(len(self.intersections)):
# intersection = []
# for j in range(len(self.intersections[i])):
# intersection.append(self.coordinates[self.intersections[i][j]])
# for i in range(len(intersection)):
# heightmap.putpixel(
# (int(self.intersections[i][2]), int(self.intersections[i][0])),
# (255, 0, 255),
# )
print("[Skeleton] Mapping completed.")
return heightmap # , roadsArea

View File

@@ -111,7 +111,7 @@ class World:
buildRect = buildArea.toRect() buildRect = buildArea.toRect()
xzStart = buildRect.begin xzStart = buildRect.begin
print(xzStart, "xzStart") print("[World]", '('+str(xzStart[0])+', '+str(xzStart[1])+')', "xzStart")
xzDistance = (max(buildRect.end[0], buildRect.begin[0]) - min(buildRect.end[0], buildRect.begin[0]), xzDistance = (max(buildRect.end[0], buildRect.begin[0]) - min(buildRect.end[0], buildRect.begin[0]),
max(buildRect.end[1], buildRect.begin[1]) - min(buildRect.end[1], buildRect.begin[1])) max(buildRect.end[1], buildRect.begin[1]) - min(buildRect.end[1], buildRect.begin[1]))
watermap = Image.new("L", xzDistance, 0) watermap = Image.new("L", xzDistance, 0)
@@ -125,12 +125,9 @@ class World:
for x in range(0, xzDistance[0]): for x in range(0, xzDistance[0]):
for z in range(0, xzDistance[1]): for z in range(0, xzDistance[1]):
y = heightmapData[x][z] - 1 y = heightmapData[x][z] - 1
yTree = treesmapData[x][z] - 1 yTree = treesmapData[x][z] - 1
print('getData', xzStart[0] + x, y, xzStart[1] + z)
biome = slice.getBiome((x, y, z)) biome = slice.getBiome((x, y, z))
block = slice.getBlock((x, y, z)) block = slice.getBlock((x, y, z))
maybeATree = slice.getBlock((x, yTree, z)) maybeATree = slice.getBlock((x, yTree, z))
@@ -145,11 +142,11 @@ class World:
number = 0 number = 0
for i in range(-1, 2): for i in range(-1, 2):
for j in range(-1, 2): for j in range(-1, 2):
if (i != 0 or j != 0): if i != 0 or j != 0:
if (0 <= x + i < xzDistance[0]) and (0 <= z + j < xzDistance[1]): if (0 <= x + i < xzDistance[0]) and (0 <= z + j < xzDistance[1]):
k = heightmapData[x + i][z + j] - 1 k = heightmapData[x + i][z + j] - 1
print('getData for tree', xzStart[0] + x + i, k, xzStart[1] + z + j) # print('getData for tree', xzStart[0] + x + i, k, xzStart[1] + z + j)
blockNeighbor = slice.getBlock((x + i, k, z + j)) blockNeighbor = slice.getBlock((x + i, k, z + j))
if blockNeighbor.id not in lookup.TREES: if blockNeighbor.id not in lookup.TREES:
@@ -157,20 +154,19 @@ class World:
number += 1 number += 1
if number != 0: if number != 0:
average = round(height / number) average = round(height / number)
print(average, "average") # print(average, "average")
heightmap.putpixel((x, z), (average, average, average)) heightmap.putpixel((x, z), (average, average, average))
if ((biome in waterBiomes) or (block.id in waterBlocks)): if (biome in waterBiomes) or (block.id in waterBlocks):
watermap.putpixel((x, z), (255)) watermap.putpixel((x, z), 255)
else: else:
watermap.putpixel((x, z), (0)) watermap.putpixel((x, z), 0)
self.addBlocks([Block((xzStart[0] + x, 100, xzStart[1] + z), block)]) # y set to 100 for 2D self.addBlocks([Block((xzStart[0] + x, 100, xzStart[1] + z), block)]) # y set to 100 for 2D
return heightmap, watermap, treesmap return heightmap, watermap, treesmap
def propagate(self, coordinates, scanned=[]): def propagate(self, coordinates, scanned=[]):
print('propagate', coordinates)
i = 0 i = 0
editor = Editor(buffering=True) editor = Editor(buffering=True)
if self.isInVolume(coordinates): if self.isInVolume(coordinates):
@@ -190,7 +186,7 @@ class World:
for y in range(self.length_y): for y in range(self.length_y):
binaryImage[x].append([]) binaryImage[x].append([])
for z in range(self.length_z): for z in range(self.length_z):
if (self.volume[x][y][z] != None): if self.volume[x][y][z] != None:
binaryImage[x][y].append(True) binaryImage[x][y].append(True)
else: else:
binaryImage[x][y].append(False) binaryImage[x][y].append(False)
@@ -222,7 +218,7 @@ class World:
for x in range(0, xzDistance[0]): for x in range(0, xzDistance[0]):
for z in range(0, xzDistance[1]): for z in range(0, xzDistance[1]):
y = heightmapData[x][z] - 1 y = heightmapData[x][z] - 1
if mask.getpixel((x, z)) == (255): if mask.getpixel((x, z)) == 255:
self.removeBlock((x, 100, z)) # y set to 100 for 2D self.removeBlock((x, 100, z)) # y set to 100 for 2D
def simplifyVolume(self): def simplifyVolume(self):

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,223 @@
import World
from PIL import Image, ImageFilter
import numpy as np
from scipy import ndimage
from Skeleton import Skeleton
from typing import Union
def get_data(world: World):
print("[Data Analysis] Generating data...")
heightmap, watermap, treemap = world.getData()
heightmap.save('./data/heightmap.png')
watermap.save('./data/watermap.png')
treemap.save('./data/treemap.png')
print("[Data Analysis] Data generated.")
return heightmap, watermap, treemap
def handle_import_image(image: Union[str, Image]) -> Image:
if isinstance(image, str):
return Image.open(image)
return image
def filter_negative(image: Image) -> Image:
"""
Invert the colors of an image.
Args:
image (image): image to filter
"""
return Image.fromarray(np.invert(np.array(image)))
def filter_sobel(image: Union[str, Image]) -> Image:
"""
Edge detection algorithms from an image.
Args:
image (image): image to filter
"""
# Open the image
image = handle_import_image(image).convert('RGB')
img = np.array(image).astype(np.uint8)
# Apply gray scale
gray_img = np.round(
0.299 * img[:, :, 0] + 0.587 * img[:, :, 1] + 0.114 * img[:, :, 2]
).astype(np.uint8)
# Sobel Operator
h, w = gray_img.shape
# define filters
horizontal = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) # s2
vertical = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]]) # s1
# define images with 0s
newhorizontalImage = np.zeros((h, w))
newverticalImage = np.zeros((h, w))
newgradientImage = np.zeros((h, w))
# offset by 1
for i in range(1, h - 1):
for j in range(1, w - 1):
horizontalGrad = (
(horizontal[0, 0] * gray_img[i - 1, j - 1])
+ (horizontal[0, 1] * gray_img[i - 1, j])
+ (horizontal[0, 2] * gray_img[i - 1, j + 1])
+ (horizontal[1, 0] * gray_img[i, j - 1])
+ (horizontal[1, 1] * gray_img[i, j])
+ (horizontal[1, 2] * gray_img[i, j + 1])
+ (horizontal[2, 0] * gray_img[i + 1, j - 1])
+ (horizontal[2, 1] * gray_img[i + 1, j])
+ (horizontal[2, 2] * gray_img[i + 1, j + 1])
)
newhorizontalImage[i - 1, j - 1] = abs(horizontalGrad)
verticalGrad = (
(vertical[0, 0] * gray_img[i - 1, j - 1])
+ (vertical[0, 1] * gray_img[i - 1, j])
+ (vertical[0, 2] * gray_img[i - 1, j + 1])
+ (vertical[1, 0] * gray_img[i, j - 1])
+ (vertical[1, 1] * gray_img[i, j])
+ (vertical[1, 2] * gray_img[i, j + 1])
+ (vertical[2, 0] * gray_img[i + 1, j - 1])
+ (vertical[2, 1] * gray_img[i + 1, j])
+ (vertical[2, 2] * gray_img[i + 1, j + 1])
)
newverticalImage[i - 1, j - 1] = abs(verticalGrad)
# Edge Magnitude
mag = np.sqrt(pow(horizontalGrad, 2.0) + pow(verticalGrad, 2.0))
newgradientImage[i - 1, j - 1] = mag
image = Image.fromarray(newgradientImage)
image = image.convert("L")
return image
def filter_smooth(image: Union[str, Image], radius: int = 3):
"""
:param image: white and black image representing the derivative of the terrain (sobel), where black is flat and white is very steep.
:param radius: Radius of the Gaussian blur.
Returns:
image: black or white image, with black as flat areas to be skeletonized
"""
image = handle_import_image(image)
# image = image.filter(ImageFilter.SMOOTH_MORE)
# image = image.filter(ImageFilter.SMOOTH_MORE)
# image = image.filter(ImageFilter.SMOOTH_MORE)
image = image.convert('L')
image = image.filter(ImageFilter.GaussianBlur(radius))
array = np.array(image)
bool_array = array > 7
# bool_array = ndimage.binary_opening(bool_array, structure=np.ones((3,3)), iterations=1)
# bool_array = ndimage.binary_closing(bool_array, structure=np.ones((3,3)), iterations=1)
# bool_array = ndimage.binary_opening(bool_array, structure=np.ones((5,5)), iterations=1)
# bool_array = ndimage.binary_closing(bool_array, structure=np.ones((5,5)), iterations=1)
# bool_array = ndimage.binary_opening(bool_array, structure=np.ones((7,7)), iterations=1)
# bool_array = ndimage.binary_closing(bool_array, structure=np.ones((7,7)), iterations=1)
return Image.fromarray(bool_array)
def subtract_map(image: Union[str, Image], substractImage: Union[str, Image]) -> Image:
image = handle_import_image(image)
substractImage = handle_import_image(substractImage).convert('L')
array_heightmap = np.array(image)
array_substractImage = np.array(substractImage)
mask = array_substractImage == 255
array_heightmap[mask] = 0
return Image.fromarray(array_heightmap)
def group_map(image1: Union[str, Image], image2: Union[str, Image]) -> Image:
image1 = handle_import_image(image1)
image2 = handle_import_image(image2)
array1 = np.array(image1)
array2 = np.array(image2)
mask = array1 == 255
array2[mask] = 255
return Image.fromarray(array2)
def filter_smooth_array(array: np.ndarray, radius: int = 3) -> np.ndarray:
image = Image.fromarray(array)
smooth_image = filter_smooth(image, radius)
array = np.array(smooth_image)
return array
def filter_remove_details(image: Union[str, Image], n: int = 20) -> Image:
image = handle_import_image(image)
array = np.array(image)
for _ in range(n):
array = ndimage.binary_dilation(array, iterations=4)
array = ndimage.binary_erosion(array, iterations=5)
array = filter_smooth_array(array, 2)
array = ndimage.binary_erosion(array, iterations=3)
image = Image.fromarray(array)
return image
def highway_map() -> Image:
print("[Data Analysis] Generating highway map...")
smooth_sobel = filter_smooth("./data/sobelmap.png", 1)
negative_smooth_sobel = filter_negative(smooth_sobel)
negative_smooth_sobel_water = subtract_map(negative_smooth_sobel, './data/watermap.png')
array_sobel_water = np.array(negative_smooth_sobel_water)
array_sobel_water = ndimage.binary_erosion(array_sobel_water, iterations=12)
array_sobel_water = ndimage.binary_dilation(array_sobel_water, iterations=5)
array_sobel_water = filter_smooth_array(array_sobel_water, 5)
array_sobel_water = ndimage.binary_erosion(array_sobel_water, iterations=20)
array_sobel_water = filter_smooth_array(array_sobel_water, 6)
image = Image.fromarray(array_sobel_water)
image_no_details = filter_remove_details(image, 15)
image_no_details.save('./data/highwaymap.png')
print("[Data Analysis] Highway map generated.")
return image_no_details
def create_volume(surface: np.ndarray, heightmap: np.ndarray, make_it_flat: bool = False) -> np.ndarray:
volume = np.full((len(surface), 255, len(surface[0])), False)
for z in range(len(surface)):
for x in range(len(surface[0])):
if not make_it_flat:
volume[x][heightmap[z][x]][z] = surface[z][x]
else:
volume[x][0][z] = surface[z][x]
return volume
def convert_2D_to_3D(image: Union[str, Image], make_it_flat: bool = False) -> np.ndarray:
image = handle_import_image(image)
heightmap = Image.open('./data/heightmap.png').convert('L')
heightmap = np.array(heightmap)
surface = np.array(image)
volume = create_volume(surface, heightmap, make_it_flat)
return volume
def skeleton_highway_map(image: Union[str, Image] = './data/highwaymap.png'):
image_array = convert_2D_to_3D(image, True)
skeleton = Skeleton(image_array)
skeleton.parse_graph(True)
heightmap_skeleton = skeleton.map()
heightmap_skeleton.save('./data/skeleton_highway.png')

View File

@@ -1,17 +1,9 @@
import World import World
from PIL import Image
from data_analysis import get_data, highway_map, filter_sobel, skeleton_highway_map
def get_data(world: World):
heightmap, watermap, treemap = world.getData()
heightmap.save('./data/heightmap.png')
watermap.save('./data/watermap.png')
treemap.save('./data/treemap.png')
def main():
world = World.World()
get_data(world)
if __name__ == '__main__': if __name__ == '__main__':
main() world = World.World()
heightmap, watermap, treemap = get_data(world)
filter_sobel("./data/heightmap.png").save('./data/sobelmap.png')
skeleton_highway_map(highway_map())