1、GeoJSON对象概述
以下内容是来自mongodb官方文档
指定GeoJSON数据,请使用具有以下内容的嵌入式文档:
- type - 类型
- coordinates - 指定对象坐标集
如果指定经度和纬度,第一个必须是经度,第二个为纬度
- 有效的经度值位于-180到180之间
- 有效的纬度值位于-90到90之间
数据格式:
: { type: , coordinates: }
eg.
"location":{
"type":"Point",
"coordinates":[106.522742,29.556538]
}
以下示例指定了GeoJSON LineString:
{ type: "LineString", coordinates: [ [ 40, 5 ], [ 41, 6 ] ] }
多边形由GeoJSON LinearRing
坐标数组组成。这些 LinearRings
都是封闭的LineStrings
。闭合LineStrings
的至少具有四个坐标对,第一个和最后一个坐标必须一样,这样才能保证构建的图形是封闭的。连接曲面上两个点的线可能包含也可能不包含连接平面上这两个点的同一组坐标。连接曲面上两个点的线将是测地线。仔细检查坐标点,以免出现共享边,重叠和其他类型的相交的错误。
单环多边形
下面的例子指定了一个GeoJSON多边形,有一个外环,没有内环(或孔)。第一个和最后一个坐标必须匹配,以便关闭多边形。
{
type: "Polygon",
coordinates: [ [ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ] ]
}
对于只有一个环的多边形,该环不能自相交。
多环多边形
- 第一个描述的环必须是外环
- 外环不能自我相交
- 任何内环必须完全被外环所包含
- 内部环不能相互交叉或重叠。内部环不能共享一条边
{
type : "Polygon",
coordinates : [
[ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ],
[ [ 2 , 2 ] , [ 3 , 3 ] , [ 4 , 2 ] , [ 2 , 2 ] ]
]
}
图片来自mongodb官网
GeoJSON MultiPoint 嵌入的文件编码为一个点的列表
{
type: "MultiPoint",
coordinates: [
[ -73.9580, 40.8003 ],
[ -73.9498, 40.7968 ],
[ -73.9737, 40.7648 ],
[ -73.9814, 40.7681 ]
]
}
以下示例指定一个 GeoJSON MultiLineString
{
type: "MultiLineString",
coordinates: [
[ [ -73.96943, 40.78519 ], [ -73.96082, 40.78095 ] ],
[ [ -73.96415, 40.79229 ], [ -73.95544, 40.78854 ] ],
[ [ -73.97162, 40.78205 ], [ -73.96374, 40.77715 ] ],
[ [ -73.97880, 40.77247 ], [ -73.97036, 40.76811 ] ]
]
}
以下示例指定一个GeoJSON MultiPolygon
{
type: "MultiPolygon",
coordinates: [
[ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ], [ -73.9737, 40.7648 ], [ -73.9814, 40.7681 ], [ -73.958, 40.8003 ] ] ],
[ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ], [ -73.9737, 40.7648 ], [ -73.958, 40.8003 ] ] ]
]
}
以下示例存储 GeometryCollection 类型的坐标,即几何集的坐标
{
type: "GeometryCollection",
geometries: [
{
type: "MultiPoint",
coordinates: [
[ -73.9580, 40.8003 ],
[ -73.9498, 40.7968 ],
[ -73.9737, 40.7648 ],
[ -73.9814, 40.7681 ]
]
},
{
type: "MultiLineString",
coordinates: [
[ [ -73.96943, 40.78519 ], [ -73.96082, 40.78095 ] ],
[ [ -73.96415, 40.79229 ], [ -73.95544, 40.78854 ] ],
[ [ -73.97162, 40.78205 ], [ -73.96374, 40.77715 ] ],
[ [ -73.97880, 40.77247 ], [ -73.97036, 40.76811 ] ]
]
}
]
}
2、基于springboot的围栏应用
2.1 搭建环境
使用idea的spring initializ创建新的项目spring-boot-mongo-geo项目,选择以下依赖:
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mongodb -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
项目包结构如下:
2.2 围栏实现
此处我们实现两种类型的围栏
- 圆形(Point)
- 多边形(Polygon)
创建圆形存储实体对象
package com.mongo.geo.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* <pre>
* Title: 圆形围栏
*
* Description:
* <pre>
* @author Steve.Chang
**/
@Document(value = "m_circle_fence")
public class CircleFence {
@Id
private String id;
/**
* 用户编号
*/
private String userNo;
/**
* 半径距离
*/
private Long distance;
/**
* 地理位置
* @GeoSpatialIndexed - 创建2dsphere索引
*/
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
private GeoJsonPoint location;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMerchantNo() {
return merchantNo;
}
public void setMerchantNo(String merchantNo) {
this.merchantNo = merchantNo;
}
public GeoJsonPoint getLocation() {
return location;
}
public void setLocation(GeoJsonPoint location) {
this.location = location;
}
public Long getDistance() {
return distance;
}
public void setDistance(Long distance) {
this.distance = distance;
}
}
多边形存储实体对象
package com.mongo.geo.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* <pre>
* Title: 多边形围栏
*
* Description:
* <pre>
* @author Steve.Chang
**/
@Document(value = "m_polygon_fence")
public class PolygonFence {
@Id
private String id;
/**
* 用户编号
*/
private String userNo;
/**
* 地理位置
* @GeoSpatialIndexed - 创建2dsphere索引
*/
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
private GeoJsonPolygon location;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMerchantNo() {
return merchantNo;
}
public void setMerchantNo(String merchantNo) {
this.merchantNo = merchantNo;
}
public GeoJsonPolygon getLocation() {
return location;
}
public void setLocation(GeoJsonPolygon location) {
this.location = location;
}
}
Respository
实现有两种方式
- MongoTemplate (适用于复杂的查询,自己可以灵活控制)
- 继承 MongoRepository (提供一些基础的模板方法,当然也可以自定义一些派生方法)
此处使用MongoTemplate方式实现
Service
package com.mongo.geo.service;
import com.mongo.geo.model.CircleFence;
import com.mongo.geo.model.PolygonFence;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* <pre>
* Title: 围栏service
*
* Description:
* <pre>
* @author Steve.Chang
**/
@Service
public class FenceService {
private final MongoTemplate mongoTemplate;
public FenceService(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
/**
* 创建原型围栏
*
* @param fence CircleFence
*/
public void createCircle(CircleFence fence) {
mongoTemplate.save(fence);
}
/**
* 创建多边形围栏
*
* @param fence CircleFence
*/
public void createPolygon(PolygonFence fence) {
mongoTemplate.save(fence);
}
/**
* 坐标是否在圆形围栏中
*
* @param userNo userNo
* @param point geo json point
* @param distance circle radius(unit: Miles)
* @return
*/
public boolean existsCircle(String userNo, GeoJsonPoint point, Long distance) {
// 指定圆的半径
Distance dis = new Distance(distance, Metrics.KILOMETERS);
// 构造圆
Circle circle = new Circle(point, dis);
// 构造查询对象
Criteria criteria = Criteria.where("userNo").is(userNo)
.and("localtion").withinSphere(circle);
Query query = Query.query(criteria);
List<CircleFence> fences = mongoTemplate.find(query, CircleFence.class);
return !CollectionUtils.isEmpty(fences);
}
/**
* 坐标是否在多边形形围栏中
*
* @param userNo userNo
* @param point geo json point
* @return
*/
public boolean existsPolygon(String userNo, GeoJsonPoint point) {
// 构造查询对象
Criteria criteria = Criteria.where("userNo").is(userNo)
.and("localtion").intersects(point);
Query query = Query.query(criteria);
List<CircleFence> fences = mongoTemplate.find(query, CircleFence.class);
return !CollectionUtils.isEmpty(fences);
}
}
以上查询用到了以下几个geo查询方法:
- $geoIntersects
选择地理空间数据与指定的GeoJSON对象相交的文档 ;也就是说,数据和指定对象的交集是非空的。 $geoIntersects操作符使用$geometry操作符来指定GeoJSON对象。要使用默认的坐标参考系统(CRS)指定GeoJSON多边形或MutiPolygon(多个多边形类型),请使用以下语法。
eg.
{
<location field>: {
$geoIntersects: {
$geometry: {
type: "Polygon" ,
coordinates: [ <coordinates> ],
crs: {
type: "name",
properties: { name: "urn:x-mongodb:crs:strictwinding:EPSG:4326" }
}
}
}
}
}
- $geoWithin
选择具有完全存在于指定形状内的地理空间数据的文档。指定的形状可以是GeoJSON多边形(单环或多环),也可以是GeoJSON多重多边形,或由传统坐标对定义的形状。$geoWithin操作符使用$geometry操作符来指定GeoJSON对象。要使用默认的坐标参考系统(CRS)指定GeoJSON多边形或多多边形,请使用以下语法。
{
<location field>: {
$geoWithin: {
$geometry: {
type: "Polygon" ,
coordinates: [ <coordinates> ],
crs: {
type: "name",
properties: { name: "urn:x-mongodb:crs:strictwinding:EPSG:4326" }
}
}
}
}
}
- $near
指定一个点,地理空间查询会从最近到最远的地方返回文件。
$near操作符可以指定一个GeoJSON点或传统的坐标点。
$near需要一个地理空间索引。
- 如果指定的是GeoJSON点,需要2dsphere索引。
- 如果指定一个使用传统坐标的点,则需要2d索引。
要指定一个GeoJSON点,$near操作符需要一个2dsphere索引,其语法如下。
{
<location field>: {
$near: {
$geometry: {
type: "Point" ,
coordinates: [ <longitude> , <latitude> ]
},
$maxDistance: <distance in meters>,
$minDistance: <distance in meters>
}
}
}
- $nearSphere
指定一个点,地理空间查询会从最近到最远的地方返回文件。MongoDB使用球面几何学计算$nearSphere的距离。
$nearSphere需要一个地理空间的索引。
- 2dsphere索引用于定义为GeoJSON点的位置数据
- 2d索引,用于定义为传统坐标对的位置数据。要在GeoJSON点上使用2d索引,请在GeoJSON对象的坐标域上创建索引。$nearSphere操作符可以指定一个GeoJSON点或传统坐标点。
eg.
{
$nearSphere: {
$geometry: {
type : "Point",
coordinates : [ <longitude>, <latitude> ]
},
$minDistance: <distance in meters>,
$maxDistance: <distance in meters>
}
}
要使用传统坐标指定点,请使用以下语法:
{
$nearSphere: [ <x>, <y> ],
$minDistance: <distance in radians>,
$maxDistance: <distance in radians>
}
- 仅当查询使用2dsphere索引时,可选选项
$minDistance
才可用。 将结果限制为距中心点至少指定距离的那些文档。$minDistance
- 在可选
$maxDistance
适用于任何一个索引。
如果您将经度和纬度用于旧版坐标,请先指定经度,然后再指定纬度。
- $box
为地理空间 $geoWithin
查询指定一个矩形,以根据其基于点的位置数据返回该矩形范围内的文档。与$box
运算符一起使用时,$geoWithin
将基于网格坐标返回文档,并且不会查询GeoJSON形状。
要使用$box
运算符,必须在数组对象中指定矩形的左下角和右上角,语法如下:
{
<location field>: {
$geoWithin: {
$box: [
[ <bottom left coordinates> ],
[ <upper right coordinates> ]
]
}
}
}
- $center
$center操作符为$geoWithin查询指定了一个圆。该查询返回在圆圈范围内的遗留坐标对。该操作符不返回GeoJSON对象。
要使用$center操作符,请指定一个包含以下内容的数组。
- 圆的中心点的网格坐标。
- 圆的半径,以坐标系统所使用的单位来测量。
{
<location field>: {
$geoWithin: { $center: [ [ <x>, <y> ] , <radius> ] }
}
}
- $centerSphere
为使用球面几何的地理空间查询定义一个圆。该查询会返回在圆圈范围内的文件。你可以在GeoJSON对象和传统的坐标对上使用$centerSphere操作符。
使用$centerSphere,指定一个包含的数组:
- 圆的中心点的网格坐标
- 以弧度为单位的圆的半径。要计算弧度,请参阅《Calculate Distance Using Spherical Geometry》
)
{
<location field>: {
$geoWithin: { $centerSphere: [ [ <x>, <y> ], <radius> ] }
}
}
- $geometry
$geometry操作符指定了一个GeoJSON几何体,用于以下地理空间查询操作符。$geoWithin, $geoIntersects, $near, 和$nearSphere。$geometry使用EPSG:4326作为默认的坐标参考系统(CRS)。
要指定使用默认CRS的GeoJSON对象,请使用以下$geometry的示例。
$geometry: {
type: "<GeoJSON object type>",
coordinates: [ <coordinates> ]
}
要指定一个带有自定义MongoDB CRS的单环GeoJSON多边形,请使用以下示例(仅对$geoWithin和$geoIntersects可用)。
$geometry: {
type: "Polygon",
coordinates: [ <coordinates> ],
crs: {
type: "name",
properties: { name: "urn:x-mongodb:crs:strictwinding:EPSG:4326" }
}
}
- $maxDistance
$maxDistance操作符将地理空间$near或$nearSphere查询的结果限制在指定距离内。最大距离的测量单位由使用的坐标系统决定。对于GeoJSON点对象,指定的距离是米,而不是弧度。你必须为$maxDistance指定一个非负数。
2dsphere和2d地理空间索引都支持$maxDistance:
db.places.find( {
loc: { $near: [ -74 , 40 ], $maxDistance: 10 }
} )
- $minDistance
将地理空间的$near或$nearSphere查询的结果过滤到与中心点至少有指定距离的文件。
如果$near或$nearSphere查询指定中心点为GeoJSON点,则指定距离为非负数,单位为米。
如果$nearSphere查询将中心点指定为遗留坐标对,则将距离指定为弧度的非负数。如果查询指定中心点为GeoJSON点,$near只能使用2dsphere索引。
重要
如果指定纬度和经度坐标,请先列出经度,然后再列出 纬度:
- 有效的经度值介于
-180
和之间180
,包括两者之间。 - 有效的纬度值介于
-90
和之间90
,包括在内。
db.places.find(
{
location:
{ $near :
{
$geometry: { type: "Point", coordinates: [ -73.9667, 40.78 ] },
$minDistance: 1000,
$maxDistance: 5000
}
}
}
)
- $polygon
指定一个多边形,用于对遗留坐标对进行地理空间$geoWithin查询。该查询返回在多边形范围内的坐标对。该运算符不对GeoJSON对象进行查询。
要定义多边形,请指定一个坐标点阵列:
{
<location field>: {
$geoWithin: {
$polygon: [ [ <x1> , <y1> ], [ <x2> , <y2> ], [ <x3> , <y3> ], ... ]
}
}
}
注意: 最后一个点必须要和第一个点相连,也就是第一个点和最后一个点要保持一样。
$polygon运算符使用平坦的(平面)几何图形来计算距离。应用程序可以使用$polygon而无需地理空间索引。然而,地理空间索引支持比无索引快的查询。
只有2D地理空间索引支持$polygon操作。
- $uniqueDocs
自2.6版起不再使用:地理空间查询不再返回重复的结果。$uniqueDocs操作符对结果没有影响。
对一个地理空间查询只返回一次文件,即使该文件与查询多次匹配。
Controller
package com.mongo.geo.controller;
import com.mongo.geo.controller.vo.CircleFenceParamVO;
import com.mongo.geo.controller.vo.FenceCheckParamVO;
import com.mongo.geo.controller.vo.FencePoint;
import com.mongo.geo.controller.vo.PolygonFenceParamVO;
import com.mongo.geo.model.CircleFence;
import com.mongo.geo.model.PolygonFence;
import com.mongo.geo.service.FenceService;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* <pre>
* Title: 围栏
*
* Description:
* <pre>
* @author Steve.Chang
**/
@RestController
@RequestMapping("api/fence")
public class FenceController {
private final FenceService fenceService;
public FenceController(FenceService fenceService) {
this.fenceService = fenceService;
}
/**
* 创建圆形围栏
*
* @param paramVO CircleFenceParamVO
* @return
*/
@PostMapping("circle")
public ResponseEntity createCircle(@RequestBody CircleFenceParamVO paramVO) {
CircleFence circle = new CircleFence();
circle.setMerchantNo(paramVO.getMerchantNo());
circle.setLocation(new GeoJsonPoint(paramVO.getLongitude(), paramVO.getLatitude()));
fenceService.createCircle(circle);
return ResponseEntity.ok().body("");
}
/**
* 创建多边形围栏
*
* @param paramVO CircleFenceParamVO
* @return
*/
@PostMapping("polygon")
public ResponseEntity createPolygon(@RequestBody PolygonFenceParamVO paramVO) {
PolygonFence polygonFence = new PolygonFence();
polygonFence.setMerchantNo(paramVO.getMerchantNo());
List<Point> points = new ArrayList<>();
for (FencePoint coordinate : paramVO.getCoordinates()) {
points.add(new Point(coordinate.getLongitude(), coordinate.getLatitude()));
}
GeoJsonPolygon polygon = new GeoJsonPolygon(points);
polygonFence.setLocation(polygon);
fenceService.createPolygon(polygonFence);
return ResponseEntity.ok().body("");
}
/**
* 原型围栏检查
*
* @param paramVO FenceCheckParamVO
* @return ResponseEntity
*/
@GetMapping("circle/warning")
public ResponseEntity existsCircle(@RequestBody FenceCheckParamVO paramVO) {
boolean result = fenceService.existsCircle(paramVO.getMerchantNo(), new GeoJsonPoint(paramVO.getLongitude(),
paramVO.getLatitude()), paramVO.getDistance());
return ResponseEntity.ok().body(result ? "围栏内" : "围栏外");
}
/**
* 多边形围栏检查
*
* @param paramVO FenceCheckParamVO
* @return ResponseEntity
*/
@GetMapping("polygon/warning")
public ResponseEntity existsPolygon(@RequestBody FenceCheckParamVO paramVO) {
boolean result = fenceService.existsPolygon(paramVO.getMerchantNo(), new GeoJsonPoint(paramVO.getLongitude(),
paramVO.getLatitude()));
return ResponseEntity.ok().body(result ? "围栏内" : "围栏外");
}
}
最后使用postman工具调试结果,此处就不具体贴图了。
特别说明
mongodb中默认是使用WGS84做计算的,使用的时候请注意要转换成对应格式的数据。
文章如有不妥支出,请指正。