Move graphics

Download Sample Viewer

Description

This sample shows how to add graphics to a map and move them in the display by updating the graphics' positions in the graphics layer. The graphics in this case are 'taxis', which are symbolized using a picture marker symbol, and represented as point geometries. The taxis move along pre-defined routes, read in from files, and are randomly assigned to routes as the user adds more taxis to the map. The taxi graphics are updated regularly on a Swing Timer using their graphic ID and the position to move to. This application was built for the Esri 2012 Developer Summit as a demo for the Java technical session.

Code snippet


    private void MoveTaxis() {
        // loop through all the taxi objects
        for (Taxi taxi:taxis) {
            // update the taxi position checking if it has actually moved
            if (taxi.MoveTaxi()) {
                // update the graphics layer
                graphicsLayer.updateGraphic(taxi.getGraphicIdentifier(), taxi.getCurrentPosition());
            }
        }
    }
  

Sample Code

/* Copyright 2014 Esri

All rights reserved under the copyright laws of the United States
and applicable international laws, treaties, and conventions.

You may freely redistribute and use this sample code, with or
without modification, provided you include the original copyright
notice and use restrictions.

See the use restrictions.*/
package com.esri.client.samples.graphics;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import com.esri.map.ArcGISTiledMapServiceLayer;
import com.esri.map.GraphicsLayer;
import com.esri.map.JMap;

import com.esri.core.geometry.Point;
import com.esri.core.map.Graphic;
import com.esri.core.symbol.PictureMarkerSymbol;
import com.esri.core.symbol.SimpleMarkerSymbol;
import com.esri.core.symbol.SimpleMarkerSymbol.Style;
import com.esri.core.symbol.Symbol;
import com.esri.map.MapEventListenerAdapter;
import com.esri.map.MapEvent;
import com.esri.runtime.ArcGISRuntime;

import javax.swing.JButton;

/***
 * Demo displaying taxis ({@link Taxi}) moving around several routes
 * ({@link TaxiRoute}) through New York City, implemented as graphics in
 * a {@link GraphicsLayer} moving on a Swing Timer.
 */
public class TaxiGraphicsApp {

  // settings
  private static final int MS_DELAY = 10; // how often to move taxis, in milliseconds
  private static final int NUM_TAXIS = 10; // number of taxis at the start of the app
  private static final int NUM_ROUTES = 9; // number of routes (in text files)
  private static final String URL_BASEMAP =
      "http://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer";
  private static final String PATH_TO_IMAGE = "resources/taxi.png";
  private static final String FSP = System.getProperty("file.separator");

  // components
  private JMap map;
  private GraphicsLayer graphicsLayer;
  private JButton startButton;
  private JButton stopButton;
  private JTextField taxisInput;
  private Symbol taxiSymbol;

  // UI constants
  private static final int BUTTON_WIDTH = 120;
  private static final int BUTTON_HEIGHT = 25;
  private static final Color BG_COLOR = new Color(0, 0, 0, 150);

  // taxi routes and taxis
  private List<TaxiRoute> routes = new ArrayList<>();
  private List<Taxi> taxis = new ArrayList<>();

  //timer to move things
  private Timer actionTimer;

  private Random random = new Random();

  // ------------------------------------------------------------------------
  // Constructor
  // ------------------------------------------------------------------------
  public TaxiGraphicsApp() {
    // create our taxi symbol
    taxiSymbol = makeTaxiSymbol();
  }

  // ------------------------------------------------------------------------
  // Core functionality
  // ------------------------------------------------------------------------
  /**
   * Creates and displays the UI, including the map, for this application.
   */
  public JComponent createUI() throws Exception {
    // application content
    JLayeredPane contentPane = createContentPane();

    // UI panels
    final JPanel buttonPanel = createButtonPanel();
    final JPanel addTaxiPanel = createAddTaxiPanel();

    // add the panels to the main window
    contentPane.add(buttonPanel);
    contentPane.add(addTaxiPanel);

    // create map
    map = new JMap();
    map.addMapEventListener(new MapEventListenerAdapter() {
      @Override
      public void mapReady(MapEvent event) {
        // start the simulation when map is ready
        SetUpTaxiSimulation();
        startButton.setEnabled(true);
      }
    });

    // basemap
    ArcGISTiledMapServiceLayer tiledLayer = new ArcGISTiledMapServiceLayer(URL_BASEMAP);
    map.getLayers().add(tiledLayer);

    // graphics layer for taxi graphics
    graphicsLayer = new GraphicsLayer();
    map.getLayers().add(graphicsLayer);

    contentPane.add(map);

    return contentPane;
  }

  /**
   * Method called on MapReady.
   * Zooms to the right extent, creates the graphics layer, creates the routes,
   * creates the taxis, creates the Timer.
   */
  private void SetUpTaxiSimulation() {
    // zoom to New York City (Manhattan)
    map.zoomToResolution(3, new Point(-8236190.434,4972532.0632));

    // make taxi routes
    for (int i = 1; i <= NUM_ROUTES; i++ ) {
      routes.add(new TaxiRoute(getPathSampleData() + "graphics"+FSP+"Route"+i+".txt"));
    }

    // add some taxis to start
    addTaxis(NUM_TAXIS);

    // set up the timer for moving the taxis
    actionTimer = new Timer(MS_DELAY , actionListener);
    actionTimer.setRepeats(true);
  }

  ActionListener actionListener = new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent arg0) {
      // move all of the taxis
      MoveTaxis();
    }
  };

  private void MoveTaxis() {
    // loop through them all
    for (Taxi taxi:taxis) {
      // update the taxi position checking if it has actually moved
      if (taxi.MoveTaxi()) {
        // update the graphics layer / display
        graphicsLayer.updateGraphic(taxi.getGraphicIdentifier(), taxi.getCurrentPosition());
      }
    }
  }

  private void addTaxis(int numTaxis) {
    for (int taxi=0; taxi < numTaxis; taxi++) {
      // choose a random route from our routes
      TaxiRoute route = routes.get(random.nextInt(routes.size()));
      // make one taxi, starts at a random position on the route
      MakeTaxi(route, random.nextInt(route.getXPos().size()));
    }
  }

  private void MakeTaxi(TaxiRoute route, int PositionOnRoute) {
    // add the graphic for this taxi
    Point point = new Point((route.getXPos().get(PositionOnRoute)).intValue(), (route.getYPos().get(PositionOnRoute)).intValue());
    Graphic gr = new Graphic(point, taxiSymbol);
    int graphicId = graphicsLayer.addGraphic(gr);

    // make the Taxi object, add to list of taxis in app so we can move it around the route
    taxis.add(new Taxi(route, PositionOnRoute, graphicId));
  }

  // ------------------------------------------------------------------------
  // Static methods
  // ------------------------------------------------------------------------
  /**
   * Starting point of the application.
   * @param args
   */
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override
      public void run() {
        try {
          TaxiGraphicsApp theApp = new TaxiGraphicsApp();
          JFrame appWindow = theApp.createWindow();
          appWindow.add(theApp.createUI());
          appWindow.setVisible(true);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }

  // ------------------------------------------------------------------------
  // Private methods
  // ------------------------------------------------------------------------
  private Symbol makeTaxiSymbol() {

    PictureMarkerSymbol symbol = null;
    BufferedImage image = null;

    URL url = this.getClass().getResource(PATH_TO_IMAGE);
    try {
      image = ImageIO.read(url);
      symbol = new PictureMarkerSymbol(image);
      symbol.setSize(20, 20);
    }
    catch (Exception e) {
      System.err.println("unable to create picture marker symbol");
      return new SimpleMarkerSymbol(Color.YELLOW, 12, Style.CIRCLE);
    }
    return symbol;
  }

  private JPanel createButtonPanel() {
    JPanel panel = new JPanel();
    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
    panel.setSize(BUTTON_WIDTH + 10, (BUTTON_HEIGHT*2) + (5*3));
    panel.setLocation(10, 10);

    // buttons
    startButton = new JButton("Start");
    startButton.setMaximumSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
    startButton.setMinimumSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
    startButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        actionTimer.start();
        stopButton.setEnabled(true);
        startButton.setEnabled(false);
      }
    });
    startButton.setEnabled(false);

    stopButton = new JButton("Stop");
    stopButton.setMaximumSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
    stopButton.setMinimumSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
    stopButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        if (actionTimer.isRunning()) {
          actionTimer.stop();
          startButton.setEnabled(true);
          stopButton.setEnabled(false);
        }
      }
    });
    stopButton.setEnabled(false);

    // layout all the components together into a panel
    panel.setBackground(BG_COLOR);
    panel.add(startButton);
    panel.add(Box.createRigidArea(new Dimension(0,5)));
    panel.add(stopButton);
    panel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
    return panel;
  }

  private JPanel createAddTaxiPanel() {
    JPanel panel = new JPanel();
    panel.setLayout(new BorderLayout(0, 5));
    panel.setSize(180, BUTTON_HEIGHT*2 + 15);
    panel.setLocation(BUTTON_WIDTH + 30, 10);

    JLabel lblNumTaxis = new JLabel("Number of taxis: ");
    lblNumTaxis.setForeground(Color.WHITE);
    lblNumTaxis.setFont(new Font("Arial", Font.BOLD, 12));
    lblNumTaxis.setMinimumSize(new Dimension(120, 20));
    lblNumTaxis.setMaximumSize(new Dimension(160, 20));

    // text field to input number of days
    taxisInput = new JTextField("10", 5);
    taxisInput.setMinimumSize(new Dimension(30, 20));
    taxisInput.setMaximumSize(new Dimension(40, 20));

    // group the above UI items into a panel
    final JPanel controlPanel = new JPanel();
    BoxLayout boxLayout = new BoxLayout(controlPanel, BoxLayout.X_AXIS);
    controlPanel.setLayout(boxLayout);
    controlPanel.setSize(BUTTON_WIDTH, 20);
    controlPanel.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
    controlPanel.setBackground(new Color(0, 0, 0, 0));
    controlPanel.add(lblNumTaxis);
    controlPanel.add(taxisInput);

    // button
    JButton button = new JButton("Add taxis");
    button.setMaximumSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
    button.setMinimumSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
    button.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        // get number from taxisInput
        int numTaxis = Integer.parseInt(taxisInput.getText());
        // add taxis
        addTaxis(numTaxis);
      }
    });

    // layout all the components together into a panel
    panel.setBackground(BG_COLOR);
    panel.add(controlPanel, BorderLayout.CENTER);
    panel.add(button, BorderLayout.SOUTH);
    panel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
    return panel;
  }

  /**
   * Creates a content pane.
   * @return a content pane.
   */
  private static JLayeredPane createContentPane() {
    JLayeredPane contentPane = new JLayeredPane();
    contentPane.setLayout(new BorderLayout(0, 0));
    contentPane.setVisible(true);
    return contentPane;
  }

  /**
   * Creates a window.
   * @return a window.
   */
  private JFrame createWindow() {
    JFrame window = new JFrame("Moving Graphics Application");
    window.setBounds(100, 100, 1000, 700);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.getContentPane().setLayout(new BorderLayout(0, 0));
    window.addWindowListener(new WindowAdapter() {
      @Override
      public void windowClosing(WindowEvent windowEvent) {
        super.windowClosing(windowEvent);
        map.dispose();
      }
    });
    return window;
  }

  private String getPathSampleData() {
    String dataPath = null;
    String javaPath = ArcGISRuntime.getInstallDirectory();
    if (javaPath != null) {
      if (!(javaPath.endsWith("/") || javaPath.endsWith("\\"))){
        javaPath += FSP;
      }
      dataPath = javaPath + "sdk" + FSP + "samples" + FSP + "data" + FSP;
    }
    File dataFile = new File(dataPath);
    if (!dataFile.exists()) { 
      dataPath = ".." + FSP + "data" + FSP;
    }
    return dataPath;
  }
}
/* Copyright 2014 Esri

All rights reserved under the copyright laws of the United States
and applicable international laws, treaties, and conventions.

You may freely redistribute and use this sample code, with or
without modification, provided you include the original copyright
notice and use restrictions.

See the use restrictions.*/
package com.esri.client.samples.graphics;

import com.esri.core.geometry.Point;

/**
 * A class representing one taxi.  A taxi keeps track of what route it is
 * following, where it is along the route, its current {@link Point} location
 * on the map, as well as its own unique graphic identifier, with which you can
 * refer to this specific graphic/taxi within the application.
 */
public class Taxi {

  private TaxiRoute followingRoute;
  private int location;
  private int graphicIdentifier;
  private Point currentPosition;
  private boolean goSlow = false;
  private boolean stopAtJunction = false;
  private int countDown = 0; // used to slow taxi or stop it for a while

  // slow and stop properties
  private final int slowCountDown = 2;
  private final int stopCountDown = 200;

  public Taxi(TaxiRoute route, int positionOnRoute, int graphicID) {
    followingRoute = route;
    graphicIdentifier = graphicID;
    setLocation(positionOnRoute);
    currentPosition = new Point();
  }

  public boolean MoveTaxi() {
    boolean haveWeMoved = false;

    // have we come to a slowdown point?
    if ((followingRoute.getSlowFlag().get(location)).intValue() == 1) {
      // 2 ticks per move now
      goSlow = true;
      countDown = slowCountDown;

      // shift to the next location along the route
      location++;
    }

    // have we come to a stop point?
    if ((followingRoute.getStopFlag().get(location)).intValue() == 1) {
      // stop for a few ticks
      stopAtJunction = true;
      countDown = stopCountDown;

      // shift to the next location along the route
      location++;
    }

    // if we are in countdown (slow or stop at junction), decrement count down
    if (countDown > 0) {
      countDown--;
    }

    // are we okay to move forward?
    if (countDown == 0) {
      //update the current position geometry
      currentPosition.setX((followingRoute.getXPos().get(location)).intValue());
      currentPosition.setY((followingRoute.getYPos().get(location)).intValue());

      haveWeMoved = true;

      // were we in stop mode?
      if (stopAtJunction == true) {
        // move ahead at full speed
        stopAtJunction = false;
      }

      // were we in show mode?
      if (goSlow == true) {
        // keep going slow
        countDown = slowCountDown;
      }

      // increment the location making sure we have not dropped off the end of the of route
      if (location++ >= (followingRoute.getXPos().size()-1)) {
        // move back to the start of the route
        location = 0;
      }
    }

    return haveWeMoved;
  }

  public Point getCurrentPosition() {
    return currentPosition;
  }

  public int getLocation() {
    return location;
  }

  public void setLocation(int location) {
    this.location = location;
  }

  public int getGraphicIdentifier() {
    return graphicIdentifier;
  }

  public TaxiRoute getFollowingRoute() {
    return followingRoute;
  }

  public void setGraphicIdentifier(int graphicIdentifier) {
    this.graphicIdentifier = graphicIdentifier;
  }
}
/* Copyright 2014 Esri

All rights reserved under the copyright laws of the United States
and applicable international laws, treaties, and conventions.

You may freely redistribute and use this sample code, with or
without modification, provided you include the original copyright
notice and use restrictions.

See the use restrictions.*/
package com.esri.client.samples.graphics;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/***
 * A class representing a taxi route, which includes a list of x and y coordinates,
 * as well as 'slow' and 'stop' flags for when taxis are nearing, or at, intersections.
 *
 */
public class TaxiRoute {

  private List<Double> xPos = new ArrayList<>();
  private List<Double> yPos =  new ArrayList<>();
  private List<Integer> slowFlag = new ArrayList<>();
  private List<Integer> stopFlag = new ArrayList<>();

  public TaxiRoute(String routeFile) {
    readRouteFromFile(routeFile);
  }

  public List<Double> getXPos() {
    return xPos;
  }

  public List<Double> getYPos() {
    return yPos;
  }

  public List<Integer> getSlowFlag() {
    return slowFlag;
  }

  public List<Integer> getStopFlag() {
    return stopFlag;
  }

  // read route into memory
  private void readRouteFromFile(String routeFile)
  {

    File file = new File(routeFile);

    BufferedReader bufRdr = null;
    String line = null;
    int row = 0;
    int col = 0;
    String csvItem = null;
    try {
      bufRdr = new BufferedReader(new FileReader(file));
      while((line = bufRdr.readLine()) != null) {
        StringTokenizer st = new StringTokenizer(line,",");

        while (st.hasMoreTokens()) {

          csvItem = st.nextToken();

          switch (col) {
            case 0:
              //position ID
              break;
            case 1:
              //route ID
              break;
            case 2:
              //X pos
              xPos.add(row, Double.valueOf(csvItem));
              break;
            case 3:
              //Y pos
              yPos.add(row, Double.valueOf(csvItem));
              break;
            case 4:
              //slow down flag
              slowFlag.add(row, Integer.valueOf(csvItem));
              break;
            case 5:
              //stop at junction flag
              stopFlag.add(row, Integer.valueOf(csvItem));
              break;
          }
          col++;
        }
        col = 0;
        row++;
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (NumberFormatException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (bufRdr != null) {
        try {
          bufRdr.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
}
Feedback on this topic?