Switch to 100% server-side map clustering
Removes client-side Leaflet MarkerCluster library in favor of server-side clustering at all zoom levels: - Zoom 1-8: Density dots - Zoom 9-15: Server-generated numbered clusters - Zoom 16+: Individual property markers This prevents the visual issue where server-returned clusters were being re-clustered by the client into a single merged marker. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Regular → Executable
+2
-5
@@ -336,15 +336,12 @@ class MLS_Cluster {
|
|||||||
return $this->get_density_data($where_sql, $values, $zoom, $center_lat, $total, self::DENSITY_DOT_SPACING);
|
return $this->get_density_data($where_sql, $values, $zoom, $center_lat, $total, self::DENSITY_DOT_SPACING);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zoom 12-15: Numbered clusters (or individual if low count)
|
// Zoom 9-15: Always use server-side clusters (let server handle grouping)
|
||||||
if ($zoom <= self::ZOOM_CLUSTER_MAX) {
|
if ($zoom <= self::ZOOM_CLUSTER_MAX) {
|
||||||
if ($total <= self::MAX_INDIVIDUAL_MARKERS) {
|
|
||||||
return $this->get_individual_markers($where_sql, $values, $total);
|
|
||||||
}
|
|
||||||
return $this->get_cluster_data($where_sql, $values, $zoom, $center_lat, $total);
|
return $this->get_cluster_data($where_sql, $values, $zoom, $center_lat, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zoom 16+: Individual markers
|
// Zoom 16+: Individual markers (no clustering needed at this zoom)
|
||||||
return $this->get_individual_markers($where_sql, $values, $total);
|
return $this->get_individual_markers($where_sql, $values, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ $view_class = $show_map ? 'is-map-view' : 'is-grid-view';
|
|||||||
$initial_filters = array(
|
$initial_filters = array(
|
||||||
'status' => isset($_GET['property_status']) ? sanitize_text_field($_GET['property_status']) : 'Active',
|
'status' => isset($_GET['property_status']) ? sanitize_text_field($_GET['property_status']) : 'Active',
|
||||||
'property_type' => isset($_GET['property_type']) ? sanitize_text_field($_GET['property_type']) : '',
|
'property_type' => isset($_GET['property_type']) ? sanitize_text_field($_GET['property_type']) : '',
|
||||||
'city' => isset($_GET['property_location']) ? sanitize_text_field($_GET['property_location']) : '',
|
'city' => isset($_GET['city']) ? sanitize_text_field($_GET['city']) : '',
|
||||||
'min_price' => isset($_GET['min_price']) ? intval($_GET['min_price']) : '',
|
'min_price' => isset($_GET['min_price']) ? intval($_GET['min_price']) : '',
|
||||||
'max_price' => isset($_GET['max_price']) ? intval($_GET['max_price']) : '',
|
'max_price' => isset($_GET['max_price']) ? intval($_GET['max_price']) : '',
|
||||||
'min_beds' => isset($_GET['beds']) ? intval($_GET['beds']) : '',
|
'min_beds' => isset($_GET['beds']) ? intval($_GET['beds']) : '',
|
||||||
@@ -137,13 +137,8 @@ if (function_exists('mls_get_property_count')) {
|
|||||||
?>
|
?>
|
||||||
<!-- Leaflet CSS -->
|
<!-- Leaflet CSS -->
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
|
||||||
<!-- Leaflet MarkerCluster CSS -->
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css" crossorigin=""/>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css" crossorigin=""/>
|
|
||||||
<!-- Leaflet JS -->
|
<!-- Leaflet JS -->
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||||
<!-- Leaflet MarkerCluster JS -->
|
|
||||||
<script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js" crossorigin=""></script>
|
|
||||||
<script>
|
<script>
|
||||||
var homeprozMapData = {
|
var homeprozMapData = {
|
||||||
isMapView: <?php echo $show_map ? 'true' : 'false'; ?>,
|
isMapView: <?php echo $show_map ? 'true' : 'false'; ?>,
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -271,9 +271,8 @@
|
|||||||
markers: {}, // Object keyed by property ID
|
markers: {}, // Object keyed by property ID
|
||||||
markerData: {}, // Object keyed by property ID -> {lat, lng, ...}
|
markerData: {}, // Object keyed by property ID -> {lat, lng, ...}
|
||||||
densityLayer: null, // Layer group for density dots (zoom 1-11)
|
densityLayer: null, // Layer group for density dots (zoom 1-11)
|
||||||
clusterLayer: null, // Layer group for server clusters (zoom 12-15)
|
clusterLayer: null, // Layer group for server clusters (zoom 9-15)
|
||||||
markerCluster: null, // MarkerClusterGroup for individual markers (zoom 16+)
|
markerLayer: null, // Plain layer group for individual markers (zoom 16+)
|
||||||
markerLayer: null, // Plain layer group for unclustered markers (low count)
|
|
||||||
selectedPropertyId: null,
|
selectedPropertyId: null,
|
||||||
isPinClickPan: false, // Flag: true when map pan is caused by pin click (don't clear selection)
|
isPinClickPan: false, // Flag: true when map pan is caused by pin click (don't clear selection)
|
||||||
hoveredPropertyId: null,
|
hoveredPropertyId: null,
|
||||||
@@ -304,42 +303,15 @@
|
|||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||||
}).addTo(this.map);
|
}).addTo(this.map);
|
||||||
|
|
||||||
// Create layer for density dots (zoom 1-11)
|
// Create layer for density dots (zoom 1-8)
|
||||||
this.densityLayer = L.layerGroup().addTo(this.map);
|
this.densityLayer = L.layerGroup().addTo(this.map);
|
||||||
|
|
||||||
// Create layer for server-side clusters (zoom 12-15)
|
// Create layer for server-side clusters (zoom 9-15)
|
||||||
this.clusterLayer = L.layerGroup().addTo(this.map);
|
this.clusterLayer = L.layerGroup().addTo(this.map);
|
||||||
|
|
||||||
// Create plain layer for unclustered markers (low count)
|
// Create plain layer for individual markers (zoom 16+ or low count)
|
||||||
this.markerLayer = L.layerGroup().addTo(this.map);
|
this.markerLayer = L.layerGroup().addTo(this.map);
|
||||||
|
|
||||||
// Create marker cluster group for individual markers (when zoomed in)
|
|
||||||
this.markerCluster = L.markerClusterGroup({
|
|
||||||
maxClusterRadius: 50,
|
|
||||||
spiderfyOnMaxZoom: true,
|
|
||||||
showCoverageOnHover: false,
|
|
||||||
zoomToBoundsOnClick: true,
|
|
||||||
disableClusteringAtZoom: 18,
|
|
||||||
chunkedLoading: true,
|
|
||||||
chunkInterval: 200,
|
|
||||||
chunkDelay: 50,
|
|
||||||
iconCreateFunction: function(cluster) {
|
|
||||||
var count = cluster.getChildCount();
|
|
||||||
var size = 'small';
|
|
||||||
if (count >= 100) {
|
|
||||||
size = 'large';
|
|
||||||
} else if (count >= 10) {
|
|
||||||
size = 'medium';
|
|
||||||
}
|
|
||||||
return L.divIcon({
|
|
||||||
html: '<div><span>' + count + '</span></div>',
|
|
||||||
className: 'marker-cluster marker-cluster-' + size,
|
|
||||||
iconSize: L.point(40, 40)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.map.addLayer(this.markerCluster);
|
|
||||||
|
|
||||||
// Bind map events for dynamic loading
|
// Bind map events for dynamic loading
|
||||||
var self = this;
|
var self = this;
|
||||||
this.map.on('moveend zoomend', function() {
|
this.map.on('moveend zoomend', function() {
|
||||||
@@ -455,7 +427,6 @@
|
|||||||
clearAllLayers: function() {
|
clearAllLayers: function() {
|
||||||
this.densityLayer.clearLayers();
|
this.densityLayer.clearLayers();
|
||||||
this.clusterLayer.clearLayers();
|
this.clusterLayer.clearLayers();
|
||||||
this.markerCluster.clearLayers();
|
|
||||||
this.markerLayer.clearLayers();
|
this.markerLayer.clearLayers();
|
||||||
this.markers = {};
|
this.markers = {};
|
||||||
|
|
||||||
@@ -653,16 +624,11 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add markers to appropriate layer based on count
|
// Add all markers to plain layer (server handles clustering)
|
||||||
// Use plain layer for <= 30 markers (no client-side clustering)
|
// Individual markers only come at zoom 16+ where no clustering is needed
|
||||||
// Use MarkerClusterGroup for > 30 markers (client-side clustering)
|
markersToAdd.forEach(function(marker) {
|
||||||
if (markersToAdd.length <= 30) {
|
self.markerLayer.addLayer(marker);
|
||||||
markersToAdd.forEach(function(marker) {
|
});
|
||||||
self.markerLayer.addLayer(marker);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.markerCluster.addLayers(markersToAdd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the pin-click-pan flag now that markers are rendered
|
// Reset the pin-click-pan flag now that markers are rendered
|
||||||
this.isPinClickPan = false;
|
this.isPinClickPan = false;
|
||||||
@@ -844,27 +810,20 @@
|
|||||||
var needsTemporaryMarker = false;
|
var needsTemporaryMarker = false;
|
||||||
|
|
||||||
if (marker) {
|
if (marker) {
|
||||||
// Marker exists - check if it's visible (not clustered and in viewport)
|
// Marker exists - check if it's in viewport
|
||||||
var markerLatLng = marker.getLatLng();
|
var markerLatLng = marker.getLatLng();
|
||||||
var inViewport = self.map.getBounds().contains(markerLatLng);
|
var inViewport = self.map.getBounds().contains(markerLatLng);
|
||||||
|
|
||||||
// Check if marker is clustered (part of a cluster group)
|
if (inViewport) {
|
||||||
var isClustered = false;
|
// Marker is visible - highlight it
|
||||||
if (self.markerCluster && self.markerCluster.hasLayer(marker)) {
|
|
||||||
var visibleParent = self.markerCluster.getVisibleParent(marker);
|
|
||||||
isClustered = visibleParent && visibleParent !== marker;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isClustered && inViewport) {
|
|
||||||
// Marker is visible as individual pin - highlight it
|
|
||||||
self.setMarkerColor(propertyId, 'blue');
|
self.setMarkerColor(propertyId, 'blue');
|
||||||
self.setMarkerZIndex(propertyId, 9000);
|
self.setMarkerZIndex(propertyId, 9000);
|
||||||
} else {
|
} else {
|
||||||
// Marker is clustered or outside viewport - need temporary marker
|
// Marker outside viewport - need temporary marker
|
||||||
needsTemporaryMarker = true;
|
needsTemporaryMarker = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Marker doesn't exist in current dataset - need temporary marker
|
// Marker doesn't exist in current dataset (server-side clustered or not loaded)
|
||||||
needsTemporaryMarker = true;
|
needsTemporaryMarker = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user