You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Information_Management_System/ShortestPath.py

314 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/python
# -*- coding: UTF-8 -*-
from math import *
import queue
import sys
import time
import os
from datetime import datetime
import pymssql
import pandas as pd
import numpy as np
import datetime
import math
sys.path.append(r'C:\Users\Administrator\Desktop')
from CarRecord import *
from Arc import *
from ArcMany import *
from CarPath import *
arc_objects=[]
map_graph=[]
car_id_count=[]
#通过经纬度计算两点之间的距离
def DistanceBetween(geo1, geo2):
return pow(pow(float(geo1[0]) - float(geo2[0]), 2) + pow(float(geo1[1]) - float(geo2[1]), 2), 0.5)
# 知道三个顶点坐标ABC看角ACB是否是锐角三角形
def CheckOxygon(A, C, B):
a = DistanceBetween(C, B)
b = DistanceBetween(A, C)
c = DistanceBetween(A, B)
if a ** 2 + b ** 2 < c ** 2:
return False
else:
return True
# 计算起点到终点线段方向与x轴的夹角 逆时针
# geo_list 第一个参数-1终点 0起点 第二个参数 0--y 1--x
def CalDirection(geo_list):
if geo_list[-1][0] == geo_list[0][0]:
if geo_list[-1][1] > geo_list[0][1]:
return float(0)
else:
return float(180)
else:
# tan=slope
# arctan(tan x)=x
slope = (geo_list[-1][1] - geo_list[0][1]) / (geo_list[-1][0] - geo_list[0][0])
if geo_list[-1][0] > geo_list[0][0]: # 如果射线在第1,2象限
return 90 - (atan(slope) / pi * 180) # 由于角度是跟x轴的夹角所以需要90-
else: # 如果射线在第3,4象限
return 90 - (atan(slope) / pi * 180) + 180
# 传入geo1, geo2为直线y = kx + d上两点求geo3在直线上的射影即作垂直后与直线的交点
# 如果形成钝角三角形则垂点取geo1geo2中靠近geo3的那个 就是直接过去
def CalProjection(geo1, geo2, geo3):
geo1 = [float(ele) for ele in geo1]
geo2 = [float(ele) for ele in geo2]
geo3 = [float(ele) for ele in geo3]
a = DistanceBetween(geo2, geo3)
b = DistanceBetween(geo1, geo3)
c = DistanceBetween(geo1, geo2)
if (a**2 + c**2) <= b**2: #钝角三角形且geo3靠近geo2包括点在线段延长线上的情况
return geo2
elif b**2 + c**2 <= a**2: #钝角三角形且geo3靠近geo1包括点在线段延长线上的情况
return geo1
elif a + b == c: # 说明点在线段上
return geo3
else:
if geo1[0] == geo2[0]: # 如果直线竖直0---x;1---y)
return [geo1[0], geo3[1]]
elif geo1[1] == geo2[1]: # 如果直线水平
return [geo3[0], geo1[1]]
else:
k = (geo1[1] - geo2[1]) / (geo1[0] - geo2[0]) # y = kx+d中的k
d = geo1[1] - k * geo1[0] # y = kx+d中的d
x4 = (k * geo3[1] - k * d + geo3[0]) / (1 + k**2) #先求交点经度
y4 = k * x4 + d #再求交点纬度
return [x4, y4]
#逐个计算路段的点、与路段延长点、出发点的的夹角
def CalProjectionOfArc(arc, geo):
length = len(arc.geometry_list)
# 先找一个虚拟点其为点arc.geometry_list[-2]到arc.geometry_list[-1]连线延长线上延长相等距离的点virtual_p
virtual_p = [(2 * arc.geometry_list[-1][i] - arc.geometry_list[-2][i]) for i in range(2)]
# 找从哪个i开始三角形ACB变成钝角其中点A为car_record.geo出发点,点C为arc.geometry_list[i]路段的点点B为virtual_p路段延长点
i = 0
while i < length - 1 and CheckOxygon(geo, arc.geometry_list[i], virtual_p): # 判断是否是锐角
i += 1
# 如果最小的i为0或者len() - 1则取最靠近目标点的两个点计算垂点
# 即使第一个点就出现钝角三角形的情况calProjection函数中会处理
if i == 0: # 如果垂点不在线段上
projection = CalProjection(arc.geometry_list[i], arc.geometry_list[i + 1], geo)
else: # 如果垂点在线段上
projection = CalProjection(arc.geometry_list[i - 1], arc.geometry_list[i], geo)
return projection
# 传入geo1, geo2为直线y = kx + d上两点求geo3到直线的距离
# 如果形成钝角三角形则垂点取geo1geo2中靠近geo3的那个点与geo3的距离
def CalDistance(geo1, geo2, geo3):
geo1 = [float(ele) for ele in geo1]
geo2 = [float(ele) for ele in geo2]
geo3 = [float(ele) for ele in geo3]
a = DistanceBetween(geo2, geo3)
b = DistanceBetween(geo1, geo3)
c = DistanceBetween(geo1, geo2)
if (a ** 2 + c ** 2) <= b ** 2: # 钝角三角形且geo3靠近geo2包括点在线段延长线上的情况
return DistanceBetween(geo2, geo3) * 5 # *5是geo点跑到外面去的panelty
elif b ** 2 + c ** 2 <= a ** 2: # 钝角三角形且geo3靠近geo1包括点在线段延长线上的情况
return DistanceBetween(geo1, geo3) * 5
elif a + b == c: # 说明点在线段上
return 0
else:
if geo1[0] == geo2[0]: # 如果直线竖直
return abs(geo3[0] - geo1[0])
elif geo1[1] == geo2[1]: # 如果直线水平
return abs(geo3[1] - geo1[1])
else:
k = (geo1[1] - geo2[1]) / (geo1[0] - geo2[0]) # y = kx+d中的k
d = geo1[1] - k * geo1[0] # y = kx+d中的d
dist = abs(k * geo3[0] + d - geo3[1]) / sqrt(1 + k ** 2) # 点到直线距离公式
return dist
# 如果起点定位跟路线的相差很远或者方向不对则没有必要取该条arc
def CheckNecessary(arc, car_record):
# 计算该地图中最长的路取一半的大一点即1000防止出现漏判的情况因为下面这个if是只取每段道路的坐标的起点和终点来跟目标点计算距离
if DistanceActual(arc.geometry_list[0], car_record.geo) > 1000:
if DistanceActual(arc.geometry_list[-1], car_record.geo) > 1000:
return False
# 下面if是计算这条线路上有一gps点跟目标点的最短距离小于100m才返回True。
min_dist = float("inf")
for geo1 in arc.geometry_list:
tmp = DistanceActual(geo1, car_record.geo)
if tmp < min_dist:
min_dist = tmp
if min_dist < 10:#起点与路线距离的判断
return True
else:
return False
# 计算GPS匹配到该路段的成本系数cost
def CalCost(arc, car_record):
cost = float("inf")#正无穷
# CheckNecessary函数首先排除与起点定位点距离相差远的路段。
if CheckNecessary(arc, car_record):
# arc.geometry_list为路段点集合length记录点个数。
length = len(arc.geometry_list)
# 先找一个虚拟点其为点arc.geometry_list[-2]到arc.geometry_list[-1]连线延长线上延长相等距离的点
# 虚拟点virtual_p在道路最后一个点之后。
virtual_p = [(2 * arc.geometry_list[-1][i] - arc.geometry_list[-2][i]) for i in range(2)]
# 找从哪个i开始三角形ACB变成钝角其中点A为car_record.geo,点C为arc.geometry_list[i]点B为virtual_p
i = 0
# CheckOxygon检查起点与线路第i点的连线与线路的夹角是否为锐角是锐角继续循环。/LOU
while i < length - 1 and CheckOxygon(car_record.geo, arc.geometry_list[i], virtual_p):
i += 1
# 如果最小的i为0或者len() - 1则取最靠近目标点的两个点计算垂点
# 即使出现钝角三角形的情况calProjection函数中会处理
if i == 0:
# dist为起点定位点与道路的距离direction_road为线路方向作为之后计算cost的直接、间接的参数。/LOU
dist = CalDistance(arc.geometry_list[i], arc.geometry_list[i + 1], car_record.geo)
direction_road = arc.direction
else: # 如果垂点在线段上
dist = CalDistance(arc.geometry_list[i - 1], arc.geometry_list[i], car_record.geo)
direction_road = CalDirection(arc.geometry_list[i - 1: i + 1])
# 综合考虑起点与道路距离得出起点定位点与路段匹配的cost。
cost = dist
return cost
# 给定car_geo和car_direction根据最短距离计算在所有道路的Cost返回Cost最小的道路id
def CalMinCostArcID(car_record):
min_cost = float("inf")
id = 0
# arc_objects存储着所有边的Arc对象实例即所有路段信息。
for arc in arc_objects:
cost = CalCost(arc, car_record)
if cost < min_cost:
min_cost = cost
id = arc.id
return id
# 计算时长
def SecondsBetween(t1, t2):
big = datetime.datetime.strptime(str(t2), "%Y-%m-%d %H:%M:%S")#例print(now.strftime('%a, %b %d %H:%M'))输出Mon, May 08 20:22
small = datetime.datetime.strptime(str(t1), "%Y-%m-%d %H:%M:%S")
return (big - small).total_seconds()#用秒统计
# 返回两个点的实际距离x,y与米的换算约为1:111000。不同的比例尺数值不同
def DistanceActual(geo1, geo2):
return pow(pow(float(geo1[0]) - float(geo2[0]), 2) + pow(float(geo1[1]) - float(geo2[1]), 2), 0.5)*111000
# 输入arc_cover是arc_id和cover的列表该函数输出这个arc_cover包含的道路总长
def CalLenCover(arc_cover):
travel_distance = 0
for k in range(len(arc_cover)):
# arc_objects存储着所有边的Arc对象实例即所有路段信息。
travel_distance += arc_objects[arc_cover[k][0] - 1].len * arc_cover[k][1]
return round(travel_distance,1)
# 输入arc_list是arc_id的列表该函数输出这个arc_list包含的道路总长
def CalLen(arc_list):
travel_distance = 0
for k in range(len(arc_list)):
travel_distance += arc_objects[arc_list[k] - 1].len
return round(travel_distance,1)
# 拿着集合找最短的
def findMinDist(dist, collected):
min_dist = float("inf")
min_key = -1
for key in dist:
if dist[key] < min_dist and (key not in collected.keys()):
min_dist = dist[key]
min_key = key
return min_key
# 用单源最短路径算法求两条边的最短距离的arc_list
# graph邻近路网关系图start_arc_id起点路段end_arc_id终点路段
def dijkstra(graph, start_arc_id, end_arc_id):
if start_arc_id == end_arc_id: # 当前一路段与后匹配路段一样
return [start_arc_id]
dist = {}
path = {}
collected = {}
# 初始化距离为无穷大
for key in graph:
dist[key] = float("inf")
for arc in graph[key]:
if not arc.to_node in dist.keys(): # 如果邻近路网关系图中没有路段的起点等于arc的终点则距离设为无穷
dist[arc.to_node] = float("inf")
from_node = arc_objects[start_arc_id - 1].to_node # 起始路段起点
to_node = arc_objects[end_arc_id - 1].from_node # 终止路段终点
if from_node in graph.keys(): # 如果graph中有路段起点与from_node一致
for arc in graph[from_node]: # 遍历路段起点与from_node一致的路段
dist[arc.to_node] = arc.len # 更新始路段起点到该点的距离
path[arc.to_node] = from_node # 更新始路段起点到该点的路径
dist[from_node] = 0 # 起始路段起点到起始路段起点距离为0
collected[from_node] = True
while True:
node = findMinDist(dist, collected)
if node == -1:
break
collected[node] = True
if node in graph.keys():
for arc in graph[node]:
if arc.to_node not in collected.keys():
if dist[arc.to_node] > dist[node] + arc.len:
dist[arc.to_node] = dist[node] + arc.len
path[arc.to_node] = node
arc_list = [end_arc_id]# 先放入终点
while to_node in path.keys():
for arc in graph[path[to_node]]:
if arc.to_node == to_node:
arc_list.append(arc.id)
to_node = path[to_node]
arc_list.append(start_arc_id) #最后放入起点
arc_list = arc_list[::-1] #两级反转
return arc_list
#arc上一个点所匹配的路径car_record点对象distarc终点到点的距离
def BFSFindPathArc(arc, car_record, dist):
min_cost = 60 # min_cost初始值为60近似为无限大
arc_list = [] # arc_list是点从上一个点走过的路径集合
min_arc_id = 0 # min_arc_id是得到最小cost的路段
visited = {} # visited 里面记录着已经搜索过计算过cost的路段
graph_around = {} # graph_around是连接着上一匹配路段的临近路段集合。
q = queue.Queue() # q是一个队列里面存放需要计算cost的备选路段
q.put(arc) # 将arc放入队列
visited[arc.id] = True # 表示arc已经搜索过
while not q.empty(): # 当q里还有需要计算cost的路段则继续循环
arc1 = q.get() # 将q里最后放进去的路段拿出并从q里删除
# 生成arc附近连通的道路的图后面用来找最短路径
if arc1.from_node in graph_around.keys():
graph_around[arc1.from_node].append(arc1) # 将起点ID相同的路段放入同一个索引
else:
graph_around[arc1.from_node] = [arc1]
cost = CalCost(arc1, car_record) # 计算arc1的cost
if cost < min_cost:
min_cost = cost # 更新最小cost
min_arc_id = arc1.id # 更新最小cost的路段
if arc1.to_node in map_graph.keys(): # 如果arc1路段的终点在地图临近关系表map_graph里
for arc2 in map_graph[arc1.to_node]: # 在以arc1路段终点为起点的路段集合里做循环以arc2变量表示
if arc2.id not in visited.keys(): # 如果arc2不在visited集合中
if DistanceBetween(arc.geometry_list[-1], arc2.geometry_list[0]) < dist*1.5:
# 如果arc1终点到arc2的起点的距离 小于 arc终点到GPS点的距离
q.put(arc2) # 将arc2放进队列
visited[arc2.id] = True # 将arc2标记已搜索
if min_arc_id != 0:
arc_list = dijkstra(graph_around, arc.id, min_arc_id) # 计算前一匹配路段到现匹配路段的最短路径
return arc_list
# 计算car_i经过的路径将路段index记录在car_i_start_end_inde_list中路径以j表示
def CarRecordsDivide(car_i):
# car_id_count存储着从1到100每一条OD的定位数量 /LOU
index_start = sum(car_id_count[:car_i-1]) # 第i条OD数据的起始索引位置
index_end = sum(car_id_count[:car_i]) # 第i条OD数据的终止索引位置
car_i_start_end_index_list = []
car_i_start_end_index_list.append([index_start, index_end])
return car_i_start_end_index_list
#计算属于一天中的第几个小时
def Time_stage(gps_time):
today_time=datetime.datetime(2022, 7, 9, 0, 0, 0)
return math.ceil(SecondsBetween(today_time, gps_time)/3600)