The search feature allows the user to find specific data from a large number of records quickly. Generally, when a search request is submitted, the filtered data is fetched from the database. If you have a large number of records, the search feature is very useful to make the data list user-friendly. Search based on the latitude and longitude is used to find the records within a certain distance. Mostly, the location-based search is used for the property/store/place list. It allows finding the places within a defined radius.
If you’re building location-based applications like store locators, nearby services, or delivery coverage tools, implementing a radius-based search is essential. In this tutorial, you’ll learn how to create a fully functional “Search Nearby Places” system using PHP and MySQL. We’ll cover everything from setting up the database to creating a user-friendly interface and implementing the search logic. By the end of this tutorial, you’ll have a solid understanding of how to build a location-based search feature that can be easily integrated into your projects.
In this tutorial, you will create a web application that allows users to search for places within a specified radius based on their latitude and longitude. The application will include:
– A MySQL database to store place information, including name, address, latitude, and longitude.
– A PHP backend to handle search requests and perform radius-based calculations.
– A Bootstrap 5 frontend to provide a responsive and user-friendly interface for searching and displaying results.
Here’s a brief overview of the project structure (radius-based location search with latitude & longitude using PHP and MySQL):
radius_based_search_with_php/
├── index.php # Main application file
├── includes/
│ └── db.php # Database connection configuration
└── assets/
└── css/
├── bootstrap.min.css # Bootstrap 5 CSS framework
└── styles.css # Custom styles
First, you need to create a MySQL database and a table to store the place information. You can use the following SQL commands to set up your database.
SQL to create the places table:
CREATE TABLE `places` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`address` TEXT DEFAULT NULL,
`latitude` DECIMAL(10,7) NOT NULL,
`longitude` DECIMAL(10,7) NOT NULL,
`created` DATETIME DEFAULT CURRENT_TIMESTAMP(),
`modified` DATETIME DEFAULT CURRENT_TIMESTAMP()
ON UPDATE CURRENT_TIMESTAMP(),
`status` TINYINT(1) DEFAULT 1,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4;
Insert some sample data into the places table to test the search functionality:
INSERT INTO `places` (`title`, `address`, `latitude`, `longitude`, `status`) VALUES
('Statue of Liberty', 'Liberty Island, New York, NY 10004, USA', 40.6892494, -74.0445004, 1),
('Times Square', 'Manhattan, New York, NY 10036, USA', 40.7580000, -73.9855000, 1),
('Central Park', 'New York, NY, USA', 40.7829000, -73.9654000, 1),
('Golden Gate Bridge', 'San Francisco, CA 94129, USA', 37.8199000, -122.4783000, 1),
('Hollywood Sign', 'Los Angeles, CA 90068, USA', 34.1341000, -118.3215000, 1),
('Grand Canyon', 'Arizona, USA', 36.1069000, -112.1129000, 1),
('Las Vegas Strip', 'Las Vegas, NV, USA', 36.1147000, -115.1728000, 1),
('Space Needle', '400 Broad St, Seattle, WA 98109, USA', 47.6205000, -122.3493000, 1),
('Millennium Park', '201 E Randolph St, Chicago, IL 60602, USA', 41.8826000, -87.6226000, 1),
('White House', '1600 Pennsylvania Avenue NW, Washington, DC 20500, USA', 38.8977000, -77.0365000, 1),
('Disney World', 'Orlando, FL 32830, USA', 28.3852000, -81.5639000, 1),
('Niagara Falls', 'Niagara Falls, NY 14303, USA', 43.0962000, -79.0377000, 1),
('Mount Rushmore', '13000 SD-244, Keystone, SD 57751, USA', 43.8791000, -103.4591000, 1),
('Alcatraz Island', 'San Francisco, CA 94133, USA', 37.8267000, -122.4230000, 1),
('Yellowstone National Park', 'Wyoming, USA', 44.4280000, -110.5885000, 1);
Create a file named db.php inside the `includes` directory to handle the database connection. This file will contain the configuration for connecting to your MySQL database using mysqli.
<?php
// Database configuration (replace with your credentials)
$DB_HOST = 'localhost';
$DB_USER = 'root';
$DB_PASS = 'root_pass';
$DB_NAME = 'codexworld_db';
// Enable mysqli exceptions for easier error handling
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$mysqli = new mysqli($DB_HOST, $DB_USER, $DB_PASS, $DB_NAME);
$mysqli->set_charset('utf8mb4');
} catch (mysqli_sql_exception $e) {
// In production you might want to log this instead
http_response_code(500);
echo 'Database connection error: ' . htmlspecialchars($e->getMessage());
exit;
}
// Helper: safely close connection on shutdown
register_shutdown_function(function() use (&$mysqli) {
if ($mysqli instanceof mysqli) {
$mysqli->close();
}
});
?>
Next, create the main application file `index.php` in the root directory. This file will include the search form and display the search results. We will use Bootstrap 5 for styling and layout.
In the index.php file, we will:
– Display a search form that allows users to input a search query, select a radius, and choose a sorting option.
– Capture browser’s geographic coordinates (latitude/longitude) using JavaScript and include them in the search request.
– Handle the search form submission with PHP, process the input parameters, and perform a radius-based search query against the database.
– Use the Haversine formula in the SQL query to calculate the distance between the user’s location and the places in the database.
– Perform the radius-based search query in the database.
– Results are filtered by radius and search keywords (if provided).
– Results are sorted based on the selected sorting option (distance or newest).
– Display the search results in a user-friendly format.
PHP code to handle search logic and display results:
The core of this system is calculating distance between two coordinates using the Haversine formula (PHP + MySQL Query).
<?php
// Include database connection
require_once __DIR__ . '/includes/db.php';
// Default search params
$q = isset($_GET['q']) ? trim($_GET['q']) : '';
$radius_km = isset($_GET['radius_km']) ? (float)$_GET['radius_km'] : 20.0;
$sort = isset($_GET['sort']) ? $_GET['sort'] : 'distance';
$lat = isset($_GET['lat']) ? (float)$_GET['lat'] : null;
$lng = isset($_GET['lng']) ? (float)$_GET['lng'] : null;
// If q is provided but lat/lng are empty, try to geocode using a simple approach
// NOTE: Geocoding is not implemented to avoid external network calls. Assume user provides lat/lng via browser.
// Haversine formula in SQL (distance in kilometers)
$haversine = "(6371 * acos( cos( radians(?) ) * cos( radians(latitude) ) * cos( radians(longitude) - radians(?) ) + sin( radians(?) ) * sin( radians(latitude) ) ))";
// Build SQL
$sql = "SELECT title, address, latitude, longitude, created, modified, status, $haversine AS distance_km FROM places WHERE status = 1";
// Parameters for prepared statement
$params = [];
if ($lat !== null && $lng !== null) {
// We'll need three copies of lat/lng for the haversine expression
$params[] = $lat;
$params[] = $lng;
$params[] = $lat;
// Apply radius filter only when radius_km > 0 (0 will mean 'All Radius')
if ($radius_km > 0) {
$sql .= " HAVING distance_km <= ?";
$params[] = $radius_km;
}
} else {
// If no location given, compute distance from 0,0 so ordering still works but results will not be limited by radius
$params[] = 0.0; $params[] = 0.0; $params[] = 0.0;
}
// Search by q in title or address
if ($q !== '') {
$sql .= ($lat !== null && $lng !== null) ? ' AND (title LIKE ? OR address LIKE ?)' : ' AND (title LIKE ? OR address LIKE ?)';
$like = '%' . $q . '%';
$params[] = $like;
$params[] = $like;
}
// Sorting
if ($sort === 'distance') {
$sql .= ' ORDER BY distance_km ASC';
} else {
$sql .= ' ORDER BY created DESC';
}
// Prepare and execute
$stmt = $mysqli->prepare($sql);
// Bind params dynamically
if ($params) {
// Build types string
$types = '';
foreach ($params as $p) {
if (is_int($p)) $types .= 'i';
else if (is_float($p)) $types .= 'd';
else $types .= 's';
}
$stmt->bind_param($types, ...$params);
}
// Execute and fetch results
$stmt->execute();
$result = $stmt->get_result();
?>
HTML code for search form and results display:
Define the HTML structure for the search form and results display. The search form will include fields for the search query, radius selection, and sorting options. The results will be displayed in a card format showing the title, address, distance, and creation date.
<!-- Search form -->
<form id="searchForm" method="get" class="row g-2 align-items-center mb-4">
<!-- Search input -->
<div class="col-12 col-md-5">
<label for="q" class="visually-hidden">Search</label>
<input id="q" name="q" value="<?php echo htmlspecialchars($q); ?>" type="search" class="form-control" placeholder="Search title or address">
</div>
<!-- Radius select -->
<div class="col-6 col-md-3">
<label for="radius_km" class="visually-hidden">Radius</label>
<select id="radius_km" name="radius_km" class="form-select">
<?php
// Radius options (added 'All radius' option)
$options = [0 => 'All radius', 5 => 'Within 5 km', 10 => 'Within 10 km', 20 => 'Within 20 km', 50 => 'Within 50 km', 100 => 'Within 100 km'];
foreach ($options as $val => $label) {
// Use loose comparison for numeric strings
$sel = ((float)$radius_km === (float)$val) ? 'selected' : '';
echo "<option value=\"$val\" $sel>$label</option>";
}
?>
</select>
</div>
<!-- Sort select -->
<div class="col-6 col-md-2">
<label for="sort" class="visually-hidden">Sort</label>
<select id="sort" name="sort" class="form-select">
<option value="distance" <?php echo $sort==='distance' ? 'selected' : ''; ?>>Sort by distance</option>
<option value="newest" <?php echo $sort==='newest' ? 'selected' : ''; ?>>Newest</option>
</select>
</div>
<!-- Submit button -->
<div class="col-12 col-md-2 d-grid">
<button type="submit" class="btn btn-primary">Search</button>
</div>
<div class="col-12 mt-2">
<small class="text-muted">Use your browser to provide your current location. Click the pin button to autofill lat/lng.</small>
</div>
<!-- Fetch current location using browser geolocation -->
<div class="col-6 col-md-3 mt-2">
<input type="hidden" id="lat" name="lat" value="<?php echo htmlspecialchars($lat); ?>">
<input type="hidden" id="lng" name="lng" value="<?php echo htmlspecialchars($lng); ?>">
<button type="button" id="useLocation" class="btn btn-outline-secondary btn-sm mt-1">Use my location</button>
</div>
<!-- Processing notification -->
<div class="col-12 mt-3" id="processingNotif" style="display: none;">
<div class="alert alert-info" role="alert">
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
<strong>Collecting your location...</strong>
</div>
</div>
<!-- Display current location -->
<div class="col-12 mt-3" id="locationDisplay" style="display: none;">
<div class="alert alert-success" role="alert">
<strong>📍 Your Location:</strong> <span id="locationText"></span>
</div>
</div>
</form>
<!-- Search results -->
<div class="row" id="results">
<?php if ($result && $result->num_rows): ?>
<?php if ($result && $result->num_rows): ?>
<?php while ($row = $result->fetch_assoc()): ?>
<div class="col-12 col-sm-6 col-lg-4 mb-4">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="card-title mb-1"><?php echo htmlspecialchars($row['title']); ?></h5>
<p class="card-text text-muted small mb-2"><?php echo htmlspecialchars($row['address']); ?></p>
<p class="mb-1"><strong>Latitude:</strong> <?php echo htmlspecialchars($row['latitude']); ?></p>
<p class="mb-1"><strong>Longitude:</strong> <?php echo htmlspecialchars($row['longitude']); ?></p>
<?php if (isset($row['distance_km'])): ?>
<p class="mt-2 mb-0 text-end small text-secondary">Distance: <?php echo number_format($row['distance_km'],2); ?> km</p>
<?php endif; ?>
</div>
</div>
</div>
<?php endwhile; ?>
<?php else: ?>
<div class="col-12">
<div class="alert alert-info">No places found. Try expanding the radius or check location settings.</div>
</div>
<?php endif; ?>
</div>
JavaScript code for geolocation handling:
Use the browser’s Geolocation API to get the user’s current location when they click the “Use my location” button. Display a processing notification while fetching the location, and then show the retrieved latitude and longitude on the page.
<script>
// Display current location if lat/lng are already set from previous search
function displayLocation() {
const lat = document.getElementById('lat').value;
const lng = document.getElementById('lng').value;
if (lat && lng) {
document.getElementById('locationText').textContent = `Latitude: ${lat}, Longitude: ${lng}`;
document.getElementById('locationDisplay').style.display = 'block';
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', displayLocation);
document.getElementById('useLocation').addEventListener('click', function(){
if (!navigator.geolocation) {
alert('Geolocation is not supported by your browser');
return;
}
// Show processing notification
document.getElementById('processingNotif').style.display = 'block';
navigator.geolocation.getCurrentPosition(function(pos){
const lat = pos.coords.latitude;
const lng = pos.coords.longitude;
document.getElementById('lat').value = lat;
document.getElementById('lng').value = lng;
// Display the location
document.getElementById('locationText').textContent = `Latitude: ${lat.toFixed(6)}, Longitude: ${lng.toFixed(6)}`;
document.getElementById('locationDisplay').style.display = 'block';
// Hide processing notification
document.getElementById('processingNotif').style.display = 'none';
// Optionally submit the form (or comment out if you prefer manual submission)
// document.getElementById('searchForm').submit();
}, function(err){
// Hide processing notification on error
document.getElementById('processingNotif').style.display = 'none';
alert('Unable to retrieve your location: ' + err.message);
});
});
</script>
htmlspecialchars() can be used to safely display user-generated content. (already implemented)This project demonstrates how to build a simple location-based search application using PHP and MySQL. It covers essential concepts such as geolocation, database interactions, and security best practices. You can further enhance this application by adding features like user authentication, saving favorite locations, or integrating with external APIs for more detailed place information.
This radius-based search system is lightweight, scalable, and perfect for:
With just PHP and MySQL, you can build powerful geolocation features without external APIs.
You can extend this project by implementing pagination for search results, allowing users to navigate through large sets of nearby places. A popular PHP pagination class can be found here: Pagination in PHP with MySQL. This will enhance the user experience by breaking down results into manageable pages, especially when there are many nearby locations to display.
Looking for expert assistance to implement or extend this script’s functionality? Submit a Service Request
💰 Budget-friendly • 🌍 Global clients • 🚀 Production-ready solutions
Good looking script, thanks. What would the code be for non Google API usage be? Thanks In Advance.
if you don’t want to depend on google api. How could I configure the distances in a radius with the zipcodes in the databases?
thank you so much for this great tutorials…