WHISTLE TUTORIAL
Part 1: Getting Started
  • System Requirements
  • Scripting with Whistle
  • Java Test Programs

System Requirements

Insert material ...


Scripting with Whistle

Insert material ...


Java Test Programs

Insert material ...

Part 2: Whistle Scripting Language
  • Basic Data Types and Physical Units
  • Builtin Constants and Math Functions
  • Physical Quantity Arithmetic
  • Relational and Logical Expressions
  • Branching and Looping Constructs
  • Arrays and Matrices


Basic Data Types and Physical Units

Physical quantities are modeled as a double precision value with units. A compact way of modeling physical units is with an exponent model:

Here, k is a scale (or conversion) factor and L, M, t, and T and length, mass, time and temperature, respectively. Radians (rad) is an alternate unit for measurement of angles.

The following table shows:

how base units can be combined into derived units for various types of quantity found in the physical domain (e.g., pressure, density, force).


Builtin Constants and Math Functions

Whistle comes with a small number of builtin constants:

   CONSTANT          VALUE 
   ===============================================================

   PI                3.14159265358979323846
   E                 2.71828182845904523536
   GAMMA             0.57721566490153286060

and math functions:

   FUNCTION          DESCRIPTION
   =====================================================================

   Cos (x)           Compute cosine of argument x.
   Sin (x)           Compute sine of argument x.
   Tan (x)           Compute tangent of argument x.

   Sqrt(x)           Return square root of quatity x.
   Abs(x)            Return absolute value of quatity x.
   Random(x)         Return random number uniformly distributed on [0,1].

   Max(x,y)          Return maximum value of x and/or y .
   Min(x,y)          Return minumum value of x and/or y .

The default units for angles is radians.


Example 1: The script of code:

   // Part 1: print builtin constants ...

   print "--- print builtin constants ...";

   print "--- PI    = ", PI;
   print "--- E     = ",  E;
   print "--- GAMMA = ", GAMMA;

   // Part 2: angles are expressed in radians ...

   print "--- evaluate trigonometric expression (angles in radians) ...";

   angle = PI/4;
   s = Sin (angle); c = Cos (angle);
   print "--- sin (PI/4) = ", s;
   print "--- cos (PI/4) = ", c;
   print "--- cos (PI/4)^2 + sin(PI/4)^2 = ", c^2 + s^2;

   // Part 3: angles are in degrees ...

   print "--- evaluate trigonometric expression (angles in degrees) ...";

   angle = 30 deg;
   s = Sin (angle); c = Cos (angle);
   print "--- sin (30 deg) = ", s;
   print "--- cos (30 deg) = ", c;
   print "--- cos (30 deg)^2 + sin(30 deg)^2 = ", c^2 + s^2;

generates the output:

   --- print builtin constants ...
    
   --- PI    = [ 3.142, constant]
   --- E     = [ 2.718, constant]
   --- GAMMA = [ 0.5772, constant]
    
   --- evaluate trigonometric expression (angles in radians) ...
    
   --- sin (PI/4) = [ 0.7071, null]
   --- cos (PI/4) = [ 0.7071, null]
   --- cos (PI/4)^2 + sin(PI/4)^2 = [ 1.000, ]

   --- evaluate trigonometric expression (angles in degrees) ...
    
   --- sin (30 deg) = [ 0.5000, null]
   --- cos (30 deg) = [ 0.8660, null]
   --- cos (30 deg)^2 + sin(30 deg)^2 = [ 1.000, ]

Here we compute the sine and cosine for 45 and 30 degrees, respectively, and verify that sine squared plus cos squared equals 1.


Example 2: The following script:

   print "--- Compute: Abs ( -2 cm ) --> ", Abs ( -2 cm );
   print "--- Compute: Random() --> ", Random();
   print "--- Compute: Random() --> ", Random();
   print "--- Compute: Random() --> ", Random();
   print "";
   print "--- Min ( 2 cm,   3 cm ) --> ", Min ( 2 cm,  3 cm );
   print "--- Max ( 2 cm,   3 cm ) --> ", Max ( 2 cm,  3 cm );
   print "--- Max ( 2 cm,  10 mm ) --> ", Max ( 2 cm, 10 mm );
   print "--- Max ( 2 cm, 3 cm )^2 --> ", Max ( 2 cm, 3 cm )^2;
   print "--- Min ( 2 cm, 3 cm )^2 --> ", Min ( 2 cm, 3 cm )^2;
   print "--- Min ( 2 cm, 30 mm )^Min(2,3)^2 --> ", Min ( 2 cm, 30 mm )^Min(2,3)^2;

exercises the builtin functions Abs(), Random(), Max() and Min(). It generates the output:

   --- Compute: Abs ( -2 cm ) --> [ 0.02000, m]
   --- Compute: Random() --> [ 0.5540, null]
   --- Compute: Random() --> [ 0.9988, null]
   --- Compute: Random() --> [ 0.2102, null]

   --- Min ( 2 cm,   3 cm ) --> [ 2.000, cm]
   --- Max ( 2 cm,   3 cm ) --> [ 3.000, cm]
   --- Max ( 2 cm,  10 mm ) --> [ 2.000, cm]
   --- Max ( 2 cm, 3 cm )^2 --> [ 9.000, cm^2]
   --- Min ( 2 cm, 3 cm )^2 --> [ 4.000, cm^2]
   --- Min ( 2 cm, 30 mm )^Min(2,3)^2 --> [ 16.00, cm^4]

The last example evaluates to: (2 cm)^2^2 --> (2 cm)^4 --> 16 cm^4.


Physical Quantity Arithmetic

Physical quantity arithmetic is defined for sets of units that are compatible.

Example 3. Physical quantity arithmetic is defined when the participating entities have units that are compatible -- they need not be from the same (e.g., SI and US) units system.

For example, the script:

   x = 1 m; y = 3 ft;
   print "--- x = ", x, " y = ", y;

   print "--- z = y + x --> ", y + x;
   print "--- z = y * x --> ", y * x;
   print "--- z = y / x --> ", y / x;
   print "--- Max ( x, y ) --> ", Max( x, y);
   print "--- Min ( x, y ) --> ", Min( x, y);
   print "--- Max(x,y) - Min(x,y)         --> ", Max(x,y) - Min(x,y);
   print "--- Max(x^2,y^2) - Min(x^2,y^2) --> ", Max(x^2,y^2) - Min(x^2,y^2);

generates the output:

   --- x = [ 1.000, m] y = [ 3.000, ft]

   --- z = y + x --> [ 6.281, ft]
   --- z = y * x --> [ 1417, in^2]
   --- z = y / x --> [ 0.9144, null]
   --- Max ( x, y ) --> [ 1.000, m]
   --- Min ( x, y ) --> [ 0.9144, m]
   --- Max(x,y) - Min(x,y)         --> [ 0.08560, m]
   --- Max(x^2,y^2) - Min(x^2,y^2) --> [ 0.1639, m^2]


Example 4. The script of code:

   width  =  10.0 cm;
   height =  20.0 cm;
   radius =      1 m;

   perimeter01 = 2.0*(width + height); // perimeter ..
   area01      = width * height;       // area ...

   print "Rectangle: width  = ", width;
   print "         : height = ", height;
   print "         : perimeter = ", perimeter01;
   print "         : area      = ", area01;

   perimeter02 = 2*PI*radius;          // perimeter ..
   area02      = PI*radius*radius;     // area ...

   print "";
   print "Circle: radius = ", radius;
   print "      : perimeter = ", perimeter02;
   print "      : area      = ", area02;

computes and prints the properties of one rectangle and one circle. The output is simply:

   Rectangle: width  = [ 10.00, cm]
            : height = [ 20.00, cm]
            : perimeter = [ 0.6000, m]
            : area      = [ 0.02000, m^2]
   
   Circle: radius = [ 1.000, m]
         : perimeter = [ 6.283, m]
         : area      = [ 3.142, m^2]


Example 5. The script of code:

   mass     = 1 kg;
   distance = 10 m;
   dt       = 2 sec;

   print "--- Required force = ", 2.0*mass*distance/dt^2;

computes the force needed to move 1 kg over a distance of 10 m in 2 seconds. The output is simply:

   --- Required force = [ 5.000, N]


Example 6. Now let's turn to fluid mechanics. The script of code:

   // Hydrostatic pressure calculation ...

   h   = 1 m;                // water depth ....
   g   = 9.81 m/sec^2;       // acceleration due to gravity ...
   rho = 1000.0 kg/m^3;      // density of water ...
   pressure = rho * g * h;   // water pressure at depth h = 1 m.

   print "--- Water depth (h)  = ", h;
   print "--- Gravity (g)      = ", g;
   print "--- Density of water = ", rho;
   print "--- Water pressure   = ", pressure;

   // Flow-rate computation ...

   area     = 0.04 m^2;      // Cross section area of a pipe ...
   velocity = 5 m/sec;       // Fluid velocity ....
   Q        = area*velocity; // Discharge rate ...

   print "--- Cross section area = ", area;
   print "--- Fluid velocity     = ", velocity;
   print "--- Discharge rate     = ", Q;

shows pressure and flow-rate computations from fluid mechanics. The output is:

   --- Water depth (h)  = [ 1.000, m]
   --- Gravity (g)      = [ 9.810, m/sec^2]
   --- Density of water = [ 1000, kg/m^3]
   --- Water pressure   = [ 9810, Pa]
   --- Cross section area = [ 0.04000, m^2]
   --- Fluid velocity     = [ 5.000, m/sec]
   --- Discharge rate     = [ 0.2000, m^3/sec]


Example 7. The script of code:

   // Define problem parameters ...

   carMass       = 1000 kg;                  // mass of car ...
   velocity      = 25 m/sec;                 // car velocity ....
   brakingForce  = 1500 kg*m/sec/sec;        // car braking force ...
   kineticEnergy = (1/2)*carMass*velocity^2; // compute kinetic energy.

   // Use Energy-balance calculation to compute car stopping distance.
   // Note: Kinetic energy == work done by braking force....

   brakingDistance = kineticEnergy / brakingForce;

   print "--- Car mass         = ", carMass;
   print "--- Velocity         = ", velocity;
   print "--- Kinetic energy   = ", kineticEnergy;
   print "--- Braking force    = ", brakingForce;
   print "--- Braking distance = ", brakingDistance;

The output is:

   --- Car mass         = [ 1000, kg]
   --- Velocity         = [ 25.00, m/sec]
   --- Kinetic energy   = [ 3.125e+05, Jou]
   --- Braking force    = [ 1500, N]
   --- Braking distance = [ 208.3, m]


Relational and Logical Expressions

Relational and logical expressions are evaluated as follows:

Example 8. The script of code:

   // Test relational expressions ...

   x01 = 2 cm > 3 cm;
   print "--- Compute: 2 cm > 3 cm --> ", x01;

   x01 = 5 cm > 3 cm;
   print "--- Compute: 5 cm > 3 cm --> ", x01;

   x01 = 20 mm > 3 cm;
   print "--- Compute: 20 mm > 3 cm --> ", x01;

   x01 = 20 mm/sec > 3 cm/sec;
   print "--- Compute: 20 mm/sec > 3 cm/sec --> ", x01;

   x01 = 20 mm/sec <= 3 cm/sec;
   print "--- Compute: 20 mm/sec <= 3 cm/sec --> ", x01;

   x01 = 20 mm/sec >= 3 cm/sec/sec;
   print "--- Compute: 20 mm/sec >= 3 cm/sec/sec --> ", x01;

   x01 = 20 mm/sec == 3 cm/sec;
   print "--- Compute: 20 mm/sec == 3 cm/sec --> ", x01;

   x01 = 30 mm/sec == 3 cm/sec;
   print "--- Compute: 30 mm/sec == 3 cm/sec --> ", x01;

   x01 = 20 mm/sec != 3 cm/sec;
   print "--- Compute: 20 mm/sec != 3 cm/sec --> ", x01;

   // Relational and logical expressions together ...

   y01 = 2 cm > 3 cm || 3 cm > 2 cm;
   print "--- Compute: 2 cm > 3 cm || 3 cm > 2 cm --> ", y01;

   y01 = 2 cm > 3 cm && 3 cm > 2 cm;
   print "--- Compute: 2 cm > 3 cm && 3 cm > 2 cm --> ", y01;

exercises relational and logical expressions. The output is:

   --- Compute: 2 cm > 3 cm --> false
   --- Compute: 5 cm > 3 cm --> true
   --- Compute: 20 mm > 3 cm --> false
   --- Compute: 20 mm/sec > 3 cm/sec --> false
   --- Compute: 20 mm/sec <= 3 cm/sec --> true
   *** In src/whistle/Quantity.ge() ....
   *** ERROR: Units are incompatible ....
   --- Compute: 20 mm/sec >= 3 cm/sec/sec --> false
   --- Compute: 20 mm/sec == 3 cm/sec --> false
   --- Compute: 30 mm/sec == 3 cm/sec --> true
   --- Compute: 20 mm/sec != 3 cm/sec --> true

   --- Compute: 2 cm > 3 cm || 3 cm > 2 cm --> true
   --- Compute: 2 cm > 3 cm && 3 cm > 2 cm --> false


Branching and Looping Constructs

Whistle supports while() and for() looping constructs adapted to work with arithmetic, relational and logical expressions involving physical quantities.


Example 9. The script of code:

   x = 0 cm;
   while ( x <= 12 cm ) {
      print "--- x = ", x;
      if ( x <= 5 cm ) {
         x = x + 1 cm;
      } else {
         x = x + 2 cm;
      }
   }

walks from x = 0 cm to 6 cm in increments of 1 cm, and then from x = 6 cm to 12 cm in increments of 2 cm. The output is:

   --- x = [ 0.000, cm]
   --- x = [ 1.000, cm]
   --- x = [ 2.000, cm]
   --- x = [ 3.000, cm]
   --- x = [ 4.000, cm]
   --- x = [ 5.000, cm]
   --- x = [ 6.000, cm]
   --- x = [ 8.000, cm]
   --- x = [ 10.00, cm]
   --- x = [ 12.00, cm]


Example 10. Looping constructs may be nested. This example:

   x = 0 cm;
   while ( x <= 4 cm ) {
      y = 0 cm;
      while ( y <= 4 cm ) {
         print "--- Coordinate: x = ", x, " y = ", y;
         y = y + 1 cm;
      }
      x = x + 1 cm;
   }

generates the output:

   ---
   --- Coordinate: x = [ 0.000, cm] y = [ 0.000, cm]
   --- Coordinate: x = [ 0.000, cm] y = [ 1.000, cm]
   --- Coordinate: x = [ 0.000, cm] y = [ 2.000, cm]
   --- Coordinate: x = [ 0.000, cm] y = [ 3.000, cm]
   --- Coordinate: x = [ 0.000, cm] y = [ 4.000, cm]
   --- Coordinate: x = [ 1.000, cm] y = [ 0.000, cm]
   --- Coordinate: x = [ 1.000, cm] y = [ 1.000, cm]
   --- Coordinate: x = [ 1.000, cm] y = [ 2.000, cm]

   ... lines of output removed ...

   --- Coordinate: x = [ 4.000, cm] y = [ 3.000, cm]
   --- Coordinate: x = [ 4.000, cm] y = [ 4.000, cm]


Arrays and Matrices

Whistle has two builtin methods for the specification of two dimensional matrices, with and without units, and when well defined, computation of matrix arithmetic and solution of Ax=b. The methods are:

   FUNCTION          DESCRIPTION
   =====================================================================

   Matrix(m,n)       Create an empty m by n matrix of quantities.
   Solve(A,b)        Solve Ax=b and return the solution vector x.


Example 11. This example demonstrates two ways to define a small matrix and then compute their sum. The input:

   // Define matrix x element by element ....

   x = [ 2 cm, 0 cm,   0 cm;
         0 cm, 2 cm,   0 cm;
         0 cm, 0 cm,   2 cm ];

   // Use builtin Matrix() function, then add elements ...

   y = Matrix([3,3]);
   y[0][0] = 1 cm; y[0][1] = 2 cm; y[0][2] = 0 cm;
   y[1][0] = 2 cm; y[1][1] = 2 cm; y[1][2] = 2 cm;
   y[2][0] = 0 cm; y[2][1] = 2 cm; y[2][2] = 2 cm;

   // Print matrices x and y ...

   print x, y;

   // Compute and print matrix sum ...

   z = x + y;
   print z;

The output is:

   Matrix: x
   row/col                   1            2            3
           units            cm           cm           cm
      1            2.00000e+00  0.00000e+00  0.00000e+00
      2            0.00000e+00  2.00000e+00  0.00000e+00
      3            0.00000e+00  0.00000e+00  2.00000e+00
   
   Matrix: y
   row/col                   1            2            3
           units            cm           cm           cm
      1            1.00000e+00  2.00000e+00  0.00000e+00
      2            2.00000e+00  2.00000e+00  2.00000e+00
      3            0.00000e+00  2.00000e+00  2.00000e+00
   
   // Compute and print matrix sum ...

   Matrix: z
   row/col                   1            2            3
           units            cm           cm           cm
      1            3.00000e+00  2.00000e+00  0.00000e+00
      2            2.00000e+00  4.00000e+00  2.00000e+00
      3            0.00000e+00  2.00000e+00  4.00000e+00


Example 12. Demonstrate a simple work calculation W = F*d:

   // Define force and displacement vectors ...

   Force    = [ 2 N, 3 N, 4 N ];
   Distance = [ 1 m; 2 m; 3 m ];

   // Compute work done ...

   Work = Force*Distance;

   // Print force, displacement and work matrices ....

   print Force, Distance, Work;

The output is:

   Matrix: Force
   row/col                   1            2            3
           units             N            N            N
      1            2.00000e+00  3.00000e+00  4.00000e+00
     
   Matrix: Distance
   row/col                   1
           units             m
      1            1.00000e+00
      2            2.00000e+00
      3            3.00000e+00
   
   Matrix: Work
   row/col                   1
           units           Jou
      1            2.00000e+01

Notice that Force is a (1 by 3) matrix and Displacement is a (3 by 1) vector. Hence, the matrix product Force*Displacement is defined.


Example 13. Demonstrate solution of matrix equations Ax = b.

   // Define and print (5x5) matrix A and (5x1) matrix b.

   A = [ 2, -1,  0,  0,  0;
        -1,  4, -2,  0,  0;
         0, -2,  4, -2,  0;
         0,  0, -2,  4, -2;
         0,  0,  0, -2,  4 ];

   b = [  1;  1; 0; 0; 2 ];

   print A, b;

   // Compute and print solution to linear matrix equations ...

   y = Solve(A,b);
   print y;

The output is:

   Matrix: A
   row/col                   1            2            3            4            5
           units
      1            2.00000e+00 -1.00000e+00  0.00000e+00  0.00000e+00  0.00000e+00
      2           -1.00000e+00  4.00000e+00 -2.00000e+00  0.00000e+00  0.00000e+00
      3            0.00000e+00 -2.00000e+00  4.00000e+00 -2.00000e+00  0.00000e+00
      4            0.00000e+00  0.00000e+00 -2.00000e+00  4.00000e+00 -2.00000e+00
      5            0.00000e+00  0.00000e+00  0.00000e+00 -2.00000e+00  4.00000e+00
   
   Matrix: b
   row/col                   1
           units
      1            1.00000e+00
      2            1.00000e+00
      3            0.00000e+00
      4            0.00000e+00
      5            2.00000e+00
  
   Matrix: y
   row/col                   1
           units
      1            1.00000e+00
      2            1.00000e+00
      3            1.00000e+00
      4            1.00000e+00
      5            1.00000e+00
Part 3: Extending Whistle
  • Importing Java classes into Whistle.
  • Working with Java Objects


Importing Java classes into Whistle

Whistle has a dynamic loader for dynamically linking the basic scripting engine capabilities to Java classes.

The syntax for importing a class is simply:

    import full-name-of-class;

Once a class has been imported, it can be used called via its short name.


Example 14. Let Circle() and ColoredCircle() be two Java classes organised into a simple hierarchy:

The former defines the (x,y) coordinates and radius for a circle. The latter is an extension of Circle() and adds details for the color and a boolean for whether or not the circle will be filled.

The script:

// =======================================================================
// Demonstrate:
//
//  1. Loading of classes into Whistle, and
//  2. listing methods available to the scripting language.
//
// Note: The class ColoredCircle() is an extension of Circle().
//
// Written by: Mark Austin                                       May 2019
// ======================================================================

import experiments.circle.Circle;
import experiments.circle.ColoredCircle;

program ("JavaFX demo") { 

   print "--- ========================================= ";
   print "--- Part 01: Print methods in Circle() ... ";
   print "--- ========================================= ";

   jfx = Circle();
   jfx.printMethods();

   print "--- ================================================ ";
   print "--- Part 02: Print methods in ColoredCircle() ... ";
   print "--- ================================================ ";

   jfy = ColoredCircle();
   jfy.printMethods();

   print "--- ================================================ ";
   print "--- Finished !!";
}

loads Java classes experiments.circle.Circle and experiments.circle.ColoredCircle into Whistle, creates objects for each type (i.e., using the short names Circle and ColoredCircle), and then prints the methods available to each class.

The abbreviated output is:

   --- Class diagnostics: experiments.circle.Circle ... 
   --- =======================================================
   
   Method name        = print
   Method return type = void
   Parameters: 

   Method name        = setY
   Method return type = void
   Parameters: whistle.Quantity

   Method name        = getArea
   Method return type = whistle.Quantity
   Parameters: 

   Method name        = setX
   Method return type = void
   Parameters: whistle.Quantity

   Method name        = setRadius
   Method return type = void
   Parameters: whistle.Quantity

   Method name        = getPerimeter
   Method return type = whistle.Quantity
   Parameters: 
 
   Method name        = getClass
   Method return type = java.lang.Class
   Parameters: 
  
   --- Class diagnostics: experiments.circle.ColoredCircle ... 
   --- =======================================================
   
   Method name        = setDashedFill
   Method return type = void
   Parameters: java.lang.Boolean

   Method name        = getColors
   Method return type = java.lang.String
   Parameters: 

   ... seven classes inherited from Circle are removed ... 

   --- ============================================================
   --- Finished !!

For brevity, only method name that begin with set (or get), add (or remove) and print are listed.

The main point to note is that most of the methods available to ColoredCircle are inhereted from Circle.


Working with Java Objects

Java classes interface with Whistle by:

  • Receiving data as objects of type Quantity (not doubles or ints), and
  • Returning objects of type Quantity (not doubles or ints).


Example 15. Now that Java classes Circle and ColoredCircle are loaded into Whistle, let's initialize their values and then exercies their methods.

The script:

// =======================================================================
// Demonstrate method calls to objects. The class ColoredCircle() is an
// extension of Circle().
//
// Written by: Mark Austin                                       May 2019
// ======================================================================

import experiments.circle.Circle;
import experiments.circle.ColoredCircle;

program ("Circle and ColoredCircle demo") {

   print "--- Part 01: Exercise methods in Circle() ... ";
   print "--- ================================================ ";

   jfx = Circle();
   jfx.setX( 2.0 );
   jfx.setY( 2.0 );
   jfx.setRadius( 5.0 );

   // Print circle ...

   jfx.print();

   // Compute and print circle area ...

   print "--- Circle Area = ", jfx.getArea();

   print "--- Part 02: Exercise methods in ColoredCircle() ... ";
   print "--- ================================================ ";

   jfy = ColoredCircle();
   jfy.setX( 4.0 );
   jfy.setY( 4.0 );
   jfy.setRadius( 6.0 );
   jfy.setDashedFill( true );

   // Print colored circle ...

   jfy.print();

   // Compute and print area of colored circle ...

   print "--- Colored Circle Area = ", jfy.getArea();

   print "--- ================================================ ";
   print "--- Finished !!";
}

generates:

   --- Part 01: Exercise methods in Circle() ... 
   --- ================================================ 
  
   Circle: (x,y) = (2.0,2.0): Radius = 5.0
   --- Circle Area = [ 78.54, null]
   
   --- Part 02: Exercise methods in ColoredCircle() ... 
   --- ================================================ 

   ColoredCircle: (x,y) = (4.0,4.0): Radius = 6.0
   --- Colored Circle Area = [ 113.1, null]

   --- ================================================ 
   --- Finished !!

The full details of Circle.java and ColoredCircle.java can be found in the Whistle source code.


Catalogue of Utility Packages

Here is a list of utility packages available to Whistle, along with a high-level description of their purpose and examples for importing the package:

  • Basic Data Model

    The basic data model stores lists of curves of data points. These data points can be imported into PtPlot() and LineChart().

       import whistle.util.data.DataModel;
       import whistle.util.data.Curve;
    

  • Collections Framework

    The collections framework in Whistle is a wrapper for a subset of collections available in Java Collections.

       import whistle.util.collections.ArrayList;
       import whistle.util.collections.HashSet;
    

  • JavaFX Chart Components

    JavaFX comes includes a library of components for creating line charts, scatter charts, area charts, bar charts and pie charts. Whistle provides classes that interface with JavaFX charts.

       import whistle.gui.chart.LineChart;
       import whistle.gui.chart.ScatterChart;
       import whistle.gui.chart.AreaChart;
       import whistle.gui.chart.BarChart;
       import whistle.gui.chart.PieChart;
    

  • Java Topology Suite

    The Java Topology Suite (JTS) ...

       // Basic JTS shapes ...
    
       import whistle.util.jts.Point;
       import whistle.util.jts.LineString;
       import whistle.util.jts.LinearRing;
       import whistle.util.jts.Polygon;
    
       // MultiPoint and MultiPolygon shapes ...
    
       import whistle.util.jts.MultiPoint;
       import whistle.util.jts.MultiPolygon;
    

  • Apache Jena

    Apache Jena is an open source Java framework for semantic modeling with ontologies and rules.

    The Jena framework in Whistle is a wrapper for methods (a subset of methods) available in Apache Jena.

       import whistle.util.jena.model.JenaSemanticModel;
    

  • OpenStreetMap

    OpenStreetMap ....

       import whistle.util.osm.model.OpenStreetMapModel;
    

  • Ptolemy PtPlot

    PtPlot is a tool for 2D data plotting tool. It was developed at UC Berkeley as part of the Ptolemy project, a study on modeling and design of embedded systems.

       import whistle.gui.ptolemy.PtPlot;
    

  • System Data Model

    The system data model (SDM) classes are organized into model and visitor packages.

       import whistle.util.system.model.SystemDataModel;
       import whistle.util.system.model.Component;
       import whistle.util.system.model.Behavior;
    

    and visitor packages:

       import whistle.util.system.visitor.SystemDataModelElementVisitor;
       import whistle.util.system.visitor.SystemDataModelComponentVisitor;
       import whistle.util.system.visitor.BehaviorStatechartVisitor;
    

    The system data model also hosts visitors from a number of domains:

       import whistle.util.system.visitor.SystemDataModelBuildingOntologyVisitor;
       import whistle.util.system.visitor.SystemDataModelCommunicationOntologyVisitor;
       import whistle.util.system.visitor.SystemDataModelDroneOntologyVisitor;
       import whistle.util.system.visitor.SystemDataModelOperatorOntologyVisitor;
    


Catalogue of Application Packages

Here is a list of application packages available to Whistle, along with a high-level description of their purpose and examples for importing the package:

  • Data Model for Urban Components

    This package stores compound components for urban things:

       import whistle.application.urban.CarModel;
       import whistle.application.urban.HouseModel;
       import whistle.application.urban.TreeModel;
    

  • EPANET Simulation

    This application package supports simulation of hydraulic networks.

       import whistle.application.epanet.EpanetMVC;
    

  • Data Model for HVAC Components

    This application package stores simplified representations of HVAC-type components:

       import whistle.application.hvac.ChillerModel;
       import whistle.application.hvac.FanModel;
       import whistle.application.hvac.PipelineRegion;
       import whistle.application.hvac.PipelineModel;
       import whistle.application.hvac.SensorModel;
       import whistle.application.hvac.ControllerModel;
       import whistle.application.hvac.WallPlateModel;
    

  • Data Model for Textual Requirements

    An experimental data model for textual requirements contains the classes:

       import whistle.util.requirement.model.RequirementDataModel;
       import whistle.util.requirement.model.RequirementDataModelJenaVisitor;
    

    Support is provided for the storage of textual requirements and their transfer to a semantic model through hosting of a Jena visitor.

Part 4: Collections Framework
  • Working with ArrayLists
  • Working with HashSets

Working with ArrayLists

Arraylists store dynamically sized collections of elements. They grow dynamically as elements are added to the list, and shrink in size when elements are removed.

Whistle's arraylist is simply a wrapper to the Java package java.util.ArrayList.


Example 16. This example demonstrates use of arraylists in Whistle.

// =========================================================================
// input-collect01: Explore support for wrapped Java Collections.
//
// Written by: Mark Austin                                        April 2016
// =========================================================================

import whistle.util.collections.ArrayList;

program ("Explore Java Collections") { 

   print "--- Part 01: Build simple ArrayList() ... ";
   print "--- ============================================ ";

   array01 = ArrayList("Array 1");
   array01.add( 2.0 );
   array01.add( 3.0 );
   array01.add( 4.0 );
   array01.add( 5.0 m );
   array01.add( 6.0 m );
   array01.add( 6.0 m/sec );

   print array01.toString();

   print "--- Part 02: Retrieve and print items in ArrayList ... ";
   print "--- =========================================================== ";

   print "--- array01.get(0) = ", array01.get(0);
   print "    array01.get(1) = ", array01.get(1);
   print "    array01.get(2) = ", array01.get(2);
   print "    array01.get(3) = ", array01.get(3);
   print "    array01.get(4) = ", array01.get(4);
   print "    array01.get(5) = ", array01.get(5);

   print "--- Part 03: Generate data for an array within a loop ...";
   print "--- =========================================================== ";

   array02 = ArrayList("Array 2");
   array03 = ArrayList("Array 3");
   for ( i = 0; i <= 2*PI; i = i + 0.5 ) {
       array02.add ( i );
       array03.add ( Sin (i) );
   }

   for ( i = 0; i < array02.size(); i = i + 1.0 ) {
       print "Coordinate Pair:      i = ", array02.get(i);
       print "               : sin(i) = ", array03.get(i);
   }

   print "--- Part 04: Add/Remove items from an ArrayList ...";
   print "--- =========================================================== ";

   print "--- Part 04-1: Add items to an ArrayList ...";

   a = 3 m;
   b = 3 sec;
   c = 3 m/sec;
   d = 3 m/sec^2;
   e = 3 kg;
   f = 3 kg/m^3;
  
   array04 = ArrayList("Array 4");
   array04.add ( a );
   array04.add ( b );
   array04.add ( c );
   array04.add ( d );
   array04.add ( e );
   array04.add ( f );

   print array04.toString();

   print "--- Part 04-2: Remove 2nd and 3rd items from the ArrayList ...";

   array04.remove ( b );
   array04.remove ( c );

   print array04.toString();

   print "--- ================================================================= ";
   print "--- Finished !! ";
}

The output is:

   --- Part 01: Build simple ArrayList() ... 
   --- ============================================ 
   
   --- ArrayList(): Name = "Array 1"
   --- Size = 6 
   --- Items =
       Item 0 : [ 2.000, null]
       Item 1 : [ 3.000, null]
       Item 2 : [ 4.000, null]
       Item 3 : [ 5.000, m]
       Item 4 : [ 6.000, m]
       Item 5 : [ 6.000, m/sec]
    
   --- Part 02: Retrieve and print items in ArrayList() ... 
   --- =========================================================== 
   
   --- array01.get(0) = [ 2.000, null]
       array01.get(1) = [ 3.000, null]
       array01.get(2) = [ 4.000, null]
       array01.get(3) = [ 5.000, m]
       array01.get(4) = [ 6.000, m]
       array01.get(5) = [ 6.000, m/sec]
   
   --- Part 03: Generate data for an array within a loop ...
   --- =========================================================== 
   
   Coordinate Pair:      i = [ 0.000, null]
                  : sin(i) = [ 0.000, null]
   Coordinate Pair:      i = [ 0.5000, ]
                  : sin(i) = [ 0.4794, null]

   ... lines of output removed ...

   Coordinate Pair:      i = [ 6.000, ]
                  : sin(i) = [ -0.2794, null]

   --- Part 04: Add/Remove items from an ArrayList ...
   --- =========================================================== 
   
   --- Part 04-1: Add items to an ArrayList ...
    
   --- ArrayList(): Name = "Array 4"
   --- Size = 6 
   --- Items =  
       Item 0 : [ 3.000, m]
       Item 1 : [ 3.000, sec]
       Item 2 : [ 3.000, m/sec]
       Item 3 : [ 3.000, m/sec^2]
       Item 4 : [ 3.000, kg]
       Item 5 : [ 3.000, kg/m^3]
  
   --- Part 04-2: Remove 2nd and 3rd items from the ArrayList ...
   
   --- ArrayList(): Name = "Array 4"
   --- Size = 4 
   --- Items =  
       Item 0 : [ 3.000, m]
       Item 1 : [ 3.000, m/sec^2]
       Item 2 : [ 3.000, kg]
       Item 3 : [ 3.000, kg/m^3]
   
   --- ================================================================= 
   --- Finished !! 

Points to note:

  • Here we are simply assembling arraylists for a variety of physical quantity and component objects.
  • Notice that like Java, Whistle's arraylist actually stores a reference to an object. Thus, primitive data types cannot be stored in an array list -- use a quantity instead.


Working with HashSets

A hashset uses a hashmap to store collections of user-defined objects, but not in any particular order.

Duplicate items are prohibited.


Example 17. This example demonstrates use of HashSets in Whistle:

// =========================================================================
// input-collections02: Test support for whistle.util.collections.HashSet().
//
// Written by: Mark Austin                                        April 2016
// =========================================================================

import whistle.util.collections.HashSet;

import experiments.circle.Circle;
import experiments.circle.ColoredCircle;

program ("Explore Java Collections") { 

   print "--- Part 01: Create HashSet() of physical quantities ... ";
   print "--- ==================================================== ";
   print "---";

   x = 3.4 m/sec;
   y = 2.0;

   set01 = HashSet("HashSet 01");
   set01.add( y );
   set01.add( 3.0 );
   set01.add( 4.0 );
   set01.add( 5.0 m );
   set01.add( 6.0 m );
   set01.add( 6.0 m/sec );
   set01.add( x );

   print set01.toString();

   print "---";
   print "--- Part 02: Create set of Circle() objects ... ";
   print "--- ==================================================== ";
   print "---";

   // Create individual circles ....

   circle01 = ColoredCircle();
   circle01.setX( 2.0 );
   circle01.setY( 2.0 );
   circle01.setRadius( 5.0 );

   circle02 = ColoredCircle();
   circle02.setX( 4.0 );
   circle02.setY( 4.0 );
   circle02.setRadius( 6.0 );

   circle03 = Circle();
   circle03.setX( 1.0 );
   circle03.setY( 1.0 );
   circle03.setRadius( 10.0 );

   // Add circles to the set ....

   set02 = HashSet();
   set02.setName("HashSet 02");
   set02.add( circle01 );
   set02.add( circle02 );
   set02.add( circle03 );

   // Print contents of the hashset ...

   print set02.toString();

   print "--- ==================================================== ";
   print "--- Finished !! ... ";
}

The output is as follows:

   --- Part 01: Create HashSet() of physical quantities ... 
   --- ==================================================== 
   
   --- HashSet(): Name = "HashSet 01"
   --- Size = 7 
   --- Items =  
       Item 0 : [ 6.000, m]
       Item 1 : [ 3.400, m/sec]
       Item 2 : [ 5.000, m]
       Item 3 : [ 2.000, null]
       Item 4 : [ 4.000, null]
       Item 5 : [ 3.000, null]
       Item 6 : [ 6.000, m/sec]
   
   --- Part 02: Create set of Circle() objects ... 
   --- ==================================================== 
     
   --- HashSet(): Name = "HashSet 02"
   --- Size = 3 
   --- Items =  
       Item 0 : Circle: (x,y) = (2.0,2.0): Radius = 5.0
       Item 1 : Circle: (x,y) = (4.0,4.0): Radius = 6.0
       Item 2 : Circle: (x,y) = (1.0,1.0): Radius = 10.0
   
   --- ==================================================== 
   --- Finished !! ... 
Part 5: Software Design Patterns
  • Working with the Visitor Design Pattern
  • Working with the Composite Hierarchy Design Pattern
  • Working with the Observer Design Pattern

Working with the Visitor Design Pattern

The visitor software design pattern provides a mechanism to separate an algorithm (i.e. system functionality) from the object structure on which it operates.

The benefit of this separation is that new capabilities (or functions) can be added to the class structure without changing its original structure.


Example 18. In this example, print, price and draw visitors visit a simple car model.

This example begins with a simple car data model containing four wheels, a body, an engine, and the car component itself. Access is provided for visitors.

Now let's see how this works for print, draw and price visitors.

/*
 * ========================================================================
 * input-sdp-visitor01.txt: Simulation of visitor design pattern ....
 *
 * Written By: Mark Austin                                    January, 2017
 * ========================================================================
 *

import test.automobile.Car;
import test.automobile.CarDrawVisitor;
import test.automobile.CarPriceVisitor;
import test.automobile.CarPrintVisitor;

program ("Exercise Visitor Design Pattern") {

   print "--- ========================================";
   print "--- PART 01: Create car object ...";
   print "--- ========================================";

   car01 = Car();

   print "--- ========================================";
   print "--- PART 02: Visit car and draw elements ...";
   print "--- ========================================";

   cpv = CarPrintVisitor();
   car01.accept( cpv );

   print "--- ========================================";
   print "--- PART 03: Visit car and draw elements ...";
   print "--- ========================================";

   cdv = CarDrawVisitor();
   car01.accept( cdv );

   print "--- ========================================";
   print "--- PART 04: Visit car and compute cost ... ";
   print "--- ========================================";

   cpr = CarPriceVisitor();
   car01.accept( cpr );

   print "--- Total cost = ", cpr.getTotalPrice();

   print "--- ========================================";
   print "--- Finished !! ";
}

generates the textual output:

   --- ========================================
   --- PART 01: Create car object ...
   --- ========================================

   --- ========================================
   --- PART 02: Visit car and draw elements ...
   --- ========================================
 
   Print: visiting front left wheel
   Print: visiting front right wheel
   Print: visiting rear left wheel
   Print: visiting rear left wheel
   Print: visiting body
   Print: visiting engine
   Print: visiting car

   --- ========================================
   --- PART 03: Visit car and draw elements ...
   --- ========================================
  
   Draw a wheel: front left ...
   Draw a wheel: front right ...
   Draw a wheel: rear left ...
   Draw a wheel: rear left ...
   Draw the body ...
   Draw the engine ...
   Draw my car ...
   
   --- ========================================
   --- PART 04: Visit car and compute cost ...
   --- ========================================
  
   Price: wheel  =   1.00
   Price: wheel  =   1.00
   Price: wheel  =   1.50
   Price: wheel  =   1.50
   Price: body   =   5.00
   Price: engine =   5.00

   --- Total cost = [ 15.00, null]
   
   --- =======================================================
   --- Finished!

Points to note:

  • Each component of the car implements an CarElement interface, which in turn, defines an accept method for entities that have implemented the CarElementVisitor interface.
  • The CarElementVisitor provides visit methods for each type of object found in the car assembly, e.g., visit (Wheel item).
  • Thus, the details of print, draw and pricing are completely separated from the Car Data Model by the common interfaces that they implement.


Working with the Composite Hierarchy Design Pattern

The composite hierarchy design pattern provides a flexible way to create tree structures of arbitrary complexity, while enabling every element in the structure to operate with a uniform interface.

In a basic implementation of this pattern, the arrangement of container and component classes is as follows:

Whistle extends this idea in the following ways:

  • Instead of thinking about components and containers of components, Whistle organizes things into hierarchies of Features.

  • Simple objects (e.g., points, edges, circles and polygons) are extensions of Abstract Feature.

  • More complex things (e.g., a house or car) can be modeled as compound features:

    A compound feature simply stores a collection of features.

  • A composite hiearchy is modeled as an extension of AbstractFeature, which, in turn, boils down to collections of things that implement the Feature interface.

The revised arrangement of Java classes is as follows:

The centerpiece of this extension is the Feature interface:

public interface Feature extends Cloneable {
   public BoundingEnvelope getBoundingEnvelope();
   public void     setName( String sName );
   public String   getName();

   public void     setX( Quantity dX );
   public Quantity getX();
   public void     setY( Quantity dY );
   public Quantity getY();
   public void     setPosition( Quantity dX, Quantity dY );

   public void     setHierarchyColor ( Color color );
   public void     setColor( Color  c );
   public void     setColor( String c );
   public Color    getColor();
   public void     setFilledShape( Boolean b );
   public boolean  getFilledShape();

   public void     setSelection( Boolean b );
   public boolean  getSelection();

   public void     accept( FeatureElementVisitor visitor );
   public void     search( AffineTransform at, int dx, int dy );
}

Points to note:

  • The method getBoundingEnvelope() returns a rectangular box that bounds the feature, a feature that is useful for quickly determing if two objects do not overlap.

  • The methods setX() and setY() position the feature in a coordinate system local to its position in the composite hiearchy.

  • The methods getX() and getY() retrieve the coordinates ....

  • The method setPosition( dX, dY ) positions a feature at coordinate (dX, dY).

  • Methods are provided for setting and getting color and setting and getting selections.

  • The accept() method provides a way for visitors to traverse the composite hierarchy.

  • The search() method provides a way for things to be found.

  • The clone() method makes a copy of everything in the composite hierarchy.


Example 19. Playing with compound models and workspaces of manually assembled composite hierarchies.

// ========================================================================
// Experiment with models and composite hierarchies ....
//
// Written by: Mark Austin                                      August 2014
// ========================================================================

import whistle.gui.Viewer2D;
import whistle.model.geometry.GridModel;
import whistle.model.WorkspaceModel;

import whistle.application.urban.TreeModel;
import whistle.application.urban.CarModel;
import whistle.application.urban.HouseModel;

program ("Composite Hierarchy Simulation") { 

   // Create and print model of an Acura 2015 and Sports Car ....

   car01 = CarModel( 500 m, 50 m, 100 m );
   car01.printMethods();

   // Add data to car models, then print ...

   car01.setName( "Acura 2015" );
   car01.setColor("red");
   car01.setDisplayName( true );

   car02 = CarModel( 500 m, 350 m, 40 m );
   car02.setName( "Sports Car" );
   car02.setColor("red");
   car02.setDisplayName( true );

   print car01;
   print car02;

   // Create and print model of a house ....

   house01 = HouseModel(  50 m, 50 m, 100 m );
   house01.setName( "Mark's house" );
   house01.setColor("blue");
   house01.setDisplayName( true );

   house02 = HouseModel(  20 m, 200 m, 200 m );
   house02.setName( "Leonards's house" );
   house02.setColor("green");
   house02.setDisplayName( true );

   print house01;
   print house02;

   // Create trees ....

   tree01 = TreeModel( 100.0 m, 500 m, 10.0 m );
   tree01.setName("Big Fur");
   tree01.setColor("blue");
   tree01.setFilledShape(true);
   tree01.setDisplayName( true );
   tree01.setTextOffSetX(-20.0);
   tree01.setTextOffSetY( 25.0);

   tree02 = TreeModel( 100.0 m, 600 m, 10.0 m );
   tree02.setName("Weeping Willow");
   tree02.setColor("green");
   tree02.setFilledShape(true);
   tree02.setDisplayName( true );
   tree02.setTextOffSetX(-20.0);
   tree02.setTextOffSetY( 25.0);

   tree03 = TreeModel( 200.0 m, 700 m, 20.0 m );
   tree03.setName("Canadian Maple");
   tree03.setColor("red");
   tree03.setFilledShape( true );
   tree03.setDisplayName( true );
   tree03.setTextOffSetX(-20.0);
   tree03.setTextOffSetY( 42.0);

   print tree01;
   print tree02;
   print tree03;

   // Assemble composite hierarchy ....

   ch = WorkspaceModel (0.0, 0.0, 0.0);
   ch.setName ("Car and Trees");
   ch.add ( car01 );
   ch.add ( car02 );
   ch.add ( house01 );
   ch.add ( house02 );
   ch.add ( tree01 );
   ch.add ( tree02 );
   ch.add ( tree03 );

   ch.print();

   // Workspace layer ....

   workspacelayer = ch.getWorkspace();

   // Create GridModel layer ....

   grid = GridModel();
   grid.setRange( 0 m, 0 m, 800 m, 800 m);
   grid.setGridSize( 200 m );
   grid.buildGrid();

   gridlayer = grid.getWorkspace();
   gridlayer.setColor("green");

   // Build 2D viewer ...

   jfx = Viewer2D();
   jfx.addLayer(  "Grid", gridlayer );
   jfx.addLayer( "Stuff", workspacelayer );

   // Setup and instantiate display ....

   jfx.sleep( 1000 );
   jfx.setSize( 1000, 1000 );
   jfx.setXRange( 0, 800 );
   jfx.setYRange( 0, 800 );
   jfx.display();
   jfx.sleep( 1000 );

   print "--- ========================================== ";
   print "--- Finished !!";
}

generates the graphic:

and the (slightly edited) textual output:

   --- Begin Program: "Composite Hierarchy Simulation" ... 
   --- ==============================================================
     
   --- Class diagnostics: whistle.model.urban.CarModel ... 
   --- ============================================================

   // Methods declared in AbstractCompountFeature.java ....
     
   Method name        = setPerformance
   Method return type = void
   Parameters: double
   
   Method name        = setPrice
   Method return type = void
   Parameters: double
   
   Method name        = getPrice
   Method return type = double
   Parameters: 
    
   Method name        = getPerformance
   Method return type = double
   Parameters: 

   // Methods declared in AbstractFeature.java ....
   
   Method name        = getName
   Method return type = java.lang.String
   Parameters: 
   
   Method name        = setName
   Method return type = void
   Parameters: java.lang.String
    
   Method name        = getDisplayName
   Method return type = boolean
   Parameters: 
    
   Method name        = setX
   Method return type = void
   Parameters: double
   
   Method name        = setX
   Method return type = void
   Parameters: whistle.Quantity
   
   Method name        = setY
   Method return type = void
   Parameters: whistle.Quantity
   
   Method name        = setY
   Method return type = void
   Parameters: double
    
   Method name        = setWidth
   Method return type = void
   Parameters: double
   
   Method name        = setWidth
   Method return type = void
   Parameters: whistle.Quantity
   
   Method name        = setHeight
   Method return type = void
   Parameters: whistle.Quantity
   
   Method name        = setHeight
   Method return type = void
   Parameters: double
   
   Method name        = getX
   Method return type = whistle.Quantity
   Parameters: 
   
   Method name        = getY
   Method return type = whistle.Quantity
   Parameters: 
   
   Method name        = getFilledShape
   Method return type = boolean
   Parameters: 
   
   Method name        = getColor
   Method return type = javafx.scene.paint.Color
   Parameters: 
   
   Method name        = setFilledShape
   Method return type = void
   Parameters: java.lang.Boolean
   
   Method name        = setDisplayName
   Method return type = void
   Parameters: java.lang.Boolean
   
   Method name        = getCoordinate
   Method return type = whistle.model.Coordinate
   Parameters: 
   
   Method name        = getBoundingEnvelope
   Method return type = whistle.model.BoundingEnvelope
   Parameters: 
   
   Method name        = setPosition
   Method return type = void
   Parameters: double, double
   
   Method name        = setPosition
   Method return type = void
   Parameters: whistle.Quantity, whistle.Quantity
   
   Method name        = setPosition
   Method return type = void
   Parameters: java.lang.Double, java.lang.Double
   
   Method name        = getHeight
   Method return type = double
   Parameters: 
   
   Method name        = getWidth
   Method return type = double
   Parameters: 
   
   Method name        = getArea
   Method return type = double
   Parameters: 
   
   Method name        = setTextOffSetX
   Method return type = void
   Parameters: whistle.Quantity
   
   Method name        = setTextOffSetX
   Method return type = void
   Parameters: int
   
   Method name        = setTextOffSetY
   Method return type = void
   Parameters: whistle.Quantity
   
   Method name        = setTextOffSetY
   Method return type = void
   Parameters: int
   
   Method name        = getTextOffSetX
   Method return type = double
   Parameters: 
   
   Method name        = getTextOffSetY
   Method return type = double
   Parameters: 
   
   Method name        = setSelection
   Method return type = void
   Parameters: java.lang.Boolean
   
   Method name        = getSelection
   Method return type = boolean
   Parameters: 
   
   Method name        = getShapeActive
   Method return type = boolean
   Parameters: 
   
   Method name        = setHierarchyColor
   Method return type = void
   Parameters: javafx.scene.paint.Color
   
   Method name        = getPolygonString
   Method return type = java.lang.String
   Parameters: 
   
   Method name        = setColor
   Method return type = void
   Parameters: javafx.scene.paint.Color
  
   Method name        = setColor
   Method return type = void
   Parameters: java.lang.String

   // Methods declared above AbstractFeature.java ....
   
   Method name        = getClass
   Method return type = java.lang.Class
   Parameters: 
   
   --- ============================================================
   --- 
   
   --- Component: CarModel() ... 
   --- name = "Acura 2015"
   --- (x,y)  =  (500.000000,50.000000)
   --- height = 25.0
   --- width  = 50.0
  
   --- Component: CarModel() ... 
   --- name = "Sports Car"
   --- (x,y)  =  (500.000000,350.000000)
   --- height = 20.0
   --- width  = 40.0
   
   House Model: Name = "Mark's house"
        Location (x,y) = ([ 50.00, null],[ 50.00, null])
   House Model: Name = "Leonards's house"
        Location (x,y) = ([ 20.00, null],[ 200.0, null])
   
   Tree: Name = "Big Fur" ...
   Coordinate(x,y) = (100.000000, 500.000000)
   Tree shape filled --> true ... 
   Tree color --> [ #0000FF ] ... 
   
   Tree: Name = "Weeping Willow" ...
   Coordinate(x,y) = (100.000000, 600.000000)
   Tree shape filled --> true ... 
   Tree color --> [ #008000 ] ... 
   
   Tree: Name = "Canadian Maple" ...
   Coordinate(x,y) = (200.000000, 700.000000)
   Tree shape filled --> true ... 
   Tree color --> [ #FF0000 ] ... 

   // Print details of composite hierarchy ...
    
   Start Composite Hierarchy: null
   =================================================
   Level =  1
   -------------------------------------------------
   
   Compound Feature: "Acura 2015"
   ----------------------------------
   Local  (x,y) = ( 500.0,  50.0 )
   Global (x,y) = ( 500.0,  50.0 )
   BoundingEnv[ x = (500.0-550.0) y = ( 50.0-75.0)]
   ----------------------------------
   Point: (x,y) = (500.0,  58.3)
   Point: (x,y) = (500.0,  66.7)
   Point: (x,y) = (550.0,  66.7)
   Point: (x,y) = (550.0,  58.3)
   Edge: (x1,y1) = (    500.00,      58.33) 
       : (x2,y2) = (    500.00,      66.67) 
       : thickness =   2 
   Edge: (x1,y1) = (    500.00,      66.67) 
       : (x2,y2) = (    550.00,      66.67) 
       : thickness =   2 

   ... details of car edges and points removed ...

   Edge: (x1,y1) = (    516.67,      75.00) 
       : (x2,y2) = (    508.33,      66.67) 
       : thickness =   2 
  
   Circle( wheel01): (x,y) = ([ 512.5, null],[ 54.17, null]) radius = 4.16666667
   Circle( wheel02): (x,y) = ([ 537.5, null],[ 54.17, null]) radius = 4.16666667
   -------------------------------
   
   Compound Feature: "Sports Car"
   ----------------------------------
   Local  (x,y) = ( 500.0, 350.0 )
   Global (x,y) = ( 500.0, 350.0 )
   BoundingEnv[ x = (500.0-540.0) y = ( 350.0-370.0)]
   ----------------------------------
   Point: (x,y) = (500.0, 356.7)

   ... details of car edges and points removed ...

   Circle( wheel01): (x,y) = ([ 510.0, null],[ 353.3, null]) radius = 3.333333335
   Circle( wheel02): (x,y) = ([ 530.0, null],[ 353.3, null]) radius = 3.333333335
   -------------------------------
   
   Compound Feature: "Mark's house"
   ----------------------------------
   Local  (x,y) = (  50.0,  50.0 )
   Global (x,y) = (  50.0,  50.0 )
   BoundingEnv[ x = (50.0-150.0) y = ( 50.0-150.0)]
   ----------------------------------
   Point: (x,y) = ( 50.0,  50.0)
   Point: (x,y) = (150.0,  50.0)
   Point: (x,y) = ( 50.0, 100.0)
   Point: (x,y) = (150.0, 100.0)
   Edge: (x1,y1) = (     50.00,      50.00) 
       : (x2,y2) = (    150.00,      50.00) 
       : thickness =   2 

   ... details of house edges and points removed ...

   Point: (x,y) = (100.0, 150.0)
   -------------------------------
   
   Compound Feature: "Leonards's house"
   ----------------------------------

   ... details of house edges and points removed ...

   ----------------------------------

   Compound Feature: "Big Fur"
   ----------------------------------
   Local  (x,y) = ( 100.0, 500.0 )
   Global (x,y) = ( 100.0, 500.0 )
   BoundingEnv[ x = (90.0-110.0) y = ( 490.0-510.0)]
   ----------------------------------
   Circle( base): (x,y) = ([ 100.0, null],[ 500.0, null]) radius = 10.0
   ----------------------------------
     
   Compound Feature: "Weeping Willow"
   ----------------------------------
   Local  (x,y) = ( 100.0, 600.0 )
   Global (x,y) = ( 100.0, 600.0 )
   BoundingEnv[ x = (90.0-110.0) y = ( 590.0-610.0)]
   ----------------------------------
   Circle( base): (x,y) = ([ 100.0, null],[ 600.0, null]) radius = 10.0
   ----------------------------------
   
   Compound Feature: "Canadian Maple"
   ----------------------------------
   Local  (x,y) = ( 200.0, 700.0 )
   Global (x,y) = ( 200.0, 700.0 )
   BoundingEnv[ x = (180.0-220.0) y = ( 680.0-720.0)]
   ----------------------------------
   Circle( base): (x,y) = ([ 200.0, null],[ 700.0, null]) radius = 20.0
   -------------------------------
   
   -------------------------------------------------
   End level =  1
   =================================================
   Create Model of Workspace Grid 
   ============================== 
  
   --- =======================================================
   --- Finished!

Points to note:

  • For software prototyping purposes, Whistle has a small number of domain-specific models, e.g.,

        import whistle.application.urban.TreeModel;
        import whistle.application.urban.CarModel;
        import whistle.application.urban.HouseModel;
    

    Each model is an extension of AbstractCompoundFeature.

  • The command ch.print() is simply a wrapper for:

       accept ( new CompositePrintVisitor() );
    


Example 20. This example demonstrates systematic assembly of a simple schematic for temperature control of two rooms with HVAC equipment with a two-layer composite hierarchy, i.e.,

The model employs and a variety of workspace transformations and rotations to size and position the components in a desirable layout. The workspace axes and orientations are illustrated with dark black arrows.

The abbreviated problem specification:

// ========================================================================
// Experiment with models and composite hierarchies ....
//
// Written by: Mark Austin                                        June 2019
// ========================================================================

import whistle.gui.Viewer2D;

import whistle.model.WorkspaceModel;
import whistle.model.geometry.GridModel;

import whistle.application.hvac.ChillerModel;
import whistle.application.hvac.FanModel;
import whistle.application.hvac.PipelineRegion;
import whistle.application.hvac.PipelineModel;
import whistle.application.hvac.SensorModel;
import whistle.application.hvac.ControllerModel;
import whistle.application.hvac.WallPlateModel;

program ("Demonstrate Composite Hierarchy Assembly") { 

   // ---------------------------------------------------------
   // PART 1: HVAC Components ...
   // =========================================================

   // Controller model ...

   controller01 = ControllerModel( 300.0, 660.0, 340.0, 140.0 );
   controller01.setName( "HVAC Control" );
   controller01.setColor( "greenyellow" );
   controller01.setFilledShape(true);
   controller01.setTextOffSetX(80);
   controller01.setTextOffSetY(80);

   // Chiller model ...

   chiller01 = ChillerModel( 500, 480, 140);
   chiller01.setName( "Chiller 01" );
   chiller01.setFilledShape(true);
   chiller01.setColor("greenyellow");
   chiller01.setDisplayName( true );
   chiller01.setTextOffSetX(0);
   chiller01.setTextOffSetY(160);

   // Fan model aligned with controller model ...

   fan01 = FanModel( 760, 495, 80);
   fan01.setName( "Fan 01" );
   fan01.setColor("red");
   fan01.setFilledShape(true);
   fan01.setDisplayName(true);
   fan01.setTextOffSetX(  0);
   fan01.setTextOffSetY(-10);

   fan02 = FanModel( 0, 0, 80);
   fan02.setName( "Fan 02" );
   fan02.setColor("red");
   fan02.setFilledShape(true);
   fan02.setDisplayName(true);
   fan02.setTextOffSetX(0);
   fan02.setTextOffSetY(100);

   // Repositioned fan model rotated by PI/2 radians ...

   ws01 = WorkspaceModel ( 840.0, 100.0, PI/2.0 );
   ws01.add ( fan02 );

   // Extract composite hierarchies from work spaces ...

   chfan02 = ws01.getWorkspace();

   // ----------------------------------------------------
   // PART 2: Workspace model for Room 1 ...
   // =========================================================

   // Define geometry of centerlines for wall segments ...

   wseg01 = PipelineRegion();
   wseg01.add( 300.0, 285.0 );
   wseg01.add(  15.0, 285.0 );
   wseg01.add(  15.0,  15.0 );
   wseg01.add( 300.0,  15.0 );

   wseg02 = PipelineRegion();
   wseg02.add( 330.0,  15.0 );
   wseg02.add( 585.0,  15.0 );
   wseg02.add( 585.0, 285.0 );
   wseg02.add( 330.0, 285.0 );

   // Transform wall centerlines into fill regions ...

   width = 30;
   room01pt1 = PipelineModel("Rm01, wall01");
   room01pt1.setWidth( width );
   room01pt1.build( wseg01 );
   room01pt1.setDisplayName( false );
   room01pt1.setColor( "blue" );
   room01pt1.setFilledShape( true );

   room01pt2 = PipelineModel("Rm01, wall02");
   room01pt2.setWidth( width );
   room01pt2.build( wseg02 );
   room01pt2.setDisplayName( false );
   room01pt2.setColor( "blue" );
   room01pt2.setFilledShape( true );

   // Wallplate models ...

   wallplate01 = WallPlateModel( 300,   0,  30);
   wallplate01.setName( "WP01" );
   wallplate01.setFilledShape(true);
   wallplate01.setColor("yellow");
   wallplate01.setDisplayName( false );

   wallplate02 = WallPlateModel( 300, 270,  30);
   wallplate02.setName( "WP02" );
   wallplate02.setFilledShape(true);
   wallplate02.setColor("yellow");
   wallplate02.setDisplayName( false );

   // Sensor models ...

   sensor01 = SensorModel( 150, 30,  10);
   sensor01.setName( "S01" );
   sensor01.setFilledShape(true);
   sensor01.setColor("orange");
   sensor01.setDisplayName( false );

   ... details of sensors removed ....

   sensor02 = SensorModel( 150, 140, 10);
   sensor03 = SensorModel( 150, 260, 10);
   sensor04 = SensorModel( 450, 30,  10);
   sensor05 = SensorModel( 450, 140, 10);
   sensor06 = SensorModel( 450, 260, 10);

   // --------------------------------------------------------
   // Assemble components for Room 01 ...
   // --------------------------------------------------------

   wsroom01 = WorkspaceModel ( 100.0, 100.0, 0.0 );
   wsroom01.add ( sensor01 );
   wsroom01.add ( sensor02 );
   wsroom01.add ( sensor03 );
   wsroom01.add ( sensor04 );
   wsroom01.add ( sensor05 );
   wsroom01.add ( sensor06 );
   wsroom01.add ( wallplate01 );
   wsroom01.add ( wallplate02 );
   wsroom01.add ( room01pt1 );
   wsroom01.add ( room01pt2 );

   // wsroom01.add ( room01pt1 );

   chroom01 = wsroom01.getWorkspace();

   // ----------------------------------------------------
   // PART 3: Workspace model for Room 2 ...
   // =========================================================

   // Define geometry of centerlines for wall segments ...

   wseg03 = PipelineRegion();
   wseg03.add( 150.0, 285.0 );
   wseg03.add(  15.0, 285.0 );
   wseg03.add(  15.0,  15.0 );
   wseg03.add( 585.0,  15.0 );
   wseg03.add( 585.0, 285.0 );
   wseg03.add( 450.0, 285.0 );

   wseg04 = PipelineRegion();
   wseg04.add( 180.0, 285.0 );
   wseg04.add( 300.0, 285.0 );
   wseg04.add( 420.0, 285.0 );

   // Transform wall centerlines into fill regions ...

   width = 30;
   room02pt1 = PipelineModel("Rm01, wall01");
   room02pt1.setWidth( width );
   room02pt1.build( wseg03 );
   room02pt1.setDisplayName( false );
   room02pt1.setColor( "blue" );
   room02pt1.setFilledShape( true );

   room02pt2 = PipelineModel("Rm01, wall02");
   room02pt2.setWidth( width );
   room02pt2.build( wseg04 );
   room02pt2.setDisplayName( false );
   room02pt2.setColor( "blue" );
   room02pt2.setFilledShape( true );

   // Wallplate models ...

   wallplate03 = WallPlateModel( 150, 270,  30);
   wallplate03.setName( "WP01" );
   wallplate03.setFilledShape(true);
   wallplate03.setColor("yellow");
   wallplate03.setDisplayName( false );

   wallplate04 = WallPlateModel( 420, 270,  30);
   wallplate04.setName( "WP02" );
   wallplate04.setFilledShape(true);
   wallplate04.setColor("yellow");
   wallplate04.setDisplayName( false );

   // Sensor models ...

   sensor07 = SensorModel( 150, 140,  10);
   sensor07.setName( "S07" );
   sensor07.setFilledShape(true);
   sensor07.setColor("orange");
   sensor07.setDisplayName( false );

   ... details of sensors removed ...

   sensor08 = SensorModel( 300, 140, 10);
   sensor09 = SensorModel( 450, 140,  10);

   // --------------------------------------------------------
   // Assemble components for Room 02 ...
   // --------------------------------------------------------

   wsroom02 = WorkspaceModel ( 1200.0, 100.0, PI/2.0 );
   wsroom02.add ( sensor07 );
   wsroom02.add ( sensor08 );
   wsroom02.add ( sensor09 );
   wsroom02.add ( wallplate03 );
   wsroom02.add ( wallplate04 );
   wsroom02.add ( room02pt1 );
   wsroom02.add ( room02pt2 );

   chroom02 = wsroom02.getWorkspace();

   // ====================================================================
   // PART 4: Use pipes to connect chiller and fans to rooms ...
   // ====================================================================

   width = 2;

   // Pipe 1: From Room 1 to Chiller  ....

   pipeline01 = PipelineRegion();
   pipeline01.add( 400.0,  400.0 );
   pipeline01.add( 400.0,  550.0 );
   pipeline01.add( 500.0,  550.0 );

   pipe01 = PipelineModel("pipe01");
   pipe01.setWidth( width );
   pipe01.build( pipeline01 );
   pipe01.setDisplayName( false );
   pipe01.setColor( "grey" );
   pipe01.setFilledShape( true );

   ... details of pipe 02 through pipe 09 removed ...

   pipe10 = PipelineModel("pipe08");

   // ====================================================================
   // PART 4: Use pipes to connect chiller and fans to rooms ...
   // ====================================================================

   width = 1;

   // Wire 1: From HVAC Controller to Chiller  ....

   wireline01 = PipelineRegion();
   wireline01.add( 640.0,  720.0 );
   wireline01.add( 700.0,  720.0 );
   wireline01.add( 700.0,  580.0 );
   wireline01.add( 640.0,  580.0 );

   wire01 = PipelineModel("wire01");
   wire01.setWidth( width );
   wire01.build( wireline01 );
   wire01.setDisplayName( false );
   wire01.setColor( "black" );
   wire01.setFilledShape( true );

   ... details of wires 02 through wire05 removed ...

   // ----------------------------------------------------
   // PART 4: Assemble scene for HVAC workspace ...
   // ----------------------------------------------------

   hvac01 = WorkspaceModel ( 0.0, 0.0, 0.0 );
   hvac01.add ( pipe01 ); hvac01.add ( pipe02 );
   hvac01.add ( pipe03 ); hvac01.add ( pipe04 );
   hvac01.add ( pipe05 ); hvac01.add ( pipe06 );
   hvac01.add ( pipe07 ); hvac01.add ( pipe08 );
   hvac01.add ( pipe09 ); hvac01.add ( pipe10 );

   hvac01.add ( wire01 ); hvac01.add ( wire02 );
   hvac01.add ( wire03 ); hvac01.add ( wire04 );
   hvac01.add ( wire05 );

   hvac01.add ( chroom01 );
   hvac01.add ( chroom02 );
   hvac01.add ( chiller01 );
   hvac01.add ( chfan02 );
   hvac01.add ( fan01 );
   hvac01.add ( controller01 );

   print "---";
   print "--- ===============================================";
   print "--- PRINT COMPOSITE HIERARCHY FOR HVAC WORKSPACE !!";
   print "--- ===============================================";
   print "---";

   hvac01.print();

   print "---";
   print "--- ============================================ ";
   print "--- BUILD 2D VIEWER AND INITIALIZE DISPLAY !! ...";
   print "--- ============================================ ";
   print "---";

   // Extract composite hierarchy ...

   hvaclayer01 = hvac01.getWorkspace();

   // Build 2D viewer ...

   jfx = Viewer2D();
   jfx.addLayer( "HVAC Schematic", hvaclayer01 );

   ... details of viewer display removed ...

   print "--- ================================================= ... ";
   print "--- Finished !! ... ";
}

generates a two-layer composite hierarchy.

  • The model imports components from a library of builtin components, e.g.,

    import whistle.application.hvac.ChillerModel;
    

    Each model instance is an extension of AbstractCompoundFeature.

When a print visitor transitions through the composite hierarchy the abbreviated output is as follows:

   --- ===============================================
   --- PRINT COMPOSITE HIERARCHY FOR HVAC WORKSPACE !!
   --- ===============================================
   
   Start Composite Hierarchy: null
   =================================================
   Level =  1
   -------------------------------------------------
   
   Compound Feature: pipe01 ...
   ------------------------------------------------ 
   Local  (x,y) = (   0.0,   0.0 )
   Global (x,y) = (   0.0,   0.0 )
   ------------------------------------------------ 
   Current Rotation (radians) = (   0.0000 )
                    (degrees) = (   0.0000 )
   ------------------------------------------------ 
   BoundingEnv[ x = (0.0--1.0) y = ( 0.0--1.0)]
   ------------------------------------------------ 
   SimplePolygon: name = P01 ... 
   ================================================= ... 
   --- width  =   101.00 ... 
   --- height =   151.00 ... 
   ================================================= ... 
   --- Point  1: (x,y) = (  399.00,   400.00) ... 
   --- Point  2: (x,y) = (  399.00,   551.00) ... 
   --- Point  3: (x,y) = (  500.00,   551.00) ... 
   --- Point  4: (x,y) = (  500.00,   549.00) ... 
   --- Point  5: (x,y) = (  401.00,   549.00) ... 
   --- Point  6: (x,y) = (  401.00,   400.00) ... 
   ================================================= ... 

   ... details of pipes removed ...

   Compound Feature: wire01 ...
   ------------------------------------------------ 
   Local  (x,y) = (   0.0,   0.0 )
   Global (x,y) = (   0.0,   0.0 )
   ------------------------------------------------ 
   Current Rotation (radians) = (   0.0000 )
                    (degrees) = (   0.0000 )
   ------------------------------------------------ 
   BoundingEnv[ x = (0.0--1.0) y = ( 0.0--1.0)]
   ------------------------------------------------ 
   SimplePolygon: name = P01 ... 
   ================================================= ... 
   --- width  =    60.50 ... 
   --- height =   141.00 ... 
   ================================================= ... 
   --- Point  1: (x,y) = (  640.00,   720.50) ... 
   --- Point  2: (x,y) = (  700.50,   720.50) ... 
   --- Point  3: (x,y) = (  700.50,   579.50) ... 
   --- Point  4: (x,y) = (  640.00,   579.50) ... 
   --- Point  5: (x,y) = (  640.00,   580.50) ... 
   --- Point  6: (x,y) = (  699.50,   580.50) ... 
   --- Point  7: (x,y) = (  699.50,   719.50) ... 
   --- Point  8: (x,y) = (  640.00,   719.50) ... 
   ================================================= ... 

   ... details of wires removed ...

   Start Composite Hierarchy: null
   =================================================
   Level =  2
   -------------------------------------------------
   
   Compound Feature: S01 ...
   ------------------------------------------------ 
   Local  (x,y) = ( 150.0,  30.0 )
   Global (x,y) = ( 250.0, 130.0 )
   ------------------------------------------------ 
   Current Rotation (radians) = (   0.0000 )
                    (degrees) = (   0.0000 )
   ------------------------------------------------ 
   BoundingEnv[ x = (150.0-160.0) y = ( 30.0-40.0)]
   ------------------------------------------------ 
   Edge: (x1,y1) = (    150.00,      30.00) 
       : (x2,y2) = (    160.00,      30.00) 
       : thickness =   2 
   
   Edge: (x1,y1) = (    160.00,      30.00) 
       : (x2,y2) = (    160.00,      40.00) 
       : thickness =   2 
 
   Edge: (x1,y1) = (    160.00,      40.00) 
       : (x2,y2) = (    150.00,      40.00) 
 ]     : thickness =   2 
  
   Edge: (x1,y1) = (    150.00,      40.00) 
       : (x2,y2) = (    150.00,      30.00) 
       : thickness =   2 
   
   Edge: (x1,y1) = (    150.00,      30.00) 
       : (x2,y2) = (    160.00,      40.00) 
       : thickness =   1 
   
   Edge: (x1,y1) = (    160.00,      30.00) 
       : (x2,y2) = (    150.00,      40.00) 
       : thickness =   1 
   
   Point: (x,y) = (150.0,  30.0)
   Point: (x,y) = (160.0,  30.0)
   Point: (x,y) = (160.0,  40.0)
   Point: (x,y) = (150.0,  40.0)
   ------------------------------------------------ 

   ... details of sensor output removed ...

   Compound Feature: WP01 ...
   ------------------------------------------------ 
   Local  (x,y) = ( 300.0,   0.0 )
   Global (x,y) = ( 400.0, 100.0 )
   ------------------------------------------------ 
   Current Rotation (radians) = (   0.0000 )
                    (degrees) = (   0.0000 )
   ------------------------------------------------ 
   BoundingEnv[ x = (300.0-330.0) y = ( 0.0-30.0)]
   ------------------------------------------------ 
   Edge: (x1,y1) = (    300.00,       0.00) 
       : (x2,y2) = (    330.00,       0.00) 
       : thickness =   4 

   ... details of wall plate removed ...

   Edge: (x1,y1) = (    330.00,      22.50) 
       : (x2,y2) = (    300.00,      22.50) 
       : thickness =   2 
 
   Point: (x,y) = (300.0,   0.0)
   Point: (x,y) = (330.0,   0.0)
   Point: (x,y) = (330.0,   7.5)
   Point: (x,y) = (330.0,  15.0)
   Point: (x,y) = (330.0,  22.5)
   Point: (x,y) = (330.0,  30.0)
   Point: (x,y) = (300.0,  30.0)
   Point: (x,y) = (300.0,  22.5)
   Point: (x,y) = (300.0,  15.0)
   Point: (x,y) = (300.0,   7.5)
   ------------------------------------------------ 

   ...  wall plates removed ...

   Compound Feature: Rm01, wall01 ...
   ------------------------------------------------ 
   Local  (x,y) = (   0.0,   0.0 )
   Global (x,y) = ( 100.0, 100.0 )
   ------------------------------------------------ 
   Current Rotation (radians) = (   0.0000 )
                    (degrees) = (   0.0000 )
   ------------------------------------------------ 
   BoundingEnv[ x = (0.0--1.0) y = ( 0.0--1.0)]
   ------------------------------------------------ 
   SimplePolygon: name = P01 ... 
   ================================================= ... 
   --- width  =   300.00 ... 
   --- height =   300.00 ... 
   ================================================= ... 
   --- Point  1: (x,y) = (  300.00,   270.00) ... 
   --- Point  2: (x,y) = (   30.00,   270.00) ... 
   --- Point  3: (x,y) = (   30.00,    30.00) ... 
   --- Point  4: (x,y) = (  300.00,    30.00) ... 
   --- Point  5: (x,y) = (  300.00,     0.00) ... 
   --- Point  6: (x,y) = (   -0.00,     0.00) ... 
   --- Point  7: (x,y) = (    0.00,   300.00) ... 
   --- Point  8: (x,y) = (  300.00,   300.00) ... 
   ================================================= ... 
   
   Compound Feature: Rm01, wall02 ...
   ------------------------------------------------ 
   Local  (x,y) = (   0.0,   0.0 )
   Global (x,y) = ( 100.0, 100.0 )
   ------------------------------------------------ 
   Current Rotation (radians) = (   0.0000 )
                    (degrees) = (   0.0000 )
   ------------------------------------------------ 
   BoundingEnv[ x = (0.0--1.0) y = ( 0.0--1.0)]
   ------------------------------------------------ 
   SimplePolygon: name = P01 ... 
   ================================================= ... 
   --- width  =   270.00 ... 
   --- height =   300.00 ... 
   ================================================= ... 
   --- Point  1: (x,y) = (  330.00,    30.00) ... 
   --- Point  2: (x,y) = (  570.00,    30.00) ... 
   --- Point  3: (x,y) = (  570.00,   270.00) ... 
   --- Point  4: (x,y) = (  330.00,   270.00) ... 
   --- Point  5: (x,y) = (  330.00,   300.00) ... 
   --- Point  6: (x,y) = (  600.00,   300.00) ... 
   --- Point  7: (x,y) = (  600.00,     0.00) ... 
   --- Point  8: (x,y) = (  330.00,     0.00) ... 
   ================================================= ... 
   
   -------------------------------------------------
   End level =  2
   =================================================

   Start Composite Hierarchy: null
   =================================================
   Level =  2
   -------------------------------------------------

   ... details of sensors S07 -- S09 removed ...

   ... details of wall plates WP01 and WP02 removed ...

   Compound Feature: Rm02, wall01 ...
   ------------------------------------------------ 
   Local  (x,y) = (   0.0,   0.0 )
   Global (x,y) = ( 1200.0, 100.0 )
   ------------------------------------------------ 
   Current Rotation (radians) = (   1.5708 )
                    (degrees) = (  90.0000 )
   ------------------------------------------------ 
   BoundingEnv[ x = (0.0--1.0) y = ( 0.0--1.0)]
   ------------------------------------------------ 
   SimplePolygon: name = P01 ... 
   ================================================= ... 
   --- width  =   600.00 ... 
   --- height =   300.00 ... 
   ================================================= ... 
   --- Point  1: (x,y) = (  150.00,   270.00) ... 
   --- Point  2: (x,y) = (   30.00,   270.00) ... 
   --- Point  3: (x,y) = (   30.00,    30.00) ... 
   --- Point  4: (x,y) = (  570.00,    30.00) ... 
   --- Point  5: (x,y) = (  570.00,   270.00) ... 
   --- Point  6: (x,y) = (  450.00,   270.00) ... 
   --- Point  7: (x,y) = (  450.00,   300.00) ... 
   --- Point  8: (x,y) = (  600.00,   300.00) ... 
   --- Point  9: (x,y) = (  600.00,     0.00) ... 
   --- Point 10: (x,y) = (   -0.00,     0.00) ... 
   --- Point 11: (x,y) = (    0.00,   300.00) ... 
   --- Point 12: (x,y) = (  150.00,   300.00) ... 
   ================================================= ... 

   ... details of Rm02, wall02 removed ...

   -------------------------------------------------
   End level =  2
   =================================================

   Compound Feature: Chiller 01 ...
   ------------------------------------------------ 
   Local  (x,y) = ( 500.0, 480.0 )
   Global (x,y) = ( 500.0, 480.0 )
   ------------------------------------------------ 
   Current Rotation (radians) = (   0.0000 )
                    (degrees) = (   0.0000 )
   ------------------------------------------------ 
   BoundingEnv[ x = (500.0-640.0) y = ( 480.0-620.0)]
   ------------------------------------------------ 

   ... details of points and edges removed ...

   ------------------------------------------------ 
   
   Start Composite Hierarchy: null
   =================================================
   Level =  2
   -------------------------------------------------
   
   Compound Feature: Fan 02 ...
   ------------------------------------------------ 
   Local  (x,y) = (   0.0,   0.0 )
   Global (x,y) = ( 840.0, 100.0 )
   ------------------------------------------------ 
   Current Rotation (radians) = (   1.5708 )
                    (degrees) = (  90.0000 )
   ------------------------------------------------ 
   BoundingEnv[ x = (0.0-80.0) y = ( 0.0-80.0)]
   ------------------------------------------------ 
   Edge: (x1,y1) = (      0.00,       0.00) 
       : (x2,y2) = (     80.00,       0.00) 
       : thickness =   2 

   ... details of edges and points removed ...

   ------------------------------------------------ 
   
   -------------------------------------------------
   End level =  2
   =================================================

   ... details of Fan 01 removed ...

   Compound Feature: HVAC Control ...
   ------------------------------------------------ 
   Local  (x,y) = ( 300.0, 660.0 )
   Global (x,y) = ( 300.0, 660.0 )
   ------------------------------------------------ 
   Current Rotation (radians) = (   0.0000 )
                    (degrees) = (   0.0000 )
   ------------------------------------------------ 
   BoundingEnv[ x = (300.0-640.0) y = ( 660.0-800.0)]
   ------------------------------------------------ 

     ... details of edges and point removed ...

   -------------------------------------------------
   End level =  1
   =================================================

Points to note:

  • The workspace for Fan 02,

       ws01 = WorkspaceModel ( 840.0, 100.0, PI/2.0 );
       ws01.add ( fan02 );
    

    is transitioned to (x,y) = (840,100) and then rotated by 90 degrees.

  • Room 1 is simply transitioned to (x,y) = ( 100, 100).

  • Room 2 is transitioned to (x,y) = (1200, 100) and then rotated by 90 degrees, i.e.,

       wsroom02 = WorkspaceModel ( 1200.0, 100.0, PI/2.0 );
    

  • We employ a two-dimensional Affine tranformation matrix (at) to keep track of coordinate system translations and rotations.

    When a visitor enters a new level, the Affine matrix is incremented by:

       at.translate( xOffset, yOffset );
       at.rotate( rotation );
    

    And when the visitor is finished, the affine matrix is decremented by:

       at.rotate( -rotation );
       at.translate( -xOffset, -yOffset );
    

    Here, xOffset, yOffset and rotation are increments in x and y translation, and rotation relative to the parent coordinate system.


Working with the Observer Design Pattern

The observer design pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.

The object which is being watched is called the subject. The objects which are watching the state changes are called observers or listeners.


Example 21. In this example, we set up a dependency relationship between a sensor (which would measure data) and three controller objects that are interested in responding to that data.

// ===========================================================================
// Input observer: Simple demonstration of observers and observables.
//
// Written by: Mark Austin                                        October 2014
// ===========================================================================

import whistle.observer.Sensor;
import whistle.observer.Controller;

program ("Demo observers") { 

   print "---";
   print "--- Demo interaction between sensors and controllers ...    ";
   print "--- ====================================================== ";
   print "---";

   // Create controller components and sensor component ..

   control01 = Controller("Control 1");
   control02 = Controller("Control 2");
   control03 = Controller("Control 3");

   s01 = Sensor("Sensor 1");
   s01.addObserver( control01 );
   s01.addObserver( control02 );
   s01.addObserver( control03 );
}

Insert content ...

    ... insert detail here ...

Points to note:

  • ...
  • ...
  • ...
Part 6: Importing Spatial Data into Whistle
  • Parsing XML data with DOM, SAX and JAXB.
  • Importing OpenStreetMap Data
  • Importing DXF (Drawing Exchange Format) Data

Parsing XML data with DOM, SAX and JAXB

Insert content ...

    ... insert example ...

Insert content ...


Importing OpenStreetMap Data

Insert content ...

    ... insert example ...

Insert content ...


Importing DXF (Drawing Exchange Format) Data

Insert content ...

    ... insert example ...

Insert content ...

Part 7: Two-Dimensional Graphics
  • Creating plots with Ptolemy PtPlot() ...
  • JavaFX Chart Components ...
  • Working with Viewer2D() ...

Creating Plots with Ptolemy PtPlot()

Insert content ...

Example 22. The input file:

// ========================================================================
// Experiment with pump data model ....
//
// Written by: Mark Austin                                      August 2014
// ========================================================================

import whistle.gui.ptolemy.PtPlot;

import whistle.util.data.Curve;
import whistle.util.data.DataModel;

program ("Display Pump Data model") { 

   // Manually assemble a toy data model for a pool pump ...
   // ------------------------------------------------------

   data01 = DataModel();
   data01.setTitle( "Pool Pump Excelanto 3000");
   data01.setXLabel("Flow Rate Q (m^3/sec)");
   data01.setYLabel("Pressure Head (m)");

   data01.addCurve("level01");
   data01.addPoint( 0.0, 10.0 );
   data01.addPoint( 1.0, 12.0 );
   data01.addPoint( 2.0, 12.0 );
   data01.addPoint( 3.0, 10.0 );
   data01.addPoint( 3.5,  9.6 );
   data01.addPoint( 3.8,  9.0 );
   data01.addPoint( 4.0,  8.0 );
   data01.addPoint( 4.5,  7.8 );
   data01.addPoint( 5.0,  7.0 );
   data01.addPoint( 5.5,  4.5 );
   data01.addPoint( 6.0,  2.5 );

   data01.addCurve("level02");
   data01.addPoint( 0.0, 12.0 );
   data01.addPoint( 1.0, 13.5 );
   data01.addPoint( 2.0, 13.5 );
   data01.addPoint( 2.5, 12.0 );
   data01.addPoint( 3.0, 11.0 );
   data01.addPoint( 3.5, 10.4 );
   data01.addPoint( 4.0,  9.0 );
   data01.addPoint( 4.5,  8.9 );
   data01.addPoint( 5.0,  8.5 );
   data01.addPoint( 5.5,  8.0 );
   data01.addPoint( 6.0,  6.5 );

   print data01;

   // Plot of pressure head (m) versus pump discharge rate (m^3/sec) ...
   // ------------------------------------------------------------------

   plot01 = PtPlot();
   plot01.setSize( 600, 700 );
   plot01.setTitle( "Pool Pump Excelanto 3000");
   plot01.setXLabel("Flow Rate Q (m^3/sec)");
   plot01.setYLabel("Pressure Head (m)");
   plot01.setXRange( 0.0,  6.0 );
   plot01.setYRange( 0.0, 15.0 );

   // Transfer data model curves plot component ...
   // ---------------------------------------------

   c01    = data01.getCurve ("level01");
   nsteps = c01.getNoPoints();
   for (i = 0; i < nsteps; i = i + 1) {
      plot01.addPoint( c01.getX(i), c01.getY(i) );
   }

   plot01.addCurve();
   c02    = data01.getCurve ("level02");
   nsteps = c02.getNoPoints();
   for (i = 0; i < nsteps; i = i + 1) {
      plot01.addPoint( c02.getX(i), c02.getY(i) );
   }

   // Display plot ...
   // ----------------

   plot01.display();

   // Export data model to XML format ... 
   // -----------------------------------

   data01.export( "pumpDataModel.xml" );
}

generates the textual output:

   --- Begin Program: "Display Pump Data model" ... 
   --- ==============================================================
    
   --- Component: DataModel() ... 
   --- name = null ... 
   --- title = "Pool Pump Excelanto 3000"
   --- x-axis label = "Flow Rate Q (m^3/sec)"
   --- y-axis label = "Pressure Head (m)"
   --- no data curves = 2
   
   --- Component: Curve() ... 
   --- name = "level01"
   --- x-axis data range = [     0.000,      6.000]
   --- y-axis data range = [     2.500,      12.00]
   --- no data points = 11
   --- Point: (x,y) = (   0.00,   10.00)
   --- Point: (x,y) = (   1.00,   12.00)
   --- Point: (x,y) = (   2.00,   12.00)
   --- Point: (x,y) = (   3.00,   10.00)
   --- Point: (x,y) = (   3.50,    9.60)
   --- Point: (x,y) = (   3.80,    9.00)
   --- Point: (x,y) = (   4.00,    8.00)
   --- Point: (x,y) = (   4.50,    7.80)
   --- Point: (x,y) = (   5.00,    7.00)
   --- Point: (x,y) = (   5.50,    4.50)
   --- Point: (x,y) = (   6.00,    2.50)
   
   --- Component: Curve() ... 
   --- name = "level02"
   --- x-axis data range = [     0.000,      6.000]
   --- y-axis data range = [     6.500,      13.50]
   --- no data points = 11
   --- Point: (x,y) = (   0.00,   12.00)
   --- Point: (x,y) = (   1.00,   13.50)
   --- Point: (x,y) = (   2.00,   13.50)
   --- Point: (x,y) = (   2.50,   12.00)
   --- Point: (x,y) = (   3.00,   11.00)
   --- Point: (x,y) = (   3.50,   10.40)
   --- Point: (x,y) = (   4.00,    9.00)
   --- Point: (x,y) = (   4.50,    8.90)
   --- Point: (x,y) = (   5.00,    8.50)
   --- Point: (x,y) = (   5.50,    8.00)
   --- Point: (x,y) = (   6.00,    6.50)
   
   --- =======================================================
   --- Finished!

and the PtPlot:

The abbreviated XML file is:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:dataModel xmlns:ns2="edu.umd.isr.austin">
    <stringToCurveMap>
        <entry>
            <key>"level01"</key>
            <value>
                <curveName>"level01"</curveName>
                <datapoints>
                    <point>
                        <x>0.0</x>
                        <y>10.0</y>
                    </point>

                ... data values removed ...

                </datapoints>
                <name>"level01"</name>
            </value>
        </entry>
        <entry>
            <key>"level02"</key>
            <value>
                <curveName>"level02"</curveName>
                <datapoints>
                    <point>
                        <x>0.0</x>
                        <y>12.0</y>
                    </point>

                    ... data values removed ...

                    <point>
                        <x>6.0</x>
                        <y>6.5</y>
                    </point>
                </datapoints>
                <name>"level02"</name>
            </value>
        </entry>
    </stringToCurveMap>
    <title>"Pool Pump Excelanto 3000"<title>
    <XLabel>"Flow Rate Q (m^3/sec)"</XLabel>
    <YLabel>"Pressure Head (m)"</YLabel>
</ns2:dataModel>

This XML file can be reloaded into the DataModel by simply writing:

   data01 = DataModel();
   data01.getData( "pumpDataModel.xml" );


JavaFX Chart Components

Insert content ...

// =======================================================================
// Input chart01: Demonstrate dynamic linecharts in Java FX.
//
// Written by: Mark Austin                                      June 2015
// ======================================================================

import whistle.gui.chart.LineChart;
import whistle.util.data.DataModel;

program ("JavaFX linechart demo") { 

   // Fabricate data for storm surge height versus time ...
   // -----------------------------------------------------

   data01 = DataModel();
   data01.setTitle( "Storm Surge Height (in) versus Time (sec)");
   data01.setXLabel("Time (sec)");
   data01.setYLabel("Height (in)");

   data01.addCurve("line01");
   for (i = 0; i < 20; i = i + 0.5) {
      y = i*(i-5)*(i-12)*(i-17)/20;
      data01.addPoint( i, y );
   }

   data01.addCurve("line02");
   for (i = 0; i < 20; i = i + 0.5) {
      y = i*(i-7)*(i-12)/10;
      data01.addPoint( i, y );
   }

   data01.addCurve("line03");
   for (i = 1; i < 20; i = i + 0.5) {
      y = 10*i*Sin(i);
      data01.addPoint( i, y );
   }

   // Setup line chart ....

   jfx = LineChart();
   jfx.setTitle( "Storm Surge Height (in) versus Time (sec)");
   jfx.setSize( 800, 500 );
   jfx.setXLabel("Time (sec)");
   jfx.setYLabel("Surge Height (in)");
   jfx.setXRange( 0.0, 20.0 );

   // Display design constraints on line chart ....

   jfx.addCurve( "Constraint 01" );
   jfx.addPoint(  0.0,   75.0);
   jfx.addPoint( 10.0,   75.0);
   jfx.addPoint( 10.0,  150.0);
   jfx.addPoint( 20.0,  150.0);

   jfx.addCurve( "Constraint 02" );
   jfx.addPoint(  0.0,  -75.0);
   jfx.addPoint( 10.0,  -75.0);
   jfx.addPoint( 10.0, -150.0);
   jfx.addPoint( 20.0, -150.0);

   // Display plot ...

   jfx.display();
   jfx.sleep( 2000 );

   // Dynamically transfer curves to plot component ...
   // -------------------------------------------------

   c01 = data01.getCurve ("line01");
   c02 = data01.getCurve ("line02");

   nsteps = c01.getNoPoints();
   for (i = 0; i < nsteps; i = i + 1) {
      jfx.addCurve( "line01" );
      jfx.addPoint( c01.getX(i), c01.getY(i) );
      jfx.addCurve( "line02" );
      jfx.addPoint( c02.getX(i), c02.getY(i) );
      jfx.sleep( 200 );
   }

   c03 = data01.getCurve ("line03");
   nsteps = c03.getNoPoints();
   for (i = 0; i < nsteps; i = i + 1) {
      jfx.addCurve( "line03" );
      jfx.addPoint( c03.getX(i), c03.getY(i) );
      jfx.sleep( 200 );
   }
}

Insert content ...


Working with Viewer2D()

Creating a Viewer

The script:

   // Build 2D viewer ...

   jfx = Viewer2D();
   jfx.addLayer(  "Grid", gridlayer );
   jfx.addLayer( "Stuff", workspacelayer );

   // Setup and instantiate display ....

   jfx.sleep( 1000 );
   jfx.setSize( 1000, 1000 );
   jfx.setXRange( 0, 800 );
   jfx.setYRange( 0, 800 );
   jfx.display();
   jfx.sleep( 1000 );

shows the essential details of initializing a viewer, adding a couple of layers, and then initializing parameters in the display.

Points to note:

  • ...

  • ...

  • ...


Coordinate Systems

Whistle works with an (x-y) coordinate system, with the origin and orientation of the axes as shown below.

Data sources such as OpenStreetMap provide a bounding box for the coverage of geographical content, with the corner points defined in terms of longitude and latitude.

Whistle binds the lower left-hand corner to the origin of the (x-y) coordinate system, and then scales and tranforms each data point from (long, lat) --> (x,y) pixel coordinates. The scaleFactor() method makes the necessary adjustment of scales.


Moving Things Around

When a script adds a composite model to the viewer, it's a reference to the model that is actually being added, not the model itself. As a result, scripts have the ability to control movement of things in a scene.

Case 1: Simple Positioning

The short script:

   car01.setPosition( 500 m, 50 m );

   jfx.sleep( 1000 );

   for (i = 0; i < 100; i = i + 1) {

       // Reposition car01 ....

       distance = 450 + 100*Sin(i/20);
       car01.setPosition( distance, 50.0 );

       // Reposition car02 ...

       xcoord = 450 - 200*Sin(i/20);
       ycoord = 450 + 300*Cos(i/20);
       car02.setPosition( xcoord, ycoord );

       // Refresh graphics then sleep 50 milliseconds ...

       jfx.refresh();
       jfx.sleep( 50 );
   }

is an extension of the Acura/BWM example, and moves car01 back-and-forth horizontally and car02 in an ellipse. At the conclusion of each step the command:

       jfx.sleep( 50 );

pauses the animation for 50 milliseconds.

Case 2: Pathway Traversal with Tangential Orientation.

TBD ...


Working with Viewer2DX()

Here is a draft of the new graphical interface (2019, work in progress):

Instead of simply displaying the contents, we want to display lists of nodes, ways and relations found in the model. A user should be able to click on a list item, and have it visually respond in the main graphics window.

Last Modified: June 20, 2020.