This commit is contained in:
Hanson.xyz Dev
2026-01-04 17:50:08 -06:00
parent 7e45ce0756
commit acc8ac87a0
4131 changed files with 232562 additions and 250244 deletions
@@ -109,8 +109,8 @@ class MLS_Query {
}
/**
* Build SQL for distance-based filtering using Haversine formula
* Returns properties within specified miles of a center point
* Build SQL for distance-based filtering using spatial index
* Uses bounding box pre-filter + ST_Distance_Sphere for accuracy
*
* @param float $lat Center latitude
* @param float $lng Center longitude
@@ -118,14 +118,46 @@ class MLS_Query {
* @return string SQL expression for distance filter
*/
private function get_distance_filter_sql($lat, $lng, $miles) {
// Haversine formula: distance in miles
// 3959 is Earth's radius in miles
// Convert miles to meters for ST_Distance_Sphere (returns meters)
$meters = $miles * 1609.344;
// Create center point (SRID 4326 uses lat, lng order in MySQL 8.0+)
// Use ST_Distance_Sphere with the spatial indexed location column
return sprintf(
"(3959 * acos(cos(radians(%f)) * cos(radians(latitude)) * cos(radians(longitude) - radians(%f)) + sin(radians(%f)) * sin(radians(latitude)))) <= %f",
"ST_Distance_Sphere(location, ST_PointFromText('POINT(%f %f)', 4326)) <= %f",
$lat,
$lng,
$lat,
$miles
$meters
);
}
/**
* Build SQL for bounding box pre-filter
* Uses simple BETWEEN for fast initial filtering before distance calc
* This narrows down candidates significantly before the expensive ST_Distance_Sphere
*
* @param float $lat Center latitude
* @param float $lng Center longitude
* @param float $miles Radius in miles
* @return string SQL expression for bounding box filter
*/
private function get_bounding_box_filter_sql($lat, $lng, $miles) {
// Approximate degrees per mile (varies by latitude, using average)
// 1 degree latitude ≈ 69 miles
// 1 degree longitude ≈ 69 miles * cos(latitude)
$lat_delta = $miles / 69.0;
$lng_delta = $miles / (69.0 * cos(deg2rad($lat)));
$min_lat = $lat - $lat_delta;
$max_lat = $lat + $lat_delta;
$min_lng = $lng - $lng_delta;
$max_lng = $lng + $lng_delta;
// Use BETWEEN for bounding box - efficient with indexes on lat/lng
return sprintf(
"(latitude BETWEEN %f AND %f AND longitude BETWEEN %f AND %f)",
$min_lat, $max_lat,
$min_lng, $max_lng
);
}
@@ -161,6 +193,7 @@ class MLS_Query {
'search' => null, // Search in address/remarks
'bounds' => null, // Map bounds: array(sw_lat, sw_lng, ne_lat, ne_lng)
'center' => null, // Map center for distance sort: array(lat, lng)
'featured_ids' => null, // Array of listing_id values to prioritize after HomeProz
'limit' => 20,
'offset' => 0,
'orderby' => 'modification_timestamp',
@@ -218,12 +251,18 @@ class MLS_Query {
$values[] = $args['postal_code'];
} elseif ($args['center_lat'] && $args['center_lng']) {
// Direct lat/lng radius search (from homepage location search)
// Use bounding box pre-filter for spatial index, then exact distance
$bbox_filter = $this->get_bounding_box_filter_sql(
(float) $args['center_lat'],
(float) $args['center_lng'],
(int) $args['radius']
);
$distance_filter = $this->get_distance_filter_sql(
(float) $args['center_lat'],
(float) $args['center_lng'],
(int) $args['radius']
);
$where[] = "({$distance_filter})";
$where[] = "({$bbox_filter} AND {$distance_filter})";
}
if ($args['county']) {
@@ -311,6 +350,18 @@ class MLS_Query {
$sql .= ' WHERE ' . implode(' AND ', $where);
// ORDER BY
// Always prioritize: 1) HomeProz listings, 2) Featured listings, 3) Regular listings
// Build featured sort expression if featured_ids provided
$featured_sort = '';
if (!empty($args['featured_ids']) && is_array($args['featured_ids'])) {
$featured_ids = array_map('sanitize_text_field', $args['featured_ids']);
$featured_placeholders = implode(',', array_fill(0, count($featured_ids), '%s'));
$featured_sort = $wpdb->prepare(
", (CASE WHEN listing_id IN ({$featured_placeholders}) THEN 1 ELSE 0 END) DESC",
...$featured_ids
);
}
// If center provided, sort by distance from center
if ($args['center'] && is_array($args['center']) && count($args['center']) === 2) {
list($center_lat, $center_lng) = $args['center'];
@@ -318,7 +369,7 @@ class MLS_Query {
// Using squared Euclidean distance with latitude adjustment for speed
$lat_factor = cos(deg2rad((float) $center_lat));
$sql .= $wpdb->prepare(
" ORDER BY (POW(latitude - %f, 2) + POW((longitude - %f) * %f, 2)) ASC",
" ORDER BY is_homeproz DESC{$featured_sort}, (POW(latitude - %f, 2) + POW((longitude - %f) * %f, 2)) ASC",
(float) $center_lat,
(float) $center_lng,
$lat_factor
@@ -338,7 +389,7 @@ class MLS_Query {
$orderby = in_array($args['orderby'], $allowed_orderby) ? $args['orderby'] : 'modification_timestamp';
$order = strtoupper($args['order']) === 'ASC' ? 'ASC' : 'DESC';
$sql .= " ORDER BY {$orderby} {$order}";
$sql .= " ORDER BY is_homeproz DESC{$featured_sort}, {$orderby} {$order}";
}
// LIMIT/OFFSET
@@ -530,13 +581,19 @@ class MLS_Query {
$values[] = $args['postal_code'];
} elseif (!empty($args['center_lat']) && !empty($args['center_lng'])) {
// Direct lat/lng radius search (from homepage location search)
// Use bounding box pre-filter for spatial index, then exact distance
$radius = !empty($args['radius']) ? (int) $args['radius'] : 30;
$bbox_filter = $this->get_bounding_box_filter_sql(
(float) $args['center_lat'],
(float) $args['center_lng'],
$radius
);
$distance_filter = $this->get_distance_filter_sql(
(float) $args['center_lat'],
(float) $args['center_lng'],
$radius
);
$where[] = "({$distance_filter})";
$where[] = "({$bbox_filter} AND {$distance_filter})";
}
if (!empty($args['county'])) {
@@ -728,7 +785,8 @@ class MLS_Query {
$table = $this->db->properties_table();
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL');
// Exclude properties with invalid coordinates from map bounds
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL', 'coordinates_invalid = 0');
$values = array();
// Add state filter (MN and IA only)