it-swarm.asia

Bir Web Sitesinde mı Yapılıyor?

Bir müşteri için bir mağaza bulucu oluşturmam gereken bir projem var.

Özel bir yazı tipi "restaurant-location" kullanıyorum ve kodu, posta kodunda depolanan adresleri, Google Geocoding API kullanarak kodlamak için yazdım. ( JSON içindeki ABD Beyaz Sarayını kodlayan bağlantıyı] Enlem ve boylamı özel alanlara geri sakladım.

Coğrafi olarak bu yazıdaki slayt gösterisinde bulduğum formül) kullanarak en yakın olanların sırasına göre bir yazı listesi döndüren bir get_posts_by_geo_distance() işlevi yazdım. sabit bir "kaynak" lat/long ile başlıyorum):

include "wp-load.php";

$source_lat = 30.3935337;
$source_long = -86.4957833;

$results = get_posts_by_geo_distance(
    'restaurant-location',
    'geo_latitude',
    'geo_longitude',
    $source_lat,
    $source_long);

echo '<ul>';
foreach($results as $post) {
    $edit_url = get_edit_url($post->ID);
    echo "<li>{$post->distance}: <a href=\"{$edit_url}\" target=\"_blank\">{$post->location}</a></li>";
}
echo '</ul>';
return;

İşte işlev get_posts_by_geo_distance()'ın kendisi:

function get_posts_by_geo_distance($post_type,$lat_key,$lng_key,$source_lat,$source_lng) {
    global $wpdb;
    $sql =<<<SQL
SELECT
    rl.ID,
    rl.post_title AS location,
    ROUND(3956*2*ASIN(SQRT(POWER(SIN(({$source_lat}-abs(lat.lat))*pi()/180/2),2)+
    COS({$source_lat}*pi()/180)*COS(abs(lat.lat)*pi()/180)*
    POWER(SIN(({$source_lng}-lng.lng)*pi()/180/2),2))),3) AS distance
FROM
    wp_posts rl
    INNER JOIN (SELECT post_id,CAST(meta_value AS DECIMAL(11,7)) AS lat FROM wp_postmeta lat WHERE lat.meta_key='{$lat_key}') lat ON lat.post_id = rl.ID
    INNER JOIN (SELECT post_id,CAST(meta_value AS DECIMAL(11,7)) AS lng FROM wp_postmeta lng WHERE lng.meta_key='{$lng_key}') lng ON lng.post_id = rl.ID
WHERE
    rl.post_type='{$post_type}' AND rl.post_name<>'auto-draft'
ORDER BY
    distance
SQL;
    $sql = $wpdb->prepare($sql,$source_lat,$source_lat,$source_lng);
    return $wpdb->get_results($sql);
}

Benim endişem SQL'in alabildiğin kadar optimize edilmemiş olmasıdır. MySQL, source geo değişkenlik gösterdiğinden ve önbelleğe alınacak sınırlı bir kaynak coğrafi kümesi olmadığından kullanılabilir bir dizine göre sipariş veremez. Şu anda, onu optimize etmenin yolları hakkında güdük var.

Yaptığım şeyi göz önünde bulundurarak göz önüne aldığım soru şudur: Bu kullanım durumunu nasıl optimize edersiniz?

Daha iyi bir çözüm atmama izin verirse, yaptığım hiçbir şeyi saklamam önemli değil. Neredeyse her türlü çözümü düşünmeye açığım, Sphinx sunucusu kurmak veya özel bir MySQL yapılandırması gerektiren herhangi bir şey yapmayı gerektiren durumlar dışında. Temel olarak çözüm, herhangi bir düz Vanilla WordPress kurulumunda çalışabilmelidir. (Bu, başkalarının daha gelişmiş ve gelecek vaat edebilecek başkaları için alternatif çözümler listelemek istemesinin harika olacağını söyledi.)

Kaynak Bulundu

Bilginize, bu konuyla ilgili biraz araştırma yaptım, araştırmayı tekrar yapmanıza ya da bu bağlantıların herhangi birini bir cevap olarak yayınlamak yerine devam edip bunları dahil edeceğim.

Sfenks Aramasıyla İlgili

11
MikeSchinkel

Hangi hassasiyete ihtiyacınız var? Eyalet/ulusal geniş bir arama ise, belki Zip aramaya bir laton yapabilir ve Zip bölgesinden restoranın Zip bölgesine kadar önceden hesaplanmış bir mesafeye sahip olabilirsiniz. Doğru mesafelere ihtiyacınız varsa, bu iyi bir seçenek olmayacaktır.

Bir Geohash çözümüne bakmalısınız, Wikipedia makalesinde, uzun zamandan beri kod çözmeyi kodlamak için bir PHPkitaplığına bir bağlantı vardır.

Burada iyi bir makaleniz var Google App Engine'de neden ve nasıl kullandıklarını açıklayan (Python kodu ancak takip etmesi kolay.) python kütüphaneleri ve örnekler.

Bu blog yazısı açıklandığı gibi, geohashes kullanmanın avantajı, o alandaki MySQL tablosunda bir dizin oluşturabilmenizdir.

6
user324

Bu sizin için çok geç olabilir, ama yine de cevaplayacağım, bu soruya verdiğim cevap ile benzer cevap , böylece gelecek ziyaretçiler her iki soruya da başvurabilir.

Bu değerleri meta veri tablosunda veya en azından only burada saklamam. post_id, lat, lon sütunlara sahip bir tablo istiyorsanız, böylece bir lat, lon dizini ve bunun üzerine sorgu ekleyebilirsiniz. Bu, kaydetme ve güncelleme sırasında bir kanca ile güncel kalmak için çok zor olmamalıdır.

Veritabanını sorguladığınızda, başlangıç ​​noktası etrafında bir sınırlayıcı kutu tanımlarsınız, böylece kutunun Kuzey-Güney ve Doğu-Batı sınırları arasındaki tüm lat, lon çiftleri için verimli bir sorgu yapabilirsiniz.

Bu azaltılmış sonucu elde ettikten sonra, sınırlama kutusunun köşelerinde ve istediğinizden daha uzakta olan yerleri filtrelemek için daha gelişmiş (dairesel veya gerçek sürüş yönleri) mesafe hesaplaması yapabilirsiniz.

Burada yönetici alanında çalışan basit bir kod örneği bulabilirsiniz. Ekstra veritabanı tablosu kendiniz oluşturmanız gerekir. Kod, en ilginçinden en küçüğüne sipariş edilir.

<?php
/*
Plugin Name: Monkeyman geo test
Plugin URI: http://www.monkeyman.be
Description: Geolocation test
Version: 1.0
Author: Jan Fabry
*/

class Monkeyman_Geo
{
    public function __construct()
    {
        add_action('init', array(&$this, 'registerPostType'));
        add_action('save_post', array(&$this, 'saveLatLon'), 10, 2);

        add_action('admin_menu', array(&$this, 'addAdminPages'));
    }

    /**
     * On post save, save the metadata in our special table
     * (post_id INT, lat DECIMAL(10,5), lon DECIMAL (10,5))
     * Index on lat, lon
     */
    public function saveLatLon($post_id, $post)
    {
        if ($post->post_type != 'monkeyman_geo') {
            return;
        }
        $lat = floatval(get_post_meta($post_id, 'lat', true));
        $lon = floatval(get_post_meta($post_id, 'lon', true));

        global $wpdb;
        $result = $wpdb->replace(
            $wpdb->prefix . 'monkeyman_geo',
            array(
                'post_id' => $post_id,
                'lat' => $lat,
                'lon' => $lon,
            ),
            array('%s', '%F', '%F')
        );
    }

    public function addAdminPages()
    {
        add_management_page( 'Quick location generator', 'Quick generator', 'edit_posts', __FILE__  . 'generator', array($this, 'doGeneratorPage'));
        add_management_page( 'Location test', 'Location test', 'edit_posts', __FILE__ . 'test', array($this, 'doTestPage'));

    }

    /**
     * Simple test page with a location and a distance
     */
    public function doTestPage()
    {
        if (!array_key_exists('search', $_REQUEST)) {
            $default_lat = ini_get('date.default_latitude');
            $default_lon = ini_get('date.default_longitude');

            echo <<<EOF
<form action="" method="post">
    <p>Center latitude: <input size="10" name="center_lat" value="{$default_lat}"/>
        <br/>Center longitude: <input size="10" name="center_lon" value="{$default_lon}"/>
        <br/>Max distance (km): <input size="5" name="max_distance" value="100"/></p>
    <p><input type="submit" name="search" value="Search!"/></p>
</form>
EOF;
            return;
        }
        $center_lon = floatval($_REQUEST['center_lon']);
        $center_lat = floatval($_REQUEST['center_lat']);
        $max_distance = floatval($_REQUEST['max_distance']);

        var_dump(self::getPostsUntilDistanceKm($center_lon, $center_lat, $max_distance));
    }

    /**
     * Get all posts that are closer than the given distance to the given location
     */
    public static function getPostsUntilDistanceKm($center_lon, $center_lat, $max_distance)
    {
        list($north_lat, $east_lon, $south_lat, $west_lon) = self::getBoundingBox($center_lat, $center_lon, $max_distance);

        $geo_posts = self::getPostsInBoundingBox($north_lat, $east_lon, $south_lat, $west_lon);

        $close_posts = array();
        foreach ($geo_posts as $geo_post) {
            $post_lat = floatval($geo_post->lat);
            $post_lon = floatval($geo_post->lon);
            $post_distance = self::calculateDistanceKm($center_lat, $center_lon, $post_lat, $post_lon);
            if ($post_distance < $max_distance) {
                $close_posts[$geo_post->post_id] = $post_distance;
            }
        }
        return $close_posts;
    }

    /**
     * Select all posts ids in a given bounding box
     */
    public static function getPostsInBoundingBox($north_lat, $east_lon, $south_lat, $west_lon)
    {
        global $wpdb;
        $sql = $wpdb->prepare('SELECT post_id, lat, lon FROM ' . $wpdb->prefix . 'monkeyman_geo WHERE lat < %F AND lat > %F AND lon < %F AND lon > %F', array($north_lat, $south_lat, $west_lon, $east_lon));
        return $wpdb->get_results($sql, OBJECT_K);
    }

    /* Geographical calculations: distance and bounding box */

    /**
     * Calculate the distance between two coordinates
     * http://stackoverflow.com/questions/365826/calculate-distance-between-2-gps-coordinates/1416950#1416950
     */
    public static function calculateDistanceKm($a_lat, $a_lon, $b_lat, $b_lon)
    {
        $d_lon = deg2rad($b_lon - $a_lon);
        $d_lat = deg2rad($b_lat - $a_lat);
        $a = pow(sin($d_lat/2.0), 2) + cos(deg2rad($a_lat)) * cos(deg2rad($b_lat)) * pow(sin($d_lon/2.0), 2);
        $c = 2 * atan2(sqrt($a), sqrt(1-$a));
        $d = 6367 * $c;

        return $d;
    }

    /**
     * Create a box around a given point that extends a certain distance in each direction
     * http://www.colorado.edu/geography/gcraft/warmup/aquifer/html/distance.html
     *
     * @todo: Mind the gap at 180 degrees!
     */
    public static function getBoundingBox($center_lat, $center_lon, $distance_km)
    {
        $one_lat_deg_in_km = 111.321543; // Fixed
        $one_lon_deg_in_km = cos(deg2rad($center_lat)) * 111.321543; // Depends on latitude

        $north_lat = $center_lat + ($distance_km / $one_lat_deg_in_km);
        $south_lat = $center_lat - ($distance_km / $one_lat_deg_in_km);

        $east_lon = $center_lon - ($distance_km / $one_lon_deg_in_km);
        $west_lon = $center_lon + ($distance_km / $one_lon_deg_in_km);

        return array($north_lat, $east_lon, $south_lat, $west_lon);
    }

    /* Below this it's not interesting anymore */

    /**
     * Generate some test data
     */
    public function doGeneratorPage()
    {
        if (!array_key_exists('generate', $_REQUEST)) {
            $default_lat = ini_get('date.default_latitude');
            $default_lon = ini_get('date.default_longitude');

            echo <<<EOF
<form action="" method="post">
    <p>Number of posts: <input size="5" name="post_count" value="10"/></p>
    <p>Center latitude: <input size="10" name="center_lat" value="{$default_lat}"/>
        <br/>Center longitude: <input size="10" name="center_lon" value="{$default_lon}"/>
        <br/>Max distance (km): <input size="5" name="max_distance" value="100"/></p>
    <p><input type="submit" name="generate" value="Generate!"/></p>
</form>
EOF;
            return;
        }
        $post_count = intval($_REQUEST['post_count']);
        $center_lon = floatval($_REQUEST['center_lon']);
        $center_lat = floatval($_REQUEST['center_lat']);
        $max_distance = floatval($_REQUEST['max_distance']);

        list($north_lat, $east_lon, $south_lat, $west_lon) = self::getBoundingBox($center_lat, $center_lon, $max_distance);


        add_action('save_post', array(&$this, 'setPostLatLon'), 5);
        $precision = 100000;
        for ($p = 0; $p < $post_count; $p++) {
            self::$currentRandomLat = mt_Rand($south_lat * $precision, $north_lat * $precision) / $precision;
            self::$currentRandomLon = mt_Rand($west_lon * $precision, $east_lon * $precision) / $precision;

            $location = sprintf('(%F, %F)', self::$currentRandomLat, self::$currentRandomLon);

            $post_data = array(
                'post_status' => 'publish',
                'post_type' => 'monkeyman_geo',
                'post_content' => 'Point at ' . $location,
                'post_title' => 'Point at ' . $location,
            );

            var_dump(wp_insert_post($post_data));
        }
    }

    public static $currentRandomLat = null;
    public static $currentRandomLon = null;

    /**
     * Because I didn't know how to save meta data with wp_insert_post,
     * I do it here
     */
    public function setPostLatLon($post_id)
    {
        add_post_meta($post_id, 'lat', self::$currentRandomLat);
        add_post_meta($post_id, 'lon', self::$currentRandomLon);
    }

    /**
     * Register a simple post type for us
     */
    public function registerPostType()
    {
        register_post_type(
            'monkeyman_geo',
            array(
                'label' => 'Geo Location',
                'labels' => array(
                    'name' => 'Geo Locations',
                    'singular_name' => 'Geo Location',
                    'add_new' => 'Add new',
                    'add_new_item' => 'Add new location',
                    'edit_item' => 'Edit location',
                    'new_item' => 'New location',
                    'view_item' => 'View location',
                    'search_items' => 'Search locations',
                    'not_found' => 'No locations found',
                    'not_found_in_trash' => 'No locations found in trash',
                    'parent_item_colon' => null,
                ),
                'description' => 'Geographical locations',
                'public' => true,
                'exclude_from_search' => false,
                'publicly_queryable' => true,
                'show_ui' => true,
                'menu_position' => null,
                'menu_icon' => null,
                'capability_type' => 'post',
                'capabilities' => array(),
                'hierarchical' => false,
                'supports' => array(
                    'title',
                    'editor',
                    'custom-fields',
                ),
                'register_meta_box_cb' => null,
                'taxonomies' => array(),
                'permalink_epmask' => EP_PERMALINK,
                'rewrite' => array(
                    'slug' => 'locations',
                ),
                'query_var' => true,
                'can_export' => true,
                'show_in_nav_menus' => true,
            )
        );
    }
}

$monkeyman_Geo_instance = new Monkeyman_Geo();
9
Jan Fabry

Bu konuda partiye geç kaldım, ama geriye dönüp baktığımda, get_post_meta kullandığınız SQL sorgusu yerine gerçekten buradaki sorun.

Son zamanlarda çalıştığım bir sitede benzer bir coğrafi arama yapmak zorunda kaldım ve lat ve lon'ları depolamak için meta tablosunu kullanmak yerine (en iyi iki birleşme ve en fazla iki birleştirme gerektirir), ve eğer get_post_meta kullanıyorsanız, iki ek veritabanı konum başına sorgular), mekansal olarak indekslenmiş bir geometri POINT veri tipi ile yeni bir tablo oluşturdum.

Benim sorgunuz seninkine çok benziyordu, MySQL ağır yük kaldırıyordu (trig fonksiyonlarını dışarıda bıraktım ve her şeyi iki boyutlu alana basitleştirdim, çünkü amaçlarım için yeterince yakındı):

function nearby_property_listings( $number = 5 ) {
    global $client_location, $wpdb;

    //sanitize public inputs
    $lat = (float)$client_location['lat'];  
    $lon = (float)$client_location['lon']; 

    $sql = $wpdb->prepare( "SELECT *, ROUND( SQRT( ( ( ( Y(geolocation) - $lat) * 
                                                       ( Y(geolocation) - $lat) ) *
                                                         69.1 * 69.1) +
                                                  ( ( X(geolocation) - $lon ) * 
                                                       ( X(geolocation) - $lon ) * 
                                                         53 * 53 ) ) ) as distance
                            FROM {$wpdb->properties}
                            ORDER BY distance LIMIT %d", $number );

    return $wpdb->get_results( $sql );
}

$ client_location, bir kamuya açık coğrafi IP arama servisinin döndürdüğü bir değerdir (geoio.com kullandım, ancak benzer bir kaç tane daha var.)

Tuhaf görünebilir, ancak test ederken, 4 saniyenin altındaki 80.000 satırlık masanın en yakın 5 yerini sürekli olarak geri döndü.

MySQL, önerilen DISTANCE işlevini açıncaya kadar bu, konum aramalarını uygulamak için bulduğum en iyi yöntem gibi görünüyor.

EDIT: Bu belirli tablo için tablo yapısını ekleme. Bu bir dizi mülk listesidir, bu nedenle diğer herhangi bir kullanım durumuna benzer olabilir veya olmayabilir.

CREATE TABLE IF NOT EXISTS `rh_properties` (
  `listingId` int(10) unsigned NOT NULL,
  `listingType` varchar(60) collate utf8_unicode_ci NOT NULL,
  `propertyType` varchar(60) collate utf8_unicode_ci NOT NULL,
  `status` varchar(20) collate utf8_unicode_ci NOT NULL,
  `street` varchar(64) collate utf8_unicode_ci NOT NULL,
  `city` varchar(24) collate utf8_unicode_ci NOT NULL,
  `state` varchar(5) collate utf8_unicode_ci NOT NULL,
  `Zip` decimal(5,0) unsigned zerofill NOT NULL,
  `geolocation` point NOT NULL,
  `county` varchar(64) collate utf8_unicode_ci NOT NULL,
  `bedrooms` decimal(3,2) unsigned NOT NULL,
  `bathrooms` decimal(3,2) unsigned NOT NULL,
  `price` mediumint(8) unsigned NOT NULL,
  `image_url` varchar(255) collate utf8_unicode_ci NOT NULL,
  `description` mediumtext collate utf8_unicode_ci NOT NULL,
  `link` varchar(255) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`listingId`),
  KEY `geolocation` (`geolocation`(25))
)

geolocation sütunu, buradaki amaçlarla ilgili tek şeydir; Veritabanına yeni değerler aktarırken sadece adresten aradığım x (lon), y (lat) koordinatlarından oluşuyor.

1
goldenapples

Sadece bütün varlıklar arasındaki mesafeleri önceden hesaplayın. Bunu, değerleri endeksleyebilme özelliğine sahip bir veritabanı tablosunda saklardım.

0
hakre