package org.openjump.core.ui.plugin.tools;
/*
* This tool has been developped by Michael Michaud Juin 2005
* Erwan Bocher added a feature to keep original attributes
* Stefan Steiniger did the internationalization
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.openjump.sigle.utilities.geom.FeatureCollectionUtil;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.IntersectionMatrix;
import com.vividsolutions.jts.geom.util.LinearComponentExtracter;
import com.vividsolutions.jts.operation.linemerge.LineMerger;
import com.vividsolutions.jts.operation.polygonize.Polygonizer;
import com.vividsolutions.jts.operation.union.UnaryUnionOp;
import com.vividsolutions.jump.I18N;
import com.vividsolutions.jump.feature.AttributeType;
import com.vividsolutions.jump.feature.BasicFeature;
import com.vividsolutions.jump.feature.Feature;
import com.vividsolutions.jump.feature.FeatureCollection;
import com.vividsolutions.jump.feature.FeatureDataset;
import com.vividsolutions.jump.feature.FeatureSchema;
import com.vividsolutions.jump.feature.IndexedFeatureCollection;
import com.vividsolutions.jump.task.TaskMonitor;
import com.vividsolutions.jump.tools.AttributeMapping;
import com.vividsolutions.jump.workbench.model.Layer;
import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory;
import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck;
import com.vividsolutions.jump.workbench.plugin.PlugInContext;
import com.vividsolutions.jump.workbench.plugin.ThreadedBasePlugIn;
import com.vividsolutions.jump.workbench.ui.MenuNames;
import com.vividsolutions.jump.workbench.ui.MultiInputDialog;
/**
* PlanarGraphPlugIn computes a planar graph from a set of features.
* The user can choose to produce the nodes, the edges and the faces, or only
* some of those features.
* The following relations are kept as edge attributes :<br>
* Initial node identifier<br>
* Final node identifier<br>
* Right face<br>
* Left face<br>
* @author Michael Michaud and Erwan Bocher (2005-06)
* Comments added by Michael Michaud on 2006-05-01
*/
public class PlanarGraphPlugIn extends ThreadedBasePlugIn {
public final static String EDGE = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Edge");
public final static String FACE = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Face");
public final static String NODE = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Node");
public final static String CATEGORY = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Graph");
public final static String MAPPING = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Mapping");
public final static String TITLE = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Topologic-Analysis");
public final static String SELECT_LAYER = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Select-layer-to-analyse");
public final static String CALCULATE_NODES = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Calculate-nodes");
public final static String CALCULATE_FACES = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Calculate-faces");
public final static String CALCULATE_RELATIONS = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Calculate-the-relations-arcs-nodes-and-/or-arcs-faces");
public final static String KEEP_ATTRIBUTES = I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Keep-attributes");
public final static Integer MINUS_ONE = new Integer(-1);
GeometryFactory gf = new GeometryFactory();
// Do you want to compute nodes
private static boolean nodeb = true;
// Do you want to compute faces
private static boolean faceb = true;
// Do you want to compute edge/node relations and/or edges/faces relations
private static boolean relb = true;
// Do you want to keep original attributes
private static boolean attributesb = true;
// Attribute names have not been internationalized
private static String LEFT_FACE = "LeftFace";
private static String RIGHT_FACE = "RightFace";
private static String INITIAL_NODE = "StartNode";
private static String FINAL_NODE = "EndNode";
private String layerName;
public Collection edges;
private MultiInputDialog mid;
/**
* Calculations take place here
*/
public void run(TaskMonitor monitor, PlugInContext context)
throws Exception {
// Faces FeatureCollection declaration
FeatureCollection fcFace = null;
// Getting options from the dialog
Layer layer = mid.getLayer(SELECT_LAYER);
FeatureCollection fcSource = layer.getFeatureCollectionWrapper();
layerName = layer.getName();
nodeb = mid.getBoolean(CALCULATE_NODES);
faceb = mid.getBoolean(CALCULATE_FACES);
relb = mid.getBoolean(CALCULATE_RELATIONS);
attributesb = mid.getBoolean(KEEP_ATTRIBUTES);
// Get linear elements from all geometries in the layer
monitor.report(I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Searching-for-linear-elements"));
List list = getLines(fcSource);
monitor.report(I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Number-of-found-elements") + ": " + list.size());
// Union the lines (unioning is the most expensive operation)
monitor.report(I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Generate-layer-of-arcs"));
FeatureCollection fcEdge = createEdgeLayer(
layer.getFeatureCollectionWrapper(), nodeb, faceb, relb, context);
monitor.report(I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Arc-layer-generated"));
// Create the node Layer
monitor.report(I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Create-nodes"));
if (nodeb) {
FeatureCollection fcNode = createNodeLayer(fcEdge, context, relb);
}
monitor.report(I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Layer-with-nodes-generated"));
// Create face Layer from edges with Polygonizer
monitor.report(I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Create-faces"));
if (faceb) {
fcFace = createFaceLayer(fcEdge, context, relb);
}
monitor.report(I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Layer-of-faces-generated"));
//Erwan aout 2005
// Here, one process the result dataset to get attributes from source layer
// Attributes are transferred if the primitive is included in the source feature
// If the source layer is made of polygons, attributes are transferred to faces
// If the source layer is made of linestrings, attributes are transferred to edges
if (faceb){
Feature fWithin = null;
AttributeMapping mapping = null;
if (attributesb) {
// Use mapping to get the attributes
mapping = new AttributeMapping(new FeatureSchema(), new FeatureSchema());
List aFeatures = new ArrayList();
monitor.report(I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Transfer-of-attributes"));
if (FeatureCollectionUtil.getFeatureCollectionDimension(fcSource)==2){
mapping = new AttributeMapping(fcSource.getFeatureSchema(), fcFace.getFeatureSchema());
aFeatures = fcFace.getFeatures();
}
else if (FeatureCollectionUtil.getFeatureCollectionDimension(fcSource)==1) {
mapping = new AttributeMapping(fcSource.getFeatureSchema(), fcFace.getFeatureSchema());
aFeatures = fcEdge.getFeatures();
}
else {context.getWorkbenchFrame().warnUser(
I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Cannot-transfer-attributes"));
}
FeatureDataset fcRecup = new FeatureDataset(mapping.createSchema("GEOMETRY"));
IndexedFeatureCollection indexedB = new IndexedFeatureCollection(fcSource);
for (int i = 0; (i < aFeatures.size());i++) {
Feature aFeature = (Feature) aFeatures.get(i);
Feature feature = new BasicFeature(fcRecup.getFeatureSchema());
int nbFeatureWithin = 0;
for (Iterator j = indexedB.query(aFeature.getGeometry().getEnvelopeInternal()).iterator();
j.hasNext() && !monitor.isCancelRequested();) {
Feature bFeature = (Feature) j.next();
if (aFeature.getGeometry().within(bFeature.getGeometry())) {
nbFeatureWithin++;
fWithin = bFeature;
}
}
// Attributes are copied if the resulting geometry is contained
// in one source geometry
if (nbFeatureWithin == 1 && attributesb) {
mapping.transferAttributes(fWithin, aFeature, feature);
}
// Resulting geometry is cloned
feature.setGeometry((Geometry) aFeature.getGeometry().clone());
fcRecup.add(feature);
}
if (fcRecup.size() > 0) {
context.getLayerManager().addLayer(CATEGORY, layerName + "_" + MAPPING, fcRecup);
}
}
else {
// Michael Michaud : Debug : gcFace is not in this else statement
//context.getLayerManager().addLayer("Graph", layerName + "_Face", fcFace);
}
context.getLayerManager().addLayer(CATEGORY, layerName + "_" + FACE, fcFace);
}
}
/**
* @param context
*/
public void initialize(PlugInContext context) throws Exception {
context.getFeatureInstaller()
.addMainMenuItem(this,new String[] { MenuNames.TOOLS, MenuNames.TOOLS_EDIT_GEOMETRY, MenuNames.CONVERT},
this.getName(), false, null,
new MultiEnableCheck().add(new EnableCheckFactory(context.getWorkbenchContext()).createTaskWindowMustBeActiveCheck())
.add(new EnableCheckFactory(context.getWorkbenchContext()).createAtLeastNLayersMustExistCheck(1))
);
}
public boolean execute(PlugInContext context) throws Exception {
initDialog(context);
mid.setVisible(true);
mid.wasOKPressed();
return mid.wasOKPressed();
}
public String getName(){
return I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.Planar-Graph") + "...";
}
private void initDialog(PlugInContext context) {
mid = new MultiInputDialog(context.getWorkbenchFrame(), TITLE, true);
mid.addLayerComboBox(SELECT_LAYER, context.getLayerManager().getLayer(0), context.getLayerManager());
mid.addLabel(I18N.get("org.openjump.sigle.plugin.PlanarGraphPlugIn.The-layer-of-arcs-is-always-generated"));
mid.addCheckBox(CALCULATE_NODES, nodeb);
mid.addCheckBox(CALCULATE_FACES, faceb);
mid.addCheckBox(CALCULATE_RELATIONS, relb);
mid.addCheckBox(KEEP_ATTRIBUTES, attributesb);
mid.pack();
//mid.show();
}
// ************************************************
// extract lines from a feature collection
// ************************************************
public List getLines(FeatureCollection fc) {
List linesList = new ArrayList();
LinearComponentExtracter filter = new LinearComponentExtracter(linesList);
int count = 0;
for (Iterator i = fc.iterator(); i.hasNext(); ) {
Geometry g = ((Feature)i.next()).getGeometry();
g.apply(filter);
}
return linesList;
}
// ************************************************
// Create edge layer
// ************************************************
public FeatureCollection createEdgeLayer(FeatureCollection fc,
boolean nodeb, boolean faceb, boolean relations,
PlugInContext context) {
// Schema edge
FeatureSchema fsEdge = new FeatureSchema();
fsEdge.addAttribute("GEOMETRY", AttributeType.GEOMETRY);
fsEdge.addAttribute("ID", AttributeType.INTEGER);
// Edge - Node relation
if (nodeb && relations) {
fsEdge.addAttribute(INITIAL_NODE, AttributeType.INTEGER);
fsEdge.addAttribute(FINAL_NODE, AttributeType.INTEGER);
}
// Edge - Face relation
if (faceb && relations) {
fsEdge.addAttribute(RIGHT_FACE, AttributeType.INTEGER);
fsEdge.addAttribute(LEFT_FACE, AttributeType.INTEGER);
}
FeatureDataset fcEdge = new FeatureDataset(fsEdge);
// Get linear elements from all geometries in the layer
List list = getLines(fc);
// Union the lines (unioning is the most expensive operation)
// fixed on 2008-11-10 by mmichaud, using the new UnaryUnionOp
Geometry gc = new UnaryUnionOp(list).union();
if (!(gc instanceof GeometryCollection)) {
gc = gf.createGeometryCollection(new Geometry[]{gc});
}
// Create the edge layer by merging lines between 3+ order nodes
// (Merged lines are multilines)
LineMerger lineMerger = new LineMerger();
for (int i = 0 ; i < gc.getNumGeometries() ; i++) {
lineMerger.add(gc.getGeometryN(i));
}
edges = lineMerger.getMergedLineStrings();
int no = 0;
for (Iterator it = edges.iterator() ; it.hasNext() ;) {
Feature f = new BasicFeature(fsEdge);
f.setGeometry((Geometry)it.next());
f.setAttribute("ID", new Integer(++no));
fcEdge.add(f);
}
context.getLayerManager().addLayer(CATEGORY, layerName + "_" + EDGE, fcEdge);
return fcEdge;
}
// ************************************************
// Create node layer
// ************************************************
public FeatureCollection createNodeLayer(FeatureCollection fcEdge,
PlugInContext context, boolean relations) {
FeatureSchema fsNode = new FeatureSchema();
fsNode.addAttribute("GEOMETRY", AttributeType.GEOMETRY);
fsNode.addAttribute("ID", AttributeType.INTEGER);
FeatureDataset fcNode = new FeatureDataset(fsNode);
// Create the node Layer
Map nodes = new HashMap();
//List edges = geometriesFromFeatures(fcEdge);
for (Iterator it = edges.iterator() ; it.hasNext() ;) {
Coordinate[] cc = ((Geometry)it.next()).getCoordinates();
nodes.put(cc[0], gf.createPoint(cc[0]));
nodes.put(cc[cc.length-1], gf.createPoint(cc[cc.length-1]));
}
int no = 0;
for (Iterator it = nodes.values().iterator() ; it.hasNext() ; ) {
Feature f = new BasicFeature(fsNode);
f.setGeometry((Geometry)it.next());
f.setAttribute("ID", new Integer(++no));
nodes.put(f.getGeometry().getCoordinate(), f);
fcNode.add(f);
}
context.getLayerManager().addLayer(CATEGORY, layerName + "_" + NODE, fcNode);
// Compute the relation between edges and nodes
if (relations) {
for (Iterator it = fcEdge.iterator() ; it.hasNext() ;) {
Feature f = (Feature)it.next();
Coordinate[] cc = f.getGeometry().getCoordinates();
f.setAttribute(INITIAL_NODE, ((Feature)nodes.get(cc[0])).getAttribute("ID"));
f.setAttribute(FINAL_NODE, ((Feature)nodes.get(cc[cc.length-1])).getAttribute("ID"));
}
}
return fcNode;
}
// ************************************************
// Create face layer
// ************************************************
public FeatureCollection createFaceLayer(FeatureCollection fcEdge,
PlugInContext context, boolean relations) {
// Create the face layer
FeatureSchema fsFace = new FeatureSchema();
fsFace.addAttribute("GEOMETRY", AttributeType.GEOMETRY);
fsFace.addAttribute("ID", AttributeType.INTEGER);
FeatureDataset fcFace = new FeatureDataset(fsFace);
Polygonizer polygonizer = new Polygonizer();
polygonizer.add(edges);
int no = 0;
for (Iterator it = polygonizer.getPolygons().iterator() ; it.hasNext() ;) {
Feature f = new BasicFeature(fsFace);
Geometry face = (Geometry)it.next();
face.normalize(); // add on 2007-08-11
f.setGeometry(face);
f.setAttribute("ID", new Integer(++no));
//System.out.println(this.sFace + ": " + f.getID() + " : " + f.getAttribute("ID"));
fcFace.add(f);
}
//context.getLayerManager().addLayer("Graph", layerName+"_Face", fcFace);
// write face identifiers into edges
// if there is no face on the side of an edge, id number is -1
if(relations) {
for (Iterator it = fcEdge.getFeatures().iterator() ; it.hasNext() ; ) {
Feature edge = (Feature)it.next();
// Fix added on 2007-07-09 [mmichaud]
edge.setAttribute(RIGHT_FACE, MINUS_ONE);
edge.setAttribute(LEFT_FACE, MINUS_ONE);
Geometry g1 = edge.getGeometry();
List list = fcFace.query(g1.getEnvelopeInternal());
for (int i = 0 ; i < list.size() ; i++) {
Feature face = (Feature)list.get(i);
labelEdge(edge, face);
/*
Geometry g2 = face.getGeometry();
Geometry inters = g2.intersection(g1);
// Michael Michaud : added on 2006-05-01
// Process properly the case of empty intersection
if (inters.isEmpty()) continue;
else if (inters.getLength()>0) {
Integer idValue = (Integer) face.getAttribute("ID");
if (!idValue.equals(MINUS_ONE)) {
if (inters.getCoordinates()[0].equals(g1.getCoordinates()[0])) {
edge.setAttribute(RIGHT_FACE, face.getAttribute("ID"));
}
else {edge.setAttribute(LEFT_FACE, face.getAttribute("ID"));}
}
}
*/
}
}
}
return fcFace;
}
private void labelEdge(Feature edge, Feature face) {
IntersectionMatrix im = edge.getGeometry().relate(face.getGeometry());
// intersection between boundaries has dimension 1
if (im.matches("*1*******")) {
int edgeC0 = getIndex(edge.getGeometry().getCoordinates()[0], face.getGeometry());
int edgeC1 = getIndex(edge.getGeometry().getCoordinates()[1], face.getGeometry());
// The Math.abs(edgeC1-edgeC0) test inverse the rule when the two consecutive
// points are the last point and the first point of a ring...
if ((edgeC1 > edgeC0 && Math.abs(edgeC1-edgeC0) == 1) ||
(edgeC1 < edgeC0 && Math.abs(edgeC1-edgeC0) > 1)) {
edge.setAttribute(RIGHT_FACE, face.getAttribute("ID"));
}
else edge.setAttribute(LEFT_FACE, face.getAttribute("ID"));
}
// intersection between the line and the polygon interior has dimension 1
else if (im.matches("1********")) {
edge.setAttribute(RIGHT_FACE, face.getAttribute("ID"));
edge.setAttribute(LEFT_FACE, face.getAttribute("ID"));
}
// intersection between the line and the polygon exterior has dimension 1
//else if (im.matches("F********")) {}
else;
}
// Returns the index of c in the geometry g or -1 if c is not a vertex of g
private int getIndex(Coordinate c, Geometry g) {
Coordinate[] cc = g.getCoordinates();
for (int i = 0 ; i < cc.length ; i++) {
if (cc[i].equals(c)) return i;
}
return -1;
}
}