RealTime Storm Intercept Plotter Description

Problem Domain

The problem domain for this project is storm chasing. Numerous projects are currently underway to gather data from severe storms. A basic requirement of these projects is getting the sensing equipment within sensing range of the weather event. On one level this is a simple problem with a simple solution: go to where the storm is. However, the solution is complicated by a number of factors.
Among these factors are the uncertainty of the current position of the storm, and uncertainty about its current speed and direction. In addition, the behavior of the storm can change on a short term basis, making old calculations obsolete. Also, while the chase vehicles are constrained to travel the roads, the choices of roads can be large, with significant impact on the actual travel time. A secondary component is that the road conditions can change depending on time of day, weather conditions, and other factors.

Program Requirements

All of these varying conditions suggest the need for a real-time support system. This system must be able to track the current position of the storm, and predict its future behavior. It must be able to track the current position of the chase vehicle. It must support a map of the existing roads, and features used by the weather service. It must be able to model the travel conditions of the roads, allows estimates of maximum (safe) speeds. It must calculate the most effective (shortest drive time) path from the vehicle to the tracked event.

Other uses

Clearly there are other uses for this type of software. It could be used to develop time and traffic dependent routing for emergency vehicles. It could be used to select the closest respond emergency service from a number of patrolling vehicles.

Design Approach

Model structure

The general design approach is to use a limited state, frequently updated representation of the storm and the chase car.
  • Road Grid (Graph)
  • Storm Position and Velocity vector
  • Car Position, Goal Node
  • Intercept Node

    The only state in the storm is it's current position and vector, and a predicted intercept point. This point is independent of the road grid, and is calculated using straightline distance from the chase car and an average chase car velocity.

    The chase car records it's current position, and it's goal node. The goal node is the next node on the road graph towards which the car is heading. The intercept Node is the closed vertex in the road graph to the predicted intercept point.

    Model Updates

    The general model updates the system at 5 minute intervals of simulated time. The storm is moved, based on the speed and direction, the car is moved towards its immediate goal. The Predicted intercept point is recalculated, and the Intercept Node is recalculated. If the car is on the road - not at a vertex, the screen is repainted. If the chase car is at it's goal node, a shortest path is calculated fron the car to the Intercept Node, and the new goal node is established.

    It is reasonable to suggest that the chase car should recalculate it while on the road. This is not the case on freeways, where u-turns are frowned upon, but could be implemented on other roads. I will add this feature in a future revision. In the meantime I have been adding additional nodes at the mid points of longer road segments.

    Shortest Path Algorithm

    The system uses a dynamic programming approach to Dijkstra's shortest path algorithm. The weighting on the edges in the graph is based on driving time, the product of the distance between the verticies and the road conditions. Road conditions for this model are fixed, with maximum speeds assigned to the four categories of road.

    The algorithm begins with a route table, with one entry for each node. Each entry contains three fields: Cost to reach the node, the node it was reached from, and whether it has been processed. At initialization, the cost is infinite (1,000,000), the source node is blank, and the processed field in blank. The cost to the source node is marked as zero. Repeat:
  • Find the lowest cost node (T) that is unprocessed.
  • For each edge from T, calculate the cost ( Cost of node T + weight of edge), to reach the directly connected nodes.
  • If this cost is lower than the current cost, change the cost to the new cost, and make T the from node.
  • Mark T as processed. Until all nodes are processed. Find the destination node, and trace backwards through the from nodes until the Node.From = Source. Return this Node as the next Goal Node.

    Algorithm Cost.

    The algorithm runs in O(V^2), and can be improved by implementing a more efficient method for finding the lowest cost unprocessed node. Since road graphs are typically sparse, the runtime can be improved by maintaining the unprocessed nodes in a priority queue with either a binary, or fibonacci heap as the underlying data structure. This would provide an amortized, asymptotic runtime of O(V lg V). However, this implementation does not utilize this.

    Known Problems

    There are some interesting problems that crop up.
  • Thread Synchronization
  • The car drifts off the road
  • Imprecise Storm Vector

    Thread Synchronization

    On some systems, noteably Windows based Netscape. The run routine will sometimes start before the initialization is complete. This results in a NullPointerException. The only fix I know of is to flush the caches, and reload.
    This also shows up (very rarely) as a method not found error on UNIX boxes.

    The car drifts off the road

    If the last car update places the car very close to the goal Node, the next update can cause it to overshoot. This can cause the car to start for the next Goal slightly off the road. It converges, but it can take a while.

    Imprecise Storm Vector

    The storm vector is stored as two integers. As a result, slow moving storms have a high angular granularity. It is impossible to set up a 12MPH storm moving ESE. I will increase the scaling factor so that there is better angular resolution.

    Data Structures

    The data structures are:
  • RoadSegments
  • Cities
  • Storm
  • Chaser

    RoadSegments

    class RoadSeg {
       int FromNode ;
       int ToNode ;
       int i_Category ;
    
       RoadSeg( int from, int to, int Cat) {
          FromNode = from ;
          ToNode = to ;
          i_Category = Cat ;
       }
    
       public void draw(Graphics g, City[] cities) {
          switch (i_Category) {
    	case 3: g.setColor(Color.blue) ;
    		break ;
    	case 2: g.setColor(Color.green) ;
    		break ;
            case 4: g.setColor(Color.orange) ;
    		break ;
    	default: g.setColor(Color.gray) ;
          }
          g.drawLine(cities[FromNode].p_Location.x, 
    		 cities[FromNode].p_Location.y, 
    		 cities[ToNode].p_Location.x, 
    		 cities[ToNode].p_Location.y) ;
       }
    } // End of RoadSeg Class
    

    Cities

    class City {
      int Print ;
      String s_Name ;
      Point p_Location ;
     
    City(){
      Print = 0;
      s_Name = new String("") ;
      p_Location = new Point(0,0) ;
    }
    
    City( String s, int x, int y, int c) {
      Print = c ;
      s_Name = new String(s) ;
      p_Location = new Point(x,y) ;
    }
    
    public void show(Graphics g) {
      if (Print == 0) return ;
      g.setColor(Color.black) ;
      g.drawString(s_Name, p_Location.x, p_Location.y) ;
    }
    
    } // End of City Class
    
    

    Storm

    class Storm {
      boolean isValid = true;
      int uncertainty = 5 ;
      Point p_Pos ;
      Point p_Vect ;
      Point p_Intercept ;
      int torHorz = 15 ;
      int torVert = 20 ;
    
      
      Storm( int PX, int PY, int VX, int VY) {
        p_Pos = new Point(PX,PY) ;
        p_Vect = new Point(VX, VY) ;
        p_Intercept = new Point(PX, PY) ;
      }
    
      public void draw( Image smtor, Graphics g, Applet a) {
        if (isValid) {
          g.setColor(Color.yellow) ;
          g.drawString("S", p_Pos.x - 4 , p_Pos.y + 4) ;
          g.setColor(Color.red) ;
          g.drawOval(p_Intercept.x, p_Intercept.y,uncertainty,uncertainty) ;
        }
      }
    
      public void update(int Width, int Height) {
          // Update StormPosition and Vector
          p_Pos.y  += p_Vect.y ;
          p_Pos.x  += p_Vect.x ;
    	if ( (p_Pos.y < 0 ) || (p_Pos.x < 0) ||
    	     (p_Pos.x > Width) || (p_Pos.y > Height) )
    	isValid = false ;
      }
     
      public Point predict( int Time) {
    	int Px = p_Pos.x + (p_Vect.x * Time) ;
    	int Py = p_Pos.y + (p_Vect.y * Time) ;
    	p_Intercept.x = Px ;
    	p_Intercept.y = Py ;
    	Point rv = new Point(Px, Py) ;
    	return rv ;
      }
    
      public void setPosition(Point here) {
        p_Pos.y = here.y ;
        p_Pos.x = here.x ;
        isValid = true;
      }
    
      public void setVect(Point here, int VectorScale) {
        
        p_Vect.y = (here.y - p_Pos.y) / VectorScale ;
        p_Vect.x = (here.x - p_Pos.x) / VectorScale ;     
      }
    
      public int Speed() {
    	return (int) (12 * Math.sqrt( (p_Vect.x*p_Vect.x) + (p_Vect.y*p_Vect.y)));
      }
    } // end of Storm Class  
    

    Chaser

    class Chaser {
      Point p_Position ;
      Point p_Target ;
      double d_Speed;
      
    
      Chaser( int x, int y) {
       p_Position = new Point(x ,y) ;
       p_Target = new Point(x,y) ;
       d_Speed =  12.0 ;
      }
    
      public int driveTime(Point P) {
    	double x = (P.x - p_Position.x) * (P.x - p_Position.x) ;
    	double y = (P.y - p_Position.y) * (P.y - p_Position.y) ;
    	double speed = d_Speed / 2 ;
    	return (int)(Math.sqrt(x+y) / speed) ;
      }
    
      public void draw(Graphics g) {
          g.setColor(Color.red) ;
          g.drawString("C",p_Position.x-4, p_Position.y+4); 	
      }
    
      public void move(int RoadCond) {
          // Move ChaseCar
          int dx = p_Position.x - p_Target.x ;
          int dy = p_Position.y - p_Target.y ;
          int mmx = 0 ;
          int mmy = 0 ;
          double vel = d_Speed / RoadCond ;
          double hyp = 0.0 ;
          
          if ((dx!=0) || (dy!=0)) {
            hyp = Math.sqrt((dx*dx) + (dy*dy));
             // get maxmove components
    	 // If we would overshoot the target, just go there.
             mmx = (int)(vel * (dx / hyp));
    	 p_Position.x -=  mmx;
    	
             mmy = (int)(vel * (dy / hyp));
    	 p_Position.y -=  mmy;
          }
     }
    
      public void retarget( Point target ) {
    	p_Target = target ;
      }
    } // End of Chaser Class
    

    Data Set

        // Set up Cities
        c_Cities[0] = new City("Norman", 213,183,1);
        c_Cities[1] = new City("Ok. City", 209, 154,1) ;
        c_Cities[2] = new City("Tulsa", 352, 91,1) ;
        c_Cities[3] = new City("Enid", 177, 63,1) ;
        c_Cities[4] = new City("Lawton", 136, 246,1) ;
        c_Cities[5] = new City("Ardmore", 240, 290,1) ;
        c_Cities[6] = new City("Muskogee", 386, 128,1) ;
        c_Cities[7] = new City("I35x194", 223,62,0) ;
        c_Cities[8] = new City("I40x240", 338, 160, 0) ;
        c_Cities[9] = new City("I40x286", 405, 153, 0) ;
        c_Cities[10] = new City("I35X231", 223,0,0) ;
        c_Cities[11] = new City("Fort Smith", 466, 162, 0) ;
        c_Cities[12] = new City("Joplin", 443, 0, 1) ;
        c_Cities[13] = new City("Pauls Valley", 232, 233, 2) ;
        c_Cities[14] = new City("Chickasha", 174, 199,  2) ;
        c_Cities[15] = new City("Clinton", 82, 153, 2) ;
        c_Cities[16] = new City("Snyder", 90, 242, 3) ;
        c_Cities[17] = new City("Fairview", 127, 74, 3) ;
        c_Cities[18] = new City("McAlester", 355, 210, 1) ;
        c_Cities[19] = new City("Hugo", 383, 306, 1) ;
        c_Cities[20] = new City("Ada", 283, 226, 1) ;
        c_Cities[21] = new City("Shawnee", 258, 170, 1) ;
        c_Cities[22] = new City("Chandler", 260, 132, 2) ;
        c_Cities[23] = new City("Asher", 260, 205, 0) ;
        c_Cities[24] = new City("Purcell", 222, 205, 0) ;
        c_Cities[25] = new City("El Reno", 172, 152, 2) ;
        c_Cities[26] = new City("Seiling", 90, 87, 0) ;
        c_Cities[27] = new City("Guthrie", 216, 115, 0) ;
        c_Cities[28] = new City("Kingfisher", 173, 117, 0) ;
        c_Cities[29] = new City("Stillwater", 245,  91, 1) ;
        c_Cities[30] = new City("Antlers", 371, 283, 0) ;
        c_Cities[31] = new City("Talihina", 420, 230, 0) ;
        c_Cities[32] = new City("Wewoka", 295, 190, 0) ;
        c_Cities[33] = new City("Watonga", 132, 119, 0) ;
        c_Cities[34] = new City("Hobart",  76, 203, 0) ;
        c_Cities[35] = new City("Durant", 308, 310, 0) ;
        c_Cities[36] = new City("Atoka", 329, 268, 0) ;    
        c_Cities[37] = new City("I40x82", 110, 151, 0) ;
        c_Cities[38] = new City("I40x101", 139, 150, 0) ;    
        c_Cities[39] = new City("Putnam",  87, 117, 0) ;    
        
        // assume we start in Ardmore
        TargetNodeID = 5 ;
        car = new Chaser(c_Cities[TargetNodeID].p_Location.x, 
    		     c_Cities[TargetNodeID].p_Location.y); 
    
    
    
        // Set up  Roads and DriveTimes
        for ( i= 0; i < MAXCITY; i++) {
    	for ( int j=0; j< MAXCITY; j++) drivetime[i][j] = 1.0E9 ;
        }
        initRoadSeg(0,0,1,2);	// OKC to Norman
        initRoadSeg(1,1,22,3) ;	// OKC to Chandler
        initRoadSeg(2,4,14,3) ;	// Lawton to Chickasha
        initRoadSeg(3,13,24,2) ;	// Pauls to purcell
        initRoadSeg(4,3,7,3) ;	// Enid to I35X194
        initRoadSeg(5,1,27,2) ;	// Guthrie to OKC
        initRoadSeg(6,7,10,2) ;	// I-35 Exit 194 to StateLine
        initRoadSeg(7,1,21,2) ;	// OKC to Shawnee
        initRoadSeg(8,8,2,3) ;	// I40X240 to Tulsa
        initRoadSeg(9,7,29,3) ;	// I35X194 to stillwater
        initRoadSeg(10,8,9,2) ;	// I40X240 to I40X286
        initRoadSeg(11,9,11,2) ;	// I40X286 to I40X330
        initRoadSeg(12,9,6,3) ;	// I40X286 to Muskegee
        initRoadSeg(13,6,2,3) ;	// Muskegee to Tulsa
        initRoadSeg(14,2,12,2) ;	// Tulsa to Missouri
        initRoadSeg(15,5,13,2) ;	// Ardmore to Pauls
        initRoadSeg(16,14,1,2) ;	// Chic to OKC
        initRoadSeg(17,14,0,4) ;	// chic to norman
        initRoadSeg(18,25,1,2) ;	// elreno to okc
        initRoadSeg(19,4,16,4) ;	// lawton to snyder
        initRoadSeg(20,15,34,3) ;	// clinton to snyder
        initRoadSeg(21,15,39,3) ;	// clinton to putnam
        initRoadSeg(22,17,3,3) ;	// fv to enid
        initRoadSeg(23,8,18,3) ;	// i40x240 to macalester
        initRoadSeg(24,18,30,3) ;	// macalester to antlers
        initRoadSeg(25,5,35,4) ;	// ardmore to hugo
        initRoadSeg(26,30,31,4) ;	// antlers to fort talihina
        initRoadSeg(27,20,36,4) ;	// ada to atoka
        initRoadSeg(28,0,23,4) ;	// norman to asher
        initRoadSeg(29,5,20,4) ;	// ardmore to ada
        initRoadSeg(30,0,24,2) ;	// norman to purcell
        initRoadSeg(31,22,2,3) ;	// chandler to tulsa
        initRoadSeg(32,21,22,4) ;	// Shawnee to chandler
        initRoadSeg(33,21,8,2) ;	// shawnee to i40x240
        initRoadSeg(34,21,23,3) ;	// shawnee to asher
        initRoadSeg(35,23,20,3) ;	// asher to ada
        initRoadSeg(36,20,18,4) ;	// ada to macalester
        initRoadSeg(37,20,13,4) ;	// ada to pauls
        initRoadSeg(38,24,23,3) ;	// purcell to asher
        initRoadSeg(39,14,13,6) ;	// chickasha to pauls
        initRoadSeg(40,25,3,3) ;	// elreno to enid
        initRoadSeg(41,15,37,2) ;	// clinton to i40x82 
        initRoadSeg(42,33,26,3) ;	// watonga to seiling
        initRoadSeg(43,26,17,3) ;	// seiling to fairview
        initRoadSeg(44,21,32,6) ;	// shawnee to wewoka
        initRoadSeg(45,0,21,3) ;	// norman to shawnee
        initRoadSeg(47,27,2,3) ;	// guthrie to tulsa
        initRoadSeg(46,7,27,2) ;	// Guthrie to I35X194
        initRoadSeg(48,28,27,3) ;	// kingfisher to guthrie
        initRoadSeg(49,29,2,2) ;	// stillwater to tulsa
        initRoadSeg(50,27,29,4) ;	// guthrie to stillwater
        initRoadSeg(51,30,19,3) ;	// antlers to hugo
        initRoadSeg(52,18,31,6) ;	// macalester  to talihina
        initRoadSeg(53,31,11,4) ;	// talihina to fort smith
        initRoadSeg(54,32,18,6) ;	// wewoka to macalester
        initRoadSeg(55,26,33,4) ;	// watonga to seiling
        initRoadSeg(56,33,28,4) ;	// watonga to kingfisher
        initRoadSeg(57,34,16,4) ;	// hobart to snyder
        initRoadSeg(58,35,19,4) ;	// durant to hugo
        initRoadSeg(59,36,30,4) ;	// atoka to antlers
        initRoadSeg(60,35,36,4) ;	// durant to atoka
        initRoadSeg(61,36,18,4) ;	// atoka to macalester
        initRoadSeg(62,34,14,4) ;	// hobart to chickasha
        initRoadSeg(63,37,38,2) ;	// i40x82 to i40x101
        initRoadSeg(64,38,25,2) ;	// i40x101 to elreno
        initRoadSeg(65,39,26,3) ;   // putnam to seiling
    
    

    Tagger
    Back To Jim's Storm Page
    Back To Jim's Home Page
    Send E-mail