Name | dji-geotagger JSON |
Version |
1.0.8
JSON |
| download |
home_page | None |
Summary | A precise PPK + MRK-based geotagging tool for DJI RTK drones |
upload_time | 2025-07-19 05:21:33 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.9 |
license | BSD-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"
}