package org.wikibrain.spatial.dao.postgis;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.typesafe.config.Config;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.opengis.feature.simple.SimpleFeature;
import org.wikibrain.conf.Configuration;
import org.wikibrain.conf.ConfigurationException;
import org.wikibrain.conf.Configurator;
import org.wikibrain.core.WikiBrainException;
import org.wikibrain.core.dao.DaoException;
import org.wikibrain.core.dao.LocalPageDao;
import org.wikibrain.core.lang.Language;
import org.wikibrain.core.model.LocalPage;
import org.wikibrain.core.model.NameSpace;
import org.wikibrain.core.model.Title;
import org.wikibrain.spatial.SpatialContainerMetadata;
import org.wikibrain.spatial.constants.Precision;
import org.wikibrain.spatial.constants.RefSys;
import org.wikibrain.spatial.dao.SpatialDataDao;
import org.wikibrain.wikidata.WikidataDao;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by bjhecht on 4/7/14.
*/
public class PostGISSpatialDataDao implements SpatialDataDao {
private final PostGISDB db;
private final WikidataDao wikidataDao;
private final LocalPageDao localPageDao;
// for writing data
private ArrayBlockingQueue<SimpleFeature> curFeaturesToStore = null;
private ThreadLocal<SimpleFeatureBuilder> simpleFeatureBuilder;
private static final Integer BUFFER_SIZE = 200;
private static final Logger LOG = LoggerFactory.getLogger(PostGISDB.class);
private PostGISSpatialDataDao(PostGISDB postGisDb, WikidataDao wikidataDao, LocalPageDao localPageDao){
this.db = postGisDb;
this.wikidataDao = wikidataDao;
this.localPageDao = localPageDao;
this.curFeaturesToStore = new ArrayBlockingQueue<SimpleFeature>(BUFFER_SIZE * 10);
simpleFeatureBuilder = new ThreadLocal<SimpleFeatureBuilder>() {
@Override
public SimpleFeatureBuilder initialValue() {
try {
return new SimpleFeatureBuilder(db.getSchema());
} catch (DaoException e) {
throw new RuntimeException(e);
}
}
};
}
@Override
public Geometry getGeometry(int itemId, String layerName, String refSysName) throws DaoException {
return db.getGeometry(itemId, layerName, refSysName);
}
@Override
public Map<String, Geometry> getGeometries(int itemId) throws DaoException {
Map<String, Geometry> result = new HashMap<String, Geometry>();
for (String refSys : this.getAllRefSysNames()){
for (String layer : this.getAllLayerNames(refSys)){
Geometry g = getGeometry(itemId, layer, refSys);
if (g != null) {
result.put(layer, g);
}
}
}
return result;
}
@Override
public Map<Integer, Geometry> getAllGeometriesInLayer(String layerName, String refSysName) throws DaoException {
return db.getAllGeometriesInLayer(layerName, refSysName);
}
@Override
public Map<Integer, Geometry> getAllGeometriesInLayer(String layerName) throws DaoException {
return getAllGeometriesInLayer(layerName, RefSys.EARTH);
}
@Override
public Map<Integer, Geometry> getAllGeometriesInLayer(String layerName, Precision.LatLonPrecision minPrecision) throws DaoException {
Map<Integer, Geometry> geoms = getAllGeometriesInLayer(layerName);
Set<Integer> keys = Sets.newHashSet();
for (Integer curKey : keys){
Geometry g = geoms.get(curKey);
if (this.filterByPrecision(g, minPrecision) == null){
geoms.remove(curKey);
}
}
return geoms;
}
@Override
public Map<Integer, Geometry> getAllGeometriesInLayer(String layerName, String[] notInLayers, String refSysName) throws DaoException {
Map<Integer, Geometry> rVal = this.getAllGeometriesInLayer(layerName, refSysName);
for (String notInLayer : notInLayers){
Map<Integer, Geometry> temp = this.getAllGeometriesInLayer(notInLayer, refSysName);
if (temp != null) {
for (Integer key : temp.keySet()) {
rVal.remove(key);
}
}else{
LOG.warn("Could not find any geometries in layer: " + layerName);
}
}
return rVal;
}
@Override
public Iterable<String> getAllRefSysNames() throws DaoException {
return db.getAllReferenceSystems();
}
@Override
public Iterable<String> getAllLayerNames(String refSysName) throws DaoException {
return db.getLayersInReferenceSystem(refSysName);
}
@Override
public SpatialContainerMetadata getReferenceSystemMetadata(String refSysName) throws DaoException {
try{
int count = 0;
SpatialContainerMetadata rVal = null;
for (String layerName : getAllLayerNames(refSysName)){
if (rVal == null){
rVal = getLayerMetadata(layerName, refSysName);
}else{
rVal.merge(getLayerMetadata(layerName, refSysName));
}
count++;
}
rVal.toReferenceSystem();
return rVal;
}catch(WikiBrainException e){
throw new DaoException(e);
}
}
@Override
public SpatialContainerMetadata getLayerMetadata(String layerName, String refSysName) throws DaoException {
SpatialContainerMetadata rVal = db.getLayerMetadata(layerName, refSysName);
return rVal;
}
@Override
public Geometry getGeometry(int itemId, String layerName) throws DaoException {
return getGeometry(itemId, layerName, RefSys.EARTH);
}
@Override
public Geometry getGeometry(int itemId, String layerName, Precision.LatLonPrecision minPrecision) throws DaoException {
Geometry g = this.getGeometry(itemId, layerName);
return filterByPrecision(g, minPrecision);
}
@Override
public Geometry getGeometry(String articleName, Language language, String layerName) throws DaoException {
return getGeometry(articleName, language, layerName, RefSys.EARTH);
}
private Geometry filterByPrecision(Geometry g, Precision.LatLonPrecision minPrecision){
if (g == null) return null;
if (!(g instanceof Point)) return g;
if (Precision.isGreaterThanOrEqualTo(Precision.getLatLonPrecision((Point)g), minPrecision)){
return g;
}else{
return null;
}
}
@Override
public Geometry getGeometry(String articleName, Language language, String layerName, Precision.LatLonPrecision minPrecision) throws DaoException {
Geometry g = getGeometry(articleName, language, layerName);
return filterByPrecision(g, minPrecision);
}
@Override
public Geometry getGeometry(String articleName, Language language, String layerName, String refSysName) throws DaoException {
LocalPage lp = localPageDao.getByTitle(new Title(articleName, language), NameSpace.ARTICLE);
if (lp == null) return null;
Integer id = wikidataDao.getItemId(lp);
if (id == null) throw new DaoException("Could not find Wikidata item for \"" + lp.toString() + "\"");
return getGeometry(id, layerName);
}
@Override
public Map<Integer, Geometry> getBulkGeometriesInLayer(List<Integer> idList, String layerName, String refSysName) throws DaoException{
return db.getBulkGeometriesInLayer(idList, layerName, refSysName);
}
@Override
public void beginSaveGeometries() throws DaoException {
try {
curFeaturesToStore.clear();
}catch(Exception e){
throw new DaoException(e);
}
}
@Override
public void endSaveGeometries() throws DaoException {
flushFeatureBuffer(true);
}
private void flushFeatureBuffer(boolean force) throws DaoException{
try {
List<SimpleFeature> batch = new ArrayList<SimpleFeature>();
synchronized (curFeaturesToStore) {
if (!force && curFeaturesToStore.size() < BUFFER_SIZE) {
return;
}
curFeaturesToStore.drainTo(batch);
}
SimpleFeatureCollection featuresToStore = new ListFeatureCollection(db.getSchema(), batch);
((SimpleFeatureStore) db.getFeatureSource()).addFeatures(featuresToStore); // GeoTools can be so weird sometimes
}catch(IOException e){
throw new DaoException(e);
}
}
@Override
public void saveGeometry(int itemId, String layerName, String refSysName, Geometry g) throws DaoException {
try {
SimpleFeature curFeature = simpleFeatureBuilder.get().buildFeature("n/a", new Object[]{new Integer(itemId), layerName, refSysName, g});
curFeaturesToStore.put(curFeature);
if (curFeaturesToStore.size() > BUFFER_SIZE){
flushFeatureBuffer(false);
}
}catch(Exception e){
throw new DaoException(e);
}
}
@Override
public void removeLayer(String refSysName, String layerName) throws DaoException {
db.removeLayer(refSysName, layerName);
}
@Override
public void optimize() throws DaoException {
db.optimize();
}
public static class Provider extends org.wikibrain.conf.Provider<PostGISSpatialDataDao> {
public Provider(Configurator configurator, Configuration config) throws ConfigurationException {
super(configurator, config);
}
@Override
public Class getType() {
return SpatialDataDao.class;
}
@Override
public String getPath() {
return "spatial.dao.spatialData";
}
@Override
public PostGISSpatialDataDao get(String name, Config config,
Map<String, String> runtimeParams) throws ConfigurationException {
return new PostGISSpatialDataDao( getConfigurator().get(PostGISDB.class, config.getString("dataSource")),
getConfigurator().get(WikidataDao.class), getConfigurator().get(LocalPageDao.class));
}
}
}