Skip to content

Commit c519e9a

Browse files
committed
wip
1 parent ab0a85d commit c519e9a

8 files changed

Lines changed: 1724 additions & 44 deletions

File tree

app.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import os
22
from dotenv import load_dotenv
3-
4-
from topographic import TopographicPlan
5-
63
load_dotenv() # reads .env into environment
74

8-
from cadastral import CadastralPlan
9-
105
from flask import Flask, request, jsonify
116

7+
from topographic import TopographicPlan
8+
from cadastral import CadastralPlan
9+
from layout import LayoutPlan
10+
1211
app = Flask(__name__)
1312
app.config["SECRET_KEY"] = "secret"
1413

@@ -37,6 +36,16 @@ def generate_topographic_plan():
3736
url = plan.save()
3837
return jsonify({"message": "Topographic plan generated", "filename": plan.name, "url": url}), 200
3938

39+
@app.route("/layout/plan", methods=["POST"])
40+
def generate_layout_plan():
41+
data = request.get_json()
42+
43+
plan = LayoutPlan(**data)
44+
plan.draw()
45+
46+
url = plan.save()
47+
return jsonify({"message": "Layout plan generated", "filename": plan.name, "url": url}), 200
48+
4049

4150
@app.errorhandler(404)
4251
def not_found(e):

cadastral.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def __init__(self, **kwargs):
2424

2525
def _setup_drawer(self) -> SurveyDXFManager:
2626
drawer = SurveyDXFManager(plan_name=self.name, scale=self.get_drawing_scale())
27+
drawer.setup_cadastral_layers()
2728
drawer.setup_font(self.font)
2829
drawer.setup_beacon_style(self.beacon_type, self.beacon_size)
2930
return drawer
@@ -114,19 +115,19 @@ def add_leg_labels(self, leg, orientation: str):
114115
text_angle += 180
115116

116117
# Add labels
117-
self._drawer.add_text(f"{leg.distance:.2f} m", mid_x, mid_y,
118-
angle=text_angle, height=self.label_size)
118+
self._drawer.add_label(f"{leg.distance:.2f} m", mid_x, mid_y,
119+
angle=text_angle, height=self.label_size)
119120
ld = line_direction(angle_deg)
120121
if ld == "left → right":
121-
self._drawer.add_text(f"{leg.bearing.degrees}°", first_x, first_y,
122-
angle=text_angle, height=self.label_size)
123-
self._drawer.add_text(f"{leg.bearing.minutes}'", last_x, last_y,
124-
angle=text_angle, height=self.label_size)
122+
self._drawer.add_label(f"{leg.bearing.degrees}°", first_x, first_y,
123+
angle=text_angle, height=self.label_size)
124+
self._drawer.add_label(f"{leg.bearing.minutes}'", last_x, last_y,
125+
angle=text_angle, height=self.label_size)
125126
else:
126-
self._drawer.add_text(f"{leg.bearing.degrees}°", last_x, last_y,
127-
angle=text_angle, height=self.label_size)
128-
self._drawer.add_text(f"{leg.bearing.minutes}'", first_x, first_y,
129-
angle=text_angle, height=self.label_size)
127+
self._drawer.add_label(f"{leg.bearing.degrees}°", last_x, last_y,
128+
angle=text_angle, height=self.label_size)
129+
self._drawer.add_label(f"{leg.bearing.minutes}'", first_x, first_y,
130+
angle=text_angle, height=self.label_size)
130131

131132
def draw_frames(self):
132133
"""Draw outer and offset frames."""

dxf.py

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717

1818

1919
class SurveyDXFManager:
20-
def __init__(self, plan_name: str = "Survey Plan", scale: float = 1.0):
20+
def __init__(self, plan_name: str = "Survey Plan", scale: float = 1.0, dxf_version="R2010"):
2121
self.plan_name = plan_name
2222
self.scale = scale
23-
self.doc = ezdxf.new(dxfversion="R2010")
23+
self.doc = ezdxf.new(dxfversion=dxf_version)
2424
self.msp = self.doc.modelspace()
25-
self._setup_layers()
25+
self.setup_layers()
2626

2727

2828
# set units
@@ -33,16 +33,20 @@ def __init__(self, plan_name: str = "Survey Plan", scale: float = 1.0):
3333
self.doc.header["$AUPREC"] = 3 # 0d00'00"
3434
self.doc.header["$ANGBASE"] = 90.0 # set 0° direction to North
3535

36-
def _setup_layers(self):
37-
"""Setup standard survey layers"""
36+
def setup_layers(self):
3837
self.doc.layers.add(name="BEACONS", color=colors.BLACK)
39-
self.doc.layers.add(name="PARCELS", color=colors.RED)
4038
self.doc.layers.add(name="LABELS", color=colors.BLACK)
4139
self.doc.layers.add(name="FRAME", color=colors.BLACK)
4240
self.doc.layers.add(name="TITLE_BLOCK", color=colors.BLACK)
4341
self.doc.layers.add(name="FOOTER", color=colors.BLACK)
42+
43+
def setup_cadastral_layers(self):
44+
self.doc.layers.add(name="PARCELS", color=colors.RED)
45+
46+
def setup_topographic_layers(self):
4447
self.doc.layers.add(name="BOUNDARY", color=colors.RED)
45-
self.doc.layers.add('CONTOUR_MAJOR', true_color=ezdxf.colors.rgb2int((127, 31, 0)), linetype="Continuous", lineweight=35)
48+
self.doc.layers.add('CONTOUR_MAJOR', true_color=ezdxf.colors.rgb2int((127, 31, 0)), linetype="Continuous",
49+
lineweight=35)
4650
self.doc.layers.add('CONTOUR_MINOR', true_color=ezdxf.colors.rgb2int((127, 31, 0)), linetype="Continuous",
4751
lineweight=18)
4852
self.doc.layers.add('CONTOUR_LABELS', true_color=ezdxf.colors.rgb2int((127, 31, 0)))
@@ -53,6 +57,21 @@ def _setup_layers(self):
5357
self.doc.layers.add('SPOT_HEIGHTS', true_color=ezdxf.colors.rgb2int((205, 105, 40)), linetype="Continuous",
5458
lineweight=25)
5559

60+
def setup_layout_layers(self):
61+
self.doc.layers.add(name="BOUNDARY", color=colors.RED, linetype="CONTINUOUS", lineweight=50)
62+
self.doc.layers.add(name="PARCELS", color=colors.GREEN, linetype="CONTINUOUS", lineweight=25)
63+
self.doc.layers.add(name="ROADS", color=colors.BLACK, linetype="CONTINUOUS", lineweight=35)
64+
self.doc.layers.add(name="ROADS_CL", color=colors.CYAN, linetype="DASHDOT", lineweight=18)
65+
self.doc.layers.add(name="SETBACKS", color=colors.MAGENTA, linetype="DASHED", lineweight=18)
66+
self.doc.layers.add(name="DIMENSIONS", color=colors.YELLOW, linetype="CONTINUOUS", lineweight=18)
67+
self.doc.layers.add(name="TEXT", color=colors.BLACK, linetype="CONTINUOUS", lineweight=18)
68+
self.doc.layers.add(name="GREEN_SPACE", color=colors.GREEN, linetype="CONTINUOUS", lineweight=25)
69+
self.doc.layers.add(name="UTILITIES", color=colors.BLUE, linetype="DASHED", lineweight=18)
70+
self.doc.layers.add(name="EASEMENTS", true_color=ezdxf.colors.rgb2int((255, 165, 0)), linetype="DASHDOT",
71+
lineweight=18)
72+
self.doc.layers.add(name="BUILDABLE", color=colors.GRAY, linetype="DASHDOT", lineweight=18)
73+
74+
5675
def setup_beacon_style(self, type_: str = "box", size: float = 1.0):
5776
size = size * self.scale
5877

@@ -156,15 +175,54 @@ def add_parcel(self, parcel_id: str, points: List[Tuple[float, float]], label_si
156175
# )
157176

158177
def add_boundary(self, points: List[Tuple[float, float]]):
159-
"""Add a boundaty given its ID and list of (x, y) points"""
178+
"""Add a boundary given its ID and list of (x, y) points"""
160179
# scale points
161180
points = [(x * self.scale, y * self.scale) for x, y, *rest in points]
162181

163182
self.msp.add_lwpolyline(points, close=True, dxfattribs={
164183
'layer': 'BOUNDARY'
165184
})
166185

167-
def add_text(self, text: str, x: float, y: float, angle: float = 0.0, height: float = 1.0):
186+
def add_buildable(self, points: List[Tuple[float, float]]):
187+
"""Add a boundary given its ID and list of (x, y) points"""
188+
# scale points
189+
points = [(x * self.scale, y * self.scale) for x, y, *rest in points]
190+
191+
self.msp.add_lwpolyline(points, close=True, dxfattribs={
192+
'layer': 'BUILDABLE'
193+
})
194+
195+
def add_road_cl(self, points: List[Tuple[float, float]]):
196+
# scale points
197+
points = [(x * self.scale, y * self.scale) for x, y, *rest in points]
198+
199+
self.msp.add_lwpolyline(points, dxfattribs={
200+
'layer': 'ROAD_CL'
201+
})
202+
203+
def add_road(self, points: List[Tuple[float, float]]):
204+
# scale points
205+
points = [(x * self.scale, y * self.scale) for x, y, *rest in points]
206+
207+
self.msp.add_lwpolyline(points, dxfattribs={
208+
'layer': 'ROADS'
209+
})
210+
211+
def add_greenspace(self, points: List[Tuple[float, float]], coords):
212+
"""Add a boundary given its ID and list of (x, y) points"""
213+
# scale points
214+
points = [(x * self.scale, y * self.scale) for x, y, *rest in points]
215+
216+
self.msp.add_lwpolyline(points, close=True, dxfattribs={
217+
'layer': 'GREEN_SPACE'
218+
})
219+
220+
hatch = self.msp.add_hatch(dxfattribs={'layer': 'GREEN_SPACE'})
221+
hatch.set_pattern_fill('ANSI31', scale=0.5)
222+
hatch.paths.add_polyline_path(coords, is_closed=True)
223+
224+
225+
def add_label(self, text: str, x: float, y: float, angle: float = 0.0, height: float = 1.0):
168226
x = x * self.scale
169227
y = y * self.scale
170228
height = height * self.scale
@@ -183,6 +241,23 @@ def add_text(self, text: str, x: float, y: float, angle: float = 0.0, height: fl
183241
align=TextEntityAlignment.MIDDLE_CENTER
184242
)
185243

244+
def add_text(self, text: str, x: float, y: float, height: float = 1.0):
245+
x = x * self.scale
246+
y = y * self.scale
247+
height = height * self.scale
248+
249+
"""Add arbitrary text at given coordinates with optional rotation"""
250+
text = self.msp.add_text(
251+
text,
252+
dxfattribs={
253+
'layer': 'TEXT',
254+
'height': height,
255+
'style': 'STANDARD'
256+
}
257+
).set_placement(
258+
(x , y),
259+
)
260+
186261
def draw_north_arrow(self, x: float, y: float, height: float = 100.0):
187262
height = height * self.scale
188263
x = x * self.scale
@@ -250,7 +325,7 @@ def add_north_arrow_label(self, start: Tuple[float, float], stop: Tuple[float, f
250325
'rotation': angle
251326
}
252327
).set_placement(
253-
(x, y),
328+
(x + 1, y + 1),
254329
align=TextEntityAlignment.TOP_LEFT,
255330
)
256331

@@ -400,7 +475,7 @@ def draw_title_block(self, text: str, x: float, y: float, width: float, title_he
400475
graphical_min_y = graphical_box.extmin.y
401476

402477
origin_mtext = self.msp.add_mtext(
403-
text=f"{MTextEditor.UNDERLINE_START}\C5;{area}{MTextEditor.NEW_LINE}\C1;{origin}{MTextEditor.UNDERLINE_STOP}",
478+
text=f"{MTextEditor.UNDERLINE_START}\C1;{area}{MTextEditor.NEW_LINE}\C5;{origin}{MTextEditor.UNDERLINE_STOP}",
404479
dxfattribs={'style': 'SURVEY_TEXT'},
405480
)
406481
origin_mtext.dxf.attachment_point = ezdxf.enums.MTextEntityAlignment.TOP_CENTER
@@ -630,6 +705,29 @@ def save_dwg(self, dxf_filepath: str, filepath: str = None):
630705
filepath = f"{self.get_filename()}.dwg"
631706
odafc.convert(dxf_filepath, filepath)
632707

708+
# def save(self, paper_size: str = "A4", orientation: str = "portrait"):
709+
# # with tempfile.TemporaryDirectory() as tmpdir:
710+
# filename = self.get_filename()
711+
# dxf_path = os.path.join("", f"{filename}.dxf")
712+
# dwg_path = os.path.join("", f"{filename}.dwg")
713+
# pdf_path = os.path.join("", f"{filename}.pdf")
714+
# zip_path = os.path.join("", f"{filename}.zip")
715+
#
716+
# self.save_dxf(dxf_path)
717+
# self.save_dwg(dxf_path, dwg_path)
718+
# self.save_pdf(pdf_path, paper_size=paper_size, orientation=orientation)
719+
#
720+
# # Create a ZIP file containing all three formats
721+
# with zipfile.ZipFile(zip_path, "w") as zipf:
722+
# zipf.write(dxf_path, os.path.basename(dxf_path))
723+
# zipf.write(dwg_path, os.path.basename(dwg_path))
724+
# zipf.write(pdf_path, os.path.basename(pdf_path))
725+
#
726+
# # url = upload_file(zip_path, folder="survey_plans", file_name=filename)
727+
# # if url is None:
728+
# # raise Exception("Upload failed")
729+
# return "url"
730+
633731
def save(self, paper_size: str = "A4", orientation: str = "portrait"):
634732
with tempfile.TemporaryDirectory() as tmpdir:
635733
filename = self.get_filename()

0 commit comments

Comments
 (0)