在項目中經常用到多個城市地理數據的合併,但因城市的邊界的規則性,就造成了在合併兩個城市邊界時出現了中間有空洞區域的情況。
以青海與甘肅張掖地理邊界進行合併爲例(爲什麼不用甘肅省的,因爲省與省的地理邊界往往是從同一套地理數據中提取的,存在邊界空洞的情況就少一些)。來說明一下我的處理思路。
1.邊界數據來源:阿里的地圖選擇器。datav.aliyun.com/tools/atlas。
2.在使用Geotools的Polygon.union進行合併後會出現邊界有許多線段,從地圖上放大看,其實是小塊的空白區域。[如圖1]
3.查看了Geotools的文檔,有一個GeometryFilter的介紹(它的一些實現類有LineStringExtracter,PolygonExtracter)。可以對需要的幾何圖形進行過來篩選。
4.參考PolygonExtracter寫了一個根據空洞圖形內的標識座標,來篩選哪些空洞圖形可以保留,哪些空洞圖形可以去掉。
主要是在filter方法,先獲取面的外邊界,生成一個全面圖形。然後遍歷原始面的內線InteriorRing,判斷哪些標記點在內線圖形上,就在全面圖形中symDifference掉需要留空的區域。
import java.util.*;
import com.vividsolutions.jts.geom.*;
/**
* 對面進行處理,去掉內部的空洞面。只保留inPointst滿足條件的內部空洞面。
*/
public class MyPolygonExtracter implements GeometryFilter {
private List inPoints;
private List<Geometry> geometries;
/**
* @param inPoints 需要保留區域的標識 。說明:如果給定的inpoints中的其中一個座標在孔洞平面的內部,就保留這個孔洞
* @param list
*/
private MyPolygonExtracter(List<Point> inPoints, List<Geometry> list) {
this.inPoints=inPoints;
this.geometries=list;
}
/**
* @param geom 多平面合併後的面圖形
* @param inPoints 需要保留區域的標識 。說明:如果給定的inpoints中的其中一個座標在孔洞平面的內部,就保留這個孔洞
* @return
*/
public static Geometry getPolygons(Geometry geom, List<Point> inPoints) {
List<Geometry> list=new ArrayList<>();
if (geom instanceof Polygon) {
geom.apply(new MyPolygonExtracter(inPoints,list));
} else if (geom instanceof MultiPolygon) {
geom.apply(new MyPolygonExtracter(inPoints,list));
}
return list.get(0);
}
@Override
public void filter(Geometry g) {
if(g instanceof Polygon){
Polygon polygon=(Polygon)g;
//使用面的外邊界生成一個大面
Geometry tempExterPoly =g.getFactory().createPolygon(polygon.getExteriorRing().getCoordinates());
if(geometries.size()==0){
geometries.add(tempExterPoly);
}else {
geometries.set(0,geometries.get(0).union(tempExterPoly));
}
//得到面內空洞區域的數量
int line=polygon.getNumInteriorRing();
for(int i=0;i<line;i++){
//獲取空洞區域線邊界
LineString linearRingIn=((Polygon) g).getInteriorRingN(i);
//生成空洞面
Polygon temP=g.getFactory().createPolygon(linearRingIn.getCoordinates());
//判斷
if(pointInPolygon(temP)){
//symDifference 從外邊界面中去掉空洞面
geometries.set(0,geometries.get(0).symDifference(temP));
}
}
}else if(g instanceof MultiPolygon){
int geoNums= ((MultiPolygon)g).getNumGeometries();
for(int i=0;i<geoNums;i++){
filter(g.getGeometryN(i));
}
}
}
/**
* 判斷 inPoints的座標是否在空洞面內部
* @param polygon
* @return
*/
private boolean pointInPolygon(Polygon polygon) {
for (int i = 0; inPoints!=null &&i <inPoints.size() ; i++) {
if( polygon.contains((Point)inPoints.get(i))){
return true;
}
}
return false;
}
}
4.調用方法進行測試和測試效果
import cn.hutool.core.io.FileUtil;
import com.fw121.web.util.MyPolygonExtracter;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
import org.geotools.geojson.geom.GeometryJSON;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
public class PolygonUnionTest {
GeometryJSON geometryJSON;
@Before
public void before(){
geometryJSON=new GeometryJSON();
}
@Test
public void uninTest() throws IOException {
String qinghai= FileUtil.readUtf8String("C:\\Users\\DELL\\Desktop\\qinghai.json");
String zhangye= FileUtil.readUtf8String("C:\\Users\\DELL\\Desktop\\zhangye.json");
Geometry qinghaiGeo=geometryJSON.read(qinghai);
Geometry zhangyeGeo=geometryJSON.read(zhangye);
Geometry unionPolygon=qinghaiGeo.union(zhangyeGeo);
System.out.println(toGeoJsonStr(unionPolygon));
//需要保留空洞的標識座標點
List<Point> inPoint=new ArrayList<>();
Coordinate coords = new Coordinate(100.5733966, 38.247051);
inPoint.add(unionPolygon.getFactory().createPoint(coords));
Geometry geometry = MyPolygonExtracter.getPolygons(unionPolygon,inPoint);
System.out.println(toGeoJsonStr(geometry));
}
public String toGeoJsonStr(Geometry geometry) throws IOException {
StringWriter w = new StringWriter();
geometryJSON.write(geometry,w);
return w.toString();
}
}
效果圖,紅色框中的爲根據關鍵點保留的空白區域。