用可视化案例讲Rust编程3. 函数分解与参数传递

上一节我们在绘制面要素的时候,发现了函数功能体是三个不同步骤组成的:

  1. 读取文件获得geometry
  2. 把geometry转变为绘图元素trace
  3. 把绘图元素绘制到地图上

像我们上一节那样,把所有的功能都写在一个函数里面,这样的函数灵活性太差,例如我们要读取和绘制若干个点、线、面,那么如果不去修改,那么每读一个shapefile就要重复去写一个方法,那就太繁琐了,我们重构的第一步,就是要把各种功能尽量的切分出来,形成一个个能够独立运行和维护的模块,所以今天我们就把昨天那个代码给切分成多个函数,增加重用,减少冗余。

首先是第一个函数,用于读取shapefile,输入的参数就是shapefile的路径,输出是解析好的集合:(下面是的注释是写个没有学习过Rust的同学的,了解过的同学就可以略过了)

//Rust通过fn定义函数,函数里面的参数有两种模式
//带`&`符号的的是标识传递进去的是一个引用(不懂引用的同学,暂时可以先跳过)
//如果不带&则标识输入的是一个具体的值。
//函数后面的 -> 表示函数的返回值的类型,这里是一个Polygon类型的vec
fn read_polygon(shapefile: &str) ->Vec{
    let shp = shapefile::read_as::<_,
        shapefile::Polygon, shapefile::dbase::Record>(
            shapefile,
    ).expect("Could not open polygon-shapefile");

    //构建输入参数
    let mut polygons:Vec = Vec::new();
    //shapefile读取文件默认得到的是一个MultiPolygon
    //
    for (polygon, polygon_record) in shp {
        let geo_mpolygon: geo_types::MultiPolygon = polygon.into();
        for poly in geo_mpolygon.iter(){
            polygons.push(poly.to_owned());
        }
    }
    //Rust中,如果语句后面没有分号`;`则表示这是一个表达式,会返回一个结果
    //如果这个表达式出现在函数最后,则等同于return polygons的功能
    polygons
}

然后就是第二个函数,把解析出来的polygon集合,转换为plotly的绘图元素trace:

//输入两个参数,上面输入的polygons的几何,和一个颜色参数。
//polygon会有
fn build_polygon(polygons: &Vec,color:Rgba)->Vec>>{
    let mut trace_vec = Vec::new();
    for ps in polygons{
        let mut lon:Vec = Vec::new();
        let mut lat:Vec = Vec::new();
        for p in ps.exterior(){
            lon.push(p.x);
            lat.push(p.y);
        }
        let trace = ScatterMapbox::new(lat, lon).mode(Mode::None)
        .fill(plotly::scatter_mapbox::Fill::ToSelf)
        .fill_color(color);
        trace_vec.push(trace);
    }
    trace_vec
}

第三个函数,就是把trace绘制成图了:

//这里直接就绘制出来了,所以没有返回值
fn draw_trace(traces:&Vec>>){
    let mut plot = Plot::new();
    let layout = Layout::new()
    .drag_mode(DragMode::Zoom)
    .margin(Margin::new().top(10).left(10).bottom(10).right(10))
    .width(1024)
    .height(700)
    .mapbox(
        Mapbox::new()
            .style(MapboxStyle::WhiteBg)
            .center(Center::new(39.9, 116.3))
            .zoom(9),
    );
    plot.set_layout(layout);
   
    for t in traces.iter(){
        plot.add_trace(t.to_owned());
    }
    plot.show();
}

有这三个函数之后,我们调用就很方便了:

#[test]
fn test_draw_2(){
    let poly1 = "./data/shp/北京行政区划.shp";
    let t1 = build_polygon(
        &read_polygon(poly1), Rgba::new(240,243,250,1.0));
    draw_trace(&t1);
}

效果如下: 

用可视化案例讲Rust编程3. 函数分解与参数传递_第1张图片

如果我们要绘制两个图层,例如再这个图层上,在绘制一个面状水系,就很省事了:

#[test]
fn test_draw_2(){
    let poly1 = "./data/shp/北京行政区划.shp";
    let mut t1 = build_polygon(
        &read_polygon(poly1), Rgba::new(240,243,250,1.0));

    let poly2 = "./data/shp/面状水系.shp";
    let mut t2 = build_polygon(
        &read_polygon(poly2), Rgba::new(108,213,250,1.0));
    
    t1.append(&mut t2);
    draw_trace(&t1);
}

效果如下: 

用可视化案例讲Rust编程3. 函数分解与参数传递_第2张图片

但是现在带来了一个问题,如果我们要输入的是线要素呢?那不是有得重新写一个读取线要素的方法么?当然,这么写是一点问题都没有的,如下所示:

fn read_line(shapefile: &str) ->Vec{
    let shp = shapefile::read_as::<_,
        shapefile::Polyline, shapefile::dbase::Record>(
            shapefile,
    ).expect(&format!("Could not open polyline-shapefile, error: {}", shapefile));

    let mut linestrings:Vec = Vec::new();
    for (pline, pline_record) in shp {
        let geo_mline: geo_types::MultiLineString = pline.into();
        for line in geo_mline.iter(){
            linestrings.push(line.to_owned());
        }
    }
    linestrings
}
  • 从代码中可以看见,除了读取部分稍有不同,线要素与面要素的处理原则大致是差不多的。如果是动态类型的语言,可以直接写成一个方法也没问题,但是Rust是静态类型,也就是你需要在编译之前,就固定下类型来的,这个问题,我们明天再说。

同样的,读取了线要素,还要把线要素转换成线类型的绘图要素,如下:

fn build_line(lines: &Vec,colors:Vec)->Vec>>{
    let mut trace_vec = Vec::new();
    for (line,color) in zip(lines,colors){
        let mut lon:Vec = Vec::new();
        let mut lat:Vec = Vec::new();
        for p in line.coords(){
            lon.push(p.x);
            lat.push(p.y);
        }
        let trace = ScatterMapbox::new(lat, lon)
        .mode(Mode::Lines)
            .marker(Marker::new().color(color));
        trace_vec.push(trace);
    }
    trace_vec
}

这样就得到了一个和面要素转换的绘图要素是一样的,然后我们就可以用同一个方法来解决了,如下:

  #[test]
fn test_draw_2(){
    let poly1 = "./data/shp/北京行政区划.shp";
    let mut t1 = build_polygon(
        &read_polygon(poly1), Rgba::new(240,243,250,1.0));

    let poly2 = "./data/shp/面状水系.shp";
    let mut t2 = build_polygon(
        &read_polygon(poly2), Rgba::new(108,213,250,1.0));

    let line1 = "./data/shp/高速.shp";
    let line1 = read_line(line1);
    let line1_color:Vec = (0..line1.len()).map(|x|Rgba::new(255,182,118,1.0)).collect();
    let mut t3 = build_line(&line1,line1_color);
    
    //把三个trace合并成一个,传递到绘图函数里面即可
    t1.append(&mut t2);
    t1.append(&mut t3);
    draw_trace(&t1);
}

效果如下: 

用可视化案例讲Rust编程3. 函数分解与参数传递_第3张图片

以此推类,如果有点要素,照样处理就行。

看到这里,有同学会问,我们有多少种类型,就一定要写多少个方法,这个可以理解,每个方法都用不同的名称来调用,让调用的人也太难记了,有没有一种方法,就调用一个方法,根据输入的类型,自动去匹配逻辑处理能力行不行?

当然是没有问题,所以下一节,我们来解决这个问题。

你可能感兴趣的:(rust,开发语言,后端)