Four ways to realize LBS "people nearby" in one breath, the interviewer smiled

Original: Four ways to realize LBS "people nearby" in one breath, the interviewer smiled

introduction

Yesterday, a public account fan and I discussed an interview question. I personally found it more meaningful. I have organized and shared it with everyone here. I hope that my friends will not step on the road during the interview. The interview question is relatively simple: "Let you realize the function of a nearby person, what plan do you have?" This question is actually mainly to examine the breadth of technology for everyone. This article introduces several plans to give you a few ideas to avoid the interview process. The Chinese language is congested and affects the result of the interview. If there is any rigour, I hope the relatives will gently correct me!

“附近的人”It is more commonly used in functional life, like restaurants near the take-out app, sharing vehicles near the bike app. Since the probability of being asked in common interviews is very high, we will analyze the functions of "nearby people" based on mysql数据库, Redisand in order, in order MongoDB.
Insert picture description here
Popular science : To mark a location in the world, the common practice is to use latitude and longitude. The range of longitude is (-180, 180), and the range of latitude is (-90, 90). The east is positive and the west is negative. For example, the longitude and latitude (116.49141, 40.01229) of Wangjing Motorola Building are all positive numbers, because China is located in the northeast hemisphere.


1. The principle of "people nearby"

“附近的人”Also known as LBS(Location Based Services, based on location services), it is based on the user's current geographic location data and services to provide users with accurate value-added services.

The core idea of ​​"people nearby" is as follows:

  1. Focus on "me" to search for nearby users

  2. Calculate the distance between others and "I" based on the current location of "I"

  3. Sort by the distance between "Me" and others, and filter out the users or stores closest to me
    Insert picture description here

2. What is the GeoHash algorithm?

Saying “附近的人”before the concrete realization of functions, first to understand GeoHashthe algorithm, because behind will always deal with it. The best way to locate a position is to use a 经、纬度logo, but 经、纬度it is two-dimensional, and it is still very troublesome to calculate the position. If you can convert the two-dimensional 经、纬度data into one-dimensional data by some method , then it is necessary It's much easier, so the GeoHashalgorithm came into being.

GeoHashThe algorithm converts the two-dimensional latitude and longitude into a character string. For example, the following 9 GeoHashcharacter strings represent 9 areas, and each character string represents a rectangular area. The other points (latitude and longitude) in this rectangular area are all represented by the same GeoHashstring.

Insert picture description here

For example : WX4ERusers in the area search for nearby restaurant data. Because the user GeoHashstrings in this area are all WX4ER, it can be regarded WX4ERas key, restaurant information as valuecache; and if the GeoHashalgorithm is not used , users in the area request restaurant data, the user The transmitted latitude and longitude are different, so the cache is not only troublesome but also has a huge amount of data.

GeoHashThe longer the string, the more precise the position. The longer the string, the smaller the error in distance. The following chart geohashcode accuracy table:

geohash code length width height
1 5,009.4km 4,992.6km
2 1,252.3km 624.1km
3 156.5km 156km
4 39.1km 19.5km
5 4.9km 4.9km
6 1.2km 609.4m
7 152.9m 152.4m
8 38.2m 19m
9 4.8m 4.8m
10 1.2m 59.5cm
11 14.9cm 14.9cm
12 3.7cm 1.9cm

Moreover, the more similar the character strings are, the closer the distance is, and the closer the character string prefix matches, the closer the distance. For example, the longitude and latitude below represent three restaurants close to each other.

Merchant Latitude and longitude Geohash string
Skewer 116.402843,39.999375 wx4er9v
Hot Pot 116.3967,39.99932 wx4ertk
Barbecue 116.40382,39.918118 wx4erfe

Let everyone simply understand what an GeoHashalgorithm is to facilitate the development of the GeoHashcontent behind. The content of the algorithm is relatively deep. Interested friends can dig deeper by themselves. It does not take up too much space (in fact, I understand too shallow, crying ~).

3. Based on Mysql

This method is purely mysqlimplementation-based and does not use GeoHashalgorithms.

1. Design ideas

Taking the user as the center, suppose a circle is given a distance of 500 meters as a radius, and all users in this circular area are "close people" that meet the user's requirements. But there is a problem that the circle has radians. It is too difficult to directly search the circular area. It is impossible to search directly by latitude and longitude.

However, if a square is placed on a round jacket, by obtaining the maximum and minimum values ​​of the user's longitude and latitude (longitude, latitude + distance), and then using the maximum and minimum values ​​as the filtering conditions, it is easy to search for user information in the square.

So the question is coming again, what should I do if there is more area swollen?

Let's analyze, the extra users in this part of the area, the distance to the dot must be greater than the radius of the circle, then we calculate the distance between the user's center point and all users in the square, and filter out all distances less than or equal to the radius Users, all users in the circular area meet the requirements “附近的人”.

Insert picture description here

2. Analysis of pros and cons

Based on pure mysqlrealization “附近的人”, it is the obvious advantage of simple, as long as the user is able to save a table built longitude and latitude information can be. The shortcomings are also obvious, requiring a large amount of calculation of the distance between two points, which greatly affects performance.

3. Realization

Create a simple table to store the user's latitude and longitude attributes.

CREATE TABLE `nearby_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL COMMENT '名称',
  `longitude` double DEFAULT NULL COMMENT '经度',
  `latitude` double DEFAULT NULL COMMENT '纬度',
  `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

To calculate the distance between two points, a three-party library is used. After all, the wheels made by yourself are not particularly round, and may be square, ah ha ha ha ~

<dependency>
     <groupId>com.spatial4j</groupId>
     <artifactId>spatial4j</artifactId>
     <version>0.5</version>
</dependency>

After the circumscribed square is obtained, the users in the square area are searched by the maximum, minimum, and longitude values ​​of the square, and then the users who exceed the specified distance are eliminated, which is the final 附近的人.

    private SpatialContext spatialContext = SpatialContext.GEO;    
	
	/**
     * 获取附近 x 米的人
     *
     * @param distance 搜索距离范围 单位km
     * @param userLng  当前用户的经度
     * @param userLat  当前用户的纬度
     */
    @GetMapping("/nearby")
    public String nearBySearch(@RequestParam("distance") double distance,
                               @RequestParam("userLng") double userLng,
                               @RequestParam("userLat") double userLat) {
        //1.获取外接正方形
        Rectangle rectangle = getRectangle(distance, userLng, userLat);
        //2.获取位置在正方形内的所有用户
        List<User> users = userMapper.selectUser(rectangle.getMinX(), rectangle.getMaxX(), rectangle.getMinY(), rectangle.getMaxY());
        //3.剔除半径超过指定距离的多余用户
        users = users.stream()
            .filter(a -> getDistance(a.getLongitude(), a.getLatitude(), userLng, userLat) <= distance)
            .collect(Collectors.toList());
        return JSON.toJSONString(users);
    }
    
    private Rectangle getRectangle(double distance, double userLng, double userLat) {
        return spatialContext.getDistCalc()
            .calcBoxByDistFromPt(spatialContext.makePoint(userLng, userLat), 
                                 distance * DistanceUtils.KM_TO_DEG, spatialContext, null);
    }


Since the sorting of the distance between users is implemented in business code, you can see that the SQL statement is also very simple.

    <select id="selectUser" resultMap="BaseResultMap">
        SELECT * FROM user
        WHERE 1=1
        and (longitude BETWEEN ${minlng} AND ${maxlng})
        and (latitude BETWEEN ${minlat} AND ${maxlat})
    </select>

Four, Mysql + GeoHash

1. Design ideas

The design idea of ​​this method is simpler. When storing user location information, the corresponding geohashcharacter string is calculated according to the user's latitude and longitude attributes . Note : When calculating a geohashstring, you need to specify geohashthe precision of the string, that is geohash, the length of the string, refer to the geohashprecision table above .

When you need to obtain 附近的人, just use the current user geohashstring, the database WHERE geohash Like 'geocode%queries geohashusers with similar strings through ' , and then calculates the distance between the current user and the searched user, and filters out all distances less than or equal to the specified distance (near 500 meters), that is 附近的人.

2. Analysis of pros and cons

There is a problem with the GeoHashalgorithm implementation “附近的人”, because the geohashalgorithm divides the map into rectangles and encodes each rectangle to get a geohashstring. But my current point is very close to the neighboring point, but it happens that we are in two areas, and the point in front of me is obviously not found, and it is really dark under the light.

How to solve this problem?

In order to avoid similar adjacent two points in different areas, we need to get the current point ( WX4G0) near the area where the 8个area geohashcode, together with screening compared.
Insert picture description here

3. Realization

A table should also be designed to store the user's latitude and longitude information, but the difference is that there is one more geo_codefield to store the geohash string. This field is calculated by the user's latitude and longitude attributes. It is recommended to add indexes for frequently used fields.

CREATE TABLE `nearby_user_geohash` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL COMMENT '名称',
  `longitude` double DEFAULT NULL COMMENT '经度',
  `latitude` double DEFAULT NULL COMMENT '纬度',
  `geo_code` varchar(64) DEFAULT NULL COMMENT '经纬度所计算的geohash码',
  `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `index_geo_hash` (`geo_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

First, based on the user's latitude and longitude information, calculate the geoHashcode of the user's coordinates after specifying the accuracy , and then obtain the code of 8 directions around the user geoHashto search the user in the database, and finally filter out the user who exceeds the given distance (within 500 meters).

 private SpatialContext spatialContext = SpatialContext.GEO;

    /***
     * 添加用户
     * @return
     */
    @PostMapping("/addUser")
    public boolean add(@RequestBody UserGeohash user) {
        //默认精度12位
        String geoHashCode = GeohashUtils.encodeLatLon(user.getLatitude(),user.getLongitude());
        return userGeohashService.save(user.setGeoCode(geoHashCode).setCreateTime(LocalDateTime.now()));
    }


/**
     * 获取附近指定范围的人
     *
     * @param distance 距离范围(附近多远的用户) 单位km
     * @param len      geoHash的精度(几位的字符串)
     * @param userLng  当前用户的经度
     * @param userLat  当前用户的纬度
     * @return json
     */
    @GetMapping("/nearby")
    public String nearBySearch(@RequestParam("distance") double distance,
                               @RequestParam("len") int len,
                               @RequestParam("userLng") double userLng,
                               @RequestParam("userLat") double userLat) {


        //1.根据要求的范围,确定geoHash码的精度,获取到当前用户坐标的geoHash码
        GeoHash geoHash = GeoHash.withCharacterPrecision(userLat, userLng, len);
        //2.获取到用户周边8个方位的geoHash码
        GeoHash[] adjacent = geoHash.getAdjacent();

        QueryWrapper<UserGeohash> queryWrapper = new QueryWrapper<UserGeohash>()
            .likeRight("geo_code",geoHash.toBase32());
        Stream.of(adjacent).forEach(a -> queryWrapper.or().likeRight("geo_code",a.toBase32()));

        //3.匹配指定精度的geoHash码
        List<UserGeohash> users = userGeohashService.list(queryWrapper);
        //4.过滤超出距离的
        users = users.stream()
                .filter(a ->getDistance(a.getLongitude(),a.getLatitude(),userLng,userLat)<= distance)
                .collect(Collectors.toList());
        return JSON.toJSONString(users);
    }

    
    /***
     * 球面中,两点间的距离
     * @param longitude 经度1
     * @param latitude  纬度1
     * @param userLng   经度2
     * @param userLat   纬度2
     * @return 返回距离,单位km
     */
    private double getDistance(Double longitude, Double latitude, double userLng, double userLat) {
        return spatialContext.calcDistance(spatialContext.makePoint(userLng, userLat),
                spatialContext.makePoint(longitude, latitude)) * DistanceUtils.DEG_TO_KM;
    }

Is 、 Redis + GeoHash

Redis 3.2After the version, based on the geohashdata structure Zsetprovides geographic location-related functions. Through the above two mysqlimplementation methods, it 附近的人is found that the function is obvious to read more and write less scenes, so the redisperformance will be greatly improved.

1. Design ideas

redisThe 附近的人function is realized mainly through Geothe six commands of the module.

  • GEOADD: Add the given location object (latitude, longitude, name) to the specified key;
  • GEOPOS: Return the position (longitude and latitude) of all objects at a given position from the key;
  • GEODIST: Return the distance between two given positions;
  • GEOHASH: Returns a Geohash representation of one or more location objects;
  • GEORADIUS: Take the given latitude and longitude as the center, return all the position objects in the target collection whose distance from the center does not exceed the given maximum distance;
  • GEORADIUSBYMEMBER: Take the given position object as the center and return all the position objects whose distance does not exceed the given maximum distance.

Take GEOADDcommands and GEORADIUScommands as simple examples:

GEOADD key longitude latitude member [longitude latitude member ...]

Among them, keyis the collection name, memberwhich is the object corresponding to the latitude and longitude.

GEOADD Add multiple merchants' "hot pot restaurant" location information:

GEOADD hotel 119.98866180732716	30.27465803229662 火锅店

GEORADIUSAccording to a given latitude and longitude as the center position of the object to get all of the distance from the center of the target set not to exceed a given maximum distance (500 meters), that is “附近的人”.

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] [STORE key] [STORedisT key]

Range unit: m| km| ft| mi-> meters | kilometers | feet | miles.

  • WITHDIST: While returning the position object, the distance between the position object and the center is also returned. The unit of distance is consistent with the range unit given by the user.
  • WITHCOORD: Also return the longitude and latitude of the location object.
  • WITHHASH: In the form of a 52-bit signed integer, returns the ordered set score of the location object after the original geohash encoding. This option is mainly used for low-level applications or debugging, the actual effect is not great.
  • ASC | DESC: Returns the position object element from near to far | Returns the position object element from far to near.
  • COUNT count: Select the first N matching position object elements. (Return all elements if not set)
  • STORE key: Save the geographic location information of the returned result to the specified key.
  • STORedisT key: Save the distance of the returned result from the center point to the specified key.

For example, the following command: Get all restaurants within 500 meters of the current location.

GEORADIUS hotel 119.98866180732716	30.27465803229662 500 m WITHCOORD

RedisAn ordered set ( zset) is used internally to store the user's location information. zsetEach element in the is an object with a position. The scorevalue of the element is a 52-bit geohashvalue calculated by latitude and longitude .

2. Analysis of pros and cons

redisThe implementation 附近的人efficiency is relatively high, the integration is relatively simple, and it also supports sorting the distance. However, there is a certain error in the results. To make the results more accurate, you need to manually calculate the distance between the user's center position and other user positions, and then filter again.

3. Realization

The following is the Java redisimplementation version, the code is very concise.

 @Autowired
    private RedisTemplate<String, Object> redisTemplate;
	
	//GEO相关命令用到的KEY
    private final static String KEY = "user_info";

    public boolean save(User user) {
        Long flag = redisTemplate.opsForGeo().add(KEY, new RedisGeoCommands.GeoLocation<>(
                user.getName(), 
                new Point(user.getLongitude(), user.getLatitude()))
        );
        return flag != null && flag > 0;
    }

    /**
     * 根据当前位置获取附近指定范围内的用户
     * @param distance 指定范围 单位km ,可根据{@link org.springframework.data.geo.Metrics} 进行设置
     * @param userLng 用户经度
     * @param userLat 用户纬度
     * @return
     */
    public String nearBySearch(double distance, double userLng, double userLat) {
        List<User> users = new ArrayList<>();
        // 1.GEORADIUS获取附近范围内的信息
        GeoResults<RedisGeoCommands.GeoLocation<Object>> reslut = 
            redisTemplate.opsForGeo().radius(KEY, 
                        new Circle(new Point(userLng, userLat), new Distance(distance, Metrics.KILOMETERS)),
                        RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                                .includeDistance()
                                .includeCoordinates().sortAscending());
        //2.收集信息,存入list
        List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = reslut.getContent();
        //3.过滤掉超过距离的数据
        content.forEach(a-> users.add(
                new User().setDistance(a.getDistance().getValue())
                .setLatitude(a.getContent().getPoint().getX())
                .setLongitude(a.getContent().getPoint().getY())));
        return JSON.toJSONString(users);
    }

Six, MongoDB + 2d index

1. Design ideas

MongoDBRealize people in the vicinity, mainly through its two geospatial indexes 2dsphereand 2d. The bottom layer of the two indexes is still based on the Geohashconstruction. But Geohashthere are some differences from the international ones, please refer to the official documents for details .

2dsphere The index only supports querying the geometry of spherical surfaces.

2dThe index supports planar geometry and some spherical queries. Although the 2dindex some support queries ball, but 2dwhen the index for these spherical query, you may be wrong. So try to choose spherical query 2dspherethe index.

Although the methods of the two indexes are different, as long as the coordinate span is not too large, the difference between the distances calculated by the two indexes is almost negligible.

2. Realization

First insert data into a number of position MongoDB, collectionis named hotel, the equivalent MySQLof the table name. Two field namenames locationare longitude and latitude data pairs.

db.hotel.insertMany([
 {'name':'hotel1',  location:[115.993121,28.676436]},
 {'name':'hotel2',  location:[116.000093,28.679402]},
 {'name':'hotel3',  location:[115.999967,28.679743]},
 {'name':'hotel4',  location:[115.995593,28.681632]},
 {'name':'hotel5',  location:[115.975543,28.679509]},
 {'name':'hotel6',  location:[115.968428,28.669368]},
 {'name':'hotel7',  location:[116.035262,28.677037]},
 {'name':'hotel8',  location:[116.024770,28.68667]},
 {'name':'hotel9',  location:[116.002384,28.683865]},
 {'name':'hotel10', location:[116.000821,28.68129]},
])

Next we have to locationcreate a field 2dindex, an index of accuracy by bitsspecifying, bitsthe greater the accuracy of the index is higher.

db.coll.createIndex({'location':"2d"}, {"bits":11111})

Use the geoNear command to test whether the nearcurrent coordinates (latitude and longitude), sphericalwhether to calculate the spherical distance, distanceMultiplierthe radius of the earth, the unit is meters, the default is 6378137, the maxDistancefilter conditions (users within the specified distance), open radians need to be divided distanceMultiplier, the distanceFieldcalculated distance between two points , Field alias (arbitrarily named).

db.hotel.aggregate({
    $geoNear:{
        near: [115.999567,28.681813], // 当前坐标
        spherical: true, // 计算球面距离
        distanceMultiplier: 6378137, // 地球半径,单位是米,那么的除的记录也是米
        maxDistance: 2000/6378137, // 过滤条件2000米内,需要弧度
        distanceField: "distance" // 距离字段别名
    }
})

When you see the data that meets the conditions in the result, there is an extra field distance. The alias you just set represents the distance between two points.

{ "_id" : ObjectId("5e96a5c91b8d4ce765381e58"), "name" : "hotel10", "location" : [ 116.000821, 28.68129 ], "distance" : 135.60095397487655 }
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e51"), "name" : "hotel3", "location" : [ 115.999967, 28.679743 ], "distance" : 233.71915803517447 }
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e50"), "name" : "hotel2", "location" : [ 116.000093, 28.679402 ], "distance" : 273.26317035334176 }
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e57"), "name" : "hotel9", "location" : [ 116.002384, 28.683865 ], "distance" : 357.5791936927476 }
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e52"), "name" : "hotel4", "location" : [ 115.995593, 28.681632 ], "distance" : 388.62555058249967 }
{ "_id" : ObjectId("5e96a5c91b8d4ce765381e4f"), "name" : "hotel1", "location" : [ 115.993121, 28.676436 ], "distance" : 868.6740526419927 }

to sum up

The focus of this article is not on specific implementation, it is to provide you with some design ideas. You may not have a deep understanding of a certain technology during the interview, but if your knowledge is wide, you can say a variety of designs If you can talk openly, the interviewer will be greatly appreciated, and the probability of getting an offer will be much higher. Moreover “附近的人”, there are many scenarios where functions are used, especially the e-commerce platform is more widely used, so students who want to enter the big factory, such knowledge points should still be understood.


Code implementation draws a big brother open source project, there are demo the first three implementations, little interested partners can learn about, GitHub address: https://github.com/larscheng/larscheng-learning-demo/tree/master/NearbySearch.


Small benefits:

Recently, many friends around me have been interviewing. I have collected some Java architecture, interview materials, and some paid courses. Hush ~, free for friends. If you need it, you can pay attention to my official account , reply [ 666 ], and collect it without routine

Guess you like

Origin www.cnblogs.com/lonelyxmas/p/12709844.html