In springboot, the geo location search of elasticsearch is used to find houses on the map

Time:2022-1-16

summary

The map search function is a function of various search source apps to improve the user experience, which is available on links, anjuke and other websites. If the database is directly used for search, frequent drag and request will definitely lead to unbearable pressure on the database. Therefore, elastic search is introduced, and the function of geo geographic location retrieval happens to be available in ES.

start

Prepare a house table

CREATE TABLE `house` (
  `id` int(11) unsigned NOT NULL AUTO_ Increment comment 'house unique identifier',
  `title` varchar(32) NOT NULL,
  `Price ` int (11) unsigned not null comment ',
  `Area ` int (11) unsigned not null comment 'area',
  `Room ` int (11) unsigned not null comment 'number of bedrooms',
  `Floor ` int (11) unsigned not null comment 'floor',
  `total_ Floor ` int (11) unsigned not null comment 'total floor',
  `watch_ Times ` int (11) unsigned default '0' comment 'viewed times',
  `build_ Year ` int (4) not null comment 'establishment years',
  `Status ` int (4) unsigned not null default '0' comment 'house status 0 - unapproved 1 - approved 2 - rented 3 - logically deleted',
  `create_ time` datetime NOT NULL DEFAULT CURRENT_ Timestamp comment 'creation time',
  `last_ update_ time` datetime NOT NULL DEFAULT CURRENT_ TIMESTAMP ON UPDATE CURRENT_ Timestamp comment 'latest data update time',
  `city_ en_ Name ` varchar (32) not null comment 'abbreviation of city tag, such as Beijing BJ',
  `region_ en_ Name ` varchar (255) not null comment 'area is abbreviated in English, such as CPQ in Changping District,
  `Cover ` varchar (32) default null comment 'cover',
  `Direction ` int (11) not null comment 'house orientation',
  `distance_ to_ Subway ` int (11) not null default '- 1' comment 'distance from the subway is - 1 by default, there is no subway nearby',
  `Parlour ` int (11) not null default '0' comment 'number of living rooms',
  `District ` varchar (32) not null comment 'cell',
  `admin_ ID ` int (11) not null comment 'administrator ID',
  `bathroom` int(11) NOT NULL DEFAULT '0',
  `Street ` varchar (32) not null comment ',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_ Increment = 26 default charset = utf8mb4 comment = 'house information table';

Add test data

Insert into ` house ` values (16, 'Fuli City International Trade CBD fashion leisure business office', 6300, 70, 2, 10, 20, 0, 2012, 1, '2021-01-12 18:56:14', '2021-01-12 16:34:03', 'BJ', 'HDQ', 'fvko1ffygbrxcp_1o9ta94u2qvbp', 1, - 1, 1, 'Rongze Jiayuan', 2, 0, 'Longyu West 2nd Road');
Insert into ` house ` values (18, 'empty loft in the east of Huamao City, clean and warm, ready to sign at any time', 5700, 52, 1, 7, 20, 0, 2012, 1, '2021-01-12 18:56:14', '2021-01-12 16:34:05', 'BJ', 'HDQ', 'fl1lnikhmmiecbtn jtsurxugtfu', 2, 1085, 1, 'Rongze Jiayuan', 2, 0, 'Longyu West 2nd Road');
Insert into ` house ` values (19, 'three bedroom self occupied Banlou, wangchunyuan, hardcover, north-south, transparent lighting, good vision!', 9200, 132, 3, 6, 14, 0, 2005, 1, '2021-01-12 18:56:14', '2021-01-12 16:34:07', 'BJ', 'HDQ', 'fp1xpkvytpscevhvqvw0hif2fxk7', 2, 1108, 2, 'Rongze Jiayuan', 2, 0, 'Longyu West 2nd Road');
Insert into ` house ` values (20, 'the owner of a large two bedroom house rents it in good faith', 5400, 56, 2, 12, 20, 0, 2012, 1, '2021-01-12 18:56:14', '2021-01-12 16:34:08', 'BJ', 'HDQ', 'fvvqu8lnez5xalabom1kxr2pz1x', 2, - 1, 1, 'Rongze Jiayuan', 2, 0, 'Longyu West 2nd Road');
  1. Import data into elasticsearch
    Geographic location information can be obtained through Baidu map API and submitted to es after assembly. You can see the value of location field by viewing the data in the browser

    In springboot, the geo location search of elasticsearch is used to find houses on the map

    image.png

    The method to obtain the location value is as follows:

/**
 Query map coordinates by city and address
*/
public BaiduMapLocation getBaiduMapLocation(String city, String address) {
        String encodeAddress;
        String encodeCity;
        final BAIDU_MAP_GEOCONV_API = "http://api.map.baidu.com/geocoder/v2/?";
        final BAIDU_MAP_KEY = "6QtSF673D1pYl3eQkEXfwp8ZgsQpB77U";
        try {
            encodeAddress = URLEncoder.encode(address, "UTF-8");
            encodeCity = URLEncoder.encode(city, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("Error to encode house address", e);
            return new ServiceResult<>(false, "Error to encode hosue address");
        }

        HttpClient httpClient = HttpClients.createDefault();
        StringBuilder sb = new StringBuilder(BAIDU_MAP_GEOCONV_API);
        sb.append("address=").append(encodeAddress).append("&")
                .append("city=").append(encodeCity).append("&")
                .append("output=json&")
                .append("ak=").append(BAIDU_MAP_KEY);

        HttpGet get = new HttpGet(sb.toString());
        try {
            HttpResponse response = httpClient.execute(get);
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                return null;
            }

            String result = EntityUtils.toString(response.getEntity(), "UTF-8");
            JsonNode jsonNode = objectMapper.readTree(result);
            int status = jsonNode.get("status").asInt();
            if (status != 0) {
                return null;
            } {
                BaiduMapLocation location = new BaiduMapLocation();
                JsonNode jsonLocation = jsonNode.get("result").get("location");
                location.setLongitude(jsonLocation.get("lng").asDouble());
                location.setLatitude(jsonLocation.get("lat").asDouble());
                return BaiduMapLocation ;
            }

        } catch (IOException e) {
            log.error("Error to fetch baidumap api", e);
            return null;
        }
    }

The entity classes of baidumaplocation are as follows:

@Data
public class BaiduMapLocation {
    //Longitude
    @JsonProperty("lon")
    private double longitude;
    //Latitude
    @JsonProperty("lat")
    private double latitude;

}
  1. After the data is ready, you can write the business logic test
    The main parameters passed in during map dragging are: map zoom level, map boundary, that is, the coordinates of the upper left corner and the lower right corner
    Therefore, create a request parameter entity class for map retrieval:
@Data
public class MapSearch {
    private String cityEnName;
    /**
     *Map zoom level
     */
    private int level = 12;
    private String orderBy = "lastUpdateTime";
    private String orderDirection = "desc";
    /**
     *Upper left corner
     */
    private Double leftLongitude;
    private Double leftLatitude;

    /**
     *Lower right corner
     */
    private Double rightLongitude;
    private Double rightLatitude;

    private int start = 0;
    private int size = 5;
}

4. Create a retrieval service class searchservice and a return result entity class servicemultiresult

ServiceMultiResult

public class ServiceMultiResult<T> {
    private long total;
    private List<T> result;

    public ServiceMultiResult(long total, List<T> result) {
        this.total = total;
        this.result = result;
    }

    public long getTotal() {
        return total;
    }

    public void setTotal(long total) {
        this.total = total;
    }

    public List<T> getResult() {
        return result;
    }

    public void setResult(List<T> result) {
        this.result = result;
    }

    public int getResultSize() {
        if (this.result == null) {
            return 0;
        }
        return this.result.size();
    }
}

SearchService

@Slf4j
@Service
public class SearchService{
    private static final String INDEX_NAME = "xunwu";
    private static final String INDEX_TYPE = "house";
    private static final String INDEX_TOPIC = "house_build";
public ServiceMultiResult<Long> mapQuery(MapSearch mapSearch) {
        //Create query criteria
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //Cities passed in map retrieval parameters
        boolQuery.filter(QueryBuilders.termQuery(HouseIndexKey.CITY_EN_NAME, mapSearch.getCityEnName()));

        //Geographic location search through geoboundingboxquery method
        boolQuery.filter(
            QueryBuilders.geoBoundingBoxQuery("location")
                .setCorners(
                        new GeoPoint(mapSearch.getLeftLatitude(), mapSearch.getLeftLongitude()),
                        new GeoPoint(mapSearch.getRightLatitude(), mapSearch.getRightLongitude())
                ));

        //Search condition settings
        SearchRequestBuilder builder = this.esClient.prepareSearch(INDEX_NAME)
                .setTypes(INDEX_TYPE)
                .setQuery(boolQuery)
                .addSort(HouseSort.getSortKey(mapSearch.getOrderBy()),
                        SortOrder.fromString(mapSearch.getOrderDirection()))
                .setFrom(mapSearch.getStart())
                .setSize(mapSearch.getSize());

        List<Long> houseIds = new ArrayList<>();
        //Query results
        SearchResponse response = builder.get();
        if (RestStatus.OK != response.status()) {
            log.warn("Search status is not ok for " + builder);
            return new ServiceMultiResult<>(0, houseIds);
        }

        for (SearchHit hit : response.getHits()) {
            houseIds.add(Longs.tryParse(String.valueOf(hit.getSourceAsMap().get(HouseIndexKey.HOUSE_ID))));
        }
        return new ServiceMultiResult<>(response.getHits().getTotalHits(), houseIds);
    }
}

Here we mainly look at querybuilders, a query interface provided in ES, in which the geoboundingboxquery method is used for geographic location query.

5. Finally, add a test controller to test

@Controller
public class HouseController {
    
    @Autowired
    SearchService searchService

    @GetMapping("/test")
    @ResponseBody
    public ServiceMultiResult<Long> test(@ModelAttribute MapSearch mapSearch) {
    
          ServiceMultiResult<Long> serviceMultiResult;
          serviceMultiResult = searchService.mapQuery(mapSearch);
          return serviceMultiResult ;
    }

}

Well, the geo location search function based on elasticsearch has been completed. There are still many places to be optimized in the demo. It only provides general ideas, or you can do it according to different ideas. For example, the location section can be obtained. You can pick up the coordinates through the map when editing the listings and add them directly to the database.

summary

Elasticsearch will often be used in future projects. Learning this middleware well can enrich your technology stack.