1+ import os
12from dotenv import load_dotenv
23load_dotenv () # reads .env into environment
34
4- import os
5- import math
5+ from cadastral import CadastralPlan
66
77from 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-
139app = Flask (__name__ )
1410app .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 ("/" )
2414def home ():
@@ -28,113 +18,166 @@ def home():
2818def 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 )
139182def not_found (e ):
140183 return jsonify ({"error" : "Resource not found" }), 404
0 commit comments