-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmeeting-place.py
336 lines (256 loc) · 9.8 KB
/
meeting-place.py
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
"""
This script provides functions to find a point of equal distance from multiple input points,
get amenities around a given point, and display these points on a map.
Author: Evan Wimpey
Date: 20230712
"""
from geopy.geocoders import OpenCage
import numpy as np
import pandas as pd
from math import radians, sin, cos, sqrt, atan2
import folium
from geopy.exc import GeocoderTimedOut
import time
import overpy
import webbrowser
def haversine_distance(coord1, coord2):
"""
Calculate the Haversine distance between two geographic points.
Parameters:
coord1, coord2 : tuple of float
Geographic coordinates (latitude, longitude) of two points.
Returns:
distance : float
Haversine distance between coord1 and coord2 in miles.
"""
# Radius of the Earth in kilometers
R = 6371.0
lat1 = radians(coord1[0])
lon1 = radians(coord1[1])
lat2 = radians(coord2[0])
lon2 = radians(coord2[1])
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
c = 2 * atan2(sqrt(a), sqrt(1 - a))
# Convert to miles
distance = R * c * 0.621371
return distance
def geocode_address(address, api_key):
"""
Calculate the latitude and longitude .
Parameters:
address : string
address of point of interest
api_key : string
OpenCage API key
Returns:
lat,lon : tuple
Returns lat-lon if availabe in OpenCage.
"""
geolocator = OpenCage(api_key)
location = geolocator.geocode(address)
if location is not None:
return (location.latitude, location.longitude)
else:
return None
def geocode_addresses(addresses, api_key):
"""
Geocode a list of addresses.
This function takes a list of addresses in string format and returns a list of
corresponding geographic coordinates.
Parameters:
addresses : list of str
The addresses to geocode.
api_key : str
The API key for the geocoding service.
Returns:
coords : list of tuple
The geographic coordinates of the addresses, as (latitude, longitude) tuples.
Raises:
GeocoderTimedOut: If the geocoding service does not respond within the timeout limit.
"""
coords = []
for address in addresses:
coord = geocode_address(address, api_key)
coords.append(coord)
return coords
def equal_distance_point(coords, resolution=None):
"""
Calculate the best point given the input coords.
Parameters:
coords : list of tuple
List of geographic coordinates (latitude, longitude) of the input points.
resolution : float, optional
The resolution for the grid search.
Returns:
best_point : tuple of float
Geographic coordinates (latitude, longitude) of the point that minimizes the
maximum Haversine distance to the input points.
"""
# Define the bounding box
min_lat = min(coord[0] for coord in coords)
max_lat = max(coord[0] for coord in coords)
min_lon = min(coord[1] for coord in coords)
max_lon = max(coord[1] for coord in coords)
# Set the default resolution if not provided
if resolution is None:
resolution = max((max_lat - min_lat), (max_lon - min_lon)) * 0.01
lat_values = np.arange(min_lat, max_lat, resolution)
lon_values = np.arange(min_lon, max_lon, resolution)
best_point = None
best_max_distance = None
for lat in lat_values:
for lon in lon_values:
point = (lat, lon)
distances = [haversine_distance(point, coord) for coord in coords]
max_distance = max(distances)
if best_max_distance is None or max_distance < best_max_distance:
best_point = point
best_max_distance = max_distance
return best_point
def create_cp_map(input_coordinates, equal_distance_point):
"""
Generate a map from the input coordinates and best meeting place.
Parameters:
input_coordinates : list of tuple
List of geographic coordinates (latitude, longitude) of the input points.
equal_distance_point : tuple
The ideal meeting spot.
Returns:
m : map
Map object from the folium package
"""
# Create a map centered at the equal distance point
m = folium.Map(location=equal_distance_point, zoom_start=10)
# Add markers for the input coordinates
for coord in input_coordinates:
folium.Marker(coord).add_to(m)
# Add a marker for the equal distance point
folium.Marker(equal_distance_point, icon=folium.Icon(color="red")).add_to(m)
# Display the map
return m
def get_all_amenities(location, radius=1000):
"""
Find all amenities within specified radius from a point.
Parameters:
location : tuple
The lat and lon of the ideal meeting spot
radius : float
The size of the search radius in meters.
Returns:
amenities : list
list of all amenities found
"""
lat, lon = location[0], location[1]
api = overpy.Overpass()
# Define query
query = f"""
(
node["amenity"](around:{radius},{lat},{lon});
);
out body;
"""
try:
result = api.query(query) # Added timeout
print("Query result obtained") # For debugging
except Exception as e:
print("Query failed:", e) # For debugging
return [] # Return an empty list if the query fails
amenities = []
for node in result.nodes:
lat = float(node.lat)
lon = float(node.lon)
amenity_type = node.tags.get('amenity', 'Unknown')
name = node.tags.get('name', 'Unnamed')
street = node.tags.get('addr:street', '')
house_number = node.tags.get('addr:housenumber', '')
address = f"{house_number} {street}" if street or house_number else "Unknown"
amenities.append((lat, lon, amenity_type, name, address))
print(f"Returning {len(amenities)} amenities") # For debugging
return amenities
def get_amenities(amenities, interests = ['restaurant']):
"""
Subset to amenities of interest.
Parameters:
amenities : list
List of all amenities
interests : list
List of all amenity types of interest.
Returns:
options : dataframe
pandas dataframe with all relevant amenities
"""
# turn list into dataframe
df = pd.DataFrame(amenities, columns=['Latitude', 'Longitude', 'Type', 'Name', 'Address'])
# Filter the DataFrame
filtered_df = df[df['Type'].isin(interests)]
print(f"Returning {len(filtered_df)} relevant amenities")
return filtered_df
def create_map_with_amenities(input_coordinates, amenities_df, n):
"""
Create a map showing the input coordinates and potential meeting places.
Parameters:
input_coordinates : list of tuple
List of geographic coordinates (latitude, longitude) of the input points.
amenities_df : DataFrame
DataFrame with columns 'Latitude', 'Longitude', and 'Name', containing potential meeting places.
n : int
The number of meeting places to display on the map.
Returns:
m : folium.Map
A map showing the input coordinates and the potential meeting places.
"""
# Find the center of all input coordinates
center_lat = np.mean([lat for lat, lon in input_coordinates])
center_lon = np.mean([lon for lat, lon in input_coordinates])
# Create map centered around the center of all input coordinates
m = folium.Map(location=[center_lat, center_lon], zoom_start=30)
# Add input coordinates to the map
for lat, lon in input_coordinates:
folium.Marker([lat, lon], icon=folium.Icon(color="red")).add_to(m)
# Add meeting places to the map
for _, row in amenities_df.head(n).iterrows():
folium.Marker([row['Latitude'], row['Longitude']],
icon=folium.Icon(color="green"),
popup=row['Name']).add_to(m)
return m
def find_amenities(addys, api_key, interests, radius=5000, resolution=None):
"""
Create a map showing the input coordinates and potential meeting places.
Parameters:
input_coordinates : list of string
List of string addresses
api_key : string
OpenCage API key
interests : list of string
Amenities of interest, from https://wiki.openstreetmap.org/wiki/Key:amenity.
Returns:
rel_amenities : dataframe of relevant amenities
A pandas dataframe of relevant amenities
"""
# turn addresses into coordinates
coords = geocode_addresses(addys, api_key)
# find best point
cp = equal_distance_point(coords, resolution)
# find all amenities within radius
all_amenities = get_all_amenities(cp, radius)
# extract only relevant amenities
rel_amenities = get_amenities(all_amenities, interests)
return rel_amenities, coords
# test input params
addys = ["541 Luckie St NW, Atlanta, GA 30313" # Coca-cola
,"2455 Paces Ferry Road Northwest, Atlanta, GA 30339" # Home Depot
,"1030 Delta Blvd, Hapeville, GA 30354" # Delta
,"6655 Peachtree Dunwoody Rd, Atlanta, GA 30328" # Newell
,"55 Glenlake Pkwy NE, Atlanta, GA 30328" # UPS
]
api_key = ew-api-key
interests = ['conference_centre', 'events_venue']
# testing for main function
rel_amenities, coords = find_amenities(addys, api_key, interests)
amen_map = create_map_with_amenities(coords, rel_amenities, 5)
### need a way to display the map
amen_map.save('map.html')
webbrowser.open_new_tab("map.html")
###