1. MongoDB地理空间索引概述

MongoDB地理空间索引是专门用于处理地理位置数据的索引类型,支持经纬度坐标的存储和查询。在Java应用中,合理使用地理空间索引可以实现附近商家查询、位置搜索、距离计算等功能。本文将详细介绍MongoDB地理空间索引的各种类型、创建方法、查询技巧以及在Java实战中的应用。

1.1 地理空间索引的核心作用

  1. 位置存储: 高效存储经纬度坐标数据
  2. 附近查询: 快速查找指定范围内的地理位置
  3. 距离计算: 精确计算两点之间的距离
  4. 范围查询: 支持圆形、矩形、多边形区域查询
  5. 排序优化: 按距离排序查询结果

1.2 地理空间索引类型

  • 2d索引: 适用于平面坐标系统,支持简单的地理位置查询
  • 2dsphere索引: 适用于球面坐标系统,支持复杂的地理位置查询
  • GeoJSON格式: 标准的地理位置数据格式
  • 坐标系统: 支持WGS84等标准坐标系统

2. 地理空间索引基础操作

2.1 2d索引操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
* MongoDB 2d索引操作
* @author 运维实战
*/
@Component
public class Geo2dIndexService {

@Autowired
private MongoTemplate mongoTemplate;

/**
* 创建2d索引
* @param collectionName 集合名称
* @param fieldName 字段名称
*/
public void create2dIndex(String collectionName, String fieldName) {
try {
// 创建2d索引
Index index = new Index().on(fieldName, Sort.Direction.ASC).named("2d_" + fieldName);
mongoTemplate.indexOps(collectionName).ensureIndex(index);

System.out.println("成功创建2d索引: " + fieldName);
} catch (Exception e) {
System.err.println("创建2d索引失败: " + e.getMessage());
}
}

/**
* 创建位置2d索引示例
* @param collectionName 集合名称
*/
public void createLocation2dIndex(String collectionName) {
try {
// 创建位置2d索引
Index index = new Index().on("location", Sort.Direction.ASC).named("location_2d");
mongoTemplate.indexOps(collectionName).ensureIndex(index);

System.out.println("成功创建位置2d索引");
} catch (Exception e) {
System.err.println("创建位置2d索引失败: " + e.getMessage());
}
}

/**
* 插入地理位置数据
* @param collectionName 集合名称
* @param name 地点名称
* @param longitude 经度
* @param latitude 纬度
*/
public void insertLocation(String collectionName, String name, double longitude, double latitude) {
try {
// 创建地理位置文档
Document locationDoc = new Document();
locationDoc.put("name", name);
locationDoc.put("location", new double[]{longitude, latitude});
locationDoc.put("createdAt", new Date());

// 插入文档
mongoTemplate.insert(locationDoc, collectionName);

System.out.println("成功插入地理位置数据: " + name);
} catch (Exception e) {
System.err.println("插入地理位置数据失败: " + e.getMessage());
}
}

/**
* 查询附近位置
* @param collectionName 集合名称
* @param longitude 经度
* @param latitude 纬度
* @param maxDistance 最大距离(度)
* @return 附近位置列表
*/
public List<Document> findNearby(String collectionName, double longitude, double latitude, double maxDistance) {
try {
// 创建附近查询
Query query = new Query();
query.addCriteria(Criteria.where("location").near(new Point(longitude, latitude)).maxDistance(maxDistance));

// 执行查询
List<Document> results = mongoTemplate.find(query, Document.class, collectionName);

System.out.println("找到 " + results.size() + " 个附近位置");
return results;
} catch (Exception e) {
System.err.println("查询附近位置失败: " + e.getMessage());
return new ArrayList<>();
}
}
}

2.2 2dsphere索引操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
* MongoDB 2dsphere索引操作
* @author 运维实战
*/
@Component
public class Geo2dsphereIndexService {

@Autowired
private MongoTemplate mongoTemplate;

/**
* 创建2dsphere索引
* @param collectionName 集合名称
* @param fieldName 字段名称
*/
public void create2dsphereIndex(String collectionName, String fieldName) {
try {
// 创建2dsphere索引
Index index = new Index().on(fieldName, Sort.Direction.ASC).named("2dsphere_" + fieldName);
mongoTemplate.indexOps(collectionName).ensureIndex(index);

System.out.println("成功创建2dsphere索引: " + fieldName);
} catch (Exception e) {
System.err.println("创建2dsphere索引失败: " + e.getMessage());
}
}

/**
* 创建位置2dsphere索引示例
* @param collectionName 集合名称
*/
public void createLocation2dsphereIndex(String collectionName) {
try {
// 创建位置2dsphere索引
Index index = new Index().on("location", Sort.Direction.ASC).named("location_2dsphere");
mongoTemplate.indexOps(collectionName).ensureIndex(index);

System.out.println("成功创建位置2dsphere索引");
} catch (Exception e) {
System.err.println("创建位置2dsphere索引失败: " + e.getMessage());
}
}

/**
* 插入GeoJSON格式地理位置数据
* @param collectionName 集合名称
* @param name 地点名称
* @param longitude 经度
* @param latitude 纬度
*/
public void insertGeoJSONLocation(String collectionName, String name, double longitude, double latitude) {
try {
// 创建GeoJSON格式的地理位置文档
Document locationDoc = new Document();
locationDoc.put("name", name);
locationDoc.put("location", new Document("type", "Point")
.append("coordinates", new double[]{longitude, latitude}));
locationDoc.put("createdAt", new Date());

// 插入文档
mongoTemplate.insert(locationDoc, collectionName);

System.out.println("成功插入GeoJSON地理位置数据: " + name);
} catch (Exception e) {
System.err.println("插入GeoJSON地理位置数据失败: " + e.getMessage());
}
}

/**
* 查询附近位置(2dsphere)
* @param collectionName 集合名称
* @param longitude 经度
* @param latitude 纬度
* @param maxDistance 最大距离(米)
* @return 附近位置列表
*/
public List<Document> findNearby2dsphere(String collectionName, double longitude, double latitude, double maxDistance) {
try {
// 创建附近查询
Query query = new Query();
query.addCriteria(Criteria.where("location").near(new Point(longitude, latitude)).maxDistance(maxDistance));

// 执行查询
List<Document> results = mongoTemplate.find(query, Document.class, collectionName);

System.out.println("找到 " + results.size() + " 个附近位置");
return results;
} catch (Exception e) {
System.err.println("查询附近位置失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 查询指定范围内的位置
* @param collectionName 集合名称
* @param longitude 经度
* @param latitude 纬度
* @param minDistance 最小距离(米)
* @param maxDistance 最大距离(米)
* @return 范围内位置列表
*/
public List<Document> findWithinDistance(String collectionName, double longitude, double latitude,
double minDistance, double maxDistance) {
try {
// 创建距离范围查询
Query query = new Query();
query.addCriteria(Criteria.where("location")
.near(new Point(longitude, latitude))
.minDistance(minDistance)
.maxDistance(maxDistance));

// 执行查询
List<Document> results = mongoTemplate.find(query, Document.class, collectionName);

System.out.println("找到 " + results.size() + " 个范围内位置");
return results;
} catch (Exception e) {
System.err.println("查询范围内位置失败: " + e.getMessage());
return new ArrayList<>();
}
}
}

2.3 地理位置查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/**
* MongoDB地理位置查询操作
* @author 运维实战
*/
@Component
public class GeoQueryService {

@Autowired
private MongoTemplate mongoTemplate;

/**
* 查询圆形区域内的位置
* @param collectionName 集合名称
* @param longitude 经度
* @param latitude 纬度
* @param radius 半径(米)
* @return 圆形区域内位置列表
*/
public List<Document> findWithinCircle(String collectionName, double longitude, double latitude, double radius) {
try {
// 创建圆形查询
Query query = new Query();
query.addCriteria(Criteria.where("location")
.within(new Circle(new Point(longitude, latitude), radius)));

// 执行查询
List<Document> results = mongoTemplate.find(query, Document.class, collectionName);

System.out.println("找到 " + results.size() + " 个圆形区域内位置");
return results;
} catch (Exception e) {
System.err.println("查询圆形区域内位置失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 查询矩形区域内的位置
* @param collectionName 集合名称
* @param minLongitude 最小经度
* @param minLatitude 最小纬度
* @param maxLongitude 最大经度
* @param maxLatitude 最大纬度
* @return 矩形区域内位置列表
*/
public List<Document> findWithinBox(String collectionName, double minLongitude, double minLatitude,
double maxLongitude, double maxLatitude) {
try {
// 创建矩形查询
Query query = new Query();
query.addCriteria(Criteria.where("location")
.within(new Box(new Point(minLongitude, minLatitude), new Point(maxLongitude, maxLatitude))));

// 执行查询
List<Document> results = mongoTemplate.find(query, Document.class, collectionName);

System.out.println("找到 " + results.size() + " 个矩形区域内位置");
return results;
} catch (Exception e) {
System.err.println("查询矩形区域内位置失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 查询多边形区域内的位置
* @param collectionName 集合名称
* @param coordinates 多边形坐标点列表
* @return 多边形区域内位置列表
*/
public List<Document> findWithinPolygon(String collectionName, List<Point> coordinates) {
try {
// 创建多边形查询
Query query = new Query();
query.addCriteria(Criteria.where("location")
.within(new Polygon(coordinates)));

// 执行查询
List<Document> results = mongoTemplate.find(query, Document.class, collectionName);

System.out.println("找到 " + results.size() + " 个多边形区域内位置");
return results;
} catch (Exception e) {
System.err.println("查询多边形区域内位置失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 按距离排序查询
* @param collectionName 集合名称
* @param longitude 经度
* @param latitude 纬度
* @param limit 限制数量
* @return 按距离排序的位置列表
*/
public List<Document> findNearest(String collectionName, double longitude, double latitude, int limit) {
try {
// 创建最近距离查询
Query query = new Query();
query.addCriteria(Criteria.where("location").near(new Point(longitude, latitude)));
query.limit(limit);

// 执行查询
List<Document> results = mongoTemplate.find(query, Document.class, collectionName);

System.out.println("找到 " + results.size() + " 个最近位置");
return results;
} catch (Exception e) {
System.err.println("查询最近位置失败: " + e.getMessage());
return new ArrayList<>();
}
}
}

3. 距离计算和工具类

3.1 距离计算工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* 距离计算工具类
* @author 运维实战
*/
@Component
public class DistanceCalculator {

private static final double EARTH_RADIUS = 6371000; // 地球半径(米)

/**
* 计算两点之间的距离(米)
* @param lon1 第一个点的经度
* @param lat1 第一个点的纬度
* @param lon2 第二个点的经度
* @param lat2 第二个点的纬度
* @return 距离(米)
*/
public double calculateDistance(double lon1, double lat1, double lon2, double lat2) {
try {
// 将角度转换为弧度
double lat1Rad = Math.toRadians(lat1);
double lat2Rad = Math.toRadians(lat2);
double deltaLatRad = Math.toRadians(lat2 - lat1);
double deltaLonRad = Math.toRadians(lon2 - lon1);

// 使用Haversine公式计算距离
double a = Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) +
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
Math.sin(deltaLonRad / 2) * Math.sin(deltaLonRad / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

return EARTH_RADIUS * c;
} catch (Exception e) {
System.err.println("计算距离失败: " + e.getMessage());
return 0.0;
}
}

/**
* 计算两点之间的距离(公里)
* @param lon1 第一个点的经度
* @param lat1 第一个点的纬度
* @param lon2 第二个点的经度
* @param lat2 第二个点的纬度
* @return 距离(公里)
*/
public double calculateDistanceInKm(double lon1, double lat1, double lon2, double lat2) {
return calculateDistance(lon1, lat1, lon2, lat2) / 1000.0;
}

/**
* 计算两点之间的距离(英里)
* @param lon1 第一个点的经度
* @param lat1 第一个点的纬度
* @param lon2 第二个点的经度
* @param lat2 第二个点的纬度
* @return 距离(英里)
*/
public double calculateDistanceInMiles(double lon1, double lat1, double lon2, double lat2) {
return calculateDistance(lon1, lat1, lon2, lat2) * 0.000621371;
}

/**
* 检查点是否在指定范围内
* @param lon1 第一个点的经度
* @param lat1 第一个点的纬度
* @param lon2 第二个点的经度
* @param lat2 第二个点的纬度
* @param radius 半径(米)
* @return 是否在范围内
*/
public boolean isWithinRadius(double lon1, double lat1, double lon2, double lat2, double radius) {
double distance = calculateDistance(lon1, lat1, lon2, lat2);
return distance <= radius;
}
}

3.2 地理位置工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* 地理位置工具类
* @author 运维实战
*/
@Component
public class GeoLocationUtils {

@Autowired
private DistanceCalculator distanceCalculator;

/**
* 创建地理位置文档
* @param name 地点名称
* @param longitude 经度
* @param latitude 纬度
* @param address 地址
* @return 地理位置文档
*/
public Document createLocationDocument(String name, double longitude, double latitude, String address) {
try {
Document locationDoc = new Document();
locationDoc.put("name", name);
locationDoc.put("location", new Document("type", "Point")
.append("coordinates", new double[]{longitude, latitude}));
locationDoc.put("address", address);
locationDoc.put("longitude", longitude);
locationDoc.put("latitude", latitude);
locationDoc.put("createdAt", new Date());

return locationDoc;
} catch (Exception e) {
System.err.println("创建地理位置文档失败: " + e.getMessage());
return null;
}
}

/**
* 验证经纬度坐标
* @param longitude 经度
* @param latitude 纬度
* @return 是否有效
*/
public boolean isValidCoordinate(double longitude, double latitude) {
return longitude >= -180 && longitude <= 180 && latitude >= -90 && latitude <= 90;
}

/**
* 格式化距离显示
* @param distance 距离(米)
* @return 格式化的距离字符串
*/
public String formatDistance(double distance) {
if (distance < 1000) {
return String.format("%.0f米", distance);
} else {
return String.format("%.2f公里", distance / 1000.0);
}
}

/**
* 计算边界框
* @param longitude 经度
* @param latitude 纬度
* @param radius 半径(米)
* @return 边界框
*/
public Map<String, Double> calculateBoundingBox(double longitude, double latitude, double radius) {
try {
// 计算经纬度偏移量
double latOffset = radius / 111000.0; // 1度纬度约等于111公里
double lonOffset = radius / (111000.0 * Math.cos(Math.toRadians(latitude)));

Map<String, Double> boundingBox = new HashMap<>();
boundingBox.put("minLongitude", longitude - lonOffset);
boundingBox.put("maxLongitude", longitude + lonOffset);
boundingBox.put("minLatitude", latitude - latOffset);
boundingBox.put("maxLatitude", latitude + latOffset);

return boundingBox;
} catch (Exception e) {
System.err.println("计算边界框失败: " + e.getMessage());
return new HashMap<>();
}
}
}

4. 实战应用示例

4.1 商家管理系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
* 商家管理系统地理位置索引
* @author 运维实战
*/
@Component
public class MerchantGeoIndexService {

@Autowired
private MongoTemplate mongoTemplate;

@Autowired
private DistanceCalculator distanceCalculator;

/**
* 配置商家地理位置索引
*/
@PostConstruct
public void configureMerchantGeoIndexes() {
String collectionName = "merchants";

try {
// 1. 创建位置2dsphere索引
Index locationIndex = new Index()
.on("location", Sort.Direction.ASC)
.named("merchant_location_2dsphere");
mongoTemplate.indexOps(collectionName).ensureIndex(locationIndex);

// 2. 创建商家名称索引
Index nameIndex = new Index()
.on("name", Sort.Direction.ASC)
.named("merchant_name");
mongoTemplate.indexOps(collectionName).ensureIndex(nameIndex);

// 3. 创建分类索引
Index categoryIndex = new Index()
.on("category", Sort.Direction.ASC)
.named("merchant_category");
mongoTemplate.indexOps(collectionName).ensureIndex(categoryIndex);

// 4. 创建状态索引
Index statusIndex = new Index()
.on("status", Sort.Direction.ASC)
.named("merchant_status");
mongoTemplate.indexOps(collectionName).ensureIndex(statusIndex);

// 5. 创建复合索引:分类 + 状态
Index categoryStatusIndex = new Index()
.on("category", Sort.Direction.ASC)
.on("status", Sort.Direction.ASC)
.named("merchant_category_status");
mongoTemplate.indexOps(collectionName).ensureIndex(categoryStatusIndex);

System.out.println("商家地理位置索引配置完成");
} catch (Exception e) {
System.err.println("商家地理位置索引配置失败: " + e.getMessage());
}
}

/**
* 添加商家
* @param name 商家名称
* @param longitude 经度
* @param latitude 纬度
* @param address 地址
* @param category 分类
*/
public void addMerchant(String name, double longitude, double latitude, String address, String category) {
try {
// 验证坐标
if (!isValidCoordinate(longitude, latitude)) {
throw new IllegalArgumentException("无效的经纬度坐标");
}

// 创建商家文档
Document merchantDoc = new Document();
merchantDoc.put("name", name);
merchantDoc.put("location", new Document("type", "Point")
.append("coordinates", new double[]{longitude, latitude}));
merchantDoc.put("address", address);
merchantDoc.put("longitude", longitude);
merchantDoc.put("latitude", latitude);
merchantDoc.put("category", category);
merchantDoc.put("status", "active");
merchantDoc.put("createdAt", new Date());

// 插入文档
mongoTemplate.insert(merchantDoc, "merchants");

System.out.println("成功添加商家: " + name);
} catch (Exception e) {
System.err.println("添加商家失败: " + e.getMessage());
}
}

/**
* 查找附近商家
* @param longitude 经度
* @param latitude 纬度
* @param radius 半径(米)
* @param category 分类(可选)
* @return 附近商家列表
*/
public List<Document> findNearbyMerchants(double longitude, double latitude, double radius, String category) {
try {
// 创建查询
Query query = new Query();
query.addCriteria(Criteria.where("location")
.near(new Point(longitude, latitude))
.maxDistance(radius));
query.addCriteria(Criteria.where("status").is("active"));

if (category != null && !category.isEmpty()) {
query.addCriteria(Criteria.where("category").is(category));
}

// 执行查询
List<Document> results = mongoTemplate.find(query, Document.class, "merchants");

// 计算距离并添加到结果中
for (Document merchant : results) {
Document location = (Document) merchant.get("location");
List<Double> coordinates = (List<Double>) location.get("coordinates");
double merchantLon = coordinates.get(0);
double merchantLat = coordinates.get(1);

double distance = distanceCalculator.calculateDistance(longitude, latitude, merchantLon, merchantLat);
merchant.put("distance", distance);
}

// 按距离排序
results.sort((a, b) -> {
double distanceA = (Double) a.get("distance");
double distanceB = (Double) b.get("distance");
return Double.compare(distanceA, distanceB);
});

System.out.println("找到 " + results.size() + " 个附近商家");
return results;
} catch (Exception e) {
System.err.println("查找附近商家失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 验证坐标
* @param longitude 经度
* @param latitude 纬度
* @return 是否有效
*/
private boolean isValidCoordinate(double longitude, double latitude) {
return longitude >= -180 && longitude <= 180 && latitude >= -90 && latitude <= 90;
}
}

4.2 用户位置服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* 用户位置服务
* @author 运维实战
*/
@Component
public class UserLocationService {

@Autowired
private MongoTemplate mongoTemplate;

@Autowired
private DistanceCalculator distanceCalculator;

/**
* 配置用户位置索引
*/
@PostConstruct
public void configureUserLocationIndexes() {
String collectionName = "user_locations";

try {
// 1. 创建位置2dsphere索引
Index locationIndex = new Index()
.on("location", Sort.Direction.ASC)
.named("user_location_2dsphere");
mongoTemplate.indexOps(collectionName).ensureIndex(locationIndex);

// 2. 创建用户ID索引
Index userIdIndex = new Index()
.on("userId", Sort.Direction.ASC)
.named("user_id");
mongoTemplate.indexOps(collectionName).ensureIndex(userIdIndex);

// 3. 创建时间索引
Index timeIndex = new Index()
.on("timestamp", Sort.Direction.DESC)
.named("user_timestamp");
mongoTemplate.indexOps(collectionName).ensureIndex(timeIndex);

// 4. 创建复合索引:用户ID + 时间
Index userTimeIndex = new Index()
.on("userId", Sort.Direction.ASC)
.on("timestamp", Sort.Direction.DESC)
.named("user_id_timestamp");
mongoTemplate.indexOps(collectionName).ensureIndex(userTimeIndex);

System.out.println("用户位置索引配置完成");
} catch (Exception e) {
System.err.println("用户位置索引配置失败: " + e.getMessage());
}
}

/**
* 更新用户位置
* @param userId 用户ID
* @param longitude 经度
* @param latitude 纬度
* @param address 地址
*/
public void updateUserLocation(String userId, double longitude, double latitude, String address) {
try {
// 验证坐标
if (!isValidCoordinate(longitude, latitude)) {
throw new IllegalArgumentException("无效的经纬度坐标");
}

// 创建用户位置文档
Document locationDoc = new Document();
locationDoc.put("userId", userId);
locationDoc.put("location", new Document("type", "Point")
.append("coordinates", new double[]{longitude, latitude}));
locationDoc.put("longitude", longitude);
locationDoc.put("latitude", latitude);
locationDoc.put("address", address);
locationDoc.put("timestamp", new Date());

// 插入文档
mongoTemplate.insert(locationDoc, "user_locations");

System.out.println("成功更新用户位置: " + userId);
} catch (Exception e) {
System.err.println("更新用户位置失败: " + e.getMessage());
}
}

/**
* 查找附近用户
* @param longitude 经度
* @param latitude 纬度
* @param radius 半径(米)
* @param limit 限制数量
* @return 附近用户列表
*/
public List<Document> findNearbyUsers(double longitude, double latitude, double radius, int limit) {
try {
// 创建查询
Query query = new Query();
query.addCriteria(Criteria.where("location")
.near(new Point(longitude, latitude))
.maxDistance(radius));
query.limit(limit);
query.with(Sort.by(Sort.Direction.DESC, "timestamp"));

// 执行查询
List<Document> results = mongoTemplate.find(query, Document.class, "user_locations");

// 计算距离并添加到结果中
for (Document user : results) {
Document location = (Document) user.get("location");
List<Double> coordinates = (List<Double>) location.get("coordinates");
double userLon = coordinates.get(0);
double userLat = coordinates.get(1);

double distance = distanceCalculator.calculateDistance(longitude, latitude, userLon, userLat);
user.put("distance", distance);
}

System.out.println("找到 " + results.size() + " 个附近用户");
return results;
} catch (Exception e) {
System.err.println("查找附近用户失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 验证坐标
* @param longitude 经度
* @param latitude 纬度
* @return 是否有效
*/
private boolean isValidCoordinate(double longitude, double latitude) {
return longitude >= -180 && longitude <= 180 && latitude >= -90 && latitude <= 90;
}
}

5. 总结

5.1 地理空间索引最佳实践

  1. 选择合适的索引类型: 2d索引适用于简单查询,2dsphere索引适用于复杂查询
  2. 使用GeoJSON格式: 标准的地理位置数据格式,兼容性好
  3. 合理设置查询范围: 避免过大的查询范围影响性能
  4. 定期清理数据: 删除过期的地理位置数据
  5. 监控索引性能: 定期检查索引使用情况和性能

5.2 性能优化建议

  • 2d索引: 适用于平面坐标系统,查询速度快
  • 2dsphere索引: 适用于球面坐标系统,功能更强大
  • 复合索引: 结合地理位置和其他字段创建复合索引
  • 查询优化: 使用合适的查询条件和排序方式
  • 数据分片: 对于大量数据考虑分片策略

5.3 常见问题解决

  • 坐标精度: 确保经纬度坐标的精度和格式正确
  • 查询性能: 合理设置查询范围,避免全表扫描
  • 索引大小: 监控索引大小,及时清理无用数据
  • 内存使用: 优化查询条件,减少内存消耗

通过本文的MongoDB经纬度索引Java实战指南,您可以掌握MongoDB地理空间索引的各种类型、创建方法、查询技巧以及在实际项目中的应用。记住,合理使用地理空间索引是实现高效地理位置查询的关键!