wip
This commit is contained in:
Regular → Executable
Regular → Executable
@@ -249,7 +249,8 @@ class MLS_Cluster {
|
||||
$table = $this->db->properties_table();
|
||||
|
||||
// Build WHERE clause
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL');
|
||||
// Exclude properties with invalid coordinates from map display
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL', 'coordinates_invalid = 0');
|
||||
$values = array();
|
||||
|
||||
// Add state filter (MN, IA only)
|
||||
@@ -291,7 +292,7 @@ class MLS_Cluster {
|
||||
$values[] = (int) $args['min_beds'];
|
||||
}
|
||||
|
||||
// Add bounds filter if provided
|
||||
// Add bounds filter (BETWEEN is efficient for rectangular viewport queries)
|
||||
if ($args['bounds'] && is_array($args['bounds']) && count($args['bounds']) === 4) {
|
||||
list($sw_lat, $sw_lng, $ne_lat, $ne_lng) = $args['bounds'];
|
||||
$where[] = 'latitude BETWEEN %f AND %f';
|
||||
@@ -571,7 +572,8 @@ class MLS_Cluster {
|
||||
global $wpdb;
|
||||
|
||||
$table = $this->db->properties_table();
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL');
|
||||
// Exclude properties with invalid coordinates
|
||||
$where = array('mlg_can_view = 1', 'latitude IS NOT NULL', 'longitude IS NOT NULL', 'coordinates_invalid = 0');
|
||||
$values = array();
|
||||
|
||||
// Add state filter (MN, IA only)
|
||||
|
||||
Regular → Executable
+60
-2
@@ -13,7 +13,7 @@ class MLS_DB {
|
||||
* Schema version for index migrations
|
||||
* Increment this when adding new indexes or columns
|
||||
*/
|
||||
const SCHEMA_VERSION = 3;
|
||||
const SCHEMA_VERSION = 5;
|
||||
|
||||
/**
|
||||
* Get table name with prefix
|
||||
@@ -415,8 +415,66 @@ class MLS_DB {
|
||||
update_option('mls_schema_version', 3);
|
||||
}
|
||||
|
||||
// Migration to schema version 4: Add spatial POINT column and index
|
||||
if ($current_schema < 4) {
|
||||
$table_properties = $wpdb->prefix . MLS_TABLE_PROPERTIES;
|
||||
|
||||
// Check if location column exists
|
||||
$column_exists = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'location'",
|
||||
DB_NAME,
|
||||
$table_properties
|
||||
));
|
||||
|
||||
if (!$column_exists) {
|
||||
// Add POINT column (nullable initially for population)
|
||||
$wpdb->query("ALTER TABLE {$table_properties} ADD COLUMN location POINT SRID 4326 DEFAULT NULL AFTER longitude");
|
||||
|
||||
// Populate location from existing lat/lng
|
||||
// Note: SRID 4326 uses axis order (latitude, longitude) in MySQL 8.0+
|
||||
$wpdb->query("UPDATE {$table_properties} SET location = ST_PointFromText(CONCAT('POINT(', latitude, ' ', longitude, ')'), 4326) WHERE latitude IS NOT NULL AND longitude IS NOT NULL");
|
||||
|
||||
// Make column NOT NULL (required for spatial index)
|
||||
$wpdb->query("ALTER TABLE {$table_properties} MODIFY location POINT NOT NULL SRID 4326");
|
||||
|
||||
// Add spatial index
|
||||
$existing_indexes = self::get_existing_indexes($table_properties);
|
||||
if (!isset($existing_indexes['idx_location_spatial'])) {
|
||||
$wpdb->query("ALTER TABLE {$table_properties} ADD SPATIAL INDEX idx_location_spatial (location)");
|
||||
}
|
||||
}
|
||||
|
||||
update_option('mls_schema_version', 4);
|
||||
}
|
||||
|
||||
// Migration to schema version 5: Add coordinates_invalid column for geo validation
|
||||
if ($current_schema < 5) {
|
||||
$table_properties = $wpdb->prefix . MLS_TABLE_PROPERTIES;
|
||||
|
||||
// Check if column exists
|
||||
$column_exists = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'coordinates_invalid'",
|
||||
DB_NAME,
|
||||
$table_properties
|
||||
));
|
||||
|
||||
if (!$column_exists) {
|
||||
$wpdb->query("ALTER TABLE {$table_properties} ADD COLUMN coordinates_invalid TINYINT(1) NOT NULL DEFAULT 0 AFTER longitude");
|
||||
}
|
||||
|
||||
// Add index if not exists
|
||||
$existing_indexes = self::get_existing_indexes($table_properties);
|
||||
if (!isset($existing_indexes['idx_coordinates_invalid'])) {
|
||||
$wpdb->query("ALTER TABLE {$table_properties} ADD INDEX idx_coordinates_invalid (coordinates_invalid)");
|
||||
}
|
||||
|
||||
update_option('mls_schema_version', 5);
|
||||
}
|
||||
|
||||
// Future migrations go here:
|
||||
// if ($current_schema < 4) { ... }
|
||||
// if ($current_schema < 6) { ... }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Regular → Executable
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* Geographic Coordinate Validator
|
||||
*
|
||||
* Validates that property coordinates fall within their claimed state boundaries.
|
||||
* Uses bounding box approximations for fast validation.
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class MLS_Geo_Validator {
|
||||
|
||||
/**
|
||||
* State bounding boxes
|
||||
* Format: state_code => [min_lat, max_lat, min_lng, max_lng]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $state_bounds = null;
|
||||
|
||||
/**
|
||||
* Load state bounds data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_state_bounds() {
|
||||
if (self::$state_bounds === null) {
|
||||
$bounds_file = MLS_PLUGIN_DIR . 'data/state-bounds.php';
|
||||
if (file_exists($bounds_file)) {
|
||||
self::$state_bounds = include $bounds_file;
|
||||
} else {
|
||||
self::$state_bounds = array();
|
||||
}
|
||||
}
|
||||
return self::$state_bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if coordinates are valid for a given state
|
||||
*
|
||||
* @param float $latitude Latitude
|
||||
* @param float $longitude Longitude
|
||||
* @param string $state_code Two-letter state code (e.g., 'MN', 'IA')
|
||||
* @return bool True if coordinates are within state bounds
|
||||
*/
|
||||
public static function validate_coordinates($latitude, $longitude, $state_code) {
|
||||
// Null or empty coordinates are invalid
|
||||
if ($latitude === null || $longitude === null || $latitude === '' || $longitude === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lat = (float) $latitude;
|
||||
$lng = (float) $longitude;
|
||||
|
||||
// Basic sanity check - valid lat/lng ranges
|
||||
if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Continental US rough bounds check (catches obviously wrong data like European coords)
|
||||
// This is a quick filter before state-specific validation
|
||||
if ($lat < 24 || $lat > 50 || $lng < -125 || $lng > -66) {
|
||||
// Allow Alaska, Hawaii, and territories which fall outside continental bounds
|
||||
if (!in_array(strtoupper($state_code), array('AK', 'HI', 'PR', 'VI', 'GU'))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get state-specific bounds
|
||||
$bounds = self::get_state_bounds();
|
||||
$state_code = strtoupper($state_code);
|
||||
|
||||
if (!isset($bounds[$state_code])) {
|
||||
// Unknown state - can't validate, assume valid
|
||||
return true;
|
||||
}
|
||||
|
||||
list($min_lat, $max_lat, $min_lng, $max_lng) = $bounds[$state_code];
|
||||
|
||||
// Check if coordinates fall within state bounding box
|
||||
// Add small buffer (0.1 degrees ~= 7 miles) for edge cases
|
||||
$buffer = 0.1;
|
||||
|
||||
return $lat >= ($min_lat - $buffer) &&
|
||||
$lat <= ($max_lat + $buffer) &&
|
||||
$lng >= ($min_lng - $buffer) &&
|
||||
$lng <= ($max_lng + $buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a property record and return whether coordinates are invalid
|
||||
*
|
||||
* @param object|array $property Property data with latitude, longitude, state_or_province
|
||||
* @return int 1 if coordinates are invalid, 0 if valid
|
||||
*/
|
||||
public static function check_property($property) {
|
||||
// Convert to array if object
|
||||
if (is_object($property)) {
|
||||
$lat = isset($property->latitude) ? $property->latitude : null;
|
||||
$lng = isset($property->longitude) ? $property->longitude : null;
|
||||
$state = isset($property->state_or_province) ? $property->state_or_province : null;
|
||||
} else {
|
||||
$lat = isset($property['latitude']) ? $property['latitude'] : null;
|
||||
$lng = isset($property['longitude']) ? $property['longitude'] : null;
|
||||
$state = isset($property['state_or_province']) ? $property['state_or_province'] : null;
|
||||
}
|
||||
|
||||
// If no coordinates, mark as invalid for map purposes
|
||||
if ($lat === null || $lng === null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If no state, can't validate - assume valid
|
||||
if (empty($state)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Normalize state code (handle full names or codes)
|
||||
$state_code = self::normalize_state_code($state);
|
||||
|
||||
// Validate
|
||||
return self::validate_coordinates($lat, $lng, $state_code) ? 0 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize state name/code to two-letter code
|
||||
*
|
||||
* @param string $state State name or code
|
||||
* @return string Two-letter state code
|
||||
*/
|
||||
private static function normalize_state_code($state) {
|
||||
$state = trim($state);
|
||||
|
||||
// Already a two-letter code
|
||||
if (strlen($state) === 2) {
|
||||
return strtoupper($state);
|
||||
}
|
||||
|
||||
// Common state name to code mappings
|
||||
$name_to_code = array(
|
||||
'minnesota' => 'MN',
|
||||
'iowa' => 'IA',
|
||||
'wisconsin' => 'WI',
|
||||
'north dakota' => 'ND',
|
||||
'south dakota' => 'SD',
|
||||
'nebraska' => 'NE',
|
||||
'missouri' => 'MO',
|
||||
'illinois' => 'IL',
|
||||
// Add more as needed
|
||||
);
|
||||
|
||||
$lower = strtolower($state);
|
||||
if (isset($name_to_code[$lower])) {
|
||||
return $name_to_code[$lower];
|
||||
}
|
||||
|
||||
// Return first two characters as fallback
|
||||
return strtoupper(substr($state, 0, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all existing properties in the database
|
||||
* Updates coordinates_invalid column for each record
|
||||
*
|
||||
* @param callable $progress_callback Optional callback for progress updates
|
||||
* @return array Results with counts
|
||||
*/
|
||||
public static function validate_all_properties($progress_callback = null) {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . MLS_TABLE_PROPERTIES;
|
||||
|
||||
$results = array(
|
||||
'total' => 0,
|
||||
'valid' => 0,
|
||||
'invalid' => 0,
|
||||
'errors' => array(),
|
||||
);
|
||||
|
||||
// Get total count
|
||||
$results['total'] = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
|
||||
|
||||
if ($results['total'] === 0) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Process in batches
|
||||
$batch_size = 500;
|
||||
$offset = 0;
|
||||
|
||||
while ($offset < $results['total']) {
|
||||
$properties = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT id, latitude, longitude, state_or_province FROM {$table} LIMIT %d OFFSET %d",
|
||||
$batch_size,
|
||||
$offset
|
||||
));
|
||||
|
||||
if (empty($properties)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$valid_ids = array();
|
||||
$invalid_ids = array();
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$is_invalid = self::check_property($property);
|
||||
|
||||
if ($is_invalid) {
|
||||
$invalid_ids[] = $property->id;
|
||||
$results['invalid']++;
|
||||
} else {
|
||||
$valid_ids[] = $property->id;
|
||||
$results['valid']++;
|
||||
}
|
||||
}
|
||||
|
||||
// Batch update valid records
|
||||
if (!empty($valid_ids)) {
|
||||
$ids_string = implode(',', array_map('intval', $valid_ids));
|
||||
$wpdb->query("UPDATE {$table} SET coordinates_invalid = 0 WHERE id IN ({$ids_string})");
|
||||
}
|
||||
|
||||
// Batch update invalid records
|
||||
if (!empty($invalid_ids)) {
|
||||
$ids_string = implode(',', array_map('intval', $invalid_ids));
|
||||
$wpdb->query("UPDATE {$table} SET coordinates_invalid = 1 WHERE id IN ({$ids_string})");
|
||||
}
|
||||
|
||||
$offset += $batch_size;
|
||||
|
||||
// Progress callback
|
||||
if ($progress_callback && is_callable($progress_callback)) {
|
||||
$progress_callback($offset, $results['total'], $results['valid'], $results['invalid']);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics about coordinate validity
|
||||
*
|
||||
* @return array Statistics
|
||||
*/
|
||||
public static function get_stats() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . MLS_TABLE_PROPERTIES;
|
||||
|
||||
return array(
|
||||
'total' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}"),
|
||||
'valid' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE coordinates_invalid = 0"),
|
||||
'invalid' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE coordinates_invalid = 1"),
|
||||
'null_coords' => (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE latitude IS NULL OR longitude IS NULL"),
|
||||
);
|
||||
}
|
||||
}
|
||||
Regular → Executable
+5
-3
@@ -375,13 +375,15 @@ class MLS_Image_Endpoint {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Send headers
|
||||
// Send headers - remove any no-cache headers WordPress may have added
|
||||
header_remove('Pragma');
|
||||
header('Pragma: public');
|
||||
header('Content-Type: ' . $mime_type);
|
||||
header('Content-Length: ' . $file_size);
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $last_modified) . ' GMT');
|
||||
header('ETag: "' . $etag . '"');
|
||||
header('Cache-Control: public, max-age=31536000'); // 1 year
|
||||
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
|
||||
header('Cache-Control: public, max-age=3600'); // 1 hour
|
||||
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
|
||||
|
||||
// Stream file
|
||||
readfile($path);
|
||||
|
||||
Regular → Executable
Regular → Executable
Regular → Executable
@@ -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)
|
||||
|
||||
Regular → Executable
Regular → Executable
+20
@@ -627,6 +627,18 @@ class MLS_Sync_Engine {
|
||||
$this->emit_progress('property_created', array('listing_key' => $listing_key));
|
||||
}
|
||||
|
||||
// Update spatial location column (wpdb can't handle ST_PointFromText directly)
|
||||
$lat = $property['Latitude'] ?? null;
|
||||
$lng = $property['Longitude'] ?? null;
|
||||
if ($lat !== null && $lng !== null) {
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"UPDATE {$this->db->properties_table()} SET location = ST_PointFromText(CONCAT('POINT(', %f, ' ', %f, ')'), 4326) WHERE listing_key = %s",
|
||||
(float) $lat,
|
||||
(float) $lng,
|
||||
$listing_key
|
||||
));
|
||||
}
|
||||
|
||||
// Process media if present
|
||||
if (isset($property['Media']) && is_array($property['Media'])) {
|
||||
$this->media_handler->sync_property_media($listing_key, $property['Media'], false, $this->progress_callback);
|
||||
@@ -652,6 +664,13 @@ class MLS_Sync_Engine {
|
||||
* @return array Mapped data for database
|
||||
*/
|
||||
private function map_property_data($property) {
|
||||
// Validate coordinates against state boundaries
|
||||
$coordinates_invalid = MLS_Geo_Validator::validate_coordinates(
|
||||
$property['Latitude'] ?? null,
|
||||
$property['Longitude'] ?? null,
|
||||
$property['StateOrProvince'] ?? 'MN'
|
||||
) ? 0 : 1;
|
||||
|
||||
return array(
|
||||
'listing_id' => $property['ListingId'] ?? null,
|
||||
'originating_system' => $property['OriginatingSystemName'] ?? 'northstar',
|
||||
@@ -673,6 +692,7 @@ class MLS_Sync_Engine {
|
||||
'county' => $property['CountyOrParish'] ?? null,
|
||||
'latitude' => $property['Latitude'] ?? null,
|
||||
'longitude' => $property['Longitude'] ?? null,
|
||||
'coordinates_invalid' => $coordinates_invalid,
|
||||
|
||||
'property_type' => $property['PropertyType'] ?? null,
|
||||
'property_sub_type' => $property['PropertySubType'] ?? null,
|
||||
|
||||
Reference in New Issue
Block a user