[Git][debian-gis-team/totalopenstation][master] 8 commits: Merge branch 'upstream' of salsa.debian.org:debian-gis-team/totalopenstation into upstream

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Fri Jan 2 15:24:48 GMT 2026



Bas Couwenberg pushed to branch master at Debian GIS Project / totalopenstation


Commits:
6b239e27 by Matteo F. Vescovi at 2025-03-25T21:29:41+01:00
Merge branch 'upstream' of salsa.debian.org:debian-gis-team/totalopenstation into upstream

- - - - -
3b8fcb2e by Bas Couwenberg at 2026-01-02T15:57:15+01:00
New upstream version 0.7.2
- - - - -
51a213d6 by Bas Couwenberg at 2026-01-02T15:57:16+01:00
Update upstream source from tag 'upstream/0.7.2'

Update to upstream version '0.7.2'
with Debian dir 332e543881670ee8f2d0d28045bba2707094cb58
- - - - -
c4f80d5b by Bas Couwenberg at 2026-01-02T15:57:28+01:00
New upstream release.

- - - - -
b30b9cff by Bas Couwenberg at 2026-01-02T16:06:51+01:00
Update copyright file.

- - - - -
8273a31d by Bas Couwenberg at 2026-01-02T16:08:40+01:00
Annotate build dependencies with nocheck.

- - - - -
b7a7ba86 by Bas Couwenberg at 2026-01-02T16:10:17+01:00
Add pybuild-plugin-pyproject to build dependencies.

- - - - -
5c29b562 by Bas Couwenberg at 2026-01-02T16:23:11+01:00
Fix pytest invocation.

- - - - -


25 changed files:

- .github/workflows/pyinstaller.yml
- .github/workflows/pytest.yml
- debian/changelog
- debian/control
- debian/copyright
- debian/rules
- docs/conf.py
- docs/contributing/main.rst
- docs/output_formats/of_implemented.rst
- docs/requirements.txt
- + pyproject.toml
- + sample_data/leica_gsi/RILIEVO.gsi
- − setup.py
- totalopenstation-gui.spec
- totalopenstation/__init__.py
- totalopenstation/formats/leica_gsi.py
- totalopenstation/output/__init__.py
- totalopenstation/output/tops_csv.py
- + totalopenstation/scripts/__init__.py
- scripts/totalopenstation-cli-connector.py → totalopenstation/scripts/cli_connector.py
- scripts/totalopenstation-cli-parser.py → totalopenstation/scripts/cli_parser.py
- scripts/totalopenstation-gui.py → totalopenstation/scripts/gui.py
- totalopenstation/tests/test_csv.py
- totalopenstation/tests/test_leica_gsi.py
- tox.ini


Changes:

=====================================
.github/workflows/pyinstaller.yml
=====================================
@@ -13,10 +13,10 @@ jobs:
 
     steps:
     - uses: actions/checkout at v2
-    - name: Set up Python 3.8
+    - name: Set up Python 3.9
       uses: actions/setup-python at v2
       with:
-        python-version: 3.8
+        python-version: 3.9
     - name: Install dependencies and package
       run: |
         python -m pip install --upgrade pip


=====================================
.github/workflows/pytest.yml
=====================================
@@ -5,36 +5,35 @@ name: pytest
 
 on:
   push:
-    branches: [ main ]
+    branches: [main]
   pull_request:
-    branches: [ main ]
+    branches: [main]
 
 jobs:
   build:
-
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        python-version: ["3.8", "3.9", "3.10", "3.11", "pypy3.8"]
+        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "pypy3.10"]
         os: [ubuntu-latest, macos-latest, windows-latest]
 
     steps:
-    - uses: actions/checkout at v3
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python at v4
-      with:
-        python-version: ${{ matrix.python-version }}
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        pip install flake8 pytest
-        pip install .
-    - name: Lint with flake8
-      run: |
-        # stop the build if there are Python syntax errors or undefined names
-        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
-        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
-        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
-    - name: Test with pytest
-      run: |
-        pytest
+      - uses: actions/checkout at v3
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python at v4
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install flake8 pytest
+          pip install .
+      - name: Lint with flake8
+        run: |
+          # stop the build if there are Python syntax errors or undefined names
+          flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+          # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+          flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+      - name: Test with pytest
+        run: |
+          pytest


=====================================
debian/changelog
=====================================
@@ -1,12 +1,16 @@
-totalopenstation (0.5.3-2) UNRELEASED; urgency=medium
+totalopenstation (0.7.2-1) UNRELEASED; urgency=medium
 
   * Team upload.
+  * New upstream release.
   * Update lintian overrides.
   * Drop Rules-Requires-Root: no, default since dpkg 1.22.13.
   * Use test-build-validate-cleanup instead of test-build-twice.
   * Make pytest output verbose.
+  * Update copyright file.
+  * Annotate build dependencies with nocheck.
+  * Add pybuild-plugin-pyproject to build dependencies.
 
- -- Bas Couwenberg <sebastic at debian.org>  Fri, 12 Sep 2025 17:51:30 +0200
+ -- Bas Couwenberg <sebastic at debian.org>  Fri, 02 Jan 2026 15:57:21 +0100
 
 totalopenstation (0.5.3-1) unstable; urgency=medium
 


=====================================
debian/control
=====================================
@@ -6,9 +6,10 @@ Priority: optional
 Build-Depends: debhelper-compat (= 13),
                dh-python,
                dh-sequence-python3,
+               pybuild-plugin-pyproject,
                python3,
                python3-pygeoif,
-               python3-pytest,
+               python3-pytest <!nocheck>,
                python3-serial,
                python3-setuptools,
                python3-tk


=====================================
debian/copyright
=====================================
@@ -2,14 +2,22 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 Upstream-Name: Total Open Station
 Upstream-Contact: Stefano Costa <steko at iosa.it>
 Source: https://github.com/totalopenstation/totalopenstation
-Comment: The upstream tarball is repacked to exclude generated files.
 
 Files: *
-Copyright: 2008-2011 Stefano Costa <steko at iosa.it>
+Copyright: 2008-2025, Stefano Costa <steko at iosa.it>
+                2023, Sebastian Gutwein <bas at rdgland.com>
+                2021, Enzo Cocca <enzo.ccc at gmail.com>
+           2015-2019, Damien Gaignon <damien.gaignon at gmail.com>
+           2008-2012, Luca Bianconi <luxetluc at yahoo.it>
+                2010, Cristiano Moscaritolo <cristianomoscaritolo at yahoo.it>
+                2010, Enza Battiante <enza.battiante at alice.it>
+                2010, Olga Pastore <olga.pastore at gmail.com>
+                2010, Raffaele Fanelli <rfl.fanelli at libero.it>
+                2009, Alessandro Bezzi <alessandro.bezzi at arc-team.com>
 License: GPL-3+
 
 Files: debian/*
-Copyright: 2012-2025 Matteo F. Vescovi <mfv at debian.org>
+Copyright: 2012-2025, Matteo F. Vescovi <mfv at debian.org>
 License: GPL-3+
 
 License: GPL-3+


=====================================
debian/rules
=====================================
@@ -6,6 +6,8 @@ export PYBUILD_BEFORE_TEST=cp -r {dir}/sample_data {build_dir}
 export PYBUILD_AFTER_TEST=rm -rf {build_dir}/sample_data
 ifeq ($(PYBUILD_AUTOPKGTEST),1)
 export PYBUILD_TEST_ARGS=-v --pyargs totalopenstation
+else
+export PYBUILD_TEST_ARGS=-v $(CURDIR)/totalopenstation/tests
 endif
 
 INSTDIR=debian/tmp


=====================================
docs/conf.py
=====================================
@@ -60,9 +60,9 @@ author = 'Stefano Costa'
 # built documents.
 #
 # The short X.Y version.
-version = '0.5'
+version = '0.7'
 # The full version, including alpha/beta/rc tags.
-release = '0.5.3'
+release = '0.7.2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.


=====================================
docs/contributing/main.rst
=====================================
@@ -107,21 +107,6 @@ independently, with the exception of the user interfaces.
 In other words, the classes for reading specific formats and those for writing
 well-known formats are entirely usable on their own.
 
-This is a feature.
-
-Example: a web app for converting total station data
-====================================================
-
-If you want to see how to write a web app to convert total station
-data in 50 lines of Python code, check out `TOPS in the Cloud
-<https://bitbucket.org/steko/tops-cloud/overview>`_. It is made with
-`Flask <http://flask.pocoo.org/>`_ and shows how to use Total Open
-Station as a programming library.
-
-.. warning::
-    TOPS in the Cloud is not maintained and does not receive security
-    updates. Please don't use it in production.
-
 ==================================
 Developing with Total Open Station
 ==================================
@@ -304,8 +289,8 @@ For more in-depth knowledge of classes, we encourage reading the code @ `Github`
 Documentation
 =============
 
-The documentation is included in the source tree, and is published
-online at `http://totalopenstation.readthedocs.org/ <http://totalopenstation.readthedocs.org/>`_.
+The documentation is included in the ``docs`` directory of the source tree, and is published
+online at `http://totalopenstation.readthedocs.io/ <http://totalopenstation.readthedocs.io/>`_.
 
 Manual pages for the three scripts provided with TOPS are not
 available at the moment.
@@ -321,12 +306,12 @@ A *source distribution* is made using::
 
   python setup.py sdist
 
-A *built distribution* is made using (e.g. for Windows installer)::
+A *built distribution* is made using the *wheel* format (this requires the ``wheel`` package)::
 
-  python setup.py bdist --formats wininst
+  python setup.py bdist_wheel
 
 We are currently following the `Python Packaging User Guide
-<https://packaging.python.org/en/latest/distributing.html>`_ and
+<https://packaging.python.org/>`_ and
 distributing sources and *wheels*.
 
 Windows portable app


=====================================
docs/output_formats/of_implemented.rst
=====================================
@@ -58,6 +58,13 @@ Yet, this format is not parametric and values return are the following::
 
     PID, type, Point Name, x, y, angle, z_angle, distance, th, ih, circle, station
 
+Trimble CSV
+-----------
+
+.. versionadded:: 0.7
+
+This module contains also a variant CSV output with the same data columns,
+but in a different order, Trimble CSV (used for LandSurveyCodesImport QGIS plugin)::
 
 ======================
 :mod:`tops_dat` -- DAT


=====================================
docs/requirements.txt
=====================================
@@ -1,4 +1 @@
 Sphinx==8.1.3
-pygeoif==0.7
-pytest==6.2.5
-pyserial==3.5


=====================================
pyproject.toml
=====================================
@@ -0,0 +1,55 @@
+[build-system]
+requires = ["setuptools >= 77.0.3"]
+build-backend = "setuptools.build_meta"
+
+[project]
+dynamic = ["version", "readme"]
+name = "totalopenstation"
+dependencies = ["pyserial==3.5", "pygeoif==1.4.0"]
+requires-python = ">= 3.9"
+authors = [
+    { name = "Stefano Costa" },
+    { name = "Damien Gaignon" },
+    { name = "Luca Bianconi" },
+]
+description = "Download and export survey data from your total station"
+license = "GPL-3.0"
+keywords = ["survey", "geodimeter", "fieldwork", "format conversion"]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "Environment :: Console",
+    "Environment :: X11 Applications",
+    "Intended Audience :: End Users/Desktop",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
+    "Topic :: Scientific/Engineering :: GIS",
+]
+
+[project.urls]
+Homepage = "https://tops.iosa.it/"
+Documentation = "https://totalopenstation.readthedocs.io/"
+Repository = "https://github.com/totalopenstation/totalopenstation.git"
+Issues = "https://github.com/totalopenstation/totalopenstation/issues"
+
+[project.scripts]
+totalopenstation-cli-parser = "totalopenstation.scripts.cli_parser:cli_parser"
+totalopenstation-cli-connector = "totalopenstation.scripts.cli_connector:cli_connector"
+
+[project.gui-scripts]
+totalopenstation-gui = "totalopenstation.scripts.gui:gui"
+
+[tool.setuptools.dynamic]
+version = { attr = "totalopenstation.__version__" }
+readme = { file = ["README.rst"] }
+
+[tool.setuptools]
+packages = ["totalopenstation"]
+
+[dependency-groups]
+test = ["pytest>=5.1"]


=====================================
sample_data/leica_gsi/RILIEVO.gsi
=====================================
@@ -0,0 +1 @@
+
110001+00000100 21.102+11545200 22.102+09885300 31..00+00000000 32..00+00000000 


110002+00000101 21.102+00000000 22.102+09681000 31..00+00000000 32..00+00000000 


110003+00000102 21.102+13247100 22.102+10378200 31..00+00005165 32..00+00005156 


110004+00000103 21.102+13603500 22.102+10381400 31..00+00005351 32..00+00005341 


110005+00000104 21.102+14438100 22.102+10620800 31..00+00004636 32..00+00004614 


110006+00000105 21.102+14579100 22.102+10685200 31..00+00004232 32..00+00004208 


110007+00000106 21.102+15288600 22.102+11935000 31..00+00003966 32..00+00003784 


110008+00000107 21.102+02838500 22.102+10620000 31..00+00003866 32..00+00003848 


110009+00000108 21.102+05895200 22.102+10233100 31..00+00006454 32..00+00006450 


110010+00000109 21.102+07337500 22.102+10190100 31..00+00011374 32..00+00011369 


110011+00000110 21.102+08174000 22.102+10127600 31..00+00020332 32..00+00020328 


110012+00000111 21.102+09088000 22.102+09955300 31..00+00026759 32..00+00026758 


110013+00000112 21.102+14020300 22.102+09968000 31..00+00036927 32..00+00036927 


110014+00000113 21.102+18508800 22.102+10066300 31..00+00033477 32..00+00033475 


110015+00000114 21.102+18689100 22.102+09962400 31..00+00050672 32..00+00050671 


110016+00000115 21.102+18446500 22.102+10038200 31..00+00065824 32..00+00065823 


110017+00000116 21.102+16740300 22.102+10037300 31..00+00072037 32..00+00072036 


110018+00000117 21.102+16672100 22.102+09992200 31..00+00069647 32..00+00069647 


110019+00000118 21.102+23066200 22.102+10080900 31..00+00041345 32..00+00041342 


110020+00000119 21.102+28207100 22.102+10125200 31..00+00024342 32..00+00024337 


110021+00000120 21.102+28333600 22.102+10062400 31..00+00067652 32..00+00067649 


110022+00000121 21.102+25011800 22.102+10043700 31..00+00083285 32..00+00083283 


110023+00000122 21.102+04392100 22.102+10354100 31..00+00004600 32..00+00004593 


\ No newline at end of file


=====================================
setup.py deleted
=====================================
@@ -1,37 +0,0 @@
-from setuptools import setup, find_packages
-
-import totalopenstation
-
-setup(
-    name='totalopenstation',
-    version=totalopenstation.__version__,
-    author='Stefano Costa',
-    author_email='steko at iosa.it',
-    packages=find_packages(exclude=['ez_setup', 'examples', 'tests', 'gui']),
-    scripts=['scripts/totalopenstation-gui.py',
-             'scripts/totalopenstation-cli-parser.py',
-             'scripts/totalopenstation-cli-connector.py'],
-    url='https://tops.iosa.it/',
-    license='GNU GPLv3',
-    description='Download and export survey data from your total station',
-    long_description=open('README.rst').read(),
-    classifiers=[
-        'Development Status :: 4 - Beta',
-        'Environment :: Console',
-        'Environment :: X11 Applications',
-        'Intended Audience :: End Users/Desktop',
-        'License :: OSI Approved :: GNU General Public License (GPL)',
-        'Operating System :: OS Independent',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 3 :: Only',
-        'Programming Language :: Python :: 3.6',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Topic :: Scientific/Engineering :: GIS',
-        ],
-    keywords='survey geodimeter',
-    install_requires=['pyserial==3.4', 'pygeoif==1.4.0'],
-    tests_require=['pytest>=5.1'],
-    include_package_data = True,
-    zip_safe = False,
-)


=====================================
totalopenstation-gui.spec
=====================================
@@ -1,5 +1,5 @@
 # -*- mode: python -*-
-a = Analysis(['scripts/totalopenstation-gui.py'],
+a = Analysis(['./totalopenstation/scripts/gui.py'],
              pathex=['.'],
              hiddenimports=[
                  'csv',
@@ -7,6 +7,7 @@ a = Analysis(['scripts/totalopenstation-gui.py'],
                  'numbers',
                  'totalopenstation.formats',
                  'totalopenstation.formats.carlson_rw5',
+                 'totalopenstation.formats.landxml',
                  'totalopenstation.formats.leica_gsi',
                  'totalopenstation.formats.leica_tcr_705',
                  'totalopenstation.formats.leica_tcr_1205',
@@ -31,6 +32,7 @@ a = Analysis(['scripts/totalopenstation-gui.py'],
                  'totalopenstation.output.tops_dat',
                  'totalopenstation.output.tops_dxf',
                  'totalopenstation.output.tops_geojson',
+                 'totalopenstation.output.tops_landxml',
                  'totalopenstation.output.tops_sql',
                  'totalopenstation.output.tops_txt',],
              hookspath=None,


=====================================
totalopenstation/__init__.py
=====================================
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
 
-__version__  = '0.5.3'
+__version__  = '0.7.2'
 
 import logging


=====================================
totalopenstation/formats/leica_gsi.py
=====================================
@@ -208,7 +208,15 @@ class FormatParser(Parser):
                     'data': t[7:],
                 }
                 self.tdict[data['wordindex']] = data
-
+            
+            if list(self.tdict.keys()) == (['11', '21', '22', '31', '32']):
+                self.tdict['84'] = {'wordindex': '84', 'info': '...0', 'sign': '+', 'data': '00000000'}
+                self.tdict['85'] = {'wordindex': '85', 'info': '...0', 'sign': '+', 'data': '00000000'}
+                self.tdict['86'] = {'wordindex': '86', 'info': '...0', 'sign': '+', 'data': '00000000'}
+                self.tdict['87'] = {'wordindex': '87', 'info': '...0', 'sign': '+', 'data': '00000000'}
+                self.tdict['88'] = {'wordindex': '88', 'info': '...0', 'sign': '+', 'data': '00000000'}
+                logger.info(f"Old GSI version detected, some values have been set to 0 by default")
+                logger.info(f"see https://github.com/totalopenstation/totalopenstation/issues/168")
             try:
                 pid = int(self.tdict['11']['info'])
                 text = self.tdict['11']['data'].lstrip('0')


=====================================
totalopenstation/output/__init__.py
=====================================
@@ -7,19 +7,19 @@ class Builder:
     def __init__(self, data):
         """Init method which **must** be overridden in the child class
         to have a working builder.
-        
+
         Args:
         data (:class:`formats.Parser`): A list of :class:`formats.Feature`
         """
 
         self.data = data
-    
+
     def process(self):
         """Action for building the output string.
-        
+
         This method **must** be overridden in the child class
         to have a working builder.
-        
+
         Process the input data (processing data).
         This is because we want to keep the generation of output separated from
         saving it to disk.
@@ -38,5 +38,6 @@ BUILTIN_OUTPUT_FORMATS = {
     'dat': ('tops_dat', 'OutputFormat', 'DAT'),
     'txt': ('tops_txt', 'OutputFormat', 'Text'),
     'geojson': ('tops_geojson', 'OutputFormat', 'GeoJSON'),
-    'landxml': ('tops_landxml', 'OutputFormat', 'LandXML')
+    'landxml': ('tops_landxml', 'OutputFormat', 'LandXML'),
+    'trimblecsv': ('tops_csv', 'TrimbleOutputFormat', 'Trimble CSV')
     }


=====================================
totalopenstation/output/tops_csv.py
=====================================
@@ -75,3 +75,72 @@ class OutputFormat(Builder):
             self.writer.writerow(row)
 
         return self.output.getvalue()
+
+class TrimbleOutputFormat(OutputFormat):
+    """
+    Exports points data in Trimble CSV format,
+    used for LandSurveyCodesImport QGIS plugin.
+
+    ``data`` should be an iterable containing Feature objects.
+    """
+
+    def __init__(self, data):
+        self.data = data
+        self.output = io.StringIO()
+        fieldnames = [
+            "id",
+            "x",
+            "y",
+            "z",
+            "type",
+            "point_name",
+            "angle",
+            "z_angle",
+            "distance",
+            "th",
+            "ih",
+            "circle",
+            "station",
+        ]
+        self.writer = csv.DictWriter(
+            self.output, quoting=csv.QUOTE_NONNUMERIC, fieldnames=fieldnames
+        )
+        self.writer.writeheader()
+
+    def process(self):
+
+        for feature in self.data:
+            row = {
+                "id": feature.id,
+                "type": feature.desc,
+                "x": feature.geometry.x,
+                "y": feature.geometry.y,
+            }
+
+            try:  # not all input formats include z coordinates
+                row["z"] = feature.geometry.z
+            except ValueError:
+                row["z"] = ""
+
+            # a few cases with simple yes/no logic
+            for prop in ["point_name", "ih", "circle", "z_angle", "th"]:
+                row[prop] = feature.properties.get(
+                    prop, ""
+                )  # empty string as default value
+
+            # not all input formats include azimuth/angle
+            row["angle"] = feature.properties.get(
+                "azimuth", feature.properties.get("angle", "")
+            )
+
+            # not all input formats include distance
+            row["distance"] = feature.properties.get(
+                "slope_dist", feature.properties.get("horizontal_dist", "")
+            )
+
+            # not all input formats include station name
+            row["station"] = feature.properties.get("st_name", "")
+
+            self.writer.writerow(row)
+
+        return self.output.getvalue()


=====================================
totalopenstation/scripts/__init__.py
=====================================


=====================================
scripts/totalopenstation-cli-connector.py → totalopenstation/scripts/cli_connector.py
=====================================
@@ -1,7 +1,7 @@
 #! /usr/bin/env python3
 # -*- coding: utf-8 -*-
 # filename: totalopenstation-cli-connector.py
-# Copyright 2021 Stefano Costa <steko at iosa.it>
+# Copyright 2025 Stefano Costa <steko at iosa.it>
 
 # This file is part of Total Open Station.
 
@@ -34,71 +34,76 @@ from totalopenstation.models import BUILTIN_MODELS
 t = gettext.translation('totalopenstation', './locale', fallback=True)
 _ = t.gettext
 
-usage = _("Usage: %prog [option] arg1 [option] arg2 ...")
-
-parser = OptionParser(usage=usage)
-parser.add_option("-m",
-                "--model",
-                action="store",
-                type="string",
-                dest="model",
-                help=_("Select input MODEL"),
-                metavar="MODEL")
-parser.add_option("-p",
-                "--port",
-                action="store",
-                type="string",
-                dest="port",
-                help=_("Select input SERIAL PORT"),
-                metavar="PORT")
-parser.add_option("-o",
-                "--outfile",
-                action="store",
-                type="string",
-                dest="outfile",
-                help=_("Select output FILE (do not specify for stdout)"),
-                metavar="FILE")
-
-(options, args) = parser.parse_args()
-
-if not (options.model and options.port):
-    sys.exit(_("Please specify your model and the port to download from"))
-
-modelclass = BUILTIN_MODELS[options.model]
-
-# import input format parser
-if isinstance(modelclass, tuple):
+def cli_connector():
+    usage = _("Usage: %prog [option] arg1 [option] arg2 ...")
+
+    parser = OptionParser(usage=usage)
+    parser.add_option("-m",
+                    "--model",
+                    action="store",
+                    type="string",
+                    dest="model",
+                    help=_("Select input MODEL"),
+                    metavar="MODEL")
+    parser.add_option("-p",
+                    "--port",
+                    action="store",
+                    type="string",
+                    dest="port",
+                    help=_("Select input SERIAL PORT"),
+                    metavar="PORT")
+    parser.add_option("-o",
+                    "--outfile",
+                    action="store",
+                    type="string",
+                    dest="outfile",
+                    help=_("Select output FILE (do not specify for stdout)"),
+                    metavar="FILE")
+
+    (options, args) = parser.parse_args()
+
+    if not (options.model and options.port):
+        sys.exit(_("Please specify your model and the port to download from"))
+
+    modelclass = BUILTIN_MODELS[options.model]
+
+    # import input format parser
+    if isinstance(modelclass, tuple):
+        try:
+            # builtin format parser
+            mod, cls, name = modelclass
+            modelclass = getattr(
+                __import__('totalopenstation.models.%s' % mod, None, None, [cls]), cls)
+        except ImportError as msg:
+            sys.exit(_('Error loading the required model module: %s') % msg)
+
+    station = modelclass(options.port)
     try:
-        # builtin format parser
-        mod, cls, name = modelclass
-        modelclass = getattr(
-            __import__('totalopenstation.models.%s' % mod, None, None, [cls]), cls)
-    except ImportError as msg:
-        sys.exit(_('Error loading the required model module: %s') % msg)
-
-station = modelclass(options.port)
-try:
-    station.close()  # sometimes the port will be already open for no reason
-    station.open()
-except serial.SerialException as detail:
-    sys.exit(detail)
-
-print(_("Now you can start download from %s device") % options.model)
-
-station.start()
-station.dl_started.wait()
-print(_("Download started..."))
-station.dl_finished.wait()
-print(_("Download finished..."))
-result = station.result
-
-if options.outfile:
-    if not os.path.exists(options.outfile):
-        e = open(options.outfile, "wb")
-        e.write(result)
-        e.close()
-        print(_("Downloaded data saved to out file %s") % options.outfile)
+        station.close()  # sometimes the port will be already open for no reason
+        station.open()
+    except serial.SerialException as detail:
+        sys.exit(detail)
+
+    print(_("Now you can start download from %s device") % options.model)
+
+    station.start()
+    station.dl_started.wait()
+    print(_("Download started..."))
+    station.dl_finished.wait()
+    print(_("Download finished..."))
+    result = station.result
+
+    if options.outfile:
+        if not os.path.exists(options.outfile):
+            e = open(options.outfile, "wb")
+            e.write(result)
+            e.close()
+            print(_("Downloaded data saved to out file %s") % options.outfile)
+        else:
+            sys.exit(_("Specified output file already exists\n"))
     else:
-        sys.exit(_("Specified output file already exists\n"))
-else:
-    sys.stdout.write(result)
+        sys.stdout.write(result)
+
+
+if __name__ == '__main__':
+    cli_connector()


=====================================
scripts/totalopenstation-cli-parser.py → totalopenstation/scripts/cli_parser.py
=====================================
@@ -36,149 +36,148 @@ import totalopenstation.output
 t = gettext.translation('totalopenstation', './locale', fallback=True)
 _ = t.gettext
 
-usage = _("Usage: %prog [option] arg1 [option] arg2 ...")
-
-parser = OptionParser(usage=usage)
-parser.add_option("-i",
-                "--infile",
-                action="store",
-                type="string",
-                dest="infile",
-                help=_("Select input FILE  (do not specify for stdin)"),
-                metavar="FILE")
-parser.add_option("-o",
-                "--outfile",
-                action="store",
-                type="string",
-                dest="outfile",
-                help=_("Select output FILE (do not specify for stdout)"),
-                metavar="FILE")
-parser.add_option("-f",
-                "--input-format",
-                action="store",
-                type="string",
-                dest="informat",
-                help=_("Select input FORMAT"),
-                metavar="FORMAT")
-parser.add_option("--2d",
-                  action="store_true",
-                  dest="xy_only",
-                  help=_("Exclude Z coordinates, output only 2D data"),
-                  metavar="ONLY2D")
-parser.add_option("-t",
-                "--output-format",
-                action="store",
-                type="string",
-                dest="outformat",
-                help=_("Select input FORMAT"),
-                metavar="FORMAT")
-parser.add_option("-r",
-                "--raw",
-                action="store_true",
-                dest="raw",
-                help=_("Enhanced parsed file process"))
-parser.add_option(
-                "--overwrite",
-                action="store_true",
-                dest="overwrite",
-                default=False,
-                help=_("Overwrite existing output file"))
-parser.add_option(
-    "--list",
-    action="store_true",
-    dest="list",
-    default=False,
-    help=_("List the available input and output formats"))
-parser.add_option(
-                "--log",
-                action="store",
-                dest="loglevel",
-                default="WARNING",
-                type="string",
-                help=_("Minimum log level"))
-parser.add_option(
-                "--logtofile",
-                action="store_true",
-                dest="logotfile",
-                default=False,
-                help=_("Log to file"))
-
-
-(options, args) = parser.parse_args()
-
-logger = logging.getLogger()
-logger.setLevel(options.loglevel.upper())
-if options.logotfile:
-    handler = logging.FileHandler("tops.log")
-else:
-    handler = logging.StreamHandler()
-formatter = logging.Formatter('%(asctime)s -- %(filename)s -- %(levelname)s -- %(funcName)s -- %(message)s')
-handler.setFormatter(formatter)
-logger.addHandler(handler)
-
-def list_formats():
-    '''Print a list of the supported input and output formats.'''
-
-    mod_string = _("List of supported input formats:\n%s\n") % ('-' * 30)
-    for k, v in sorted(totalopenstation.formats.BUILTIN_INPUT_FORMATS.items()):
-        mod_string += "%s%s\n" % (k.ljust(20), v[2])
-    mod_string += "\n\n"
-
-    mod_string += _("List of supported output formats:\n%s\n") % ('-' * 30)
-    for k, v in sorted(totalopenstation.output.BUILTIN_OUTPUT_FORMATS.items()):
-        mod_string += "%s%s\n" % (k.ljust(20), v[2])
-    mod_string += "\n"
-    return mod_string
-
-if options.list:
-    sys.stdout.write(list_formats())
-    sys.exit()
-
-def exit_with_error(message):
-    sys.exit(_("\nError:\n%(message)s\n\n%(formats)s") % {'message': message,
-                                                          'formats': list_formats()})
-
-if options.informat:
-    try:
-        inputclass = totalopenstation.formats.BUILTIN_INPUT_FORMATS[options.informat]
-    except KeyError as message:
-        exit_with_error(_('%s is not a valid input format') % message)
+def  cli_parser():
+    usage = _("Usage: %prog [option] arg1 [option] arg2 ...")
+
+    parser = OptionParser(usage=usage)
+    parser.add_option("-i",
+                    "--infile",
+                    action="store",
+                    type="string",
+                    dest="infile",
+                    help=_("Select input FILE  (do not specify for stdin)"),
+                    metavar="FILE")
+    parser.add_option("-o",
+                    "--outfile",
+                    action="store",
+                    type="string",
+                    dest="outfile",
+                    help=_("Select output FILE (do not specify for stdout)"),
+                    metavar="FILE")
+    parser.add_option("-f",
+                    "--input-format",
+                    action="store",
+                    type="string",
+                    dest="informat",
+                    help=_("Select input FORMAT"),
+                    metavar="FORMAT")
+    parser.add_option("--2d",
+                    action="store_true",
+                    dest="xy_only",
+                    help=_("Exclude Z coordinates, output only 2D data"),
+                    metavar="ONLY2D")
+    parser.add_option("-t",
+                    "--output-format",
+                    action="store",
+                    type="string",
+                    dest="outformat",
+                    help=_("Select input FORMAT"),
+                    metavar="FORMAT")
+    parser.add_option("-r",
+                    "--raw",
+                    action="store_true",
+                    dest="raw",
+                    help=_("Enhanced parsed file process"))
+    parser.add_option(
+                    "--overwrite",
+                    action="store_true",
+                    dest="overwrite",
+                    default=False,
+                    help=_("Overwrite existing output file"))
+    parser.add_option(
+        "--list",
+        action="store_true",
+        dest="list",
+        default=False,
+        help=_("List the available input and output formats"))
+    parser.add_option(
+                    "--log",
+                    action="store",
+                    dest="loglevel",
+                    default="WARNING",
+                    type="string",
+                    help=_("Minimum log level"))
+    parser.add_option(
+                    "--logtofile",
+                    action="store_true",
+                    dest="logotfile",
+                    default=False,
+                    help=_("Log to file"))
+
+
+    (options, args) = parser.parse_args()
+
+    logger = logging.getLogger()
+    logger.setLevel(options.loglevel.upper())
+    if options.logotfile:
+        handler = logging.FileHandler("tops.log")
     else:
-        if isinstance(inputclass, tuple):
-            try:
-                # builtin format parser
-                mod, cls, name = inputclass
-                inputclass = getattr(importlib.import_module('totalopenstation.formats.%s' % mod), cls)
-            except ImportError as message:
-                exit_with_error(message)
-else:
-    sys.exit(_("Please specify an input format"))
-
-if options.outformat:
-    try:
-        outputclass = totalopenstation.output.BUILTIN_OUTPUT_FORMATS[options.outformat]
-    except KeyError as message:
-        exit_with_error(_('%s is not a valid output format') % message)
-    else:
-        if isinstance(outputclass, tuple):
-            try:
-                # builtin output builder
-                mod, cls, name = outputclass
-                outputclass = getattr(importlib.import_module('totalopenstation.output.%s' % mod), cls)
-            except ImportError as message:
-                exit_with_error(message)
-
-if options.infile:
-    infile = open(options.infile, 'r').read()
-else:
-    if sys.stdin.isatty():
-        sys.exit(_('No input data!'))
+        handler = logging.StreamHandler()
+    formatter = logging.Formatter('%(asctime)s -- %(filename)s -- %(levelname)s -- %(funcName)s -- %(message)s')
+    handler.setFormatter(formatter)
+    logger.addHandler(handler)
+
+    def list_formats():
+        '''Print a list of the supported input and output formats.'''
+
+        mod_string = _("List of supported input formats:\n%s\n") % ('-' * 30)
+        for k, v in sorted(totalopenstation.formats.BUILTIN_INPUT_FORMATS.items()):
+            mod_string += "%s%s\n" % (k.ljust(20), v[2])
+        mod_string += "\n\n"
+
+        mod_string += _("List of supported output formats:\n%s\n") % ('-' * 30)
+        for k, v in sorted(totalopenstation.output.BUILTIN_OUTPUT_FORMATS.items()):
+            mod_string += "%s%s\n" % (k.ljust(20), v[2])
+        mod_string += "\n"
+        return mod_string
+
+    if options.list:
+        sys.stdout.write(list_formats())
+        sys.exit()
+
+    def exit_with_error(message):
+        sys.exit(_("\nError:\n%(message)s\n\n%(formats)s") % {'message': message,
+                                                            'formats': list_formats()})
+
+    if options.informat:
+        try:
+            inputclass = totalopenstation.formats.BUILTIN_INPUT_FORMATS[options.informat]
+        except KeyError as message:
+            exit_with_error(_('%s is not a valid input format') % message)
+        else:
+            if isinstance(inputclass, tuple):
+                try:
+                    # builtin format parser
+                    mod, cls, name = inputclass
+                    inputclass = getattr(importlib.import_module('totalopenstation.formats.%s' % mod), cls)
+                except ImportError as message:
+                    exit_with_error(message)
     else:
-        infile = sys.stdin.read()
+        sys.exit(_("Please specify an input format"))
 
+    if options.outformat:
+        try:
+            outputclass = totalopenstation.output.BUILTIN_OUTPUT_FORMATS[options.outformat]
+        except KeyError as message:
+            exit_with_error(_('%s is not a valid output format') % message)
+        else:
+            if isinstance(outputclass, tuple):
+                try:
+                    # builtin output builder
+                    mod, cls, name = outputclass
+                    outputclass = getattr(importlib.import_module('totalopenstation.output.%s' % mod), cls)
+                except ImportError as message:
+                    exit_with_error(message)
+
+    if options.infile:
+        infile = open(options.infile, 'r').read()
+    else:
+        if sys.stdin.isatty():
+            sys.exit(_('No input data!'))
+        else:
+            infile = sys.stdin.read()
 
-def main(infile):
-    '''After setting up all parameters, finally try to process input data.'''
+    # After setting up all parameters, finally try to process input data.
 
     parsed_data = inputclass(infile)
     if options.raw:
@@ -216,4 +215,4 @@ def main(infile):
         sys.stdout.write(output.process())
 
 if __name__ == '__main__':
-    main(infile)
+    cli_parser()


=====================================
scripts/totalopenstation-gui.py → totalopenstation/scripts/gui.py
=====================================
@@ -1,7 +1,7 @@
 #! /usr/bin/env python3
 # -*- coding: utf-8 -*-
 # filename: totalopenstation-gui.py
-# Copyright 2008-2019 Stefano Costa <steko at iosa.it>
+# Copyright 2008-2025 Stefano Costa <steko at iosa.it>
 # Copyright 2010,2012 Luca Bianconi <luxetluc at yahoo.it>
 #
 # This file is part of Total Open Station.
@@ -119,7 +119,7 @@ class AboutDialog(tkinter.simpledialog.Dialog):
     def body(self, master):
         title = "Total Open Station %s" % totalopenstation.__version__
         message = _("""
-Total Open Station is copyright 2008-2019 Stefano Costa, Damien
+Total Open Station is copyright 2008-2025 Stefano Costa, Damien
 Gaignon, Luca Bianconi and the IOSA project, under the GNU GPL v3
 or any later version.
 
@@ -802,14 +802,14 @@ class Tops:
         self.text_area.update_idletasks()
 
 
-root = Tk()
-Tops = Tops(root)
+def gui():
+    root = Tk()
+    app = Tops(root)
 
+    #save user's preferences (model, port and sleeptime if custom model)
 
-#save user's preferences (model, port and sleeptime if custom model)
-
-atexit.register(Tops.upref.setvalues,
-                {'model': Tops.optionMODEL_value.get(),
-                 'port': Tops.option1_value.get(),
-                 'sleeptime': Tops.option6_value.get(),
-                 })
+    atexit.register(app.upref.setvalues,
+                    {'model': app.optionMODEL_value.get(),
+                    'port': app.option1_value.get(),
+                    'sleeptime': app.option6_value.get(),
+                    })


=====================================
totalopenstation/tests/test_csv.py
=====================================
@@ -1,7 +1,7 @@
 import unittest
 
 from totalopenstation.formats import Feature, Point
-from totalopenstation.output.tops_csv import OutputFormat
+from totalopenstation.output.tops_csv import OutputFormat, TrimbleOutputFormat
 
 class TestCSVOutput(unittest.TestCase):
 
@@ -20,3 +20,20 @@ class TestCSVOutput(unittest.TestCase):
     def test_output(self):
         self.output = OutputFormat(self.data).process()
         self.assertEqual(self.output.splitlines()[1], '1,"PT","TEST POINT",12.8,76.3,56.2,"","","","","","",""')
+
+class TestTrimbleCSVOutput(unittest.TestCase):
+
+    def setUp(self):
+        self.data = [
+            Feature(Point(12.8, 76.3, 56.2), desc="PT", point_name="TEST POINT", id=1),
+            Feature(
+                Point(19.8, 26.3, 46.2), desc="PT", point_name="TEST POINT #2", id=2
+            ),
+        ]
+
+    def test_output(self):
+        self.output = TrimbleOutputFormat(self.data).process()
+        self.assertEqual(
+            self.output.splitlines()[1],
+            '1,12.8,76.3,56.2,"PT","TEST POINT","","","","","","",""',
+        )


=====================================
totalopenstation/tests/test_leica_gsi.py
=====================================
@@ -70,3 +70,20 @@ class TestLeicaGSI8Output(BaseTestOutput):
     def setup(self):
         with open('sample_data/leica_gsi/leica_gsi16_gurob.gsi') as testdata:
             self.fp = FormatParser(testdata.read())
+
+
+class TestLeicaGSIOldParser(unittest.TestCase):
+    def setUp(self):
+        with open('sample_data/leica_gsi/RILIEVO.gsi') as testdata:
+            self.fp = FormatParser(testdata.read())
+
+    def test_all_points_processed(self):
+        self.assertEqual(len(self.fp.points), 23)
+
+    def test_values(self):
+        self.assertEqual(self.fp.points[2].id, 3)
+        self.assertEqual(self.fp.points[2].desc, 'PT')
+        self.assertEqual(self.fp.points[2].point_name, '102')
+        self.assertAlmostEqual(self.fp.points[2].geometry.x, 4.49963916)
+        self.assertAlmostEqual(self.fp.points[2].geometry.y, -2.51722711)
+        self.assertAlmostEqual(self.fp.points[2].geometry.z, -0.30665937)


=====================================
tox.ini
=====================================
@@ -1,10 +1,10 @@
 [tox]
 env_list =
-    py38
     py39
     py310
     py311
     py312
+    py313
 minversion = 4.14.2
 
 [testenv]



View it on GitLab: https://salsa.debian.org/debian-gis-team/totalopenstation/-/compare/349ac604ef178ab285f0072ee991ff3f33f99ca7...5c29b562b815cbb250ec6a65112b04848e64ce40

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/totalopenstation/-/compare/349ac604ef178ab285f0072ee991ff3f33f99ca7...5c29b562b815cbb250ec6a65112b04848e64ce40
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20260102/bf1b3f3a/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list