fetch_geocode($address); if ($result) { // Cache the result set_transient($cache_key, $result, self::CACHE_EXPIRATION); } return $result; } /** * Fetch geocoding data from Nominatim * * @param string $address * @return array|null */ private function fetch_geocode($address) { // Build request URL $url = add_query_arg(array( 'q' => $address, 'format' => 'json', 'addressdetails' => 1, 'limit' => 1, 'countrycodes' => 'us', ), self::API_URL); // Make request with proper User-Agent (required by Nominatim) $response = wp_remote_get($url, array( 'timeout' => 10, 'headers' => array( 'User-Agent' => 'HomeProz WordPress MLS Plugin/1.0 (contact@homeproz.com)', ), )); if (is_wp_error($response)) { return null; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (empty($data) || !is_array($data) || empty($data[0])) { return null; } $result = $data[0]; $addr = $result['address'] ?? array(); // Extract components return array( 'latitude' => isset($result['lat']) ? (float) $result['lat'] : null, 'longitude' => isset($result['lon']) ? (float) $result['lon'] : null, 'city' => $this->extract_city($addr), 'state' => $addr['state'] ?? null, 'state_code' => $this->get_state_code($addr['state'] ?? ''), 'postal_code' => $addr['postcode'] ?? null, 'county' => isset($addr['county']) ? str_replace(' County', '', $addr['county']) : null, 'country' => $addr['country'] ?? null, 'display_name' => $result['display_name'] ?? null, ); } /** * Extract city from address components * * Nominatim may return city in different fields depending on location type * * @param array $addr Address components * @return string|null */ private function extract_city($addr) { // Try different fields where city might be $city_fields = array('city', 'town', 'village', 'municipality', 'hamlet'); foreach ($city_fields as $field) { if (!empty($addr[$field])) { return $addr[$field]; } } return null; } /** * Get state code from state name * * @param string $state_name Full state name * @return string|null Two-letter state code */ private function get_state_code($state_name) { $states = array( 'Alabama' => 'AL', 'Alaska' => 'AK', 'Arizona' => 'AZ', 'Arkansas' => 'AR', 'California' => 'CA', 'Colorado' => 'CO', 'Connecticut' => 'CT', 'Delaware' => 'DE', 'Florida' => 'FL', 'Georgia' => 'GA', 'Hawaii' => 'HI', 'Idaho' => 'ID', 'Illinois' => 'IL', 'Indiana' => 'IN', 'Iowa' => 'IA', 'Kansas' => 'KS', 'Kentucky' => 'KY', 'Louisiana' => 'LA', 'Maine' => 'ME', 'Maryland' => 'MD', 'Massachusetts' => 'MA', 'Michigan' => 'MI', 'Minnesota' => 'MN', 'Mississippi' => 'MS', 'Missouri' => 'MO', 'Montana' => 'MT', 'Nebraska' => 'NE', 'Nevada' => 'NV', 'New Hampshire' => 'NH', 'New Jersey' => 'NJ', 'New Mexico' => 'NM', 'New York' => 'NY', 'North Carolina' => 'NC', 'North Dakota' => 'ND', 'Ohio' => 'OH', 'Oklahoma' => 'OK', 'Oregon' => 'OR', 'Pennsylvania' => 'PA', 'Rhode Island' => 'RI', 'South Carolina' => 'SC', 'South Dakota' => 'SD', 'Tennessee' => 'TN', 'Texas' => 'TX', 'Utah' => 'UT', 'Vermont' => 'VT', 'Virginia' => 'VA', 'Washington' => 'WA', 'West Virginia' => 'WV', 'Wisconsin' => 'WI', 'Wyoming' => 'WY', ); // If already a code, return as-is if (strlen($state_name) === 2) { return strtoupper($state_name); } return $states[$state_name] ?? null; } /** * Reverse geocode coordinates to address * * @param float $lat Latitude * @param float $lng Longitude * @return array|null Address components or null on failure */ public function reverse_geocode($lat, $lng) { $cache_key = 'mls_rgeo_' . md5($lat . '_' . $lng); $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } $url = add_query_arg(array( 'lat' => $lat, 'lon' => $lng, 'format' => 'json', 'addressdetails' => 1, ), 'https://nominatim.openstreetmap.org/reverse'); $response = wp_remote_get($url, array( 'timeout' => 10, 'headers' => array( 'User-Agent' => 'HomeProz WordPress MLS Plugin/1.0 (contact@homeproz.com)', ), )); if (is_wp_error($response)) { return null; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (empty($data) || !isset($data['address'])) { return null; } $addr = $data['address']; $result = array( 'latitude' => (float) $lat, 'longitude' => (float) $lng, 'city' => $this->extract_city($addr), 'state' => $addr['state'] ?? null, 'state_code' => $this->get_state_code($addr['state'] ?? ''), 'postal_code' => $addr['postcode'] ?? null, 'county' => isset($addr['county']) ? str_replace(' County', '', $addr['county']) : null, 'display_name' => $data['display_name'] ?? null, ); set_transient($cache_key, $result, self::CACHE_EXPIRATION); return $result; } /** * Clear geocoding cache for an address * * @param string $address */ public function clear_cache($address) { $cache_key = 'mls_geo_' . md5(strtolower(trim($address))); delete_transient($cache_key); } }