将C绑定添加到H3 Rust库

ddibiase

我试图将kRing函数(以及其他功能)添加到现有的Rust项目中,该项目实现了Uber H3库的C绑定。这是原始来源:https//github.com/scottmmjackson/h3api

这是我非常新颖的补充:

extern crate libc;

#[macro_use]
extern crate failure;

use std::ffi::CString;
use std::fmt;
use std::str;

use libc::{c_char, c_int, c_ulonglong, size_t};

#[link(name = "h3")]
extern "C" {
    // Indexing.
    fn geoToH3(g: *const GeoCoordInternal, res: c_int) -> c_ulonglong;
    fn h3ToGeo(h3: c_ulonglong, g: *mut GeoCoordInternal);
    fn h3ToGeoBoundary(h3: c_ulonglong, gp: *mut GeoBoundaryInternal);

    // Inspection.
    fn h3GetResolution(h: c_ulonglong) -> c_int;
    fn h3GetBaseCell(h: c_ulonglong) -> c_int;
    fn stringToH3(str: *const c_char) -> c_ulonglong;
    fn h3ToString(h: c_ulonglong, str: *const c_char, sz: size_t);
    fn h3IsValid(h: c_ulonglong) -> c_int;
    fn h3IsResClassIII(h: c_ulonglong) -> c_int;
    fn h3IsPentagon(h: c_ulonglong) -> c_int;

    // Traversal.
    fn h3Distance(origin: c_ulonglong, h3: c_ulonglong) -> c_int;
    fn kRing(origin: c_ulonglong, k: c_int, h3: [ *mut c_ulonglong; 6 ]);

    // Hierarchy.
    fn h3ToParent(h: c_ulonglong, parentRes: c_int) -> c_ulonglong;
}

const DEG_TO_RAD: f64 = std::f64::consts::PI / 180.0;
const RAD_TO_DEG: f64 = 180.0 / std::f64::consts::PI;

// Maximum number of cell boundary vertices. The worst case is a pentagon: 5 original verts
// and 5 edge crossings.
const MAX_CELL_BNDRY_VERTS: usize = 10;

/// H3Index is a point in the H3 geospatial indexing system.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub struct H3Index(u64);

impl H3Index {
    /// Creates a new `H3Index` from the given point. If the point is not a valid index in
    /// H3 then `None` is returned.
    ///
    /// # Example
    ///
    /// ```
    /// extern crate h3_rs as h3;
    /// use h3::H3Index;
    ///
    /// let h = H3Index::new(0x850dab63fffffff).unwrap();
    /// ```
    pub fn new(h: u64) -> Result<Self, Error> {
        let valid;
        unsafe {
            valid = h3IsValid(h);
        }
        if valid == 0 {
            return Err(Error::InvalidIndex { value: h });
        }
        Ok(Self(h))
    }

    /// Converts a string to an H3 index.
    ///
    /// # Example
    ///
    /// ```
    /// extern crate h3_rs as h3;
    /// use h3::H3Index;
    ///
    /// assert_eq!(
    ///   H3Index::from_str("0x850dab63fffffff").unwrap(),
    ///   H3Index::new(0x850dab63fffffff).unwrap()
    /// )
    /// ```
    pub fn from_str(s: &str) -> Result<Self, Error> {
        let c_str = match CString::new(s) {
            Ok(c_str) => c_str,
            Err(_) => {
                return Err(Error::InvalidString {
                    value: s.to_owned(),
                })
            }
        };

        let h;
        unsafe {
            h = stringToH3(c_str.as_ptr());
        }

        if h == 0 {
            return Err(Error::InvalidString {
                value: s.to_owned(),
            });
        }
        return Ok(H3Index(h));
    }

    /// Finds the centroid of the index.
    ///
    /// # Example
    ///
    /// ```
    /// extern crate h3_rs as h3;
    /// use h3::{GeoCoord, H3Index};
    ///
    /// let h = H3Index::new(0x850dab63fffffff).unwrap();
    /// assert_eq!(h.to_geo(), GeoCoord::new(67.15092686397712, -168.39088858096966));
    /// ```
    pub fn to_geo(self) -> GeoCoord {
        let mut geo = GeoCoordInternal::new(0.0, 0.0);
        unsafe {
            h3ToGeo(self.0, &mut geo);
        }
        geo.to_deg()
    }

    /// Finds the boundary of the index.
    ///
    /// # Example
    ///
    /// ```
    ///  // TODO
    /// ```
    pub fn to_geo_boundary(self) -> GeoBoundary {
        let mut gb = GeoBoundaryInternal::new();
        unsafe {
            h3ToGeoBoundary(self.0, &mut gb);
        }
        gb.convert()
    }

    /// Returns the resolution of the index.
    ///
    /// # Example
    ///
    /// ```
    /// extern crate h3_rs as h3;
    /// use h3::H3Index;
    ///
    /// let h = H3Index::new(0x850dab63fffffff).unwrap();
    /// assert_eq!(h.resolution(), 5);
    /// ```
    pub fn resolution(self) -> i32 {
        unsafe { h3GetResolution(self.0) }
    }

    /// Returns the base cell number of the index.
    ///
    /// # Example
    ///
    /// ```
    /// extern crate h3_rs as h3;
    /// use h3::H3Index;
    ///
    /// let h = H3Index::new(0x850dab63fffffff).unwrap();
    /// assert_eq!(h.base_cell(), 6);
    /// ```
    pub fn base_cell(self) -> i32 {
        unsafe { h3GetBaseCell(self.0) }
    }

    /// Returns a `bool` indicating whether this index has a resolution with a Class
    /// III orientation.
    ///
    /// # Example
    ///
    /// ```
    /// extern crate h3_rs as h3;
    /// use h3::H3Index;
    ///
    /// assert!(H3Index::new(0x850dab63fffffff).unwrap().is_res_class_3());
    /// ```
    pub fn is_res_class_3(self) -> bool {
        unsafe { h3IsResClassIII(self.0) != 0 }
    }

    /// Returns a `bool` indicating whether this index represents a pentagonal cell.
    ///
    /// # Example
    ///
    /// ```
    /// extern crate h3_rs as h3;
    /// use h3::H3Index;
    ///
    /// assert!(H3Index::new(0x821c07fffffffff).unwrap().is_pentagon());
    /// assert!(!H3Index::new(0x850dab63fffffff).unwrap().is_pentagon());
    /// ```
    pub fn is_pentagon(self) -> bool {
        unsafe { h3IsPentagon(self.0) != 0 }
    }

    /// Returns the distance in grid cells between two indexes or an error if finding the
    /// distance fails. Finding the distance can fail because the two indexes are not comparable
    /// (different resolutions), too far apart, or are separated by pentagonal distortion.
    ///
    /// # Example
    ///
    /// ```
    /// // TODO
    /// ```
    pub fn distance(self, other: Self) -> Result<i32, Error> {
        let d;
        unsafe {
            d = h3Distance(self.0, other.0);
        }

        if d < 0 {
            return Err(Error::IncompatibleIndexes {
                left: self,
                right: other,
            });
        }
        Ok(d)
    }

    pub fn krings(self, index: i32) -> [H3Index; 6] {
        let values: [*mut u64; 6] = array_init::array_init(|_| -> *mut u64 {
            let val: *mut u64 = &mut u64::default();
            val
        });
        unsafe {
            kRing(self.0, index, values);
        }
        let h3s: [H3Index; 6] = array_init::array_init(|i| -> H3Index {
            let val = H3Index(values[i] as u64);
            println!("{}", val); // printing out the resulting H3Index value to study
            val
        });
        h3s
    }

    /// Returns the parent (coarser) index containing h.
    ///
    /// # Example
    ///
    /// ```
    /// // TODO
    /// ```
    pub fn parent(self, res: i32) -> Result<Self, Error> {
        let h;
        unsafe {
            h = h3ToParent(self.0, res);
        }

        if h == 0 {
            return Err(Error::FailedConversion);
        }
        Ok(Self(h))
    }
}

impl fmt::Display for H3Index {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut buf = vec![0u8; 17];
        unsafe {
            h3ToString(self.0, buf.as_mut_ptr() as *mut i8, buf.capacity());
        }
        let res = String::from_utf8(buf);
        let s = res
            .as_ref()
            .map(|s| s.trim_end_matches('\0'))
            .unwrap_or("<invalid>");
        write!(f, "{}", s)
    }
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct GeoCoordInternal {
    pub lat: f64,
    pub lon: f64,
}

impl GeoCoordInternal {
    pub fn new(lat: f64, lon: f64) -> Self {
        Self { lat, lon }
    }

    fn to_deg(&self) -> GeoCoord {
        GeoCoord::new(self.lat * RAD_TO_DEG, self.lon * RAD_TO_DEG)
    }

    fn to_h3(&self, res: i32) -> H3Index {
        unsafe { H3Index(geoToH3(self, res)) }
    }
}

/// GeoCoord is a point on the earth. It is comprised of a latitude and longitude expressed in
/// degrees. The C API for H3 expects the latitude and longitude to be expressed in radians so
/// the coordinates are transparently converted to radians before being passed to the C library.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct GeoCoord {
    pub lat: f64,
    pub lon: f64,
}

impl GeoCoord {
    /// Creates a new `GeoCoord` from the given latitude and longitude. The unit of the
    /// coordinates is degrees.
    ///
    /// # Example
    ///
    /// ```
    /// extern crate h3_rs as h3;
    /// use h3::GeoCoord;
    ///
    /// let mut coord: GeoCoord = GeoCoord::new(67.194013596, 191.598258018);
    /// ```
    pub fn new(lat: f64, lon: f64) -> Self {
        Self { lat, lon }
    }

    fn to_radians(&self) -> GeoCoordInternal {
        GeoCoordInternal::new(self.lat * DEG_TO_RAD, self.lon * DEG_TO_RAD)
    }

    /// Indexes the location at the specified resolution.
    ///
    /// # Example
    ///
    /// ```
    /// extern crate h3_rs as h3;
    /// use h3::{GeoCoord, H3Index};
    ///
    /// let mut coord: GeoCoord = GeoCoord::new(67.194013596, 191.598258018);
    /// assert_eq!(coord.to_h3(5).unwrap(), H3Index::new(0x850dab63fffffff).unwrap());
    /// ```
    pub fn to_h3(&self, res: i32) -> Result<H3Index, Error> {
        let index = self.to_radians().to_h3(res);
        if index.0 == 0 {
            return Err(Error::FailedConversion);
        }
        return Ok(index);
    }
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct GeoBoundaryInternal {
    num_verts: i32,
    verts: [GeoCoordInternal; MAX_CELL_BNDRY_VERTS],
}

impl GeoBoundaryInternal {
    fn new() -> Self {
        Self {
            num_verts: 0,
            verts: [GeoCoordInternal::new(0.0, 0.0); MAX_CELL_BNDRY_VERTS],
        }
    }

    fn convert(&self) -> GeoBoundary {
        let mut verts = Vec::with_capacity(self.num_verts as usize);
        for i in 0..self.num_verts {
            verts.push(self.verts[i as usize].to_deg());
        }
        GeoBoundary { verts }
    }
}

/// GeoBoundary is a collection of points which defines the boundary of a cell.
#[derive(Debug, Clone)]
pub struct GeoBoundary {
    pub verts: Vec<GeoCoord>,
}

#[derive(Debug, Fail)]
pub enum Error {
    #[fail(display = "invalid value for H3 index: {}", value)]
    InvalidIndex { value: u64 },
    #[fail(display = "invalid string representation of H3 index: {}", value)]
    InvalidString { value: String },
    #[fail(display = "could not convert to H3 index")]
    FailedConversion,
    #[fail(display = "h3 indexes are incompatible: {} and {}", left, right)]
    IncompatibleIndexes { left: H3Index, right: H3Index },
}

#[cfg(test)]
mod tests {
    use super::*;

    struct Setup {
        valid_index: H3Index,
        pentagon_index: H3Index,
        valid_geo_coord: GeoCoord,
    }

    impl Setup {
        fn new() -> Self {
            Self {
                valid_index: H3Index::new(0x850dab63fffffff).unwrap(),
                pentagon_index: H3Index::new(0x821c07fffffffff).unwrap(),
                valid_geo_coord: GeoCoord::new(67.15092686397712, -168.39088858096966),
            }
        }
    }

    #[test]
    fn test_h3_from_str() {
        assert_eq!(
            H3Index::from_str("0x850dab63fffffff").unwrap(),
            H3Index::new(0x850dab63fffffff).unwrap()
        );
        assert!(H3Index::from_str("invalid string").is_err());
    }

    #[test]
    fn test_h3_to_geo() {
        let setup = Setup::new();

        assert_eq!(setup.valid_index.to_geo(), setup.valid_geo_coord);
    }

    #[test]
    fn test_h3_to_geo_boundary() {
        // TODO
    }

    #[test]
    fn test_h3_resolution() {
        let setup = Setup::new();

        for res in 0..16 {
            let h = setup.valid_geo_coord.to_h3(res).unwrap();
            assert_eq!(h.resolution(), res);
        }
    }

    #[test]
    fn test_h3_base_cell() {
        let setup = Setup::new();

        assert_eq!(setup.valid_index.base_cell(), 6);
    }

    #[test]
    fn test_h3_is_res_class_3() {
        let setup = Setup::new();

        assert!(setup.valid_index.is_res_class_3());

        // TODO: Test an index which should return from false. From the Go package:
        // res := Resolution(validH3Index) - 1
        // parent := ToParent(validH3Index, res)
        // assert.False(t, IsResClassIII(parent))
    }

    #[test]
    fn test_h3_is_pentagon() {
        let setup = Setup::new();

        assert!(!setup.valid_index.is_pentagon());
        assert!(setup.pentagon_index.is_pentagon());
    }

    #[test]
    fn test_h3_distance() {
        // let setup = Setup::new();

        // TODO
    }

    #[test]
    fn test_h3_parent() {
        // let setup = Setup::new();

        // TODO
    }

    #[test]
    fn test_h3_display() {
        let setup = Setup::new();

        assert_eq!(format!("{}", setup.valid_index), "850dab63fffffff");
    }

    #[test]
    fn test_geo_to_h3() {
        let setup = Setup::new();

        assert_eq!(setup.valid_geo_coord.to_h3(5).unwrap(), setup.valid_index);
        assert!(setup.valid_geo_coord.to_h3(-1).is_err());
        assert!(setup.valid_geo_coord.to_h3(17).is_err());
    }
}

fn main() {

    // geoToH3 --resolution 6 --latitude 43.6411124 --longitude -79.4180424
    let _coord = GeoCoordInternal{ lat: 43.6411124, lon: -79.4180424 };
    let index = _coord.to_h3(6); // 862b9bc57ffffff
    // kRing -k 1 --origin 862b9bc57ffffff
    let _first = index.krings(1);
    // 862b9bc57ffffff
    // 862b9bc0fffffff
    // 862b9bce7ffffff
    // 862b9bcefffffff
    // 862b9bc5fffffff
    // 862b9bc47ffffff
    // 862b9bc77ffffff

    println!("{}", index);

}

您可以看到我在extern“ C”块中添加了定义:

fn kRing(origin: c_ulonglong, k: c_int, h3: [ *mut c_ulonglong; 6 ]);

我认为这[ *mut c_ulonglong; 6 ]是适当的,因为https://h3geo.org/#/documentation/api-reference/traversal的文档具有一个接口,void kRing(H3Index origin, int k, H3Index* out);其中的接口是可变的H3Index数组。

声明C函数后,我尝试使用它:

pub fn krings(self, index: i32) -> [H3Index; 6] {
    let values: [*mut u64; 6] = array_init::array_init(|_| -> *mut u64 {
        let val: *mut u64 = &mut u64::default();
        val
    });
    unsafe {
        kRing(self.0, index, values);
    }
    let h3s: [H3Index; 6] = array_init::array_init(|i| -> H3Index {
        let val = H3Index(values[i] as u64);
        println!("{}", val);
        val
    });
    h3s
}

然后,在我的主要文章中,我完成了为某些定义的坐标获取H3索引的过程,然后获取kring:

let _coord = GeoCoordInternal{ lat: 43.6411124, lon: -79.4180424 };
let index = _coord.to_h3(6);

结果应该是862b9bc57ffffff然后,我用ak值1(应该返回6个值)调用我的krings方法。

我得到的值是:

7ffeeace1ea8
7ffeeace1ea8
7ffeeace1ea8
7ffeeace1ea8
7ffeeace1ea8
7ffeeace1ea8

这似乎是不正确的返回值。我要寻找的正确值是:

862b9bc57ffffff
862b9bc0fffffff
862b9bce7ffffff
862b9bcefffffff
862b9bc5fffffff
862b9bc47ffffff
862b9bc77ffffff

我相信不正确的值只是基于u64 :: default()的索引表示,表示未填充该数组。

我也遇到了不安全的FFI错误,我不确定该如何处理,我相信这是我返回错误值的原因。

在此处输入图片说明

请注意,以上逻辑将不可避免地发生变化。数组的大小必须是动态的/未指定的,因为C函数返回的数组的大小会根据k的值(确切地说是k * 6 +1)而变化。

ddibiase

麦卡顿有很多很棒的地方。我发现另一个libh3正确实现了这些。这是正确实现的kring函数的示例:

pub fn kring(origin:H3Index, radius: i32) -> Vec<H3Index> {
    unsafe {
        let max = libh3_sys::maxKringSize(radius);
        let mut r = Vec::<H3Index>::with_capacity(max as usize);
        kRing(origin, radius, r.as_mut_ptr());
        r.set_len(max as usize);
        r = r.into_iter().filter(|v| *v != 0).collect();
        return r;
    }
}

除了使用mcarton的点之外,还必须使用Vector来存储和返回索引值。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

将C ++库添加到Eclipse C ++项目中

来自分类Dev

C库释放了来自Rust的指针

来自分类Dev

将外部库添加到CMakeList.txt C ++

来自分类Dev

如何将Eigen库添加到C ++项目

来自分类Dev

阻止ArmClang将调用添加到标准C库

来自分类Dev

使用cmake将Eigen库添加到C ++项目

来自分类Dev

将库添加到C编译器

来自分类Dev

您如何将 Python.h 库添加到 Eclipse 以在 C++ 程序中使用?

来自分类Dev

为Rust的C库编写绑定的目的是什么?

来自分类Dev

将元素添加到下拉列表中,该列表最初绑定到数据库

来自分类Dev

使用Android绑定库将dimen资源添加到布局

来自分类Dev

如何将 XML 文档注释添加到 Xamarin iOS 绑定库?

来自分类Dev

将库添加到android studio

来自分类Dev

将静态库添加到podspec

来自分类Dev

将库添加到gradle构建

来自分类Dev

将库添加到项目

来自分类Dev

将Guava的库添加到NetBeans

来自分类Dev

将库添加到项目

来自分类Dev

将库添加到makefile

来自分类Dev

将包含添加到存储库

来自分类Dev

将Java库添加到Maven

来自分类Dev

如何将h3和p添加到锚标记

来自分类Dev

将CSS添加到H3后,HTML按钮不起作用

来自分类Dev

如何为Android NDK使用共享库(将C库添加到C ++)

来自分类Dev

将共享库添加到 gcc 中的静态库

来自分类Dev

如何在C中动态加载Rust库?

来自分类Dev

如何从Rust库访问结构的全局C数组?

来自分类Dev

在C ++中使用rust库时的未定义参考

来自分类Dev

将静态库添加到C或C ++项目的通常方法是什么?