GIS算法基础——左转算法拓扑生成

GIS基础算法目录

GIS算法基础——左转算法拓扑生成
GIS算法基础——矢量数据压缩道格拉斯普克压缩算法(非递归实现)



基于JavaScript的左转算法拓扑生成

本博文用以梳理课堂及自学内容,转载请标明出处
本人应用JS中的Canvas对算法结果进行可视化验证,在算法说明及实现中绘制验证部分省略
拓扑相关基本概念:拓扑空间关系是一种对空间结构进行明确定义的数学方法。
•具有拓扑关系的矢量数据结构就是拓扑数据结构。
•拓扑数据结构描述了基本空间目标点、线、面之间的关联、邻接和包含关系
•拓扑空间关系信息是空间分析的基础之一

拓扑生成算法的技术路线

数据预处理–弧段处理,使整幅图形中的所有弧段,除在端点处相交外,没有其他交点,即没有相交或自相交的弧段–结点匹配,建立结点、弧段关系拓扑构建–建立多边形,以左转算法或右转算法跟踪,生成多边形,建立多边形与弧段的拓扑关系–建立多边形与多边形的拓扑关系

拓扑生成技术路线

弧段预处理

拓扑关系自动建立的第一步就是处理弧段,使得弧段不存在自相交和相交现象,其过程分为三步:
–直线段相交的判断方法
–自相交弧段处理
–弧段相交打断处理

左转算法流程

从组成多边形边界的某一条弧段开始;如果该弧段与x轴正向夹角为最大,则从该弧段的同一结点出发的其他弧段中,方向角最小的弧段是该多边形的后续弧段;如果该弧段的方向角最小或介于同一结点的其他弧段方向角之间,则最小夹角偏差所对应的弧段为多边形的后续弧段
(1)顺序取一个结点作为起始结点,取完为止;取过该结点的方位角最小的未使用过的或仅使用过一次,且使用过的方向与本次相反的弧段作为起始弧段。(2)取这条弧段的另一个结点,找这个结点关联的弧段集合中的本条弧段的下一条弧段,如果本条弧段是最后一条弧段,则取弧段集合的第一条弧段,作为下一条弧段。
(3)判断是否回到起点,如果是则形成了一个多边形,记录下它,并且根据弧段的方向,设置组成该多边形的左右多边形信息。否则转(2)。
(4)取起始点上开始的,刚才所形成多边形的最后一条边作为新的起始弧段,转(2);若这条弧段已经使用过两次,即形成了两个多边形,转(1)。

构建结点、弧段、多边形类

// 结点类
	class Nodep extends Point{//结点类继承点类  
	    constructor(id,x,y){  
	        super(x,y);  
	        this.id = id;  
	        this.linkarc = new Array();  
	    }  
	  
	    SortArc(){//将与该结点相关的弧段按方位角排序  
	        for(let i = 0;i < this.linkarc.length;i++){  
	            this.linkarc[i].GetAzimuth(this);  
	        }  
	        this.linkarc.sort(Utils.CompareProp('azimuth'));  
	    }  
	  
	    GetNearArc(aarc){//获取该弧的左转的下一条弧(需使用SortArc排过序)  
	        this.SortArc();  
	        let i = 0;  
	        while(this.linkarc[i] != aarc) ++i;  
	        return this.linkarc[(i+1)%this.linkarc.length];  
	    }  
	  
	    GetuseableArc(){//选择没有被遍历过或被遍历方向与当前不同的弧段  
	        this.SortArc();  
	        let i = 0;  
	        for(i;i<this.linkarc.length;i++){  
	            if(this.linkarc[i].ifergodic!=2){  
	                return this.linkarc[i];  
	            }  
	        }  
	        return 0;  
	    }  
	}  
//弧段类
	class Arc{ 
	    constructor(id,points){  
	        this.id = id;  
	        this.points = points;  
	        this.sp = null;//起始结点  
	        this.ep = null;//终止结点  
	        this.lpg = null;//左多边形  
	        this.rpg = null;//右多边形  
	        this.azimuth = 0;//方位角  
	        this.ifergodic = 0;//用于判断这条边方向的使用次数  
	    }  
  
	    Getspep(){//用于在构建多边形中动态获取起点终点  
	        this.sp = this.points[0];  
	        this.ep = this.points[this.points.length-1];  
	    }  
	  
	    Reverse(){//将点倒置使得正常多边形的边均为顺时针  
	        this.points.reverse();  
	        this.Getspep();  
	    }  
	  
	    GetAzimuth(anode){//获取弧段上一个结点的方位角  
	        if(this.points[0]==anode){  
	            this.azimuth = Utils.GetAzimuth(this.points[0],this.points[1]);  
	        }  
	        else{  
	            this.azimuth = Utils.GetAzimuth(this.points[this.points.length-1],this.points[this.points.length-2]);  
	        }  
	    }  
	  
	    GetDirection(anode){//判断这条弧段的遍历方向,如果方向与点相反则逆置points  
	        if(anode == this.points[0]){  
	            this.Getspep();  
	        }  
	        else if(anode == this.points[this.points.length-1]){  
	            this.Reverse();  
	        }  
	        else console.log("点匹配失败获取方向出现问题");  
	    }  
	      
	    GetanotherNode(node){//获得弧上的另一个结点  
	        if(this.sp == node) return this.ep;  
	        return this.sp;  
	    }  
	}  
//多边形类
	class Polygon{  
	    constructor(id,arcs){  
	        this.id = id;  
	        this.arcs = arcs;  
	        this.points = new Array();  
	        this.part = new Array();//岛  
	        this.centerpoint = this.GetCp();  
	        this.bbox;  
	    }  
	  
	    GetCp(){//获取多边形中心点  
	        let sumx = 0, sumy = 0, sump =0;  
	        for(let i = 0;i<this.arcs.length;i++){  
	            let pointlist = this.arcs[i].points;  
	            sump+=pointlist.length;  
	            for(let j = 0;j<pointlist.length;j++){  
	                sumx += pointlist[j].x;  
	                sumy += pointlist[j].y;  
             }  
	        }  
	        let centerP = new Point(sumx/sump,sumy/sump);  
	        return centerP;  
	    }  
	  
	    JudgePart(){//岛只有一个节点的情况判断删除多余重复弧段  
	        if(this.arcs[0]==this.arcs[1]){  
	            this.arcs.splice(1,1);  
	        }  
	    }  
	  
     GetPoints(){  
	        let i  = 0;  
	        this.JudgePart();  
	        for(i ; i < this.arcs.length;i++){  
	            let arc = this.arcs[i];  
	            if(arc.ifergodic==1){  
	                for(let m = 0;m<arc.points.length-1;++m){  
	                    this.points.push(arc.points[m]);  
 	                }  
	            }  
	            else if(arc.ifergodic==2){  
	                for(let j = arc.points.length-1;j>0;--j){  
	                    this.points.push(arc.points[j]);  
	                }  
	            }  
	            else console.log("赋予多边形点信息出错");  
	        }  
	        this.points.push(this.points[0]);  
	    }  
	  
	    ArcSide(){//给多边形内的弧段赋予多边形拓扑信息  
	        for(let arc of this.arcs){  
	            if(arc.ifergodic == 1) arc.rpg = this;  
	            else if(arc.ifergodic == 2) arc.lpg = this;  
	            else console.log("左转出现错误");  
	        }  
	    }  
	  
	    Area(){  
	        let asum = 0;  
	        for(let i = 0;i<this.points.length-1;i++){  
	            let apoint = this.points[i];  
	            asum+=(this.points[i+1].x*apoint.y-apoint.x*this.points[i+1].y);  
	        }  
	        return asum/2;  
	    }  
	  
	    PolygonBBox(){  
	        let ltp = new Point(this.points[0].x,this.points[0].y);//左上  
	        let rbp = new Point(this.points[0].x,this.points[0].y);//右下  
	        for(let point of this.points){  
	            ltp.x = (ltp.x<point.x)?ltp.x:point.x;  
	            ltp.y = (ltp.y>point.y)?ltp.y:point.y;  
                rbp.x = (rbp.x>point.x)?rbp.x:point.x;  
	            rbp.y = (rbp.y<point.y)?rbp.y:point.y;  
	        }  
	        this.bbox = new BBox(new Array(ltp,rbp));  
	    }  
	} 

左转算法部分

//左转算法
	static SubTurnLeft(stnode,starc){//左转构造多边形递归部分  
	        starc.ifergodic++;  
	        let curnode = starc.GetanotherNode(stnode);  
	        let polyarcs = new Array();  
	        polyarcs.push(starc);  
	        let curarc = curnode.GetNearArc(starc);  
	  
	        while(curnode!=stnode){//当没有回到起始边的时候循环找下一条边  
	            if(curarc.ifergodic==0){//没有遍历过的边先确定方向  
	                curarc.GetDirection(curnode);  
	                curnode = curarc.ep;  
	            }  
	            else if(curarc.ifergodic==1) curnode = curarc.sp;//确定了方向的边则一定是选取起始点  
	            else console.log("左转选取下一条边出现问题");  
	            curarc.ifergodic++;  
	            polyarcs.push(curarc);  
	            curarc = curnode.GetNearArc(curarc);  
	        }  
	        let apolygon = new Polygon(polyid,polyarcs);  
	        ++polyid;  
	  
	        apolygon.ArcSide();//左右多边形  
	        apolygon.GetPoints();//按顺序连接点  
	        this.JudgeHole(apolygon,polygons,holes);//通过面积判断将多边形分别放入多边形以及岛列表中  
	        starc = apolygon.arcs[apolygon.arcs.length-1];//多边形最后一条边作为起始进行递归  
	        //TODO:似乎有更好的逻辑???尝试过之后会有bug  
	          
	        if(starc.ifergodic!=2) this.SubTurnLeft(stnode,starc);  
	        else return;  
	    }  
	  
	    static Turnleft(nodelist){//左转构建多边形主体以及岛的list  
	        for(let node of nodelist){  
	            node.SortArc();  
	            for(let arc of node.linkarc){//选取可以开始遍历构造多边形的边  
	                if(arc.ifergodic==0){//第一次遍历则为其赋予方向  
	                    arc.GetDirection(node);  
	                    this.SubTurnLeft(node,arc);  
	                    break;  
	                }  
	                else if(arc.ifergodic==1){//第二次遍历判断本次遍历方向与第一次是否相同  
	                    if(arc.sp==node) continue;//通过判断第一次遍历的方向起始点判断方向  
	                    else{  
	                        this.SubTurnLeft(node,arc);  
	                        break;  
	                    }  
	                }  
	                else if(arc.ifergodic==2) continue;//遍历过两次的边跳过  
	            }  
	        }  
	    }  

匹配多边形岛

	static PairHoles(polygons,holes){  
	        let flag = 0;  
	        for(let apoly of polygons){  
	            let apolyarea =  apoly.Area();  
	            apoly.PolygonBBox();  
	            let polybbox = apoly.bbox;  
	            for(let ahole of holes){  
	                let holearea = ahole.Area();  
	                if(holearea+apolyarea<0) continue;//第一层判断面积  
	  
	                ahole.PolygonBBox();  
	                let holebbox = ahole.bbox;  
	  
	                if(polybbox.Compare(holebbox)) continue;//第二层判断包围盒  
	  
	                for(let apoint of ahole.points){//第三层逐个点判断是否在内部  
	                    if(Utils.ptInPolyByCorner(apoint,apoly)==0){  
	                        flag = 0;  
	                        console.log(apoint);  
	                        break;  
	                    }  
	                    flag = 1;  
	                }  
	                if(flag){  
	                    apoly.part.push(ahole);  
	                }  
	            }  
	        }  
	}  

可视化效果

利用Canvas将生成的多边形随机填充为不同的颜色,多边形岛统一不填充(但是他们并非为空而也是多边形)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

左转算法逻辑清晰,多读几遍算法过程描述就能理解其逻辑,但是将此逻辑转化为编程语言并不容易,特别是在需要从基础的类开始构建的情况下。
在我进行的过程中,按照自己的思路写了一遍发现总是会有bug,在debug改代码进行了很长时间依然没有效果的情况下,我果断选择全部重写,事实证明是有效的。对于JS这门语言,在很多书中以及博主的教学中,都提及其动态性等导致自己的程序很难debug成功,而花费的时间精力不如从头来过,并且这样能解决大多数的问题。我是真实体验过了。

如果对大家有帮助的话,点赞收藏一下,欢迎回踩,谢谢大家,一起共同进步!

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值