SiteLogParser 1.2.0
Parsing IGS Sitelog files (supports version 2+3)
Requires Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,<4,>=2.7
Keywords: beidou, galileo, glonass, gnss, gps, parser, sitelog

# SitelogParser

A Python library and command-line tool for parsing, validating, and exporting IGS (International GNSS Service) sitelog files. SitelogParser converts sitelog text files into structured Python dataclasses, enables programmatic manipulation of sitelog data, and exports them back to the IGS sitelog v2.0 format.

## Features

- **Parse IGS sitelog files** into type-safe Python dataclasses
- **Export sitelog data** back to IGS sitelog v2.0 format with automatic metadata updates
- **Generate Bernese files** (.sta) from sitelog data with equipment history and time ranges
- **Generate SINEX files** (.snx) in SINEX 2.02 format with coordinates and eccentricity data
- **Generate coordinate files** (.crd) with ECEF coordinates in multiple reference frames
- **Merge multiple sitelogs** into single station information, SINEX, or coordinate files
- **Command-line interface** for validation, listing, preparing, and generating files
- **Type hints** throughout for better IDE support and type checking
- **Automatic form metadata** computation (latest modification dates and changed sections)
- **Timestamp handling** with UTC timezone support
- **Glob pattern support** for batch file processing

## Installation

```bash
pip install sitelogparser
```

For development:
```bash
git clone <repository-url>
cd SitelogParser
pip install -e .
```

## Command-Line Usage

The `slp` command provides several operations:

### Validate a sitelog file

```bash
slp validate tests/AMST_20190705.txt
```

**Output:**
```
✓ Valid sitelog: tests/AMST_20190705.txt

📊 Statistics:
  Site: Amsterdam, Netherlands (AMST)
  DOMES: 13502M001
  Location: Amsterdam, Netherlands
  Receivers: 20
  Antennas: 6
  Last Modified: 2018-11-28
  Modified Sections: 3.19, 3.20
```

### List receivers, antennas, or metadata

```bash
# List all receivers
slp list tests/AMST_20190705.txt --type receivers

# List all antennas
slp list tests/AMST_20190705.txt --type antennas

# Show site metadata
slp list tests/AMST_20190705.txt --type metadata

# Show everything (default)
slp list tests/AMST_20190705.txt
```

**Example output (receivers):**
```

📡 GNSS Receivers:
#    Type                      Serial          Installed            Removed
-------------------------------------------------------------------------------------
1    TRIMBLE NETRS             4439239154      2005-04-13T12:00Z    2005-12-15T10:00Z
2    TRIMBLE NETRS             4439239154      2005-12-15T10:00Z    2010-03-15T08:30Z
3    LEICA GRX1200+GNSS        495553          2010-03-15T08:30Z    2010-09-06T08:35Z
4    LEICA GRX1200+GNSS        495553          2010-09-06T08:35Z    2010-10-28T11:40Z
5    LEICA GRX1200+GNSS        495553          2010-10-28T11:50Z    2010-11-15T10:30Z
6    LEICA GRX1200+GNSS        495553          2010-11-15T10:30Z    2011-01-20T07:40Z
7    LEICA GRX1200+GNSS        495553          2011-01-20T07:50Z    2011-06-01T07:50Z
8    LEICA GRX1200+GNSS        495553          2011-06-01T07:50Z    2011-08-31T05:30Z
9    TRIMBLE NETR9             5106K73572      2011-08-31T05:30Z    2011-09-13T11:00Z
10   LEICA GRX1200+GNSS        495553          2011-09-13T11:00Z    2011-11-18T14:00Z
11   TPS GB-1000               8RXJKJ48V7K     2011-11-18T14:00Z    2012-08-22T07:30Z
12   TPS NET-G3A               618-00899       2012-08-22T07:30Z    2012-09-19T15:25Z
13   TPS NET-G3A               618-00899       2012-09-19T15:25Z    2013-10-31T15:23Z
14   TPS NET-G3A               618-00899       2013-10-31T15:34Z    2014-05-23T12:08Z
15   TPS NET-G3A               618-00899       2014-05-23T12:25Z    2017-03-17T13:12Z
16   LEICA GR30                1705298         2017-03-17T13:13Z    2017-10-10T05:17Z
17   TPS NET-G3A               618-00899       2017-10-10T06:07Z    2017-11-17T13:11Z
18   LEICA GR30                1705298         2017-11-17T14:24Z    2018-06-21T06:02Z
19   LEICA GR30                1705298         2018-06-21T06:04Z    2018-11-28T07:23Z
20   LEICA GR30                1705298         2018-11-28T07:25Z    (CCYY-MM-DDThh:mmZ)

```

**Example output (antennas):**
```

📶 GNSS Antennas:
#    Type                      Serial          Installed            Removed
-------------------------------------------------------------------------------------
1    TRM41249.00     TZGD      12696814        2005-04-13T12:00Z    2010-03-15T08:30Z
2    LEIAR25.R3      LEIT      09480032        2010-03-15T08:30Z    2011-08-31T05:30Z
3    TRM57971.00     TZGD      1441101374      2011-08-31T05:30Z    2011-09-13T11:00Z
4    LEIAR25.R3      LEIT      09470019        2011-09-13T11:00Z    2012-08-22T07:30Z
5    TPSCR.G3        TPSH      383-2172        2012-08-22T07:30Z    2017-10-10T06:07Z
6    LEIAR25.R4      LEIT      726448          2017-10-10T06:07Z    (CCYY-MM-DDThh:mmZ)

```

**Example output (metadata):**
```
📋 Site Metadata:

Site Name: Amsterdam, Netherlands
Four Character ID: AMST
DOMES Number: 13502M001
Location: Amsterdam, Netherlands
Coordinates:
  Latitude: +52.3712800855
  Longitude: +4.8563185513
  Elevation: 1.0 m

On-Site Agency: Technische Universiteit Delft
Responsible Agency: Technische Universiteit Delft
```

### Prepare/export a sitelog file

```bash
# Read and export with auto-generated filename
slp prepare tests/AMST_20190705.txt

# Specify output file
slp prepare tests/AMST_20190705.txt -o output/AMST_prepared.log
```

**Output:**
```
✓ Parsed sitelog: tests/AMST_20190705.txt
✓ Exported to: output/AMST_prepared.log
```

The prepared file will be in IGS sitelog v2.0 format with:
- Automatically updated form metadata (date prepared, modified sections)
- Normalized formatting and field alignment
- UTC timestamps in `CCYY-MM-DDThh:mmZ` format

### Generate Bernese station information file (.sta)

Generate a Bernese GNSS Software 5.2 compatible station information file from one or more sitelogs:

```bash
# Generate from single file
slp generate_sta tests/AMST_20190705.txt

# Generate from multiple files (glob pattern)
slp generate_sta tests/testlogs/*.log -o stations.sta

# Generate from directory (finds all .txt and .log files)
slp generate_sta tests/testlogs/

# Custom output file
slp generate_sta tests/AMST_20190705.txt -o output/AMST.sta
```

**Features:**
- Combines multiple sitelogs into a single station file
- TYPE 001: Station renaming entries
- TYPE 002: Combined receiver and antenna entries (overlapping time periods)
- TYPE 003: Receiver equipment history
- TYPE 004: Antenna equipment history
- Automatic serial number replacement with "999999"
- Time range overlap resolution (prevents overlapping periods)

**Output example:**
```
✓ Loaded: AMST_20190705.log (AMST)

✓ Generated Bernese station information file (.sta)
  Output: amst.sta
  Stations: 1
  Lines: 85
  Size: 5234 bytes

📋 Included stations:
  - AMST (11048M001): 20 RX, 6 ANT
```

### Generate SINEX file (.snx)

Generate a SINEX 2.02 (Solution Independent Exchange) format file from one or more sitelogs:

```bash
# Generate from single file
slp generate_snx tests/AMST_20190705.txt

# Generate from multiple files (glob pattern)
slp generate_snx tests/testlogs/*.log -o stations.snx

# Generate from directory (finds all .txt and .log files)
slp generate_snx tests/testlogs/

# Custom output file
slp generate_snx tests/AMST_20190705.txt -o output/AMST.snx
```

**Features:**
- Combines multiple sitelogs into a single SINEX file
- +FILE/REFERENCE: File metadata and contact information
- +SITE/ID: Station identification with coordinates
- +SITE/RECEIVER: Receiver equipment history with time periods
- +SITE/ANTENNA: Antenna equipment history with time periods
- +SITE/ECCENTRICITY: Antenna offset information (Up, North, East)
- +SOLUTION/ESTIMATE: ECEF coordinates (X, Y, Z) for each station
- Automatic serial number replacement with "99999"
- Time range overlap resolution (prevents overlapping periods)

**Output example:**
```
✓ Loaded: AMST_20190705.log (AMST)

✓ Generated SINEX file (.snx)
  Output: amst.snx
  Stations: 1
  Lines: 68
  Size: 4334 bytes

📋 Included stations:
  - AMST (11048M001): 20 RX, 6 ANT
```

### Generate Bernese coordinate file (.crd)

Generate a Bernese GNSS Software coordinate list file from one or more sitelogs:

```bash
# Generate from single file with default ETRS89 frame
slp generate_crd tests/AMST_20190705.txt

# Generate with specific coordinate frame
slp generate_crd tests/AMST_20190705.txt --frame IGS20

# Generate from multiple files (glob pattern)
slp generate_crd tests/testlogs/*.log -o stations.crd

# Generate from directory (finds all .txt and .log files)
slp generate_crd tests/testlogs/

# Custom output file with coordinate frame
slp generate_crd tests/AMST_20190705.txt -o output/AMST.crd --frame ITRF2020
```

**Features:**
- Combines multiple sitelogs into a single coordinate file
- ECEF coordinates (X, Y, Z) in meters with 5 decimal precision
- Supports multiple coordinate frames: ETRS89 (default), IGS20, IGS14, ITRF2020, etc.
- Auto-determines datum name from coordinate frame
- Stations sorted alphabetically by four-character ID
- Skips stations with zero coordinates
- Includes station names and DOMES numbers

**Output example:**
```
✓ Loaded: AMST_20190705.log (AMST)

✓ Generated Bernese coordinate file (.crd)
  Output: amst.crd
  Frame: ETRS89
  Stations: 1
  Lines: 8
  Size: 392 bytes

📋 Included stations:
  - AMST (13502M001)
```

**Generated file format:**
```
ETRS89: coordinate list                                          23-JAN-26 07:50
--------------------------------------------------------------------------------

LOCAL GEODETIC DATUM: ETRS89               EPOCH: 2005-04-13 12:00:00

NUM  STATION NAME           X (M)          Y (M)          Z (M)     FLAG     SYSTEM

  1  AMST 13502M001   3799725.65441  901944.13241  5028762.14325    ETRS89
```

### Show version

```bash
slp --version
```

```bash
slp --version
```

## API Usage

### Basic Parsing

```python
from sitelogparser.common.sitelog import SiteLogParser

# Parse a sitelog file
parser = SiteLogParser(sitelog_file="path/to/sitelog.txt")
sitelog = parser.sitelog

# Access site information
print(f"Site: {sitelog.site_identification.site_name}")
print(f"Four-char ID: {sitelog.site_identification.four_character_id}")
print(f"DOMES: {sitelog.site_identification.iers_domes_number}")

# Access location data
location = sitelog.site_location
print(f"Location: {location.city}, {location.country}")
print(f"Coordinates: {location.latitude}, {location.longitude}")

# Iterate over receivers
for receiver in sitelog.gnss_receivers:
    print(f"Receiver {receiver.number}: {receiver.receiver_type}")
    print(f"  Serial: {receiver.serial_number}")
    print(f"  Firmware: {receiver.firmware_version}")

# Iterate over antennas
for antenna in sitelog.gnss_antennas:
    print(f"Antenna {antenna.number}: {antenna.antenna_type}")
    print(f"  Serial: {antenna.serial_number}")
    print(f"  Radome: {antenna.antenna_radome_type}")

# Access agency information
print(f"On-site agency: {sitelog.on_site_agency.name}")
print(f"Primary contact: {sitelog.on_site_agency.primary_contact.name}")
print(f"Email: {sitelog.on_site_agency.primary_contact.email}")
```

### Exporting Sitelog Data

```python
from sitelogparser.common.sitelog import SiteLogParser

# Parse existing sitelog
parser = SiteLogParser(sitelog_file="input.txt")

# Modify data programmatically
parser.sitelog.site_identification.site_name = "Updated Site Name"

# Export to new file (automatically updates form metadata)
output_text = parser.export_sitelog()
with open("output.txt", "w") as f:
    f.write(output_text)
```

### Creating a New Sitelog

```python
from sitelogparser.common.models import (
    Sitelog, FormInformation, SiteIdentification, SiteLocation,
    GNSSReceiver, GNSSAntenna, Agency, Contact
)
from sitelogparser.common.sitelog import SiteLogParser
from datetime import datetime, timezone

# Create sitelog structure
sitelog = Sitelog()

# Fill form information
sitelog.form_information = FormInformation(
    prepared_by="John Doe",
    report_type="NEW"
)

# Fill site identification
sitelog.site_identification = SiteIdentification(
    site_name="Example Site",
    four_character_id="EXMP",
    iers_domes_number="12345M001",
    date_installed=datetime(2025, 1, 1, tzinfo=timezone.utc).timestamp()
)

# Fill location
sitelog.site_location = SiteLocation(
    city="Example City",
    country="Example Country",
    latitude="+12.3456789012",
    longitude="+123.4567890123",
    elevation="100.0"
)

# Add receivers
receiver = GNSSReceiver(
    number=1,
    receiver_type="EXAMPLE RECEIVER",
    satellite_system="GPS",
    serial_number="12345",
    firmware_version="1.0.0",
    elevation_cutoff_setting="0",
    date_installed=datetime(2025, 1, 1, tzinfo=timezone.utc).timestamp(),
    date_removed=0.0,  # 0.0 means not removed yet
    temperature_stabilization="none"
)
sitelog.gnss_receivers.append(receiver)

# Add antennas
antenna = GNSSAntenna(
    number=1,
    antenna_type="EXAMPLE ANTENNA",
    serial_number="ANT123",
    antenna_reference_point="BPA",
    marker_arp_up_ecc="0.0000",
    marker_arp_north_ecc="0.0000",
    marker_arp_east_ecc="0.0000",
    alignment_of_true_n="0",
    antenna_radome_type="NONE",
    radome_serial_number="",
    antenna_cable_type="",
    antenna_cable_length="",
    date_installed=datetime(2025, 1, 1, tzinfo=timezone.utc).timestamp(),
    date_removed=0.0
)
sitelog.gnss_antennas.append(antenna)

# Add agency information
sitelog.on_site_agency = Agency(
    section="11",
    name="Example Organization",
    abbreviation="EXORG",
    mail_address="123 Example St, Example City, Country",
    primary_contact=Contact(
        name="Jane Doe",
        phone1="+1-555-0100",
        email="jane.doe@example.org"
    )
)

# Export to sitelog format
parser = SiteLogParser(sitelog_text="")
parser.sitelog = sitelog
output_text = parser.export_sitelog()

with open("new_sitelog.txt", "w") as f:
    f.write(output_text)
```

### Working with Timestamps

Dates in the dataclasses are stored as float timestamps (seconds since Unix epoch):

```python
from datetime import datetime, timezone

# Convert datetime to timestamp (for setting dates)
dt = datetime(2025, 1, 15, 12, 30, tzinfo=timezone.utc)
timestamp = dt.timestamp()
receiver.date_installed = timestamp

# Convert timestamp to datetime (for reading dates)
from sitelogparser.common.sitelog import SiteLogParser
parser = SiteLogParser(sitelog_file="sitelog.txt")
receiver = parser.sitelog.gnss_receivers[0]

if receiver.date_installed > 0:
    dt = datetime.fromtimestamp(receiver.date_installed, tz=timezone.utc)
    print(f"Installed: {dt.strftime('%Y-%m-%d %H:%M:%S %Z')}")

# Dates are exported in IGS format: CCYY-MM-DDThh:mmZ
# Empty dates (0.0) are shown as: (CCYY-MM-DDThh:mmZ)
```

### Data Models

The library uses Python dataclasses for type-safe data representation:

- **`Sitelog`**: Top-level container for all sitelog data
- **`FormInformation`**: Section 0 - Form metadata
- **`SiteIdentification`**: Section 1 - Site identification
- **`SiteLocation`**: Section 2 - Site location information
- **`GNSSReceiver`**: Section 3.x - Individual receiver configuration
- **`GNSSAntenna`**: Section 4.x - Individual antenna configuration
- **`Agency`**: Sections 11-12 - Agency information with contacts
- **`Contact`**: Contact person information

All dataclasses have full type hints and can be converted to dictionaries using the `to_dict()` method.

## Requirements

- Python >= 3.8
- No external dependencies (uses only Python standard library)

## Development

Run tests:
```bash
python -m unittest discover tests/ -v
```

Run specific test:
```bash
python -m unittest tests.test_parser
python -m unittest tests.test_generate
```

## License

See [LICENSE.txt](LICENSE.txt) for details.

## Contributing

Contributions are welcome! Please ensure all tests pass before submitting pull requests.