Доброго времени!
Немного истории: в ходе учебы программированию, искал я себе реальную задачу, да такую чтобы с пользой. Нашел. Увидел как знакомый геодезист, на работе, считает объем земельного участка. Очень долго и нудно…
Геодезический расчет объемов:
При возведении жилых сооружений, высокотехнологичных помещений, автомобильных и железных дорог, а так – же в целях определения объемов строительных материалов и подсчета объема земляных работ, требуется помощь геодезистов. Они “отстреливают” территорию, разбивая всю площадь на так называемую геосетку, далее полученые точки из прибора выгружаются в autoCAD и высчитывают объем всей территории. Ниже пример геосетки:
Каждая точка имеет свои координаты (x,y,z-высота) относительно балтийского моря. По порядку берутся 4 точки как показано на предыдущем изображении и вычисляется исходя из координат этих точек объем участка земли, так вычисляются все объемы получившихся на сетке фигур, плюсуются и в итоге мы имеем объем всей территории.
Работа очень кропотливая, а так как я уже писал, я хотел взяться за реальный проект, взамен на опыт. Поэтому я даже не стал рассматривать тот, вариант, что давно уже все есть до меня.
Несколько дней я был заморочен на алгоритме поиска соседей, в результате вышло не совсем то чего я ждал, алгоритм работал как хотел в связи с тем, что сетка не ровная как в теории, а кривая как ходил помощник геодезиста по полю и ставил отметки, затем я понял что и AutoCAD очень просто сортирует по времени создания точки. Далее немного включившись в AutoCAD я заметил интересный порядок выгрузки точек в XML файл, и нашел возможность назначения каждой точки ID, теперь мы назначили каждой точке имя с левого угла до правого края и так-же понимаясь на ряд выше про именовали все точки (это куда быстрее, чем два дня считать объем территории площадью 500х700 метров). Ниже пример такого готового файла с именованными по порядку точками:
По идее объемы считаются по пифагору, но фигуры совершенно разные и как показывает практика очень редко встречаются квадраты и прямоугольники. Поэтому я прибегнул к формуле Герона в своих вычислениях. То есть принцип софтины, что я написал таков: Читаю XML файл, собираю точки, далее массив с массивами четырех угольников, получаю все фигуры которым нужно посчитать объем, теперь перед расчетом объема я проверяю по сторонам фигур(вектора) и углам на прямоугольник и квадрат (большая редкость, в случае конкретно нашего геодезиста, что таковые будут, и если есть то применяю пифагора) а фигуры типа трапеции, параллелепипеда и прочие я просто принимаю за неизвестный четырех угольник и считаю их по формуле Герона, делю по диагонали фигуру на два треугольника считаю их площади в пространстве (учитываю высоту), высота, берется самая высокая из 4-х точек (так мне объяснил, мой коллега-геодезист) и плюсуя площади двух треугольников далее я уже исходя из общей площади фигуры получаю ее объем.
Плюсую все эти объемы и получаю за 0.2 секунды результат к которому он идет пару дней, проверяли на трех проектах пока данные сходятся, программой даже более точнее получается объем. Продолжаем ее тестировать. Теперь на днях я решил прикрутить этот код к юзабельному интерфейсу, с PyQt4, мой первый опыт работы с написанием графики, ниже я опубликую код который относится только к GUI.
from PyQt4 import QtGui
import sys
import cvgLeicaXmlReader
import cvgMath
class myWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle('CVG2014')
self.setFixedSize(350, 350)
self.setWindowIcon(QtGui.QIcon('static/Icon.png'))
self.setStyleSheet("QMainWindow {background-image: url(static/background.png);}")
self.directory = ''
self.file = ''
self.labelFilename = QtGui.QLabel('Select .XML file with points', self)
self.labelFilename.setFixedWidth(300)
self.labelFilename.setFixedHeight(25)
self.labelFilename.move(10, 5)
self.labelFilename.setStyleSheet("QLabel { background-color: white;
border: 1px solid grey;
color: grey;}")
self.SB_WidthOfXAxis = QtGui.QSpinBox(self)
self.SB_WidthOfXAxis.move(10, 35)
self.SB_WidthOfXAxis.setFixedWidth(50)
self.SB_WidthOfXAxis.setMaximum(9999)
self.labelPointsWidth = QtGui.QLabel('Length of points on X axis', self)
self.labelPointsWidth.setFixedWidth(250)
self.labelPointsWidth.setFixedHeight(25)
self.labelPointsWidth.move(65, 47.5)
self.SB_HeightAboveSeaLevel = QtGui.QDoubleSpinBox(self)
self.SB_HeightAboveSeaLevel.move(10, 75)
self.SB_HeightAboveSeaLevel.setFixedWidth(50)
self.SB_HeightAboveSeaLevel.setRange(-9999.99, 9999.99)
self.labelPointsSeaLevel = QtGui.QLabel('Height above sea level', self)
self.labelPointsSeaLevel.setFixedWidth(250)
self.labelPointsSeaLevel.setFixedHeight(25)
self.labelPointsSeaLevel.move(65, 87)
self.buttonOpenFile = QtGui.QPushButton('...', self)
self.buttonOpenFile.setFixedWidth(30)
self.buttonOpenFile.setFixedHeight(27)
self.buttonOpenFile.move(311, 4)
self.buttonOpenFile.clicked.connect(self.getXmlFile)
self.buttonGetVolume = QtGui.QPushButton('RUN', self)
self.buttonGetVolume.setFixedWidth(52)
self.buttonGetVolume.setFixedHeight(35)
self.buttonGetVolume.move(9, 115)
self.buttonGetVolume.clicked.connect(self.getVolume)
self.showVolume = QtGui.QLabel('Get Vol', self)
self.showVolume.setFixedWidth(140)
self.showVolume.setFixedHeight(32)
self.showVolume.move(72, 117)
self.showVolume.setStyleSheet("QLabel { background-color: white;
border: 1px solid grey;
color: grey;}")
def getVolume(self):
xml_file = win.getFileName()
points = cvgLeicaXmlReader.getPointsFromXmlFile(xml_file)
if(xml_file and points):
# length_of_points = len(points)
QUANTITY_POINTS_AT_X_AXIS = self.SB_WidthOfXAxis.value()
STATIC_HEIGHT = self.SB_HeightAboveSeaLevel.value()
rows = cvgLeicaXmlReader.getRowsFromPoints(points, QUANTITY_POINTS_AT_X_AXIS)
quads = cvgLeicaXmlReader.getAllQuads(rows)
volumes = []
if (STATIC_HEIGHT or QUANTITY_POINTS_AT_X_AXIS) != 0:
for quadrangle in quads:
Quadrangle_type = cvgMath.getTypeQuadrangle(quadrangle)
v = cvgMath.getVolumeQuadrangle(quadrangle, Quadrangle_type, STATIC_HEIGHT)
volumes.append(v)
else:
volumes = 0
volumes = 0 if STATIC_HEIGHT == 0 else (round(sum(volumes), 3))
result = '-'+str(volumes) if STATIC_HEIGHT < 0 else str(volumes)
result = '0' if result == '-0' else result
self.showVolume.setStyleSheet("QLabel { background-color: white;
border: 1px solid grey;
color: grey;}")
self.showVolume.setText(result)
else:
self.showVolume.setText('Select the correct file!')
self.showVolume.setStyleSheet("QLabel { background-color: white;
border: 1px solid grey;
color: red;
font-weight: bold}")
def getXmlFile(self):
sender = self.sender()
path = QtGui.QFileDialog.getOpenFileName(sender, 'Open Xml file with points', self.directory, 'XML *.xml')
fileName = path[path.rfind('/')+1:]
self.directory = path[:path.rfind('/')]
if(len(path) > 54):
start = len(path)-54
pathSlice = path[start:]
pathSlice = pathSlice[pathSlice.find('/'):]
pathSlice = '..'+pathSlice
else:
pathSlice = path
self.labelFilename.setText(pathSlice)
print(len(path))
self.file = path
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = myWindow()
win.show()
app.exec_()
С остальными модулями кому интересно, можно ознакомиться на гитхабе
Так же я загрузил .rar архив со скомпилированным кодом в exe для windows.
Что требуется от нас: открываем XML файл с точками (два файла для примера лежат так-же в на гите (2 корректных и один битый, для теста)), указываем количество точек по ширине поля, ниже указываем высоту относительно уровня моря, и ждем кнопку RUN, получаем объем в поле с право от кнопки запуска.
Если честно я не знаю, можно ли применять этот инструмент на производстве. Но свое желание я удовлетворил. Всем спасибо за внимание.
Файл с 65 точками (ширина 13 точек)
Файл с 78 точками (ширина 13 точек)
Модуль чтения XML файла
Модуль с математическими вычислениями
Модуль визуального оформления
Архив со скомпилированным кодом в Exe
Автор: quas