dji-geotagger


Namedji-geotagger JSON
Version 1.0.8 PyPI version JSON
download
home_pageNone
SummaryA precise PPK + MRK-based geotagging tool for DJI RTK drones
upload_time2025-07-19 05:21:33
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseBSD-2-Clause
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # DJI Geotagger

**A precise PPK + MRK-based geotagging tool for DJI RTK drones**

This Python library enables centimetre-level camera geotagging by combining PPK `.pos` solutions, DJI `.MRK` gimbal offset corrections, and EXIF/XMP metadata from DJI RTK drone images. It is designed for photogrammetry and remote sensing workflows that require accurate EOPs without using ground control points (GCPs).

## Features

- Batch process `.obs` raw GNSS logs into RINEX and perform PPK with RTKLIB
- Download precise ephemeris data (SP3/CLK) automatically
- Parse DJI `.MRK` and interpolate correction vectors to camera center (ECEF)
- Match images by GPS time, apply PPK + MRK correction with covariance propagation
- Export geotagged results in ECEF/ENU/UTM with estimated 3D precision
- Support for DJI P1, M300, and other RTK-enabled drones

## Installation

```bash
git clone https://github.com/RayPan-UC/dji-geotagger.git
cd dji-geotagger
pip install -r requirements.txt
pip install dji-geotagger
```

## Dependencies

- Python ≥ 3.9
-  `pillow`, `defusedxml`, `pandas`, `numpy`, `pyproj`, `tqdm`
- RTKLIB (`convbin.exe`, `rnx2rtkp.exe`)

## Workflow Overview

1. **Convert raw GNSS to RINEX**  
   Uses RTKLIB `convbin` for both base and rover logs.

2. **Download precise IGS ephemeris**  
   Automatically fetch `.sp3` and `.clk` based on RINEX timestamps.

3. **Run PPK**  
   Batch PPK processing using `rnx2rtkp` with optional override base coordinates from PPP `.sum` file.

4. **Parse image EXIF/XMP metadata**  
   Extracts capture time, attitude, and gimbal orientation.

5. **Parse MRK files**  
   Converts NED to ENU, then ENU → ECEF correction vectors.

6. **Interpolate camera center**  
   Matches MRK by time, interpolates PPK positions, applies gimbal offset.

7. **Export results**  
   Generates a DataFrame (or CSV) of corrected positions and attitude per image.

## Example Usage

```python
from pathlib import Path
import datetime
from pyproj import CRS
from dji_geotagger import *

# === User-defined project path ===
project_root = Path(r"/path/to/your/project/SynopticSite1")
ppp_sum_file = project_root / "base_data" / "DRTK" / "PPP_result" / "DRTK3_0006_20250513073737_8PHDMCM00A1369.sum"

# === Clean temporary directories ===
clean_temp_dirs()

# === Convert base and rover raw logs to RINEX ===
base_obs, base_nav = raw_to_rinex_batch(
    keywords=['20250513', '0006', 'DRTK', '.dat'],
    input_dir=project_root,
    type="base",
)

rover_dir = raw_to_rinex_batch(
    keywords=['20250513', 'PPKRAW', '.bin'],
    input_dir=project_root,
    type="rover"
)

# === Post-process PPK with base .sum file ===
process_ppk(
    base_obs=base_obs,
    base_nav=base_nav,
    rover_dir=rover_dir,
    override_base_from_sum_file=ppp_sum_file,
    output_dir=Path("temp/ppk_result"),
)

# === Compute corrected camera positions ===
final_df = load_and_compute_camera_positions(
    mrk_dir=project_root,
    img_dir=project_root,
    pos_dir=Path("temp/ppk_result"),
    base_sum_file=ppp_sum_file
)

# === Transform to target coordinate system (e.g., NAD83 / UTM zone 12N) ===
target_crs = 26912
final_df = transform_coordinates(
    final_df,
    target_crs=CRS.from_user_input(target_crs),
    out_x="E_NAD83",
    out_y="N_NAD83",
    out_z="H_NAD83",
    cov_ecef2enu=True,
    drop_original=True
)

# === Save result as CSV ===
output_csv = Path(f"geotag_output/geotagged_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv")
output_csv.parent.mkdir(parents=True, exist_ok=True)
final_df.to_csv(output_csv, index=False)
print(f"[INFO] Exported geotagged data to: {output_csv}")
```

## Output Format


The output CSV contains the following columns:

| Column Name           | Description |
|------------------------|-------------|
| `file_name`           | Image file name |
| `gps_week`            | GPS week number |
| `gps_time`            | Seconds into the GPS week |
| `sd_x_ecef`           | Standard deviation in ECEF X (metres) |
| `sd_y_ecef`           | Standard deviation in ECEF Y (metres) |
| `sd_z_ecef`           | Standard deviation in ECEF Z (metres) |
| `cov_ecef_flat`       | Flattened 3×3 ECEF covariance matrix (row-major, space-separated) |
| `flight_roll`         | Aircraft body roll (degrees) |
| `flight_pitch`        | Aircraft body pitch (degrees) |
| `flight_yaw`          | Aircraft body yaw (degrees) |
| `gimbal_roll`         | Gimbal-reported roll (degrees) |
| `gimbal_pitch`        | Gimbal-reported pitch (degrees) |
| `gimbal_yaw`          | Gimbal-reported yaw (degrees) |
| `dji_geotagger_roll`  | Corrected camera roll for photogrammetry (degrees), with gimbal lock handling |
| `dji_geotagger_pitch` | Corrected camera pitch for photogrammetry (degrees), computed as `gimbal_pitch + 90` |
| `dji_geotagger_yaw`   | Camera yaw for photogrammetry (degrees), taken directly from `flight_yaw` |
| `E_NAD83`             | Easting in NAD83 / UTM Zone 12N (metres) |
| `N_NAD83`             | Northing in NAD83 / UTM Zone 12N (metres) |
| `H_NAD83`             | Height in NAD83 ellipsoidal coordinates (metres) |
| `sd_E`                | Standard deviation in Easting (ENU, metres) |
| `sd_N`                | Standard deviation in Northing (ENU, metres) |
| `sd_U`                | Standard deviation in Up (ENU, metres) |
| `cov_enu_flat`        | Flattened 3×3 ENU covariance matrix (row-major, space-separated) |

Note: The projected coordinates (`E_NAD83`, `N_NAD83`, `H_NAD83`) are output in NAD83 / UTM Zone 12N by default. Users can customize the coordinate reference system (CRS) and output column formats according to their project requirements.

## License

This project is licensed under the BSD 2-Clause (see LICENSE for details).

## Acknowledgments

- Developed at the University of Calgary, Applied Geospatial Research Group ([appliedgrg.ca](https://www.appliedgrg.ca))
- Inspired by real-world field workflows involving DJI Matrice 350 RTK + Zenmuse P1, Hemisphere base stations, and CSRS-PPP post-processing

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "dji-geotagger",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": null,
    "author": null,
    "author_email": "Ray Pan <hungwei.pan2@ucalgary.ca>",
    "download_url": "https://files.pythonhosted.org/packages/4a/a3/c1f50c6338b7231d68fba6f140039c68c3a76f9cb25d8e46ce3f8f269234/dji_geotagger-1.0.8.tar.gz",
    "platform": null,
    "description": "# DJI Geotagger\r\n\r\n**A precise PPK + MRK-based geotagging tool for DJI RTK drones**\r\n\r\nThis Python library enables centimetre-level camera geotagging by combining PPK `.pos` solutions, DJI `.MRK` gimbal offset corrections, and EXIF/XMP metadata from DJI RTK drone images. It is designed for photogrammetry and remote sensing workflows that require accurate EOPs without using ground control points (GCPs).\r\n\r\n## Features\r\n\r\n- Batch process `.obs` raw GNSS logs into RINEX and perform PPK with RTKLIB\r\n- Download precise ephemeris data (SP3/CLK) automatically\r\n- Parse DJI `.MRK` and interpolate correction vectors to camera center (ECEF)\r\n- Match images by GPS time, apply PPK + MRK correction with covariance propagation\r\n- Export geotagged results in ECEF/ENU/UTM with estimated 3D precision\r\n- Support for DJI P1, M300, and other RTK-enabled drones\r\n\r\n## Installation\r\n\r\n```bash\r\ngit clone https://github.com/RayPan-UC/dji-geotagger.git\r\ncd dji-geotagger\r\npip install -r requirements.txt\r\npip install dji-geotagger\r\n```\r\n\r\n## Dependencies\r\n\r\n- Python \u2265 3.9\r\n-  `pillow`, `defusedxml`, `pandas`, `numpy`, `pyproj`, `tqdm`\r\n- RTKLIB (`convbin.exe`, `rnx2rtkp.exe`)\r\n\r\n## Workflow Overview\r\n\r\n1. **Convert raw GNSS to RINEX**  \r\n   Uses RTKLIB `convbin` for both base and rover logs.\r\n\r\n2. **Download precise IGS ephemeris**  \r\n   Automatically fetch `.sp3` and `.clk` based on RINEX timestamps.\r\n\r\n3. **Run PPK**  \r\n   Batch PPK processing using `rnx2rtkp` with optional override base coordinates from PPP `.sum` file.\r\n\r\n4. **Parse image EXIF/XMP metadata**  \r\n   Extracts capture time, attitude, and gimbal orientation.\r\n\r\n5. **Parse MRK files**  \r\n   Converts NED to ENU, then ENU \u2192 ECEF correction vectors.\r\n\r\n6. **Interpolate camera center**  \r\n   Matches MRK by time, interpolates PPK positions, applies gimbal offset.\r\n\r\n7. **Export results**  \r\n   Generates a DataFrame (or CSV) of corrected positions and attitude per image.\r\n\r\n## Example Usage\r\n\r\n```python\r\nfrom pathlib import Path\r\nimport datetime\r\nfrom pyproj import CRS\r\nfrom dji_geotagger import *\r\n\r\n# === User-defined project path ===\r\nproject_root = Path(r\"/path/to/your/project/SynopticSite1\")\r\nppp_sum_file = project_root / \"base_data\" / \"DRTK\" / \"PPP_result\" / \"DRTK3_0006_20250513073737_8PHDMCM00A1369.sum\"\r\n\r\n# === Clean temporary directories ===\r\nclean_temp_dirs()\r\n\r\n# === Convert base and rover raw logs to RINEX ===\r\nbase_obs, base_nav = raw_to_rinex_batch(\r\n    keywords=['20250513', '0006', 'DRTK', '.dat'],\r\n    input_dir=project_root,\r\n    type=\"base\",\r\n)\r\n\r\nrover_dir = raw_to_rinex_batch(\r\n    keywords=['20250513', 'PPKRAW', '.bin'],\r\n    input_dir=project_root,\r\n    type=\"rover\"\r\n)\r\n\r\n# === Post-process PPK with base .sum file ===\r\nprocess_ppk(\r\n    base_obs=base_obs,\r\n    base_nav=base_nav,\r\n    rover_dir=rover_dir,\r\n    override_base_from_sum_file=ppp_sum_file,\r\n    output_dir=Path(\"temp/ppk_result\"),\r\n)\r\n\r\n# === Compute corrected camera positions ===\r\nfinal_df = load_and_compute_camera_positions(\r\n    mrk_dir=project_root,\r\n    img_dir=project_root,\r\n    pos_dir=Path(\"temp/ppk_result\"),\r\n    base_sum_file=ppp_sum_file\r\n)\r\n\r\n# === Transform to target coordinate system (e.g., NAD83 / UTM zone 12N) ===\r\ntarget_crs = 26912\r\nfinal_df = transform_coordinates(\r\n    final_df,\r\n    target_crs=CRS.from_user_input(target_crs),\r\n    out_x=\"E_NAD83\",\r\n    out_y=\"N_NAD83\",\r\n    out_z=\"H_NAD83\",\r\n    cov_ecef2enu=True,\r\n    drop_original=True\r\n)\r\n\r\n# === Save result as CSV ===\r\noutput_csv = Path(f\"geotag_output/geotagged_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv\")\r\noutput_csv.parent.mkdir(parents=True, exist_ok=True)\r\nfinal_df.to_csv(output_csv, index=False)\r\nprint(f\"[INFO] Exported geotagged data to: {output_csv}\")\r\n```\r\n\r\n## Output Format\r\n\r\n\r\nThe output CSV contains the following columns:\r\n\r\n| Column Name           | Description |\r\n|------------------------|-------------|\r\n| `file_name`           | Image file name |\r\n| `gps_week`            | GPS week number |\r\n| `gps_time`            | Seconds into the GPS week |\r\n| `sd_x_ecef`           | Standard deviation in ECEF X (metres) |\r\n| `sd_y_ecef`           | Standard deviation in ECEF Y (metres) |\r\n| `sd_z_ecef`           | Standard deviation in ECEF Z (metres) |\r\n| `cov_ecef_flat`       | Flattened 3\u00d73 ECEF covariance matrix (row-major, space-separated) |\r\n| `flight_roll`         | Aircraft body roll (degrees) |\r\n| `flight_pitch`        | Aircraft body pitch (degrees) |\r\n| `flight_yaw`          | Aircraft body yaw (degrees) |\r\n| `gimbal_roll`         | Gimbal-reported roll (degrees) |\r\n| `gimbal_pitch`        | Gimbal-reported pitch (degrees) |\r\n| `gimbal_yaw`          | Gimbal-reported yaw (degrees) |\r\n| `dji_geotagger_roll`  | Corrected camera roll for photogrammetry (degrees), with gimbal lock handling |\r\n| `dji_geotagger_pitch` | Corrected camera pitch for photogrammetry (degrees), computed as `gimbal_pitch + 90` |\r\n| `dji_geotagger_yaw`   | Camera yaw for photogrammetry (degrees), taken directly from `flight_yaw` |\r\n| `E_NAD83`             | Easting in NAD83 / UTM Zone 12N (metres) |\r\n| `N_NAD83`             | Northing in NAD83 / UTM Zone 12N (metres) |\r\n| `H_NAD83`             | Height in NAD83 ellipsoidal coordinates (metres) |\r\n| `sd_E`                | Standard deviation in Easting (ENU, metres) |\r\n| `sd_N`                | Standard deviation in Northing (ENU, metres) |\r\n| `sd_U`                | Standard deviation in Up (ENU, metres) |\r\n| `cov_enu_flat`        | Flattened 3\u00d73 ENU covariance matrix (row-major, space-separated) |\r\n\r\nNote: The projected coordinates (`E_NAD83`, `N_NAD83`, `H_NAD83`) are output in NAD83 / UTM Zone 12N by default. Users can customize the coordinate reference system (CRS) and output column formats according to their project requirements.\r\n\r\n## License\r\n\r\nThis project is licensed under the BSD 2-Clause (see LICENSE for details).\r\n\r\n## Acknowledgments\r\n\r\n- Developed at the University of Calgary, Applied Geospatial Research Group ([appliedgrg.ca](https://www.appliedgrg.ca))\r\n- Inspired by real-world field workflows involving DJI Matrice 350 RTK + Zenmuse P1, Hemisphere base stations, and CSRS-PPP post-processing\r\n",
    "bugtrack_url": null,
    "license": "BSD-2-Clause",
    "summary": "A precise PPK + MRK-based geotagging tool for DJI RTK drones",
    "version": "1.0.8",
    "project_urls": {
        "Homepage": "https://github.com/RayPan-UC/dji-geotagger"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "4aa3c1f50c6338b7231d68fba6f140039c68c3a76f9cb25d8e46ce3f8f269234",
                "md5": "de1f075fa3afa34470de1905e160b892",
                "sha256": "7a89e063459ffc4e4a1041d1bab769314d6ec33ebe473eb8df0fcccb28160423"
            },
            "downloads": -1,
            "filename": "dji_geotagger-1.0.8.tar.gz",
            "has_sig": false,
            "md5_digest": "de1f075fa3afa34470de1905e160b892",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 24332,
            "upload_time": "2025-07-19T05:21:33",
            "upload_time_iso_8601": "2025-07-19T05:21:33.527894Z",
            "url": "https://files.pythonhosted.org/packages/4a/a3/c1f50c6338b7231d68fba6f140039c68c3a76f9cb25d8e46ce3f8f269234/dji_geotagger-1.0.8.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-19 05:21:33",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "RayPan-UC",
    "github_project": "dji-geotagger",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "dji-geotagger"
}
        
Elapsed time: 0.74628s