-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcadastral.py
More file actions
238 lines (186 loc) · 10.2 KB
/
cadastral.py
File metadata and controls
238 lines (186 loc) · 10.2 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
from dxf import SurveyDXFManager
from models.plan import PlanProps, PlanType
from utils import polygon_orientation, line_normals, line_direction, html_to_mtext, format_number
import math
class CadastralPlan(PlanProps):
def __init__(self, **kwargs):
super().__init__(**kwargs)
if self.type != PlanType.CADASTRAL:
raise ValueError("CadastralPlan must have type PlanType.CADASTRAL")
self._frame_x_percent = 0.9
self._frame_y_percent = 1.5
self._bounding_box = self.get_bounding_box()
self._frame_coords = self._setup_frame_coords()
self._coord_dict = {coord.id: coord for coord in self.coordinates}
if not self._frame_coords:
raise ValueError("Cannot determine frame coordinates without valid coordinates.")
self._drawer = self._setup_drawer()
def _setup_drawer(self) -> SurveyDXFManager:
drawer = SurveyDXFManager(plan_name=self.name, scale=self.get_drawing_scale(), dxf_version=self.dxf_version)
drawer.setup_cadastral_layers()
drawer.setup_font(self.font)
drawer.setup_beacon_style(self.beacon_type, self.beacon_size)
return drawer
def _setup_frame_coords(self):
min_x, min_y, max_x, max_y = self._bounding_box
if min_x is None or min_y is None or max_x is None or max_y is None:
return None
width = max_x - min_x
height = max_y - min_y
margin_x = max(width, height) * self._frame_x_percent
margin_y = max(height, width) * self._frame_y_percent
frame_left = min_x - margin_x
frame_bottom = min_y - margin_y
frame_right = max_x + margin_x
frame_top = max_y + margin_y
return frame_left, frame_bottom, frame_right, frame_top
def _get_drawing_extent(self) -> float:
# get bounding box
min_x, min_y, max_x, max_y = self._bounding_box
if min_x is None or min_y is None or max_x is None or max_y is None:
return 0.0
width = max_x - min_x
height = max_y - min_y
extent = math.sqrt(width ** 2 + height ** 2)
return extent
def draw_beacons(self):
if not self.coordinates:
return
for coord in self.coordinates:
self._drawer.draw_beacon(coord.easting, coord.northing, 0, self.label_size, self._get_drawing_extent(), coord.id)
def draw_parcels(self):
if not self.parcels or not self.coordinates:
return
for parcel in self.parcels:
parcel_points = [(self._coord_dict[pid].easting, self._coord_dict[pid].northing)
for pid in parcel.ids if pid in self._coord_dict]
if not parcel_points:
continue
self._drawer.add_parcel(parcel.name, parcel_points, label_size=self.label_size)
orientation = polygon_orientation(parcel_points)
for leg in parcel.legs:
self.add_leg_labels(leg, orientation)
def add_leg_labels(self, leg, orientation: str):
"""Add distance and bearing labels to a leg."""
# Angle and positions
angle_rad = math.atan2(leg.to.northing - leg.from_.northing,
leg.to.easting - leg.from_.easting)
angle_deg = math.degrees(angle_rad)
# Fractional positions
first_x = leg.from_.easting + (0.2 * (leg.to.easting - leg.from_.easting))
first_y = leg.from_.northing + (0.2 * (leg.to.northing - leg.from_.northing))
last_x = leg.from_.easting + (0.8 * (leg.to.easting - leg.from_.easting))
last_y = leg.from_.northing + (0.8 * (leg.to.northing - leg.from_.northing))
mid_x = (leg.from_.easting + leg.to.easting) / 2
mid_y = (leg.from_.northing + leg.to.northing) / 2
# Offset text above/below the line
normals = line_normals((leg.from_.easting, leg.from_.northing), (leg.to.easting, leg.to.northing), orientation)
offset_distance = self._get_drawing_extent() * 0.02
offset_inside_x = (normals[0][0] / math.hypot(*normals[0])) * offset_distance
offset_inside_y = (normals[0][1] / math.hypot(*normals[0])) * offset_distance
offset_outside_x = (normals[1][0] / math.hypot(*normals[1])) * offset_distance
offset_outside_y = (normals[1][1] / math.hypot(*normals[1])) * offset_distance
first_x += offset_outside_x; first_y += offset_outside_y
last_x += offset_outside_x; last_y += offset_outside_y
mid_x += offset_inside_x; mid_y += offset_inside_y
# Text angle adjustment
text_angle = angle_deg
if text_angle > 90 or text_angle < -90:
text_angle += 180
# Add labels
self._drawer.add_label(f"{leg.distance:.2f}m", mid_x, mid_y,
angle=text_angle, height=self.label_size)
ld = line_direction(angle_deg)
if ld == "left → right":
# self._drawer.add_label_mtext(f"{format_number(leg.bearing.degrees, "hundredth")}° {format_number(leg.bearing.minutes, "tenth")}'", mid_x + offset_outside_x, mid_y + offset_outside_y,
# angle=text_angle, height=self.label_size)
self._drawer.add_label(f"{format_number(leg.bearing.degrees, "hundredth")}°", first_x, first_y,
angle=text_angle, height=self.label_size)
self._drawer.add_label(f"{format_number(leg.bearing.minutes, "tenth")}'", last_x, last_y,
angle=text_angle, height=self.label_size)
else:
self._drawer.add_label(f"{format_number(leg.bearing.degrees, "hundredth")}°", last_x, last_y,
angle=text_angle, height=self.label_size)
self._drawer.add_label(f"{format_number(leg.bearing.minutes, "tenth")}'", first_x, first_y,
angle=text_angle, height=self.label_size)
# print(
# ''
# )
def draw_frames(self):
"""Draw outer and offset frames."""
min_x, min_y, max_x, max_y = self._bounding_box
width, height = max_x - min_x, max_y - min_y
margin_x, margin_y = max(width, height) * self._frame_x_percent, max(height, width) * self._frame_y_percent
frame_left, frame_bottom = min_x - margin_x, min_y - margin_y
frame_right, frame_top = max_x + margin_x, max_y + margin_y
self._drawer.draw_frame(frame_left, frame_bottom, frame_right, frame_top)
# offset_x, offset_y = max(width, height) * (self._frame_x_percent + 0.03), max(height, width) * (self._frame_y_percent + 0.03)
# self._drawer.draw_frame(min_x - offset_x, min_y - offset_y,
# max_x + offset_x, max_y + offset_y)
def draw_title_block(self):
"""Add title block to the frame."""
min_x, min_y, max_x, max_y = self._bounding_box
width, height = max_x - min_x, max_y - min_y
margin_x, margin_y = max(width, height) * self._frame_x_percent, max(height, width) * self._frame_y_percent
frame_left, frame_bottom = min_x - margin_x, min_y - margin_y
frame_right, frame_top = max_x + margin_x, max_y + margin_y
frame_width = frame_right - frame_left
frame_center_x = frame_left + (frame_width / 2)
title_y = frame_top - (margin_y * 0.2)
self._drawer.draw_title_block(html_to_mtext(self.build_title()),
frame_center_x,
title_y,
frame_width * 0.6,
self.font_size,
graphical_scale_length=(self._frame_coords[2] - self._frame_coords[0]) * 0.4,
area=f"AREA :- {self.parcels[0].area} SQ.METRES",
origin=f"ORIGIN :- {self.origin.upper()}")
def draw_footer_boxes(self):
if len(self.footers) == 0:
return
x_min = self._frame_coords[0]
y_min = self._frame_coords[1]
x_max = self._frame_coords[2]
y_max = self._frame_coords[3]
box_width = (x_max - x_min) / len(self.footers)
box_height = (y_max - y_min) * 0.18
for i, footer in enumerate(self.footers):
x1 = x_min + i * box_width
x2 = x1 + box_width
y1 = y_min
y2 = y1 + box_height
self._drawer.draw_footer_box(html_to_mtext(footer), x1, y1, x2, y2, self.footer_size)
def draw_north_arrow(self):
if len(self.parcels) == 0:
return
coord = self._coord_dict[self.parcels[0].ids[0]]
height = (self._frame_coords[3] - self._frame_coords[1]) * 0.07
self._drawer.draw_north_arrow(coord.easting, self._frame_coords[3] - height, height)
# for easting label
width = (self._frame_coords[2] - self._frame_coords[0]) * 0.1
self._drawer.add_north_arrow_label((self._frame_coords[0], coord.northing),
(self._frame_coords[0] + width, coord.northing), f"{coord.easting}mE",
self.label_size)
self._drawer.add_north_arrow_label((self._frame_coords[2], coord.northing),
(self._frame_coords[2] - width, coord.northing), "",
self.label_size)
# for northing label
northing_label_y = self._frame_coords[1]
if len(self.footers) > 0:
northing_label_y = northing_label_y + ((self._frame_coords[3] - self._frame_coords[1]) * 0.18)
self._drawer.add_north_arrow_label((coord.easting, northing_label_y),
(coord.easting, northing_label_y + height), f"{coord.northing}mN",
self.label_size, "vertical")
self._drawer.draw_north_arrow_cross(coord.easting, coord.northing, self.beacon_size * 3)
def draw(self):
# Draw elements
self.draw_beacons()
self.draw_parcels()
self.draw_frames()
self.draw_title_block()
self.draw_footer_boxes()
self.draw_north_arrow()
def save_dxf(self, file_path: str):
self._drawer.save_dxf(file_path)
def save(self) -> str:
return self._drawer.save(paper_size=self.page_size, orientation=self.page_orientation)