X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;ds=sidebyside;f=gis%2Fruntime%2Forg.argeo.gis.geotools%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fgeotools%2Fjcr%2FGeoJcrIndex.java;h=b9281b8229d1c9e19d1df22ebc1ea62e46fd4773;hb=c3be9f8db8a79e159d6a057758dfc7f3580efc2d;hp=e6afb943066585aef8cfe12c8fbe6795547bb4de;hpb=977a7a352131b082a98739f15e421f2bff747567;p=lgpl%2Fargeo-commons.git diff --git a/gis/runtime/org.argeo.gis.geotools/src/main/java/org/argeo/geotools/jcr/GeoJcrIndex.java b/gis/runtime/org.argeo.gis.geotools/src/main/java/org/argeo/geotools/jcr/GeoJcrIndex.java index e6afb9430..b9281b822 100644 --- a/gis/runtime/org.argeo.gis.geotools/src/main/java/org/argeo/geotools/jcr/GeoJcrIndex.java +++ b/gis/runtime/org.argeo.gis.geotools/src/main/java/org/argeo/geotools/jcr/GeoJcrIndex.java @@ -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 pointsIndex; private Session session; - private SystemExecutionService systemExecutionService; + private Executor systemExecutionService; + + private String crs = "EPSG:4326"; + + /** The key is the workspace */ + private Map> geoJcrIndexes = Collections + .synchronizedMap(new HashMap>()); // 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 pointsToAdd = FeatureCollections + public void onEvent(final EventIterator events) { + final FeatureCollection toAdd = FeatureCollections .newCollection(); - Set pointsToRemove = new HashSet(); - 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 toRemove = new HashSet(); + + // 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 getGeoJcrIndex() { + String workspaceName = session.getWorkspace().getName(); + if (!geoJcrIndexes.containsKey(workspaceName)) { + SimpleFeatureType indexType = getWorkspaceGeoJcrIndexType(workspaceName); + FeatureStore 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 coordinates = new ArrayList(); + // 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; }