-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
introduce ionex2kml tool #119
Draft
gwbres
wants to merge
4
commits into
main
Choose a base branch
from
ionex2kml
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
[workspace] | ||
members = ["rinex", "crx2rnx", "rnx2crx", "rinex-cli", "ublox-rnx", "sinex"] | ||
members = ["rinex", "crx2rnx", "rnx2crx", "rinex-cli", "ublox-rnx", "sinex", "ionex2kml"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
[package] | ||
name = "ionex2kml" | ||
version = "0.0.1" | ||
license = "MIT OR Apache-2.0" | ||
authors = ["Guillaume W. Bres <[email protected]>"] | ||
description = "IONEX to KML converter" | ||
homepage = "https://github.com/georust/ionex2kml" | ||
repository = "https://github.com/georust/ionex2kml" | ||
keywords = ["rinex", "gps", "gpx"] | ||
categories = ["parser", "science::geo", "command-line-interface"] | ||
edition = "2021" | ||
readme = "README.md" | ||
|
||
[dependencies] | ||
log = "0.4" | ||
kml = "0.8.3" | ||
thiserror = "1" | ||
pretty_env_logger = "0.5" | ||
clap = { version = "4", features = ["derive", "color"] } | ||
rinex = { path = "../rinex", features = ["flate2", "serde"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
ionex2kml | ||
========= | ||
|
||
`ionex2kml` is a small application to convert a IONEX file into KML format. | ||
|
||
It is a convenient method to visualize TEC maps using third party tools. | ||
|
||
Getting started | ||
=============== | ||
|
||
Provide a IONEX file with `-i`: | ||
|
||
```bash | ||
ionex2kml -i /tmp/ionex.txt | ||
``` | ||
|
||
Input IONEX files do not have to follow naming conventions. | ||
|
||
This tool will preserve the input file name by default, just changing the internal | ||
format and file extension. In the previous example we would get `/tmp/ionex.kml`. | ||
|
||
Define the output file yourself with `-o`: | ||
|
||
```bash | ||
ionex2kml -i jjim12.12i -o /tmp/test.kml | ||
``` | ||
|
||
Each Epoch is put in a dedicated KML folder. | ||
|
||
Equipotential TEC values | ||
======================== | ||
|
||
When converting to KML, we round the TEC values and shrink it to N equipotential areas. | ||
In other words, the granularity on the TEC visualization you get is max|tec|/N where max|tec| | ||
is the absolute maximal TEC value in given file, through all epochs and all altitudes. | ||
|
||
Another way to see this, is N defines the number of distinct colors in the color map | ||
|
||
Visualizing KML maps | ||
==================== | ||
|
||
Google maps is one way to visualize a KML file. | ||
|
||
KML content customization | ||
========================= | ||
|
||
Define a specific KML revision with `-v` | ||
|
||
```bash | ||
ionex2kml -i jjim12.12i -v http://www.opengis.net/kml/2.2 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
use clap::{ | ||
Arg, //ArgAction, | ||
ArgMatches, | ||
ColorChoice, | ||
Command, | ||
}; | ||
use log::error; | ||
use std::collections::HashMap; | ||
use std::str::FromStr; | ||
use thiserror::Error; | ||
|
||
use kml::types::KmlVersion; | ||
|
||
#[derive(Debug, Error)] | ||
pub enum CliError { | ||
#[error("file type \"{0}\" is not supported")] | ||
FileTypeError(String), | ||
#[error("failed to parse ionex file")] | ||
IonexError(#[from] rinex::Error), | ||
#[error("failed to generate kml content")] | ||
GpxError(#[from] kml::Error), | ||
} | ||
|
||
#[derive(Debug, Clone, Default)] | ||
pub struct Cli { | ||
matches: ArgMatches, | ||
} | ||
|
||
impl Cli { | ||
pub fn new() -> Self { | ||
Self { | ||
matches: { | ||
Command::new("ionex2kml") | ||
.author("Guillaume W. Bres, <[email protected]>") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
.version(env!("CARGO_PKG_VERSION")) | ||
.about("IONEX to KML conveter") | ||
.arg_required_else_help(true) | ||
.color(ColorChoice::Always) | ||
.arg( | ||
Arg::new("ionex") | ||
.short('i') | ||
.value_name("FILEPATH") | ||
.help("Input IONEX file") | ||
.required(true), | ||
) | ||
.arg( | ||
Arg::new("output") | ||
.short('o') | ||
.value_name("FILEPATH") | ||
.help("Output KML file"), | ||
) | ||
//.arg( | ||
// Arg::new("equipotiential") | ||
// .short('n') | ||
// .value_name("N") | ||
// .help("Number of isosurfaces allowed")) | ||
.next_help_heading("KML content") | ||
.arg( | ||
Arg::new("version") | ||
.short('v') | ||
.value_name("VERSION") | ||
.help("Define specific KML Revision"), | ||
) | ||
.arg( | ||
Arg::new("attributes") | ||
.short('a') | ||
.value_name("[NAME,VALUE]") | ||
.help("Define custom file attributes"), | ||
) | ||
.get_matches() | ||
}, | ||
} | ||
} | ||
/// Returns KML version to use, based on user command line | ||
pub fn kml_version(&self) -> KmlVersion { | ||
if let Some(version) = self.matches.get_one::<String>("version") { | ||
if let Ok(version) = KmlVersion::from_str(version) { | ||
version | ||
} else { | ||
let default = KmlVersion::default(); | ||
error!("invalid KML version, using default value \"{:?}\"", default); | ||
default | ||
} | ||
} else { | ||
KmlVersion::default() | ||
} | ||
} | ||
/// Returns optional <String, String> "KML attributes" | ||
pub fn kml_attributes(&self) -> Option<HashMap<String, String>> { | ||
if let Some(attributes) = self.matches.get_one::<String>("attributes") { | ||
let content: Vec<&str> = attributes.split(",").collect(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't matter, but this isn't a great way to write this. |
||
if content.len() > 1 { | ||
Some( | ||
vec![(content[0].to_string(), content[1].to_string())] | ||
.into_iter() | ||
.collect(), | ||
) | ||
} else { | ||
error!("invalid csv, need a \"field\",\"value\" description"); | ||
None | ||
} | ||
} else { | ||
None | ||
} | ||
} | ||
// /// Returns nb of equipotential TEC map we will exhibit, | ||
// /// the maximal error on resulting TEC is defined as max|tec_u| / n | ||
// pub fn nb_tec_potentials(&self) -> usize { | ||
// //if let Some(n) = self.matches.get_one::<u32>("equipoential") { | ||
// // *n as usize | ||
// //} else { | ||
// 20 // default value | ||
// //} | ||
// } | ||
/// Returns ionex filename | ||
pub fn ionex_filepath(&self) -> &str { | ||
self.matches.get_one::<String>("ionex").unwrap() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
use kml::{Kml, KmlWriter}; | ||
use rinex::prelude::*; | ||
|
||
mod cli; | ||
use cli::{Cli, CliError}; | ||
use log::{info, warn}; | ||
|
||
use kml::{ | ||
types::{AltitudeMode, Coord, LineString, LinearRing, Polygon, Placemark, Geometry}, | ||
KmlDocument, | ||
}; | ||
use std::collections::HashMap; | ||
|
||
|
||
|
||
//use std::io::Write; | ||
|
||
fn main() -> Result<(), CliError> { | ||
pretty_env_logger::init_timed(); | ||
|
||
let cli = Cli::new(); | ||
|
||
let fp = cli.ionex_filepath(); | ||
info!("reading {}", fp); | ||
let rinex = Rinex::from_file(fp)?; | ||
|
||
if !rinex.is_ionex() { | ||
warn!("input file is not a ionex file"); | ||
return Err(CliError::FileTypeError(format!( | ||
"{:?}", | ||
rinex.header.rinex_type | ||
))); | ||
} | ||
|
||
let mut kml_doc: KmlDocument<f64> = KmlDocument::default(); | ||
kml_doc.version = cli.kml_version(); | ||
if let Some(attrs) = cli.kml_attributes() { | ||
kml_doc.attrs = attrs; | ||
} | ||
|
||
let record = rinex.record.as_ionex().unwrap(); | ||
|
||
let mut buf = std::io::stdout().lock(); | ||
let mut writer = KmlWriter::<_, f64>::from_writer(&mut buf); | ||
|
||
// We wrap each Epoch in separate "Folders" | ||
for (epoch, (_map, _, _)) in record { | ||
let mut epoch_folder: Vec<Kml<f64>> = Vec::new(); | ||
let epoch_folder_attrs = vec![(String::from("Epoch"), epoch.to_string())] | ||
.into_iter() | ||
.collect::<HashMap<String, String>>(); | ||
|
||
//test a linestring to describe equipoential TECu area | ||
let polygon = Polygon::<f64> { | ||
inner: vec![], | ||
outer: { | ||
LinearRing::<f64> { | ||
coords: vec![ | ||
Coord { | ||
x: 4.119067147539055, | ||
y: 43.73425044812969, | ||
z: None, | ||
}, | ||
Coord { | ||
x: 4.11327766588697, | ||
y: 43.73124529989733, | ||
z: None, | ||
}, | ||
Coord { | ||
x: 4.119067147539055, | ||
y: 43.73425044812969, | ||
z: None, | ||
}, | ||
Coord { | ||
x: 4.129067147539055, | ||
y: 44.73425044812969, | ||
z: None, | ||
}, | ||
Coord { | ||
x: 4.109067147539055, | ||
y: 44.73425044812969, | ||
z: None, | ||
}, | ||
], | ||
extrude: false, | ||
tessellate: true, | ||
altitude_mode: AltitudeMode::RelativeToGround, | ||
attrs: vec![(String::from("name"), String::from("test"))] | ||
.into_iter() | ||
.collect(), | ||
} | ||
}, | ||
extrude: false, | ||
tessellate: true, | ||
altitude_mode: AltitudeMode::RelativeToGround, | ||
attrs: vec![(String::from("name"), String::from("test"))] | ||
.into_iter() | ||
.collect(), | ||
}; | ||
|
||
let placemark = Placemark::<f64> { | ||
name: Some(String::from("This is a test")), | ||
description: Some(String::from("Great description")), | ||
geometry: Some(Geometry::Polygon(polygon)), | ||
children: vec![], | ||
attrs: HashMap::new(), | ||
}; | ||
|
||
epoch_folder.push(Kml::Placemark(placemark)); | ||
|
||
// // We wrap equipotential TECu areas in | ||
// // we wrap altitude levels in separate "Folders" | ||
// // in 2D IONEX (single altitude value): you only get one folder per Epoch | ||
// for (h, maps) in rinex.ionex() { | ||
// let folder = Folder::default(); | ||
// folder.attrs = | ||
// attrs: vec![("elevation", format!("{:.3e}", h)].into_iter().collect() | ||
// }; | ||
// for (potential, boundaries) in maps.tec_equipotential(10) { | ||
// // define one color for following areas | ||
// let color = colorous::linear(percentile); | ||
// let style = LineStyle { | ||
// id: Some(percentile.to_string()), | ||
// width: 1.0_f64, | ||
// color_mode: ColorMode::Default, | ||
// attrs: vec![("percentile", format!("{}", percentile)].into_iter().collect(), | ||
// }; | ||
// folder.elements.push(style); | ||
// folder.elements.push(boundaries); | ||
// } | ||
// kml.elements.push(folder); | ||
// } | ||
//folder_content..push(epoch_folder); | ||
|
||
let epoch_folder: Kml<f64> = Kml::Folder { | ||
attrs: epoch_folder_attrs, | ||
elements: epoch_folder, | ||
}; | ||
// add folder to document | ||
kml_doc.elements.push(epoch_folder); | ||
|
||
break;//TODO | ||
} | ||
|
||
// generate document | ||
let kml = Kml::KmlDocument(kml_doc); | ||
writer.write(&kml)?; | ||
info!("kml generated"); | ||
|
||
Ok(()) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions might make this easier to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know if we can use latex like equations in docrs too ? At the time I looked at it, there was a tweak involving storing html pages if I remember correctly, seemd too complicated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, not really: https://crates.io/crates/rust-latex-doc-minimal-example, https://docs.rs/katex-doc/, https://docs.rs/pwnies/0.0.13/pwnies/.