Search code examples
repast-simphony

Can the Repast NetworkBuilder be used in Geography and GIS Displays?


The popular Repast zombie tutorial using java employs a NetworkBuilder that allows you to visualize the infection network. I was wondering if there is anything similar that can be used with the Geography/GIS context. Alternatively, any thoughts on how I could construct my own? For example, if my agent moves from point A to point B, can I write a function that will draw a vector connecting the two?


Solution

  • I wrote a projection listener class that will manage Repast network objects in a geography projection that can be visualized in a GIS display. The attached listener class can be placed in your project and used in the ContextBuilder to link a Geography and Network projection like this:

    GeographyParameters geoParams = new GeographyParameters(); 
    Geography geography = GeographyFactoryFinder.createGeographyFactory(null).createGeography("Geography", context, geoParams);
    
    NetworkBuilder<Object> netBuilder = new NetworkBuilder<Object>("Network", context, true);
    Network net = netBuilder.buildNetwork();
    
    GISNetworkListener netListener = new GISNetworkListener(context, geography, net);
    

    The GIS NetworkListener binds the geography and network within the specified context such that changes in one will be reflected in the other. This handles agent movement, additions, and removal from the geography along with network add/remove edge events. If you have multiple networks you can create separate listener instances for each. As long as the Repast network and geography projections are otherwise used normally in your code, no additional code is required to update events between the projections.

    To visualize the network edges in the GIS displays, you will need to create a simple edge class that extends RepastEdge, for example the attached MyNetworkEdge class. This is only because the display wizards need a user class for styling. In the displays, you can select the MyNetworkEdge class in the agent types dialog and specify either a line type or style class like the attached MyNetworkStyle class. To create links in the Repast network, you will need to use the add and remove edge methods with the MyNetworkEdge, e.g.

    net.addEdge(new MyNetworkEdge(source, target));
    

    and you can of course add more capabilities to the MyNetworkEdge class as needed.

    ProjectionListener class:

    package geography;
    
    import com.vividsolutions.jts.geom.Coordinate;
    import com.vividsolutions.jts.geom.GeometryFactory;
    import com.vividsolutions.jts.geom.LineString;
    import com.vividsolutions.jts.geom.MultiLineString;
    
    import repast.simphony.context.Context;
    import repast.simphony.space.gis.Geography;
    import repast.simphony.space.graph.Network;
    import repast.simphony.space.graph.RepastEdge;
    import repast.simphony.space.projection.ProjectionEvent;
    import repast.simphony.space.projection.ProjectionListener;
    
    /**
     * A ProjectionListener implementation for managing Repast network edges in a
     * Repast geography projection.  This listener responds to both geography event
     * and network events.
     * 
     * @author Eric Tatara
     *
     */
    public class GISNetworkListener implements ProjectionListener {
    
        Context context;
        Network network;
        Geography geography;
        GeometryFactory fac = new GeometryFactory();
    
        public GISNetworkListener(Context c, Geography g, Network n) {
            context = c;
            network = n;
            geography = g;
    
            network.addProjectionListener(this);
            geography.addProjectionListener(this);
        }
    
        @Override
        public void projectionEventOccurred(ProjectionEvent evt) {
    
            // When an object is moved in the geography, its network edges positions 
            // should be updated if the object has edges.
            if (evt.getType() == ProjectionEvent.OBJECT_MOVED){
                Iterable<RepastEdge> edges = network.getEdges(evt.getSubject());
    
                if (edges != null){
                    for (RepastEdge e : edges){
                        // Get the existing geometry for this edge
                        MultiLineString lineFeature = (MultiLineString)geography.getGeometry(e);
    
                        Coordinate sourceCoord = geography.getGeometry(e.getSource()).getCoordinate();
                        Coordinate targetCoord = geography.getGeometry(e.getTarget()).getCoordinate();
    
                        Coordinate coords[] = lineFeature.getCoordinates();
    
                        // Update the edge coordinates based on the source and target object 
                        // (agent) coordinates.
                        coords[0].setCoordinate(sourceCoord);
                        coords[1].setCoordinate(targetCoord);
                    }
                }
            }
    
            // When a Repast network edge is added, create a new MultiLineString geometry
            // to represent the edge in the geography.
            else if (evt.getType() == ProjectionEvent.EDGE_ADDED){  
                RepastEdge e = (RepastEdge)evt.getSubject();
    
                Coordinate sourceCoord = geography.getGeometry(e.getSource()).getCoordinate();
                Coordinate targetCoord = geography.getGeometry(e.getTarget()).getCoordinate();
    
                LineString lineString = fac.createLineString(new Coordinate[]{sourceCoord, 
                        targetCoord});
    
                MultiLineString mls = fac.createMultiLineString(new LineString[]{lineString});
    
                context.add(e);
                geography.move(e, mls);
            }
    
            // When a Repast edge remove event occurs, remove the edge geometry from the 
            // geography and the context.  This should also occur automatically when agents
            // are removed from a context or network.
            else if (evt.getType() == ProjectionEvent.EDGE_REMOVED){
                RepastEdge e = (RepastEdge)evt.getSubject();
    
                geography.move(e, null);
                context.remove(e);          
            }
        }
    }
    

    RepastEdge class:

    package geography;
    
    import repast.simphony.space.graph.RepastEdge;
    
    public class MyNetworkEdge extends RepastEdge {
    
        public MyNetworkEdge(Object source, Object target){
            super(source, target, false);
        }
    
    }
    

    Network style class:

    package geography;
    
    import gov.nasa.worldwind.render.SurfacePolyline;
    import gov.nasa.worldwind.render.SurfaceShape;
    
    import java.awt.Color;
    
    import repast.simphony.visualization.gis3D.style.SurfaceShapeStyle;
    
    /**
     * Style for MyNetworkEdges.
     * 
     * @author Eric Tatara
     *
     */
    public class MyNetworkStyle implements SurfaceShapeStyle<MyNetworkEdge>{
    
        @Override
        public SurfaceShape getSurfaceShape(MyNetworkEdge object, SurfaceShape shape) {
          return new SurfacePolyline();
        }
    
        @Override
        public Color getFillColor(MyNetworkEdge obj) {
            return null;
        }
    
        @Override
        public double getFillOpacity(MyNetworkEdge obj) {
            return 0;
        }
    
        @Override
        public Color getLineColor(MyNetworkEdge obj) {
            return Color.BLUE;
        }
    
        @Override
        public double getLineOpacity(MyNetworkEdge obj) {
            return 1.0;
        }
    
        @Override
        public double getLineWidth(MyNetworkEdge obj) {
            return 2;
        }
    }