# 4 kinds of LBS “nearby people” implementation scheme, everyone can understand

Time：2020-11-13

Yesterday, a buddies of the official account and I discussed an interview question. Personally, it was more meaningful. I arranged it here to share it with you. The interview question is relatively simple: “let you realize the function of a nearby person, what plan do you have?” In fact, this question is mainly to investigate the breadth of technology. This paper introduces several schemes to give you some ideas, so as to avoid affecting the interview result due to the language jam in the interview process. If there is any laxity, please kindly point it out!

`"People in the neighborhood"`It is more commonly used in functional life, such as restaurants near takeout app and sharing cars near bike app. Since the probability of being asked in a common interview is very high, the following analysis is based on`Mysql database``Redis``MongoDB`The function of “people nearby” is realized.

The popular science: to mark a position in the world, the general practice is to use longitude and latitude. The range of longitude is (- 180, 180), and the range of latitude is (- 90, 90). The latitude is bounded by the equator, north is positive and south is negative, and the longitude is bounded by the prime meridian (Greenwich Observatory), positive in the East and negative in the West. For example, the longitude and latitude (116.49141, 40.01229) of the Motorola building in Wangjing are all positive numbers, because China is located in the Northeast hemisphere.

#### 1、 “People nearby” principle

`"People in the neighborhood"`That is often said`LBS`Based on the current location services, it provides value-added services for users based on their current location.

The core idea of “people nearby” is as follows:

1. Search nearby users with “I” as the center
2. Calculate the distance between others and me based on my current geographical location
3. According to the distance between “I” and others, the nearest user or store is selected

#### 2、 What is geohash algorithm?

I’m talking about it`"People in the neighborhood"`Before the specific implementation of the function, let’s have a look at it`GeoHash`Algorithm, because it will always be dealt with later. The best way to locate a position is to use`Longitude and latitude`Identification, but`Longitude and latitude`It is two-dimensional, and it is very troublesome to calculate the position`Longitude and latitude`It’s much easier to compare data into one-dimensional data, so`GeoHash`The algorithm came into being.

`GeoHash`The algorithm converts the two-dimensional longitude and latitude into a string, for example: 9 in the figure below`GeoHash`The string represents nine regions, and each string represents a rectangular region. The other points (longitude and latitude) in this rectangular area use the same one`GeoHash`String representation.

such as`WX4ER`Due to the user’s data within this area of the user’s search`GeoHash`All strings are`WX4ER`Therefore, we can put the`WX4ER`As`key`, restaurant information as`value`If you do not use the`GeoHash`In this algorithm, users in the region request restaurant data, and the longitude and latitude of users are different, so caching is not only troublesome, but also a huge amount of data.

`GeoHash`The longer the string is, the more accurate the position is. The longer the string is, the smaller the error in distance is. Below`geohash`Code precision table:

Length, width and height of geohash codes
——– | —– | —– |
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 string is, the closer the distance is, and the more string prefix matches, the closer the distance is. For example, the longitude and latitude below represent three restaurants close to each other.

Merchant | latitude and longitude | geohash string
——– | —– | —– |
Chuanchuanxiang | 116.402843,39.999375 | wx4er9v
Hot pot | 116.3967,39.99932 | wx4rtk
Barbecue | 116.40382,39.918118 | wx4erfe

Let’s have a simple understanding of what is`GeoHash`Easy to expand the content,`GeoHash`The content of the algorithm is relatively high and deep, and the interested partners can dig deeply by themselves. It doesn’t take too much space here (in fact, I know too shallow, cry and haw ~).

#### 3、 Based on MySQL

This approach is purely based on`mysql`Implemented, not used`GeoHash`Algorithm.

##### 1. Design ideas

Taking the user as the center, assuming a distance of 500 meters as the radius, draw a circle. All users in this circular area are “people nearby” who meet the user’s requirements. However, there is a problem that the circle has radian. It is too difficult to search the circular area directly. It is impossible to search directly with longitude and latitude.

But if there is a square on the circular jacket, it is easy to search the user information in the square by obtaining the maximum and minimum values of user longitude and latitude (longitude, latitude + distance) and then using the maximum and minimum values as the filtering criteria.

So here comes the question,Does more come out area swollen do?

Let’s analyze. The distance from the user in this part of the area to the circle must be larger than the radius of the circle. Then we calculate the distance between the user’s center point and all users in the square, and screen out all users whose distance is less than or equal to the radius, and all users in the circular area meet the requirements`"People in the neighborhood"`

Purely based on`mysql`realization`"People in the neighborhood"`The advantages are obvious, that is, it is simple, as long as you create a table to store the longitude and latitude information of users. The disadvantage is also obvious, which requires a lot of calculation of the distance between two points, which greatly affects the performance.

##### 3. Implementation

Create a simple table to store the latitude and longitude attributes of users.

``````CREATE TABLE `nearby_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`Name ` varchar (255) default null comment 'name',
`Longitude ` double default null comment 'longitude',
`Latitude ` double default null comment 'latitude',
`create_ time` datetime DEFAULT NULL ON UPDATE CURRENT_ Timestamp comment 'creation time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;``````

To calculate the distance between two points, a tripartite library is used. After all, the wheel made by ourselves is not particularly round, and it may be square, ha ha ha ha~

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

After the outer square is obtained, the users in the square area are searched by the maximum and minimum longitude and latitude values of the square, and then the users who exceed the specified distance are eliminated`people nearby`

``````private SpatialContext spatialContext = SpatialContext.GEO;

/**
*Get people in the neighborhood x meters
*
*@ param distance search range unit: km
*@ param userlng longitude of current user
*@ param userlat latitude of current user
*/
@GetMapping("/nearby")
public String nearBySearch(@RequestParam("distance") double distance,
@RequestParam("userLng") double userLng,
@RequestParam("userLat") double userLat) {
//1. Get the circumscribed square
Rectangle rectangle = getRectangle(distance, userLng, userLat);
//2. Get all users in the square
List<User> users = userMapper.selectUser(rectangle.getMinX(), rectangle.getMaxX(), rectangle.getMinY(), rectangle.getMaxY());
//3. Remove redundant users whose radius exceeds the specified distance
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);
}``````

Because the sorting of distances between users is implemented in business code, you can see that SQL statements are 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>
``````

#### 4、 MySQL + geohash

##### 1. Design ideas

The design idea of this method is simpler. When storing the location information of users, the corresponding values are calculated according to the user’s longitude and latitude attributes`geohash`character string.be careful: in calculation`geohash`String, you need to specify`geohash`The precision of the string, that is`geohash`The length of the string,See above`geohash`Precision table

When you need to get`people nearby`, only the current user is required`geohash`String, database through`WHERE geohash Like 'geocode%`‘To find out`geohash`Then calculate the distance between the current user and the searched user, and filter out all the users whose distance is less than or equal to the specified distance (500 meters nearby), i.e`people nearby`

utilize` GeoHash`Algorithm implementation`"People in the neighborhood"`There is a problem because`geohash`The algorithm divides the map into rectangles and encodes each rectangle`geohash`character string. But my current point is very close to the adjacent point, but we are in two areas respectively. The point clearly in front of me cannot be found. It is dark under the light.

How to solve this problem?

In order to avoid similar two adjacent points in different regions, we need to obtain the current point at the same time（`WX4G0`）Near the area`8`Regional`geohash`Code, screening and comparison.

##### 3. Implementation

We also need to design a table to store the longitude and latitude information of users, but the difference is to add one more`geo_code`Field to store the geohash string. This field is calculated by the user’s latitude and longitude attributes. Frequently used fields are recommended to be indexed.

``````CREATE TABLE `nearby_user_geohash` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`Name ` varchar (255) default null comment 'name',
`Longitude ` double default null comment 'longitude',
`Latitude ` double default null comment 'latitude',
`geo_ Code ` varchar (64) default null comment 'geohash code calculated by latitude and longitude',
`create_ time` datetime DEFAULT NULL ON UPDATE CURRENT_ Timestamp comment 'creation time',
PRIMARY KEY (`id`),
KEY `index_geo_hash` (`geo_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;``````

Firstly, according to the user’s latitude and longitude information, the user’s coordinates are calculated after the specified precision`geoHash`Code, and then get 8 directions around the user`geoHash`Code searches for users in the database, and finally filters out users beyond the given distance (within 500 meters).

``````private SpatialContext spatialContext = SpatialContext.GEO;

/***
* @return
*/
public boolean add(@RequestBody UserGeohash user) {
//The default precision is 12 bits
String geoHashCode = GeohashUtils.encodeLatLon(user.getLatitude(),user.getLongitude());
return userGeohashService.save(user.setGeoCode(geoHashCode).setCreateTime(LocalDateTime.now()));
}

/**
*Get people in the specified range nearby
*
*@ param distance distance (distance of nearby users) unit km
*The precision of @ param len geohash (several bit string)
*@ param userlng longitude of current user
*@ param userlat latitude of current user
* @return json
*/
@GetMapping("/nearby")
public String nearBySearch(@RequestParam("distance") double distance,
@RequestParam("len") int len,
@RequestParam("userLng") double userLng,
@RequestParam("userLat") double userLat) {

//1. According to the required range, determine the accuracy of geohash code, and obtain the geohash code of current user coordinates
GeoHash geoHash = GeoHash.withCharacterPrecision(userLat, userLng, len);
//2. Get the geohash codes of 8 directions around the user

QueryWrapper<UserGeohash> queryWrapper = new QueryWrapper<UserGeohash>()
.likeRight("geo_code",geoHash.toBase32());

//3. Match the geohash code with specified precision
List<UserGeohash> users = userGeohashService.list(queryWrapper);
//4. Filter those that exceed the distance
users = users.stream()
.filter(a ->getDistance(a.getLongitude(),a.getLatitude(),userLng,userLat)<= distance)
.collect(Collectors.toList());
return JSON.toJSONString(users);
}

/***
*The distance between two points in a sphere
*@ param longitude 1
*@ param latitude 1
*@ param userlng longitude 2
*@ param userlat latitude 2
*@ return return distance, in 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;
}``````

#### 5、 Redis + geohash

`Redis 3.2`After version, based on`geohash`And data structure`Zset`It provides geographic location related functions. Through the top two`mysql`The implementation of the discovery,`people nearby`The function is obviously read more and write less scenes, so use`redis`Performance will be greatly improved.

##### 1. Design ideas

`redis`realization`people nearby`The function is mainly through`Geo`Six commands for the module.

• `GEOADD`: add the given location object (latitude, longitude, name) to the specified key;
• `GEOPOS`: returns the location (longitude and latitude) of all the given location objects from the key;
• `GEODIST`: returns the distance between two given positions;
• `GEOHASH`: returns the geohash representation of one or more location objects;
• `GEORADIUS`: return all position objects in the target set whose distance from the center does not exceed the given maximum distance with the given longitude and latitude as the center;
• `GEORADIUSBYMEMBER`: returns all the position objects whose distance does not exceed the given maximum distance with the given position object as the center.

with`GEOADD`Command and`GEORADIUS`Simple example of command:

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

Among them,`key`Is the collection name,`member`Is the object corresponding to the latitude and longitude.

`GEOADD`Add location information of “hot pot shop” of multiple merchants:

``Geoadd Hotel 119.98866180732716 30.27465803229662 hot pot restaurant``

`GEORADIUS`According to the given longitude and latitude as the center, all position objects in the target set whose distance from the center does not exceed the given maximum distance (within 500m) are obtained`"People in the neighborhood"`

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

Scope unit:`m` | `km` | `ft` | `mi`–>Meters, kilometers, feet, miles.

• `WITHDIST`: returns the distance between the position object and the center at the same time. The unit of distance is consistent with the range unit given by the user.
• `WITHCOORD`: returns the longitude and dimension of the location object.
• `WITHHASH`: returns the ordered set score of the location object in the form of a 52 bit signed integer. This option is mainly used for the underlying application or debugging, but it has little effect in practice.
• `ASC | DESC`: return position object element from near to far | return 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 between the return result and the center point to the specified key.

For example, the following command: get all hotels within 500 meters of the current location.

``GEORADIUS hotel 119.98866180732716    30.27465803229662 500 m WITHCOORD``

`Redis`Internal use of ordered sets（`zset`）Save the user’s location information,`zset`Each element in the is an object with a location`score`The value is 52 bits calculated by latitude and longitude`geohash`Value.

`redis`realization`people nearby`It has high efficiency, simple integration, and also supports distance sorting. However, there are some errors in the results. In order to make the results more accurate, it is necessary to manually calculate the distance between the user center position and other user locations, and then screen again.

##### 3. Implementation

Here’s what it looks like`Java` `redis`Implementation version, the code is very concise.

``````@Autowired
private RedisTemplate<String, Object> redisTemplate;

//Key used by geo related commands
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;
}

/**
*Get users in the specified range nearby according to the current location
*@ param distance specifies the range unit km, which can be determined according to {@ link org.springframework.data . geo.Metrics }Set it up
*@ param userlng user longitude
*@ param userlat user latitude
* @return
*/
public String nearBySearch(double distance, double userLng, double userLat) {
List<User> users = new ArrayList<>();
//1. Georadius obtains information in the vicinity
GeoResults<RedisGeoCommands.GeoLocation<Object>> reslut =
new Circle(new Point(userLng, userLat), new Distance(distance, Metrics.KILOMETERS)),
.includeDistance()
.includeCoordinates().sortAscending());
//2. Collect information and save it in the list
List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = reslut.getContent();
//3. Filter out the data beyond the distance
new User().setDistance(a.getDistance().getValue())
.setLatitude(a.getContent().getPoint().getX())
.setLongitude(a.getContent().getPoint().getY())));
return JSON.toJSONString(users);
}``````

#### 6、 Mongodb + 2D index

##### 1. Design ideas

`MongoDB`The realization of nearby people is mainly through its two kinds of geospatial index`2dsphere`and`2d`。 The bottom layer of the two indexes is still based on`Geohash`To build. But with the international common`Geohash`There are also some differences. Please refer to the official documents for details.

`2dsphere`The index only supports geometry query of spherical surface.

`2d`The index supports flat geometry and some spherical queries. although`2d`Indexes support some spherical queries, but`2d`Errors may occur when indexing these spherical queries. Therefore, spherical query should be selected as much as possible`2dsphere`Indexes.

Although the methods of the two indexes are different, as long as the coordinate span is not too large, the distance difference between the two indexes can be ignored.

##### 2. Implementation

First insert a batch of location data into the`MongoDB``collection`Name it`hotel`, equivalent to`MySQL`Table name of. Two fields`name`name,`location`It is a pair of longitude and latitude data.

``````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 give`location`Field creates a`2d`Index, index accuracy through`bits`To specify,`bits`The larger the index, the more accurate the index is.

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

use`geoNear `Order a test,`near`Current coordinates (longitude, latitude),`spherical`Whether to calculate the spherical distance,`distanceMultiplier`Radius of the earth. The unit is meters. The default value is 6378137,`maxDistance`Filter conditions (users within the specified distance), open radian must be divided into`distanceMultiplier``distanceField`The calculated distance between two points, field alias (random name).

``````db.hotel.aggregate({
\$geoNear:{
Near: [115.999567,28.681813], // current coordinates
Spherical: true, // calculate spherical distance
Distancemultiplier: 6378137, // the radius of the earth in meters, then the division record is also meter
Maxdistance: 2000 / 6378137, // within 2000m, radian is required
Distancefield: "distance" // distance field alias
}
})``````

You can see that there is qualified data in the result, and there is one more 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 }``````

#### summary

The focus of this paper is not on the specific implementation, but to provide some design ideas. During the interview, you may not have a deep understanding of a certain technology, but if you have a wide range of knowledge, you can say a variety of design ideas from many aspects, and you can talk with a lot, then you will give the interviewers great favor and the probability of getting an offer will be much higher. and`"People in the neighborhood"`There are many scenarios for the use of functions, especially for e-commerce platforms. Therefore, students who want to enter large factories should have some knowledge about this kind of knowledge.

Code implementation refers to an open source project of a big guy. Here are demos of the first three implementation methods. Interested partners can learn about GitHub address:`https://github.com/larscheng/larscheng-learning-demo/tree/master/NearbySearch`，。

## Summary of recent use of gin

Recently, a new project is developed by using gin. Some problems are encountered in the process. To sum up, as a note, I hope it can help you. Cross domain problems Middleware: func Cors() gin.HandlerFunc { return func(c *gin.Context) { //Here you can use * or the domain name you specify c.Header(“Access-Control-Allow-Origin”, “*”) //Allow header […]