99-Speedmart / app.py
David-Chew-HL's picture
Update app.py
eb9aa71 verified
import gradio as gr
from gradio_folium import Folium
import folium
import pandas as pd
import numpy as np
from geopy.distance import geodesic
def calculate_distances(coords):
distances = []
num_coords = len(coords)
shortest_distances = []
for i in range(num_coords):
shortest_distance = float('inf')
closest_pair = None
for j in range(num_coords):
if i != j:
distance = geodesic(coords[i], coords[j]).kilometers
distances.append((distance, coords[i], coords[j]))
if distance < shortest_distance:
shortest_distance = distance
closest_pair = (coords[i], coords[j])
if closest_pair:
shortest_distances.append((shortest_distance, closest_pair[0], closest_pair[1]))
return distances, shortest_distances
def plot_on_map(coords_list, shortest_distance, longest_shortest_distance, avg_distance):
all_coords = [coord for coords in coords_list for coord in coords]
avg_lat = np.mean([coord[0] for coord in all_coords])
avg_lon = np.mean([coord[1] for coord in all_coords])
m = folium.Map(location=[avg_lat, avg_lon], zoom_start=10)
colors = ['blue', 'red', 'green', 'purple', 'orange', 'darkblue', 'lightgray', 'darkgreen', 'cadetblue', 'pink']
for i, coords in enumerate(coords_list):
group_marker_color = colors[i % len(colors)]
for coord in coords:
store_name = f"Store: {coord}"
if len(coords) == 1:
store_name = "Only 1 store"
folium.Marker(location=coord, popup=store_name, icon=folium.Icon(color=group_marker_color)).add_to(m)
if shortest_distance:
folium.PolyLine(locations=[shortest_distance[1], shortest_distance[2]], color='green', weight=5, tooltip=f'Shortest Distance: {shortest_distance[0]:.2f} km').add_to(m)
if longest_shortest_distance:
folium.PolyLine(locations=[longest_shortest_distance[1], longest_shortest_distance[2]], color='red', weight=5, tooltip=f'Longest Among the Shortest Distances : {longest_shortest_distance[0]:.2f} km').add_to(m)
return m
def generate_map_with_stats(postcode_groups_input):
df = pd.read_csv("speedmart99.csv")
df['postcode'] = df['postcode'].astype(str).str.zfill(5) # Ensure postcodes are 5 digits long
postcode_groups = postcode_groups_input.split(',')
coords_list = []
for postcode_group_input in postcode_groups:
key_length = len(postcode_group_input)
# Filter rows where the postcode starts with the user input (considering the key length)
group = df[df['postcode'].str[:key_length] == postcode_group_input]
if not group.empty:
coords = group['coords'].apply(lambda x: tuple(map(float, x.strip('()').split(',')))).tolist()
coords_list.append(coords)
# Combine all coordinates into a single list
all_coords = [coord for coords in coords_list for coord in coords]
if all_coords:
distances, shortest_distances = calculate_distances(all_coords)
if shortest_distances:
shortest_distance = min(shortest_distances, key=lambda x: x[0])
longest_shortest_distance = max(shortest_distances, key=lambda x: x[0])
avg_distance = np.mean([dist[0] for dist in shortest_distances])
map_object = plot_on_map([all_coords], shortest_distance, longest_shortest_distance, avg_distance)
statistics = []
for i, postcode_group in enumerate(postcode_groups):
statistics.append(f"Postcode Group {i+1}: {postcode_group}")
statistics.append(f"Shortest Distance (green): {shortest_distance[0]:.2f} km")
statistics.append(f"Longest Among the Shortest Distances (red): {longest_shortest_distance[0]:.2f} km")
statistics.append(f"Average of the Shortest Distances: {avg_distance:.2f} km")
return map_object, "\n".join(statistics)
else:
return None, "No data found for the specified postcode groups."
iface = gr.Interface(
fn=generate_map_with_stats,
inputs="text",
outputs=[Folium(), "text"],
title="99 Speedmart Stores Analyzer",
description="This app allows you to <b>input <a href='https://en.wikipedia.org/wiki/Postal_codes_in_Malaysia#:~:text=Postal%20codes%20in%20Malaysia%20%2D%20Wikipedia,History'>postcode areas</a></b> and <b>visualize</b> the locations of <a href='https://www.99speedmart.com.my/About'>99 Speedmart</a> stores within those areas.\n \nYou can <b>analyze the <span style='color: red;'>longest</span>, <span style='color: green;'>shortest</span> and average distances</b> between stores. \nEnter the first few digits of the postcode (up to 5 digits) and you may combine multiple postcode areas by separating them by commas.\n\n\nYou may try: \n\n 811,813 for Tebrau(811xx) and Kempas(813xx) \n\n 4 for Klang(4xxxx) \n\n 83 for Batu Pahat(83xxx)",
article="\n\n\n*Store location data was last updated in February 2024.",
)
iface.launch(share=True)