Files
surveyladder/surveyladder/ladder.py
T
2026-08-07 19:59:27 +05:30

166 lines
5.1 KiB
Python

import csv
from dataclasses import dataclass
from solver import DirectSolver
import openpyxl
from util import is_corner_label
from sketch import Point
@dataclass
class OffsetPoint:
"""
A point on the ladder with a label, distance along the line, and offset from the line.
Together with the definition of the line (start and end points), this allows us to calculate the
position of the point in 2D space.
"""
label: str
distance_on_line: float
offset: float
@dataclass
class LadderSegment:
"""
A segment between two seed points, containing the distance between the seed points and
all the offset points in between.
"""
start_label: str
end_label: str
distance: float
offset_points: list[OffsetPoint]
def extract_all_corner_labels(ladder_segments):
corner_labels = list()
for segment in ladder_segments:
if segment.start_label not in corner_labels:
corner_labels.append(segment.start_label)
if segment.end_label not in corner_labels:
corner_labels.append(segment.end_label)
return sorted(corner_labels)
def load_ladder_xls(filename):
"""
Loads survey ladder from xlsx file
"""
workbook = openpyxl.load_workbook(filename)
worksheet = workbook.active
rows = []
def to_str(val):
if val is None:
return ""
return str(val)
for row_index in range(1, worksheet.max_row + 1):
row = [to_str(worksheet.cell(row=row_index, column=col_index).value) for col_index in range(1, 6)]
rows.append(row)
return create_ladder(rows)
def load_ladder_csv(filename, invert_segments=False):
"""
Loads survey ladder from csv file
"""
rows = load_csv_rows(filename)
return create_ladder(rows, invert_segments=invert_segments)
def load_chainage_csv(filename):
"""
Loads chainage points from csv file
"""
return load_csv_rows(filename)
def load_csv_rows(filename):
with open(filename, newline="") as csvfile:
reader = csv.reader(csvfile)
return [row for row in reader]
def create_ladder(rows, invert_segments=False):
"""
Creates a survey ladder from a list of rows, where each row is a 5-tuple of (left_label, left_offset, distance_on_line, right_offset, right_label).
"""
# column 1 contains the labels of the ladder points on the left side.
# column 2 contains the offsets of the ladder points on the left side.
# column 3 contains the labels of the reference points, and the distances between them.
# column 4 contains the offsets of the ladder points on the right side.
# column 5 contains the distances of the ladder points on the right side.
segments = []
for (start_row, end_row) in get_segment_borders(rows):
start_label = str(rows[start_row][2])
end_label = str(rows[end_row][2])
if invert_segments:
start_label, end_label = end_label, start_label
distance = parse_distance(rows[start_row+1][2])
offset_points = []
for i in range(start_row + 2, end_row):
offset_points.extend(parse_offset_points(rows[i]))
segments.append(LadderSegment(start_label, end_label, distance, offset_points))
return segments
def parse_offset_points(row):
points = []
distance_on_line = parse_distance(row[2])
left_label = row[0]
right_label = row[4]
if left_label:
left_label = str(left_label)
left_offset = parse_distance(row[1])
points.append(OffsetPoint(left_label, distance_on_line, left_offset))
if right_label:
right_label = str(right_label)
right_offset = parse_distance(row[3])
points.append(OffsetPoint(right_label, distance_on_line, -right_offset))
return points
def get_segment_borders(rows):
segment_borders = []
start_row = None
for i, row in enumerate(rows):
if is_corner_label(row[2]):
if start_row is None:
start_row = i
else:
end_row = i
segment_borders.append((start_row, end_row))
start_row = None
return segment_borders
def parse_distance(val):
if isinstance(val, str):
val = val.strip().lower()
if val == "line":
return 0.0
if (val.startswith("(") and val.endswith(")")):
val = val[1:-1]
return float(val)
else:
return float(val)
def get_corner_points(ladder_segments):
origin_label = ladder_segments[0].start_label
corner_labels = extract_all_corner_labels(ladder_segments)
solver = DirectSolver(corner_labels, origin_label=origin_label)
for segment in ladder_segments:
solver.add_distance(segment.start_label, segment.end_label, segment.distance)
for offset_point in segment.offset_points:
if is_corner_label(offset_point.label):
solver.add_offset(offset_point.label, segment.start_label, segment.end_label, offset_point.distance_on_line, offset_point.offset)
P = solver.solve()
points = list()
for label in corner_labels:
points.append(Point(label, P[label], "corner"))
return points