Online editing

Download Sample Viewer

Description

This application shows an example of an online editing workflow. The features from a feature service display in the application via the creation of a FeatureLayer. This feature layer is created from a GeodatabaseFeatureServiceTable, itself created from a feature service URL and layer ID. On application load, features within the map's initial extent are shown. When you pan and zoom, any additional features will be requested from the service and displayed via the FeatureLayer.

Edits can be made to the features using the UI components in the application. These components are available as part of the SDK's open-source toolkit. Any edits made to features are immediately applied to the feature service by these components.

To use the application, click on the toolbar buttons on the left. The radio buttons will display some additional instructions/options in the main map panel. The application includes querying functionalities in addition to feature editing.

Code snippet


   // create a feature service table from feature layer url
   featureServiceTable = new GeodatabaseFeatureServiceTable(FEATURE_SERVICE_URL, 0);
   
   // create a feature layer from the feature service table
   featureLayer = new FeatureLayer(featureServiceTable);
   map.getLayers().add(featureLayer);

   // features could be edited in multiple ways - one of them is to add the edit tools picker
   jEditToolsPicker = new JEditToolsPicker(map);
   jEditToolsPicker.setCreationOverlay(overlay);
   jEditToolsPicker.setVisible(false);
  

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.editing;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JToolBar;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;

import com.esri.toolkit.utilities.ComponentDragger;
import com.esri.core.geodatabase.GeodatabaseFeature;
import com.esri.core.geodatabase.GeodatabaseFeatureServiceTable;
import com.esri.core.geodatabase.GeodatabaseFeatureTable;
import com.esri.core.geodatabase.GeodatabaseFeatureServiceTable.Status;
import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.Point;
import com.esri.core.map.CallbackListener;
import com.esri.core.map.FeatureType;
import com.esri.core.renderer.Renderer;
import com.esri.core.symbol.Symbol;
import com.esri.core.table.TableException;
import com.esri.core.tasks.query.QueryParameters;
import com.esri.map.ArcGISTiledMapServiceLayer;
import com.esri.map.FeatureLayer;
import com.esri.map.JMap;
import com.esri.map.LayerInitializeCompleteEvent;
import com.esri.map.LayerInitializeCompleteListener;
import com.esri.map.MapOverlay;
import com.esri.toolkit.editing.JEditToolsPicker;
import com.esri.toolkit.editing.JTemplatePicker;

/***
 * This application shows an example of an online editing workflow. The features from 
 * a feature service display in the application via the creation of a <code>FeatureLayer</code>. 
 * This feature layer is created from a <code>GeodatabaseFeatureServiceTable</code>, 
 * itself created from a feature service URL and layer ID. On application load, 
 * features within the map's initial extent are shown. When you pan and zoom, any 
 * additional features will be requested from the service and displayed via the 
 * FeatureLayer.
 * <p>
 * Edits can be made to the features using the UI components in the application. 
 * These components are available as part of the SDK's open-source toolkit. 
 * Any edits made to features are immediately applied to the feature service by 
 * these components.
 * <p>
 * To use the application, click on the toolbar buttons on the left. The radio buttons will
 * display some additional instructions/options in the main map panel. The
 * application includes querying functionalities in addition to feature editing.
 * 
 */
public class OnlineEditingApp {

  // ArcGIS objects
  private JMap map;
  private FeatureLayer featureLayer;
  private GeodatabaseFeatureServiceTable featureServiceTable;
  
  // toolkit components
  private JTemplatePicker jPicker;
  private JEditToolsPicker jEditToolsPicker;

  // Swing components
  private JLayeredPane mapPane;
  private JToolBar toolbar;
  private ContextPanel searchPanel;
  private JRadioButton editingButton;
  private JRadioButton searchButton;
  private DefaultComboBoxModel<FeaturePrototype> comboBoxModel = new DefaultComboBoxModel<>();

  // UI constants 
  private static final Color TRANS_COLOR = new Color(0, 0, 0, 0);
  private static final Color BLACK_TRANSPARENT = new Color(0, 0, 0, 80);
  private static final String SEARCH_STRING = "Search";
  private static final String EDITING_STRING = "Edit";
  private static final Dimension H_SPACE = new Dimension(5, 0);
  private static final Dimension V_SPACE = new Dimension(0, 5);

  private static final String FEATURE_SERVICE_URL = 
      "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Sync/WildfireSync/FeatureServer";

  /**
   * Starting point of this application: creates the window, creates an instance
   * of this application and calls method to create the UI.
   * 
   * @param args
   *          any arguments
   */
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        try {
          // instance of this application
          OnlineEditingApp application = new OnlineEditingApp();

          // create the UI, including the map, for the application
          JFrame appWindow = application.createWindow();
          appWindow.getContentPane().add(application.createUI());
          appWindow.setVisible(true);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }

  /**
   * Dispose the map and feature service table. This must be called when you are
   * finished using the map and the feature service table, for example on
   * application exit.
   */
  public void dispose() {
    if (featureServiceTable != null) {
      featureServiceTable.dispose();
    }
    if (map != null) {
      map.dispose();
    }
  }

  /**
   * Creates the map, toolbar, and various Swing panels used in the
   * application.
   */
  public JComponent createUI() {

    JPanel mainPanel = new JPanel();
    mainPanel.setLayout(new BorderLayout());

    mapPane = createMapPane();
    map = new JMap();

    final ArcGISTiledMapServiceLayer tiledLayer = new ArcGISTiledMapServiceLayer(
        "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer");
    map.getLayers().add(tiledLayer);
    
    // set an initial extent - features in this extent will be displayed on application load
    map.setExtent(new Envelope(-1.3637862136574738E7, 4543803.104647163,
        -1.362424776285072E7, 4552972.107530484));

    // creates the geodatabase feature service table and the feature layer
    createFeatureServiceTable();
      
    // search/query panel
    searchPanel = createSearchPanel();
    mapPane.add(searchPanel);

    // add template picker
    jPicker = new JTemplatePicker(map);
    mapPane.add(jPicker, BorderLayout.WEST);
    jPicker.setIconWidth(36);
    jPicker.setIconHeight(36);
    jPicker.setShowNames(true);
    jPicker.setWatchMap(true);
    jPicker.setVisible(false);
    jPicker.setBackground(Color.LIGHT_GRAY);
    jPicker.setForeground(Color.WHITE);

    // add edit tools picker
    jEditToolsPicker = new JEditToolsPicker(map);
    jEditToolsPicker.setCreationOverlay(jPicker.getOverlay());
    jEditToolsPicker.setVisible(false);
    mapPane.add(jEditToolsPicker, BorderLayout.NORTH);

    // map must be added last to the layered pane!
    mapPane.add(map);
    mainPanel.add(mapPane, BorderLayout.CENTER);

    // create toolbar
    toolbar = new JToolBar();
    toolbar.setBorder(new LineBorder(Color.DARK_GRAY, 1));
    toolbar.setOrientation(SwingConstants.VERTICAL);
    mainPanel.add(toolbar, BorderLayout.WEST);

    // group where user can only select one button at a time
    ButtonGroup buttonGroup = new ButtonGroup();
    // listener for the button group
    ButtonGroupListener buttonGroupListener = new ButtonGroupListener();

    searchButton = new JRadioButton("Search for features");
    searchButton.setName(SEARCH_STRING);
    toolbar.add(searchButton);
    buttonGroup.add(searchButton);
    searchButton.setEnabled(false);
    searchButton.addItemListener(buttonGroupListener);
    searchButton.addActionListener(buttonGroupListener);

    editingButton = new JRadioButton("Edit data");
    editingButton.setName(EDITING_STRING);
    toolbar.add(editingButton);
    buttonGroup.add(editingButton);
    editingButton.setEnabled(false);
    editingButton.addItemListener(buttonGroupListener);
    editingButton.addActionListener(buttonGroupListener);

    return mainPanel;
  }

  /**
   * Creates geodatabase feature service table and feature 
   * layer to display features in the map.
   */
  private void createFeatureServiceTable() {

    featureServiceTable = new GeodatabaseFeatureServiceTable(
        FEATURE_SERVICE_URL, 0);
    
    //featureServiceTable.initialize();
    featureServiceTable.initialize(new CallbackListener<GeodatabaseFeatureServiceTable.Status>() {
      
      @Override
      public void onError(Throwable e) {
             JOptionPane.showMessageDialog(map, wrap(e.getLocalizedMessage()),
                 "",JOptionPane.ERROR_MESSAGE);     
      }
      
      @Override
      public void onCallback(Status status) {
        if (Status.INITIALIZED == status) {
          createFeatureLayer();
        }
      }
    });
  }
    
    /**
     * Creates feature layer to display features in the map.
     */
    private void createFeatureLayer() {
    featureLayer = new FeatureLayer(featureServiceTable);

    featureLayer
        .addLayerInitializeCompleteListener(new LayerInitializeCompleteListener() {

          @Override
          public void layerInitializeComplete(LayerInitializeCompleteEvent e) {

            // use a runnable as we need to execute code that uses Swing
            // components on the EDT
            Runnable layerInitialisedRunnable = new Runnable() {

              @Override
              public void run() {
                // add feature layer data to the map
                map.getLayers().add(featureLayer);

                // set up the array of type names and IDs for the search box
                List<FeatureType> featureTypes = featureServiceTable
                    .getFeatureTypes();

                Renderer renderer = featureLayer.getRenderer();

                Map<String, Object> attributes = new HashMap<>();
                for (FeatureType t : featureTypes) {

                  String typeIDfield = ((GeodatabaseFeatureTable) featureLayer
                      .getFeatureTable()).getTypeIdField();
                  attributes.put(typeIDfield, new Integer(t.getId()));
                  try {
                    // get symbol image
                    GeodatabaseFeature f = new GeodatabaseFeature(attributes,
                        featureServiceTable);
                    Symbol symbol = renderer.getSymbol(f);
                    Image image = featureLayer.createSymbolImage(symbol,
                        new Point(30.0, 30.0), 60, 60, new Color(0x0, true));
                    // scale symbol image for the combo box
                    image = image.getScaledInstance(40, 40,
                        java.awt.Image.SCALE_SMOOTH);

                    FeaturePrototype prototype = new FeaturePrototype(t
                        .getName(), Integer.parseInt(t.getId()), new ImageIcon(
                        image));
                    comboBoxModel.addElement(prototype);
                  } catch (TableException e1) {
                    e1.printStackTrace();
                  }
                }
                editingButton.setEnabled(true);
                searchButton.setEnabled(true);
              }
            };

            if (SwingUtilities.isEventDispatchThread()) {
              layerInitialisedRunnable.run();
            } else {
              SwingUtilities.invokeLater(layerInitialisedRunnable);
            }
          }
        });

    featureLayer.initializeAsync();
  }

  private void search(int searchItem) {
    QueryParameters query = new QueryParameters();
    String typeIDfield = ((GeodatabaseFeatureTable) featureLayer
        .getFeatureTable()).getTypeIdField();
    query.setWhere(typeIDfield + "=" + searchItem);
    featureLayer.getFeatureTable().queryIds(query,
        new CallbackListener<long[]>() {

          @Override
          public void onError(Throwable e) {
            JOptionPane.showMessageDialog(map, wrap(e.getMessage()), "", JOptionPane.ERROR_MESSAGE);
          }

          @Override
          public void onCallback(long[] result) {
            if (result != null) {
              featureLayer.selectFeatures(result, false);
            }
          }
        });
  }

  private ContextPanel createSearchPanel() {
    final JComboBox<FeaturePrototype> comboBox = new JComboBox<>(comboBoxModel);
    comboBox.setRenderer(new CellRenderer(true, true));
    comboBox.setAlignmentX(Component.LEFT_ALIGNMENT);

    JButton findButton = new JButton("Find and select features");

    findButton.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent arg0) {
        if (featureLayer != null) {
          featureLayer.clearSelection();
        }

        search(((FeaturePrototype) comboBox.getSelectedItem()).getValue());
      }
    });

    JButton clearSelectionButton = new JButton("Clear selection");
    clearSelectionButton.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent arg0) {
        if (featureLayer != null) {
          featureLayer.clearSelection();
        }
      }
    });

    // button panel
    JPanel buttonPanel = new JPanel();
    buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
    buttonPanel.add(findButton);
    buttonPanel.add(Box.createRigidArea(H_SPACE));
    buttonPanel.add(clearSelectionButton);
    buttonPanel.setBackground(TRANS_COLOR);
    buttonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);

    // button panel
    JPanel userPanel = new JPanel();
    userPanel.setLayout(new BoxLayout(userPanel, BoxLayout.Y_AXIS));
    userPanel.add(Box.createRigidArea(V_SPACE));
    userPanel.add(comboBox);
    userPanel.add(Box.createRigidArea(V_SPACE));
    userPanel.add(buttonPanel);
    userPanel.setBackground(BLACK_TRANSPARENT);
    comboBox.setPreferredSize(new Dimension(435, 28));

    ContextPanel panel = new ContextPanel();
    panel.setLocation(10, 10);
    panel.setSize(450, 95);
    panel.setTitle("Choose an event type value to search for:");
    panel.setContent(userPanel);
    return panel;
  }

  /**
   * Creates a map pane which will contain the map component and be able to
   * display other components above the map in z-order, such as panels.
   * 
   * @return the map pane
   */
  private static JLayeredPane createMapPane() {
    JLayeredPane contentPane = new JLayeredPane();
    contentPane.setBounds(0, 0, 1000, 700);
    contentPane.setLayout(new BorderLayout(0, 0));
    contentPane.setVisible(true);
    return contentPane;
  }

  /**
   * Listener for the radio buttons in the button group. Each radio button has a
   * corresponding panel which displays when the button is selected, and
   * potentially has an associated overlay to respond to button clicks on the
   * map in the appropriate way. Implements two interfaces so that the radio
   * buttons can respond to state changes (selected, deselected), and also if
   * the user clicks the button when it is already selected, for example if the
   * context panel has been closed and the user wants it to display again.
   */
  class ButtonGroupListener implements ActionListener, ItemListener {

    @Override
    public void itemStateChanged(ItemEvent ev) {
      String name = ((AbstractButton) ev.getSource()).getName();
      boolean enablePanel = false;
      if (ev.getStateChange() == ItemEvent.SELECTED) {
        enablePanel = true;
      } else if (ev.getStateChange() == ItemEvent.DESELECTED) {
        enablePanel = false;
      }

      if (name.contentEquals(SEARCH_STRING)) {
        searchPanel.setVisible(enablePanel);
      } else if (name.contentEquals(EDITING_STRING)) {
        jPicker.setVisible(enablePanel);
        jEditToolsPicker.setVisible(enablePanel);
      }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      if (((JRadioButton) e.getSource()).isSelected()) {
        if (((AbstractButton) e.getSource()).getName().contentEquals(
            SEARCH_STRING)) {
          searchPanel.setVisible(true);
          jPicker.setEnabled(false);
        } else if (((AbstractButton) e.getSource()).getName().contentEquals(
            EDITING_STRING)) {
          jPicker.setEnabled(true);
          jPicker.setVisible(true);
          jEditToolsPicker.setVisible(true);
        }
      }
    }
  }

  private JFrame createWindow() {
    JFrame window = new JFrame("Online Workflow Application");
    window.setSize(1000, 600);
    window.setLocationRelativeTo(null);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.getContentPane().setLayout(new BorderLayout());
    // dispose map and geodatabase just before application window is closed
    window.addWindowListener(new WindowAdapter() {
      @Override
      public void windowClosing(WindowEvent windowEvent) {
        super.windowClosing(windowEvent);
        dispose();
      }
    });
    return window;
  }

  /**
   * A custom JPanel that is draggable and closeable, to show a title (text) and
   * content below it (another JPanel).
   */
  class ContextPanel extends JPanel {

    private static final long serialVersionUID = 1L;
    private final Color BORDER_COLOR = new Color(0, 0, 0, 80);
    private final Color TOP_COLOR = new Color(0, 0, 0, 150);

    private JPanel contentPanel;
    private JLabel titleText;
    private MapOverlay overlay;

    private int defaultWidth = 400; // default
    private int defaultHeight = 100; // default
    private ImageIcon closeIcon;
    private boolean showCloseIcon = true;

    public ContextPanel() {
      super();
      initPanel();
    }

    public ContextPanel(boolean showCloseIcon) {
      super();
      this.showCloseIcon = showCloseIcon;
      initPanel();
    }

    public void setTitle(String title) {
      titleText.setText(title);
    }

    public void setContent(JPanel content) {
      contentPanel.add(content, BorderLayout.CENTER);
    }

    public Color getPanelColor() {
      return BORDER_COLOR;
    }

    public Color getTopColor() {
      return TOP_COLOR;
    }

    public MapOverlay getOverlay() {
      return overlay;
    }

    public void setOverlay(MapOverlay overlay) {
      this.overlay = overlay;
    }

    // override to deal with transparency issues when background color has alpha
    // value
    @Override
    protected void paintComponent(Graphics g) {
      g.setColor(getBackground());
      g.fillRect(0, 0, getWidth(), getHeight());
      super.paintComponent(g);
    }

    private void initPanel() {

      // title for panel
      titleText = new JLabel();
      titleText.setHorizontalAlignment(SwingConstants.LEFT);
      titleText
          .setFont(new Font(titleText.getFont().getName(), Font.PLAIN, 12));
      titleText.setForeground(Color.WHITE);
      titleText.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10));

      JPanel topPanel = new JPanel();
      topPanel.setBackground(TOP_COLOR);
      topPanel.setLayout(new BorderLayout(0, 0));
      topPanel.add(titleText, BorderLayout.CENTER);

      // create close/hide panel button
      if (showCloseIcon) {
        loadCloseIcon();
        JButton close = new JButton(closeIcon);
        close.setBackground(BORDER_COLOR);
        close.setSize(closeIcon.getIconWidth(), closeIcon.getIconHeight());
        close.setBorder(null);
        close.setToolTipText("Close");
        close.setRolloverIcon(new ImageIcon(getClass().getResource(
            "/com/esri/client/toolkit/images/close_select.png")));
        close.setRolloverEnabled(true);
        close.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            setVisible(false);
          }
        });
        topPanel.add(close, BorderLayout.EAST);
      }

      // group the above UI items into a panel
      setLayout(new BorderLayout());
      setLocation(10, 10); // set a default location
      setSize(defaultWidth, defaultHeight);

      contentPanel = new JPanel();
      contentPanel.setLayout(new BorderLayout());
      add(topPanel, BorderLayout.NORTH);
      add(contentPanel, BorderLayout.CENTER);
      setOpaque(false);
      setBackground(new Color(0, 0, 0, 0));
      setBorder(new LineBorder(BORDER_COLOR, 5, false));
      setVisible(false);

      // make the result panel draggable using out component dragger
      ComponentDragger componentDragger = new ComponentDragger(this);
      addMouseMotionListener(componentDragger);
      addMouseListener(componentDragger);

      applyComponentOrientation(ComponentOrientation.getOrientation(Locale
          .getDefault()));
    }

    private void loadCloseIcon() {
      closeIcon = new ImageIcon(getClass().getResource(
          "/com/esri/client/toolkit/images/close.png"));
    }
  }

  class FeaturePrototype {
    String text;
    int value;
    ImageIcon icon;

    public FeaturePrototype(String name, int value, ImageIcon icon) {
      this.text = name;
      this.value = value;
      this.icon = icon;
    }

    public String getText() {
      return text;
    }

    public int getValue() {
      return value;
    }

    public ImageIcon getIcon() {
      return icon;
    }
  }

  class CellRenderer extends JLabel implements ListCellRenderer<Object> {

    private static final long serialVersionUID = 1L;

    boolean showIcon = false;
    boolean showText = false;

    public CellRenderer(boolean showText, boolean showIcon) {
      this.showText = showText;
      this.showIcon = showIcon;
      setOpaque(true);
    }

    @Override
    public Component getListCellRendererComponent(JList<?> list, Object value,
        int index, boolean isSelected, boolean cellHasFocus) {
      if (isSelected) {
        setBackground(list.getSelectionBackground());
        setForeground(list.getSelectionForeground());
      } else {
        setBackground(list.getBackground());
        setForeground(list.getForeground());
      }

      setFont(list.getFont());

      if (showText) {
        setText(((FeaturePrototype) value).getText());
      }

      if (showIcon) {
        setIcon(((FeaturePrototype) value).getIcon());
      }

      return this;
    }
  }
  
  private String wrap(String str) {
    // create a HTML string that wraps text when longer
    return "<html><p style='width:200px;'>" + str + "</html>";
  }
}
Feedback on this topic?