Shortest path between two cities and plotting [BFS, sorting] - Python solution

Foreword: Use Python for data processing, and draw the distribution map of major cities in my country through the support of third-party packages, and use BFS broad search priority search to calculate the shortest route between any two cities

Related Packages

import re   #regular expression package 
import math   #math toolkit 
import networkx as nx #networkx drawing package 
import matplotlib   #matplotlib drawing package 
import matplotlib .pyplot as plt
 from collections import defaultdict  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

data preparation

The following is the latitude and longitude information of some cities:

coordination_source =  """
{name:'Lanzhou', geoCoord:[103.73, 36.03]},
{name:'Jiayuguan', geoCoord:[98.17, 39.47]},
{name:'Xining', geoCoord:[101.74, 36.56]},
{name:'Chengdu', geoCoord:[104.06, 30.67]},
{name:'Shijiazhuang', geoCoord:[114.48, 38.03]},
{name:'Lhasa', geoCoord:[102.73, 25.04]},
{name:'Guiyang', geoCoord:[106.71, 26.57]},
{name:'Wuhan', geoCoord:[114.31, 30.52]},
{name:'Zhengzhou', geoCoord:[113.65, 34.76]},
{name:'Jinan', geoCoord:[117, 36.65]},
{name:'Nanjing', geoCoord:[118.78, 32.04]},
{name:'Hefei', geoCoord:[117.27, 31.86]},
{name:'Hangzhou', geoCoord:[120.19, 30.26]},
{name:'Nanchang', geoCoord:[115.89, 28.68]},
{name:'Fuzhou', geoCoord:[119.3, 26.08]},
{name:'Guangzhou', geoCoord:[113.23, 23.16]},
{name:'Changsha', geoCoord:[113, 28.21]},
//{name:'Haikou', geoCoord:[110.35, 20.02]},
{name:'Shenyang', geoCoord:[123.38, 41.8]},
{name:'Changchun', geoCoord:[125.35, 43.88]},
{name:'Harbin', geoCoord:[126.63, 45.75]},
{name:'Taiyuan', geoCoord:[112.53, 37.87]},
{name:'Xi'an', geoCoord:[108.95, 34.27]},
//{name:'Taiwan', geoCoord:[121.30, 25.03]},
{name:'Beijing', geoCoord:[116.46, 39.92]},
{name:'Shanghai', geoCoord:[121.48, 31.22]},
{name:'Chongqing', geoCoord:[106.54, 29.59]},
{name:'Tianjin', geoCoord:[117.2, 39.13]},
{name:'Hohhot', geoCoord:[111.65, 40.82]},
{name:'Nanning', geoCoord:[108.33, 22.84]},
//{name:'Tibet', geoCoord:[91.11, 29.97]},
{name:'Yinchuan', geoCoord:[106.27, 38.47]},
{name:'Urumqi', geoCoord:[87.68, 43.77]},
{name:'Hong Kong', geoCoord:[114.17, 22.28]},
{name:'Macau', geoCoord:[113.54, 22.19]}
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

data processing

Generally, the initial data is not easy to process, and there is a lot of information that we do not need, and the main information needs to be extracted

def  get_city_info ( city_coordination ) : 
    city_location =  { } #Split 
    the data with a newline 
    for line in city_coordination . split ( "\n" ) : 
        if line . startswith ( "//" ) :  continue #If   the string starts with "// ", skip 
        if line . strip ( )  ==  "" : continue #Remove    the leading and trailing null characters, if the returned empty string, return directly
        
        #\w matches any alphanumeric character, same as [A-Za-z0-9_] 
        city = re . findall ( "name:'(\w+)'" , line ) [ 0 ]    
         #\d matches any decimal digit, Identical to [0-9]; \s matches any whitespace character 
        x_y = re . findall ( "Coord:\[(\d+.\d+),\s(\d+.\d+)\]" , line ) [ 0 ] #returns  a list, take the first element 
        # map converts the elements in x_y to floating-point data, and tuple converts it into a tuple 
        x_y =  tuple ( map ( float , x_y ) ) 
        # store the result in dictionary city_location
        city_location [ city ]  = x_y
     return city_location
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

After the data is processed, print the write result:

city_info = get_city_info ( coordination_source ) 
print ( city_info )
  • 1
  • 2
{'Lanzhou': (103.73, 36.03),
 'Jiayuguan': (98.17, 39.47),
 'Xining': (101.74, 36.56),
 'Chengdu': (104.06, 30.67),
 'Shijiazhuang': (114.48, 38.03),
 'Lhasa': (102.73, 25.04),
 'Guiyang': (106.71, 26.57),
 'Wuhan': (114.31, 30.52),
 'Zhengzhou': (113.65, 34.76),
 'Jinan': (117.0, 36.65),
 'Nanjing': (118.78, 32.04),
 'Hefei': (117.27, 31.86),
 'Hangzhou': (120.19, 30.26),
 'Nanchang': (115.89, 28.68),
 'Fuzhou': (119.3, 26.08),
 'Guangzhou': (113.23, 23.16),
 'Changsha': (113.0, 28.21),
 'Shenyang': (123.38, 41.8),
 'Changchun': (125.35, 43.88),
 'Harbin': (126.63, 45.75),
 'Taiyuan': (112.53, 37.87),
 'Xi'an': (108.95, 34.27),
 'Beijing': (116.46, 39.92),
 'Shanghai': (121.48, 31.22),
 'Chongqing': (106.54, 29.59),
 'Tianjin': (117.2, 39.13),
 'Hohhot': (111.65, 40.82),
 'Nanning': (108.33, 22.84),
 'Yinchuan': (106.27, 38.47),
 'Urumqi': (87.68, 43.77),
 'Hong Kong': (114.17, 22.28),
 'Macau': (113.54, 22.19)}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

Calculate the distance between two cities

#The function input to calculate the distance between cities is the latitude and longitude information of the starting point and destination 
def  geo_distance ( origin , destination ) : 
    """
    Calculate the Haversine distance.

    Examples
    --------
    >>> origin = (48.1372, 11.5756) # Munich
    >>> destination = (52.5186, 13.4083) # Berlin
    >>> round(distance(origin, destination), 1)
    504.2
    """ 
    lat1 , lon1 = origin
    lat2 , lon2 = destination
    radius =  6371   # km

    dlat = math . radians ( lat2 - lat1 ) 
    dlon = math . radians ( lon2 - lon1 ) 
    a =  ( math . sin ( dlat /  2 )  * math . sin ( dlat /  2 )  + 
         math . cos ( math . radians ( lat1 ) ) )  *math . cos ( math . radians ( lat2 ) )  * 
         math . sin ( dlon /  2 )  * math . sin ( dlon /  2 ) ) 
    c =  2  * math . atan2 ( math . sqrt ( a ) , math . sqrt ( 1  - a ) ) 
    d= radius * c

    return d

def  get_city_distance ( city1 , city2 ) : 
    return geo_distance ( city_info [ city1 ] , city_info [ city2 ] )
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Because it needs to be up to the connection situation of each city with surrounding cities, it needs to be connected according to the situation

#Build a connection If the distance between two cities is less than the threshold, it means that you can go from the current city to another city 
def  build_connection ( city_info ) : 
    #cities_connection[key] If the key is not in the dictionary, it will return an empty list instead of reporting an error 
    cities_connection = defaultdict ( list ) #Get 
    a list of city names 
    cities =  list ( city_info . keys ( ) ) 
    for c1 in cities : 
        for c2 in cities : 
            if c1 == c2 :  continue  # skip the same city
            
            #If the distance between two cities is less than the threshold threshold, connect them 
            if get_city_distance ( c1 , c2 )  < threshold : #Add 
                the city name to the dictionary value of 
                cities_connection [ c1 ] . append ( c2 ) 
    return cities_connection
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

Result test:

cities_connection = build_connection(city_info)
# draw connecting lines
cities_connection_graph = nx.Graph(cities_connection)
nx.draw(cities_connection_graph,city_info,with_labels=True,node_size=10)
  • 1
  • 2
  • 3
  • 4

insert image description here

#sort function 
def  sort_by_distance ( pathes ) : 
    def  get_distance_of_path ( path ) : 
        distance =  0 
        # Calculate distance sum 
        for i , _ in  enumerate ( path [ : - 1 ] ) : 
            distance += get_city_distance ( path [ i ] , path [ i ] + 1 ] ) 
        return distance
    return  sorted ( pathes , key = get_distance_of_path )

#Breadth-first search BFS 
def  BFS ( graph , start , destination , search_strategy ) :
     
    #Initial node pathes =  [ [ start ] ] #When 
    the queue is not empty 
    while paths : #The path 
        to take out the head of the queue 
        path = paths . pop ( 0 ) #Get 
        the last city of the path 
        frontiter = path [ -1 ] #Get the list of cities that the city can reach 
        successsors = graph [
        froniter ]
         #traverse the list 
        for city in successsors : #if 
            the city has been skipped between paths [equivalent to being marked] 
            if city in path :  continue #not 
            in the path, add it to the current path 
            new_path = path +  [ city ] 
            # Enqueue the current path 
            pathes . append ( new_path )   # bfs 
        # Sort the data in the result according to the sorting strategy 
        pathes = search_strategy ( pathes ) 
        # If paths are not empty and the last location with the shortest distance is the target location. then returns the current path 
        if paths and  (destination == paths [ 0 ] [ - 1 ] ) : 
            return paths [ 0 ]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

The main function code is as follows:

if __name__ ==  '__main__' : 
    city_info = get_city_info ( coordination_source ) 
    #print(city_info) 
    # draw the map 
    city_graph = nx . Graph ( ) 
    city_graph . add_nodes_from ( list ( city_info . keys ( ) ) ) 
    matplotlib . rcParams [ 'font . sans -serif' ]  =  [ 'KaiTi' ]   # Modify font 
    nx .draw ( city_graph , city_info , with_labels = True , node_size = 10 )

    #Connect cities 
    cities_connection = build_connection ( city_info ) 
    cities_connection_graph = nx . Graph ( cities_connection ) 
    nx . draw ( cities_connection_graph , city_info , with_labels = True , node_size = 8 ) 
    #plt.show() 
    print ( BFS ( cities_connection ,  "Beijing " ,  "Shanghai" , search_strategy =sort_by_distance ) ) 
    print ( BFS ( cities_connection ,  "Zhengzhou" ,  "Shanghai" , search_strategy = sort_by_distance ) )
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

The output is as follows:

['Beijing', 'Tianjin', 'Shanghai']
['Zhengzhou', 'Nanjing', 'Shanghai']
  • 1
  • 2

Related: Shortest path between two cities and plotting [BFS, sorting] - Python solution