]> git.argeo.org Git - lgpl/argeo-commons.git/blob - gis/runtime/org.argeo.gis.geotools/src/main/java/org/argeo/geotools/jcr/GeoJcrIndex.java
Adapt for GeoTools 2.7.2
[lgpl/argeo-commons.git] / gis / runtime / org.argeo.gis.geotools / src / main / java / org / argeo / geotools / jcr / GeoJcrIndex.java
1 package org.argeo.geotools.jcr;
2
3 import java.io.IOException;
4 import java.util.Collections;
5 import java.util.Date;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.Map;
9 import java.util.Set;
10 import java.util.concurrent.Executor;
11
12 import javax.jcr.Node;
13 import javax.jcr.Property;
14 import javax.jcr.RepositoryException;
15 import javax.jcr.Session;
16 import javax.jcr.observation.Event;
17 import javax.jcr.observation.EventIterator;
18 import javax.jcr.observation.EventListener;
19 import javax.jcr.observation.ObservationManager;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.argeo.ArgeoException;
24 import org.argeo.geotools.GeoToolsUtils;
25 import org.argeo.jcr.JcrUtils;
26 import org.argeo.jcr.gis.GisNames;
27 import org.argeo.jcr.gis.GisTypes;
28 import org.argeo.jts.jcr.JtsJcrUtils;
29 import org.geotools.data.DataStore;
30 import org.geotools.data.DefaultTransaction;
31 import org.geotools.data.FeatureStore;
32 import org.geotools.data.Transaction;
33 import org.geotools.feature.FeatureCollection;
34 import org.geotools.feature.FeatureCollections;
35 import org.geotools.feature.simple.SimpleFeatureBuilder;
36 import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
37 import org.geotools.filter.FilterFactoryImpl;
38 import org.geotools.referencing.CRS;
39 import org.opengis.feature.simple.SimpleFeature;
40 import org.opengis.feature.simple.SimpleFeatureType;
41 import org.opengis.filter.FilterFactory2;
42 import org.opengis.filter.identity.FeatureId;
43 import org.opengis.referencing.FactoryException;
44 import org.opengis.referencing.NoSuchAuthorityCodeException;
45 import org.opengis.referencing.crs.CoordinateReferenceSystem;
46
47 import com.vividsolutions.jts.geom.Geometry;
48 import com.vividsolutions.jts.geom.Point;
49 import com.vividsolutions.jts.geom.Polygon;
50
51 /** Index JCR nodes containing or referencing GIS data. */
52 public class GeoJcrIndex implements EventListener, GisNames, GisTypes {
53 // PostGIS convention
54 final static String DEFAULT_GEOM_NAME = "the_geom";
55
56 private final static Log log = LogFactory.getLog(GeoJcrIndex.class);
57
58 private DataStore dataStore;
59 private Session session;
60 private Executor systemExecutionService;
61
62 private String crs = "EPSG:4326";
63
64 /** The key is the workspace */
65 private Map<String, FeatureStore<SimpleFeatureType, SimpleFeature>> geoJcrIndexes = Collections
66 .synchronizedMap(new HashMap<String, FeatureStore<SimpleFeatureType, SimpleFeature>>());
67
68 // TODO: use common factory finder?
69 private FilterFactory2 ff = new FilterFactoryImpl();
70
71 /** Expects to execute with system authentication */
72 public void init() {
73 if (systemExecutionService != null)// legacy
74 systemExecutionService.execute(new Runnable() {
75 public void run() {
76 initGeoJcrIndex();
77 }
78 });
79 else
80 initGeoJcrIndex();
81 }
82
83 protected void initGeoJcrIndex() {
84 try {
85 // create GIS schema
86 SimpleFeatureType indexType = getWorkspaceGeoJcrIndexType(session
87 .getWorkspace().getName());
88 GeoToolsUtils.createSchemaIfNeeded(dataStore, indexType);
89
90 // register JCR listeners
91 ObservationManager om = session.getWorkspace()
92 .getObservationManager();
93 om.addEventListener(this, Event.NODE_ADDED | Event.NODE_REMOVED,
94 "/", true, null, null, false);
95
96 // FIXME: use a different listener for properties since it resets
97 // the filter
98 // om.addEventListener(this, Event.PROPERTY_CHANGED, "/",
99 // true, null, new String[] { GIS_LOCATED }, false);
100
101 } catch (RepositoryException e) {
102 throw new ArgeoException("Cannot initialize GeoJcr index", e);
103 }
104 }
105
106 public void dispose() {
107 }
108
109 public void onEvent(final EventIterator events) {
110 final FeatureCollection<SimpleFeatureType, SimpleFeature> toAdd = FeatureCollections
111 .newCollection();
112 final Set<FeatureId> toRemove = new HashSet<FeatureId>();
113
114 // execute with system authentication so that JCR can be read
115 systemExecutionService.execute(new Runnable() {
116 public void run() {
117 while (events.hasNext()) {
118 Event event = events.nextEvent();
119 try {
120 Integer eventType = event.getType();
121 if (Event.NODE_ADDED == eventType) {
122 Node node = session.getNodeByIdentifier(event
123 .getIdentifier());
124 if (node.isNodeType(GIS_INDEXED)) {
125 SimpleFeature feature = mapNodeToFeature(node,
126 getGeoJcrIndex().getSchema());
127 toAdd.add(feature);
128 }
129 } else if (Event.NODE_REMOVED == eventType) {
130 // we have no way to check whether the node was
131 // actually
132 // geoindexed without querying the index, this is
133 // therefore
134 // more optimal to create a filter with all ideas
135 // and apply
136 // a remove later on
137 String id = event.getIdentifier();
138 toRemove.add(ff.featureId(id));
139 } else if (Event.PROPERTY_CHANGED == eventType) {
140 // TODO: monitor changes to SRS, BBOX AND CENTROID
141 }
142 } catch (Exception e) {
143 log.error("Cannot process event " + event, e);
144 }
145 }
146 }
147 });
148
149 // TODO: this may be more optimal to persist in one single
150 // transaction,
151 // but we will loose modifications on all nodes if a single one
152 // fails
153 try {
154 Transaction transaction = new DefaultTransaction();
155 getGeoJcrIndex().setTransaction(transaction);
156 try {
157 // points
158 getGeoJcrIndex().addFeatures(toAdd);
159 if (toRemove.size() != 0)
160 getGeoJcrIndex().removeFeatures(ff.id(toRemove));
161 transaction.commit();
162 } catch (Exception e) {
163 transaction.rollback();
164 throw new ArgeoException("Cannot persist changes", e);
165 } finally {
166 transaction.close();
167 }
168 } catch (ArgeoException e) {
169 throw e;
170 } catch (IOException e) {
171 throw new ArgeoException("Unexpected issue with the transaction", e);
172 }
173 }
174
175 protected FeatureStore<SimpleFeatureType, SimpleFeature> getGeoJcrIndex() {
176 String workspaceName = session.getWorkspace().getName();
177 if (!geoJcrIndexes.containsKey(workspaceName)) {
178 SimpleFeatureType indexType = getWorkspaceGeoJcrIndexType(workspaceName);
179 FeatureStore<SimpleFeatureType, SimpleFeature> geoIndex = GeoToolsUtils
180 .getFeatureStore(dataStore, indexType.getName());
181 geoJcrIndexes.put(workspaceName, geoIndex);
182 }
183 return geoJcrIndexes.get(workspaceName);
184 }
185
186 protected SimpleFeatureType getWorkspaceGeoJcrIndexType(String workspaceName) {
187
188 SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
189 builder.setNamespaceURI(GIS_NAMESPACE);
190 builder.setName(workspaceName + "_geojcr_index");
191 try {
192 builder.setCRS(CRS.decode(crs));
193 } catch (Exception e) {
194 throw new ArgeoException("Cannot set CRS " + crs, e);
195 }
196
197 builder.setDefaultGeometry(JcrUtils.normalize(GIS_BBOX));
198 builder.add(JcrUtils.normalize(GIS_BBOX), Polygon.class);
199 builder.add(JcrUtils.normalize(GIS_CENTROID), Point.class);
200
201 builder.add(JcrUtils.normalize("jcr:uuid"), String.class);
202 builder.add(JcrUtils.normalize("jcr:path"), String.class);
203 builder.add(JcrUtils.normalize("jcr:primaryType"), String.class);
204 // mix:lastModified
205 builder.add(JcrUtils.normalize("jcr:lastModified"), Date.class);
206 builder.add(JcrUtils.normalize("jcr:lastModifiedBy"), String.class);
207
208 return builder.buildFeatureType();
209 }
210
211 protected SimpleFeature mapNodeToFeature(Node node, SimpleFeatureType type) {
212 try {
213 SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type);
214
215 Node locatedNode;
216 if (node.isNodeType(GIS_LOCATED)) {
217 locatedNode = node;
218 } else if (node.isNodeType(GIS_INDEXED)) {
219 locatedNode = findLocatedParent(node);
220 } else {
221 throw new ArgeoException("Unsupported node " + node);
222 }
223
224 // TODO: reproject to the feature store SRS
225 Polygon bbox = (Polygon) JtsJcrUtils.readWkb(locatedNode
226 .getProperty(GIS_BBOX));
227 builder.set(JcrUtils.normalize(GIS_BBOX), bbox);
228 Point centroid = (Point) JtsJcrUtils.readWkb(locatedNode
229 .getProperty(GIS_CENTROID));
230 builder.set(JcrUtils.normalize(GIS_CENTROID), centroid);
231
232 builder.set(JcrUtils.normalize("jcr:uuid"), node.getIdentifier());
233 builder.set(JcrUtils.normalize("jcr:path"), node.getPath());
234 builder.set(JcrUtils.normalize("jcr:primaryType"), node
235 .getPrimaryNodeType().getName());
236 if (node.hasProperty(Property.JCR_LAST_MODIFIED))
237 builder.set(JcrUtils.normalize("jcr:lastModified"), node
238 .getProperty(Property.JCR_LAST_MODIFIED).getDate()
239 .getTime());
240 if (node.hasProperty(Property.JCR_LAST_MODIFIED_BY))
241 builder.set(JcrUtils.normalize("jcr:lastModifiedBy"), node
242 .getProperty(Property.JCR_LAST_MODIFIED_BY).getString());
243 return builder.buildFeature(node.getIdentifier());
244 } catch (RepositoryException e) {
245 throw new ArgeoException("Cannot map " + node + " to " + type, e);
246 }
247 }
248
249 protected Node findLocatedParent(Node child) {
250 try {
251 if (child.getParent().isNodeType(GIS_LOCATED))
252 return child.getParent();
253 else
254 return findLocatedParent(child.getParent());
255 } catch (Exception e) {
256 // also if child is root node
257 throw new ArgeoException("Cannot find located parent", e);
258 }
259 }
260
261 /** Returns the node as a point in the CRS of the related feature store. */
262 protected Geometry reproject(CoordinateReferenceSystem crs,
263 Geometry geometry, CoordinateReferenceSystem targetCrs) {
264 // transform if not same CRS
265 // FIXME: there is certainly a more standard way to reproject
266 if (!targetCrs.getIdentifiers().contains(crs.getName())) {
267 throw new ArgeoException("Reprojection not yet supported");
268 // MathTransform transform;
269 // try {
270 // transform = CRS.findMathTransform(nodeCrs, featureStoreCrs);
271 // if (geometry instanceof Point) {
272 // Point point = (Point) geometry;
273 // DirectPosition2D pos = new DirectPosition2D(nodeCrs,
274 // point.getX(), point.getY());
275 // DirectPosition targetPos = transform.transform(pos, null);
276 // return geometryFactory.createPoint(new Coordinate(targetPos
277 // .getCoordinate()[0], targetPos.getCoordinate()[1]));
278 // } else if (geometry instanceof Polygon) {
279 // Polygon polygon = (Polygon) geometry;
280 // List<Coordinate> coordinates = new ArrayList<Coordinate>();
281 // for (Coordinate coo : polygon.getExteriorRing()) {
282 // DirectPosition pos = new DirectPosition2D(nodeCrs,
283 // coo.x, coo.y);
284 // DirectPosition targetPos = transform.transform(pos,
285 // null);
286 // // coordinates.add(o)
287 // }
288 // LinearRing ring = geometryFactory
289 // .createLinearRing(coordinates
290 // .toArray(new Coordinate[coordinates.size()]));
291 // return geometryFactory.createPolygon(ring, null);
292 // }
293 // } catch (Exception e) {
294 // throw new ArgeoException("Cannot transform from " + nodeCrs
295 // + " to " + featureStoreCrs, e);
296 // }
297 } else {
298 return geometry;
299 }
300 }
301
302 public void setDataStore(DataStore dataStore) {
303 this.dataStore = dataStore;
304 }
305
306 public void setSession(Session session) {
307 this.session = session;
308 }
309
310 public void setSystemExecutionService(Executor systemExecutionService) {
311 this.systemExecutionService = systemExecutionService;
312 }
313
314 }