001: package org.openjump.sigle.plugin.geoprocessing.oneLayer.topology;
002:
003: /*
004: * This tool has been developped by Michael Michaud Juin 2005
005: * Erwan Bocher added a feature to keep original attributes
006: * Stefan Steiniger did the internationalization
007: */
008:
009: import java.util.ArrayList;
010: import java.util.Collection;
011: import java.util.HashMap;
012: import java.util.Iterator;
013: import java.util.List;
014: import java.util.Map;
015:
016: import org.openjump.sigle.utilities.geom.FeatureCollectionUtil;
017:
018: import com.vividsolutions.jts.geom.Coordinate;
019: import com.vividsolutions.jts.geom.Geometry;
020: import com.vividsolutions.jts.geom.GeometryCollection;
021: import com.vividsolutions.jts.geom.GeometryFactory;
022: import com.vividsolutions.jts.geom.IntersectionMatrix;
023: import com.vividsolutions.jts.geom.util.LinearComponentExtracter;
024: import com.vividsolutions.jts.operation.linemerge.LineMerger;
025: import com.vividsolutions.jts.operation.polygonize.Polygonizer;
026:
027: import com.vividsolutions.jump.I18N;
028: import com.vividsolutions.jump.feature.AttributeType;
029: import com.vividsolutions.jump.feature.BasicFeature;
030: import com.vividsolutions.jump.feature.Feature;
031: import com.vividsolutions.jump.feature.FeatureCollection;
032: import com.vividsolutions.jump.feature.FeatureDataset;
033: import com.vividsolutions.jump.feature.FeatureSchema;
034: import com.vividsolutions.jump.feature.IndexedFeatureCollection;
035: import com.vividsolutions.jump.task.TaskMonitor;
036: import com.vividsolutions.jump.tools.AttributeMapping;
037: import com.vividsolutions.jump.workbench.model.Layer;
038: import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory;
039: import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck;
040: import com.vividsolutions.jump.workbench.plugin.PlugInContext;
041: import com.vividsolutions.jump.workbench.plugin.ThreadedBasePlugIn;
042: import com.vividsolutions.jump.workbench.ui.MenuNames;
043: import com.vividsolutions.jump.workbench.ui.MultiInputDialog;
044:
045: /**
046: * PlanarGraphPlugIn computes a planar graph from a set of features.
047: * The user can choose to produce the nodes, the edges and the faces, or only
048: * some of those features.
049: * The following relations are kept as edge attributes :<br>
050: * Initial node identifier<br>
051: * Final node identifier<br>
052: * Right face<br>
053: * Left face<br>
054: * @author Michael Michaud and Erwan Bocher (2005-06)
055: * Comments added by Michael Michaud on 2006-05-01
056: */
057: public class PlanarGraphPlugIn extends ThreadedBasePlugIn {
058:
059: public final static String EDGE = I18N
060: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Edge");
061: public final static String FACE = I18N
062: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Face");
063: public final static String NODE = I18N
064: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Node");
065: public final static String CATEGORY = I18N
066: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Graph");
067: public final static String MAPPING = I18N
068: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Mapping");
069:
070: public final static String TITLE = I18N
071: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Topologic-Analysis");
072: public final static String SELECT_LAYER = I18N
073: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Select-layer-to-analyse");
074: public final static String CALCULATE_NODES = I18N
075: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Calculate-nodes");
076: public final static String CALCULATE_FACES = I18N
077: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Calculate-faces");
078: public final static String CALCULATE_RELATIONS = I18N
079: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Calculate-the-relations-arcs-nodes-and-/or-arcs-faces");
080: public final static String KEEP_ATTRIBUTES = I18N
081: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Keep-attributes");
082:
083: public final static Integer MINUS_ONE = new Integer(-1);
084:
085: GeometryFactory gf = new GeometryFactory();
086:
087: // Do you want to compute nodes
088: private static boolean nodeb = true;
089: // Do you want to compute faces
090: private static boolean faceb = true;
091: // Do you want to compute edge/node relations and/or edges/faces relations
092: private static boolean relb = true;
093: // Do you want to keep original attributes
094: private static boolean attributesb = true;
095:
096: // Attribute names have not been internationalized
097: private static String LEFT_FACE = "LeftFace";
098: private static String RIGHT_FACE = "RightFace";
099: private static String INITIAL_NODE = "StartNode";
100: private static String FINAL_NODE = "EndNode";
101:
102: private String layerName;
103:
104: public Collection edges;
105:
106: private MultiInputDialog mid;
107:
108: /**
109: * Calculations take place here
110: */
111: public void run(TaskMonitor monitor, PlugInContext context)
112: throws Exception {
113:
114: // Faces FeatureCollection declaration
115: FeatureCollection fcFace = null;
116:
117: // Getting options from the dialog
118: Layer layer = mid.getLayer(SELECT_LAYER);
119: FeatureCollection fcSource = layer
120: .getFeatureCollectionWrapper();
121: layerName = layer.getName();
122: nodeb = mid.getBoolean(CALCULATE_NODES);
123: faceb = mid.getBoolean(CALCULATE_FACES);
124: relb = mid.getBoolean(CALCULATE_RELATIONS);
125: attributesb = mid.getBoolean(KEEP_ATTRIBUTES);
126:
127: // Get linear elements from all geometries in the layer
128: monitor
129: .report(I18N
130: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Searching-for-linear-elements"));
131: List list = getLines(fcSource);
132: monitor
133: .report(I18N
134: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Number-of-found-elements")
135: + ": " + list.size());
136:
137: // Union the lines (unioning is the most expensive operation)
138: monitor
139: .report(I18N
140: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Generate-layer-of-arcs"));
141: FeatureCollection fcEdge = createEdgeLayer(layer
142: .getFeatureCollectionWrapper(), nodeb, faceb, relb,
143: context);
144: monitor
145: .report(I18N
146: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Arc-layer-generated"));
147:
148: // Create the node Layer
149: monitor
150: .report(I18N
151: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Create-nodes"));
152: if (nodeb) {
153: FeatureCollection fcNode = createNodeLayer(fcEdge, context,
154: relb);
155: }
156: monitor
157: .report(I18N
158: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Layer-with-nodes-generated"));
159:
160: // Create face Layer from edges with Polygonizer
161: monitor
162: .report(I18N
163: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Create-faces"));
164: if (faceb) {
165: fcFace = createFaceLayer(fcEdge, context, relb);
166: }
167: monitor
168: .report(I18N
169: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Layer-of-faces-generated"));
170:
171: //Erwan aout 2005
172: //Ici on applique la procédure pour récuperer les attributs de la couche d'origine
173: //Les attributs sont rappatriés si l'entité produite est contenue dans l'entité source
174: // Si la couche d'entrée est une couche de polygones alors les attributs sont rappatriés pour la couche de faces
175: // Si la couche d'entrée est une couche de linestring alors les attributs sont rappatriés pour la couche d'arcs
176:
177: if (faceb) {
178: Feature fWithin = null;
179: AttributeMapping mapping = null;
180:
181: if (attributesb) {
182: // Use mapping to get the attributes
183: mapping = new AttributeMapping(new FeatureSchema(),
184: new FeatureSchema());
185: List aFeatures = new ArrayList();
186: monitor
187: .report(I18N
188: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Transfer-of-attributes"));
189: if (FeatureCollectionUtil
190: .getFeatureCollectionDimension(fcSource) == 2) {
191: mapping = new AttributeMapping(fcSource
192: .getFeatureSchema(), fcFace
193: .getFeatureSchema());
194: aFeatures = fcFace.getFeatures();
195: } else if (FeatureCollectionUtil
196: .getFeatureCollectionDimension(fcSource) == 1) {
197: mapping = new AttributeMapping(fcSource
198: .getFeatureSchema(), fcFace
199: .getFeatureSchema());
200: aFeatures = fcEdge.getFeatures();
201: } else {
202: context
203: .getWorkbenchFrame()
204: .warnUser(
205: I18N
206: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Cannot-transfer-attributes"));
207: }
208:
209: FeatureDataset fcRecup = new FeatureDataset(mapping
210: .createSchema("GEOMETRY"));
211: IndexedFeatureCollection indexedB = new IndexedFeatureCollection(
212: fcSource);
213:
214: for (int i = 0; (i < aFeatures.size()); i++) {
215: Feature aFeature = (Feature) aFeatures.get(i);
216: Feature feature = new BasicFeature(fcRecup
217: .getFeatureSchema());
218: int nbFeatureWithin = 0;
219: for (Iterator j = indexedB.query(
220: aFeature.getGeometry()
221: .getEnvelopeInternal()).iterator(); j
222: .hasNext()
223: && !monitor.isCancelRequested();) {
224:
225: Feature bFeature = (Feature) j.next();
226: if (aFeature.getGeometry().within(
227: bFeature.getGeometry())) {
228: nbFeatureWithin++;
229: fWithin = bFeature;
230: }
231: }
232: // Attributes are copied if the resulting geometry is contained
233: // in one source geometry
234: if (nbFeatureWithin == 1 && attributesb) {
235: mapping.transferAttributes(fWithin, aFeature,
236: feature);
237: }
238: // Resulting geometry is cloned
239: feature.setGeometry((Geometry) aFeature
240: .getGeometry().clone());
241: fcRecup.add(feature);
242: }
243: if (fcRecup.size() > 0) {
244: context.getLayerManager().addLayer(CATEGORY,
245: layerName + "_" + MAPPING, fcRecup);
246: }
247: } else {
248: // Michael Michaud : Debug : gcFace is not in this else statement
249: //context.getLayerManager().addLayer("Graph", layerName + "_Face", fcFace);
250: }
251: context.getLayerManager().addLayer(CATEGORY,
252: layerName + "_" + FACE, fcFace);
253: }
254: }
255:
256: /**
257: * @param context
258: * @return
259: */
260: public void initialize(PlugInContext context) throws Exception {
261: context
262: .getFeatureInstaller()
263: .addMainMenuItem(
264: this ,
265: new String[] { MenuNames.TOOLS,
266: MenuNames.TOOLS_ANALYSIS },
267: this .getName(),
268: false,
269: null,
270: new MultiEnableCheck()
271: .add(
272: new EnableCheckFactory(context
273: .getWorkbenchContext())
274: .createTaskWindowMustBeActiveCheck())
275: .add(
276: new EnableCheckFactory(context
277: .getWorkbenchContext())
278: .createAtLeastNLayersMustExistCheck(1)));
279: }
280:
281: public boolean execute(PlugInContext context) throws Exception {
282: initDialog(context);
283: mid.setVisible(true);
284: mid.wasOKPressed();
285: return mid.wasOKPressed();
286: }
287:
288: public String getName() {
289: return I18N
290: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Planar-Graph");
291: }
292:
293: private void initDialog(PlugInContext context) {
294:
295: mid = new MultiInputDialog(context.getWorkbenchFrame(), TITLE,
296: true);
297: mid.addLayerComboBox(SELECT_LAYER, context.getLayerManager()
298: .getLayer(0), context.getLayerManager());
299: mid
300: .addLabel(I18N
301: .get("org.openjump.sigle.plugin.PlanarGraphPlugIn.The-layer-of-arcs-is-always-generated"));
302: mid.addCheckBox(CALCULATE_NODES, nodeb);
303: mid.addCheckBox(CALCULATE_FACES, faceb);
304: mid.addCheckBox(CALCULATE_RELATIONS, relb);
305: mid.addCheckBox(KEEP_ATTRIBUTES, attributesb);
306: mid.pack();
307: //mid.show();
308: }
309:
310: // ************************************************
311: // extract lines from a feature collection
312: // ************************************************
313: public List getLines(FeatureCollection fc) {
314: List linesList = new ArrayList();
315: LinearComponentExtracter filter = new LinearComponentExtracter(
316: linesList);
317: int count = 0;
318: for (Iterator i = fc.iterator(); i.hasNext();) {
319: Geometry g = ((Feature) i.next()).getGeometry();
320: g.apply(filter);
321: }
322: return linesList;
323: }
324:
325: // ************************************************
326: // Create edge layer
327: // ************************************************
328: public FeatureCollection createEdgeLayer(FeatureCollection fc,
329: boolean nodeb, boolean faceb, boolean relations,
330: PlugInContext context) {
331: // Schema edge
332: FeatureSchema fsEdge = new FeatureSchema();
333: fsEdge.addAttribute("GEOMETRY", AttributeType.GEOMETRY);
334: fsEdge.addAttribute("ID", AttributeType.INTEGER);
335: // Edge - Node relation
336: if (nodeb && relations) {
337: fsEdge.addAttribute(INITIAL_NODE, AttributeType.INTEGER);
338: fsEdge.addAttribute(FINAL_NODE, AttributeType.INTEGER);
339: }
340: // Edge - Face relation
341: if (faceb && relations) {
342: fsEdge.addAttribute(RIGHT_FACE, AttributeType.INTEGER);
343: fsEdge.addAttribute(LEFT_FACE, AttributeType.INTEGER);
344: }
345: FeatureDataset fcEdge = new FeatureDataset(fsEdge);
346:
347: // Get linear elements from all geometries in the layer
348: List list = getLines(fc);
349:
350: // Union the lines (unioning is the most expensive operation)
351: Geometry geom = gf.createMultiLineString(gf
352: .toLineStringArray(list));
353: geom = gf.createMultiLineString(null).union(geom);
354: GeometryCollection gc = geom instanceof GeometryCollection ? (GeometryCollection) geom
355: : gf.createGeometryCollection(new Geometry[] { geom });
356:
357: // Create the edge layer by merging lines between 3+ order nodes
358: // (Merged lines are multilines)
359: LineMerger lineMerger = new LineMerger();
360: for (int i = 0; i < gc.getNumGeometries(); i++) {
361: lineMerger.add(gc.getGeometryN(i));
362: }
363: edges = lineMerger.getMergedLineStrings();
364: int no = 0;
365: for (Iterator it = edges.iterator(); it.hasNext();) {
366: Feature f = new BasicFeature(fsEdge);
367: f.setGeometry((Geometry) it.next());
368: f.setAttribute("ID", new Integer(++no));
369: fcEdge.add(f);
370: }
371: context.getLayerManager().addLayer(CATEGORY,
372: layerName + "_" + EDGE, fcEdge);
373: return fcEdge;
374: }
375:
376: // ************************************************
377: // Create node layer
378: // ************************************************
379: public FeatureCollection createNodeLayer(FeatureCollection fcEdge,
380: PlugInContext context, boolean relations) {
381: FeatureSchema fsNode = new FeatureSchema();
382: fsNode.addAttribute("GEOMETRY", AttributeType.GEOMETRY);
383: fsNode.addAttribute("ID", AttributeType.INTEGER);
384: FeatureDataset fcNode = new FeatureDataset(fsNode);
385:
386: // Create the node Layer
387: Map nodes = new HashMap();
388: //List edges = geometriesFromFeatures(fcEdge);
389: for (Iterator it = edges.iterator(); it.hasNext();) {
390: Coordinate[] cc = ((Geometry) it.next()).getCoordinates();
391: nodes.put(cc[0], gf.createPoint(cc[0]));
392: nodes.put(cc[cc.length - 1], gf
393: .createPoint(cc[cc.length - 1]));
394: }
395: int no = 0;
396: for (Iterator it = nodes.values().iterator(); it.hasNext();) {
397: Feature f = new BasicFeature(fsNode);
398: f.setGeometry((Geometry) it.next());
399: f.setAttribute("ID", new Integer(++no));
400: nodes.put(f.getGeometry().getCoordinate(), f);
401: fcNode.add(f);
402: }
403: context.getLayerManager().addLayer(CATEGORY,
404: layerName + "_" + NODE, fcNode);
405:
406: // Compute the relation between edges and nodes
407: if (relations) {
408: for (Iterator it = fcEdge.iterator(); it.hasNext();) {
409: Feature f = (Feature) it.next();
410: Coordinate[] cc = f.getGeometry().getCoordinates();
411: f.setAttribute(INITIAL_NODE, ((Feature) nodes
412: .get(cc[0])).getAttribute("ID"));
413: f.setAttribute(FINAL_NODE, ((Feature) nodes
414: .get(cc[cc.length - 1])).getAttribute("ID"));
415: }
416: }
417: return fcNode;
418: }
419:
420: // ************************************************
421: // Create face layer
422: // ************************************************
423: public FeatureCollection createFaceLayer(FeatureCollection fcEdge,
424: PlugInContext context, boolean relations) {
425: // Create the face layer
426: FeatureSchema fsFace = new FeatureSchema();
427: fsFace.addAttribute("GEOMETRY", AttributeType.GEOMETRY);
428: fsFace.addAttribute("ID", AttributeType.INTEGER);
429: FeatureDataset fcFace = new FeatureDataset(fsFace);
430:
431: Polygonizer polygonizer = new Polygonizer();
432: polygonizer.add(edges);
433: int no = 0;
434: for (Iterator it = polygonizer.getPolygons().iterator(); it
435: .hasNext();) {
436: Feature f = new BasicFeature(fsFace);
437: Geometry face = (Geometry) it.next();
438: face.normalize(); // add on 2007-08-11
439: f.setGeometry(face);
440: f.setAttribute("ID", new Integer(++no));
441: //System.out.println(this.sFace + ": " + f.getID() + " : " + f.getAttribute("ID"));
442: fcFace.add(f);
443: }
444: //context.getLayerManager().addLayer("Graph", layerName+"_Face", fcFace);
445:
446: // inscrit les numéros de face dans les arcs
447: // Les arcs qui sont en bords de face sont codés à -1.
448: if (relations) {
449: for (Iterator it = fcEdge.getFeatures().iterator(); it
450: .hasNext();) {
451: Feature edge = (Feature) it.next();
452: // Fix added on 2007-07-09 [mmichaud]
453: edge.setAttribute(RIGHT_FACE, MINUS_ONE);
454: edge.setAttribute(LEFT_FACE, MINUS_ONE);
455: Geometry g1 = edge.getGeometry();
456: List list = fcFace.query(g1.getEnvelopeInternal());
457: for (int i = 0; i < list.size(); i++) {
458: Feature face = (Feature) list.get(i);
459: labelEdge(edge, face);
460: /*
461: Geometry g2 = face.getGeometry();
462: Geometry inters = g2.intersection(g1);
463: // Michael Michaud : added on 2006-05-01
464: // Process properly the case of empty intersection
465: if (inters.isEmpty()) continue;
466: else if (inters.getLength()>0) {
467: Integer idValue = (Integer) face.getAttribute("ID");
468: if (!idValue.equals(MINUS_ONE)) {
469: if (inters.getCoordinates()[0].equals(g1.getCoordinates()[0])) {
470: edge.setAttribute(RIGHT_FACE, face.getAttribute("ID"));
471: }
472: else {edge.setAttribute(LEFT_FACE, face.getAttribute("ID"));}
473: }
474: }
475: */
476: }
477: }
478: }
479: return fcFace;
480: }
481:
482: private void labelEdge(Feature edge, Feature face) {
483: IntersectionMatrix im = edge.getGeometry().relate(
484: face.getGeometry());
485: // intersection between boundaries has dimension 1
486: if (im.matches("*1*******")) {
487: int edgeC0 = getIndex(
488: edge.getGeometry().getCoordinates()[0], face
489: .getGeometry());
490: int edgeC1 = getIndex(
491: edge.getGeometry().getCoordinates()[1], face
492: .getGeometry());
493: // The Math.abs(edgeC1-edgeC0) test inverse the rule when the two consecutive
494: // points are the last point and the first point of a ring...
495: if ((edgeC1 > edgeC0 && Math.abs(edgeC1 - edgeC0) == 1)
496: || (edgeC1 < edgeC0 && Math.abs(edgeC1 - edgeC0) > 1)) {
497: edge.setAttribute(RIGHT_FACE, face.getAttribute("ID"));
498: } else
499: edge.setAttribute(LEFT_FACE, face.getAttribute("ID"));
500: }
501: // intersection between the line and the polygon interior has dimension 1
502: else if (im.matches("1********")) {
503: edge.setAttribute(RIGHT_FACE, face.getAttribute("ID"));
504: edge.setAttribute(LEFT_FACE, face.getAttribute("ID"));
505: }
506: // intersection between the line and the polygon exterior has dimension 1
507: //else if (im.matches("F********")) {}
508: else
509: ;
510: }
511:
512: // Returns the index of c in the geometry g or -1 if c is not a vertex of g
513: private int getIndex(Coordinate c, Geometry g) {
514: Coordinate[] cc = g.getCoordinates();
515: for (int i = 0; i < cc.length; i++) {
516: if (cc[i].equals(c))
517: return i;
518: }
519: return -1;
520: }
521:
522: }
|