File size: 9,381 Bytes
3bd69a8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
Chào bạn, đây là phần đi sâu chi tiết vào các kỹ thuật **tối ưu hóa thẩm mỹ (Aesthetic Optimization)** kèm theo các đoạn code Python thực tế sử dụng thư viện `Shapely``NumPy` mà bạn có thể tích hợp ngay vào dự án.

Chúng ta sẽ tập trung vào 3 giải pháp cốt lõi: **Kiểm soát hình học**, **Cắt gọt trực giao (Orthogonal Slicing)**, và **Xử lý phần dư thông minh**.

-----

### 1\. Phương pháp 1: Kiểm soát Hình học (Geometric Regularization)

**Mục tiêu:** Loại bỏ các lô đất có hình dạng "kỳ dị" (quá dẹt, quá méo) ngay từ bước sinh phương án hoặc dùng làm hàm mục tiêu để phạt điểm.

**Chỉ số kỹ thuật:**

1.  **Rectangularity (Độ đầy đặn):** Tỷ lệ diện tích lô đất so với hình chữ nhật bao quanh nó (Oriented Bounding Box - OBB). Giá trị càng gần 1.0 càng vuông.
2.  **Aspect Ratio (Tỷ lệ cạnh):** Tỷ lệ giữa chiều dài và chiều rộng.

**Đoạn Code Tối ưu:**

```python
import numpy as np
from shapely.geometry import Polygon

def analyze_shape_quality(polygon):
    """
    Trả về điểm số thẩm mỹ và trạng thái hợp lệ của lô đất.
    """
    if polygon.is_empty or not polygon.is_valid:
        return 0.0, False

    # 1. Tính OBB (Oriented Bounding Box) - Hình chữ nhật bao quanh nhỏ nhất
    obb = polygon.minimum_rotated_rectangle
    
    # Tính độ vuông vắn (Rectangularity)
    # Nếu polygon là hình chữ nhật, tỷ số = 1.0. Nếu là tam giác, tỷ số ~ 0.5
    rectangularity = polygon.area / obb.area
    
    # 2. Tính Tỷ lệ cạnh (Aspect Ratio) từ OBB
    x, y = obb.exterior.coords.xy
    # Tính độ dài 2 cạnh kề nhau của OBB
    edge_1 = np.hypot(x[1] - x[0], y[1] - y[0])
    edge_2 = np.hypot(x[2] - x[1], y[2] - y[1])
    
    if edge_1 == 0 or edge_2 == 0: return 0.0, False
    
    width, length = sorted([edge_1, edge_2])
    aspect_ratio = length / width
    
    # --- CÁC LUẬT RÀNG BUỘC (HARD CONSTRAINTS) ---
    is_valid = True
    
    # Luật 1: Phải tương đối vuông (chấp nhận méo nhẹ do vạt góc)
    if rectangularity < 0.75: 
        is_valid = False 
        
    # Luật 2: Không được quá dẹt (ví dụ: dài gấp 4 lần rộng là xấu)
    if aspect_ratio > 4.0:
        is_valid = False
        
    # Luật 3: Diện tích tối thiểu (để tránh lô vụn)
    if polygon.area < 1000: # m2
        is_valid = False

    # Điểm thưởng cho lô đất đẹp (để dùng cho hàm mục tiêu)
    score = (rectangularity * 0.7) + ((1.0 / aspect_ratio) * 0.3)
    
    return score, is_valid

# Ví dụ sử dụng trong vòng lặp chia lô
# for lot in generated_lots:
#     score, valid = analyze_shape_quality(lot)
#     if not valid:
#         # Đưa vào danh sách "Đất cây xanh/Kỹ thuật" thay vì "Đất thương phẩm"
#         move_to_leftover(lot)
```

-----

### 2\. Phương pháp 2: Cắt gọt Trực giao (Orthogonal Alignment)

**Mục tiêu:** Thay vì dùng Voronoi ngẫu nhiên (tạo ra các cạnh xiên xẹo), chúng ta ép buộc các đường cắt phải **vuông góc** với trục đường chính hoặc cạnh dài nhất của Block mẹ.

**Thuật toán:**

1.  Xác định "Cạnh chủ" (Dominant Edge) của Block mẹ (thường là cạnh giáp đường).
2.  Tạo vector vuông góc với Cạnh chủ.
3.  Thực hiện chia lô theo vector này.

**Đoạn Code Tối ưu:**

```python
from shapely.affinity import rotate, translate

def get_dominant_edge_vector(polygon):
    """Tìm vector chỉ phương của cạnh dài nhất (thường là mặt tiền)"""
    rect = polygon.minimum_rotated_rectangle
    x, y = rect.exterior.coords.xy
    
    # Lấy 3 điểm đầu để xác định 2 cạnh kề nhau
    p0 = np.array([x[0], y[0]])
    p1 = np.array([x[1], y[1]])
    p2 = np.array([x[2], y[2]])
    
    edge1_len = np.linalg.norm(p1 - p0)
    edge2_len = np.linalg.norm(p2 - p1)
    
    # Trả về vector đơn vị của cạnh dài nhất
    if edge1_len > edge2_len:
        vec = p1 - p0
    else:
        vec = p2 - p1
        
    return vec / np.linalg.norm(vec)

def orthogonal_slice(block, num_lots):
    """
    Chia block thành các lô song song, vuông góc với cạnh chính.
    Đây là kỹ thuật quan trọng nhất để tạo ra sự ngăn nắp.
    """
    # 1. Lấy hướng trục chính
    direction_vec = get_dominant_edge_vector(block)
    
    # Vector vuông góc (dùng để quét cắt)
    perp_vec = np.array([-direction_vec[1], direction_vec[0]])
    
    # 2. Xoay block về trục ngang để dễ tính toán (Optional nhưng recommended)
    # Ở đây ta dùng cách tạo đường cắt trực tiếp
    
    minx, miny, maxx, maxy = block.bounds
    # Tạo một đường thẳng dài bao trùm block
    diagonal = np.hypot(maxx-minx, maxy-miny)
    
    lots = []
    # Giả sử chia đều (trong thực tế bạn sẽ dùng OR-Tools để tính widths)
    # Ta cần tìm điểm bắt đầu và kết thúc dọc theo cạnh chính
    # (Phần này cần logic chiếu hình học phức tạp hơn một chút, 
    # dưới đây là mô phỏng cách dao cắt hoạt động)
    
    # ... Logic cắt (simplified) ...
    # Thay vì code cắt phức tạp, hãy áp dụng nguyên tắc:
    # "Luôn xoay vector cắt lệch 90 độ so với vector đường giao thông tiếp giáp"
    
    return lots # Trả về danh sách lô đã cắt vuông
```

-----

### 3\. Phương pháp 3: Xử lý phần dư (Leftover Management) - "Biến rác thành hoa"

**Mục tiêu:** Sau khi chia các lô vuông vắn, sẽ luôn còn lại các mẩu đất hình tam giác hoặc hình thang méo ở các góc cua. Thay vì cố ép nó thành đất ở (làm xấu bản vẽ và giảm giá trị), hãy tự động chuyển đổi nó thành **Tiện ích**.

**Chiến lược:**

  * Chạy thuật toán chia lô vuông vắn tối đa.
  * Phần diện tích còn lại (Difference) $\rightarrow$ Kiểm tra hình dáng.
  * Nếu `Rectangularity < 0.6` $\rightarrow$ Gán label `Cây xanh` hoặc `Bãi đỗ xe`.

**Đoạn Code Tối ưu quy trình:**

```python
def optimize_layout_w_leftovers(site_polygon, road_network):
    # 1. Tạo các Block lớn từ mạng lưới đường
    blocks = site_polygon.difference(road_network)
    
    final_commercial_lots = []
    final_green_spaces = []
    
    for block in blocks.geoms:
        # Bước A: Làm sạch hình học block (Simplify)
        clean_block = block.simplify(0.5, preserve_topology=True)
        
        # Bước B: Chia lô (Sử dụng thuật toán cắt trực giao ở trên)
        # Giả sử hàm subdivide_block trả về danh sách các lô
        raw_lots = subdivide_block_orthogonally(clean_block) 
        
        for lot in raw_lots:
            # Bước C: Đánh giá thẩm mỹ
            score, is_valid = analyze_shape_quality(lot)
            
            if is_valid:
                # Nếu lô đẹp -> Đất thương phẩm
                final_commercial_lots.append(lot)
            else:
                # Nếu lô quá méo/nhỏ -> Chuyển thành đất cây xanh
                # Đây là bước "Làm đẹp" bản vẽ
                final_green_spaces.append(lot)
                
    return final_commercial_lots, final_green_spaces
```

### 4\. Tích hợp vào OR-Tools (Hàm mục tiêu nâng cao)

Nếu bạn đang dùng `CP-SAT` solver của Google OR-Tools (như trong context cũ), bạn có thể sửa hàm mục tiêu để tối ưu sự đồng đều (Symmetry).

**Code OR-Tools (Logic):**

```python
# Thay vì chỉ Maximize(TotalArea), hãy thêm phạt sự chênh lệch
# Mục tiêu: Các lô nên có kích thước bằng nhau (Target Width)

# ... khai báo biến widths[i] ...

target_width = 15.0 # mét (Mặt tiền mong muốn)
deviations = []

for w in widths:
    # Tạo biến phụ để tính trị tuyệt đối: abs(w - target)
    diff = model.NewIntVar(0, 100, 'diff')
    
    # Mẹo OR-Tools để tính abs:
    # diff >= w - target  VÀ  diff >= target - w
    model.Add(diff >= w - int(target_width))
    model.Add(diff >= int(target_width) - w)
    
    deviations.append(diff)

# Hàm mục tiêu mới:
# Tối đa hóa diện tích NHƯNG Trừ điểm nặng nếu kích thước không đều
model.Maximize(sum(widths) * 100 - sum(deviations) * 50) 
```

### Tổng kết chiến lược

Để bản vẽ từ "Tối ưu tài chính (xấu)" trở thành "Tối ưu tổng thể (đẹp + hiệu quả)", bạn chỉ cần thực hiện thay đổi nhỏ trong luồng xử lý:

1.  **Input:** Xác định cạnh đường chính.
2.  **Process:** Chỉ cắt vuông góc với cạnh đường (Force 90 deg).
3.  **Filter:** Dùng hàm `analyze_shape_quality` để lọc các mảnh vỡ.
4.  **Post-Process:** Tô màu xanh (công viên) cho các mảnh vỡ đó thay vì cố gắng bán chúng.

Bản vẽ sẽ trông chuyên nghiệp hơn ngay lập tức vì sự ngăn nắp và có nhiều không gian xanh ở các góc ngã tư.