Skip to content

Commit b47ea40

Browse files
authored
Merge pull request #10 from developmentseed/osmnx-tool
🎉 add osmnx tool to query geometry attributes from OSM & road networks.
2 parents dc79118 + 81828b2 commit b47ea40

File tree

6 files changed

+467
-0
lines changed

6 files changed

+467
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,6 @@ cython_debug/
158158
# and can be added to the global gitignore or merged into this file. For a more nuclear
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
#.idea/
161+
162+
# streamlit cache
163+
cache/

app.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import os
2+
3+
import osmnx as ox
4+
import geopandas as gpd
5+
6+
from langchain.chat_models import ChatOpenAI
7+
from langchain.tools import Tool, DuckDuckGoSearchRun
8+
9+
from tools.mercantile_tool import MercantileTool
10+
from tools.geopy.geocode import GeopyGeocodeTool
11+
from tools.geopy.distance import GeopyDistanceTool
12+
from tools.osmnx.geometry import OSMnxGeometryTool
13+
from tools.osmnx.network import OSMnxNetworkTool
14+
from agents.l4m_agent import base_agent
15+
16+
import geopandas as gpd
17+
import streamlit as st
18+
import folium
19+
from streamlit_folium import folium_static
20+
21+
22+
def get_llm():
23+
llm = ChatOpenAI(
24+
temperature=0,
25+
openai_api_key=os.environ["OPENAI_API_KEY"],
26+
model_name="gpt-3.5-turbo",
27+
)
28+
return llm
29+
30+
31+
def get_agent(llm, name="structured-chat-zero-shot-react-description"):
32+
# define a set of tools the agent has access to for queries
33+
duckduckgo_tool = Tool(
34+
name="DuckDuckGo",
35+
description="Use this tool to answer questions about current events and places. \
36+
Please ask targeted questions.",
37+
func=DuckDuckGoSearchRun().run,
38+
)
39+
geocode_tool = GeopyGeocodeTool()
40+
distance_tool = GeopyDistanceTool()
41+
mercantile_tool = MercantileTool()
42+
geometry_tool = OSMnxGeometryTool()
43+
network_tool = OSMnxNetworkTool()
44+
45+
tools = [
46+
duckduckgo_tool,
47+
geocode_tool,
48+
distance_tool,
49+
mercantile_tool,
50+
geometry_tool,
51+
network_tool,
52+
]
53+
54+
agent = base_agent(llm, tools, name=name)
55+
return agent
56+
57+
58+
def run_query(agent, query):
59+
response = agent(query)
60+
return response
61+
62+
63+
def show_on_map(response):
64+
gdf = response["output"]
65+
center = gdf.centroid.iloc[0]
66+
folium_map = folium.Map(location=[center.y, center.x], zoom_start=12)
67+
folium.GeoJson(gdf).add_to(folium_map)
68+
folium_static(folium_map)
69+
70+
71+
st.title("LLLLM")
72+
73+
query = st.text_input(
74+
"Ask me stuff about the flat world: ",
75+
placeholder="Find all the hospitals in Bangalore",
76+
)
77+
78+
if st.button("Submit", key="submit", type="primary"):
79+
llm = get_llm()
80+
agent = get_agent(llm)
81+
response = run_query(agent, query)
82+
show_on_map(response)
83+
st.success("Great, you have the results plotted on the map.")

environment.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ channels:
44
dependencies:
55
- python=3
66
- pip
7+
- osmnx=1.3.1
78
- pip:
89
- openai
910
- langchain
@@ -12,3 +13,5 @@ dependencies:
1213
- geopy
1314
- ipywidgets
1415
- jupyterlab
16+
- streamlit
17+
- streamlit-folium

nbs/23-05-26_osmnx-tool.ipynb

Lines changed: 312 additions & 0 deletions
Large diffs are not rendered by default.

tools/osmnx/geometry.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from typing import Type, Dict
2+
3+
import osmnx as ox
4+
import geopandas as gpd
5+
from pydantic import BaseModel, Field
6+
from langchain.tools import BaseTool
7+
8+
9+
class PlaceWithTags(BaseModel):
10+
"Name of a place and tags in OSM."
11+
12+
place: str = Field(..., description="name of a place")
13+
tags: Dict[str, str] = Field(..., description="open street maps tags")
14+
15+
16+
class OSMnxGeometryTool(BaseTool):
17+
"""Custom tool to query geometries from OSM."""
18+
19+
name: str = "geometry"
20+
args_schema: Type[BaseModel] = PlaceWithTags
21+
description: str = "Use this tool to get geometry of different features of a place like building footprints, parks, lakes, hospitals, schools etc. \
22+
Pass the name of the place & relevant tags of Open Street Map as args."
23+
return_direct = True
24+
25+
def _run(self, place: str, tags: Dict[str, str]) -> gpd.GeoDataFrame:
26+
gdf = ox.geometries_from_place(place, tags)
27+
gdf = gdf[gdf["geometry"].type.isin({"Polygon", "MultiPolygon"})]
28+
gdf = gdf[["name", "geometry"]].reset_index(drop=True).head(100)
29+
return gdf
30+
31+
def _arun(self, place: str):
32+
raise NotImplementedError

tools/osmnx/network.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from typing import Type, Dict
2+
3+
import osmnx as ox
4+
from osmnx import utils_graph
5+
import geopandas as gpd
6+
from pydantic import BaseModel, Field
7+
from langchain.tools import BaseTool
8+
9+
10+
class PlaceWithNetworktype(BaseModel):
11+
"Name of a place on the map"
12+
place: str = Field(..., description="name of a place on the map")
13+
network_type: str = Field(
14+
..., description="network type: one of walk, bike, drive or all"
15+
)
16+
17+
18+
class OSMnxNetworkTool(BaseTool):
19+
"""Custom tool to query road networks from OSM."""
20+
21+
name: str = "network"
22+
args_schema: Type[BaseModel] = PlaceWithNetworktype
23+
description: str = "Use this tool to get road network of a place. \
24+
Pass the name of the place & type of road network i.e walk, bike, drive or all."
25+
return_direct = True
26+
27+
def _run(self, place: str, network_type: str) -> gpd.GeoDataFrame:
28+
G = ox.graph_from_place(place, network_type=network_type, simplify=True)
29+
network = utils_graph.graph_to_gdfs(G, nodes=False)
30+
network = network[["name", "geometry"]].reset_index(drop=True).head(100)
31+
return network
32+
33+
def _arun(self, place: str):
34+
raise NotImplementedError

0 commit comments

Comments
 (0)