Skip to content

Commit 2dc7955

Browse files
committed
refactoring
1 parent 8f2d1f5 commit 2dc7955

9 files changed

Lines changed: 548 additions & 174 deletions

File tree

.dockerignore

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,65 @@
1-
fly.toml
1+
# Byte-compiled / cache
2+
__pycache__/
3+
*.pyc
4+
*.pyo
5+
*.pyd
6+
*.py[cod]
7+
*$py.class
8+
9+
# Virtual environments
10+
.venv/
11+
venv/
12+
env/
13+
14+
# Distribution / packaging
15+
*.egg-info/
16+
.eggs/
17+
build/
18+
dist/
19+
pip-wheel-metadata/
20+
21+
# Logs
22+
*.log
23+
24+
# Unit test / coverage reports
25+
.coverage
26+
.tox/
27+
.nox/
28+
.pytest_cache/
29+
30+
# Jupyter / IPython
31+
.ipynb_checkpoints
32+
*.ipynb
33+
34+
# Environment files (secrets)
35+
#.env
36+
#.env.*
37+
#*.env
38+
39+
# IDE / editor configs
40+
.vscode/
41+
.idea/
42+
*.swp
43+
*.swo
44+
45+
# OS files
46+
.DS_Store
47+
Thumbs.db
48+
49+
# Git
50+
.git
51+
.gitignore
52+
53+
# Docker
54+
.dockerignore
55+
docker-compose.override.yml
56+
57+
# Flask specific
58+
instance/
59+
*.db
60+
*.sqlite3
61+
62+
# Cache
63+
.cache/
64+
65+
fly.toml

app.py

Lines changed: 160 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
1+
import os
12
from dotenv import load_dotenv
23
load_dotenv() # reads .env into environment
34

4-
import os
5-
import math
5+
from cadastral import CadastralPlan
66

77
from flask import Flask, request, jsonify
88

9-
from dxf import SurveyDXFManager
10-
from models.plan import PlanProps
11-
from utils import polygon_orientation, line_normals, line_direction, html_to_mtext
12-
139
app = Flask(__name__)
1410
app.config["SECRET_KEY"] = "secret"
1511

16-
# cloudinary.config(
17-
# cloud_name = os.getenv("CLOUDINARY_CLOUD_NAME"),
18-
# api_key = os.getenv("CLOUDINARY_API_KEY"),
19-
# api_secret = os.getenv("CLOUDINARY_API_SECRET")
20-
# )
21-
2212

2313
@app.get("/")
2414
def home():
@@ -28,113 +18,166 @@ def home():
2818
def generate_cadastral_plan():
2919
data = request.get_json()
3020

31-
plan = PlanProps(**data)
32-
extent = plan.get_extent()
33-
beacon_size = extent * 0.02
34-
35-
drawer = SurveyDXFManager(plan_name=plan.name, scale=plan.get_drawing_scale())
36-
drawer.setup_beacon_style(type_=plan.beacon_type, size=beacon_size or 1.0)
37-
drawer.setup_font(plan.font)
38-
39-
label_height = extent * 0.015 if extent > 0 else 1.0
40-
41-
# Draw beacon and labels
42-
for coord in plan.coordinates:
43-
drawer.add_beacon(coord.easting, coord.northing, 0, beacon_size * 0.5, coord.id)
44-
45-
# create a dictionary of coordinates for easy lookup
46-
coord_dict = {coord.id: coord for coord in plan.coordinates}
47-
parcel_dict = {}
48-
49-
# Draw parcels
50-
for parcel in plan.parcels:
51-
parcel_points = []
52-
for point_id in parcel.ids:
53-
if point_id in coord_dict:
54-
coord = coord_dict[point_id]
55-
parcel_points.append((coord.easting, coord.northing))
56-
if parcel_points:
57-
drawer.add_parcel(parcel.name, parcel_points)
58-
59-
# add bearing and distance text
60-
orientation = polygon_orientation(parcel_points)
61-
for leg in parcel.legs:
62-
# compute rotational angle for text
63-
angle_rad = math.atan2(leg.to.northing - leg.from_.northing, leg.to.easting - leg.from_.easting)
64-
angle_deg = math.degrees(angle_rad)
65-
66-
first_x = leg.from_.easting + (0.2 * (leg.to.easting - leg.from_.easting))
67-
first_y = leg.from_.northing + (0.2 * (leg.to.northing - leg.from_.northing))
68-
last_x = leg.from_.easting + (0.8 * (leg.to.easting - leg.from_.easting))
69-
last_y = leg.from_.northing + (0.8 * (leg.to.northing - leg.from_.northing))
70-
mid_x = (leg.from_.easting + leg.to.easting) / 2
71-
mid_y = (leg.from_.northing + leg.to.northing) / 2
72-
73-
# Offset text above/below the line
74-
normals = line_normals((leg.from_.easting, leg.from_.northing), (leg.to.easting, leg.to.northing), orientation)
75-
offset_distance = extent * 0.02
76-
offset_inside_x = (normals[0][0] / math.hypot(*normals[0])) * offset_distance
77-
offset_inside_y = (normals[0][1] / math.hypot(*normals[0])) * offset_distance
78-
offset_outside_x = (normals[1][0] / math.hypot(*normals[1])) * offset_distance
79-
offset_outside_y = (normals[1][1] / math.hypot(*normals[1])) * offset_distance
80-
first_x += offset_outside_x
81-
first_y += offset_outside_y
82-
last_x += offset_outside_x
83-
last_y += offset_outside_y
84-
mid_x += offset_inside_x
85-
mid_y += offset_inside_y
86-
87-
# add texts
88-
text_angle = angle_deg
89-
if text_angle > 90 or text_angle < -90:
90-
text_angle += 180
91-
92-
drawer.add_text(f"{leg.distance:.2f} m", mid_x, mid_y, angle=text_angle, height=label_height)
93-
ld = line_direction(angle_deg)
94-
if ld == "left → right":
95-
drawer.add_text(f"{leg.bearing.degrees}°", first_x, first_y, angle=text_angle, height=label_height)
96-
drawer.add_text(f"{leg.bearing.minutes}'", last_x, last_y, angle=text_angle, height=label_height)
97-
else:
98-
drawer.add_text(f"{leg.bearing.degrees}°", last_x, last_y, angle=text_angle, height=label_height)
99-
drawer.add_text(f"{leg.bearing.minutes}'", first_x, first_y, angle=text_angle, height=label_height)
100-
parcel_dict[parcel.name] = parcel_points
101-
102-
# Compute extent sizes
103-
min_x, min_y, max_x, max_y = plan.get_bounding_box()
104-
width = max_x - min_x
105-
height = max_y - min_y
106-
107-
# Draw frame
108-
margin_x = max(width, height) * 0.35
109-
margin_y = max(height, width) * 0.7
110-
frame_left = min_x - margin_x
111-
frame_bottom = min_y - margin_y
112-
frame_right = max_x + margin_x
113-
frame_top = max_y + margin_y
114-
drawer.draw_frame(frame_left, frame_bottom, frame_right,frame_top)
115-
116-
# offset frame
117-
offset_x = max(width, height) * 0.38
118-
offset_y = max(height, width) * 0.73
119-
drawer.draw_frame(min_x - offset_x, min_y - offset_y, max_x + offset_x, max_y + offset_y)
120-
121-
# add title block
122-
# Calculate center position for title
123-
frame_width = frame_right - frame_left
124-
frame_center_x = frame_left + (frame_width / 2)
125-
126-
# Title positioning
127-
title_y = frame_top - (margin_y * 0.2) # 5 units below the top of frame
128-
title_height = plan.font_size # Height of title text
129-
title_width = frame_width * 0.6 # Use 60% of frame width for title box
130-
131-
drawer.add_title(html_to_mtext(plan.build_title()), frame_center_x, title_y, title_width, title_height)
132-
133-
# drawer.save_dxf()
134-
# drawer.dxf_to_dwg()
135-
url = drawer.save()
21+
plan = CadastralPlan(**data)
22+
plan.draw()
23+
24+
url = plan.save()
13625
return jsonify({"message": "Cadastral plan generated", "filename": plan.name, "url": url}), 200
13726

27+
# @app.route("/route/plan", methods=["POST"])
28+
# def generate_route_plan():
29+
# data = request.get_json()
30+
# plan = PlanProps(**data)
31+
# return jsonify({"message": "Cadastral plan generated", "filename": plan.name, "url": "url"}), 200
32+
33+
# def read_json_file(file_path: str):
34+
# """
35+
# Reads data from a JSON file.
36+
#
37+
# Args:
38+
# file_path (str): Path to the JSON file.
39+
#
40+
# Returns:
41+
# dict | list: Parsed JSON data.
42+
# """
43+
# try:
44+
# with open(file_path, "r", encoding="utf-8") as f:
45+
# data = json.load(f)
46+
# return data
47+
# except FileNotFoundError:
48+
# print(f"Error: File '{file_path}' not found.")
49+
# return None
50+
# except json.JSONDecodeError as e:
51+
# print(f"Error: Failed to decode JSON - {e}")
52+
# return None
53+
54+
# @app.route("/topographic/plan", methods=["POST"])
55+
# def generate_topographic_plan():
56+
# data = request.get_json()
57+
#
58+
# plan = PlanProps(**data)
59+
#
60+
# drawer = SurveyDXFManager(plan_name=plan.name, scale=plan.get_drawing_scale())
61+
# drawer.setup_font(plan.font)
62+
# drawer.setup_topo_point_style()
63+
#
64+
# data = read_json_file("point2.json")
65+
# plan.coordinates = [CoordinateProps(**c) for c in data]
66+
#
67+
# # draw spot heights
68+
# # for coord in plan.coordinates:
69+
# # drawer.add_topo_point(coord.easting, coord.northing, coord.elevation, f"{coord.elevation:.3f}", plan.top_setting.point_label_scale)
70+
#
71+
# # Generate a surface (TIN interpolation).
72+
# x = np.array([coord.easting for coord in plan.coordinates])
73+
# y = np.array([coord.northing for coord in plan.coordinates])
74+
# z = np.array([coord.elevation for coord in plan.coordinates])
75+
#
76+
# # Create triangulation
77+
# triangulation = Triangulation(x, y)
78+
#
79+
# # Generate contour levels
80+
# z_min, z_max = z.min(), z.max()
81+
# levels = np.linspace(z_min, z_max, 100)
82+
#
83+
# # Create matplotlib contours (using memory buffer to avoid display)
84+
# contours = plt.tricontour(triangulation, z, levels=levels)
85+
#
86+
# # Define major contour interval (every 5th contour)
87+
# major_interval = max(1, len(levels) // 5)
88+
#
89+
# # Extract and draw contour lines
90+
# contour_data = []
91+
#
92+
# # Access contour segments using allsegs attribute (more reliable)
93+
# if hasattr(contours, 'allsegs') and len(contours.allsegs) > 0:
94+
# for level_idx, level_segments in enumerate(contours.allsegs):
95+
# elevation = levels[level_idx]
96+
# is_major = (level_idx % major_interval == 0)
97+
# layer_name = 'CONTOURS_MAJOR' if is_major else 'CONTOURS_MINOR'
98+
#
99+
# # Process each contour segment at this elevation
100+
# for segment in level_segments:
101+
# if len(segment) < 2:
102+
# continue
103+
#
104+
# # Convert to list of tuples for ezdxf
105+
# points = [(float(x), float(y), float(elevation)) for x, y in segment]
106+
#
107+
# # Add polyline to DXF
108+
# polyline = drawer.msp.add_polyline3d(
109+
# points,
110+
# dxfattribs={'layer': layer_name}
111+
# )
112+
#
113+
# # Store contour data
114+
# contour_data.append({
115+
# 'elevation': elevation,
116+
# 'coordinates': segment,
117+
# 'is_major': is_major,
118+
# 'polyline': polyline
119+
# })
120+
#
121+
# # Add elevation labels for major contours
122+
# if is_major and len(points) > 0:
123+
# # Place label at midpoint of contour
124+
# mid_idx = len(points) // 2
125+
# label_x, label_y, _ = points[mid_idx]
126+
#
127+
# drawer.msp.add_text(
128+
# f"{elevation:.1f}",
129+
# dxfattribs={
130+
# 'layer': 'CONTOUR_LABELS',
131+
# 'height': 2.5,
132+
# 'style': 'Standard'
133+
# }
134+
# ).set_placement((label_x, label_y), align=TextEntityAlignment.MIDDLE_CENTER)
135+
# else:
136+
# print("Warning: No contour segments found. Check your input data.")
137+
#
138+
# # # Create triangulation
139+
# # triangulation = Triangulation(x, y)
140+
# #
141+
# # # Draw triangle edges
142+
# # for triangle in triangulation.triangles:
143+
# # # Get the three vertices of each triangle
144+
# # p1 = (x[triangle[0]], y[triangle[0]])
145+
# # p2 = (x[triangle[1]], y[triangle[1]])
146+
# # p3 = (x[triangle[2]], y[triangle[2]])
147+
# #
148+
# # # Create closed polyline for triangle
149+
# # triangle_points = [p1, p2, p3, p1] # Close the triangle
150+
# #
151+
# # drawer.msp.add_lwpolyline(
152+
# # triangle_points,
153+
# # dxfattribs={'layer': "TIN_TRIANGLES"}
154+
# # )
155+
#
156+
# # # Find range
157+
# # z_min, z_max = z.min(), z.max()
158+
# #
159+
# # # Choose interval (e.g., 1 meter)
160+
# # interval = 0.1
161+
# #
162+
# # # Define levels
163+
# # levels = np.arange(np.floor(z_min), np.ceil(z_max) + interval, interval)
164+
# #
165+
# # contours = plt.tricontour(triang, z, levels=levels)
166+
# #
167+
# # # ✅ Each contour level has multiple paths
168+
# # for level, path_collection in zip(contours.levels, contours.get_paths()):
169+
# # for polygon in path_collection.to_polygons():
170+
# # points = [(pt[0], pt[1], float(level)) for pt in polygon]
171+
# # if len(points) > 1:
172+
# # # Create 3D polyline
173+
# # drawer.msp.add_polyline3d(points, dxfattribs={"layer": "CONTOURS"})
174+
#
175+
# drawer.save_dxf()
176+
# # url = drawer.save()
177+
# return jsonify({"message": "Topographic plan generated", "filename": plan.name, "url": "url"}), 200
178+
179+
180+
138181
@app.errorhandler(404)
139182
def not_found(e):
140183
return jsonify({"error": "Resource not found"}), 404

0 commit comments

Comments
 (0)