Aplikacja „sprzedaż samochodów” – Spatial Search, czyli wprowadzenie danych lokalizacyjnych (cz. 3)

Ilość ogłoszeń w naszej bazie rozrosła się do tego stopnia, że klienci zaproponowali dodanie nowej opcji przy filtrowaniu wyników wyszukiwania oraz nowej opcji sortowania. Mianowicie musimy dodać funkcjonalność, która pozwoli nam operować na danych związanych z lokalizacją auta w danym ogłoszeniu.

Analiza wymagań

Chcemy dodać dwie nowe funkcjonalności:

  1. Zawężanie wyników wyszukiwania w taki sposób, aby możliwe było wyświetlenie tylko tych aut, które są położone nie dalej niż x kilometrów od określonego miejsca, gdzie x = 50,100,200,500,1000 km.
  2. Sortowanie wyników wyszukiwania po odległości pomiędzy danym punktem, a lokalizacją auta z danego ogłoszenia.

W celu realizacji powyższych zadań, skorzystamy z funkcjonalności solr zwanej „Spatial Search”, która dostępna jest od wersji 3.1. Zmiany, które będziemy musieli wprowadzić, dotyczyć będą modyfikacji pliku schema.xml oraz danych wejściowych, do których dodamy informację o położeniu geograficznym każdego z aut. Na końcu zostanie nam już tylko odpowiednie złożenie zapytań.

Zmiany w schema.xml

  1. Definicja nowych typów pól:
    • pierwsza definicja to nic innego jak kolejny typ liczbowy – double:
    • <fieldType name="tdouble" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
    • druga definicja zaś wykorzystuje specjalną klasę „solr.LatLonType”, która pozwoli nam na zaindeksowanie danych geograficznych wykorzystując pole dynamiczne o suffixie „_coordinate”:
    • <fieldType name="location" subFieldSuffix="_coordinate"/>
  2. Definicja  nowych pól:
    • pole, które będzie wykorzystywane do gromadzenia informacji o nazwie miejscowości, z którego pochodzi auto:
    • <field name="city" type="string" indexed="true" stored="true" />
    • pole „loc” posłuży nam do zaindeksowania danych lokalizacyjnych:
    • <field name="loc" type="location" indexed="true" stored="false"/>
    • pole dynamiczne będzie wykorzystywane wewnętrznie do gromadzenia informacji, które wprowadzimy do pola „loc”:
    • <dynamicField name="*_coordinate"  type="tdouble" indexed="true" stored="false"/>

Analiza danych wejściowych

W celu prezentacji sposobu modyfikacji nowych danych, weźmy próbkę 5-ciu ogłoszeń z miast:

  1. Koszalin
    • szerokość geograficzna: 54.12
    • długość geograficzna: 16.11
  2. Białystok
    • szerokość geograficzna: 53.08
    • długość geograficzna: 23.09
  3. Szczecin
    • szerokość geograficzna: 53.25
    • długość geograficzna: 14.35
  4. Gdańsk
    • szerokość geograficzna: 54.21
    • długość geograficzna: 18.40
  5. Warszawa
    • szerokość geograficzna: 52.15
    • długość geograficzna: 21.00

Dane lokalizacyjne wprowadzamy do pola „loc” wpisując szerokość geograficzną danego miasta oraz po przecinku jego długość. Nasze dane mogą wyglądać zatem tak:

<add>
   <doc>
      <field name="id">1</field>
      <field name="make">Audi</field>
      <field name="model">80</field>
      <field name="year">2008</field>
      <field name="price">9774</field>
      <field name="engine_size">2000</field>
      <field name="mileage">92467</field>
      <field name="colour">green</field>
      <field name="damaged">false</field>
      <field name="city">Koszalin</field>
      <field name="loc">54.12,16.11</field>
   </doc>
   <doc>
      <field name="id">2</field>
      <field name="make">Audi</field>
      <field name="model">A8</field>
      <field name="year">2009</field>
      <field name="price">9078</field>
      <field name="engine_size">1000</field>
      <field name="mileage">31369</field>
      <field name="colour">black</field>
      <field name="damaged">false</field>
      <field name="city">Białystok</field>
      <field name="loc">53.08,23.09</field>
   </doc>
   <doc>
      <field name="id">3</field>
      <field name="make">Audi</field>
      <field name="model">TT</field>
      <field name="year">1997</field>
      <field name="price">1109</field>
      <field name="engine_size">1299</field>
      <field name="mileage">116987</field>
      <field name="colour">silver</field>
      <field name="damaged">true</field>
      <field name="city">Szczecin</field>
      <field name="loc">53.25,14.35</field>
   </doc>
   <doc>
      <field name="id">4</field>
      <field name="make">BMW</field>
      <field name="model">Seria 7</field>
      <field name="year">2007</field>
      <field name="price">140000</field>
      <field name="engine_size">3000</field>
      <field name="mileage">418000</field>
      <field name="colour">green</field>
      <field name="damaged">false</field>
      <field name="city">Gdańsk</field>
      <field name="loc">54.21,18.40</field>
   </doc>
   <doc>
      <field name="id">5</field>
      <field name="make">Chevrolet</field>
      <field name="model">TrailBlazer</field>
      <field name="year">2007</field>
      <field name="price">140000</field>
      <field name="engine_size">3000</field>
      <field name="mileage">418000</field>
      <field name="colour">green</field>
      <field name="damaged">false</field>
      <field name="city">Warszawa</field>
      <field name="loc">52.15,21.00</field>
   </doc>
</add>

Tworzymy zapytania

Dane lokalizacyjne mamy w indeksie, zatem zostało nam już tylko złożyć odpowiednie zapytania, które zrealizują nasze nowe funkcjonalności. Załóżmy, że będziemy wyszukiwać ogłoszenia znajdując się w mieście Białystok, które jest położone w odległości ok. 200 km od miasta Warszawa, ok. 400 km od miasta Gdańsk, ok. 550 km od miasta Koszalin oraz ok. 650 km od miasta Szczecin.

W celu realizacji punktu 1 z analizy wymagań, dodajemy do żądania nowe zapytanie filtrujące:

...&fq={!geofilt sfield=loc}&pt=53.08,23.09&d=50

gdzie:

  • sfield – nazwa pola, do którego wprowadzaliśmy nasze dane lokalizacyjne.
  • pt – współrzędne punktu startowego, w naszym wypadku są to współrzędne miasta Białystok.
  • d – dystans o jaki chcemy zawęzić wyniki wyszukiwania. Podstawiając kolejno wartości 50,100,200,500,1000 możemy zrealizować nasze wymagania.

Przykład:

  1. Zapytanie:
    q=*:*&fq={!geofilt sfield=loc}&pt=53.08,23.09&d=200
  2. Wyniki wyszukiwania:
  3. <result name="response" numFound="2" start="0">
       <doc>
          <str name="city">Białystok</str>
          <str name="colour">black</str>
          <bool name="damaged">false</bool>
          <int name="engine_size">1000</int>
          <str name="id">2</str>
          <str name="make">Audi</str>
          <int name="mileage">31369</int>
          <str name="model">A8</str>
          <float name="price">9078.0</float>
          <int name="year">2009</int>
       </doc>
       <doc>
          <str name="city">Warszawa</str>
          <str name="colour">green</str>
          <bool name="damaged">false</bool>
          <int name="engine_size">3000</int>
          <str name="id">5</str>
          <str name="make">Chevrolet </str>
          <int name="mileage">418000</int>
          <str name="model">TrailBlazer</str>
          <float name="price">140000.0</float>
          <int name="year">2007</int>
       </doc>
    </result>

Świetnie, w wynikach nie mamy ogłoszeń z miast Koszalin, Gdańsk oraz Szczecin, gdyż te miasta leżą w odległości ponad 200 km od miasta Białystok.

W celu realizacji punktu 2 z analizy wymagań, wykorzystamy możliwość sortowania wyników wyszukiwania z użyciem funkcji geodist. Tworzymy następujące zapytanie:

...&sfield=loc&pt=53.08,23.09&sort=geodist()+desc

Przykład sortowania wyników wyszukiwania po odległości, rozpoczynając od miasta Białystok:

  1. Zapytanie:
    q=*:*&sfield=loc&pt=53.08,23.09&sort=geodist()+asc
  2. Wyniki wyszukiwania:
  3. <result name="response" numFound="5" start="0">
       <doc>
          <str name="city">Białystok</str>
          <str name="colour">black</str>
          <bool name="damaged">false</bool>
          <int name="engine_size">1000</int>
          <str name="id">2</str>
          <str name="make">Audi</str>
          <int name="mileage">31369</int>
          <str name="model">A8</str>
          <float name="price">9078.0</float>
          <int name="year">2009</int>
       </doc>
       <doc>
          <str name="city">Warszawa</str>
          <str name="colour">green</str>
          <bool name="damaged">false</bool>
          <int name="engine_size">3000</int>
          <str name="id">5</str>
          <str name="make">Chevrolet </str>
          <int name="mileage">418000</int>
          <str name="model">TrailBlazer</str>
          <float name="price">140000.0</float>
          <int name="year">2007</int>
       </doc>
       <doc>
          <str name="city">Gdańsk</str>
          <str name="colour">green</str>
          <bool name="damaged">false</bool>
          <int name="engine_size">3000</int>
          <str name="id">4</str>
          <str name="make">BMW</str>
          <int name="mileage">418000</int>
          <str name="model">Seria 7</str>
          <float name="price">140000.0</float>
          <int name="year">2007</int>
       </doc>
       <doc>
          <str name="city">Koszalin</str>
          <str name="colour">green</str>
          <bool name="damaged">false</bool>
          <int name="engine_size">2000</int>
          <str name="id">1</str>
          <str name="make">Audi</str>
          <int name="mileage">92467</int>
          <str name="model">80</str>
          <float name="price">9774.0</float>
          <int name="year">2008</int>
       </doc>
       <doc>
          <str name="city">Szczecin</str>
          <str name="colour">silver</str>
          <bool name="damaged">true</bool>
          <int name="engine_size">1299</int>
          <str name="id">3</str>
          <str name="make">Audi</str>
          <int name="mileage">116987</int>
          <str name="model">TT</str>
          <float name="price">1109.0</float>
          <int name="year">1997</int>
       </doc>
    </result>

Zgadza się! Wymagania zostały zrealizowane.

Podsumowanie

Po raz kolejny udało nam się sprostać oczekiwaniom naszych klientów. Tym razem dodaliśmy funkcjonalności związane z lokalizacją geograficzną aut, które pozwolą użytkownikom na zawężanie oraz sortowanie wyników wyszukiwania wykorzystując odległości geograficzne. Pełen sukces.

This post is also available in: angielski

This entry was posted on poniedziałek, Marzec 14th, 2011 at 09:40 and is filed under Bez kategorii. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

4 komentarze to “Aplikacja „sprzedaż samochodów” – Spatial Search, czyli wprowadzenie danych lokalizacyjnych (cz. 3)”

  1. Piotr Says:

    W wynikach brakuje mi informacji o wyliczonej odległości. Uwiarygodniłoby to sortowanie 🙂 Czy Solr to umożliwia?

  2. rA Says:

    Oczywiście wyniki sortowania były by wiarygodniejsze, gdybyśmy widzieli w odpowiedzi pole, po którym sortujemy. Tutaj jednak sortowanie nie opera się stricte na wartości konkretnego pola, a na wyniku funkcji, która wykorzystuje odpowiednie dane z indeksu. Solr niestety nie umożliwia nam póki co dodawania do response’a wyników wykonania konkretnej funkcji, ale na solr jira można zaobserwować już pierwsze działania idące w tym kierunku: https://issues.apache.org/jira/browse/SOLR-1298 🙂 Będziemy z pewnością śledzić rozwój tej funkcjonalności.

  3. virus Says:

    Mnie ciekawi grupowanie wyników.
    np. jeżeli mamy po pare tysięcy w krakowie i warszawie to zamiast podawać po pare tysięcy wyników zgrupować dane w granicy np. 1 kilometr.
    Jest to możliwe jakoś prosto ?

  4. gr0 Says:

    Grupowanie na podstawie wyników zwracanych przez funkcje, będzie możliwe w Solr 4.0 (parameter group.func). Teoretycznie da się to zrobić właśnie za pomocą tego parametru. Natomiast sam nie próbowałem, więc nie wiem jak praktycznie 🙂