#!/usr/bin/env python
# -*- coding: utf-8 -*-
# # Shortest city tour
#
# This notebooks introduces an algorithmic challenge which consists in finding the shortest path through a set of streets. It gives examples and functions to test a solution.
# In[1]:
get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as plt
plt.style.use("ggplot")
# In[2]:
from jyquickhelper import add_notebook_menu
add_notebook_menu()
# ## Problem definition
# In[3]:
from ensae_projects.datainc.data_geo_streets import get_seattle_streets, shapely_records
from ensae_projects.datainc.data_geo_streets import seattle_streets_set_small, folium_html_street_map
name = get_seattle_streets()
shapes, records, fields = shapely_records(name)
edges_index, edges, vertices, distances = seattle_streets_set_small(shapes, records)
folium_html_street_map(edges_index, shapes, html_width="80%", zoom_start=15)
# **Somebody must walk through all the blue streets and only these streets. What is the shortest way?**
#
# Inputs:
#
# * A list of vertices (crossroads).
# * A list of edges/streets *(a,b)*: it goes from *vertices[a]* to *vertices[b]*.
# * The distances, the distance measure the street length which is longer than the length of the straight line between its extremities.
#
# Outputs:
#
# * a list of edges
#
# We introduce a last array `edges_index` which contains the indices of the streets in Seattle database. A list of edges can be defined by a list of couples *(a,b)* or a list of indices taken from this database. Let's see an example:
# In[4]:
vertices
# In[5]:
edges
# In[6]:
distances
# The last array represents the edges indices related to the whole database describing streets in Seattle. The database is used to compute the length of the streets and their locations. The location is used to draw maps. `edges_index` defines a set of indices of streets in Seattle. **A street is always identified by its index in this database.** They do not change whatever the subset of street is as opposed to the set vertices which is built from this subset of streets.
# In[7]:
edges_index
# The array `shapes` defines the streets as a list of segments. They should be needed only to draw maps.
# In[8]:
shapes[4994].points
# The array `records` contains informations about each street. It is not needed to find the shortest tour.
# In[9]:
{k[0]:v for k, v in zip(fields[1:], records[4994])}
# We display a simplified map.
# In[10]:
from ensae_projects.datainc.data_geo_streets import plot_streets_network
plot_streets_network(edges_index, edges, vertices, shapes, figsize=(10,10));
# ## One solution
#
# A solution is a set of indices of edges. Let's try `edges_index` without one edge as a solution.
# In[11]:
from ensae_projects.challenge.city_tour import distance_solution
solution = edges_index[:-1].copy()
try:
distance_solution(edges_index, edges, distances, solution)
except Exception as e:
print(type(e), str(e))
# The function `distance_solution` detected one edge was not covered. Let's try another one.
# In[12]:
solution = edges_index
try:
distance_solution(edges_index, edges, distances, solution)
except Exception as e:
print(type(e), str(e))
# The path is inconsistent. The function could not find a way to go through all of them without jumping. Note that the order of the streets does not have to be specified. The distance is able to tell if somebody can walk through all the street in one go. This means some streets must be present more than one time in the path. Let's try the following solution:
# In[13]:
solution = edges_index + [17680, 30370, 12783, 0, 3353, 9118, 8378, 15023]
distance_solution(edges_index, edges, distances, solution)
# Let's plot the solution. A function will reorder the streets to go through all of them without jumping. The blue number indicates the position of each street in this path.
# In[14]:
from ensae_projects.challenge.city_tour import euler_path
path = euler_path(edges_index, edges, solution)
plot_streets_network(edges_index, edges, vertices, shapes, order=path, figsize=(10,10));
# Now, what is the best path?
# In[15]: