/** ** Person.java ** ** Copyright 2011 by Sarah Wise, Mark Coletti, Andrew Crooks, and ** George Mason University. ** ** Licensed under the Academic Free License version 3.0 ** ** See the file "LICENSE" for more information ** ** $Id$ **/ package sim.app.geo.schellingspace; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.util.AffineTransformation; import java.util.ArrayList; import java.util.Random; import sim.engine.SimState; import sim.engine.Steppable; import sim.util.Bag; import sim.util.geo.MasonGeometry; /** * The mobile agent in the simulation. * */ //@SuppressWarnings("restriction") public class Person implements Steppable { private static final long serialVersionUID = 1L; /** What "class" the agent belongs to */ public enum Affiliation { RED, BLUE } private Affiliation affiliation; // position information MasonGeometry location; SchellingGeometry region; // given parameters double personalThreshold = .5; double moveDist = 1000.; double minDist = 100.; // updated variables int numMoves = 0; /** * Constructor function */ public Person(Affiliation a) { affiliation = a; } public Affiliation getAffiliation() { return affiliation; } public void setAffiliation(Affiliation affiliation) { this.affiliation = affiliation; } /** * @param world the SchellingSpace, which holds the GeomVectorFields * @return whether the location is acceptable for the Person, * based on the Person's personalThreshold */ boolean acceptable(SchellingSpace world) { Bag neighbors = world.agents.getObjectsWithinDistance(location, minDist); // calculate the proportion of unlike neighbors double unlikeNeighbors = 0.; for (Object o : neighbors) { Person neighbor = (Person) ((MasonGeometry) o).getUserData(); if (! neighbor.getAffiliation().equals(affiliation)) { unlikeNeighbors++; } } // if the location is unacceptable, return false if (unlikeNeighbors / neighbors.numObjs > personalThreshold) { return false; } else // if it is acceptable, return true { return true; } } public MasonGeometry getGeometry() { return location; } /** * Moves the Person randomly in the space, updating the SchellingPolygons * about their contents as it goes * @param world the SchellingSpace instance, which holds the GeomVectorFields */ public void moveRandomly(SchellingSpace world) { // the current location Coordinate coord = (Coordinate) location.geometry.getCoordinate().clone(); // find a new position Random rand = new Random(); double xinc = moveDist * (rand.nextDouble() - .5), yinc = moveDist * (rand.nextDouble() - .5); coord.x += xinc; coord.y += yinc; // while the new position is not inside the space, keep trying while (!world.world.isInsideUnion(coord)) { coord.x -= xinc; coord.y -= yinc; xinc = moveDist * (rand.nextDouble() - .5); yinc = moveDist * (rand.nextDouble() - .5); coord.x += xinc; coord.y += yinc; } // once the location works, move to the new location location.geometry.apply(AffineTransformation.translationInstance(xinc, yinc)); // if the Person has moved to a different region, update the SchellingPolygons // about their current contents if (!region.geometry.contains(location.geometry)) { region.residents.remove(this); determineCurrentRegion(region); region.residents.add(this); } // update the number of moves made numMoves++; } /** * Determines whether the Person's current location is acceptable. * If the location is not acceptable, attempts to move the Person to * a better location. */ @Override public void step(SimState state) { SchellingSpace world = (SchellingSpace) state; // check to see if the number of neighbors exceeds a given tolerance if (!acceptable(world)) { moveRandomly(world); // if it does, move randomly } } /** * breadth first search on the polygons to determine current location relative to * SchellingPolygons * @param poly the SchellingGeometry in which the Person last found himself */ void determineCurrentRegion(SchellingGeometry poly) { // keep track of which SchellingPolygons have been investigated and which // are about to be investigated ArrayList<SchellingGeometry> checked = new ArrayList<SchellingGeometry>(); ArrayList<SchellingGeometry> toCheck = new ArrayList<SchellingGeometry>(); checked.add(poly); // we know it's not where it was anymore! toCheck.addAll(poly.neighbors); // first check the neighbors // while there is a Polygon to investigate, keep running while (toCheck.size() > 0) { SchellingGeometry p = toCheck.remove(0); if (p.geometry.contains(location.geometry)) { // ---successfully located!--- region = p; return; } else { checked.add(p); // we have investigated this polygon // add all uninvestigated neighbors not already slated for investigation for (SchellingGeometry n : p.neighbors) { if (!checked.contains(n) && !toCheck.contains(n)) { toCheck.add(n); } } } } // if it's not anywhere, throw an error System.out.println("ERROR: Person is not located within any polygon"); } }