Skip to content

Commit

Permalink
Use JTS to determine applicable spatial rules (#1829)
Browse files Browse the repository at this point in the history
* Use parameterized logger calls

* Allow the user to provide multiple geojson files

* Add missing hashCode and equals

* Throw an exception if the old option is still in use

* Update the spatial rules documentation

* Use JTS to lookup SpatialRules

* Add a unittest to check SpatialRuleLookup performance

* Add a unittest for polygons with holes

* Use tiling

* Remove old test code
  • Loading branch information
otbutz committed Jan 29, 2020
1 parent 2e55c30 commit 7c52b8a
Show file tree
Hide file tree
Showing 17 changed files with 456 additions and 455 deletions.
4 changes: 2 additions & 2 deletions config-example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ graphhopper:


# Spatial Rules require you to provide Polygons in which the rules are enforced
# The line below contains the default location for these rules
# spatial_rules.location: core/files/spatialrules/countries.geo.json
# The line below contains the default location for the files which define these borders
# spatial_rules.borders_directory: core/files/spatialrules

# You can define the maximum BBox for which spatial rules are loaded.
# You might want to do this if you are only importing a small area and don't need rules for other countries.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@
*/
package com.graphhopper.routing.util.spatialrules;

import com.graphhopper.util.shapes.Polygon;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.locationtech.jts.geom.Polygon;

/**
* @author Robin Boldt
*/
public abstract class AbstractSpatialRule implements SpatialRule {

protected List<Polygon> polygons = Collections.EMPTY_LIST;
protected List<Polygon> polygons = Collections.emptyList();

public List<Polygon> getBorders() {
return polygons;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import com.graphhopper.routing.util.spatialrules.countries.AustriaSpatialRule;
import com.graphhopper.routing.util.spatialrules.countries.GermanySpatialRule;
import com.graphhopper.util.shapes.Polygon;

import java.util.List;

import org.locationtech.jts.geom.Polygon;

public class CountriesSpatialRuleFactory implements SpatialRuleLookupBuilder.SpatialRuleFactory {
@Override
public SpatialRule createSpatialRule(String id, List<Polygon> polygons) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
package com.graphhopper.routing.util.spatialrules;

import com.graphhopper.routing.profiles.RoadAccess;
import com.graphhopper.util.shapes.Polygon;

import java.util.List;
import java.util.Objects;

import org.locationtech.jts.geom.Polygon;

/**
* Defines rules that are valid for a certain region, e.g. a country.
Expand Down Expand Up @@ -87,5 +89,18 @@ public List<Polygon> getBorders() {
public String toString() {
return "SpatialRule.EMPTY";
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof SpatialRule)) {
return false;
}
return Objects.equals(getId(), ((SpatialRule) obj).getId());
}

@Override
public int hashCode() {
return getId().hashCode();
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,57 +19,99 @@

import java.util.*;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.prep.PreparedGeometry;
import org.locationtech.jts.geom.prep.PreparedGeometryFactory;

/**
* This class contains a collection of SpatialRule and is used for the implementation SpatialRuleLookupArray.
* This class contains a collection of SpatialRule which are valid for a certain Polygon.
*
* @author Robin Boldt
* @author Thomas Butz
*/
class SpatialRuleContainer {
private static final PreparedGeometryFactory PREP_GEOM_FACTORY = new PreparedGeometryFactory();
private static final GeometryFactory FAC = new GeometryFactory();
private static final int GRID_SIZE = 10;
private static final double COORD_EPSILON = 0.00001;

final Set<SpatialRule> rules = new LinkedHashSet<>();

public SpatialRuleContainer addRule(SpatialRule spatialRule) {
rules.add(spatialRule);
return this;
private final PreparedGeometry preparedPolygon;
private final Set<SpatialRule> rules = new LinkedHashSet<>();
private final List<Envelope> filledLines;

public SpatialRuleContainer(Polygon polygon) {
this(PREP_GEOM_FACTORY.create(polygon));
}

private SpatialRuleContainer(PreparedGeometry preparedPolygon) {
this.preparedPolygon = preparedPolygon;
this.filledLines = findFilledLines(preparedPolygon);
}

public SpatialRuleContainer addRules(Collection<SpatialRule> rules) {
this.rules.addAll(rules);
return this;
public void addRule(SpatialRule spatialRule) {
rules.add(spatialRule);
}

/**
* Returns a list of all spatial rules including the EMPTY one.
*/
Collection<SpatialRule> getRules() {
public Collection<SpatialRule> getRules() {
return rules;
}

public int size() {
return this.rules.size();
}

SpatialRule first() {
return this.rules.iterator().next();
}

@Override
public int hashCode() {
return rules.hashCode();
}

@Override
public boolean equals(Object o) {
if (o instanceof SpatialRuleContainer) {
if (this.rules.equals(((SpatialRuleContainer) o).getRules()))
public boolean containsProperly(Point point) {
Coordinate coord = point.getCoordinate();

for (Envelope line : filledLines) {
if (line.covers(coord)) {
return true;
}
}
return false;

return preparedPolygon.containsProperly(point);
}

public SpatialRuleContainer copy() {
SpatialRuleContainer container = new SpatialRuleContainer();
container.addRules(this.rules);
SpatialRuleContainer container = new SpatialRuleContainer(this.preparedPolygon);
container.rules.addAll(this.rules);
return container;
}

private static List<Envelope> findFilledLines(PreparedGeometry prepGeom) {
List<Envelope> lines = new ArrayList<>();

Envelope bbox = prepGeom.getGeometry().getEnvelopeInternal();
double tileWidth = bbox.getWidth() / GRID_SIZE;
double tileHeight = bbox.getHeight() / GRID_SIZE;

Envelope tile = new Envelope();
Envelope line;
for (int row = 0; row < GRID_SIZE; row++) {
line = null;
for (int column = 0; column < GRID_SIZE; column++) {
double minX = bbox.getMinX() + (column * tileWidth);
double minY = bbox.getMinY() + (row * tileHeight);
tile.init(minX, minX + tileWidth, minY, minY + tileHeight);

if (prepGeom.covers(FAC.toGeometry(tile))) {
if (line != null && Math.abs(line.getMaxX() - tile.getMinX()) < COORD_EPSILON) {
line.expandToInclude(tile);
} else {
line = new Envelope(tile);
lines.add(line);
}
}
}
}

return lines;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
*/
package com.graphhopper.routing.util.spatialrules;

import com.graphhopper.util.shapes.BBox;
import org.locationtech.jts.geom.Envelope;

import com.graphhopper.util.shapes.GHPoint;

/**
Expand Down Expand Up @@ -63,7 +64,7 @@ public interface SpatialRuleLookup {
/**
* @return the bounds of the SpatialRuleLookup
*/
BBox getBounds();
Envelope getBounds();

SpatialRuleLookup EMPTY = new SpatialRuleLookup() {
@Override
Expand Down Expand Up @@ -92,8 +93,8 @@ public int size() {
}

@Override
public BBox getBounds() {
return new BBox(-180, 180, -90, 90);
public Envelope getBounds() {
return new Envelope(-180d, 180d, -90d, 90d);
}
};
}
Loading

0 comments on commit 7c52b8a

Please sign in to comment.