166 lines
5.1 KiB
Python
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
|