By Abdullah Atif. Built for the Geoapify Open Geospatial Program, Batch #2: AI × Geospatial.
Emergency in Bangladesh? Call 999 first. This project helps you find a facility. It does not replace emergency services.
In Dhaka, the hospital that looks closest on a map is often not the one you can reach first. One-way roads,
canals, rivers and dense traffic change the answer. I wanted to see whether AI tools can answer the question
people actually have in an emergency, "where can I get to fastest?", and where they get it wrong.
I answered the same question in three ways with Geoapify:
- An AI agent that calls Geoapify MCP tools at run time (tested in Claude Code and in VS Code + GitHub Copilot).
- A small Python CLI that an AI coding agent wrote from the Geoapify OpenAPI specifications.
- A phone-friendly web app for people who are not developers: "use my location", tap to call 999, Bangla or English.
Then I tested the pipeline on 10 real Dhaka locations, in English and in Bangla.
Try the app: https://abdullah-atif.github.io/dhaka-emergency-finder/app/ ·
Code: https://github.com/Abdullah-Atif/dhaka-emergency-finder (MIT)
What you need
- Python 3.9 or newer (the CLI uses only the standard library)
- A free Geoapify API key from https://myprojects.geoapify.com
- Optional: an MCP-capable AI client (Claude Code, Claude Desktop, VS Code + GitHub Copilot, Cursor)
Put the key in an environment variable. Never commit it:
export GEOAPIFY_API_KEY=your_key # macOS / Linux / Git Bash
$env:GEOAPIFY_API_KEY = "your_key" # Windows PowerShell
Part 1: The question, step by step
Whichever way you choose, the answer comes from the same four steps:
| Step | Geoapify MCP tool | Geoapify REST API |
|---|---|---|
| 1. Where am I? | geocode_address |
Forward Geocoding |
| 2. What is nearby? |
search_places (healthcare.hospital, ...) |
Places |
| 3. What can I reach fastest? | calculate_route_matrix |
Route Matrix |
| 4. How do I get there? | calculate_route |
Routing |
Step 3 is the important one. Instead of sorting by straight-line distance, we ask for travel times from one
source to up to 10 candidates in a single request, and sort by time. Use traffic=approximated: it lowers
speeds on roads that are likely to be congested.
Part 2: The AI agent (Geoapify MCP)
Connect the client
Geoapify MCP is a remote Streamable HTTP server at https://api.geoapify.com/v1/mcp. It authenticates with your
key in the x-api-key header. In Claude Code:
claude mcp add --transport http geoapify https://api.geoapify.com/v1/mcp --header "x-api-key: YOUR_API_KEY"
Check that it is connected:
$ claude mcp list
geoapify: https://api.geoapify.com/v1/mcp (HTTP) - ✔ Connected
I tested this command with Claude Code 2.1, and I also tested VS Code + GitHub Copilot (session 3 below). Setup for both
is in agent/MCP_SETUP.md.
Tell the agent how to behave
Tools alone are not enough. The agent needs rules, especially for an emergency use case. Mine
(agent/AGENT_INSTRUCTIONS.md) say:
- Always start with "Call 999".
- Never invent a facility, phone number or time. Every fact must come from a tool result.
- Rank by travel time, not by distance.
- If geocoding only matched the city, ask for a landmark instead of answering for the city centre.
Session 1: English, every call logged
I asked: "I am at Mirpur 10 golchottor. Where is the fastest hospital to reach by car?" For this first session,
the agent (Claude Code) called the MCP tools through a tiny JSON-RPC helper,
agent/mcp_call.py, so that
every request and response is logged. The agent decided every call itself; the helper only sends them. It made 6 tool
calls. The full transcript is in the repository. Here is what happened:
-
geocode_addressfound the Mirpur 10 bus station withmatch_type: inner_partand confidence 1. -
search_placesfound 10 places tagged as hospitals within 75 to 201 m. -
calculate_route_matrixreturned travel times of 120 to 429 seconds. - The first
calculate_routecall failed:geometry_scope "steps" requires include_instructions to be true. The error was clear, and the agent fixed it in one retry. - The second call worked, but returned 33 s while the matrix said 120 s. The agent had forgotten
traffic=approximatedon the route call. - With the same
trafficsetting the route took 121 s, which matches the matrix.
The answer it gave:
Emergency? Call 999. Fastest by car: AALOK HEALTHCARE (~2 min, 254 m), FSCD Burn Unite Hospital (~2 min),
DR. AZMAL HOSPITAL LTD. (~2 min). Note: Medinova Hospital looks very close on the map (122 m), but by road it is
about 1 km and 7 minutes away.
That last line is the whole point of the project.
Session 2: Bangla, inside Claude Code
Then I asked in Bangla, "আমি ফার্মগেটে আছি, গাড়িতে সবচেয়ে তাড়াতাড়ি কোন হাসপাতালে যেতে পারব?" ("I am at
Farmgate. Which hospital can I reach fastest by car?"). This time Claude Code used its built-in MCP client, with
only the Geoapify tools allowed. It took 4 Geoapify calls and 35 seconds
(write-up and transcript):
geocode_address → search_places (15 places) → calculate_route_matrix (10 targets) → calculate_route with
include_instructions: true, which was right on the first try this time.
The answer came back in Bangla. It listed Al Razi Hospital Complex first (about 1 min, 401 m, "open 24/7" from the
place's opening_hours). It also put Al Fateh Diagnostics, an eye institute and Brain and Mind Hospital in a separate
list as "may not be a general hospital". The instructions only ask for a general reminder; sorting the places this
way was the agent's own idea.
One gap: my instructions said "rank by travel time" but did not say to use traffic=approximated, so the agent used
free-flow speeds. It told the user so ("traffic not counted; real times will be longer"). The instructions now require it.
Session 3: the same question in VS Code + GitHub Copilot
Finally I asked the session 1 question again, this time in VS Code with GitHub Copilot in Agent mode, which used an
OpenAI model and VS Code's own MCP client
(write-up and transcript).
It made the same 4 Geoapify calls, this time with traffic: "approximated" on both the matrix and the route, as the
updated instructions require. It gave the same answer as Claude Code in session 1: AALOK HEALTHCARE, about 2 min, 254 m.
Two surprises worth knowing:
-
Copilot read
.vscode/mcp.jsonon its own, after a permission prompt, and so sent my Geoapify key to the model, because for this test I had written the key straight into that file. A key exposed like this should be replaced. Use VS Code'sinputsprompt (${input:geoapify-key}) instead, and deny file-read requests for config files. - Pressing Start on the MCP server before sending any chat message fails with "Send a message first." Just ask your question; the server starts on its own.
What worked: MCP tools take {lat, lon} objects everywhere, so the agent never mixed up coordinate order.
Error messages were precise enough to fix in one retry.
What needed a human: the two mistakes in session 1 (steps 4 and 5), and the missing traffic in session 2. Each one
became a rule in the instructions: use traffic: "approximated" for driving, and the same mode and traffic for
the matrix and the route.
Part 3: The CLI, written by an AI coding agent from the OpenAPI specs
I gave Claude Code the four JSON specifications (Forward Geocoding, Places, Route Matrix, Routing) and a short
brief. The brief is in prompts/01-prototype.md.
The result is a standard-library-only Python package:
python -m dhaka_emergency "Farmgate, Dhaka" --top 2
!! Emergency? Call 999 first.
From: Farmgate, Airport Road, Tejgaon, Dhaka - 1215, Bangladesh
Nearest hospital by drive travel time:
1. Al Fateh Diagnostics
1 min | 0.3 km by road | 0.3 km straight line
Note: the name suggests this may not have an emergency department
Kazi Nazrul Islam Avenue, Tejgaon, Dhaka - 1215, Bangladesh
2. Brain and Mind Hospital
1 min | 0.5 km by road | 0.3 km straight line
Airport Road, Tejgaon, Dhaka - 1215, Bangladesh
Add --map out.html to get a Leaflet map with Geoapify tiles and the route to the first result.
The core of it
The heart of the code is the ranking step: one Route Matrix request with one source and all candidates as targets
(simplified from geoapify.py):
body = {
"mode": mode,
"sources": [{"location": [lon, lat]}],
"targets": [{"location": [f.lon, f.lat]} for f in candidates],
"traffic": "approximated",
}
data = post("https://api.geoapify.com/v1/routematrix", body)
times = data["sources_to_targets"][0] # one row: source -> each target
What the AI got right from the specs
- The right servers (
/v1for geocoding, routing and matrix;/v2for places). - Exact category keys (
healthcare.hospital,service.fire_station,emergency.ambulance_station). - The
x-api-keyheader from the specs'securitySchemes, instead of putting the key in the URL.
The trap: coordinate order
The REST APIs do not agree on coordinate order. Routing waypoints use lat,lon, while Places
filter/bias and Route Matrix location use lon,lat. This is the easiest bug to ship. The fix is to keep
all HTTP calls in one module, with a comment at each spot. (The MCP tools avoid the problem completely with
named {lat, lon} fields.)
Part 4: A web app for everyone else
A CLI and an AI client are fine for developers, but in an emergency people reach for a phone. So the same pipeline
also runs as a single static page (docs/app/,
no build step, no server), hosted free on GitHub Pages:
| Bangla, dark mode, hospitals near Mirpur 10 | English, light mode, fire service near Shahbag |
|---|---|
![]() |
![]() |
What it does differently from a plain "nearest hospital" search:
- A big "tap to call 999" button at the top, before anything else.
- 📍 Use my location starts the search right away, and looks up the street address in parallel.
-
Suggestions while you type, like Google Maps, from the Geoapify
Address Autocomplete API. One catch: Autocomplete
returned nothing for partial Bangla words ("ধানমন্ডি", "মিরপুর ১০"), while the regular search found them. So for Bangla
text the app falls back to
/geocode/search.
- Results appear in two steps. The 10 nearest places show by distance after the Places call (1.6 s in one test on a good connection; it depends on the network); they are re-sorted by travel time when the Route Matrix answers, and the route to #1 is drawn on the map.
- The same warnings as the CLI: city-level geocoding fallback, and a badge on every hospital saying whether it likely has an emergency department (see Part 6), plus a warning for places whose name says they are something else. In the Shahbag fire-service search, #2 was "Mosque of Bangladesh Fire Service & Civil Defence Headquarter".
- It merges duplicates ("radda" and "RADDA", 5 m apart, were both in the Places results).
- Bangla and English interface, a Navigate button that opens directions in Google Maps, and tap-to-call for places that have a phone number in the map data.
- 🕒 10 / 15 min reach: one tap shades the area you can reach in 10 and 15 minutes, from the Isoline API (3 credits), and says how many of the listed places fall inside it.
- 📤 Share: sends "I am going to X, about 12 min by car" with map links to where you are and where you are going (the phone's share menu, or WhatsApp on a computer).
-
✏️ Fix it on OpenStreetMap: every card links to the place on OpenStreetMap (Places returns
osm_typeandosm_id), so anyone who sees a wrong name or a missing phone number can correct it for every app, not only this one. - Verified emergency lines. Map data rarely has a phone number, and a hospital's switchboard is not its emergency room. So a separate pass read the official websites of the 7 confirmed hospitals and kept only numbers the hospital itself labels emergency or hotline: BIRDEM, Evercare and Bangladesh Specialized Hospital publish one; DMCH, BMU, NITOR and the eye institute publish only switchboard or daytime numbers, so the app shows none for them rather than a wrong one. Each number carries its source link. Fire-service searches also show the 102 hotline.
- Works like an app: it can be installed to the home screen (web app manifest + service worker), opens without a network, and then shows your last search with a clear "no internet" note. The Geoapify calls themselves are never cached: emergency data must be live.
The browser calls Geoapify directly. Geoapify answers with access-control-allow-origin: *, so a static page is
enough. The pure logic (ranking, duplicate merging, the warning heuristics) has its own tests:
node --test tests/app.test.js.
Part 5: What 10 Dhaka locations showed
python benchmark/run.py geocodes each location, finds up to 10 hospitals, and compares the nearest on the map
with the fastest to reach. Full results ·
manual review
| Finding | Result |
|---|---|
| The nearest hospital on the map was not the fastest by car | 8 of 9 locations; at least 1 minute faster in 6 of 9 (up to 6.9 min) |
| The fastest "hospital" is, by its name, a diagnostic centre or eye hospital | 3 of 9 |
| Geocoding found the right place | 8 of 10: English 4 / 6, Bangla 4 / 4 |
Three lessons:
1. Bangla often geocodes better than English. ধানমন্ডি ২৭ found the Dhanmondi 27 junction, but
Dhanmondi 27, Dhaka fell back to the whole city. বুয়েট found the BUET campus, but BUET, Dhaka matched a
building with "BUET" in its name 8 km away. The full English name matched a company called "… Technology".
2. Check match_type. A city-level fallback looks like a normal answer. The only hint is
rank.match_type = "match_by_city_or_disrict". My first version listed hospitals around the city centre without
any warning, and the benchmark caught it. Now the CLI warns, and the agent asks for a landmark. Some wrong
results still come back as full_match, though, so this check does not catch everything.
3. "Hospital" in the map data is not always a hospital. In Dhaka, OpenStreetMap tags many diagnostic centres,
eye hospitals and even a pharmacy as hospitals. The CLI flags names that suggest no emergency department. It is a
heuristic, so verify before relying on it.
Part 6: Which "hospital" actually has an emergency department?
Testing the app around Dhaka, the fastest "hospital" was often a diagnostic centre, a pharmacy or an unnamed building.
Map data is the reason. Of the 447 places Geoapify returns as healthcare.hospital inside Dhaka city, none has a bed
count, only 40 say anything about emergency care, and 4 of those 40 are wrong: emergency=yes on a homeo pharmacy,
a dental clinic, an outpatient unit and a diagnostic centre.
So I ran a deep-research pass: four researchers in parallel, then one writer
(full report with sources). It covered the OSM tagging rules, Bangladeshi licensing and naming,
sources for 24-hour emergency departments, and the status of the hotlines. Three findings shaped the design:
- Bangladeshi law puts "hospital", "clinic" and "nursing home" in one category. Diagnostic centres, labs and blood banks hold separate licences with no inpatient care. So a word like Diagnostic or Chamber in a name is a strong "no", but Hospital alone proves little.
- Government tiers (medical college hospitals, Upazila Health Complexes) reliably run emergency departments. Specialised institutes take only their own kind of emergency (eye, trauma, children, burns, heart).
- Strong sources confirm a 24-hour emergency department for only 7 Dhaka hospitals.
From that, one rules file (facility_rules.json)
drives the same classifier in the web app (JavaScript) and the CLI (Python). The two agree on all 837 hospital and clinic places
in Dhaka:
| Name in the map data | Badge |
|---|---|
| Dhaka Medical College Hospital, Government hospital, Secretariat Rd, Dhaka | 🚑 24-hour emergency (confirmed, with source) |
| Mugda Medical College Hospital | 🚑 Government hospital: emergency expected |
| Ibn Sina Hospital | Hospital: emergency department not verified |
| Ispahani Islamia Eye Institute and Hospital | Specialised: eye emergencies only |
| Square hospital Consultation and Diagnostic centre | ⛔ Not an emergency hospital: a consultation centre |
Meherun Homeo Pharmacy (tagged emergency=yes) |
⛔ Not an emergency hospital: a pharmacy |
Al Razi Hospital Complex (tagged emergency=no) |
⛔ Map data says: no emergency department |
Places marked ⛔ move to the end of the list but stay visible. The badges work in Bangla too, and Bangla names are
matched (ডায়াগনস্টিক, চেম্বার, চক্ষু, শিশু, পঙ্গু ...).
How good is it? (evaluation)
I reviewed a random sample of 40 places by hand: 36 of 40 were right. The 4 misses were fixed: a consultation centre
with "hospital" in its name, a real name hidden in the second comma part, "Orthopedic and General Hospital", and the
typo "Metarnity". Because I fixed those rules on that same sample, I then checked 30 fresh places: 28 right. The
2 misses ended up as "unknown", never as a wrong answer. In both samples no non-hospital was promoted and no emergency
hospital was hidden. And wherever the classifier disagreed with OSM's own emergency tag, the tag was the one that was wrong.
Outside the samples, a real test with the AI agent found one dangerous miss. Two of NITOR's five map entries
("... Rehabilitation (NITOR)" and the typo "Traum*t*ology") were labelled a physiotherapy centre because of the word
"Rehabilitation", so the country's main trauma hospital was pushed to the end. Now the confirmed-hospital check also
reads the text inside brackets and fixes that typo, and both entries are confirmed again.
A third, larger check after all fixes: 50 fresh places, 47 right.
The 3 misses (an office, a nuclear-medicine institute, "DigiLab") all came out as "unknown", again never as a wrong answer.
What it still cannot do: say how big a hospital is or which departments it has. That data is simply not in the map,
and fixing it upstream in OpenStreetMap would help every app.
The nearest major hospital
The fastest place is not always the best one. From Mirpur 10 the fastest "hospital" is 2 minutes away, but its emergency
department is unknown; a big general hospital is about half an hour away. Without bed counts, the classifier marks
three groups as major: hospitals with a source-confirmed general 24-hour emergency department, government general
hospitals, and medical college hospitals (teaching hospitals with many departments). The app then:
- asks Places for the 200 nearest hospitals instead of 10 (100 reach only about 4 km in central Dhaka), keeps the 10 nearest and adds up to 6 major hospitals from further away, so they get real travel times too;
- shows a "Nearest major hospital" card above the list when #1 is not a major hospital;
- offers a "Major hospitals only" switch (CLI:
--major).
Two map details mattered. The same hospital often appears several times: "Shahid Sohrawardy Medical College" and
"Shaheed Suhrawardi Medical College Hospital" 190 m apart, or three entries for PG Hospital. Distance alone cannot merge
them, because BIRDEM is only 80 m from PG Hospital. So entries merge when they match the same confirmed hospital, or
when they are within 300 m and the first word of the name has the same consonants ("shrwrd").
The cost is honest to state: a hospital search now uses about 27 Geoapify credits (Places: 1 + 1 per 20 places;
Route Matrix: 1 per target) instead of about 13, so a free key covers roughly 100 hospital searches a day.
Part 7: How much of Dhaka is within 15 minutes of an emergency department?
The app answers "where do I go from here?". The same APIs can answer a city-wide question too.
benchmark/coverage.py
(standard library only):
- takes the Dhaka Metropolitan boundary (OSM "ঢাকা মহানগর") from the Geoapify
Boundaries API
part-ofendpoint; - finds every hospital in and around it with the Places API and keeps the ones the classifier from Part 6 can defend: set A = confirmed general 24-hour emergency departments + government general hospitals (11 after merging entries of one campus); set B = A + medical college hospitals (26);
- asks the Isoline API for the area reachable by car in 10 and 15 minutes from each of them (traffic approximated);
- lays a 300 m grid over the boundary (304 km²) and checks which points fall inside any isoline.
| Hospitals counted | Within 10 min | Within 15 min |
|---|---|---|
| A: confirmed + government (11) | 12% of the area | 28% |
| B: A + medical college hospitals (26) | 30% | 54% |
Open the interactive coverage map ·
method and caveats
The public emergency departments cluster in the old city and around Shahbag; most of the metropolitan area, east and
north, relies on private hospitals whose emergency units nobody has verified. Read it with its limits: it measures
area, not people, uses Geoapify's typical-traffic model rather than rush hour, and treats isolines that go out of
a hospital as minutes to it. It also found a data gap: Bangladesh Specialized Hospital, whose 24-hour emergency
department is confirmed, is not in the map data as a hospital at all, so it could not be counted.
Two classifier fixes came out of this run: the map spells Shaheed Suhrawardy Medical College Hospital "Shahid
Sohrawardy" and "Shaheed Suhrawardi", so this 1,000-bed government hospital was being shown as an unverified
private one; and Dhaka Mohanagar General Hospital (Dhaka South City Corporation) was missing from the government list.
Part 8: Feedback for Geoapify
The full list, with evidence for each point, is in feedback.md. The main suggestions:
- Understand Dhaka abbreviations:
R/A→ Residential Area,Gulshan 1 Circle. - Penalise single-token matches for landmark queries ("BUET" → "BUET-TPHS Building").
- Wrong places (up to about 15 km away) returned as
full_matchmakematch_typeless useful as a quality signal. - Small typo in the API:
match_by_city_or_disrict. - MCP
geocode_addresscould tell agents in its description to checkmatch_typebefore using the result. - An MCP tool for place details or outlines. Today
search_placesreturns points only. - A Places condition for hospitals with an emergency department (OpenStreetMap
emergency=yes). - MCP
search_placesdrops the OSM tags (emergency,name:en,opening_hours), which the REST Places API returns. - Address Autocomplete returns nothing for partial Bangla words, while search finds them.
- An Isoline option for the reverse direction ("from where can I reach this point in 15 minutes?"), which is what emergency coverage needs.
Try it
In your browser: https://abdullah-atif.github.io/dhaka-emergency-finder/app/ ·
coverage map
From the command line:
git clone https://github.com/Abdullah-Atif/dhaka-emergency-finder
cd dhaka-emergency-finder
export GEOAPIFY_API_KEY=your_key
python -m dhaka_emergency "ধানমন্ডি ২৭" --type pharmacy
python -m unittest discover -s tests
Location data © OpenStreetMap contributors. Geocoding, places, routing and map tiles by Geoapify.








Top comments (0)