Package edu.caltech.csn.gwt.client

Source Code of edu.caltech.csn.gwt.client.CellMap$CellMapUiBinder

package edu.caltech.csn.gwt.client;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import com.github.gwtbootstrap.client.ui.Button;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.AttachEvent.Handler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.event.MapMoveEndHandler;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.geom.LatLngBounds;
import com.google.gwt.maps.client.overlay.Polygon;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Widget;

import edu.caltech.csn.geocell.Bounds;
import edu.caltech.csn.geocell.GeocellLibrary;
import edu.caltech.csn.geocell.Point;

/**
* This class is used to generate a Google Map which it layers with geocells
* of the resolution specified by the provided list box. This class is
* intended as a visualization tool for understanding the size of geocells at
* different resolutions and zoom lengths.
*
* @author Michael Olson <michael.olson@gmail.com>
*/
// CSOFF: MagicNumber - Liberal use of formatting / default preference numbers.
public class CellMap extends Composite implements MapMoveEndHandler,
    Handler, ResizeHandler {

  private static CellMapUiBinder uiBinder = GWT
      .create(CellMapUiBinder.class);

  interface CellMapUiBinder extends UiBinder<Widget, CellMap> {
  }

  @UiField(provided = true)
  protected MapWidget map;

  @UiField
  protected ListBox res;

  @UiField
  protected Button freeze;
  @UiField
  protected Button unfreeze;

  private final List<GwtGeocell> geocells;
  private int resolution;
  private boolean labelsEnabled = true;
  private int freezeZoom;
  private Polygon frozenBounds = null;
  private boolean disableUpdates = true;

  /**
   * This class instantiates the map with all of its default parameters.
   * Adjustments to these parameters are then made with the use of history
   * tokens.
   */
  public CellMap() {
    // Center new map on Caltech (for now).
    // #TODO: Should eventually center on a wider view of SoCal/user region
    map = new MapWidget(LatLng.newInstance(34.139719, -118.123847), 13);
    map.setSize("100%", "100%");
    map.setUIToDefault();

    initWidget(uiBinder.createAndBindUi(this));

    geocells = new ArrayList<GwtGeocell>();

    this.res.setVisibleItemCount(1);
    this.res.addItem("Auto Resolution");
    for (int i = GeocellLibrary.MIN_RESOLUTION;
        i <= GeocellLibrary.MAX_RESOLUTION; i++) {
      this.res.addItem("Resolution " + Integer.toString(i));
    }

    // Set defaults.
    resolution = 0;
    this.res.setItemSelected(resolution, true);

    // TODO: Add status display for number of cells, min res, max res
    // bounded area, and total cell area.
    // TODO: Make Min/MaxUseful and StepSize modifiable by synchronized
    // library function and add boxes for changing those values.
    // TODO: Add dropdown to select output format: no labels, binary, hex,
    // GeoModel, Geohash (fallback to Geostring), Geostring, long.
    // TODO: Add infobox to cells that tells bounds, area, resolution, and
    // representations in alternative formats.

    setUnfreezeActive();

    map.addMapMoveEndHandler(this);
    Window.addResizeHandler(this);
    this.addAttachHandler(this);
  }

  /**
   * This function is called whenever the map stops moving. We do not
   * attempt to keep the geocells updated while the map is moving, but,
   * instead, when the map stops moving, clear existing geocells and
   * recalculate the geocells for the viewable map area. Because this
   * calculation is extremely fast for modest resolutions, we are not
   * attempting to preserve previously calculated geocells.
   *
   * @see com.google.gwt.maps.client.event.MapMoveEndHandler#onMoveEnd
   */
  public void onMoveEnd(final MapMoveEndEvent event) {
    // Halt event processing while updating member variables.
    if (disableUpdates) {
      return;
    }
    if (frozenBounds != null) {
      // Recalculate text overlays if zoom was used.
      if (freezeZoom != map.getZoomLevel()) {
        for (GwtGeocell g: geocells) {
          map.removeOverlay(g.getLabel());
          map.addOverlay(g.getLabel(true));
        }
        freezeZoom = map.getZoomLevel();
      }
    } else {
      final LatLngBounds bounds = map.getBounds();
      recalculateCells(bounds);
    }
    updateToken();
  }

  private void recalculateCells(final LatLngBounds bounds) {
    // Start fresh.
    clearCells();

    if (resolution == 0) {
      // Sort getQueryCells result first for predictable insertion pattern.
      final Set<GwtGeocell> results = new TreeSet<GwtGeocell>(getQueryCells(
          bounds.getSouthWest(), bounds.getNorthEast()));
      for (GwtGeocell g : results) {
        addCell(g);
      }
    } else {
      final GwtGeocell northEast = new GwtGeocell(bounds.getNorthEast(), resolution);
      final GwtGeocell southWest = new GwtGeocell(bounds.getSouthWest(), resolution);
      final GwtGeocell northWest = new GwtGeocell(
          LatLng.newInstance(bounds.getNorthEast().getLatitude(),
              bounds.getSouthWest().getLongitude()), resolution);

      // There's always at least one geocell, and if there is only one,
      // using the geocell from the northwest corner is as good as the
      // geocell from any other corner.
      int width = 1;
      addCell(northWest);

      // If the northwest corner and northeast corner are in separate
      // geocells, move east until we enter the geocell that the northeast
      // corner occupies. Use this movement to determine the width (in
      // geocells) of the grid for the current map view.
      if (!northWest.equals(northEast) ||
          // If far western and far eastern geocells are both visible,
          // they are the same, and no intermediate cells would be
          // included without this additional condition.
          !containsCenterLongitude(bounds, northWest)) {
        final GwtGeocell currCell = new GwtGeocell(northWest);
        long target = northEast.getGeocell();
        // Reset the target if we wrapped around the world.
        if (northWest.equals(northEast)) {
          target = GeocellLibrary.getWest(northEast.getGeocell());
        }

        while (currCell.getGeocell() != target) {
          currCell.moveEast();
          addCell(new GwtGeocell(currCell));
          width++;
          // TODO: Make this check more efficient and configurable.
          if (width > 15) {
            clearCells();
            return;
          }
        }
      }

      // If the northwest corner and southwest corner are in separate
      // geocells, move south until we enter the geocell that the
      // southwest corner occupies. Use this movement to determine the
      // height (in geocells) of the grid for the current map view.
      if (!northWest.equals(southWest)) {
        final GwtGeocell currCell = new GwtGeocell(northWest);
        while (!currCell.equals(southWest)) {
          currCell.moveSouth();
          addCell(new GwtGeocell(currCell));

          // If we previously determined the width of the grid is greater
          // than 1 (that is, the northwest and northeast geocells are
          // not the same), then for each row collect all cells in the
          // row by moving east width number of times.
          if (width > 1) {
            final GwtGeocell acrossCell = new GwtGeocell(currCell);
            for (int i = 1; i < width; i++) {
              acrossCell.moveEast();
              addCell(new GwtGeocell(acrossCell));
            }
          }
        }
      }
    }
  }

  private static boolean containsCenterLongitude(final LatLngBounds boxBounds,
      final GwtGeocell northWest) {
    final Bounds bounds = northWest.getBounds();
    final Point centerTop = new Point(boxBounds.getNorthEast().getLatitude(),
        bounds.getCenter().getLongitude());
    return bounds.contains(centerTop);
  }

  private static Set<GwtGeocell> getQueryCells(final LatLng southWest, final LatLng northEast) {
    return GeocellLibrary.getQueryCells(
        southWest.getLatitude(), southWest.getLongitude(),
        northEast.getLatitude(), northEast.getLongitude(),
        new GwtGeocellConv());
  }

  /**
   * Centralize actions to perform when adding a cell, such as keeping the
   * array up to date and incrementally adding them to the map (rather than
   * all at once at the end).
   *
   * @param cell the cell that should be added to the map
   */
  public void addCell(final GwtGeocell cell) {
    geocells.add(cell);
    map.addOverlay(cell.getPolygon());
    if (labelsEnabled) {
      map.addOverlay(cell.getLabel());
    }
  }

  /**
   * Operations to perform when clearing the cells from the map.
   */
  public void clearCells() {
    for (GwtGeocell g : geocells) {
      map.removeOverlay(g.getPolygon());
      if (g.hasLabel()) {
        map.removeOverlay(g.getLabel());
      }
    }
    geocells.clear();
  }

  /**
   * In this class, onChange(ChangeEvent event) is only called when the
   * resolution is modified. Check the current value of the resolution and
   * call the onMoveEnd handler.
   */
  @UiHandler("res")
  public void resChange(final ChangeEvent event) {
    resolution = res.getSelectedIndex();
    // Unfreeze when automatic selection value changes.
    if (resolution == 0 && frozenBounds != null) {
      handleUnfreeze(null);
    }
    GWT.log("Res changed to: " + resolution);
    onMoveEnd(null);
  }

  @UiHandler("freeze")
  public void handleFreeze(final ClickEvent event) {
    updateFreeze(map.getBounds());
    setFreezeActive();
  }

  @UiHandler("unfreeze")
  public void handleUnfreeze(final ClickEvent event) {
    if (frozenBounds != null) {
      map.removeOverlay(frozenBounds);
      frozenBounds = null;
    }
    onMoveEnd(null);
    setUnfreezeActive();
  }

  private void setUnfreezeActive() {
    freeze.removeStyleName("active");
    unfreeze.addStyleName("active");
  }

  private void setFreezeActive() {
    unfreeze.removeStyleName("active");
    freeze.addStyleName("active");
  }

  private void updateFreeze(final LatLngBounds bounds) {
    freezeZoom = map.getZoomLevel();
    final LatLng sw = bounds.getSouthWest();
    final LatLng ne = bounds.getNorthEast();
    final LatLng[] polyPoints = {
        LatLng.newInstance(ne.getLatitude(), sw.getLongitude()),
        ne,
        LatLng.newInstance(sw.getLatitude(), ne.getLongitude()),
        sw,
        LatLng.newInstance(ne.getLatitude(), sw.getLongitude())
    };

    frozenBounds = new Polygon(polyPoints, "#0000FF", 3, .75, "#3333cc", 0);
    map.addOverlay(frozenBounds);
    updateToken();
  }

  @Override
  public void onAttachOrDetach(final AttachEvent event) {
    if (event.isAttached()) {
      GWT.log("CellMap attached; calculating cells.");
      // Force frozen labels to be recalculated if they were added
      // before the map was attached.
      disableUpdates = false;
      freezeZoom = -1;
      onMoveEnd(null);
    } else {
      GWT.log("CellMap detached; disabling updates.");
      disableUpdates = true;
    }
  }

  private void updateToken() {
    final String currToken = getToken();
    if (!History.getToken().equals(currToken)) {
      History.newItem(currToken, false);
    }
  }

  protected String getToken() {
    final StringBuffer token = new StringBuffer();
    token.append("mc=").append(map.getCenter().toUrlValue()).append('&');
    token.append("mz=").append(map.getZoomLevel()).append('&');
    if (frozenBounds != null) {
      token.append("fr=").append(frozenBounds.getVertex(3).toUrlValue());
      token.append('+').append(frozenBounds.getVertex(1).toUrlValue()).append('&');
    }
    token.append("r=").append(res.getSelectedIndex());
    return token.toString();
  }

  protected void fromToken(final Map<String, String> vars) {
    LatLng mc = null;
    int mz;
    LatLng sw = null;
    LatLng ne = null;
    int r = 0;

    try {
      mc = LatLng.fromUrlValue(vars.get("mc"));
      mz = Integer.parseInt(vars.get("mz"));
      final String fr = vars.get("fr");
      if (fr != null) {
        final String[] coords = fr.split("\\+");
        sw = LatLng.fromUrlValue(coords[0]);
        ne = LatLng.fromUrlValue(coords[1]);
      }
      r = Integer.parseInt(vars.get("r"));
    } catch (final Exception e) {
      return;
    }

    disableUpdates = true;
    map.setCenter(mc);
    map.setZoomLevel(mz);
    disableUpdates = false;

    resolution = r;
    res.setItemSelected(r, true);
    final LatLngBounds bounds;
    if (sw != null) {
      bounds = LatLngBounds.newInstance(sw, ne);
      freezeZoom = -1;
      updateFreeze(bounds);
      setFreezeActive();
    } else {
      bounds = map.getBounds();
      handleUnfreeze(null);
    }
    recalculateCells(bounds);
  }

  @Override
  public void onResize(final ResizeEvent event) {
    onMoveEnd(null);
  }
}
TOP

Related Classes of edu.caltech.csn.gwt.client.CellMap$CellMapUiBinder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.