]> git.argeo.org Git - lgpl/argeo-commons.git/blobdiff - gis/runtime/org.argeo.gis.geotools/src/main/java/org/argeo/geotools/jcr/GeoJcrIndex.java
Move Commons GIS to Connect
[lgpl/argeo-commons.git] / gis / runtime / org.argeo.gis.geotools / src / main / java / org / argeo / geotools / jcr / GeoJcrIndex.java
index e6afb943066585aef8cfe12c8fbe6795547bb4de..b9281b8229d1c9e19d1df22ebc1ea62e46fd4773 100644 (file)
@@ -1,9 +1,13 @@
 package org.argeo.geotools.jcr;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 import javax.jcr.Node;
 import javax.jcr.Property;
@@ -12,6 +16,7 @@ import javax.jcr.Session;
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
 import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -20,7 +25,7 @@ import org.argeo.geotools.GeoToolsUtils;
 import org.argeo.jcr.JcrUtils;
 import org.argeo.jcr.gis.GisNames;
 import org.argeo.jcr.gis.GisTypes;
-import org.argeo.security.SystemExecutionService;
+import org.argeo.jts.jcr.JtsJcrUtils;
 import org.geotools.data.DataStore;
 import org.geotools.data.DefaultTransaction;
 import org.geotools.data.FeatureStore;
@@ -35,124 +40,124 @@ import org.opengis.feature.simple.SimpleFeature;
 import org.opengis.feature.simple.SimpleFeatureType;
 import org.opengis.filter.FilterFactory2;
 import org.opengis.filter.identity.FeatureId;
-import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.NoSuchAuthorityCodeException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.MathTransform;
 
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.GeometryFactory;
+import com.vividsolutions.jts.geom.Geometry;
 import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
 
-public class GeoJcrIndex implements EventListener {
-       public final static SimpleFeatureType JCR_POINT;
-
-       final static String JCR_POINT_NAME = "JCR_POINT";
+/** Index JCR nodes containing or referencing GIS data. */
+public class GeoJcrIndex implements EventListener, GisNames, GisTypes {
        // PostGIS convention
        final static String DEFAULT_GEOM_NAME = "the_geom";
 
        private final static Log log = LogFactory.getLog(GeoJcrIndex.class);
 
-       static {
-               SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
-               builder.setNamespaceURI(GisNames.GIS_NAMESPACE);
-               builder.setName(JCR_POINT_NAME);
-
-               builder.setDefaultGeometry(DEFAULT_GEOM_NAME);
-               builder.add(DEFAULT_GEOM_NAME, Point.class);
-
-               builder.add(JcrUtils.normalize(Property.JCR_UUID), String.class);
-               builder.add(JcrUtils.normalize(Property.JCR_PATH), String.class);
-               builder.add(JcrUtils.normalize(Property.JCR_PRIMARY_TYPE), String.class);
-               // mix:created
-               // builder.add(JcrUtils.normalize(Property.JCR_CREATED), Date.class);
-               // builder.add(JcrUtils.normalize(Property.JCR_CREATED_BY),
-               // String.class);
-               // mix:lastModified
-               builder.add(JcrUtils.normalize(Property.JCR_LAST_MODIFIED), Date.class);
-               builder.add(JcrUtils.normalize(Property.JCR_LAST_MODIFIED_BY),
-                               String.class);
-
-               JCR_POINT = builder.buildFeatureType();
-       }
-
        private DataStore dataStore;
-       private FeatureStore<SimpleFeatureType, SimpleFeature> pointsIndex;
        private Session session;
-       private SystemExecutionService systemExecutionService;
+       private Executor systemExecutionService;
+
+       private String crs = "EPSG:4326";
+
+       /** The key is the workspace */
+       private Map<String, FeatureStore<SimpleFeatureType, SimpleFeature>> geoJcrIndexes = Collections
+                       .synchronizedMap(new HashMap<String, FeatureStore<SimpleFeatureType, SimpleFeature>>());
 
        // TODO: use common factory finder?
        private FilterFactory2 ff = new FilterFactoryImpl();
 
-       // TODO: use finder?
-       private GeometryFactory geometryFactory = new GeometryFactory();
-
+       /** Expects to execute with system authentication */
        public void init() {
-               GeoToolsUtils.createSchemaIfNeeded(dataStore, JCR_POINT);
-               pointsIndex = GeoToolsUtils.getFeatureStore(dataStore,
-                               JCR_POINT.getName());
-
-               systemExecutionService.executeAsSystem(new Runnable() {
-                       public void run() {
-                               try {
-                                       session.getWorkspace()
-                                                       .getObservationManager()
-                                                       .addEventListener(GeoJcrIndex.this,
-                                                                       Event.NODE_ADDED | Event.NODE_REMOVED, "/",
-                                                                       true, null,
-                                                                       new String[] { GisTypes.GIS_GEOMETRY },
-                                                                       false);
-                               } catch (RepositoryException e) {
-                                       throw new ArgeoException("Cannot initialize GeoJcr index",
-                                                       e);
+               if (systemExecutionService != null)// legacy
+                       systemExecutionService.execute(new Runnable() {
+                               public void run() {
+                                       initGeoJcrIndex();
                                }
+                       });
+               else
+                       initGeoJcrIndex();
+       }
 
-                       }
-               });
+       protected void initGeoJcrIndex() {
+               try {
+                       // create GIS schema
+                       SimpleFeatureType indexType = getWorkspaceGeoJcrIndexType(session
+                                       .getWorkspace().getName());
+                       GeoToolsUtils.createSchemaIfNeeded(dataStore, indexType);
+
+                       // register JCR listeners
+                       ObservationManager om = session.getWorkspace()
+                                       .getObservationManager();
+                       om.addEventListener(this, Event.NODE_ADDED | Event.NODE_REMOVED,
+                                       "/", true, null, null, false);
+
+                       // FIXME: use a different listener for properties since it resets
+                       // the filter
+                       // om.addEventListener(this, Event.PROPERTY_CHANGED, "/",
+                       // true, null, new String[] { GIS_LOCATED }, false);
+
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot initialize GeoJcr index", e);
+               }
        }
 
        public void dispose() {
        }
 
-       public void onEvent(EventIterator events) {
-               FeatureCollection<SimpleFeatureType, SimpleFeature> pointsToAdd = FeatureCollections
+       public void onEvent(final EventIterator events) {
+               final FeatureCollection<SimpleFeatureType, SimpleFeature> toAdd = FeatureCollections
                                .newCollection();
-               Set<FeatureId> pointsToRemove = new HashSet<FeatureId>();
-               while (events.hasNext()) {
-                       Event event = events.nextEvent();
-                       try {
-                               Integer eventType = event.getType();
-                               if (Event.NODE_ADDED == eventType) {
-                                       Node node = session.getNodeByIdentifier(event
-                                                       .getIdentifier());
-                                       if (node.isNodeType(GisTypes.GIS_POINT)) {
-                                               Point point = nodeToPoint(node);
-                                               SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(
-                                                               JCR_POINT);
-                                               featureBuilder.set(DEFAULT_GEOM_NAME, point);
-                                               mapNodeToFeature(node, featureBuilder);
-                                               pointsToAdd.add(featureBuilder.buildFeature(node
-                                                               .getIdentifier()));
+               final Set<FeatureId> toRemove = new HashSet<FeatureId>();
+
+               // execute with system authentication so that JCR can be read
+               systemExecutionService.execute(new Runnable() {
+                       public void run() {
+                               while (events.hasNext()) {
+                                       Event event = events.nextEvent();
+                                       try {
+                                               Integer eventType = event.getType();
+                                               if (Event.NODE_ADDED == eventType) {
+                                                       Node node = session.getNodeByIdentifier(event
+                                                                       .getIdentifier());
+                                                       if (node.isNodeType(GIS_INDEXED)) {
+                                                               SimpleFeature feature = mapNodeToFeature(node,
+                                                                               getGeoJcrIndex().getSchema());
+                                                               toAdd.add(feature);
+                                                       }
+                                               } else if (Event.NODE_REMOVED == eventType) {
+                                                       // we have no way to check whether the node was
+                                                       // actually
+                                                       // geoindexed without querying the index, this is
+                                                       // therefore
+                                                       // more optimal to create a filter with all ideas
+                                                       // and apply
+                                                       // a remove later on
+                                                       String id = event.getIdentifier();
+                                                       toRemove.add(ff.featureId(id));
+                                               } else if (Event.PROPERTY_CHANGED == eventType) {
+                                                       // TODO: monitor changes to SRS, BBOX AND CENTROID
+                                               }
+                                       } catch (Exception e) {
+                                               log.error("Cannot process event " + event, e);
                                        }
-                               } else if (Event.NODE_REMOVED == eventType) {
-                                       String id = event.getIdentifier();
-                                       pointsToRemove.add(ff.featureId(id));
                                }
-                       } catch (Exception e) {
-                               log.error("Cannot process event " + event, e);
                        }
-               }
+               });
 
-               // persist
-               // TODO: this may be more optimal to persist in one single transaction,
-               // but we will loose modifications on all nodes if one fails
+               // TODO: this may be more optimal to persist in one single
+               // transaction,
+               // but we will loose modifications on all nodes if a single one
+               // fails
                try {
                        Transaction transaction = new DefaultTransaction();
-                       pointsIndex.setTransaction(transaction);
+                       getGeoJcrIndex().setTransaction(transaction);
                        try {
                                // points
-                               pointsIndex.addFeatures(pointsToAdd);
-                               if (pointsToRemove.size() != 0)
-                                       pointsIndex.removeFeatures(ff.id(pointsToRemove));
+                               getGeoJcrIndex().addFeatures(toAdd);
+                               if (toRemove.size() != 0)
+                                       getGeoJcrIndex().removeFeatures(ff.id(toRemove));
                                transaction.commit();
                        } catch (Exception e) {
                                transaction.rollback();
@@ -167,55 +172,131 @@ public class GeoJcrIndex implements EventListener {
                }
        }
 
-       protected void mapNodeToFeature(Node node,
-                       SimpleFeatureBuilder featureBuilder) {
+       protected FeatureStore<SimpleFeatureType, SimpleFeature> getGeoJcrIndex() {
+               String workspaceName = session.getWorkspace().getName();
+               if (!geoJcrIndexes.containsKey(workspaceName)) {
+                       SimpleFeatureType indexType = getWorkspaceGeoJcrIndexType(workspaceName);
+                       FeatureStore<SimpleFeatureType, SimpleFeature> geoIndex = GeoToolsUtils
+                                       .getFeatureStore(dataStore, indexType.getName());
+                       geoJcrIndexes.put(workspaceName, geoIndex);
+               }
+               return geoJcrIndexes.get(workspaceName);
+       }
+
+       protected SimpleFeatureType getWorkspaceGeoJcrIndexType(String workspaceName) {
+
+               SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
+               builder.setNamespaceURI(GIS_NAMESPACE);
+               builder.setName(workspaceName + "_geojcr_index");
+               try {
+                       builder.setCRS(CRS.decode(crs));
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot set CRS " + crs, e);
+               }
+
+               builder.setDefaultGeometry(JcrUtils.normalize(GIS_BBOX));
+               builder.add(JcrUtils.normalize(GIS_BBOX), Polygon.class);
+               builder.add(JcrUtils.normalize(GIS_CENTROID), Point.class);
+
+               builder.add(JcrUtils.normalize("jcr:uuid"), String.class);
+               builder.add(JcrUtils.normalize("jcr:path"), String.class);
+               builder.add(JcrUtils.normalize("jcr:primaryType"), String.class);
+               // mix:lastModified
+               builder.add(JcrUtils.normalize("jcr:lastModified"), Date.class);
+               builder.add(JcrUtils.normalize("jcr:lastModifiedBy"), String.class);
+
+               return builder.buildFeatureType();
+       }
+
+       protected SimpleFeature mapNodeToFeature(Node node, SimpleFeatureType type) {
                try {
-                       featureBuilder.set(JcrUtils.normalize(Property.JCR_UUID),
-                                       node.getIdentifier());
-                       featureBuilder.set(JcrUtils.normalize(Property.JCR_PATH),
-                                       node.getPath());
-                       featureBuilder.set(JcrUtils.normalize(Property.JCR_PRIMARY_TYPE),
-                                       node.getPrimaryNodeType().getName());
+                       SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type);
+
+                       Node locatedNode;
+                       if (node.isNodeType(GIS_LOCATED)) {
+                               locatedNode = node;
+                       } else if (node.isNodeType(GIS_INDEXED)) {
+                               locatedNode = findLocatedParent(node);
+                       } else {
+                               throw new ArgeoException("Unsupported node " + node);
+                       }
+
+                       // TODO: reproject to the feature store SRS
+                       Polygon bbox = (Polygon) JtsJcrUtils.readWkb(locatedNode
+                                       .getProperty(GIS_BBOX));
+                       builder.set(JcrUtils.normalize(GIS_BBOX), bbox);
+                       Point centroid = (Point) JtsJcrUtils.readWkb(locatedNode
+                                       .getProperty(GIS_CENTROID));
+                       builder.set(JcrUtils.normalize(GIS_CENTROID), centroid);
+
+                       builder.set(JcrUtils.normalize("jcr:uuid"), node.getIdentifier());
+                       builder.set(JcrUtils.normalize("jcr:path"), node.getPath());
+                       builder.set(JcrUtils.normalize("jcr:primaryType"), node
+                                       .getPrimaryNodeType().getName());
                        if (node.hasProperty(Property.JCR_LAST_MODIFIED))
-                               featureBuilder.set(
-                                               JcrUtils.normalize(Property.JCR_LAST_MODIFIED), node
-                                                               .getProperty(Property.JCR_LAST_MODIFIED)
-                                                               .getDate().getTime());
+                               builder.set(JcrUtils.normalize("jcr:lastModified"), node
+                                               .getProperty(Property.JCR_LAST_MODIFIED).getDate()
+                                               .getTime());
                        if (node.hasProperty(Property.JCR_LAST_MODIFIED_BY))
-                               featureBuilder
-                                               .set(JcrUtils.normalize(Property.JCR_LAST_MODIFIED_BY),
-                                                               node.getProperty(Property.JCR_LAST_MODIFIED_BY)
-                                                                               .getString());
+                               builder.set(JcrUtils.normalize("jcr:lastModifiedBy"), node
+                                               .getProperty(Property.JCR_LAST_MODIFIED_BY).getString());
+                       return builder.buildFeature(node.getIdentifier());
                } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot map " + node + " to "
-                                       + featureBuilder.getFeatureType(), e);
+                       throw new ArgeoException("Cannot map " + node + " to " + type, e);
                }
        }
 
-       /** Return the node as a point in the CRS of the related feature store. */
-       protected Point nodeToPoint(Node node) {
-               CoordinateReferenceSystem featureStoreCrs = pointsIndex.getSchema()
-                               .getCoordinateReferenceSystem();
-               DirectPosition nodePosition = GeoJcrUtils.nodeToPosition(node);
-               CoordinateReferenceSystem nodeCrs = nodePosition
-                               .getCoordinateReferenceSystem();
+       protected Node findLocatedParent(Node child) {
+               try {
+                       if (child.getParent().isNodeType(GIS_LOCATED))
+                               return child.getParent();
+                       else
+                               return findLocatedParent(child.getParent());
+               } catch (Exception e) {
+                       // also if child is root node
+                       throw new ArgeoException("Cannot find located parent", e);
+               }
+       }
 
+       /** Returns the node as a point in the CRS of the related feature store. */
+       protected Geometry reproject(CoordinateReferenceSystem crs,
+                       Geometry geometry, CoordinateReferenceSystem targetCrs) {
                // transform if not same CRS
-               DirectPosition targetPosition;
-               if (!featureStoreCrs.getIdentifiers().contains(nodeCrs.getName())) {
-                       MathTransform transform;
-                       try {
-                               transform = CRS.findMathTransform(nodeCrs, featureStoreCrs);
-                               targetPosition = transform.transform(nodePosition, null);
-                       } catch (Exception e) {
-                               throw new ArgeoException("Cannot transform from " + nodeCrs
-                                               + " to " + featureStoreCrs, e);
-                       }
+               // FIXME: there is certainly a more standard way to reproject
+               if (!targetCrs.getIdentifiers().contains(crs.getName())) {
+                       throw new ArgeoException("Reprojection not yet supported");
+                       // MathTransform transform;
+                       // try {
+                       // transform = CRS.findMathTransform(nodeCrs, featureStoreCrs);
+                       // if (geometry instanceof Point) {
+                       // Point point = (Point) geometry;
+                       // DirectPosition2D pos = new DirectPosition2D(nodeCrs,
+                       // point.getX(), point.getY());
+                       // DirectPosition targetPos = transform.transform(pos, null);
+                       // return geometryFactory.createPoint(new Coordinate(targetPos
+                       // .getCoordinate()[0], targetPos.getCoordinate()[1]));
+                       // } else if (geometry instanceof Polygon) {
+                       // Polygon polygon = (Polygon) geometry;
+                       // List<Coordinate> coordinates = new ArrayList<Coordinate>();
+                       // for (Coordinate coo : polygon.getExteriorRing()) {
+                       // DirectPosition pos = new DirectPosition2D(nodeCrs,
+                       // coo.x, coo.y);
+                       // DirectPosition targetPos = transform.transform(pos,
+                       // null);
+                       // // coordinates.add(o)
+                       // }
+                       // LinearRing ring = geometryFactory
+                       // .createLinearRing(coordinates
+                       // .toArray(new Coordinate[coordinates.size()]));
+                       // return geometryFactory.createPolygon(ring, null);
+                       // }
+                       // } catch (Exception e) {
+                       // throw new ArgeoException("Cannot transform from " + nodeCrs
+                       // + " to " + featureStoreCrs, e);
+                       // }
                } else {
-                       targetPosition = nodePosition;
+                       return geometry;
                }
-               double[] coo = targetPosition.getCoordinate();
-               return geometryFactory.createPoint(new Coordinate(coo[0], coo[1]));
        }
 
        public void setDataStore(DataStore dataStore) {
@@ -226,8 +307,7 @@ public class GeoJcrIndex implements EventListener {
                this.session = session;
        }
 
-       public void setSystemExecutionService(
-                       SystemExecutionService systemExecutionService) {
+       public void setSystemExecutionService(Executor systemExecutionService) {
                this.systemExecutionService = systemExecutionService;
        }