Add server-side clustering for map with 30k+ properties

- Remove 1000 property limit from count display
- Add MLS_Cluster class for geohash-based server-side clustering
- Add AJAX endpoint for dynamic cluster loading based on viewport/zoom
- Update property-results.php and ajax-handlers.php to use efficient counting
- Update map JavaScript to fetch clusters dynamically as user pans/zooms
- Server returns clusters at low zoom, individual markers at high zoom
- Fixes property count showing 1000 instead of actual ~30k properties

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hanson.xyz Dev
2025-12-16 00:04:22 -06:00
parent 30eb593020
commit 1862cef42a
10 changed files with 741 additions and 174 deletions
@@ -54,6 +54,7 @@ final class MLS_Plugin {
private $media_handler;
private $image_endpoint;
private $query;
private $cluster;
/**
* Get single instance
@@ -87,6 +88,7 @@ final class MLS_Plugin {
require_once MLS_PLUGIN_DIR . 'includes/class-mls-media-handler.php';
require_once MLS_PLUGIN_DIR . 'includes/class-mls-image-endpoint.php';
require_once MLS_PLUGIN_DIR . 'includes/class-mls-query.php';
require_once MLS_PLUGIN_DIR . 'includes/class-mls-cluster.php';
// Activation/Deactivation
require_once MLS_PLUGIN_DIR . 'includes/class-mls-activator.php';
@@ -137,6 +139,11 @@ final class MLS_Plugin {
$this->logger
);
$this->query = new MLS_Query($this->db);
$this->cluster = new MLS_Cluster($this->db);
// Register AJAX handlers
add_action('wp_ajax_mls_get_clusters', array($this, 'ajax_get_clusters'));
add_action('wp_ajax_nopriv_mls_get_clusters', array($this, 'ajax_get_clusters'));
// Initialize admin
if (is_admin()) {
@@ -223,6 +230,43 @@ final class MLS_Plugin {
public function get_query() {
return $this->query;
}
/**
* Get Cluster instance
*/
public function get_cluster() {
return $this->cluster;
}
/**
* AJAX handler for getting map clusters
*/
public function ajax_get_clusters() {
// Parse input
$zoom = isset($_REQUEST['zoom']) ? (int) $_REQUEST['zoom'] : 10;
$bounds = isset($_REQUEST['bounds']) ? array_map('floatval', (array) $_REQUEST['bounds']) : null;
$status = isset($_REQUEST['status']) ? sanitize_text_field($_REQUEST['status']) : 'Active';
$property_type = isset($_REQUEST['property_type']) ? sanitize_text_field($_REQUEST['property_type']) : null;
$city = isset($_REQUEST['city']) ? sanitize_text_field($_REQUEST['city']) : null;
$min_price = isset($_REQUEST['min_price']) ? (int) $_REQUEST['min_price'] : null;
$max_price = isset($_REQUEST['max_price']) ? (int) $_REQUEST['max_price'] : null;
$min_beds = isset($_REQUEST['min_beds']) ? (int) $_REQUEST['min_beds'] : null;
$args = array(
'zoom' => $zoom,
'bounds' => $bounds,
'status' => $status,
'property_type' => $property_type ?: null,
'city' => $city ?: null,
'min_price' => $min_price ?: null,
'max_price' => $max_price ?: null,
'min_beds' => $min_beds ?: null,
);
$result = $this->cluster->get_clusters($args);
wp_send_json_success($result);
}
}
/**