Как стать автором
Обновить

Геодезия: по полю на «питоне»

Время на прочтение 5 мин
Количество просмотров 15K
image

Доброго времени, Хабр!
Немного истории: в ходе учебы программированию, искал я себе реальную задачу, да такую чтобы с пользой. Нашел. Увидел как знакомый геодезист, на работе, считает объем земельного участка. Очень долго и нудно…

Геодезический расчет объемов:
При возведении жилых сооружений, высокотехнологичных помещений, автомобильных и железных дорог, а так – же в целях определения объемов строительных материалов и подсчета объема земляных работ, требуется помощь геодезистов. Они “отстреливают” территорию, разбивая всю площадь на так называемую геосетку, далее полученые точки из прибора выгружаются в autoCAD и высчитывают объем всей территории. Ниже пример геосетки:

image

Каждая точка имеет свои координаты (x,y,z-высота) относительно балтийского моря. По порядку берутся 4 точки как показано на предыдущем изображении и вычисляется исходя из координат этих точек объем участка земли, так вычисляются все объемы получившихся на сетке фигур, плюсуются и в итоге мы имеем объем всей территории.

Работа очень кропотливая, а так как я уже писал, я хотел взяться за реальный проект, взамен на опыт. Поэтому я даже не стал рассматривать тот, вариант, что давно уже все есть до меня.

Несколько дней я был заморочен на алгоритме поиска соседей, в результате вышло не совсем то чего я ждал, алгоритм работал как хотел в связи с тем, что сетка не ровная как в теории, а кривая как ходил помощник геодезиста по полю и ставил отметки, затем я понял что и AutoCAD очень просто сортирует по времени создания точки. Далее немного включившись в AutoCAD я заметил интересный порядок выгрузки точек в XML файл, и нашел возможность назначения каждой точки ID, теперь мы назначили каждой точке имя с левого угла до правого края и так-же понимаясь на ряд выше про именовали все точки (это куда быстрее, чем два дня считать объем территории площадью 500х700 метров). Ниже пример такого готового файла с именованными по порядку точками:

image

По идее объемы считаются по пифагору, но фигуры совершенно разные и как показывает практика очень редко встречаются квадраты и прямоугольники. Поэтому я прибегнул к формуле Герона в своих вычислениях. То есть принцип софтины, что я написал таков: Читаю 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 = self.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 getFileName(self):
        return self.file

    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.

image

Что требуется от нас: открываем XML файл с точками (два файла для примера лежат так-же в на гите (2 корректных и один битый, для теста)), указываем количество точек по ширине поля, ниже указываем высоту относительно уровня моря, и ждем кнопку RUN, получаем объем в поле с право от кнопки запуска.

Если честно я не знаю, можно ли применять этот инструмент на производстве. Но свое желание я удовлетворил. Всем спасибо за внимание.

Файл с 65 точками (ширина 13 точек)
Файл с 78 точками (ширина 13 точек)
Модуль чтения XML файла
Модуль с математическими вычислениями
Модуль визуального оформления
Архив со скомпилированным кодом в Exe
Теги:
Хабы:
+18
Комментарии 12
Комментарии Комментарии 12

Публикации

Истории

Работа

Data Scientist
66 вакансий
QT разработчик
13 вакансий
Python разработчик
136 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн