LUCENE-7951: Spatial4j implementations using Geo3d

This commit is contained in:
David Smiley 2017-09-20 23:09:46 -04:00
parent 171b3739ba
commit 035523ba7a
16 changed files with 1328 additions and 228 deletions

View File

@ -45,6 +45,13 @@
<artifactId>lucene-test-framework</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-spatial3d</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
@lucene-spatial-extras.internal.dependencies@
@lucene-spatial-extras.external.dependencies@
@lucene-spatial-extras.internal.test.dependencies@

View File

@ -28,6 +28,10 @@ New Features
* LUCENE-7392: Add point based LatLonBoundingBox as new RangeField Type.
(Nick Knize)
* LUCENE-7951: Spatial-extras has much better Geo3d support by implementing Spatial4j
abstractions: SpatialContextFactory, ShapeFactory, BinaryCodec, DistanceCalculator.
(Ignacio Vera, David Smiley)
Optimizations
* LUCENE-7905: Optimize how OrdinalMap (used by

View File

@ -38,10 +38,19 @@
<path refid="test.base.classpath" />
<path refid="spatialjar"/>
<pathelement path="src/test-files" />
<pathelement path="${common.dir}/build/spatial3d/classes/test" />
</path>
<target name="compile-core" depends="jar-spatial3d,common.compile-core" />
<target name="compile-test" depends="compile-spatial3d-tests,common.compile-test" />
<target name="compile-spatial3d-tests">
<ant dir="${common.dir}/spatial3d" target="compile-test" inheritAll="false">
<propertyset refid="uptodate.and.compiled.properties"/>
</ant>
</target>
<target name="javadocs" depends="javadocs-spatial3d,compile-core,check-javadocs-uptodate"
unless="javadocs-uptodate-${name}">
<invoke-module-javadoc>

View File

@ -0,0 +1,130 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.spatial4j;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.lucene.spatial3d.geom.GeoAreaShape;
import org.apache.lucene.spatial3d.geom.GeoBBox;
import org.apache.lucene.spatial3d.geom.GeoCircle;
import org.apache.lucene.spatial3d.geom.GeoPointShape;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.apache.lucene.spatial3d.geom.SerializableObject;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.io.BinaryCodec;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeCollection;
/**
* Geo3d implementation of {@link BinaryCodec}
*
* @lucene.experimental
*/
public class Geo3dBinaryCodec extends BinaryCodec {
private PlanetModel planetModel;
@SuppressWarnings("unchecked")
public Geo3dBinaryCodec(SpatialContext ctx, SpatialContextFactory factory) {
super(ctx, factory);
planetModel = ((Geo3dSpatialContextFactory) factory).planetModel;
}
@Override
public Shape readShape(DataInput dataInput) throws IOException {
SerializableObject serializableObject = SerializableObject.readObject(planetModel, (InputStream) dataInput);
if (serializableObject instanceof GeoAreaShape) {
GeoAreaShape shape = (GeoAreaShape) serializableObject;
return new Geo3dShape<>(shape, ctx);
}
throw new IllegalArgumentException("trying to read a not supported shape: " + serializableObject.getClass());
}
@Override
public void writeShape(DataOutput dataOutput, Shape s) throws IOException {
if (s instanceof Geo3dShape) {
Geo3dShape geoAreaShape = (Geo3dShape) s;
SerializableObject.writeObject((OutputStream) dataOutput, geoAreaShape.shape);
} else {
throw new IllegalArgumentException("trying to write a not supported shape: " + s.getClass().getName());
}
}
@Override
public Point readPoint(DataInput dataInput) throws IOException {
SerializableObject serializableObject = SerializableObject.readObject(planetModel, (InputStream) dataInput);
if (serializableObject instanceof GeoPointShape) {
GeoPointShape shape = (GeoPointShape) serializableObject;
return new Geo3dPointShape(shape, ctx);
}
throw new IllegalArgumentException("trying to read a not supported point shape: " + serializableObject.getClass());
}
@Override
public void writePoint(DataOutput dataOutput, Point pt) throws IOException {
writeShape(dataOutput, pt);
}
@Override
public Rectangle readRect(DataInput dataInput) throws IOException {
SerializableObject serializableObject = SerializableObject.readObject(planetModel, (InputStream) dataInput);
if (serializableObject instanceof GeoBBox) {
GeoBBox shape = (GeoBBox) serializableObject;
return new Geo3dRectangleShape(shape, ctx);
}
throw new IllegalArgumentException("trying to read a not supported rectangle shape: " + serializableObject.getClass());
}
@Override
public void writeRect(DataOutput dataOutput, Rectangle r) throws IOException {
writeShape(dataOutput, r);
}
@Override
public Circle readCircle(DataInput dataInput) throws IOException {
SerializableObject serializableObject = SerializableObject.readObject(planetModel, (InputStream) dataInput);
if (serializableObject instanceof GeoCircle) {
GeoCircle shape = (GeoCircle) serializableObject;
return new Geo3dCircleShape(shape, ctx);
}
throw new IllegalArgumentException("trying to read a not supported circle shape: " + serializableObject.getClass());
}
@Override
public void writeCircle(DataOutput dataOutput, Circle c) throws IOException {
writeShape(dataOutput, c);
}
@Override
public ShapeCollection readCollection(DataInput dataInput) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void writeCollection(DataOutput dataOutput, ShapeCollection col) throws IOException {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.spatial4j;
import org.apache.lucene.spatial3d.geom.GeoCircle;
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.SpatialRelation;
/**
* Specialization of a {@link Geo3dShape} which represents a {@link Circle}.
*
* @lucene.experimental
*/
public class Geo3dCircleShape extends Geo3dShape<GeoCircle> implements Circle {
public Geo3dCircleShape(final GeoCircle shape, final SpatialContext spatialcontext) {
super(shape, spatialcontext);
}
@Override
public void reset(double x, double y, double radiusDEG) {
shape = GeoCircleFactory.makeGeoCircle(shape.getPlanetModel(),
y * DistanceUtils.DEGREES_TO_RADIANS,
x * DistanceUtils.DEGREES_TO_RADIANS,
radiusDEG * DistanceUtils.DEGREES_TO_RADIANS);
center = null;
boundingBox = null;
}
@Override
public double getRadius() {
return shape.getRadius() * DistanceUtils.RADIANS_TO_DEGREES;
}
@Override
public Point getCenter() {
Point center = this.center;//volatile read once
if (center == null) {
center = new Geo3dPointShape(
GeoPointShapeFactory.makeGeoPointShape(shape.getPlanetModel(),
shape.getCenter().getLatitude(),
shape.getCenter().getLongitude()),
spatialcontext);
this.center = center;
}
return center;
}
//TODO Improve GeoCircle to properly relate a point with WGS84 model -- LUCENE-7970
@Override
public SpatialRelation relate(Shape other) {
if (shape.getPlanetModel() != PlanetModel.SPHERE && other instanceof Point) {
if (spatialcontext.getDistCalc().distance((Point) other, getCenter()) <= getRadius()) {
return SpatialRelation.CONTAINS;
}
return SpatialRelation.DISJOINT;
}
return super.relate(other);
}
}

View File

@ -0,0 +1,154 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.spatial4j;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPointShape;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceCalculator;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
/**
* Geo3d implementation of {@link DistanceCalculator}
*
* @lucene.experimental
*/
public class Geo3dDistanceCalculator implements DistanceCalculator {
protected final PlanetModel planetModel;
public Geo3dDistanceCalculator(PlanetModel planetModel) {
this.planetModel = planetModel;
}
@Override
public double distance(Point from, Point to) {
if (from instanceof Geo3dPointShape && to instanceof Geo3dPointShape) {
GeoPointShape pointShape1 = ((Geo3dPointShape) from).shape;
GeoPointShape pointShape2 = ((Geo3dPointShape) to).shape;
return planetModel.surfaceDistance(pointShape1.getCenter(), pointShape2.getCenter()) * DistanceUtils.RADIANS_TO_DEGREES;
}
return distance(from, to.getX(), to.getY());
}
@Override
public double distance(Point from, double toX, double toY) {
GeoPoint fromGeoPoint;
if (from instanceof Geo3dPointShape) {
fromGeoPoint = (((Geo3dPointShape) from).shape).getCenter();
} else {
fromGeoPoint = new GeoPoint(planetModel,
from.getY() * DistanceUtils.DEGREES_TO_RADIANS,
from.getX() * DistanceUtils.DEGREES_TO_RADIANS);
}
GeoPoint toGeoPoint = new GeoPoint(planetModel,
toY * DistanceUtils.DEGREES_TO_RADIANS,
toX * DistanceUtils.DEGREES_TO_RADIANS);
return planetModel.surfaceDistance(fromGeoPoint, toGeoPoint) * DistanceUtils.RADIANS_TO_DEGREES;
}
@Override
public boolean within(Point from, double toX, double toY, double distance) {
return (distance < distance(from, toX, toY));
}
@Override
public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) {
// Algorithm using Vincenty's formulae (https://en.wikipedia.org/wiki/Vincenty%27s_formulae)
// which takes into account that planets may not be spherical.
//Code adaptation from http://www.movable-type.co.uk/scripts/latlong-vincenty.html
Geo3dPointShape geoFrom = (Geo3dPointShape) from;
GeoPoint point = (GeoPoint) geoFrom.shape;
double lat = point.getLatitude();
double lon = point.getLongitude();
double dist = DistanceUtils.DEGREES_TO_RADIANS * distDEG;
double bearing = DistanceUtils.DEGREES_TO_RADIANS * bearingDEG;
double sinα1 = Math.sin(bearing);
double cosα1 = Math.cos(bearing);
double tanU1 = (1 - planetModel.flattening) * Math.tan(lat);
double cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1));
double sinU1 = tanU1 * cosU1;
double σ1 = Math.atan2(tanU1, cosα1);
double sinα = cosU1 * sinα1;
double cosSqα = 1 - sinα * sinα;
double uSq = cosSqα * planetModel.squareRatio;// (planetModel.ab* planetModel.ab - planetModel.c*planetModel.c) / (planetModel.c*planetModel.c);
double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
double cos2σM;
double sinσ;
double cosσ;
double Δσ;
double σ = dist / (planetModel.c * A);
double σʹ;
double iterations = 0;
do {
cos2σM = Math.cos(2 * σ1 + σ);
sinσ = Math.sin(σ);
cosσ = Math.cos(σ);
Δσ = B * sinσ * (cos2σM + B / 4 * (cosσ * (-1 + 2 * cos2σM * cos2σM) -
B / 6 * cos2σM * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σM * cos2σM)));
σʹ = σ;
σ = dist / (planetModel.c * A) + Δσ;
} while (Math.abs(σ - σʹ) > 1e-12 && ++iterations < 200);
if (iterations >= 200) {
throw new RuntimeException("Formula failed to converge");
}
double x = sinU1 * sinσ - cosU1 * cosσ * cosα1;
double φ2 = Math.atan2(sinU1 * cosσ + cosU1 * sinσ * cosα1, (1 - planetModel.flattening) * Math.sqrt(sinα * sinα + x * x));
double λ = Math.atan2(sinσ * sinα1, cosU1 * cosσ - sinU1 * sinσ * cosα1);
double C = planetModel.flattening / 16 * cosSqα * (4 + planetModel.flattening * (4 - 3 * cosSqα));
double L = λ - (1 - C) * planetModel.flattening * sinα *
(σ + C * sinσ * (cos2σM + C * cosσ * (-1 + 2 * cos2σM * cos2σM)));
double λ2 = (lon + L + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180
return ctx.getShapeFactory().pointXY(λ2 * DistanceUtils.RADIANS_TO_DEGREES,
φ2 * DistanceUtils.RADIANS_TO_DEGREES);
}
@Override
public Rectangle calcBoxByDistFromPt(Point from, double distDEG, SpatialContext ctx, Rectangle reuse) {
Circle circle = ctx.getShapeFactory().circle(from, distDEG);
return circle.getBoundingBox();
}
@Override
public double calcBoxByDistFromPt_yHorizAxisDEG(Point from, double distDEG, SpatialContext ctx) {
throw new UnsupportedOperationException();
}
@Override
public double area(Rectangle rect) {
throw new UnsupportedOperationException();
}
@Override
public double area(Circle circle) {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.spatial4j;
import org.apache.lucene.spatial3d.geom.GeoPointShape;
import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
/**
* Specialization of a {@link Geo3dShape} which represents a {@link Point}.
*
* @lucene.experimental
*/
public class Geo3dPointShape extends Geo3dShape<GeoPointShape> implements Point {
public Geo3dPointShape(final GeoPointShape shape, final SpatialContext spatialcontext) {
super(shape, spatialcontext);
center = this;
}
@Override
public void reset(double x, double y) {
shape = GeoPointShapeFactory.makeGeoPointShape(shape.getPlanetModel(),
y * DistanceUtils.DEGREES_TO_RADIANS,
x * DistanceUtils.DEGREES_TO_RADIANS);
center = this;
boundingBox = null;
}
@Override
public double getX() {
return shape.getCenter().getLongitude() * DistanceUtils.RADIANS_TO_DEGREES;
}
@Override
public double getY() {
return shape.getCenter().getLatitude() * DistanceUtils.RADIANS_TO_DEGREES;
}
@Override
public Rectangle getBoundingBox() {
Rectangle bbox = this.boundingBox;//volatile read once
if (bbox == null) {
bbox = new Geo3dRectangleShape(shape, spatialcontext);
this.boundingBox = bbox;
}
return bbox;
}
@Override
public Shape getBuffered(double distance, SpatialContext spatialContext) {
return spatialContext.getShapeFactory().circle(getX(), getY(), distance);
}
@Override
public boolean hasArea() {
return false;
}
}

View File

@ -0,0 +1,163 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.spatial4j;
import org.apache.lucene.spatial3d.geom.GeoBBox;
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory;
import org.apache.lucene.spatial3d.geom.LatLonBounds;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.SpatialRelation;
/**
* Specialization of a {@link Geo3dShape} which represents a {@link Rectangle}.
*
* @lucene.experimental
*/
public class Geo3dRectangleShape extends Geo3dShape<GeoBBox> implements Rectangle {
private double minX;
private double maxX;
private double minY;
private double maxY;
public Geo3dRectangleShape(final GeoBBox shape,
final SpatialContext spatialcontext,
double minX,
double maxX,
double minY,
double maxY) {
super(shape, spatialcontext);
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
}
public Geo3dRectangleShape(final GeoBBox shape, final SpatialContext spatialcontext) {
super(shape, spatialcontext);
setBoundsFromshape();
}
/**
* Set the bounds from the wrapped GeoBBox.
*/
private void setBoundsFromshape() {
LatLonBounds bounds = new LatLonBounds();
shape.getBounds(bounds);
minX = bounds.checkNoLongitudeBound() ? -180.0 : bounds.getLeftLongitude() * DistanceUtils.RADIANS_TO_DEGREES;
minY = bounds.checkNoBottomLatitudeBound() ? -90.0 : bounds.getMinLatitude() * DistanceUtils.RADIANS_TO_DEGREES;
maxX = bounds.checkNoLongitudeBound() ? 180.0 : bounds.getRightLongitude() * DistanceUtils.RADIANS_TO_DEGREES;
maxY = bounds.checkNoTopLatitudeBound() ? 90.0 : bounds.getMaxLatitude() * DistanceUtils.RADIANS_TO_DEGREES;
}
@Override
public Point getCenter() {
Point center = this.center;//volatile read once
if (center == null) {
GeoPoint point = shape.getCenter();
center = new Geo3dPointShape(
GeoPointShapeFactory.makeGeoPointShape(shape.getPlanetModel(),
point.getLatitude(),
point.getLongitude()),
spatialcontext);
this.center = center;
}
return center;
}
@Override
public void reset(double minX, double maxX, double minY, double maxY) {
shape = GeoBBoxFactory.makeGeoBBox(shape.getPlanetModel(),
maxY * DistanceUtils.DEGREES_TO_RADIANS,
minY * DistanceUtils.DEGREES_TO_RADIANS,
minX * DistanceUtils.DEGREES_TO_RADIANS,
maxX * DistanceUtils.DEGREES_TO_RADIANS);
center = null;
boundingBox = null;
}
@Override
public Rectangle getBoundingBox() {
return this;
}
@Override
public double getWidth() {
double result = getMaxX() - getMinX();
if (result < 0) {
result += 360;
}
return result;
}
@Override
public double getHeight() {
return getMaxY() - getMinY();
}
@Override
public double getMinX() {
return minX;
}
@Override
public double getMinY() {
return minY;
}
@Override
public double getMaxX() {
return maxX;
}
@Override
public double getMaxY() {
return maxY;
}
@Override
public boolean getCrossesDateLine() {
return (getMaxX() > 0 && getMinX() < 0);
}
@Override
public SpatialRelation relateYRange(double minY, double maxY) {
Rectangle r = spatialcontext.getShapeFactory().rect(-180, 180, minY, maxY);
return relate(r);
}
@Override
public SpatialRelation relateXRange(double minX, double maxX) {
Rectangle r = spatialcontext.getShapeFactory().rect(minX, maxX, -90, 90);
return relate(r);
}
@Override
public Shape getBuffered(double distance, SpatialContext spatialContext) {
GeoBBox bBox = shape.expand(distance * DistanceUtils.DEGREES_TO_RADIANS);
return new Geo3dRectangleShape(bBox, spatialContext);
}
}

View File

@ -14,127 +14,110 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.spatial4j;
import org.apache.lucene.spatial3d.geom.GeoArea;
import org.apache.lucene.spatial3d.geom.GeoAreaFactory;
import org.apache.lucene.spatial3d.geom.GeoAreaShape;
import org.apache.lucene.spatial3d.geom.GeoBBox;
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.LatLonBounds;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.SpatialRelation;
import org.locationtech.spatial4j.shape.impl.RectangleImpl;
import org.apache.lucene.spatial3d.geom.LatLonBounds;
import org.apache.lucene.spatial3d.geom.GeoArea;
import org.apache.lucene.spatial3d.geom.GeoAreaFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoShape;
import org.apache.lucene.spatial3d.geom.PlanetModel;
/**
* A Spatial4j Shape wrapping a {@link GeoShape} ("Geo3D") -- a 3D planar geometry based Spatial4j Shape implementation.
* A Spatial4j Shape wrapping a {@link GeoAreaShape} ("Geo3D") -- a 3D planar geometry
* based Spatial4j Shape implementation.
* Geo3D implements shapes on the surface of a sphere or ellipsoid.
*
* @param <T> is the type of {@link GeoAreaShape}
* @lucene.experimental
*/
public class Geo3dShape implements Shape {
/** The required size of this adjustment depends on the actual planetary model chosen.
* This value is big enough to account for WGS84. */
protected static final double ROUNDOFF_ADJUSTMENT = 0.05;
public final SpatialContext ctx;
public final GeoShape shape;
public final PlanetModel planetModel;
public class Geo3dShape<T extends GeoAreaShape> implements Shape {
private volatile Rectangle boundingBox = null; // lazy initialized
protected final SpatialContext spatialcontext;
public Geo3dShape(final GeoShape shape, final SpatialContext ctx) {
this(PlanetModel.SPHERE, shape, ctx);
}
protected T shape;
protected volatile Rectangle boundingBox = null; // lazy initialized
protected volatile Point center = null; // lazy initialized
public Geo3dShape(final PlanetModel planetModel, final GeoShape shape, final SpatialContext ctx) {
if (!ctx.isGeo()) {
throw new IllegalArgumentException("SpatialContext.isGeo() must be true");
}
this.ctx = ctx;
this.planetModel = planetModel;
public Geo3dShape(final T shape, final SpatialContext spatialcontext) {
this.spatialcontext = spatialcontext;
this.shape = shape;
}
@Override
public SpatialContext getContext() {
return ctx;
}
@Override
public SpatialRelation relate(Shape other) {
if (other instanceof Rectangle)
return relate((Rectangle)other);
else if (other instanceof Point)
return relate((Point)other);
else
int relationship;
if (other instanceof Geo3dShape<?>) {
relationship = relate((Geo3dShape<?>) other);
} else if (other instanceof Rectangle) {
relationship = relate((Rectangle) other);
} else if (other instanceof Point) {
relationship = relate((Point) other);
} else {
throw new RuntimeException("Unimplemented shape relationship determination: " + other.getClass());
}
protected SpatialRelation relate(Rectangle r) {
switch (relationship) {
case GeoArea.DISJOINT:
return SpatialRelation.DISJOINT;
case GeoArea.OVERLAPS:
return (other instanceof Point ? SpatialRelation.CONTAINS : SpatialRelation.INTERSECTS);
case GeoArea.CONTAINS:
return (other instanceof Point ? SpatialRelation.CONTAINS : SpatialRelation.WITHIN);
case GeoArea.WITHIN:
return SpatialRelation.CONTAINS;
}
throw new RuntimeException("Undetermined shape relationship: " + relationship);
}
private int relate(Geo3dShape<?> s) {
return shape.getRelationship(s.shape);
}
private int relate(Rectangle r) {
// Construct the right kind of GeoArea first
GeoArea geoArea = GeoAreaFactory.makeGeoArea(planetModel,
GeoArea geoArea = GeoAreaFactory.makeGeoArea(shape.getPlanetModel(),
r.getMaxY() * DistanceUtils.DEGREES_TO_RADIANS,
r.getMinY() * DistanceUtils.DEGREES_TO_RADIANS,
r.getMinX() * DistanceUtils.DEGREES_TO_RADIANS,
r.getMaxX() * DistanceUtils.DEGREES_TO_RADIANS);
int relationship = geoArea.getRelationship(shape);
if (relationship == GeoArea.WITHIN)
return SpatialRelation.WITHIN;
else if (relationship == GeoArea.CONTAINS)
return SpatialRelation.CONTAINS;
else if (relationship == GeoArea.OVERLAPS)
return SpatialRelation.INTERSECTS;
else if (relationship == GeoArea.DISJOINT)
return SpatialRelation.DISJOINT;
else
throw new RuntimeException("Unknown relationship returned: "+relationship);
return geoArea.getRelationship(shape);
}
protected SpatialRelation relate(Point p) {
// Create a GeoPoint
GeoPoint point = new GeoPoint(planetModel, p.getY()* DistanceUtils.DEGREES_TO_RADIANS, p.getX()* DistanceUtils.DEGREES_TO_RADIANS);
private int relate(Point p) {
GeoPoint point = new GeoPoint(shape.getPlanetModel(),
p.getY() * DistanceUtils.DEGREES_TO_RADIANS,
p.getX() * DistanceUtils.DEGREES_TO_RADIANS);
if (shape.isWithin(point)) {
// Point within shape
return SpatialRelation.CONTAINS;
return GeoArea.WITHIN;
}
return SpatialRelation.DISJOINT;
return GeoArea.DISJOINT;
}
@Override
public Rectangle getBoundingBox() {
Rectangle bbox = this.boundingBox;//volatile read once
if (bbox == null) {
LatLonBounds bounds = new LatLonBounds();
shape.getBounds(bounds);
double leftLon;
double rightLon;
if (bounds.checkNoLongitudeBound()) {
leftLon = -180.0;
rightLon = 180.0;
} else {
leftLon = bounds.getLeftLongitude().doubleValue() * DistanceUtils.RADIANS_TO_DEGREES;
rightLon = bounds.getRightLongitude().doubleValue() * DistanceUtils.RADIANS_TO_DEGREES;
}
double minLat;
if (bounds.checkNoBottomLatitudeBound()) {
minLat = -90.0;
} else {
minLat = bounds.getMinLatitude().doubleValue() * DistanceUtils.RADIANS_TO_DEGREES;
}
double maxLat;
if (bounds.checkNoTopLatitudeBound()) {
maxLat = 90.0;
} else {
maxLat = bounds.getMaxLatitude().doubleValue() * DistanceUtils.RADIANS_TO_DEGREES;
}
bbox = new RectangleImpl(leftLon, rightLon, minLat, maxLat, ctx).getBuffered(ROUNDOFF_ADJUSTMENT, ctx);
double leftLon = bounds.checkNoLongitudeBound() ? -Math.PI : bounds.getLeftLongitude();
double rightLon = bounds.checkNoLongitudeBound() ? Math.PI : bounds.getRightLongitude();
double minLat = bounds.checkNoBottomLatitudeBound() ? -Math.PI * 0.5 : bounds.getMinLatitude();
double maxLat = bounds.checkNoTopLatitudeBound() ? Math.PI * 0.5 : bounds.getMaxLatitude();
GeoBBox geoBBox = GeoBBoxFactory.makeGeoBBox(shape.getPlanetModel(), maxLat, minLat, leftLon, rightLon);
bbox = new Geo3dRectangleShape(geoBBox, spatialcontext);
this.boundingBox = bbox;
}
return bbox;
@ -146,17 +129,22 @@ public class Geo3dShape implements Shape {
}
@Override
public double getArea(SpatialContext ctx) {
public double getArea(SpatialContext spatialContext) {
throw new UnsupportedOperationException();
}
@Override
public Point getCenter() {
throw new UnsupportedOperationException();
Point center = this.center;//volatile read once
if (center == null) {
center = getBoundingBox().getCenter();
this.center = center;
}
return center;
}
@Override
public Shape getBuffered(double distance, SpatialContext ctx) {
public Shape getBuffered(double distance, SpatialContext spatialContext) {
throw new UnsupportedOperationException();
}
@ -166,20 +154,20 @@ public class Geo3dShape implements Shape {
}
@Override
public String toString() {
return "Geo3dShape{planetmodel=" + planetModel + ", shape=" + shape + '}';
public SpatialContext getContext() {
return spatialcontext;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Geo3dShape))
public boolean equals(Object o) {
if (!(o instanceof Geo3dShape<?>))
return false;
Geo3dShape tr = (Geo3dShape)other;
return tr.ctx.equals(ctx) && tr.planetModel.equals(planetModel) && tr.shape.equals(shape);
final Geo3dShape<?> other = (Geo3dShape<?>) o;
return (other.spatialcontext.equals(spatialcontext) && other.shape.equals(shape));
}
@Override
public int hashCode() {
return planetModel.hashCode() + shape.hashCode();
return spatialcontext.hashCode() + shape.hashCode();
}
}

View File

@ -0,0 +1,396 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.spatial4j;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.spatial3d.geom.GeoBBox;
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
import org.apache.lucene.spatial3d.geom.GeoCircle;
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
import org.apache.lucene.spatial3d.geom.GeoCompositeAreaShape;
import org.apache.lucene.spatial3d.geom.GeoPath;
import org.apache.lucene.spatial3d.geom.GeoPathFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPointShape;
import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory;
import org.apache.lucene.spatial3d.geom.GeoPolygon;
import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeCollection;
import org.locationtech.spatial4j.shape.ShapeFactory;
/**
* Geo3d implementation of {@link ShapeFactory}
*
* @lucene.experimental
*/
public class Geo3dShapeFactory implements ShapeFactory {
private final boolean normWrapLongitude;
private SpatialContext context;
private PlanetModel planetModel;
@SuppressWarnings("unchecked")
public Geo3dShapeFactory(SpatialContext context, SpatialContextFactory factory) {
this.context = context;
this.planetModel = ((Geo3dSpatialContextFactory) factory).planetModel;
this.normWrapLongitude = context.isGeo() && factory.normWrapLongitude;
}
@Override
public SpatialContext getSpatialContext() {
return context;
}
@Override
public boolean isNormWrapLongitude() {
return normWrapLongitude;
}
@Override
public double normX(double x) {
if (this.normWrapLongitude) {
x = DistanceUtils.normLonDEG(x);
}
return x;
}
@Override
public double normY(double y) {
return y;
}
@Override
public double normZ(double z) {
return z;
}
@Override
public double normDist(double distance) {
return distance;
}
@Override
public void verifyX(double x) {
Rectangle bounds = this.context.getWorldBounds();
if (x < bounds.getMinX() || x > bounds.getMaxX()) {
throw new InvalidShapeException("Bad X value " + x + " is not in boundary " + bounds);
}
}
@Override
public void verifyY(double y) {
Rectangle bounds = this.context.getWorldBounds();
if (y < bounds.getMinY() || y > bounds.getMaxY()) {
throw new InvalidShapeException("Bad Y value " + y + " is not in boundary " + bounds);
}
}
@Override
public void verifyZ(double v) {
}
@Override
public Point pointXY(double x, double y) {
GeoPointShape point = GeoPointShapeFactory.makeGeoPointShape(planetModel,
y * DistanceUtils.DEGREES_TO_RADIANS,
x * DistanceUtils.DEGREES_TO_RADIANS);
return new Geo3dPointShape(point, context);
}
@Override
public Point pointXYZ(double x, double y, double z) {
GeoPoint point = new GeoPoint(x, y, z);
GeoPointShape pointShape = GeoPointShapeFactory.makeGeoPointShape(planetModel,
point.getLatitude(),
point.getLongitude());
return new Geo3dPointShape(pointShape, context);
//throw new UnsupportedOperationException();
}
@Override
public Rectangle rect(Point point, Point point1) {
return rect(point.getX(), point1.getX(), point.getY(), point1.getY());
}
@Override
public Rectangle rect(double minX, double maxX, double minY, double maxY) {
GeoBBox bBox = GeoBBoxFactory.makeGeoBBox(planetModel,
maxY * DistanceUtils.DEGREES_TO_RADIANS,
minY * DistanceUtils.DEGREES_TO_RADIANS,
minX * DistanceUtils.DEGREES_TO_RADIANS,
maxX * DistanceUtils.DEGREES_TO_RADIANS);
return new Geo3dRectangleShape(bBox, context, minX, maxX, minY, maxY);
}
@Override
public Circle circle(double x, double y, double distance) {
GeoCircle circle = GeoCircleFactory.makeGeoCircle(planetModel,
y * DistanceUtils.DEGREES_TO_RADIANS,
x * DistanceUtils.DEGREES_TO_RADIANS,
distance * DistanceUtils.DEGREES_TO_RADIANS);
return new Geo3dCircleShape(circle, context);
}
@Override
public Circle circle(Point point, double distance) {
return circle(point.getX(), point.getY(), distance);
}
@Override
public Shape lineString(List<Point> list, double distance) {
LineStringBuilder builder = lineString();
for (Point point : list) {
builder.pointXY(point.getX(), point.getY());
}
builder.buffer(distance);
return builder.build();
}
@Override
public <S extends Shape> ShapeCollection<S> multiShape(List<S> list) {
throw new UnsupportedOperationException();
}
@Override
public LineStringBuilder lineString() {
return new Geo3dLineStringBuilder();
}
@Override
public PolygonBuilder polygon() {
return new Geo3dPolygonBuilder();
}
@Override
public <T extends Shape> MultiShapeBuilder<T> multiShape(Class<T> aClass) {
return new Geo3dMultiShapeBuilder<>();
}
@Override
public MultiPointBuilder multiPoint() {
return new Geo3dMultiPointBuilder();
}
@Override
public MultiLineStringBuilder multiLineString() {
return new Geo3dMultiLineBuilder();
}
@Override
public MultiPolygonBuilder multiPolygon() {
return new Geo3dMultiPolygonBuilder();
}
/**
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.PointsBuilder} interface to
* generate {@link GeoPoint}.
*
* @param <T> is normally this object
*/
private class Geo3dPointBuilder<T> implements PointsBuilder<T> {
List<GeoPoint> points = new ArrayList<>();
@SuppressWarnings("unchecked")
@Override
public T pointXY(double x, double y) {
GeoPoint point = new GeoPoint(planetModel, y * DistanceUtils.DEGREES_TO_RADIANS, x * DistanceUtils.DEGREES_TO_RADIANS);
points.add(point);
return (T) this;
}
@SuppressWarnings("unchecked")
@Override
public T pointXYZ(double x, double y, double z) {
GeoPoint point = new GeoPoint(x, y, z);
if (!points.contains(point)) {
points.add(point);
}
return (T) this;
}
}
/**
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.LineStringBuilder} to generate
* nine Strings. Note that GeoPath needs a buffer so we set the
* buffer to 1e-10.
*/
private class Geo3dLineStringBuilder extends Geo3dPointBuilder<LineStringBuilder> implements LineStringBuilder {
double distance = 0;
@Override
public LineStringBuilder buffer(double distance) {
this.distance = distance;
return this;
}
@Override
public Shape build() {
GeoPath path = GeoPathFactory.makeGeoPath(planetModel, distance, points.toArray(new GeoPoint[points.size()]));
return new Geo3dShape<>(path, context);
}
}
/**
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.PolygonBuilder} to generate
* polygons.
*/
private class Geo3dPolygonBuilder extends Geo3dPointBuilder<PolygonBuilder> implements PolygonBuilder {
List<GeoPolygon> polyHoles;
@Override
public HoleBuilder hole() {
return new Geo3dHoleBuilder();
}
class Geo3dHoleBuilder extends Geo3dPointBuilder<PolygonBuilder.HoleBuilder> implements PolygonBuilder.HoleBuilder {
@Override
public PolygonBuilder endHole() {
if (polyHoles == null) {
polyHoles = new ArrayList<>();
}
polyHoles.add(GeoPolygonFactory.makeGeoPolygon(planetModel, points));
return Geo3dPolygonBuilder.this;
}
}
@SuppressWarnings("unchecked")
@Override
public Shape build() {
GeoPolygon polygon = GeoPolygonFactory.makeGeoPolygon(planetModel, points, polyHoles);
return new Geo3dShape<>(polygon, context);
}
@Override
public Shape buildOrRect() {
return build();
}
}
private class Geo3dMultiPointBuilder extends Geo3dPointBuilder<MultiPointBuilder> implements MultiPointBuilder {
@Override
public Shape build() {
GeoCompositeAreaShape areaShape = new GeoCompositeAreaShape(planetModel);
for (GeoPoint point : points) {
GeoPointShape pointShape = GeoPointShapeFactory.makeGeoPointShape(planetModel, point.getLatitude(), point.getLongitude());
areaShape.addShape(pointShape);
}
return new Geo3dShape<>(areaShape, context);
}
}
/**
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiLineStringBuilder} to generate
* multi-lines
*/
private class Geo3dMultiLineBuilder implements MultiLineStringBuilder {
List<LineStringBuilder> builders = new ArrayList<>();
@Override
public LineStringBuilder lineString() {
return new Geo3dLineStringBuilder();
}
@Override
public MultiLineStringBuilder add(LineStringBuilder lineStringBuilder) {
builders.add(lineStringBuilder);
return this;
}
@SuppressWarnings("unchecked")
@Override
public Shape build() {
GeoCompositeAreaShape areaShape = new GeoCompositeAreaShape(planetModel);
for (LineStringBuilder builder : builders) {
Geo3dShape<GeoPolygon> shape = (Geo3dShape<GeoPolygon>) builder.build();
areaShape.addShape(shape.shape);
}
return new Geo3dShape<>(areaShape, context);
}
}
/**
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiPolygonBuilder} to generate
* multi-polygons. We have chosen to use a composite shape but
* it might be possible to use GeoComplexPolygon.
*/
private class Geo3dMultiPolygonBuilder implements MultiPolygonBuilder {
List<PolygonBuilder> builders = new ArrayList<>();
@Override
public PolygonBuilder polygon() {
return new Geo3dPolygonBuilder();
}
@Override
public MultiPolygonBuilder add(PolygonBuilder polygonBuilder) {
builders.add(polygonBuilder);
return this;
}
@SuppressWarnings("unchecked")
@Override
public Shape build() {
GeoCompositeAreaShape areaShape = new GeoCompositeAreaShape(planetModel);
for (PolygonBuilder builder : builders) {
Geo3dShape<GeoPolygon> shape = (Geo3dShape<GeoPolygon>) builder.build();
areaShape.addShape(shape.shape);
}
return new Geo3dShape<>(areaShape, context);
}
}
/**
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiShapeBuilder} to generate
* geometry collections
*
* @param <T> is the type of shapes.
*/
private class Geo3dMultiShapeBuilder<T extends Shape> implements MultiShapeBuilder<T> {
GeoCompositeAreaShape composite = new GeoCompositeAreaShape(planetModel);
@Override
public MultiShapeBuilder<T> add(T shape) {
Geo3dShape<?> areaShape = (Geo3dShape<?>) shape;
composite.addShape(areaShape.shape);
return this;
}
@Override
public Shape build() {
return new Geo3dShape<>(composite, context);
}
}
}

View File

@ -0,0 +1,94 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.spatial4j;
import java.util.Map;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
/**
* Geo3d implementation of {@link SpatialContextFactory}
*
* @lucene.experimental
*/
public class Geo3dSpatialContextFactory extends SpatialContextFactory {
/**
* The default planet model
*/
private static final PlanetModel DEFAULT_PLANET_MODEL = PlanetModel.SPHERE;
/**
* The planet model
*/
public PlanetModel planetModel;
/**
* Empty Constructor.
*/
public Geo3dSpatialContextFactory() {
this.binaryCodecClass = Geo3dBinaryCodec.class;
this.shapeFactoryClass = Geo3dShapeFactory.class;
}
@Override
public SpatialContext newSpatialContext() {
if (planetModel == null) {
planetModel = DEFAULT_PLANET_MODEL;
}
if (distCalc == null) {
this.distCalc = new Geo3dDistanceCalculator(planetModel);
}
return new SpatialContext(this);
}
@Override
protected void init(Map<String, String> args, ClassLoader classLoader) {
initPlanetModel(args);
super.init(args, classLoader);
}
protected void initPlanetModel(Map<String, String> args) {
String planetModel = args.get("planetModel");
if (planetModel != null) {
if (planetModel.equalsIgnoreCase("sphere")) {
this.planetModel = PlanetModel.SPHERE;
} else if (planetModel.equalsIgnoreCase("wgs84")) {
this.planetModel = PlanetModel.WGS84;
} else {
throw new RuntimeException("Unknown planet model: " + planetModel);
}
} else {
this.planetModel = DEFAULT_PLANET_MODEL;
}
}
@Override
protected void initCalculator() {
String calcStr = this.args.get("distCalculator");
if (calcStr == null) {
return;
} else if (calcStr.equals("geo3d")) {
this.distCalc = new Geo3dDistanceCalculator(planetModel);
} else {
super.initCalculator(); // some other distance calculator
}
}
}

View File

@ -16,18 +16,18 @@
*/
package org.apache.lucene.spatial;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.spatial4j.Geo3dSpatialContextFactory;
import org.apache.lucene.util.LuceneTestCase;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class SpatialArgsTest {
public class SpatialArgsTest extends LuceneTestCase {
@Test
public void calcDistanceFromErrPct() {
final SpatialContext ctx = SpatialContext.GEO;
final SpatialContext ctx = usually() ? SpatialContext.GEO : new Geo3dSpatialContextFactory().newSpatialContext();
final double DEP = 0.5;//distErrPct
//the result is the diagonal distance from the center to the closest corner,

View File

@ -28,16 +28,15 @@ import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
import org.apache.lucene.spatial3d.geom.GeoAreaShape;
import org.apache.lucene.spatial3d.geom.GeoPath;
import org.apache.lucene.spatial3d.geom.GeoPathFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
import org.apache.lucene.spatial3d.geom.GeoShape;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.apache.lucene.spatial3d.geom.RandomGeo3dShapeGenerator;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
@ -45,11 +44,10 @@ import static org.locationtech.spatial4j.distance.DistanceUtils.DEGREES_TO_RADIA
public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
private PlanetModel planetModel;
private RandomGeo3dShapeGenerator shapeGenerator;
private SpatialPrefixTree grid;
private RecursivePrefixTreeStrategy rptStrategy;
{
this.ctx = SpatialContext.GEO;
}
private void setupGeohashGrid() {
this.grid = new GeohashPrefixTree(ctx, 2);//A fairly shallow grid
@ -64,7 +62,12 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
}
private void setupStrategy() {
//setup
shapeGenerator = new RandomGeo3dShapeGenerator();
planetModel = shapeGenerator.randomPlanetModel();
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
factory.planetModel = planetModel;
ctx = factory.newSpatialContext();
setupGeohashGrid();
SerializedDVStrategy serializedDVStrategy = new SerializedDVStrategy(ctx, getClass().getSimpleName() + "_sdv");
@ -76,12 +79,12 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
public void testFailure1() throws IOException {
setupStrategy();
final List<GeoPoint> points = new ArrayList<GeoPoint>();
points.add(new GeoPoint(PlanetModel.SPHERE, 18 * DEGREES_TO_RADIANS, -27 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(PlanetModel.SPHERE, -57 * DEGREES_TO_RADIANS, 146 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(PlanetModel.SPHERE, 14 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(PlanetModel.SPHERE, -15 * DEGREES_TO_RADIANS, 153 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(planetModel, 18 * DEGREES_TO_RADIANS, -27 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(planetModel, -57 * DEGREES_TO_RADIANS, 146 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(planetModel, 14 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(planetModel, -15 * DEGREES_TO_RADIANS, 153 * DEGREES_TO_RADIANS));
final Shape triangle = new Geo3dShape(GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, points),ctx);
final Shape triangle = new Geo3dShape(GeoPolygonFactory.makeGeoPolygon(planetModel, points),ctx);
final Rectangle rect = ctx.makeRectangle(-49, -45, 73, 86);
testOperation(rect,SpatialOperation.Intersects,triangle, false);
}
@ -91,16 +94,16 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
setupStrategy();
final List<GeoPoint> points = new ArrayList<>();
points.add(new GeoPoint(PlanetModel.SPHERE, 18 * DEGREES_TO_RADIANS, -27 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(PlanetModel.SPHERE, -57 * DEGREES_TO_RADIANS, 146 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(PlanetModel.SPHERE, 14 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(PlanetModel.SPHERE, -15 * DEGREES_TO_RADIANS, 153 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(planetModel, 18 * DEGREES_TO_RADIANS, -27 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(planetModel, -57 * DEGREES_TO_RADIANS, 146 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(planetModel, 14 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS));
points.add(new GeoPoint(planetModel, -15 * DEGREES_TO_RADIANS, 153 * DEGREES_TO_RADIANS));
final GeoPoint[] pathPoints = new GeoPoint[] {
new GeoPoint(PlanetModel.SPHERE, 55.0 * DEGREES_TO_RADIANS, -26.0 * DEGREES_TO_RADIANS),
new GeoPoint(PlanetModel.SPHERE, -90.0 * DEGREES_TO_RADIANS, 0.0),
new GeoPoint(PlanetModel.SPHERE, 54.0 * DEGREES_TO_RADIANS, 165.0 * DEGREES_TO_RADIANS),
new GeoPoint(PlanetModel.SPHERE, -90.0 * DEGREES_TO_RADIANS, 0.0)};
final GeoShape path = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, 29 * DEGREES_TO_RADIANS, pathPoints);
new GeoPoint(planetModel, 55.0 * DEGREES_TO_RADIANS, -26.0 * DEGREES_TO_RADIANS),
new GeoPoint(planetModel, -90.0 * DEGREES_TO_RADIANS, 0.0),
new GeoPoint(planetModel, 54.0 * DEGREES_TO_RADIANS, 165.0 * DEGREES_TO_RADIANS),
new GeoPoint(planetModel, -90.0 * DEGREES_TO_RADIANS, 0.0)};
final GeoPath path = GeoPathFactory.makeGeoPath(planetModel, 29 * DEGREES_TO_RADIANS, pathPoints);
final Shape shape = new Geo3dShape(path,ctx);
final Rectangle rect = ctx.makeRectangle(131, 143, 39, 54);
testOperation(rect,SpatialOperation.Intersects,shape,true);
@ -114,111 +117,57 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
testOperationRandomShapes(SpatialOperation.Intersects);
}
private Shape makeTriangle(double x1, double y1, double x2, double y2, double x3, double y3) {
final List<GeoPoint> geoPoints = new ArrayList<>();
geoPoints.add(new GeoPoint(PlanetModel.SPHERE, y1 * DEGREES_TO_RADIANS, x1 * DEGREES_TO_RADIANS));
geoPoints.add(new GeoPoint(PlanetModel.SPHERE, y2 * DEGREES_TO_RADIANS, x2 * DEGREES_TO_RADIANS));
geoPoints.add(new GeoPoint(PlanetModel.SPHERE, y3 * DEGREES_TO_RADIANS, x3 * DEGREES_TO_RADIANS));
final GeoShape shape = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, geoPoints);
return new Geo3dShape(shape, ctx);
}
@Override
protected Shape randomIndexedShape() {
return randomRectangle();
int type = shapeGenerator.randomShapeType();
GeoAreaShape areaShape = shapeGenerator.randomGeoAreaShape(type, planetModel);
return new Geo3dShape<>(areaShape, ctx);
}
@Override
protected Shape randomQueryShape() {
final int shapeType = random().nextInt(4);
switch (shapeType) {
case 0: {
// Polygons
final int vertexCount = random().nextInt(3) + 3;
while (true) {
final List<GeoPoint> geoPoints = new ArrayList<>();
while (geoPoints.size() < vertexCount) {
final Point point = randomPoint();
final GeoPoint gPt = new GeoPoint(PlanetModel.SPHERE, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS);
geoPoints.add(gPt);
}
final int convexPointIndex = random().nextInt(vertexCount); //If we get this wrong, hopefully we get IllegalArgumentException
try {
final GeoShape shape = GeoPolygonFactory.makeGeoPolygon(PlanetModel.SPHERE, geoPoints);
if (shape == null) {
continue;
}
return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
case 1: {
// Circles
while (true) {
final int circleRadius = random().nextInt(179) + 1;
final Point point = randomPoint();
try {
final GeoShape shape = GeoCircleFactory.makeGeoCircle(PlanetModel.SPHERE, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS,
circleRadius * DEGREES_TO_RADIANS);
return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
case 2: {
// Rectangles
while (true) {
Point ulhcPoint = randomPoint();
Point lrhcPoint = randomPoint();
if (ulhcPoint.getY() < lrhcPoint.getY()) {
//swap
Point temp = ulhcPoint;
ulhcPoint = lrhcPoint;
lrhcPoint = temp;
}
try {
final GeoShape shape = GeoBBoxFactory.makeGeoBBox(PlanetModel.SPHERE, ulhcPoint.getY() * DEGREES_TO_RADIANS,
lrhcPoint.getY() * DEGREES_TO_RADIANS,
ulhcPoint.getX() * DEGREES_TO_RADIANS,
lrhcPoint.getX() * DEGREES_TO_RADIANS);
//System.err.println("Trial rectangle shape: "+shape);
return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
case 3: {
// Paths
final int pointCount = random().nextInt(5) + 1;
final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS;
final GeoPoint[] points = new GeoPoint[pointCount];
while (true) {
for (int i = 0; i < pointCount; i++) {
final Point nextPoint = randomPoint();
points[i] = new GeoPoint(PlanetModel.SPHERE, nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS);
}
try {
final GeoShape path = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, width, points);
return new Geo3dShape(path, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
default:
throw new IllegalStateException("Unexpected shape type");
int type = shapeGenerator.randomShapeType();
GeoAreaShape areaShape = shapeGenerator.randomGeoAreaShape(type, planetModel);
return new Geo3dShape<>(areaShape, ctx);
}
//TODO move to a new test class?
@Test
public void testWKT() throws Exception {
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
SpatialContext ctx = factory.newSpatialContext();
String wkt = "POLYGON ((20.0 -60.4, 20.1 -60.4, 20.1 -60.3, 20.0 -60.3,20.0 -60.4))";
Shape s = ctx.getFormats().getWktReader().read(wkt);
assertTrue(s instanceof Geo3dShape<?>);
wkt = "POINT (30 10)";
s = ctx.getFormats().getWktReader().read(wkt);
assertTrue(s instanceof Geo3dShape<?>);
wkt = "LINESTRING (30 10, 10 30, 40 40)";
s = ctx.getFormats().getWktReader().read(wkt);
assertTrue(s instanceof Geo3dShape<?>);
wkt = "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))";
s = ctx.getFormats().getWktReader().read(wkt);
assertTrue(s instanceof Geo3dShape<?>);
wkt = "MULTIPOINT ((10 40), (40 30), (20 20), (30 10))";
s = ctx.getFormats().getWktReader().read(wkt);
assertTrue(s instanceof Geo3dShape<?>);
wkt = "MULTILINESTRING ((10 10, 20 20, 10 40),(40 40, 30 30, 40 20, 30 10))";
s = ctx.getFormats().getWktReader().read(wkt);
assertTrue(s instanceof Geo3dShape<?>);
wkt = "MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20)))";
s = ctx.getFormats().getWktReader().read(wkt);
assertTrue(s instanceof Geo3dShape<?>);
wkt = "GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))";
s = ctx.getFormats().getWktReader().read(wkt);
assertTrue(s instanceof Geo3dShape<?>);
wkt = "ENVELOPE(1, 2, 4, 3)";
s = ctx.getFormats().getWktReader().read(wkt);
assertTrue(s instanceof Geo3dShape<?>);
wkt = "BUFFER(POINT(-10 30), 5.2)";
s = ctx.getFormats().getWktReader().read(wkt);
assertTrue(s instanceof Geo3dShape<?>);
//wkt = "BUFFER(LINESTRING(1 2, 3 4), 0.5)";
//s = ctx.getFormats().getWktReader().read(wkt);
//assertTrue(s instanceof Geo3dShape<?>);
}
}

View File

@ -19,6 +19,8 @@ package org.apache.lucene.spatial.spatial4j;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.spatial3d.geom.GeoPath;
import org.apache.lucene.spatial3d.geom.GeoPolygon;
import org.locationtech.spatial4j.TestLog;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
@ -119,9 +121,9 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest
protected Geo3dShape generateRandomShape(Point nearP) {
final int circleRadius = 180 - random().nextInt(180);//no 0-radius
final Point point = nearP;
final GeoShape shape = GeoCircleFactory.makeGeoCircle(planetModel, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS,
final GeoCircle shape = GeoCircleFactory.makeGeoCircle(planetModel, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS,
circleRadius * DEGREES_TO_RADIANS);
return new Geo3dShape(planetModel, shape, ctx);
return new Geo3dShape(shape, ctx);
}
@Override
@ -153,11 +155,11 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest
ulhcPoint = lrhcPoint;
lrhcPoint = temp;
}
final GeoShape shape = GeoBBoxFactory.makeGeoBBox(planetModel, ulhcPoint.getY() * DEGREES_TO_RADIANS,
final GeoBBox shape = GeoBBoxFactory.makeGeoBBox(planetModel, ulhcPoint.getY() * DEGREES_TO_RADIANS,
lrhcPoint.getY() * DEGREES_TO_RADIANS,
ulhcPoint.getX() * DEGREES_TO_RADIANS,
lrhcPoint.getX() * DEGREES_TO_RADIANS);
return new Geo3dShape(planetModel, shape, ctx);
return new Geo3dShape(shape, ctx);
}
@Override
@ -185,11 +187,11 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest
geoPoints.add(gPt);
}
try {
final GeoShape shape = GeoPolygonFactory.makeGeoPolygon(planetModel, geoPoints);
final GeoPolygon shape = GeoPolygonFactory.makeGeoPolygon(planetModel, geoPoints);
if (shape == null) {
continue;
}
return new Geo3dShape(planetModel, shape, ctx);
return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
@ -231,8 +233,8 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest
}
try {
final GeoShape path = GeoPathFactory.makeGeoPath(planetModel, width, points);
return new Geo3dShape(planetModel, path, ctx);
final GeoPath path = GeoPathFactory.makeGeoPath(planetModel, width, points);
return new Geo3dShape(path, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
@ -257,6 +259,6 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest
private Point geoPointToSpatial4jPoint(GeoPoint geoPoint) {
return ctx.makePoint(geoPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES,
geoPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES);
geoPoint.getLatitude() * DistanceUtils.RADIANS_TO_DEGREES);
}
}

View File

@ -19,21 +19,29 @@ package org.apache.lucene.spatial.spatial4j;
import java.util.ArrayList;
import java.util.List;
import org.locationtech.spatial4j.shape.Rectangle;
import org.apache.lucene.spatial3d.geom.GeoArea;
import org.apache.lucene.spatial3d.geom.GeoBBox;
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
import org.apache.lucene.spatial3d.geom.GeoCircle;
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
import org.apache.lucene.spatial3d.geom.GeoShape;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.junit.Test;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.SpatialRelation;
public class Geo3dShapeSphereModelRectRelationTest extends Geo3dShapeRectRelationTestCase {
public Geo3dShapeSphereModelRectRelationTest() {
super(PlanetModel.SPHERE);
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
factory.planetModel = PlanetModel.SPHERE;
//factory.distCalc = new GeodesicSphereDistCalc.Haversine();
this.ctx = factory.newSpatialContext();
}
@Test
@ -60,13 +68,28 @@ public class Geo3dShapeSphereModelRectRelationTest extends Geo3dShapeRectRelatio
@Test
public void testFailure2_LUCENE6475() {
GeoShape geo3dCircle = GeoCircleFactory.makeGeoCircle(planetModel, 1.6282053147165243E-4 * RADIANS_PER_DEGREE,
GeoCircle geo3dCircle = GeoCircleFactory.makeGeoCircle(planetModel, 1.6282053147165243E-4 * RADIANS_PER_DEGREE,
-70.1600629789353 * RADIANS_PER_DEGREE, 86 * RADIANS_PER_DEGREE);
Geo3dShape geo3dShape = new Geo3dShape(planetModel, geo3dCircle, ctx);
Geo3dShape geo3dShape = new Geo3dShape(geo3dCircle, ctx);
Rectangle rect = ctx.makeRectangle(-118, -114, -2.0, 32.0);
assertTrue(geo3dShape.relate(rect).intersects());
// thus the bounding box must intersect too
assertTrue(geo3dShape.getBoundingBox().relate(rect).intersects());
}
@Test
public void pointBearingTest(){
double radius = 136;
double distance = 135.97;
double bearing = 188;
Point p = ctx.getShapeFactory().pointXY(35, 85);
Circle circle = ctx.getShapeFactory().circle(p, radius);
Point bPoint = ctx.getDistCalc().pointOnBearing(p, distance, bearing, ctx, (Point) null);
double d = ctx.getDistCalc().distance(p, bPoint);
assertEquals(d, distance, 10-8);
assertEquals(circle.relate(bPoint), SpatialRelation.CONTAINS);
}
}

View File

@ -26,11 +26,18 @@ import org.apache.lucene.spatial3d.geom.GeoPath;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.junit.Test;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.SpatialRelation;
public class Geo3dShapeWGS84ModelRectRelationTest extends Geo3dShapeRectRelationTestCase {
public Geo3dShapeWGS84ModelRectRelationTest() {
super(PlanetModel.WGS84);
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
factory.planetModel = PlanetModel.WGS84;
//factory.distCalc = new GeodesicSphereDistCalc.Haversine();
this.ctx = factory.newSpatialContext();
}
@Test
@ -92,4 +99,18 @@ public class Geo3dShapeWGS84ModelRectRelationTest extends Geo3dShapeRectRelation
// (3) The point mentioned is NOT inside the path segment, either. (I think it should be...)
}
@Test
public void pointBearingTest(){
double radius = 136;
double distance = 135.97;
double bearing = 188;
Point p = ctx.getShapeFactory().pointXY(35, 85);
Circle circle = ctx.getShapeFactory().circle(p, radius);
Point bPoint = ctx.getDistCalc().pointOnBearing(p, distance, bearing, ctx, (Point) null);
double d = ctx.getDistCalc().distance(p, bPoint);
assertEquals(d, distance, 10-8);
assertEquals(circle.relate(bPoint), SpatialRelation.CONTAINS);
}
}