When you already have a latitude and longitude pair, you can do much more than simply place a marker on a map.
For example, given:
Latitude: 40.7128
Longitude: -74.0060
you may want to find:
- restaurants nearby,
- cafes,
- hotels,
- hospitals,
- gas stations,
- pharmacies,
- or other points of interest.
This is a common feature in travel apps, location tools, map interfaces, delivery applications, and local discovery websites.
In this tutorial, we'll build a simple nearby-place search with JavaScript using OpenStreetMap data and the Overpass API.
We'll cover:
- How radius-based nearby searches work
- How to query OpenStreetMap POIs
- How to search multiple place categories
- How to extract coordinates from results
- How to calculate the distance to each result
- A few important production considerations
The Basic Idea
A nearby search usually starts with three pieces of information:
Latitude
Longitude
Search radius
For example:
40.7128
-74.0060
1500 meters
We can then ask a geographic data source:
Find restaurants within
1500 meters of
40.7128, -74.0060
Conceptually:
User selects location
↓
Latitude + Longitude
↓
Nearby place query
↓
POI results
↓
Name + category + coordinates
↓
Display on map
Once you already have coordinates, the rest becomes a spatial search problem.
Using OpenStreetMap Data
OpenStreetMap contains geographic objects such as:
restaurants
cafes
hotels
schools
hospitals
shops
parks
pharmacies
fuel stations
These locations are usually described using tags.
A restaurant, for example, might contain:
amenity=restaurant
A cafe:
amenity=cafe
A hospital:
amenity=hospital
One way to query this data is the Overpass API.
For a simple experiment or developer tool, it gives us a convenient way to request OpenStreetMap objects near a coordinate.
A Simple Restaurant Query
Suppose we want to find restaurants within 1,500 meters of New York City coordinates:
const latitude = 40.7128;
const longitude = -74.0060;
const radius = 1500;
An Overpass query can look like this:
const query = `
[out:json][timeout:25];
(
node["amenity"="restaurant"]
(around:${radius},${latitude},${longitude});
way["amenity"="restaurant"]
(around:${radius},${latitude},${longitude});
relation["amenity"="restaurant"]
(around:${radius},${latitude},${longitude});
);
out center tags;
`;
There are three object types here:
node
way
relation
A small restaurant may be represented as a node.
A larger building may be represented as a way.
More complex geographic objects can also be relations.
Searching all three gives us more complete results.
Send the Request with JavaScript
We can create a reusable function:
async function findNearbyRestaurants(
latitude,
longitude,
radius = 1500
) {
const query = `
[out:json][timeout:25];
(
node["amenity"="restaurant"]
(around:${radius},${latitude},${longitude});
way["amenity"="restaurant"]
(around:${radius},${latitude},${longitude});
relation["amenity"="restaurant"]
(around:${radius},${latitude},${longitude});
);
out center tags;
`;
const response = await fetch(
"https://overpass-api.de/api/interpreter",
{
method: "POST",
body: new URLSearchParams({
data: query
})
}
);
if (!response.ok) {
throw new Error(
`Nearby search failed: ${response.status}`
);
}
return response.json();
}
Then call it:
findNearbyRestaurants(
40.7128,
-74.0060
).then(data => {
console.log(data.elements);
});
The response contains OpenStreetMap elements matching our query.
Extract Useful Place Information
Raw OpenStreetMap objects contain more information than we usually need.
Let's normalize the response.
function normalizePlace(element) {
const latitude =
element.lat ??
element.center?.lat;
const longitude =
element.lon ??
element.center?.lon;
return {
id: element.id,
name:
element.tags?.name ??
"Unnamed place",
latitude,
longitude,
category:
element.tags?.amenity,
cuisine:
element.tags?.cuisine,
website:
element.tags?.website,
openingHours:
element.tags?.opening_hours
};
}
Now:
const data =
await findNearbyRestaurants(
40.7128,
-74.0060
);
const places =
data.elements
.map(normalizePlace)
.filter(
place =>
place.latitude != null &&
place.longitude != null
);
console.log(places);
The result becomes much easier to work with:
[
{
id: 123456,
name: "Example Restaurant",
latitude: 40.715,
longitude: -74.003,
category: "restaurant",
cuisine: "italian"
}
]
Search Multiple Categories
Searching only for restaurants is useful, but most map applications support several categories.
We can use a regular expression in the Overpass query.
For example:
const amenityTypes = [
"restaurant",
"cafe",
"bar",
"fast_food"
];
Convert them into:
const pattern =
amenityTypes.join("|");
Then use:
["amenity"~"restaurant|cafe|bar|fast_food"]
Our JavaScript function becomes:
async function findNearbyFood(
latitude,
longitude,
radius = 1500
) {
const pattern =
"restaurant|cafe|bar|fast_food";
const query = `
[out:json][timeout:25];
(
node["amenity"~"${pattern}"]
(around:${radius},${latitude},${longitude});
way["amenity"~"${pattern}"]
(around:${radius},${latitude},${longitude});
relation["amenity"~"${pattern}"]
(around:${radius},${latitude},${longitude});
);
out center tags;
`;
const response = await fetch(
"https://overpass-api.de/api/interpreter",
{
method: "POST",
body: new URLSearchParams({
data: query
})
}
);
if (!response.ok) {
throw new Error(
"Unable to search nearby places"
);
}
return response.json();
}
This gives us a simple nearby food search.
Calculate Distance to Each Place
The API tells us which places are nearby, but we may also want to show:
Cafe A 240 m
Restaurant B 480 m
Cafe C 720 m
We can calculate this using the Haversine formula.
function distanceBetween(
lat1,
lon1,
lat2,
lon2
) {
const earthRadius = 6371000;
const toRadians = degrees =>
degrees * Math.PI / 180;
const dLat =
toRadians(lat2 - lat1);
const dLon =
toRadians(lon2 - lon1);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(toRadians(lat1)) *
Math.cos(toRadians(lat2)) *
Math.sin(dLon / 2) ** 2;
const c =
2 * Math.atan2(
Math.sqrt(a),
Math.sqrt(1 - a)
);
return earthRadius * c;
}
Then:
const origin = {
latitude: 40.7128,
longitude: -74.0060
};
const placesWithDistance =
places.map(place => ({
...place,
distance:
distanceBetween(
origin.latitude,
origin.longitude,
place.latitude,
place.longitude
)
}));
We can sort the results:
placesWithDistance.sort(
(a, b) =>
a.distance - b.distance
);
Now the nearest location appears first.
Format the Distance
A small formatting helper improves the UI:
function formatDistance(meters) {
if (meters < 1000) {
return `${Math.round(meters)} m`;
}
return `${(
meters / 1000
).toFixed(1)} km`;
}
Usage:
for (
const place
of placesWithDistance
) {
console.log(
place.name,
formatDistance(place.distance)
);
}
Example:
Coffee Shop 180 m
Pizza Restaurant 420 m
Cafe 1.2 km
Connect Nearby Search to a Map Click
This becomes especially useful when combined with an interactive map.
For example, with MapLibre:
map.on(
"click",
async event => {
const {
lat,
lng
} = event.lngLat;
const data =
await findNearbyFood(
lat,
lng,
1500
);
console.log(
"Nearby places:",
data.elements
);
}
);
Now the workflow becomes:
Click map
↓
Get longitude / latitude
↓
Search nearby places
↓
Display markers
This makes the map itself the search interface.
Instead of forcing users to type an exact address, they can explore visually.
Combine It with Browser Geolocation
We can also start from the user's current position.
navigator.geolocation
.getCurrentPosition(
async position => {
const {
latitude,
longitude
} = position.coords;
const data =
await findNearbyFood(
latitude,
longitude,
1500
);
console.log(data);
},
error => {
console.error(
"Unable to get location",
error
);
}
);
This creates another common workflow:
Use My Location
↓
Browser geolocation
↓
Latitude + Longitude
↓
Nearby search
↓
Restaurants / Cafes / POIs
Always remember that browser geolocation requires user permission.
Location access should also be requested only when the feature actually needs it.
Don't Forget Coordinate Validation
Before sending coordinates to any geographic service, validate them.
Latitude must be:
-90 to 90
Longitude must be:
-180 to 180
A simple helper:
function validateCoordinates(
latitude,
longitude
) {
if (
!Number.isFinite(latitude) ||
latitude < -90 ||
latitude > 90
) {
throw new RangeError(
"Invalid latitude"
);
}
if (
!Number.isFinite(longitude) ||
longitude < -180 ||
longitude > 180
) {
throw new RangeError(
"Invalid longitude"
);
}
}
This prevents many surprisingly common bugs.
The Latitude / Longitude Order Problem
One more thing deserves special attention.
Some APIs expect:
latitude, longitude
Others use:
longitude, latitude
GeoJSON, for example, uses:
[
longitude,
latitude
]
MapLibre also commonly works with:
[
longitude,
latitude
]
But the Overpass around syntax in our example uses:
latitude, longitude
Mixing these up can move your result thousands of kilometers away.
Naming variables explicitly helps:
const latitude = 40.7128;
const longitude = -74.0060;
instead of:
const x = 40.7128;
const y = -74.0060;
Production Considerations
The public Overpass API is great for learning, prototypes, and relatively light queries.
It should not automatically be treated as the backend for a high-traffic production application.
If you're building a larger application, consider:
- caching nearby searches,
- limiting the search radius,
- limiting requested categories,
- avoiding unnecessary repeat queries,
- debouncing map interactions,
- using your own backend,
- or using a commercial/local-search provider designed for production traffic.
Also remember that OpenStreetMap coverage varies by location.
Some cities contain extremely detailed POI information, while other areas may contain fewer mapped businesses.
Try Nearby Search with Real Locations
When developing a nearby-search feature, one of the most useful things you can do is test it with coordinates from different types of places.
For example:
New York
40.7128, -74.0060
London
51.5074, -0.1278
Tokyo
35.6762, 139.6503
Sydney
-33.8688, 151.2093
I've been building CoordMap, a browser-based collection of geographic tools for working with coordinates, maps, elevation, location lookup, and nearby places.
You can use CoordMap Nearby Places to select an address or map location and explore restaurants, cafes, hotels, and similar places around it.
The live CoordMap tool uses its own nearby-place provider; the OpenStreetMap/Overpass implementation in this tutorial is an independent example for developers who want to understand how radius-based geographic searches work.
It's also useful when you need real coordinates to test your own mapping application.
Final Thoughts
Once your application has a reliable latitude and longitude pair, nearby search becomes another layer on top of the same location state.
The architecture can stay surprisingly simple:
Address Search ───────┐
│
Map Click ────────────┼──→ Latitude + Longitude
│
Current Location ─────┘
│
▼
Nearby Search
│
┌──────────┼──────────┐
▼ ▼ ▼
Restaurants Cafes Hotels
│
▼
Markers
This pattern is reusable across travel applications, mapping tools, local search interfaces, delivery systems, and location-based web apps.
And once the coordinate pair becomes the center of your application state, you can easily add other geographic layers such as:
Address
Elevation
Weather
Distance
Nearby places
Time zone
That's one of the reasons latitude and longitude are such useful building blocks for location-based applications.
Top comments (0)