GIS缓冲区算法对比研究(Buffer Algorithm)

来源:互联网 发布:李小龙数据 编辑:程序博客网 时间:2024/04/19 02:37

GIS缓冲区算法对比研究(Buffer Algorithm)

                 ----王少华

 

一.介绍算法:

缓冲区分析是邻近度分析的一种,缓冲区是为了识别某一地理实体或空间物体对其周围地物的影响度而在其周围建立具有一定宽度的带状区域。缓冲区作为独立的数据层进行叠加分析,可应用到道路、河流、环境污染源、居民点、辐射源等的空间分析,为某种应用目的提供科学依据,另外,结合不同的专业模型,可以在生活、军事、城乡规划等领域发挥重要的作用。

针对点、线、面不同的几何类型,建立缓冲区的方式相互有所不同。建立点缓冲区比较简单,即以某点要素为圆心,以缓冲半径 R 作圆,得到点要素的缓冲区;线要素的缓冲区是以线为轴,以 R 为距离作两侧的平行线,在线的两端构建两个半圆弧段,和平行线一起组成缓冲区;面缓冲区的建立,是以面要素的边界为基线向内外侧作平行线,平行线和基线里的区域就是面缓冲区。除此之外,还可以对栅格数据建立缓冲区,根据不同的模型方程建立动态缓冲区,不论对什么样的数据建立缓冲区,其基本方法都是相似的。(见参考文献4)

开源版本的的GRASS,GEOS和JTS里面都有Buffer算法模块,其中GEOS是JTS的C++版本。

使用GEOS的应用有PostGIS (C API),MapServer (C API),Quantum GIS (C API),OGR (C API),GRASS (C API),Shapely (C API),INGRES (C API),SpatiaLite (C API),MapGuide Open Source GeoDjango (C API),MapWindow GIS (C API), osm2pgsql (C++ API),osgEarth (C++ API),MonetDB (C API),rgeos (C API),其中还有软件包的FME和Autodesk MapGuide Enterprise。

GRASS版本介绍(见http://grass.itc.it/gdp/html_grass63/v.buffer.html)(相关的工程介绍见http://download.osgeo.org/grass/grass6_progman/)

这里仅列出相关源码,待以后列出对比算法分析,算法性能等。

二.算法说明及伪代码演示

缓冲区实现算法有矢量方法和栅格方法两种。其中矢量方法数据量小,方法相对成熟,栅格图像需要进行栅格像元之间进行布尔运算,当缓冲区较大时会带来较重的运算负荷,实际运用中存在一定的局限性。矢量方法算法一般遵循以下步骤:

点 :确定中心点——以中心点为圆心、 R 为半径生成一个圆——得到缓冲区边界;

线、面 :确定轴线——以距离 R 生成中心轴线的平行曲线——处理转角弧段——对生成的弧段进行求交、合并运算——生成缓冲区边界;

常用的矢量数据中心线扩张算法:

角分线法

基本思想:即“简单平行线法”,在轴线的两边作出平行线,在转角处形成尖角,两端形成弧段,组成缓冲区。

缺陷:难以保证在尖角处缓冲区左右边线等宽;校正过程复杂,主要体现在轴线折角很大和很小时的情况;算法模型复杂,主要是因为几何生成过程中需要处理较多的异常。

凸角圆弧法

基本思想:顾名思义,即是在转角外侧用圆弧来代替尖角,内侧仍然使用尖角的方法,生成缓冲区。

实施步骤:

1.        直线性判断,判断相邻三点是否在同一直线上;

2.        折点凸凹性判断,确定转角的地方哪侧使用直线求交,哪侧使用圆弧连接;

3.        凸点圆弧的嵌入,即将转角外侧形成的圆弧和两边的线段相连;

4.        边线关系的判别与处理,岛屿多边形参与缓冲区边界的构成,重叠多边形不参与缓冲区边界的构成;

5.        缓冲区边界的形成,具体是将重叠区域进行合并,绘制外围的边线,包括岛屿多边形的轮廓,形成最终的缓冲区边界。

在缓冲区算法中,需要注意的一个问题是缓冲区多边形的重叠与合并,包括同一要素缓冲区的重叠和多个要素之间缓冲区的重叠。栅格数据缓冲区内的栅格具有一个与其影响度对应的一个值,如果重叠区域具有相同影响度则任取一值,如果不同则采取影响度大的代替影响度小的方法处理。对于矢量数据的处理算法有三种:数学运算法;矢量-栅格转换法;矢量-栅格混合法。(见参考文献4)

 

这里简单给出GEOS的源码(源码位置在GEOS/source/operation/buffer/BufferOp.cpp):

double BufferOp::precisionScaleFactor(const Geometry *g,

     double distance,

     int maxPrecisionDigits)

{

     const Envelope *env=g->getEnvelopeInternal();

     double envSize=std::max(env->getHeight(), env->getWidth());

     double expandByDistance=distance > 0.0 ? distance : 0.0;

     double bufEnvSize=envSize + 2 * expandByDistance;

     // the smallest power of 10 greater than the buffer envelope

     int bufEnvLog10=(int) (std::log(bufEnvSize) / std::log(10.0) + 1.0);

     int minUnitLog10=bufEnvLog10 - maxPrecisionDigits;

     // scale factor is inverse of min Unit size, so flip sign of exponent

     double scaleFactor=std::pow(10.0,-minUnitLog10);

     return scaleFactor;

}

 

/*public static*/

Geometry* BufferOp::bufferOp(const Geometry *g, double distance,

         int quadrantSegments,

         int nEndCapStyle)

{

     BufferOp bufOp(g);

     bufOp.setQuadrantSegments(quadrantSegments);

     bufOp.setEndCapStyle(nEndCapStyle);

     return bufOp.getResultGeometry(distance);

}

 

/*public*/

Geometry* BufferOp::getResultGeometry(double nDistance)

{

     distance=nDistance;

     computeGeometry();

     return resultGeometry;

}

 

/*public*/

Geometry* BufferOp::getResultGeometry(double nDistance, int nQuadrantSegments)

{

     distance=nDistance;

     setQuadrantSegments(nQuadrantSegments);

     computeGeometry();

     return resultGeometry;

}

 

/*private*/

Void BufferOp::computeGeometry()

{

#if GEOS_DEBUG

     std::cerr<<"BufferOp::computeGeometry: trying with original precision"<<std::endl;

#endif

     //bufferReducedPrecision(); return; // FIXME: remove this code

     bufferOriginalPrecision();

     if (resultGeometry!=NULL) return;

     std::cerr << "bufferOriginalPrecision failed (" << saveException.what() << "), trying with reduced precision"

               << std::endl;

     const PrecisionModel& argPM = *(argGeom->getFactory()->getPrecisionModel());

     if ( argPM.getType() == PrecisionModel::FIXED )

         bufferFixedPrecision(argPM);

     else

         bufferReducedPrecision();

}

 

/*private*/

Void BufferOp::bufferReducedPrecision()

{

     // try and compute with decreasing precision

     for (int precDigits=MAX_PRECISION_DIGITS; precDigits >= 0; precDigits--)

     {

#if GEOS_DEBUG

         std::cerr<<"BufferOp::computeGeometry: trying with precDigits "<<precDigits<<std::endl;

#endif

         try {

              bufferReducedPrecision(precDigits);

         } catch (const util::TopologyException& ex) {

              saveException=ex;

              // don't propagate the exception - it will be detected by fact that resultGeometry is null

         }

         if (resultGeometry!=NULL) {

              // debug

              //if ( saveException ) std::cerr<<saveException->toString()<<std::endl;

              return;

         }

     }

     // tried everything - have to bail

     throw saveException;

}

/*private*/

Void BufferOp::bufferOriginalPrecision()

{

     BufferBuilder bufBuilder;

     bufBuilder.setQuadrantSegments(quadrantSegments);

     bufBuilder.setEndCapStyle(endCapStyle);

     //std::cerr<<"computing with original precision"<<std::endl;

     try

     {

         resultGeometry=bufBuilder.buffer(argGeom, distance);

     }

     catch (const util::TopologyException& ex)

     {

         // don't propagate the exception - it will be detected by

         // fact that resultGeometry is null

         saveException=ex;

         //std::cerr<<ex->toString()<<std::endl;

     }

     //std::cerr<<"done"<<std::endl;

}

 

Void BufferOp::bufferReducedPrecision(int precisionDigits)

{

     double sizeBasedScaleFactor=precisionScaleFactor(argGeom, distance, precisionDigits);

 

     std::cerr << "recomputing with precision scale factor = "

         << sizeBasedScaleFactor

         << std::endl;

     assert(sizeBasedScaleFactor>0);

     PrecisionModel fixedPM(sizeBasedScaleFactor);

     bufferFixedPrecision(fixedPM);

}

 

/*private*/

Void BufferOp::bufferFixedPrecision(const PrecisionModel& fixedPM)

{

     PrecisionModel pm(1.0); // fixed as well

//

// FIXME: both MCIndexSnapRounder and SimpleSnapRounder

// makes buffer_snapround.xml test fail.

//

#if 0

     snapround::MCIndexSnapRounder inoder(pm); // fail

     snapround::SimpleSnapRounder inoder(pm); // fail

#else

     algorithm::LineIntersector li(&fixedPM);

     IntersectionAdder ia(li);

     MCIndexNoder inoder(&ia); // This works fine (but does not snapround)

#endif

     ScaledNoder noder(inoder, fixedPM.getScale());

     BufferBuilder bufBuilder;

     bufBuilder.setWorkingPrecisionModel(&fixedPM);

     bufBuilder.setNoder(&noder);

 

     bufBuilder.setQuadrantSegments(quadrantSegments);

     bufBuilder.setEndCapStyle(endCapStyle);

     // this may throw an exception, if robustness errors are encountered

     resultGeometry=bufBuilder.buffer(argGeom, distance);

}

 

这里给出GRASS两个版本(源码位置grass/vector/v.buffer和grass-. src/vector/v.buffer,具体算法在grass/lib/vector/Vlib/buffer.c和grass/lib/vector/Vlib/buffer2.c)

第一个版本就是buffer.c版本(根据点,线和面类型分开处理:点和线归为一类处理;面分解为线对象处理,最后合并为面对象):

/*!

   /brief Create buffer around the line line.

   This function is replaced by Vect_line_buffer().

   Buffer is closed counter clockwise polygon. Warning: output line

   may contain loops!

   /param InPoints input line

   /param distance create buffer in distance

   /param tolerance maximum distance between theoretical arc and polygon segments

   /param[out] OutPoints output line

 */

Void Vect_line_buffer(const struct line_pnts *InPoints, double distance,

          double tolerance, struct line_pnts *OutPoints)

{

    double dangle;

    int side, npoints;

    static struct line_pnts *Points = NULL;

    static struct line_pnts *PPoints = NULL;

    distance = fabs(distance);

    dangle = 2 * acos(1 - tolerance / fabs(distance));  /* angle step */

    if (Points == NULL)

     Points = Vect_new_line_struct();

    if (PPoints == NULL)

     PPoints = Vect_new_line_struct();

    /* Copy and prune input */

    Vect_reset_line(Points);

    Vect_append_points(Points, InPoints, GV_FORWARD);

    Vect_line_prune(Points);

    Vect_reset_line(OutPoints);

    npoints = Points->n_points;

    if (npoints <= 0) {

     return;

    }

    else if (npoints == 1) {     /* make a circle */

     double angle, x, y;

     for (angle = 0; angle < 2 * PI; angle += dangle) {

         x = Points->x[0] + distance * cos(angle);

         y = Points->y[0] + distance * sin(angle);

         Vect_append_point(OutPoints, x, y, 0);

     }

     /* Close polygon */

     Vect_append_point(OutPoints, OutPoints->x[0], OutPoints->y[0], 0);

    }

    else {             /* 2 and more points */

     for (side = 0; side < 2; side++) {

         double angle, sangle;

         double lx1, ly1, lx2, ly2;

         double x, y, nx, ny, sx, sy, ex, ey;

         /* Parallel on one side */

         if (side == 0) {

         Vect_line_parallel(Points, distance, tolerance, 0, PPoints);

         Vect_append_points(OutPoints, PPoints, GV_FORWARD);

         }

         else {

         Vect_line_parallel(Points, -distance, tolerance, 0, PPoints);

         Vect_append_points(OutPoints, PPoints, GV_BACKWARD);

         }

         /* Arc at the end */

         /* 2 points at theend of original line */

         if (side == 0) {

         lx1 = Points->x[npoints - 2];

         ly1 = Points->y[npoints - 2];

         lx2 = Points->x[npoints - 1];

         ly2 = Points->y[npoints - 1];

         }

         else {

         lx1 = Points->x[1];

         ly1 = Points->y[1];

         lx2 = Points->x[0];

         ly2 = Points->y[0];

         }

         /* normalized vector */

         vect(lx1, ly1, lx2, ly2, &nx, &ny);

         /* starting point */

         sangle = atan2(-nx, ny);     /* starting angle */

         sx = lx2 + ny * distance;

         sy = ly2 - nx * distance;

         /* end point */

         ex = lx2 - ny * distance;

         ey = ly2 + nx * distance;

         Vect_append_point(OutPoints, sx, sy, 0);

         /* arc */

         for (angle = dangle; angle < PI; angle += dangle) {

         x = lx2 + distance * cos(sangle + angle);

         y = ly2 + distance * sin(sangle + angle);

         Vect_append_point(OutPoints, x, y, 0);

         }

         Vect_append_point(OutPoints, ex, ey, 0);

     }

     /* Close polygon */

     Vect_append_point(OutPoints, OutPoints->x[0], OutPoints->y[0], 0);

    }

    Vect_line_prune(OutPoints);

    return;

}

 

第二个版本就是buffer2.c版本(这个是Google Summer of Code 2008的源码,将点,线和面的buffer分开处理的):

/* area_outer and area_isles[i] must be closed non self-intersecting lines

   side: 0 - auto, 1 - right, -1 left

 */

static void buffer_lines(struct line_pnts *area_outer, struct line_pnts **area_isles,

               int isles_count, int side, double da, double db, double dalpha, int round, int caps, double tol,struct line_pnts **oPoints, struct line_pnts ***iPoints, int *inner_count)

{

    struct planar_graph *pg2;

    struct line_pnts *sPoints, *cPoints;

    struct line_pnts **arrPoints;

    int i, count = 0;

    int res, winding;

    int auto_side;

    int more = 8;

    int allocated = 0;

    double px, py;

    G_debug(3, "buffer_lines()");

    auto_side = (side == 0);

    /* initializations */

    sPoints = Vect_new_line_struct();

    cPoints = Vect_new_line_struct();

    arrPoints = NULL;

    /* outer contour */

    G_debug(3, "    processing outer contour");

    *oPoints = Vect_new_line_struct();

    if (auto_side)

     side =

         get_polygon_orientation(area_outer->x, area_outer->y,

                       area_outer->n_points -

                       1) ? LEFT_SIDE : RIGHT_SIDE;

    convolution_line(area_outer, da, db, dalpha, side, round, caps, tol,

              sPoints);

    pg2 = pg_create(sPoints);

    extract_outer_contour(pg2, 0, *oPoints);

    res = extract_inner_contour(pg2, &winding, cPoints);

    while (res != 0) {

     if (winding == 0) {

         if (!Vect_point_in_poly(cPoints->x[0], cPoints->y[0], area_outer)) {

         if (Vect_get_point_in_poly(cPoints, &px, &py) != 0)

             G_fatal_error(_("Vect_get_point_in_poly() failed"));

         if (!point_in_buf(area_outer, px, py, da, db, dalpha)) {

             add_line_to_array(cPoints, &arrPoints, &count, &allocated,

                         more);

             cPoints = Vect_new_line_struct();

         }

         }

     }

     res = extract_inner_contour(pg2, &winding, cPoints);

    }

    pg_destroy_struct(pg2);

    /* inner contours */

    G_debug(3, "    processing inner contours");

    for (i = 0; i < isles_count; i++) {

     if (auto_side)

         side =

         get_polygon_orientation(area_isles[i]->x, area_isles[i]->y,

                       area_isles[i]->n_points -

                       1) ? RIGHT_SIDE : LEFT_SIDE;

     convolution_line(area_isles[i], da, db, dalpha, side, round, caps,

               tol, sPoints);

     pg2 = pg_create(sPoints);

     extract_outer_contour(pg2, 0, cPoints);

     res = extract_inner_contour(pg2, &winding, cPoints);

     while (res != 0) {

         if (winding == -1) {

         /* we need to check if the area is in the buffer.

            I've simplfied convolution_line(), so that it runs faster,

            however that leads to ocasional problems */

          if (Vect_point_in_poly

             (cPoints->x[0], cPoints->y[0], area_isles[i])) {

             if (Vect_get_point_in_poly(cPoints, &px, &py) != 0)

              G_fatal_error(_("Vect_get_point_in_poly() failed"));

             if (!point_in_buf(area_isles[i], px, py, da, db, dalpha)) {

              add_line_to_array(cPoints, &arrPoints, &count,

                         &allocated, more);

              cPoints = Vect_new_line_struct();

             }

         }

         }

         res = extract_inner_contour(pg2, &winding, cPoints);

     }

     pg_destroy_struct(pg2);

    }

    arrPoints = G_realloc(arrPoints, count * sizeof(struct line_pnts *));

    *inner_count = count;

    *iPoints = arrPoints;

    Vect_destroy_line_struct(sPoints);

    Vect_destroy_line_struct(cPoints);

    G_debug(3, "buffer_lines() ... done");

    return;

}

 

/*!

   /brief Creates buffer around line.

   See also Vect_line_buffer().

   /param InPoints input line geometry

   /param da distance along major axis

   /param db distance along minor axis

   /param dalpha angle between 0x and major axis

   /param round make corners round

   /param caps add caps at line ends

   /param tol maximum distance between theoretical arc and output segments

   /param[out] oPoints output polygon outer border (ccw order)

   /param[out] inner_count number of holes

   /param[out] iPoints array of output polygon's holes (cw order)

 */

void Vect_line_buffer2(const struct line_pnts *Points, double da, double db,

                double dalpha, int round, int caps, double tol,

                struct line_pnts **oPoints,

                struct line_pnts ***iPoints, int *inner_count)

{

    struct planar_graph *pg;

    struct line_pnts *tPoints, *outer;

    struct line_pnts **isles;

    int isles_count = 0;

    int res, winding;

    int more = 8;

    int isles_allocated = 0;

    G_debug(2, "Vect_line_buffer()");

    /* initializations */

    tPoints = Vect_new_line_struct();

    isles = NULL;

    pg = pg_create(Points);

    /* outer contour */

    outer = Vect_new_line_struct();

    extract_outer_contour(pg, 0, outer);

    /* inner contours */

    res = extract_inner_contour(pg, &winding, tPoints);

    while (res != 0) {

     add_line_to_array(tPoints, &isles, &isles_count, &isles_allocated,

                more);

     tPoints = Vect_new_line_struct();

     res = extract_inner_contour(pg, &winding, tPoints);

    }

    buffer_lines(outer, isles, isles_count, RIGHT_SIDE, da, db, dalpha, round,

          caps, tol, oPoints, iPoints, inner_count);

    Vect_destroy_line_struct(tPoints);

    Vect_destroy_line_struct(outer);

    destroy_lines_array(isles, isles_count);

    pg_destroy_struct(pg);

    return;

}

 

/*!

   /brief Creates buffer around area.

   /param Map vector map

   /param area area id

   /param da distance along major axis

   /param db distance along minor axis

   /param dalpha angle between 0x and major axis

   /param round make corners round

   /param caps add caps at line ends

   /param tol maximum distance between theoretical arc and output segments

   /param[out] oPoints output polygon outer border (ccw order)

   /param[out] inner_count number of holes

   /param[out] iPoints array of output polygon's holes (cw order)

 */

void Vect_area_buffer2(const struct Map_info *Map, int area, double da, double db,

                double dalpha, int round, int caps, double tol,

                struct line_pnts **oPoints,

                struct line_pnts ***iPoints, int *inner_count)

{

    struct line_pnts *tPoints, *outer;

    struct line_pnts **isles;

    int isles_count = 0;

    int i, isle;

    int more = 8;

    int isles_allocated = 0;

    G_debug(2, "Vect_area_buffer()");

    /* initializations */

    tPoints = Vect_new_line_struct();

    isles_count = Vect_get_area_num_isles(Map, area);

    isles_allocated = isles_count;

    isles = G_malloc(isles_allocated * sizeof(struct line_pnts *));

    /* outer contour */

    outer = Vect_new_line_struct();

    Vect_get_area_points(Map, area, outer);

    Vect_append_point(outer, outer->x[0], outer->y[0], outer->z[0]);

    /* inner contours */

    for (i = 0; i < isles_count; i++) {

     isle = Vect_get_area_isle(Map, area, i);

     Vect_get_isle_points(Map, isle, tPoints);

     /* Check if the isle is big enough */

     /*

        if (Vect_line_length(tPoints) < 2*PI*max)

        continue;

      */

     Vect_append_point(tPoints, tPoints->x[0], tPoints->y[0],

                tPoints->z[0]);

     add_line_to_array(tPoints, &isles, &isles_count, &isles_allocated,

                more);

     tPoints = Vect_new_line_struct();

    }

    buffer_lines(outer, isles, isles_count, 0, da, db, dalpha, round, caps,

          tol, oPoints, iPoints, inner_count);

    Vect_destroy_line_struct(tPoints);

    Vect_destroy_line_struct(outer);

    destroy_lines_array(isles, isles_count);

    return;

}

 

/*!

   /brief Creates buffer around the point (px, py).

   /param px input point x-coordinate

   /param py input point y-coordinate

   /param da distance along major axis

   /param da distance along minor axis

   /param dalpha angle between 0x and major axis

   /param round make corners round

   /param tol maximum distance between theoretical arc and output segments

   /param[out] nPoints output polygon outer border (ccw order)

 */

void Vect_point_buffer2(double px, double py, double da, double db,

              double dalpha, int round, double tol,

              struct line_pnts **oPoints)

{

    double tx, ty;

    double angular_tol, angular_step, phi1;

    int j, nsegments;

    G_debug(2, "Vect_point_buffer()");

    *oPoints = Vect_new_line_struct();

    dalpha *= PI / 180;     /* convert dalpha from degrees to radians */

     if (round || (!round))

     {

     angular_tol = angular_tolerance(tol, da, db);

     nsegments = (int)(2 * PI / angular_tol) + 1;

     angular_step = 2 * PI / nsegments;

     phi1 = 0;

     for (j = 0; j < nsegments; j++) {

         elliptic_transform(cos(phi1), sin(phi1), da, db, dalpha, &tx,

                     &ty);

         Vect_append_point(*oPoints, px + tx, py + ty, 0);

         phi1 += angular_step;

     }

    }

    else {

    }

    /* close the output line */

    Vect_append_point(*oPoints, (*oPoints)->x[0], (*oPoints)->y[0],

               (*oPoints)->z[0]);

    return;

}

三.参考文献

a)         GRASS (Geographic Resources Analysis Support System)  http://grass.itc.it/

b)        GEOS (Geometry Engine - Open Source) http://trac.osgeo.org/geos/

c)        JTS(JTS Topology Suite)http://www.vividsolutions.com/jts/jtshome.htm

d)        GIS缓冲区应用及算法实现 http://www.blogjava.net/flyingis/archive/2006/04/17/41459.html

e)         Buffer介绍 http://www.volusia.org/gis/buffer.htm

f)         《地理信息系统算法基础》,科学出版社。