1. GeoJSON格式概述

GeoJSON是一种基于JSON的地理空间数据交换格式,用于表示地理要素、要素集合和几何对象。MongoDB原生支持GeoJSON格式,可以高效存储和查询地理空间数据。在Java应用中,合理使用GeoJSON格式可以实现复杂的地理空间数据管理和分析功能。本文将详细介绍GeoJSON格式规范、MongoDB存储方法、查询技巧以及在Java实战中的应用。

1.1 GeoJSON的核心特点

  1. 标准格式: 基于JSON的地理空间数据标准格式
  2. 类型丰富: 支持点、线、面、多点、多线、多面等几何类型
  3. 属性支持: 可以存储地理要素的属性信息
  4. 坐标系统: 支持WGS84坐标系统
  5. 查询优化: MongoDB原生支持GeoJSON查询和索引

1.2 GeoJSON几何类型

  • Point: 点几何对象
  • LineString: 线几何对象
  • Polygon: 面几何对象
  • MultiPoint: 多点几何对象
  • MultiLineString: 多线几何对象
  • MultiPolygon: 多面几何对象
  • GeometryCollection: 几何对象集合
  • Feature: 地理要素
  • FeatureCollection: 地理要素集合

2. GeoJSON基础操作

2.1 Point几何对象

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
/**
* MongoDB GeoJSON Point几何对象操作
* @author 运维实战
*/
@Component
public class GeoJSONPointService {

@Autowired
private MongoTemplate mongoTemplate;

/**
* 创建Point几何对象
* @param longitude 经度
* @param latitude 纬度
* @return Point几何对象
*/
public Document createPoint(double longitude, double latitude) {
try {
Document point = new Document();
point.put("type", "Point");
point.put("coordinates", new double[]{longitude, latitude});

return point;
} catch (Exception e) {
System.err.println("创建Point几何对象失败: " + e.getMessage());
return null;
}
}

/**
* 插入Point地理位置数据
* @param collectionName 集合名称
* @param name 地点名称
* @param longitude 经度
* @param latitude 纬度
* @param properties 属性信息
*/
public void insertPoint(String collectionName, String name, double longitude, double latitude, Map<String, Object> properties) {
try {
// 创建Point几何对象
Document geometry = createPoint(longitude, latitude);

// 创建地理要素文档
Document feature = new Document();
feature.put("type", "Feature");
feature.put("geometry", geometry);
feature.put("properties", properties != null ? properties : new Document());

// 添加额外信息
Document doc = new Document();
doc.put("name", name);
doc.put("location", geometry);
doc.put("longitude", longitude);
doc.put("latitude", latitude);
doc.put("createdAt", new Date());

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

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

/**
* 查询Point几何对象
* @param collectionName 集合名称
* @param longitude 经度
* @param latitude 纬度
* @param maxDistance 最大距离(米)
* @return Point几何对象列表
*/
public List<Document> findNearbyPoints(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() + " 个附近Point几何对象");
return results;
} catch (Exception e) {
System.err.println("查询Point几何对象失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 验证Point几何对象
* @param point Point几何对象
* @return 是否有效
*/
public boolean validatePoint(Document point) {
try {
if (point == null) return false;

String type = point.getString("type");
if (!"Point".equals(type)) return false;

List<Double> coordinates = (List<Double>) point.get("coordinates");
if (coordinates == null || coordinates.size() != 2) return false;

double longitude = coordinates.get(0);
double latitude = coordinates.get(1);

return longitude >= -180 && longitude <= 180 && latitude >= -90 && latitude <= 90;
} catch (Exception e) {
System.err.println("验证Point几何对象失败: " + e.getMessage());
return false;
}
}
}

2.2 LineString几何对象

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
/**
* MongoDB GeoJSON LineString几何对象操作
* @author 运维实战
*/
@Component
public class GeoJSONLineStringService {

@Autowired
private MongoTemplate mongoTemplate;

/**
* 创建LineString几何对象
* @param coordinates 坐标点列表
* @return LineString几何对象
*/
public Document createLineString(List<double[]> coordinates) {
try {
Document lineString = new Document();
lineString.put("type", "LineString");
lineString.put("coordinates", coordinates);

return lineString;
} catch (Exception e) {
System.err.println("创建LineString几何对象失败: " + e.getMessage());
return null;
}
}

/**
* 插入LineString地理位置数据
* @param collectionName 集合名称
* @param name 路线名称
* @param coordinates 坐标点列表
* @param properties 属性信息
*/
public void insertLineString(String collectionName, String name, List<double[]> coordinates, Map<String, Object> properties) {
try {
// 验证坐标点
if (coordinates == null || coordinates.size() < 2) {
throw new IllegalArgumentException("LineString至少需要2个坐标点");
}

// 创建LineString几何对象
Document geometry = createLineString(coordinates);

// 创建地理要素文档
Document feature = new Document();
feature.put("type", "Feature");
feature.put("geometry", geometry);
feature.put("properties", properties != null ? properties : new Document());

// 添加额外信息
Document doc = new Document();
doc.put("name", name);
doc.put("path", geometry);
doc.put("coordinates", coordinates);
doc.put("createdAt", new Date());

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

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

/**
* 查询与LineString相交的几何对象
* @param collectionName 集合名称
* @param lineString LineString几何对象
* @return 相交的几何对象列表
*/
public List<Document> findIntersectingLineString(String collectionName, Document lineString) {
try {
// 创建查询
Query query = new Query();
query.addCriteria(Criteria.where("path").intersects(lineString));

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

System.out.println("找到 " + results.size() + " 个相交的LineString几何对象");
return results;
} catch (Exception e) {
System.err.println("查询相交LineString几何对象失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 计算LineString长度
* @param coordinates 坐标点列表
* @return 长度(米)
*/
public double calculateLineStringLength(List<double[]> coordinates) {
try {
double totalLength = 0.0;

for (int i = 0; i < coordinates.size() - 1; i++) {
double[] point1 = coordinates.get(i);
double[] point2 = coordinates.get(i + 1);

double distance = calculateDistance(point1[0], point1[1], point2[0], point2[1]);
totalLength += distance;
}

return totalLength;
} catch (Exception e) {
System.err.println("计算LineString长度失败: " + e.getMessage());
return 0.0;
}
}

/**
* 计算两点之间的距离
* @param lon1 第一个点的经度
* @param lat1 第一个点的纬度
* @param lon2 第二个点的经度
* @param lat2 第二个点的纬度
* @return 距离(米)
*/
private double calculateDistance(double lon1, double lat1, double lon2, double lat2) {
final double EARTH_RADIUS = 6371000; // 地球半径(米)

double lat1Rad = Math.toRadians(lat1);
double lat2Rad = Math.toRadians(lat2);
double deltaLatRad = Math.toRadians(lat2 - lat1);
double deltaLonRad = Math.toRadians(lon2 - lon1);

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;
}
}

2.3 Polygon几何对象

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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/**
* MongoDB GeoJSON Polygon几何对象操作
* @author 运维实战
*/
@Component
public class GeoJSONPolygonService {

@Autowired
private MongoTemplate mongoTemplate;

/**
* 创建Polygon几何对象
* @param coordinates 坐标环列表
* @return Polygon几何对象
*/
public Document createPolygon(List<List<double[]>> coordinates) {
try {
Document polygon = new Document();
polygon.put("type", "Polygon");
polygon.put("coordinates", coordinates);

return polygon;
} catch (Exception e) {
System.err.println("创建Polygon几何对象失败: " + e.getMessage());
return null;
}
}

/**
* 创建简单Polygon(外环)
* @param coordinates 外环坐标点列表
* @return Polygon几何对象
*/
public Document createSimplePolygon(List<double[]> coordinates) {
try {
// 确保外环闭合
if (coordinates.size() < 4) {
throw new IllegalArgumentException("Polygon至少需要4个坐标点");
}

// 检查是否闭合
double[] first = coordinates.get(0);
double[] last = coordinates.get(coordinates.size() - 1);
if (first[0] != last[0] || first[1] != last[1]) {
coordinates.add(new double[]{first[0], first[1]});
}

List<List<double[]>> polygonCoordinates = new ArrayList<>();
polygonCoordinates.add(coordinates);

return createPolygon(polygonCoordinates);
} catch (Exception e) {
System.err.println("创建简单Polygon失败: " + e.getMessage());
return null;
}
}

/**
* 插入Polygon地理位置数据
* @param collectionName 集合名称
* @param name 区域名称
* @param coordinates 坐标环列表
* @param properties 属性信息
*/
public void insertPolygon(String collectionName, String name, List<List<double[]>> coordinates, Map<String, Object> properties) {
try {
// 验证坐标环
if (coordinates == null || coordinates.isEmpty()) {
throw new IllegalArgumentException("Polygon至少需要一个坐标环");
}

// 创建Polygon几何对象
Document geometry = createPolygon(coordinates);

// 创建地理要素文档
Document feature = new Document();
feature.put("type", "Feature");
feature.put("geometry", geometry);
feature.put("properties", properties != null ? properties : new Document());

// 添加额外信息
Document doc = new Document();
doc.put("name", name);
doc.put("area", geometry);
doc.put("coordinates", coordinates);
doc.put("createdAt", new Date());

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

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

/**
* 查询包含指定点的Polygon
* @param collectionName 集合名称
* @param longitude 经度
* @param latitude 纬度
* @return 包含指定点的Polygon列表
*/
public List<Document> findPolygonsContainingPoint(String collectionName, double longitude, double latitude) {
try {
// 创建Point几何对象
Document point = new Document();
point.put("type", "Point");
point.put("coordinates", new double[]{longitude, latitude});

// 创建查询
Query query = new Query();
query.addCriteria(Criteria.where("area").intersects(point));

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

System.out.println("找到 " + results.size() + " 个包含指定点的Polygon");
return results;
} catch (Exception e) {
System.err.println("查询包含指定点的Polygon失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 查询与Polygon相交的几何对象
* @param collectionName 集合名称
* @param polygon Polygon几何对象
* @return 相交的几何对象列表
*/
public List<Document> findIntersectingPolygon(String collectionName, Document polygon) {
try {
// 创建查询
Query query = new Query();
query.addCriteria(Criteria.where("area").intersects(polygon));

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

System.out.println("找到 " + results.size() + " 个相交的Polygon几何对象");
return results;
} catch (Exception e) {
System.err.println("查询相交Polygon几何对象失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 计算Polygon面积
* @param coordinates 坐标环列表
* @return 面积(平方米)
*/
public double calculatePolygonArea(List<List<double[]>> coordinates) {
try {
if (coordinates == null || coordinates.isEmpty()) {
return 0.0;
}

// 计算外环面积
List<double[]> outerRing = coordinates.get(0);
double area = calculateRingArea(outerRing);

// 减去内环面积
for (int i = 1; i < coordinates.size(); i++) {
List<double[]> innerRing = coordinates.get(i);
area -= calculateRingArea(innerRing);
}

return Math.abs(area);
} catch (Exception e) {
System.err.println("计算Polygon面积失败: " + e.getMessage());
return 0.0;
}
}

/**
* 计算坐标环面积
* @param ring 坐标环
* @return 面积(平方米)
*/
private double calculateRingArea(List<double[]> ring) {
if (ring.size() < 3) return 0.0;

double area = 0.0;
int j = ring.size() - 1;

for (int i = 0; i < ring.size(); i++) {
double[] point1 = ring.get(j);
double[] point2 = ring.get(i);

area += (point2[0] - point1[0]) * (point2[1] + point1[1]);
j = i;
}

return Math.abs(area) / 2.0 * 111000 * 111000; // 转换为平方米
}
}

2.4 MultiPoint几何对象

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
155
156
157
/**
* MongoDB GeoJSON MultiPoint几何对象操作
* @author 运维实战
*/
@Component
public class GeoJSONMultiPointService {

@Autowired
private MongoTemplate mongoTemplate;

/**
* 创建MultiPoint几何对象
* @param coordinates 坐标点列表
* @return MultiPoint几何对象
*/
public Document createMultiPoint(List<double[]> coordinates) {
try {
Document multiPoint = new Document();
multiPoint.put("type", "MultiPoint");
multiPoint.put("coordinates", coordinates);

return multiPoint;
} catch (Exception e) {
System.err.println("创建MultiPoint几何对象失败: " + e.getMessage());
return null;
}
}

/**
* 插入MultiPoint地理位置数据
* @param collectionName 集合名称
* @param name 多点名称
* @param coordinates 坐标点列表
* @param properties 属性信息
*/
public void insertMultiPoint(String collectionName, String name, List<double[]> coordinates, Map<String, Object> properties) {
try {
// 验证坐标点
if (coordinates == null || coordinates.isEmpty()) {
throw new IllegalArgumentException("MultiPoint至少需要一个坐标点");
}

// 创建MultiPoint几何对象
Document geometry = createMultiPoint(coordinates);

// 创建地理要素文档
Document feature = new Document();
feature.put("type", "Feature");
feature.put("geometry", geometry);
feature.put("properties", properties != null ? properties : new Document());

// 添加额外信息
Document doc = new Document();
doc.put("name", name);
doc.put("points", geometry);
doc.put("coordinates", coordinates);
doc.put("pointCount", coordinates.size());
doc.put("createdAt", new Date());

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

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

/**
* 查询MultiPoint几何对象
* @param collectionName 集合名称
* @param longitude 经度
* @param latitude 纬度
* @param maxDistance 最大距离(米)
* @return MultiPoint几何对象列表
*/
public List<Document> findNearbyMultiPoints(String collectionName, double longitude, double latitude, double maxDistance) {
try {
// 创建查询
Query query = new Query();
query.addCriteria(Criteria.where("points")
.near(new Point(longitude, latitude))
.maxDistance(maxDistance));

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

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

/**
* 计算MultiPoint中心点
* @param coordinates 坐标点列表
* @return 中心点坐标
*/
public double[] calculateMultiPointCenter(List<double[]> coordinates) {
try {
if (coordinates == null || coordinates.isEmpty()) {
return null;
}

double totalLon = 0.0;
double totalLat = 0.0;

for (double[] coord : coordinates) {
totalLon += coord[0];
totalLat += coord[1];
}

return new double[]{totalLon / coordinates.size(), totalLat / coordinates.size()};
} catch (Exception e) {
System.err.println("计算MultiPoint中心点失败: " + e.getMessage());
return null;
}
}

/**
* 计算MultiPoint边界框
* @param coordinates 坐标点列表
* @return 边界框
*/
public Map<String, Double> calculateMultiPointBounds(List<double[]> coordinates) {
try {
if (coordinates == null || coordinates.isEmpty()) {
return new HashMap<>();
}

double minLon = Double.MAX_VALUE;
double maxLon = Double.MIN_VALUE;
double minLat = Double.MAX_VALUE;
double maxLat = Double.MIN_VALUE;

for (double[] coord : coordinates) {
minLon = Math.min(minLon, coord[0]);
maxLon = Math.max(maxLon, coord[0]);
minLat = Math.min(minLat, coord[1]);
maxLat = Math.max(maxLat, coord[1]);
}

Map<String, Double> bounds = new HashMap<>();
bounds.put("minLongitude", minLon);
bounds.put("maxLongitude", maxLon);
bounds.put("minLatitude", minLat);
bounds.put("maxLatitude", maxLat);

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

3. GeoJSON查询和空间分析

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
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 GeoJSON空间查询操作
* @author 运维实战
*/
@Component
public class GeoJSONSpatialQueryService {

@Autowired
private MongoTemplate mongoTemplate;

/**
* 查询相交的几何对象
* @param collectionName 集合名称
* @param geometry 几何对象
* @return 相交的几何对象列表
*/
public List<Document> findIntersecting(String collectionName, Document geometry) {
try {
// 创建查询
Query query = new Query();
query.addCriteria(Criteria.where("geometry").intersects(geometry));

// 执行查询
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 geometry 几何对象
* @return 包含的几何对象列表
*/
public List<Document> findContaining(String collectionName, Document geometry) {
try {
// 创建查询
Query query = new Query();
query.addCriteria(Criteria.where("geometry").within(geometry));

// 执行查询
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 maxDistance 最大距离(米)
* @return 附近的几何对象列表
*/
public List<Document> findNearby(String collectionName, double longitude, double latitude, double maxDistance) {
try {
// 创建查询
Query query = new Query();
query.addCriteria(Criteria.where("geometry")
.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 minLongitude 最小经度
* @param minLatitude 最小纬度
* @param maxLongitude 最大经度
* @param maxLatitude 最大纬度
* @return 边界框内的几何对象列表
*/
public List<Document> findWithinBounds(String collectionName, double minLongitude, double minLatitude,
double maxLongitude, double maxLatitude) {
try {
// 创建边界框
Document box = new Document();
box.put("type", "Polygon");
box.put("coordinates", new Object[]{
new double[][]{
{minLongitude, minLatitude},
{maxLongitude, minLatitude},
{maxLongitude, maxLatitude},
{minLongitude, maxLatitude},
{minLongitude, minLatitude}
}
});

// 创建查询
Query query = new Query();
query.addCriteria(Criteria.where("geometry").within(box));

// 执行查询
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.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/**
* MongoDB GeoJSON空间分析工具
* @author 运维实战
*/
@Component
public class GeoJSONSpatialAnalysisService {

@Autowired
private MongoTemplate mongoTemplate;

/**
* 计算几何对象面积
* @param geometry 几何对象
* @return 面积(平方米)
*/
public double calculateArea(Document geometry) {
try {
String type = geometry.getString("type");

switch (type) {
case "Polygon":
return calculatePolygonArea(geometry);
case "MultiPolygon":
return calculateMultiPolygonArea(geometry);
default:
return 0.0;
}
} catch (Exception e) {
System.err.println("计算几何对象面积失败: " + e.getMessage());
return 0.0;
}
}

/**
* 计算Polygon面积
* @param polygon Polygon几何对象
* @return 面积(平方米)
*/
private double calculatePolygonArea(Document polygon) {
try {
List<List<List<Double>>> coordinates = (List<List<List<Double>>>) polygon.get("coordinates");
if (coordinates == null || coordinates.isEmpty()) {
return 0.0;
}

// 计算外环面积
List<List<Double>> outerRing = coordinates.get(0);
double area = calculateRingArea(outerRing);

// 减去内环面积
for (int i = 1; i < coordinates.size(); i++) {
List<List<Double>> innerRing = coordinates.get(i);
area -= calculateRingArea(innerRing);
}

return Math.abs(area);
} catch (Exception e) {
System.err.println("计算Polygon面积失败: " + e.getMessage());
return 0.0;
}
}

/**
* 计算MultiPolygon面积
* @param multiPolygon MultiPolygon几何对象
* @return 面积(平方米)
*/
private double calculateMultiPolygonArea(Document multiPolygon) {
try {
List<List<List<List<Double>>>> coordinates = (List<List<List<List<Double>>>>) multiPolygon.get("coordinates");
if (coordinates == null || coordinates.isEmpty()) {
return 0.0;
}

double totalArea = 0.0;
for (List<List<List<Double>>> polygon : coordinates) {
Document polygonDoc = new Document();
polygonDoc.put("type", "Polygon");
polygonDoc.put("coordinates", polygon);
totalArea += calculatePolygonArea(polygonDoc);
}

return totalArea;
} catch (Exception e) {
System.err.println("计算MultiPolygon面积失败: " + e.getMessage());
return 0.0;
}
}

/**
* 计算坐标环面积
* @param ring 坐标环
* @return 面积(平方米)
*/
private double calculateRingArea(List<List<Double>> ring) {
if (ring.size() < 3) return 0.0;

double area = 0.0;
int j = ring.size() - 1;

for (int i = 0; i < ring.size(); i++) {
List<Double> point1 = ring.get(j);
List<Double> point2 = ring.get(i);

area += (point2.get(0) - point1.get(0)) * (point2.get(1) + point1.get(1));
j = i;
}

return Math.abs(area) / 2.0 * 111000 * 111000; // 转换为平方米
}

/**
* 计算几何对象长度
* @param geometry 几何对象
* @return 长度(米)
*/
public double calculateLength(Document geometry) {
try {
String type = geometry.getString("type");

switch (type) {
case "LineString":
return calculateLineStringLength(geometry);
case "MultiLineString":
return calculateMultiLineStringLength(geometry);
default:
return 0.0;
}
} catch (Exception e) {
System.err.println("计算几何对象长度失败: " + e.getMessage());
return 0.0;
}
}

/**
* 计算LineString长度
* @param lineString LineString几何对象
* @return 长度(米)
*/
private double calculateLineStringLength(Document lineString) {
try {
List<List<Double>> coordinates = (List<List<Double>>) lineString.get("coordinates");
if (coordinates == null || coordinates.size() < 2) {
return 0.0;
}

double totalLength = 0.0;
for (int i = 0; i < coordinates.size() - 1; i++) {
List<Double> point1 = coordinates.get(i);
List<Double> point2 = coordinates.get(i + 1);

double distance = calculateDistance(point1.get(0), point1.get(1), point2.get(0), point2.get(1));
totalLength += distance;
}

return totalLength;
} catch (Exception e) {
System.err.println("计算LineString长度失败: " + e.getMessage());
return 0.0;
}
}

/**
* 计算MultiLineString长度
* @param multiLineString MultiLineString几何对象
* @return 长度(米)
*/
private double calculateMultiLineStringLength(Document multiLineString) {
try {
List<List<List<Double>>> coordinates = (List<List<List<Double>>>) multiLineString.get("coordinates");
if (coordinates == null || coordinates.isEmpty()) {
return 0.0;
}

double totalLength = 0.0;
for (List<List<Double>> lineString : coordinates) {
Document lineStringDoc = new Document();
lineStringDoc.put("type", "LineString");
lineStringDoc.put("coordinates", lineString);
totalLength += calculateLineStringLength(lineStringDoc);
}

return totalLength;
} catch (Exception e) {
System.err.println("计算MultiLineString长度失败: " + e.getMessage());
return 0.0;
}
}

/**
* 计算两点之间的距离
* @param lon1 第一个点的经度
* @param lat1 第一个点的纬度
* @param lon2 第二个点的经度
* @param lat2 第二个点的纬度
* @return 距离(米)
*/
private double calculateDistance(double lon1, double lat1, double lon2, double lat2) {
final double EARTH_RADIUS = 6371000; // 地球半径(米)

double lat1Rad = Math.toRadians(lat1);
double lat2Rad = Math.toRadians(lat2);
double deltaLatRad = Math.toRadians(lat2 - lat1);
double deltaLonRad = Math.toRadians(lon2 - lon1);

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;
}
}

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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
* 地理要素管理系统
* @author 运维实战
*/
@Component
public class GeoFeatureManagementService {

@Autowired
private MongoTemplate mongoTemplate;

@Autowired
private GeoJSONSpatialAnalysisService spatialAnalysisService;

/**
* 配置地理要素索引
*/
@PostConstruct
public void configureGeoFeatureIndexes() {
String collectionName = "geo_features";

try {
// 1. 创建几何对象2dsphere索引
Index geometryIndex = new Index()
.on("geometry", Sort.Direction.ASC)
.named("geo_features_geometry_2dsphere");
mongoTemplate.indexOps(collectionName).ensureIndex(geometryIndex);

// 2. 创建要素类型索引
Index typeIndex = new Index()
.on("properties.type", Sort.Direction.ASC)
.named("geo_features_type");
mongoTemplate.indexOps(collectionName).ensureIndex(typeIndex);

// 3. 创建名称索引
Index nameIndex = new Index()
.on("properties.name", Sort.Direction.ASC)
.named("geo_features_name");
mongoTemplate.indexOps(collectionName).ensureIndex(nameIndex);

// 4. 创建创建时间索引
Index createdAtIndex = new Index()
.on("createdAt", Sort.Direction.DESC)
.named("geo_features_created_at");
mongoTemplate.indexOps(collectionName).ensureIndex(createdAtIndex);

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

/**
* 添加地理要素
* @param name 要素名称
* @param type 要素类型
* @param geometry 几何对象
* @param properties 属性信息
*/
public void addGeoFeature(String name, String type, Document geometry, Map<String, Object> properties) {
try {
// 创建地理要素文档
Document feature = new Document();
feature.put("type", "Feature");
feature.put("geometry", geometry);

// 设置属性
Document props = new Document();
props.put("name", name);
props.put("type", type);
if (properties != null) {
props.putAll(properties);
}
feature.put("properties", props);

// 添加额外信息
feature.put("createdAt", new Date());
feature.put("updatedAt", new Date());

// 插入文档
mongoTemplate.insert(feature, "geo_features");

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

/**
* 查询地理要素
* @param type 要素类型
* @param bounds 边界框
* @return 地理要素列表
*/
public List<Document> queryGeoFeatures(String type, Map<String, Double> bounds) {
try {
// 创建查询
Query query = new Query();

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

if (bounds != null) {
double minLon = bounds.get("minLongitude");
double minLat = bounds.get("minLatitude");
double maxLon = bounds.get("maxLongitude");
double maxLat = bounds.get("maxLatitude");

// 创建边界框查询
Document box = new Document();
box.put("type", "Polygon");
box.put("coordinates", new Object[]{
new double[][]{
{minLon, minLat},
{maxLon, minLat},
{maxLon, maxLat},
{minLon, maxLat},
{minLon, minLat}
}
});

query.addCriteria(Criteria.where("geometry").within(box));
}

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

System.out.println("找到 " + results.size() + " 个地理要素");
return results;
} catch (Exception e) {
System.err.println("查询地理要素失败: " + e.getMessage());
return new ArrayList<>();
}
}

/**
* 分析地理要素
* @param featureId 要素ID
* @return 分析结果
*/
public Map<String, Object> analyzeGeoFeature(String featureId) {
try {
// 查询地理要素
Query query = new Query();
query.addCriteria(Criteria.where("_id").is(new ObjectId(featureId)));

Document feature = mongoTemplate.findOne(query, Document.class, "geo_features");
if (feature == null) {
return new HashMap<>();
}

// 分析几何对象
Document geometry = (Document) feature.get("geometry");
String geometryType = geometry.getString("type");

Map<String, Object> analysis = new HashMap<>();
analysis.put("type", geometryType);
analysis.put("name", ((Document) feature.get("properties")).getString("name"));

// 根据几何类型进行分析
switch (geometryType) {
case "Point":
analysis.put("coordinates", geometry.get("coordinates"));
break;
case "LineString":
analysis.put("length", spatialAnalysisService.calculateLength(geometry));
analysis.put("coordinates", geometry.get("coordinates"));
break;
case "Polygon":
analysis.put("area", spatialAnalysisService.calculateArea(geometry));
analysis.put("coordinates", geometry.get("coordinates"));
break;
case "MultiPoint":
analysis.put("pointCount", ((List<?>) geometry.get("coordinates")).size());
analysis.put("coordinates", geometry.get("coordinates"));
break;
case "MultiLineString":
analysis.put("lineCount", ((List<?>) geometry.get("coordinates")).size());
analysis.put("totalLength", spatialAnalysisService.calculateLength(geometry));
analysis.put("coordinates", geometry.get("coordinates"));
break;
case "MultiPolygon":
analysis.put("polygonCount", ((List<?>) geometry.get("coordinates")).size());
analysis.put("totalArea", spatialAnalysisService.calculateArea(geometry));
analysis.put("coordinates", geometry.get("coordinates"));
break;
}

System.out.println("地理要素分析完成: " + featureId);
return analysis;
} catch (Exception e) {
System.err.println("分析地理要素失败: " + e.getMessage());
return new HashMap<>();
}
}
}

5. 总结

5.1 GeoJSON最佳实践

  1. 使用标准格式: 遵循GeoJSON规范,确保数据兼容性
  2. 合理选择几何类型: 根据数据特点选择合适的几何类型
  3. 优化坐标精度: 根据应用需求设置合适的坐标精度
  4. 建立空间索引: 为几何对象字段创建2dsphere索引
  5. 验证数据格式: 确保GeoJSON数据的完整性和正确性

5.2 性能优化建议

  • 索引优化: 为几何对象创建2dsphere索引
  • 查询优化: 使用合适的空间查询操作符
  • 数据分片: 对于大量数据考虑分片策略
  • 缓存策略: 缓存常用的空间查询结果
  • 批量操作: 使用批量操作提高数据插入效率

5.3 常见问题解决

  • 坐标系统: 确保使用WGS84坐标系统
  • 几何验证: 验证几何对象的有效性
  • 性能问题: 优化查询条件和索引策略
  • 内存使用: 控制查询范围,避免内存溢出

通过本文的MongoDB存储GeoJSON格式Java实战指南,您可以掌握GeoJSON格式规范、MongoDB存储方法、空间查询技巧以及在实际项目中的应用。记住,合理使用GeoJSON格式是实现高效地理空间数据管理的关键!