package org.wikibrain.spatial.dao.postgis;
import com.google.common.collect.Lists;
import com.typesafe.config.Config;
import com.vividsolutions.jts.geom.Geometry;
import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet;
import org.geotools.data.FeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.referencing.GeodeticCalculator;
import org.opengis.feature.Feature;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.PropertyName;
import org.wikibrain.conf.Configuration;
import org.wikibrain.conf.ConfigurationException;
import org.wikibrain.conf.Configurator;
import org.wikibrain.core.dao.DaoException;
import org.wikibrain.spatial.dao.SpatialNeighborDao;
import java.io.IOException;
import java.util.*;
/**
* Created by toby on 4/17/14.
*/
public class PostGISSpatialNeighborDao implements SpatialNeighborDao{
private final PostGISDB db;
public PostGISSpatialNeighborDao (PostGISDB db){
this.db = db;
}
@Override
public TIntSet getNeighboringItemIds(Integer itemId, String layerName, String refSysName, Set<String> subLayers, double minDist, double maxDist) throws DaoException{
Geometry g = db.getGeometry(itemId, layerName, refSysName);
if (g == null){
throw new DaoException(String.format("Could not find item %d in layer %s (%s)", itemId, layerName, refSysName));
}
return getNeighboringItemIds(g, refSysName, subLayers, minDist, maxDist);
}
@Override
public TIntSet getNeighboringItemIds(Geometry g, String refSysName, Set<String> subLayers, double minDist, double maxDist) throws DaoException{
if (subLayers.size() == 0) throw new DaoException("Cannot get containment without any layers");
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
PropertyName geomProperty = ff.property(db.getGeometryAttributeName());
//build ref sys clause
PropertyName refSysProperty = ff.property(db.getRefSysAttributeName());
Filter refSysFilter = ff.equals(refSysProperty, ff.literal(refSysName));
// build layer-related clause
PropertyName layerProperty = ff.property(db.getLayerAttributeName());
List<Filter> layerFilters = Lists.newArrayList();
for (String subLayer : subLayers){
Filter curLayerFilter = ff.equals(layerProperty, ff.literal(subLayer));
layerFilters.add(curLayerFilter);
}
Filter layerFilter = ff.and(layerFilters);
Filter withinFilter = ff.dwithin(geomProperty, ff.literal(g), maxDist , "4396");
Filter beyondFilter = ff.beyond(geomProperty, ff.literal(g), minDist , "4396");
List<Filter> filters = Lists.newArrayList();
filters.add(refSysFilter);
filters.add(layerFilter);
filters.add(withinFilter);
filters.add(beyondFilter);
Filter finalFilter = ff.and(filters);
try {
FeatureSource featureSource = db.getFeatureSource();
FeatureCollection containedFeatures = featureSource.getFeatures(finalFilter);
FeatureIterator featureIterator = containedFeatures.features();
TIntSet rVal = new TIntHashSet();
while (featureIterator.hasNext()){
Feature f = featureIterator.next();
Integer itemId = (Integer)f.getProperty(db.getItemIdAttributeName()).getValue();
rVal.add(itemId);
}
featureIterator.close();
return rVal;
}catch(IOException e){
throw new DaoException(e);
}
}
@Override
public Map<Integer, Geometry> getKNNeighbors (Geometry g, int k, String layerName, String refSysName, Set<Integer> excludeSet) throws DaoException{
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
PropertyName geomProperty = ff.property(db.getGeometryAttributeName());
//build ref sys clause
PropertyName refSysProperty = ff.property(db.getRefSysAttributeName());
Filter refSysFilter = ff.equals(refSysProperty, ff.literal(refSysName));
// build layer-related clause
PropertyName layerProperty = ff.property(db.getLayerAttributeName());
Filter layerFilter = ff.equals(layerProperty, ff.literal(layerName));
double guess = 0.01;
Filter withinFilter = ff.dwithin(geomProperty, ff.literal(g), guess * k , "4396");
List<Filter> filters = Lists.newArrayList();
filters.add(refSysFilter);
filters.add(layerFilter);
filters.add(withinFilter);
for(Integer i : excludeSet){
Filter nonEqualFilter = ff.notEqual(ff.property(db.getItemIdAttributeName()), ff.literal(i));
filters.add(nonEqualFilter);
}
Filter finalFilter = ff.and(filters);
try {
FeatureSource featureSource = db.getFeatureSource();
FeatureCollection containedFeatures = featureSource.getFeatures(finalFilter);
while(containedFeatures.size() < k && guess * k < 180){
if(containedFeatures.size() == 0)
guess = guess * 2;
else
guess = guess * (k / containedFeatures .size() > 2 ? 1.3 * Math.sqrt((k / containedFeatures.size())) : 2);
withinFilter = ff.dwithin(geomProperty, ff.literal(g), guess * k , "4396");
List<Filter> newFilters = Lists.newArrayList();
newFilters.add(refSysFilter);
newFilters.add(layerFilter);
newFilters.add(withinFilter);
for(Integer i : excludeSet){
Filter nonEqualFilter = ff.notEqual(ff.property(db.getItemIdAttributeName()), ff.literal(i));
newFilters.add(nonEqualFilter);
}
finalFilter = ff.and(newFilters);
containedFeatures = featureSource.getFeatures(finalFilter);
}
FeatureIterator featureIterator = containedFeatures.features();
Map<Integer, Geometry> rVal = new HashMap<Integer, Geometry>();
Map<Integer, Geometry> finalRVal = new LinkedHashMap<Integer, Geometry>();
final Map<Integer, Double> distMap = new HashMap<Integer, Double>();
List<Integer> order = new LinkedList<Integer>();
GeodeticCalculator geoCalc = new GeodeticCalculator();
while (featureIterator.hasNext()){
try{
Feature f = featureIterator.next();
Integer itemId = (Integer)f.getProperty(db.getItemIdAttributeName()).getValue();
Geometry geometry = (Geometry) f.getDefaultGeometryProperty().getValue();
rVal.put(itemId, geometry);
order.add(itemId);
geoCalc.setStartingGeographicPoint(geometry.getCoordinate().x, geometry.getCoordinate().y);
geoCalc.setDestinationGeographicPoint(g.getCoordinate().x, g.getCoordinate().y);
distMap.put(itemId, geoCalc.getOrthodromicDistance());
}
catch (Exception e){
//do nothing
}
}
featureIterator.close();
Collections.sort(order, new Comparator<Integer>() {
@Override
public int compare(Integer integer, Integer integer2){
try {
double dist1 = distMap.get(integer);
double dist2 = distMap.get(integer2);
if(dist1 < dist2) return 0;
else
return 1;
}
catch (Exception e){
}
return 0;
}
});
if(k > order.size())
k = order.size();
for(Integer i: order.subList(0, k)){
finalRVal.put(i, rVal.get(i));
}
featureIterator.close();
return finalRVal;
}catch(IOException e){
throw new DaoException(e);
}
}
@Override
public Map<Integer, Geometry> getNeighbors(Integer itemId, String layerName, String refSysName, Set<Integer> excludeSet) throws DaoException{
return getNeighbors(db.getGeometry(itemId, layerName, refSysName), layerName, refSysName, excludeSet);
}
@Override
public Map<Integer, Geometry> getNeighbors(Geometry g, String layerName, String refSysName, Set<Integer> excludeSet) throws DaoException{
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
PropertyName geomProperty = ff.property(db.getGeometryAttributeName());
//build ref sys clause
PropertyName refSysProperty = ff.property(db.getRefSysAttributeName());
Filter refSysFilter = ff.equals(refSysProperty, ff.literal(refSysName));
// build layer-related clause
PropertyName layerProperty = ff.property(db.getLayerAttributeName());
Filter layerFilter = ff.equals(layerProperty, ff.literal(layerName));
/*
Filter touchFilter = ff.touches(geomProperty, ff.literal(g));
Filter intersectFilter = ff.intersects(geomProperty, ff.literal(g));
List<Filter> orFilters = Lists.newArrayList();
orFilters.add(touchFilter);
orFilters.add(intersectFilter);
Filter touchOrIntersectFilter = ff.or(orFilters);
*/
Filter intersectFilter = ff.intersects(geomProperty, ff.literal(g));
List<Filter> filters = Lists.newArrayList();
filters.add(refSysFilter);
filters.add(layerFilter);
filters.add(intersectFilter);
for(Integer i : excludeSet){
Filter nonEqualFilter = ff.notEqual(ff.property(db.getItemIdAttributeName()), ff.literal(i));
filters.add(nonEqualFilter);
}
Filter finalFilter = ff.and(filters);
try {
FeatureSource featureSource = db.getFeatureSource();
FeatureCollection containedFeatures = featureSource.getFeatures(finalFilter);
FeatureIterator featureIterator = containedFeatures.features();
Map<Integer, Geometry> result = new HashMap<Integer, Geometry>();
while (featureIterator.hasNext()){
Feature f = featureIterator.next();
result.put((Integer)f.getProperty(db.getItemIdAttributeName()).getValue(), (Geometry) f.getDefaultGeometryProperty().getValue());
}
featureIterator.close();
return result;
}catch(IOException e){
throw new DaoException(e);
}
}
public TIntSet getMaxDistanceKmItemIds(Integer itemId, String layerName, String refSysName, Set<String> subLayers, double maxDist) throws DaoException{
return getNeighboringItemIds(itemId, layerName, refSysName, subLayers, 0, maxDist / 112);
}
public TIntSet getMaxDistanceKmItemIds(Geometry g, String refSysName, Set<String> subLayers, double maxDist) throws DaoException{
return getNeighboringItemIds(g, refSysName, subLayers, 0, maxDist / 112);
}
@Override
public Map<Integer, Geometry> getKNNeighbors(Integer itemId, int k, String layerName, String refSysName, Set<Integer> excludeSet) throws DaoException{
Geometry stPoint = db.getGeometry(itemId, layerName, refSysName);
return getKNNeighbors(stPoint, k, layerName,refSysName, excludeSet);
}
public static class Provider extends org.wikibrain.conf.Provider<PostGISSpatialNeighborDao> {
public Provider(Configurator configurator, Configuration config) throws ConfigurationException {
super(configurator, config);
}
@Override
public Class getType() {
return SpatialNeighborDao.class;
}
@Override
public String getPath() {
return "spatial.dao.spatialNeighbor";
}
@Override
public PostGISSpatialNeighborDao get(String name, Config config, Map<String, String> runtimeParams) throws ConfigurationException {
return new PostGISSpatialNeighborDao( getConfigurator().get(PostGISDB.class, config.getString("dataSource")));
}
}
}