package org.activityinfo.server.database.hibernate.dao;
import com.google.common.collect.Lists;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.index.sweepline.SweepLineIndex;
import com.vividsolutions.jts.index.sweepline.SweepLineInterval;
import com.vividsolutions.jts.index.sweepline.SweepLineOverlapAction;
import org.activityinfo.server.database.hibernate.entity.AdminEntity;
import org.activityinfo.server.util.mapping.JtsUtil;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.spatial.criterion.SpatialRestrictions;
import java.util.ArrayList;
import java.util.List;
/**
* Matches a large set of points to their respective Administrative
* Entities to which they belong using a SweepLineIndex
*/
public class BatchGeocoder {
private final Session session;
private final GeometryFactory gf = new GeometryFactory();
private final List<Point> points = Lists.newArrayList();
private final SweepLineIndex index = new SweepLineIndex();
private List<AdminEntity> entities;
private List<List<AdminEntity>> results = Lists.newArrayList();
public BatchGeocoder(Session session) {
super();
this.session = session;
}
public void addPoint(double x, double y) {
int pointIndex = points.size();
// add the point to the list
points.add(gf.createPoint(new Coordinate(x, y)));
// index the point
index.add(new SweepLineInterval(x, x, pointIndex));
// add an empty result
results.add(new ArrayList<AdminEntity>());
}
public List<List<AdminEntity>> geocode() {
entities = queryEntities();
// add the entities to the sweep line index
indexEntities();
index.computeOverlaps(new SweepLineOverlapAction() {
@Override
public void overlap(SweepLineInterval s0, SweepLineInterval s1) {
// is this an overlap between a point and and entity?
if (s0.getItem() instanceof Integer && s1.getItem() instanceof AdminEntity) {
checkContains((Integer) s0.getItem(), (AdminEntity) s1.getItem());
} else if (s1.getItem() instanceof Integer && s0.getItem() instanceof AdminEntity) {
checkContains((Integer) s1.getItem(), (AdminEntity) s0.getItem());
}
}
});
return results;
}
private List<AdminEntity> queryEntities() {
// to do the job efficiently on the set of points, we'll use a SweepLine algorithm.
// but first we need to compute the bounds of the bounds so we know what to fetch
Envelope pointsMbr = new Envelope();
for (Point point : points) {
pointsMbr.expandToInclude(point.getCoordinate());
}
// now query the x/y ranges of all admin entities that
// might intersect with the ranges
Criteria criteria = session.createCriteria(AdminEntity.class);
criteria.add(SpatialRestrictions.intersects("geometry", gf.toGeometry(pointsMbr)));
List<AdminEntity> entities = criteria.list();
return entities;
}
private void indexEntities() {
for (AdminEntity entity : entities) {
index.add(new SweepLineInterval(entity.getBounds().getX1(), entity.getBounds().getX2(), entity));
}
}
private void checkContains(Integer pointIndex, AdminEntity entity) {
Point point = points.get(pointIndex);
if (JtsUtil.contains(entity.getGeometry(), point)) {
results.get(pointIndex).add(entity);
}
}
}