1本文主要講解服務區分析的實現(最優路徑已經有很多文章了)
設施服務範圍指在一定限制條件下(如時間、費用或路程等)設施所能提供服務的最大空間領域, 在道路網絡環境中,它通常由一系列結點及邊組成。例如, 某救助站在接到求救電話後10 min 所能到達的區域;某物流公司在配送貨物時500元花費所能到達的區域等。
(1)根據拓撲關係,計算地理網絡的最大鄰接結點數;
(2)構造鄰接結點矩陣和初始判斷矩陣描述地理網絡結構;
(3)應用廣度優先搜索算法確定地理網絡中心服務範圍。
本算法是對Dijkstra最短路徑算法的改進(簡稱“最短路徑算法”)。首先, 將網絡中所有結點初始化爲未標記結點。然後從起點(第一次搜索的起點爲網絡中心)開始搜索與其有路徑連通的未標記結點, 計算阻值, 並將起點標記爲已標記結點, 重複上述過程, 直到某結點的阻值超過網絡中心的阻值。最後, 基於結點及邊的阻值搜索並存儲所有在中心阻值範圍內的邊, 這些邊和結點的集合爲網絡中心的服務範圍。
(但實際情況中可能需要內插一些點,直到找到阻值等於網絡中心的阻值爲止)
2實現過程:<1>數據讀取:直接讀取shp
//1讀取shp文件,得到pgDatastore
public static void conShp(String path){
try {
File file=new File(path);
Map<String, Object> map = new HashMap<String, Object>();
map.put("url", file.toURI().toURL());
System.out.println(map);
pgDatastore = DataStoreFinder.getDataStore(map);
} catch (Exception e) {
e.printStackTrace();
}
}
從postgis中讀取
首先讀取postgis數據庫得到DataStore對象,然後用getfeaturesource(LayerName)得到SimpleFeatureSource即可(注意:這裏的LayerName即爲表名)
//2讀取postgis,得到pgDatastore
//鏈接postgis
public static void conPostGis(String dbtype, String host, String port,
String database, String userName, String password) {
Map<String, Object> params = new HashMap<String, Object>();
params.put(PostgisNGDataStoreFactory.DBTYPE.key, dbtype);
params.put(PostgisNGDataStoreFactory.HOST.key, host);
params.put(PostgisNGDataStoreFactory.PORT.key, new Integer(port));
params.put(PostgisNGDataStoreFactory.DATABASE.key, database);
params.put(PostgisNGDataStoreFactory.SCHEMA.key, "public");
params.put(PostgisNGDataStoreFactory.USER.key, userName);
params.put(PostgisNGDataStoreFactory.PASSWD.key, password);
try {
pgDatastore = DataStoreFinder.getDataStore(params);
if (pgDatastore != null) {
System.out.println("系統連接到位於:" + host + "的空間數據庫" + database
+ "成功!");
} else {
System.out.println("系統連接到位於:" + host + "的空間數據庫" + database
+ "失敗!請檢查相關參數");
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("系統連接到位於:" + host + "的空間數據庫" + database
+ "失敗!請檢查相關參數");
}
}
//3利用pgDatastore,得到featuresource(表)
public static SimpleFeatureSource getFeatureSource(String LayerName) throws IOException{
if(pgDatastore==null){
System.out.println("還未導入數據源,請導入pgDatastore");
return null;
}
featureSource = pgDatastore.getFeatureSource(LayerName);
System.out.println(featureSource.getCount(Query.ALL));
return featureSource;
}
注意事項:
讀取postgis時,數據庫裏面的geom字段不能爲二進制
讀取文件時,文件中最好不要有中文
(1)得到SimpleFeatureCollection
(2)創建一個FeatureGraphGenerator利用它添加SimpleFeature元素並調用其getGraph方法創建Graph
(3)創建出來的Graph中保存着V(節點)和E(邊),這樣就可以進行網絡分析了
//創建graph
public static Graph getGraph(SimpleFeatureSource source) throws IOException{
if(source==null)
{
System.out.println("資源不存在,請先得到featureSource");
return null;
}
SimpleFeatureCollection fCollection = source.getFeatures();
//create a linear graph generate
//構圖時也可以創建一個DirectedLineStringGraphGenerator構建有向圖
LineStringGraphGenerator lineStringGen = new LineStringGraphGenerator();
//wrap it in a feature graph generator
FeatureGraphGenerator featureGen = new FeatureGraphGenerator( lineStringGen );
//throw all the features into the graph generator
FeatureIterator<SimpleFeature> iter = fCollection.features();
try {
while(iter.hasNext()){
Feature feature = iter.next();
featureGen.add(feature);
}
} finally {
iter.close();
}
graph = featureGen.getGraph();
return graph;
}
<3>最短路徑
(1)最短路徑:
使用Astar算法:
1.首先利用AstarFunctions設定權值(即通過此邊的消耗)
2.然後設定start點(起點)和target點(終點)
3.調用AstarShortestFinder()來進行處理
具體代碼如下:
設定權(成本):
public static double discost(Edge e ){
SimpleFeature feature = (SimpleFeature) e.getObject();
Geometry geom = (Geometry) feature.getDefaultGeometry();
//geom.convexHull()將其構成一個圖形
if(Barriers!=null){
for(int i=0;i<Barriers.size();i++){
Geometry g=Barriers.get(i);
if(geom.intersects(g)){
return Double.POSITIVE_INFINITY;
}
}
}
return geom.getLength();
}
public static double discost(AStarNode n1, AStarNode n2){
Node nd1=n1.getNode();
Node nd2=n2.getNode();
Edge e=nd1.getEdge(nd2);
if(e!=null){
SimpleFeature feature=(SimpleFeature)e.getObject();
Geometry geom=(Geometry) feature.getDefaultGeometry();
if(Barriers!=null){
for(int i=0;i<Barriers.size();i++){
Geometry g=Barriers.get(i);
if(geom.intersects(g)){
return Double.POSITIVE_INFINITY;
}
}
}
return ((Point) n1.getNode().getObject())
.distance((Point)n2.getNode().getObject());
}else{
return ((Point) n1.getNode().getObject())
.distance((Point)n2.getNode().getObject());
}
}
//Astar方法的最短路徑計算
public static Path searchRouteByAstar(Node source,Node destination) throws Exception{
if(graph==null){
System.out.println("graph不存在,請構建graph");
return null;
}
if(source.equals(destination)){
System.out.println("起點和終點相同,請重新選點");
return null;
}
Path path=null;
AStarFunctions afuncs=new AStarFunctions(destination) {
@Override
public double h(Node n) {
//結合Astar的算法可以知道這裏的h指的是一個預估的距離destination的消耗值
//disPoint指的是預估的終點
Point disPoint=(Point)this.getDest().getObject();
return ((Point)n.getObject()).distance(disPoint);
}
@Override
public double cost(AStarNode n1, AStarNode n2) {
//注意矢量性和有向性
return discost(n1, n2);
}
};
AStarShortestPathFinder finder=new AStarShortestPathFinder(graph, source, destination, afuncs);
finder.calculate();
path=finder.getPath();
return path;
}
這裏是可以看到傳入的變量是node節點,但是我們實際中是要在地圖上點擊一個起點終點求出最優路徑,因此還需要將鼠標點擊的任意一點歸算的graph的節點裏去,這裏最好使用數據庫空間查詢來算,本文只是用了最簡單的遍歷,算法如下:
//搜尋graph上最近節點的方法
//暫時先採用遍歷的方法
//這裏如果點隔的太遠會直接把pointy輸出,調用最短路徑算法會拋出空指針異常
public static Node getNearestGraphNode(Point pointy){
if(graph==null){
System.out.println("graph不存在,請構建graph");
return null;
}
double dist=0;
Node nearestNode=null;
for(Object o:graph.getNodes()){
Node n=(Node)o;
Point gPoint=(Point)n.getObject();
double distance=gPoint.distance(pointy);
if(nearestNode==null||distance<dist){
dist=distance;
nearestNode=n;
}
}
return nearestNode;
}
歸算到節點之後就可以改造下Astar算法了:
public static Path searchRouteByAstar(Point startPoint,Point endPoint) throws Exception{
if(graph==null){
System.out.println("graph不存在,請構建graph");
return null;
}
Node source=getNearestGraphNode(startPoint);
Node destination=getNearestGraphNode(endPoint);
if(source.equals(destination)){
System.out.println("起點和終點相同,請重新選點");
return null;
}
Path path=null;
AStarFunctions afuncs=new AStarFunctions(destination) {
@Override
public double h(Node n) {
//結合Astar的算法可以知道這裏的h指的是一個預估的距離destination的消耗值
//disPoint指的是預估的終點
Point disPoint=(Point)this.getDest().getObject();
return ((Point)n.getObject()).distance(disPoint);
}
@Override
public double cost(AStarNode n1, AStarNode n2) {
//注意矢量性和有向性
return discost(n1, n2);
}
};
AStarShortestPathFinder finder=new AStarShortestPathFinder(graph, source, destination, afuncs);
finder.calculate();
path=finder.getPath();
return path;
}
這樣看起來就挺完美了,但是如果要加入障礙點怎麼辦那?
其實我們在成本計算中已經考慮障礙物了,如果是個障礙範圍就與當前的graph求交集,交集處的權設置成無窮就好了,這樣就解決了障礙點的問題。
如果是停靠點那?
那就每段都計算一次最優路徑加起來就行了。
使用Dijkstra算法:
1.首先利用Edgeweighter設定權值(即通過此邊的消耗)
2.然後設定start點(起點)和target點(終點)
3.調用DijkstraShortestPathFinder()來進行處理
dijkstra算法大概差不多,直接貼代碼: //dijkstra方法
public static Path searchRouteByDijkstra(Node source,Node destination) throws Exception{
if(graph==null){
System.out.println("graph不存在,請構建graph");
return null;
}
Path path=null;
EdgeWeighter weighter = new EdgeWeighter() {
@Override
public double getWeight(Edge e) {
return discost(e);
}
};
// Create GraphWalker - in this case DijkstraShortestPathFinder
DijkstraShortestPathFinder pf = new DijkstraShortestPathFinder( graph, source, weighter );
pf.calculate();
path= pf.getPath(destination);
return path;
}
public static Path searchRouteByDijkstra(Point startPoint,Point endPoint) throws Exception{
if(graph==null){
System.out.println("graph不存在,請構建graph");
return null;
}
Node source=getNearestGraphNode(startPoint);
Node destination=getNearestGraphNode(endPoint);
Path path=null;
EdgeWeighter weighter = new EdgeWeighter() {
@Override
public double getWeight(Edge e) {
return discost(e);
}
};
// Create GraphWalker - in this case DijkstraShortestPathFinder
DijkstraShortestPathFinder pf = new DijkstraShortestPathFinder( graph, source, weighter );
pf.calculate();
path= pf.getPath(destination);
return path;
}
<4>服務區分析
改造DijkstraShortestPathFinder方法:
1.首先通過Edgeweighter設定權值(即通過此邊的消耗)
2.然後設定start點(起點)
3.最後通過設置一個判定(該判定可能是根據距離也可能是根據時間)來終止該方法的搜索,然後得到該方法返回的所有邊和節點。
public static List<Point> getAdjancyPoint(Node node){
if(graph==null){
System.out.println("graph不存在,請構建graph");
return null;
}
List<Point> points=new ArrayList<Point>();
Point pt=(Point)node.getObject();
System.out.println("傳入的節點:"+pt);
List<Edge> edges=node.getEdges();
for(Edge e:edges){
Node nodeA=e.getNodeA();
Point pa=(Point)nodeA.getObject();
Node nodeB=e.getNodeB();
Point pb=(Point)nodeB.getObject();
if(!pt.equals(pa)){
points.add(pa);
}else if(!pt.equals(pb)){
points.add(pb);
}
}
List<Point>points1=(List<Point>) CollectionUtils.subtract(points,serviceAreaPoints);
System.out.println("加入的臨近點:"+points1);
return points1;
}
//服務區範圍,目前我只是把節點加入進去
public static void ServiceArea(Point startPoint, double cost) throws Exception{
if(graph==null){
System.out.println("graph不存在,請構建graph");
return;
}
Node source=getNearestGraphNode(startPoint);
Point pt=(Point)source.getObject();
serviceAreaPoints.add(pt);
//其實遞歸應該從這裏開始,前面的不用遞歸
List<Point> pts=getAdjancyPoint(source);
for(Iterator<?>itr=pts.iterator();itr.hasNext();){
Point p=(Point)itr.next();
if(p!=null){
Geometry geo=iterRoute(searchRouteByAstar(serviceAreaPoints.get(0), p)).getRoutePath();
double len=geo.getLength();
if(len<=cost){
ServiceArea(p, cost);
System.out.println("點"+p+"加人serviceArea");
}
else{
System.out.println("點"+p+"不加人serviceArea");
}
}
}
}
//獲得服務區點集合
public static Set<Point> getServiceAreaPoints() {
serviceAreaPoints1.clear();
serviceAreaPoints1.addAll(serviceAreaPoints);
return serviceAreaPoints1;
}
這樣就完成了服務範圍分析。
有什麼問題歡迎大家評論與交流。