world_image_file/
lib.rs

1//! Read, writes and uses [World Files](https://en.wikipedia.org/wiki/World_file) for
2//! [georeferenced images](https://en.wikipedia.org/wiki/Georeferencing).
3//!
4//! ## Example
5//!
6//! A world file can be created from a string, or read from a file with
7//! `WorldFile::from_path(&path)`:
8//! ```
9//! use world_image_file::WorldFile;
10//! let contents = "32.0\n0.0\n0.0\n-32.0\n691200.0\n4576000.0\n";
11//! let w = WorldFile::from_string(&contents).unwrap();
12//! ```
13//!
14//! Coordinates can be converted from image pixel to 'world' coordinates, and vice-versa.
15//! ```
16//! # use world_image_file::WorldFile;
17//! # let contents = "32.0\n0.0\n0.0\n-32.0\n691200.0\n4576000.0\n";
18//! # let w = WorldFile::from_string(&contents).unwrap();
19//! assert_eq!(w.image_to_world((171., 343.)), (696672., 4565024.));
20//! assert_eq!(w.world_to_image((696672., 4565024.)), (171., 343.));
21//! ````
22//! Pixel coordinates can be fractional. `(10.0, 2.0)` refers to the top left of pixel (10, 2).
23//! `(10.5, 2.5)` is in the middle of pixel (10, 2).
24//!
25//! World Files do not store any SRID/spatial reference system (SRS)/coordinate reference system
26//! (CRS) data. World Files were originally defined by
27//! [ESRI](https://support.esri.com/en/technical-article/000002860).
28//!
29//! Currently `Result<WorldFile, ()>` is returned (i.e. all errors are flatted to `()`), but this
30//! may change to be more descriptive, and would not be seen as a breaking change.
31use std::fmt;
32use std::fs::File;
33use std::io::{Read, Write};
34use std::path::Path;
35
36/// A World File
37///
38/// See the [module top level documention](./index.html)
39#[derive(Debug, PartialEq)]
40pub struct WorldFile {
41    pub x_scale: f64,
42    pub y_scale: f64,
43
44    pub x_skew: f64,
45    pub y_skew: f64,
46
47    pub x_coord: f64,
48    pub y_coord: f64,
49}
50
51impl WorldFile {
52    /// Open a world file from a path
53    pub fn from_path(p: impl AsRef<Path>) -> Result<Self, ()> {
54        let p: &Path = p.as_ref();
55        let mut f = File::open(p).or(Err(()))?;
56        Self::from_reader(&mut f)
57    }
58
59    /// Open a world file from something that can `Read`
60    pub fn from_reader(mut r: impl Read) -> Result<Self, ()> {
61        let mut s = String::new();
62        r.read_to_string(&mut s).or(Err(()))?;
63        Self::from_string(&s)
64    }
65
66    /// Open a world file from a raw string file content
67    pub fn from_string(s: impl AsRef<str>) -> Result<Self, ()> {
68        let s: &str = s.as_ref();
69        let lines: Vec<&str> = s.lines().collect();
70        let x_scale = lines.get(0).and_then(|s| s.parse().ok()).ok_or(())?;
71        let y_skew = lines.get(1).and_then(|s| s.parse().ok()).ok_or(())?;
72        let x_skew = lines.get(2).and_then(|s| s.parse().ok()).ok_or(())?;
73        let y_scale = lines.get(3).and_then(|s| s.parse().ok()).ok_or(())?;
74        let x_coord = lines.get(4).and_then(|s| s.parse().ok()).ok_or(())?;
75        let y_coord = lines.get(5).and_then(|s| s.parse().ok()).ok_or(())?;
76        assert!(x_scale != 0.);
77        assert!(y_scale != 0.);
78
79        Ok(WorldFile {
80            x_scale,
81            y_scale,
82            x_skew,
83            y_skew,
84            x_coord,
85            y_coord,
86        })
87    }
88
89    /// Convert this world file to a raw string
90    pub fn to_string(&self) -> String {
91        format!(
92            "{}\n{}\n{}\n{}\n{}\n{}\n",
93            self.x_scale, self.y_skew, self.x_skew, self.y_scale, self.x_coord, self.y_coord
94        )
95    }
96
97    /// Write this world file to this `Write` object
98    pub fn write_to_writer(&self, mut w: impl Write) {
99        write!(
100            w,
101            "{}\n{}\n{}\n{}\n{}\n{}\n",
102            self.x_scale, self.y_skew, self.x_skew, self.y_scale, self.x_coord, self.y_coord
103        )
104        .unwrap();
105    }
106
107    /// Write this world file to a path
108    pub fn write_to_path(&self, p: impl AsRef<Path>) {
109        let mut f = File::create(p).unwrap();
110        self.write_to_writer(&mut f);
111    }
112
113    /// Convert image coordinates to world coordinates.
114    pub fn image_to_world(&self, image_x_y: impl Into<(f64, f64)>) -> (f64, f64) {
115        let x_y = image_x_y.into();
116        let x = x_y.0;
117        let y = x_y.1;
118
119        (
120            self.x_scale * x + self.x_skew * y + self.x_coord,
121            self.y_skew * x + self.y_scale * y + self.y_coord,
122        )
123    }
124
125    /// Convert world coordinates to image coordinates
126    pub fn world_to_image(&self, world_x_y: impl Into<(f64, f64)>) -> (f64, f64) {
127        let x_y = world_x_y.into();
128        let x = x_y.0;
129        let y = x_y.1;
130
131        let a = self.x_scale;
132        let b = self.x_skew;
133        let c = self.x_coord;
134        let d = self.y_skew;
135        let e = self.y_scale;
136        let f = self.y_coord;
137
138        let det = a * e - b * d; // determinate
139        assert!(det != 0.);
140
141        (
142            (x * e - b * y + b * f - c * e) / det,
143            (-x * d + a * y - a * f - c * d) / det,
144        )
145    }
146}
147
148impl fmt::Display for WorldFile {
149    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
150        write!(f, "{}", self.to_string())
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_simple() {
160        let test = "32.0\n0.0\n0.0\n-32.0\n691200.0\n4576000.0\n";
161        let w = WorldFile::from_string(&test).unwrap();
162        assert_eq!(w.image_to_world((171., 343.)), (696672., 4565024.));
163        assert_eq!(w.world_to_image((696672., 4565024.)), (171., 343.));
164        let p = (100., 200.);
165        assert_eq!(w.image_to_world(w.world_to_image(p)), p);
166    }
167}