-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlayout_generator.py
More file actions
294 lines (237 loc) · 6.92 KB
/
layout_generator.py
File metadata and controls
294 lines (237 loc) · 6.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import matplotlib.pyplot as plt
# site measurements
SITE_WIDTH=200
SITE_HEIGHT=140
# building measurements
TOWER_A={
"width":30,
"height":20
}
TOWER_B={
"width":20,
"height":20
}
# creating a function to place build. randomly on the site
import random
def place_building(building_type):
if building_type == "A":
width = TOWER_A["width"]
height = TOWER_A["height"]
else:
width = TOWER_B["width"]
height = TOWER_B["height"]
margin = 10 # distance from the boundary of the site
x = random.uniform(margin, SITE_WIDTH - width-margin)
y = random.uniform(margin, SITE_HEIGHT - height-margin)
building = {
"type": building_type,
"x": x,
"y": y,
"width": width,
"height": height
}
return building
def buildings_valid(b1, b2, min_distance=15):
# edges of build. 1
left1 = b1["x"]
right1 = b1["x"] + b1["width"]
bottom1 = b1["y"]
top1 = b1["y"] + b1["height"]
# edges of build. 2
left2 = b2["x"]
right2 = b2["x"] + b2["width"]
bottom2 = b2["y"]
top2 = b2["y"] + b2["height"]
# horizontal gap
if right1 < left2:
gap_x = left2 - right1
elif right2 < left1:
gap_x = left1 - right2
else:
gap_x = 0 # overlap
# vertical gap
if top1 < bottom2:
gap_y = bottom2 - top1
elif top2 < bottom1:
gap_y = bottom1 - top2
else:
gap_y = 0 # overlap
# min edge 2 edge distance
distance = max(gap_x, gap_y)
return distance >= min_distance
# Central plaza
PLAZA_SIZE = 40
PLAZA_X = (SITE_WIDTH / 2) - (PLAZA_SIZE / 2)
PLAZA_Y = (SITE_HEIGHT / 2) - (PLAZA_SIZE / 2)
def plaza(building):
# building edges
b_left = building["x"]
b_right = building["x"] + building["width"]
b_bottom = building["y"]
b_top = building["y"] + building["height"]
# plaza edges
p_left = PLAZA_X
p_right = PLAZA_X + PLAZA_SIZE
p_bottom = PLAZA_Y
p_top = PLAZA_Y + PLAZA_SIZE
# check overlap
if b_right <= p_left or b_left >= p_right:
return False # no overlap left and right
if b_top <= p_bottom or b_bottom >= p_top:
return False # no overlap up and down
return True # overlap happened
# trying to place a new build
def is_valid_building(building, existing_buildings):
# checking that it does not overlap plaza
if plaza(building):
return False
# checking if far enough from all existing build
for other in existing_buildings:
if not buildings_valid(building, other):
return False
return True
def generate_layout(num_A, num_B, max_attempts=1000):
layout = []
# Place Tower A buildings
for _ in range(num_A):
placed = False
attempts = 0
while not placed and attempts < max_attempts:
b = place_building("A")
if is_valid_building(b, layout):
layout.append(b)
placed = True
attempts += 1
if not placed:
return None # failed to generate valid layout
# Place Tower B buildings
for _ in range(num_B):
placed = False
attempts = 0
while not placed and attempts < max_attempts:
b = place_building("B")
if is_valid_building(b, layout):
layout.append(b)
placed = True
attempts += 1
if not placed:
return None
return layout
# final rule
def check_tower_A_neighbors(layout, max_distance=60):
towers_A = [b for b in layout if b["type"] == "A"]
towers_B = [b for b in layout if b["type"] == "B"]
for a in towers_A:
has_neighbor = False
for b in towers_B:
# existing distance logic
if buildings_valid(a, b, min_distance=0):
# check if within 60m
left_a = a["x"]
right_a = a["x"] + a["width"]
bottom_a = a["y"]
top_a = a["y"] + a["height"]
left_b = b["x"]
right_b = b["x"] + b["width"]
bottom_b = b["y"]
top_b = b["y"] + b["height"]
# compute gap
if right_a < left_b:
gap_x = left_b - right_a
elif right_b < left_a:
gap_x = left_a - right_b
else:
gap_x = 0
if top_a < bottom_b:
gap_y = bottom_b - top_a
elif top_b < bottom_a:
gap_y = bottom_a - top_b
else:
gap_y = 0
distance = max(gap_x, gap_y)
if distance <= max_distance:
has_neighbor = True
break
if not has_neighbor:
return False
return True
def layout_stats(layout):
total_area = 0
for b in layout:
total_area += b["width"] * b["height"]
return {
"Total buildings": len(layout),
"Built-up area (sq.m)": total_area
}
stats = layout_stats(layout)
print(stats)
def visualize_layout(layout, title="Layout Visualization"):
fig, ax = plt.subplots(figsize=(10, 7))
# site boundary
ax.add_patch(
plt.Rectangle(
(0, 0),
SITE_WIDTH,
SITE_HEIGHT,
fill=False,
edgecolor="black",
linewidth=2
)
)
# central plaza
ax.add_patch(
plt.Rectangle(
(PLAZA_X, PLAZA_Y),
PLAZA_SIZE,
PLAZA_SIZE,
color="lightgrey",
alpha=0.7,
label="Central Plaza"
)
)
# buildings
for b in layout:
if b["type"] == "A":
color = "skyblue"
label = "Tower A"
else:
color = "orange"
label = "Tower B"
ax.add_patch(
plt.Rectangle(
(b["x"], b["y"]),
b["width"],
b["height"],
color=color,
alpha=0.8
)
)
# label building type
ax.text(
b["x"] + b["width"] / 2,
b["y"] + b["height"] / 2,
b["type"],
ha="center",
va="center",
fontsize=9,
color="black"
)
# Plot settings
ax.set_xlim(0, SITE_WIDTH)
ax.set_ylim(0, SITE_HEIGHT)
ax.set_aspect("equal")
ax.set_title(title)
ax.set_xlabel("Width (m)")
ax.set_ylabel("Height (m)")
# ax.grid(True)
plt.show()
if __name__ == "__main__":
for i in range(3):
layout = generate_layout(num_A=3, num_B=4)
if layout and check_tower_A_neighbors(layout):
print(f"\nValid layout {i+1}")
stats = layout_stats(layout)
print(stats)
visualize_layout(layout, title=f"Valid Site Layout {i+1}")
else:
print(f"\nLayout {i+1} failed to satisfy constraints")