GIS演算法基礎(五)向量資料向柵格資料的轉換(點,線演算法實現)
向量結構資料與柵格結構資料的相互轉換,是地理資訊系統的基本功能之一,已發展形成了許多高效的轉換演算法。
原始碼已經放在github上了,需要朋友自取。
https://github.com/XiaoZhong233/GIS_ALG/blob/master/src/scau/gz/zhw/Raster.java
一、向量點的柵格化
向量點的柵格化: 實質是將點的座標x、y換算為柵格行、列號。
注意:柵格的座標原點是從左上角開始的,行數I向下遞增,列數J向左遞增
(X0,Y0)代表柵格的原點,dx,dy代表分別代表柵格的長和寬,也就是大小
實際程式碼中,這個x0,yo因為已知了柵格大小,所以可以求的出來。
在向量資料向柵格資料轉換的過程中,柵格資料可以用二維陣列來表達。
/** * * @param point 帶轉化的向量點 * @param xOffset 起始柵格點的x位移量 * @param yOffset 起始柵格點的y位移量 * @return 0-行 1-列 */ public int[] transformRasterPoint(Point point,double xOffset,double yOffset) { double x0=size+xOffset+this.xOffset;//柵格起始點座標 double y0=ROW+yOffset+this.yOffset; int[] rowAndColumn = new int[2]; //像元大小預設為1 rowAndColumn[0] = (int) (1+ (Math.floor(Math.abs((y0-point.getY())/size)))); rowAndColumn[1] = (int)(1+(Math.floor(Math.abs((point.getX()-x0))/size))); return rowAndColumn; }
測試結果:
輸入:
Raster raster = new Raster(30,30); //建立一個30x30的柵格
Point p = new Point(.5, .5);
System.out.println(p);
int[] a = raster.transformRasterPoint(p, 0, 0);
System.out.println(String.format("I=%d,J=%d", a[0],a[1]));
輸出:
(0.500000,0.500000)
I=30,J=1
二、向量線的柵格化
①八方向柵格化
八方向柵格法很容易理解,就是已知直線的傾角情況,在每行或每列只有一個像元被塗黑。
演算法思想:
- 一條線段有兩個端點:P1(x1,y1)、P2(x2,y2),先分別確定其行列號(I1,J1)及(I2,J2),然後求出這兩個端點位置的行數差和列數差。
- 若行數差大於列數差,則逐行求出本行中心線與過這兩個端點的直線的交點(X,Y),得到交點行、列號,將交點“塗黑”:Y=yi行中心線,X=(yi-y1)k+x1;式中k=(x2-x1)/(y2-y1),這個公式不看也罷,其實就是兩直線求交點的方程
- 若行數差小於或等於列數差,則逐列求出本列中心線與過這兩個端點的直線的交點(X,Y),得到交點行、列號,將交點“塗黑” :X=xi行中心線,Y=(xj-x1)k’+y1;式中k’=(y2-y1) /(x2-x1)
演算法實現:
public void RasterLine(Point start,Point end,int type,double xOffset,double yOffset) {
//開始點與結束點的起始座標
int[] startPoint = transformRasterPoint(start, 0, 0);
int[] endPoint = transformRasterPoint(end, 0, 0);
//
System.out.println(String.format("開始點:(%d,%d)", startPoint[0],startPoint[1]));
System.out.println(String.format("結束點:(%d,%d)", endPoint[0],endPoint[1]));
//塗黑,開始點與終止點(值設為1)
setValue(startPoint[0], startPoint[1]);
setValue(endPoint[0], endPoint[1]);
Line line = new Line(start, end);
//如果是垂直或者水平者直接塗黑
if(line.isHorizontal() || Double.doubleToLongBits(start.getX())==Double.doubleToLongBits(end.getX())) {
if(line.isHorizontal()) {
int i = startPoint[0];
int j0 = Math.min(startPoint[1], endPoint[1]);
int j1 = Math.max(startPoint[1], endPoint[1]);
while(j0!=j1) {
setValue(i, j0);
j0++;
}
}else {
int i = startPoint[1];
int j0 = Math.min(startPoint[0], endPoint[0]);
int j1 = Math.max(startPoint[0], endPoint[0]);
while(j0!=j1) {
setValue(j0, i);
j0++;
}
}
return;
}
//行差與列差
int difOfRow = Math.abs(startPoint[0]-endPoint[0]);
int difOfColumn = Math.abs(startPoint[1]-endPoint[1]);
//若行差大於列差,則逐行求出本行中心線與過兩個端點的直線的交點
//反之則反之
if(difOfRow>difOfColumn) {
//求每行中心線
int num = Math.min(startPoint[0], endPoint[0]); //用於記錄當前行
while(num<=Math.max(startPoint[0], endPoint[0])) {
//因為只需要求出行,所以設列為1即可
Point temPoint=transformVetorPoint(num, 1, 0, 0);
//中心線
double y = temPoint.getY();
//求交點
double x=line.getXByY(y);
Point result = new Point(x, y);
//向量點轉柵格
int[] resultRasterPoint=transformRasterPoint(result, 0, 0);
//"塗黑"柵格
setValue(resultRasterPoint[0], resultRasterPoint[1]);
num++;
}
}else {
//求每列中心線
int num = Math.min(startPoint[1], endPoint[1]);
while(num<Math.max(startPoint[1], endPoint[1])) {
Point temPoint = transformVetorPoint(1, num, 0, 0);
double x = temPoint.getX();
double y = line.getYByX(x);
Point result = new Point(x, y);
int[] resultRasterPoint = transformRasterPoint(result, 0, 0);
setValue(resultRasterPoint[0], resultRasterPoint[1]);
num++;
}
}
}
測試:
輸入:
p(3,9),q(15,30)組成的線段,在30x30的柵格上顯示
輸出
這個渲染的程式碼就不放出來了,有興趣可以到我的github上看。
②全路徑柵格化
演算法思想
說白了就是固定行,求列,固定列,求行的過程。
但值得注意的是,書上並沒有給出計算行號的過程:
實際上也不難推出,因為求起止行列的過程就是已知tan角求不同邊的過程
固定行,求起止列就是求上圖中a的過程 a = tana/b
固定列,求起止行就是求b的過程, b=tana * b
因此可以推出
起始行號:
終止行號:
m是柵格的邊長,j是當前處理的列,y0是柵格座標原點在直角座標系中的縱座標值
演算法實現:
public void RasterLine(Point start,Point end,int type,double xOffset,double yOffset) {
//開始點與結束點的起始座標
int[] startPoint = transformRasterPoint(start, 0, 0);
int[] endPoint = transformRasterPoint(end, 0, 0);
//
System.out.println(String.format("開始點:(%d,%d)", startPoint[0],startPoint[1]));
System.out.println(String.format("結束點:(%d,%d)", endPoint[0],endPoint[1]));
//塗黑,開始點與終止點(值設為1)
setValue(startPoint[0], startPoint[1]);
setValue(endPoint[0], endPoint[1]);
Line line = new Line(start, end);
//如果是垂直或者水平者直接塗黑
if(line.isHorizontal() || Double.doubleToLongBits(start.getX())==Double.doubleToLongBits(end.getX())) {
if(line.isHorizontal()) {
int i = startPoint[0];
int j0 = Math.min(startPoint[1], endPoint[1]);
int j1 = Math.max(startPoint[1], endPoint[1]);
while(j0!=j1) {
setValue(i, j0);
j0++;
}
}else {
int i = startPoint[1];
int j0 = Math.min(startPoint[0], endPoint[0]);
int j1 = Math.max(startPoint[0], endPoint[0]);
while(j0!=j1) {
setValue(j0, i);
j0++;
}
}
return;
}
//計算柵格原點在直角座標系中的座標
double x0=1+xOffset+this.xOffset;
double y0=ROW+yOffset+this.yOffset;
//△x>=△y,則計算列號,反之則計算行號
double difX = Math.abs(start.getX()-end.getX());
double difY = Math.abs(start.getY()-end.getY());
double k = line.getSlope();
if(difX>=difY) {
//開始點與結束點的起始座標
if(Double.doubleToLongBits(end.getY())<Double.doubleToLongBits(start.getY())) {
startPoint = transformRasterPoint(start, 0, 0);
endPoint = transformRasterPoint(end, 0, 0);
line = new Line(start,end);
}else {
startPoint = transformRasterPoint(end, 0, 0);
endPoint = transformRasterPoint(start, 0, 0);
line = new Line(end,start);
}
k = line.getSlope();
//System.out.println(k);
//用於記錄當前行
int i = startPoint[0];
//終止行
int j = endPoint[0];
//計算起始列號
int j0 = (int)Math.floor(((((y0-(i-1)*size-line.getStart().getY())/k)+line.getStart().getX()-x0)/size))+1;
//計算終止列號
int j1 = (int)Math.floor((((y0-i*size-line.getStart().getY())/k)+line.getStart().getX()-x0)/size)+1;
//System.out.println(String.format("s%d,%d", j0,j1));
while(i!=j) {
//i行從j0-j1塗黑
int ja,jb;
ja=j0;
jb=j1;
System.out.println(String.format("%d,%d", j0,j1));
while(ja!=jb) {
if(ja<jb) {
//System.out.println(String.format("i=%d ja=%d", i,ja));
setValue(i, ja);
ja++;
}
else {
//System.out.println(String.format("i=%d jb=%d", i,jb));
setValue(i, jb);
jb++;
}
}
i++;
//本行終止列號等於下一行的起始列號
j0=j1;
//計算下一行終止列號
j1 = (int)Math.floor((((y0-i*size-line.getStart().getY())/k)+line.getStart().getX()-x0)/size)+1;
//System.out.println(String.format("%d,%d", j0,j1));
}
}else {
//System.out.println("第二種");
//開始點與結束點的起始座標
if(Double.doubleToLongBits(end.getX())>Double.doubleToLongBits(start.getX())) {
startPoint = transformRasterPoint(start, 0, 0);
endPoint = transformRasterPoint(end, 0, 0);
line = new Line(start,end);
}else {
startPoint = transformRasterPoint(end, 0, 0);
endPoint = transformRasterPoint(start, 0, 0);
line = new Line(end,start);
}
k = line.getSlope();
//System.out.println(k);
//記錄當前列
int js = startPoint[1];
//記錄終止列
int je = endPoint[1];
//計算起始行號
int is = (int)Math.floor(((line.getStart().getX()-x0-(js-1)*size)*k+y0-line.getStart().getY())/size)+1;
//計算終止行號
int ie = (int)Math.floor(((line.getStart().getX()-x0-js*size)*k+y0-line.getStart().getY())/size)+1;
//System.out.println(String.format("first %d,%d", is,ie));
while(js!=je) {
int ia,ib;
//從is-ie行塗黑
ia=is;
ib=ie;
while(ia!=ib) {
if(ia<ib) {
//System.out.println(ia);
setValue(ia, js);
ia++;
}
else {
//System.out.println(ib);
setValue(ib, js);
ib++;
}
}
js++;
is=ie;
ie = (int)Math.floor(((line.getStart().getX()-x0-js*size)*k+y0-line.getStart().getY())/size)+1;
//System.out.println(String.format("%d,%d", is,ie));
}
}
測試結果:
輸入:
p(3,9),q(15,30)組成的線段,在30x30的柵格上顯示
輸出:
同時,由於輸入的線段是相同的,也可以比對一下八方向柵格化和全路徑柵格化的柵格化效果。
八方向的特點是每行或每列只有一個柵格被塗黑,所以看起來比較細長。而全路徑柵格化,由於計算過程是把所有經過的柵格都”塗黑“了,因此當要以任何方向探測柵格影響的存在或者想要知道向量可能出現在哪些柵格所覆蓋的範圍時,就可以用全路徑柵格化。