// This software is released into the Public Domain. See copying.txt for details.
package org.openstreetmap.osmosis.areafilter.v0_6;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.io.File;
import org.openstreetmap.osmosis.areafilter.common.PolygonFileReader;
import org.openstreetmap.osmosis.core.container.v0_6.BoundContainer;
import org.openstreetmap.osmosis.core.domain.v0_6.Bound;
import org.openstreetmap.osmosis.core.domain.v0_6.Node;
import org.openstreetmap.osmosis.core.filter.common.IdTrackerType;
/**
* Provides a filter for extracting all entities that lie within a specific
* geographical box identified by latitude and longitude coordinates.
*
* @author Brett Henderson
*/
public class PolygonFilter extends AreaFilter {
private File polygonFile;
private Area area;
/**
* Creates a new instance.
*
* @param idTrackerType
* Defines the id tracker implementation to use.
* @param polygonFile
* The file containing the polygon coordinates.
* @param clipIncompleteEntities
* If true, entities referring to non-existent entities will be
* modified to ensure referential integrity. For example, ways
* will be modified to only include nodes inside the area.
* @param completeWays
* Include all nodes for ways which have at least one node inside the filtered area.
* @param completeRelations
* Include all relations referenced by other relations which have members inside
* the filtered area.
* @param cascadingRelations
* Include all relations that reference other relations which have members inside the
* filtered area. This is less costly than completeRelations.
*/
public PolygonFilter(
IdTrackerType idTrackerType, File polygonFile, boolean clipIncompleteEntities, boolean completeWays,
boolean completeRelations, boolean cascadingRelations) {
super(idTrackerType, clipIncompleteEntities, completeWays, completeRelations, cascadingRelations);
this.polygonFile = polygonFile;
area = null;
}
/**
* {@inheritDoc}
*/
@Override
public void process(BoundContainer boundContainer) {
Bound newBound = null;
// Configure the area if it hasn't been created yet. (Should this be in an "initialize" method?)
if (area == null) {
area = new PolygonFileReader(polygonFile).loadPolygon();
}
for (Bound b : boundContainer.getEntity().toSimpleBound()) {
if (newBound == null) {
newBound = simpleBoundIntersect(b);
} else {
newBound = newBound.union(simpleBoundIntersect(b));
}
}
if (newBound != null) {
super.process(new BoundContainer(newBound));
}
}
/**
* Get the simple intersection of this polygon with the passed Bound.
*
* @param bound
* Bound with which to intersect. Must be "simple" (not cross antimeridian).
* @return Bound resulting rectangular area after intersection
*/
private Bound simpleBoundIntersect(Bound bound) {
Rectangle2D r;
double width, height;
Bound newBound = null;
Area a2 = (Area) area.clone(); // make a copy so we don't disturb the original
/*
* Note that AWT uses the computer graphics convention with the origin at the top left, so
* top and bottom are reversed for a Rectangle2D vs. a Bound.
*/
if (bound.getLeft() > bound.getRight()) {
return null;
}
width = bound.getRight() - bound.getLeft();
height = bound.getTop() - bound.getBottom();
/*
* Perform the intersect against the Area itself instead of its bounding box for maximum
* precision.
*/
a2.intersect(new Area(new Rectangle2D.Double(
bound.getLeft(),
bound.getBottom(),
width,
height)));
if (!a2.isEmpty()) {
r = a2.getBounds2D();
newBound = new Bound(
r.getMaxX(),
r.getMinX(),
r.getMaxY(),
r.getMinY(),
bound.getOrigin());
}
return newBound;
}
/**
* {@inheritDoc}
*/
@Override
protected boolean isNodeWithinArea(Node node) {
double latitude;
double longitude;
// Configure the area if it hasn't been created yet.
if (area == null) {
area = new PolygonFileReader(polygonFile).loadPolygon();
}
latitude = node.getLatitude();
longitude = node.getLongitude();
return area.contains(longitude, latitude);
}
}