Hibernate-Spatial + PostGIS 中的谓词使用纬度/经度在英里半径范围内进行搜索
Predicates in Hibernate-Spatial + PostGIS to search within miles radius with Latitude / Longitude
我正在尝试使用 Hibernate-Spatial 中的谓词在 X 英里(用户定义)半径内进行搜索。
虽然这种问题以前曾被问过,但之前只用谓词问过一次,而且可能不是最新的,因为它不起作用。而且我必须能够使用谓词而不是 HQL。
我通过另一个答案找到了如何为 PostGIS 的 WITHIN 关键字设置谓词,但无法获得对 运行 的查询。
最后设置后,我说明错误输出和包版本。
这是我的架构中的内容:
CREATE EXTENSION Postgis;
CREATE TABLE "user" (
id bigint NOT NULL,
search_radius int,
latitude double precision,
longitude double precision,
location geography(point, 4326),
PRIMARY KEY (id)
);
在我的模型中:
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import org.hibernate.annotations.Type;
@Entity
@Table(name = "`user`")
public class User implements UserDetails {
@Id
private Long id;
private Integer searchRadius = 125;
private Double latitude;
private Double longitude;
@Column(name = "location", columnDefinition = "geography(Point, 4326)", nullable = true)
@Type(type = "jts_geometry")
private Point location;
...
private void updateLocation() {
if (latitude != null && longitude != null) {
try {
location = (Point) new WKTReader().read("POINT(" + latitude + " " + longitude + ")");
location.setSRID(Location.SRID);
} catch (ParseException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}
在 lat/lng 的 setter 中调用更新位置的地方。
我从Using JPA Criteria Api and hibernate spatial 4 together
获得的谓词
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
import java.io.Serializable;
import javax.persistence.criteria.Expression;
import org.hibernate.jpa.criteria.CriteriaBuilderImpl;
import org.hibernate.jpa.criteria.ParameterRegistry;
import org.hibernate.jpa.criteria.Renderable;
import org.hibernate.jpa.criteria.compile.RenderingContext;
import org.hibernate.jpa.criteria.predicate.AbstractSimplePredicate;
public class WithinPredicate extends AbstractSimplePredicate implements Expression<Boolean>, Serializable {
private final Expression<Point> matchExpression;
private final Expression<Geometry> area;
public WithinPredicate(CriteriaBuilderImpl criteriaBuilder, Expression<Point> matchExpression, Expression<Geometry> area) {
super(criteriaBuilder);
this.matchExpression = matchExpression;
this.area = area;
}
public Expression<Point> getMatchExpression() {
return matchExpression;
}
public Expression<Geometry> getArea() {
return area;
}
public void registerParameters(ParameterRegistry registry) {
// Nothing to register
}
@Override
public String render(boolean isNegated, RenderingContext renderingContext) {
StringBuilder buffer = new StringBuilder();
buffer.append(" within(")
.append(((Renderable) getMatchExpression()).render(renderingContext))
.append(", ")
.append(((Renderable) getArea()).render(renderingContext))
.append(" ) = true ");
String bo = buffer.toString();
return bo;
}
}
并使用 WithinPredicate 和 circle 工厂创建查询。
import com.xxxx.repository.WithinPredicate;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.util.GeometricShapeFactory;
@Service
class ... {
public List<User> fetchFor(User user) {
...
Geometry radius = createCircle(user.getLatitude(), user.getLongitude(), user.getSearchRadius());
radius.setSRID(Location.SRID);
p = b.and(p, new WithinPredicate(
(CriteriaBuilderImpl) b,
u.get(User_.location),
new LiteralExpression<Geometry>((CriteriaBuilderImpl) b, radius)
));
}
private static Geometry createCircle(double x, double y, final double RADIUS) {
GeometricShapeFactory shapeFactory = new GeometricShapeFactory();
shapeFactory.setNumPoints(32);
shapeFactory.setCentre(new Coordinate(x, y));//there are your coordinates
shapeFactory.setSize(RADIUS * 2);//this is how you set the radius
return shapeFactory.createCircle().getBoundary();
}
}
不幸的是,我在输出中得到的错误是:
2016-09-09 14:03:30.862 WARN 56393 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 42883
2016-09-09 14:03:30.862 ERROR 56393 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: function st_within(geography, bytea) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Position: 336
gradle 中的 Hibernate-spatial 版本:
compile('org.hibernate:hibernate-spatial:5.0.9.Final')
我怀疑问题可能是由于模型/模式被设置为 "Geography(Point, 4326)" 类型,但 shapeFactory 创建了 "Geometry(Point, 4326)".
类型的半径
非常感谢对此的任何帮助。谢谢。
我只读了你问题的一部分,但有几件事引发了几个危险信号:
- 您需要将轴顺序翻转为(X Y),或(经度纬度)
- ST_Within 仅适用于
geometry
类型,不适用于 geography
- 要在区域内查找点,请不要创建不完美的缓冲点
- 您会发现 ST_DWithin 更快,并且
geography
类型得到了很好的支持;对于半径,只需将英里转换为米
我正在尝试使用 Hibernate-Spatial 中的谓词在 X 英里(用户定义)半径内进行搜索。
虽然这种问题以前曾被问过,但之前只用谓词问过一次,而且可能不是最新的,因为它不起作用。而且我必须能够使用谓词而不是 HQL。
我通过另一个答案找到了如何为 PostGIS 的 WITHIN 关键字设置谓词,但无法获得对 运行 的查询。
最后设置后,我说明错误输出和包版本。
这是我的架构中的内容:
CREATE EXTENSION Postgis;
CREATE TABLE "user" (
id bigint NOT NULL,
search_radius int,
latitude double precision,
longitude double precision,
location geography(point, 4326),
PRIMARY KEY (id)
);
在我的模型中:
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import org.hibernate.annotations.Type;
@Entity
@Table(name = "`user`")
public class User implements UserDetails {
@Id
private Long id;
private Integer searchRadius = 125;
private Double latitude;
private Double longitude;
@Column(name = "location", columnDefinition = "geography(Point, 4326)", nullable = true)
@Type(type = "jts_geometry")
private Point location;
...
private void updateLocation() {
if (latitude != null && longitude != null) {
try {
location = (Point) new WKTReader().read("POINT(" + latitude + " " + longitude + ")");
location.setSRID(Location.SRID);
} catch (ParseException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}
在 lat/lng 的 setter 中调用更新位置的地方。
我从Using JPA Criteria Api and hibernate spatial 4 together
获得的谓词import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
import java.io.Serializable;
import javax.persistence.criteria.Expression;
import org.hibernate.jpa.criteria.CriteriaBuilderImpl;
import org.hibernate.jpa.criteria.ParameterRegistry;
import org.hibernate.jpa.criteria.Renderable;
import org.hibernate.jpa.criteria.compile.RenderingContext;
import org.hibernate.jpa.criteria.predicate.AbstractSimplePredicate;
public class WithinPredicate extends AbstractSimplePredicate implements Expression<Boolean>, Serializable {
private final Expression<Point> matchExpression;
private final Expression<Geometry> area;
public WithinPredicate(CriteriaBuilderImpl criteriaBuilder, Expression<Point> matchExpression, Expression<Geometry> area) {
super(criteriaBuilder);
this.matchExpression = matchExpression;
this.area = area;
}
public Expression<Point> getMatchExpression() {
return matchExpression;
}
public Expression<Geometry> getArea() {
return area;
}
public void registerParameters(ParameterRegistry registry) {
// Nothing to register
}
@Override
public String render(boolean isNegated, RenderingContext renderingContext) {
StringBuilder buffer = new StringBuilder();
buffer.append(" within(")
.append(((Renderable) getMatchExpression()).render(renderingContext))
.append(", ")
.append(((Renderable) getArea()).render(renderingContext))
.append(" ) = true ");
String bo = buffer.toString();
return bo;
}
}
并使用 WithinPredicate 和 circle 工厂创建查询。
import com.xxxx.repository.WithinPredicate;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.util.GeometricShapeFactory;
@Service
class ... {
public List<User> fetchFor(User user) {
...
Geometry radius = createCircle(user.getLatitude(), user.getLongitude(), user.getSearchRadius());
radius.setSRID(Location.SRID);
p = b.and(p, new WithinPredicate(
(CriteriaBuilderImpl) b,
u.get(User_.location),
new LiteralExpression<Geometry>((CriteriaBuilderImpl) b, radius)
));
}
private static Geometry createCircle(double x, double y, final double RADIUS) {
GeometricShapeFactory shapeFactory = new GeometricShapeFactory();
shapeFactory.setNumPoints(32);
shapeFactory.setCentre(new Coordinate(x, y));//there are your coordinates
shapeFactory.setSize(RADIUS * 2);//this is how you set the radius
return shapeFactory.createCircle().getBoundary();
}
}
不幸的是,我在输出中得到的错误是:
2016-09-09 14:03:30.862 WARN 56393 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 42883
2016-09-09 14:03:30.862 ERROR 56393 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: function st_within(geography, bytea) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Position: 336
gradle 中的 Hibernate-spatial 版本:
compile('org.hibernate:hibernate-spatial:5.0.9.Final')
我怀疑问题可能是由于模型/模式被设置为 "Geography(Point, 4326)" 类型,但 shapeFactory 创建了 "Geometry(Point, 4326)".
类型的半径非常感谢对此的任何帮助。谢谢。
我只读了你问题的一部分,但有几件事引发了几个危险信号:
- 您需要将轴顺序翻转为(X Y),或(经度纬度)
- ST_Within 仅适用于
geometry
类型,不适用于geography
- 要在区域内查找点,请不要创建不完美的缓冲点
- 您会发现 ST_DWithin 更快,并且
geography
类型得到了很好的支持;对于半径,只需将英里转换为米