コード例 #1
0
  @Override
  public void run() {
    while (true) {

      synchronized (lastestPosition) {
        currentPosition.set(lastestPosition);
      }

      // obtain the coordiante in geographic crs
      final ReadOnlyVector3 corrected = container.correctLocation(currentPosition);

      for (Double key : sensitives.keySet()) {
        final Object[] combination = sensitives.get(key);
        final Vector3 vect = (Vector3) combination[0];
        final List<LocationSensitiveGraphic> graphics =
            (List<LocationSensitiveGraphic>) combination[1];

        if (vect.distance(currentPosition) > key) {
          vect.set(currentPosition);
          for (LocationSensitiveGraphic gra : graphics) {
            gra.update(corrected);
          }
        }
      }

      try {
        // we dont need to consume much cpu
        sleep(100);
      } catch (InterruptedException ex) {
        Logging.getLogger(LocationSensitiveUpdater.class).log(Level.WARNING, null, ex);
      }
    }
  }
コード例 #2
0
 /**
  * setUp Called before each test.
  *
  * @throws FactoryException
  */
 static {
   CRSFactory crsFact = AuthorityFactoryFinder.getCRSFactory(null);
   try {
     crs = crsFact.createFromWKT(WGS84_WKT);
   } catch (FactoryException ex) {
     Logging.getLogger(AbstractGeometryTest.class).log(Level.WARNING, null, ex);
   }
   GEOMETRY_FACTORY = new JTSGeometryFactory(crs);
   PRIMITIVE_FACTORY = new JTSPrimitiveFactory(crs);
 }
コード例 #3
0
/**
 * Informations relative to a rendering in progress. A {@code RenderingContext} instance is created
 * by {@link J2DCanvas} at rendering time, which iterates over all graphic objects and invokes the
 * rendering process for each of them. The rendering context is disposed once the rendering is
 * completed. {@code RenderingContext} instances contain the following informations:
 *
 * <p>
 *
 * <ul>
 *   <li>The {@link Graphics2D} handler to use for rendering.
 *   <li>The coordinate reference systems in use and the transformations between them.
 *   <li>The area rendered up to date. This information shall be updated by each {@link GraphicJ2D}
 *       while they are painting.
 *   <li>The map scale.
 * </ul>
 *
 * <p>A rendering usually implies the following transformations (names are {@linkplain
 * CoordinateReferenceSystem coordinate reference systems} and arrows are {@linkplain MathTransform
 * transforms}):
 *
 * <p align="center">&nbsp; {@code graphicCRS} &nbsp; <img src="doc-files/right.png"> &nbsp; {@link
 * #objectiveCRS} &nbsp; <img src="doc-files/right.png"> &nbsp; {@link #displayCRS} &nbsp; <img
 * src="doc-files/right.png"> &nbsp; {@code deviceCRS}
 *
 * @module pending
 * @since 2.3
 * @version $Id$
 * @author Martin Desruisseaux (IRD)
 * @author Johann Sorel (Geomatys)
 */
public final class DefaultRenderingContext2D implements RenderingContext2D {

  private static final Logger LOGGER = Logging.getLogger(DefaultRenderingContext2D.class);
  private static Map<Font, FontMetrics> fontMetrics = new HashMap<Font, FontMetrics>();

  private static final int DISPLAY_TRS = 0;
  private static final int OBJECTIVE_TRS = 1;
  private static final int OTHER_TRS = 2;
  private int current = DISPLAY_TRS;

  /** The originating canvas. */
  private final J2DCanvas canvas;

  /**
   * The graphics handle to use for painting. This graphics is set by {@link BufferedCanvas2D} when
   * a new painting in underway. It is reset to {@code null} once the rendering is finished.
   *
   * @see #getGraphics
   */
  private Graphics2D graphics = null;

  /*
   * cache of the Graphics2D rendering hints.
   */
  private RenderingHints renderingHints = null;

  private double dpi = 90;

  /**
   * A snapshot of {@link ReferencedCanvas#getObjectiveCRS} at the time of painting. This is the
   * "real world" coordinate reference system that the user will see on the screen. Data from all
   * {@link GraphicPrimitive2D} must be transformed to this CRS before to be painted. Units are
   * usually "real world" metres.
   *
   * <p>This coordinate system is usually set once for a given {@link BufferedCanvas2D} and do not
   * change anymore, except if the user wants to change the projection see on screen.
   *
   * @see #displayCRS
   * @see #setGraphicsCRS
   * @see ReferencedCanvas#getObjectiveCRS
   */
  private CoordinateReferenceSystem objectiveCRS = null;

  private CoordinateReferenceSystem objectiveCRS2D = null;

  /**
   * A snapshot of {@link ReferencedCanvas#getDisplayCRS} at the time of painting. This CRS maps the
   * {@linkplain Graphics2D user space} in terms of <cite>Java2D</cite>: each "unit" is a dot (about
   * 1/72 of inch). <var>x</var> values increase toward the right of the screen and <var>y</var>
   * values increase toward the bottom of the screen. This CRS is appropriate for rendering text and
   * labels.
   *
   * <p>This coordinate system may be different between two different renderings, especially if the
   * zoom (or map scale) has changed since the last rendering.
   *
   * @see #objectiveCRS
   * @see #setGraphicsCRS
   * @see ReferencedCanvas#getDisplayCRS
   */
  private CoordinateReferenceSystem displayCRS = null;

  private CanvasMonitor monitor = null;

  private AffineTransform2D objectiveToDisplay = null;
  private AffineTransform2D displayToObjective = null;

  /**
   * The affine transform from {@link #objectiveCRS} to {@code deviceCRS}. Used by {@link
   * #setGraphicsCRS} when the CRS is {@link #objectiveCRS}. This is a pretty common case, and
   * unfortunatly one that is badly optimized by {@link ReferencedCanvas#getMathTransform}.
   */
  private AffineTransform objectiveToDevice = null;

  /**
   * The affine transform from {@link #displayCRS} to {@code deviceCRS}. Used by {@link
   * #setGraphicsCRS} when the CRS is {@link #displayCRS}.
   */
  private AffineTransform displayToDevice = null;

  /** The label renderer. Shall be created only once. */
  private LabelRenderer labelRenderer = null;

  /** List of coefficients from "Unit" to Objective CRS. */
  private final Map<Unit<Length>, Float> coeffs = new IdentityHashMap<Unit<Length>, Float>();

  /** Precalculated resolution, avoid graphics to recalculate it since */
  private double[] resolution;

  /** Precalculated geographic scale, avoid graphics to recalculate it. */
  private double geoScale = 1;

  /**
   * Precaculated geographic scale calculated using OGC Symbology Encoding Specification. This is
   * not the scale Objective to Display. This is not an accurate geographic scale. This is a fake
   * average scale unproper for correct rendering. It is used only to filter SE rules.
   */
  private double seScale = 1;

  private final Date[] temporalRange = new Date[2];
  private final Double[] elevationRange = new Double[2];

  private Shape paintingDisplayShape = null;
  private Rectangle paintingDisplaybounds = null;
  private Shape paintingObjectiveShape = null;
  private Envelope paintingObjectiveBBox = null;
  private Envelope paintingObjectiveBBox2D = null;

  private Shape canvasDisplayShape = null;
  private Rectangle canvasDisplaybounds = null;
  private Shape canvasObjectiveShape = null;
  private Envelope canvasObjectiveBBox = null;
  private Envelope canvasObjectiveBBox2D = null;

  /**
   * Constructs a new {@code RenderingContext} for the specified canvas.
   *
   * @param canvas The canvas which creates this rendering context.
   */
  public DefaultRenderingContext2D(final J2DCanvas canvas) {
    this.canvas = canvas;
  }

  public void initParameters(
      final AffineTransform2D objToDisp,
      final CanvasMonitor monitor,
      final Shape paintingDisplayShape,
      final Shape paintingObjectiveShape,
      final Shape canvasDisplayShape,
      final Shape canvasObjectiveShape,
      final double dpi) {
    this.canvasObjectiveBBox = canvas.getController().getVisibleEnvelope();
    this.objectiveCRS = canvasObjectiveBBox.getCoordinateReferenceSystem();
    this.objectiveCRS2D = canvas.getObjectiveCRS2D();
    this.displayCRS = canvas.getDisplayCRS();
    this.objectiveToDisplay = objToDisp;
    try {
      this.displayToObjective = (AffineTransform2D) objToDisp.inverse();
    } catch (NoninvertibleTransformException ex) {
      Logging.getLogger(DefaultRenderingContext2D.class).log(Level.WARNING, null, ex);
    }
    this.monitor = monitor;

    this.labelRenderer = null;

    this.coeffs.clear();
    // set the Pixel coeff = 1
    this.coeffs.put(NonSI.PIXEL, 1f);

    // calculate canvas shape/bounds values ---------------------------------
    this.canvasDisplayShape = canvasDisplayShape;
    final Rectangle2D canvasDisplayBounds = canvasDisplayShape.getBounds2D();
    this.canvasDisplaybounds = canvasDisplayBounds.getBounds();
    this.canvasObjectiveShape = canvasObjectiveShape;

    final Rectangle2D canvasObjectiveBounds = canvasObjectiveShape.getBounds2D();

    // calculate the objective bbox with there temporal and elevation parameters ----
    this.canvasObjectiveBBox2D = new Envelope2D(objectiveCRS2D, canvasObjectiveBounds);

    // calculate the resolution -----------------------------------------------
    this.dpi = dpi;
    this.resolution = new double[canvasObjectiveBBox.getDimension()];
    this.resolution[0] = canvasObjectiveBounds.getWidth() / canvasDisplayBounds.getWidth();
    this.resolution[1] = canvasObjectiveBounds.getHeight() / canvasDisplayBounds.getHeight();
    for (int i = 2; i < resolution.length; i++) {
      // other dimension are likely to be the temporal and elevation one.
      // we set a hug resolution to ensure that only one slice of data will be retrived.
      resolution[i] = Double.MAX_VALUE;
    }
    adjustResolutionWithDPI(resolution);

    // calculate painting shape/bounds values -------------------------------
    this.paintingDisplayShape = paintingDisplayShape;
    final Rectangle2D paintingDisplayBounds = paintingDisplayShape.getBounds2D();
    this.paintingDisplaybounds = paintingDisplayBounds.getBounds();
    this.paintingObjectiveShape = paintingObjectiveShape;

    final Rectangle2D paintingObjectiveBounds = paintingObjectiveShape.getBounds2D();
    this.paintingObjectiveBBox2D = new Envelope2D(objectiveCRS2D, paintingObjectiveBounds);
    this.paintingObjectiveBBox = new GeneralEnvelope(canvasObjectiveBBox);
    ((GeneralEnvelope) this.paintingObjectiveBBox)
        .setRange(0, paintingObjectiveBounds.getMinX(), paintingObjectiveBounds.getMaxX());
    ((GeneralEnvelope) this.paintingObjectiveBBox)
        .setRange(1, paintingObjectiveBounds.getMinY(), paintingObjectiveBounds.getMaxY());

    try {
      geoScale = canvas.getController().getGeographicScale();
    } catch (TransformException ex) {
      // could not calculate the geographic scale.
      geoScale = 1;
      LOGGER.log(Level.WARNING, null, ex);
    }

    // set temporal and elevation range--------------------------------------
    final Date[] temporal = canvas.getController().getTemporalRange();
    if (temporal != null) {
      temporalRange[0] = temporal[0];
      temporalRange[1] = temporal[1];
    } else {
      Arrays.fill(temporalRange, null);
    }

    final Double[] elevation = canvas.getController().getElevationRange();
    if (elevation != null) {
      elevationRange[0] = elevation[0];
      elevationRange[1] = elevation[1];
    } else {
      Arrays.fill(elevationRange, null);
    }

    // calculate the symbology encoding scale -------------------------------
    seScale = GO2Utilities.computeSEScale(this);
  }

  public void initGraphic(final Graphics2D graphics) {
    this.graphics = graphics;
    this.renderingHints = graphics.getRenderingHints();
    this.displayToDevice = (graphics != null) ? graphics.getTransform() : null;
    this.objectiveToDevice =
        (displayToDevice != null) ? new AffineTransform(displayToDevice) : new AffineTransform();
    this.objectiveToDevice.concatenate(objectiveToDisplay);
    this.current = DISPLAY_TRS;
  }

  public void reset() {
    this.coeffs.clear();
    this.canvasDisplaybounds = null;
    this.displayCRS = null;
    this.canvasDisplayShape = null;
    this.displayToDevice = null;
    this.graphics = null;
    this.renderingHints = null;
    this.labelRenderer = null;
    this.monitor = null;
    this.canvasObjectiveBBox = null;
    this.objectiveCRS = null;
    this.canvasObjectiveShape = null;
    this.objectiveToDevice = null;
    this.objectiveToDisplay = null;
    this.resolution = null;
    this.current = DISPLAY_TRS;
  }

  public void dispose() {
    if (graphics != null) {
      graphics.dispose();
    }
    reset();
  }

  /** {@inheritDoc } */
  @Override
  public J2DCanvas getCanvas() {
    return canvas;
  }

  /** {@inheritDoc } */
  @Override
  public CoordinateReferenceSystem getObjectiveCRS() {
    return objectiveCRS;
  }

  /** {@inheritDoc } */
  @Override
  public CoordinateReferenceSystem getObjectiveCRS2D() {
    return objectiveCRS2D;
  }

  /** {@inheritDoc } */
  @Override
  public CoordinateReferenceSystem getDisplayCRS() {
    return displayCRS;
  }

  /** {@inheritDoc } */
  @Override
  public final Graphics2D getGraphics() {
    return graphics;
  }

  /** {@inheritDoc } */
  @Override
  public void switchToDisplayCRS() {
    if (current != DISPLAY_TRS) {
      graphics.setTransform(displayToDevice);
      current = DISPLAY_TRS;
    }
  }

  /** {@inheritDoc } */
  @Override
  public void switchToObjectiveCRS() {
    if (current != OBJECTIVE_TRS) {
      graphics.setTransform(objectiveToDevice);
      current = OBJECTIVE_TRS;
    }
  }

  /** {@inheritDoc } */
  @Override
  public void setGraphicsCRS(CoordinateReferenceSystem crs) throws TransformException {

    if (crs == displayCRS) {
      switchToDisplayCRS();
    } else if (crs == objectiveCRS || crs == objectiveCRS2D) {
      switchToObjectiveCRS();
    } else
      try {
        crs = CRSUtilities.getCRS2D(crs);
        AffineTransform at = getAffineTransform(crs, displayCRS);
        at.preConcatenate(displayToDevice);
        current = OTHER_TRS;
        graphics.setTransform(at);
      } catch (FactoryException e) {
        throw new TransformException(
            Errors.format(Errors.Keys.ILLEGAL_COORDINATE_REFERENCE_SYSTEM), e);
      }
  }

  /** {@inheritDoc } */
  @Override
  public AffineTransform getAffineTransform(
      final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS)
      throws FactoryException {
    final MathTransform mt =
        canvas.getMathTransform(
            sourceCRS, targetCRS, DefaultRenderingContext2D.class, "getAffineTransform");
    try {
      return (AffineTransform) mt;
    } catch (ClassCastException cause) {
      throw new FactoryException(Errors.format(Errors.Keys.NOT_AN_AFFINE_TRANSFORM), cause);
    }
  }

  /** {@inheritDoc } */
  @Override
  public MathTransform getMathTransform(
      final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS)
      throws FactoryException {
    return canvas.getMathTransform(
        sourceCRS, targetCRS, DefaultRenderingContext2D.class, "getMathTransform");
  }

  /** {@inheritDoc } */
  @Override
  public RenderingContext2D create(final Graphics2D g2d) {
    final DefaultRenderingContext2D context = new DefaultRenderingContext2D(canvas);
    context.initParameters(
        objectiveToDisplay,
        monitor,
        paintingDisplayShape,
        paintingObjectiveShape,
        canvasDisplayShape,
        canvasObjectiveShape,
        dpi);
    context.initGraphic(g2d);
    g2d.setRenderingHints(this.graphics.getRenderingHints());
    context.labelRenderer = getLabelRenderer(true);
    return context;
  }

  /** {@inheritDoc } */
  @Override
  public LabelRenderer getLabelRenderer(final boolean create) {
    if (labelRenderer == null && create) {
      Class candidate = (Class) canvas.getRenderingHint(GO2Hints.KEY_LABEL_RENDERER_CLASS);

      if (candidate != null && LabelRenderer.class.isAssignableFrom(candidate)) {
        try {
          labelRenderer = (LabelRenderer) candidate.newInstance();
          labelRenderer.setRenderingContext(this);
        } catch (InstantiationException ex) {
          LOGGER.log(Level.WARNING, null, ex);
        } catch (IllegalAccessException ex) {
          LOGGER.log(Level.WARNING, null, ex);
        }
      } else {
        labelRenderer = new DecimationLabelRenderer();
        labelRenderer.setRenderingContext(this);
      }
    }
    return labelRenderer;
  }

  /** {@inheritDoc } */
  @Override
  public CanvasMonitor getMonitor() {
    return monitor;
  }

  // Informations related to scale datas -------------------------------------
  /** {@inheritDoc } */
  @Override
  public float getUnitCoefficient(final Unit<Length> uom) {
    Float f = coeffs.get(uom);
    if (f == null) {
      f = GO2Utilities.calculateScaleCoefficient(this, uom);
      coeffs.put(uom, f);
    }

    return f;
  }

  public double getDPI() {
    return dpi;
  }

  /** {@inheritDoc } */
  @Override
  public double[] getResolution() {
    return resolution.clone();
  }

  /** {@inheritDoc } */
  @Override
  public double[] getResolution(final CoordinateReferenceSystem crs) {
    if (CRS.equalsIgnoreMetadata(objectiveCRS, crs)) {
      return getResolution();
    } else {
      final double[] res = new double[crs.getCoordinateSystem().getDimension()];

      final Envelope env;
      try {
        env = CRS.transform(canvasObjectiveBBox2D, crs);
        final Rectangle2D canvasCRSBounds =
            new Rectangle2D.Double(0, 0, env.getSpan(0), env.getSpan(1));
        res[0] = Math.abs(canvasCRSBounds.getWidth() / canvasDisplaybounds.getWidth());
        res[1] = Math.abs(canvasCRSBounds.getHeight() / canvasDisplaybounds.getHeight());
        for (int i = 2; i < res.length; i++) {
          // other dimension are likely to be the temporal and elevation one.
          // we set a hug resolution to ensure that only one slice of data will be retrived.
          res[i] = Double.MAX_VALUE;
        }
      } catch (TransformException ex) {
        LOGGER.log(Level.WARNING, null, ex);
      } catch (IllegalArgumentException ex) {
        LOGGER.log(Level.WARNING, null, ex);
      } catch (Exception ex) {
        LOGGER.log(Level.WARNING, null, ex);
      }

      return adjustResolutionWithDPI(res);
    }
  }

  /**
   * Adjust the resolution relative to 90 DPI. a dpi under 90 with raise the resolution level while
   * a bigger spi will lower the resolution level.
   */
  private double[] adjustResolutionWithDPI(final double[] res) {
    res[0] = (90 / dpi) * res[0];
    res[1] = (90 / dpi) * res[1];
    return res;
  }

  /** {@inheritDoc } */
  @Override
  public double getScale() {
    return canvas.getController().getScale();
  }

  /** {@inheritDoc } */
  @Override
  public double getGeographicScale() {
    return geoScale;
  }

  /** {@inheritDoc } */
  @Override
  public double getSEScale() {
    return seScale;
  }

  // Informations about the currently painted area ---------------------------
  /** {@inheritDoc } */
  @Override
  public Shape getPaintingDisplayShape() {
    return paintingDisplayShape;
  }

  /** {@inheritDoc } */
  @Override
  public Rectangle getPaintingDisplayBounds() {
    return paintingDisplaybounds;
  }

  /** {@inheritDoc } */
  @Override
  public Shape getPaintingObjectiveShape() {
    return paintingObjectiveShape;
  }

  /** {@inheritDoc } */
  @Override
  public BoundingBox getPaintingObjectiveBounds2D() {
    return new DefaultBoundingBox(paintingObjectiveBBox2D);
  }

  /** {@inheritDoc } */
  @Override
  public Envelope getPaintingObjectiveBounds() {
    return paintingObjectiveBBox;
  }

  // Informations about the complete canvas area -----------------------------
  /** {@inheritDoc } */
  @Override
  public Shape getCanvasDisplayShape() {
    return canvasDisplayShape;
  }

  /** {@inheritDoc } */
  @Override
  public Rectangle getCanvasDisplayBounds() {
    return canvasDisplaybounds;
  }

  /** {@inheritDoc } */
  @Override
  public Shape getCanvasObjectiveShape() {
    return canvasObjectiveShape;
  }

  /** {@inheritDoc } */
  @Override
  public BoundingBox getCanvasObjectiveBounds2D() {
    return new DefaultBoundingBox(canvasObjectiveBBox2D);
  }

  /** {@inheritDoc } */
  @Override
  public Envelope getCanvasObjectiveBounds() {
    return canvasObjectiveBBox;
  }

  @Override
  public AffineTransform2D getObjectiveToDisplay() {
    return objectiveToDisplay;
  }

  @Override
  public AffineTransform2D getDisplayToObjective() {
    return displayToObjective;
  }

  @Override
  public Date[] getTemporalRange() {
    return temporalRange;
  }

  @Override
  public Double[] getElevationRange() {
    return elevationRange;
  }

  @Override
  public RenderingHints getRenderingHints() {
    return renderingHints;
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();
    sb.append("========== Rendering Context 2D ==========\n");

    sb.append("---------- Coordinate Reference Systems ----------\n");
    sb.append("Objective CRS = \n");
    sb.append(objectiveCRS).append("\n");
    sb.append("Objective CRS 2D = \n");
    sb.append(objectiveCRS2D).append("\n");
    sb.append("Display CRS = \n");
    sb.append(displayCRS).append("\n");

    if (resolution != null) {
      sb.append("Resolution = ");
      for (double d : resolution) {
        sb.append(d).append("   ");
      }
    }

    sb.append("\n");
    sb.append("Geographic Scale = ");
    sb.append(geoScale).append("\n");
    sb.append("OGC SE Scale = ");
    sb.append(seScale).append("\n");
    sb.append("Temporal range = ");
    sb.append(temporalRange[0]).append("  to  ").append(temporalRange[1]).append("\n");
    sb.append("Elevation range = ");
    sb.append(elevationRange[0]).append("  to  ").append(elevationRange[1]).append("\n");

    sb.append("\n---------- Canvas Geometries ----------\n");
    sb.append("Display Shape = \n");
    sb.append(canvasDisplayShape).append("\n");
    sb.append("Display Bounds = \n");
    sb.append(canvasDisplaybounds).append("\n");
    sb.append("Objective Shape = \n");
    sb.append(canvasObjectiveShape).append("\n");
    sb.append("Objective BBOX = \n");
    sb.append(canvasObjectiveBBox).append("\n");
    sb.append("Objective BBOX 2D = \n");
    sb.append(canvasObjectiveBBox2D).append("\n");

    sb.append("\n---------- Painting Geometries (dirty area) ----------\n");
    sb.append("Display Shape = \n");
    sb.append(paintingDisplayShape).append("\n");
    sb.append("Display Bounds = \n");
    sb.append(paintingDisplaybounds).append("\n");
    sb.append("Objective Shape = \n");
    sb.append(paintingObjectiveShape).append("\n");
    sb.append("Objective BBOX = \n");
    sb.append(paintingObjectiveBBox).append("\n");
    sb.append("Objective BBOX 2D = \n");
    sb.append(paintingObjectiveBBox2D).append("\n");

    sb.append("\n---------- Transforms ----------\n");
    sb.append("Objective to Display = \n");
    sb.append(objectiveToDisplay).append("\n");
    sb.append("Display to Objective = \n");
    sb.append(displayToObjective).append("\n");

    sb.append("\n---------- Rendering Hints ----------\n");
    if (renderingHints != null) {
      for (Entry<Object, Object> entry : renderingHints.entrySet()) {
        sb.append(entry.getKey()).append("=").append(entry.getValue()).append("\n");
      }
    }

    sb.append("========== Rendering Context 2D ==========\n");
    return sb.toString();
  }

  @Override
  public FontMetrics getFontMetrics(Font f) {
    FontMetrics fm = fontMetrics.get(f);
    if (fm == null) {
      fm = getGraphics().getFontMetrics(f);
      fontMetrics.put(f, fm);
    }
    return fm;
  }
}
コード例 #4
0
  public void initParameters(
      final AffineTransform2D objToDisp,
      final CanvasMonitor monitor,
      final Shape paintingDisplayShape,
      final Shape paintingObjectiveShape,
      final Shape canvasDisplayShape,
      final Shape canvasObjectiveShape,
      final double dpi) {
    this.canvasObjectiveBBox = canvas.getController().getVisibleEnvelope();
    this.objectiveCRS = canvasObjectiveBBox.getCoordinateReferenceSystem();
    this.objectiveCRS2D = canvas.getObjectiveCRS2D();
    this.displayCRS = canvas.getDisplayCRS();
    this.objectiveToDisplay = objToDisp;
    try {
      this.displayToObjective = (AffineTransform2D) objToDisp.inverse();
    } catch (NoninvertibleTransformException ex) {
      Logging.getLogger(DefaultRenderingContext2D.class).log(Level.WARNING, null, ex);
    }
    this.monitor = monitor;

    this.labelRenderer = null;

    this.coeffs.clear();
    // set the Pixel coeff = 1
    this.coeffs.put(NonSI.PIXEL, 1f);

    // calculate canvas shape/bounds values ---------------------------------
    this.canvasDisplayShape = canvasDisplayShape;
    final Rectangle2D canvasDisplayBounds = canvasDisplayShape.getBounds2D();
    this.canvasDisplaybounds = canvasDisplayBounds.getBounds();
    this.canvasObjectiveShape = canvasObjectiveShape;

    final Rectangle2D canvasObjectiveBounds = canvasObjectiveShape.getBounds2D();

    // calculate the objective bbox with there temporal and elevation parameters ----
    this.canvasObjectiveBBox2D = new Envelope2D(objectiveCRS2D, canvasObjectiveBounds);

    // calculate the resolution -----------------------------------------------
    this.dpi = dpi;
    this.resolution = new double[canvasObjectiveBBox.getDimension()];
    this.resolution[0] = canvasObjectiveBounds.getWidth() / canvasDisplayBounds.getWidth();
    this.resolution[1] = canvasObjectiveBounds.getHeight() / canvasDisplayBounds.getHeight();
    for (int i = 2; i < resolution.length; i++) {
      // other dimension are likely to be the temporal and elevation one.
      // we set a hug resolution to ensure that only one slice of data will be retrived.
      resolution[i] = Double.MAX_VALUE;
    }
    adjustResolutionWithDPI(resolution);

    // calculate painting shape/bounds values -------------------------------
    this.paintingDisplayShape = paintingDisplayShape;
    final Rectangle2D paintingDisplayBounds = paintingDisplayShape.getBounds2D();
    this.paintingDisplaybounds = paintingDisplayBounds.getBounds();
    this.paintingObjectiveShape = paintingObjectiveShape;

    final Rectangle2D paintingObjectiveBounds = paintingObjectiveShape.getBounds2D();
    this.paintingObjectiveBBox2D = new Envelope2D(objectiveCRS2D, paintingObjectiveBounds);
    this.paintingObjectiveBBox = new GeneralEnvelope(canvasObjectiveBBox);
    ((GeneralEnvelope) this.paintingObjectiveBBox)
        .setRange(0, paintingObjectiveBounds.getMinX(), paintingObjectiveBounds.getMaxX());
    ((GeneralEnvelope) this.paintingObjectiveBBox)
        .setRange(1, paintingObjectiveBounds.getMinY(), paintingObjectiveBounds.getMaxY());

    try {
      geoScale = canvas.getController().getGeographicScale();
    } catch (TransformException ex) {
      // could not calculate the geographic scale.
      geoScale = 1;
      LOGGER.log(Level.WARNING, null, ex);
    }

    // set temporal and elevation range--------------------------------------
    final Date[] temporal = canvas.getController().getTemporalRange();
    if (temporal != null) {
      temporalRange[0] = temporal[0];
      temporalRange[1] = temporal[1];
    } else {
      Arrays.fill(temporalRange, null);
    }

    final Double[] elevation = canvas.getController().getElevationRange();
    if (elevation != null) {
      elevationRange[0] = elevation[0];
      elevationRange[1] = elevation[1];
    } else {
      Arrays.fill(elevationRange, null);
    }

    // calculate the symbology encoding scale -------------------------------
    seScale = GO2Utilities.computeSEScale(this);
  }
コード例 #5
0
/** @author Guilhem Legal (Geomatys) */
public class ElementFeatureWriter {

  /** Logger for this writer. */
  protected static final Logger LOGGER = Logging.getLogger(JAXPStreamFeatureWriter.class);

  /** The pool of marshallers used for marshalling geometries. */
  private static final MarshallerPool POOL = JTSWrapperMarshallerPool.getInstance();

  /** Object factory to build a geometry. */
  private static final ObjectFactory OBJECT_FACTORY = new ObjectFactory();

  protected String schemaLocation;

  private int lastUnknowPrefix = 0;

  private final Map<String, String> unknowNamespaces = new HashMap<String, String>();

  public ElementFeatureWriter() {}

  public ElementFeatureWriter(final Map<String, String> schemaLocations) {

    if (schemaLocations != null && schemaLocations.size() > 0) {
      final StringBuilder sb = new StringBuilder();
      for (Entry<String, String> entry : schemaLocations.entrySet()) {
        sb.append(entry.getKey()).append(' ').append(entry.getValue()).append(' ');
      }
      if (sb.length() > 0) {
        sb.setLength(sb.length() - 1); // remove last ' '
      }
      schemaLocation = sb.toString();
    }
  }

  /** {@inheritDoc} */
  public Element write(final Object candidate, final boolean fragment)
      throws IOException, DataStoreException, ParserConfigurationException {

    if (candidate instanceof Feature) {
      return writeFeature((Feature) candidate, null, fragment);
    } else if (candidate instanceof FeatureCollection) {
      return writeFeatureCollection((FeatureCollection) candidate, fragment, true);
    } else {
      throw new IllegalArgumentException(
          "The given object is not a Feature or a" + " FeatureCollection: " + candidate);
    }
  }

  /**
   * Write the feature into the stream.
   *
   * @param feature The feature
   * @param root
   * @throws XMLStreamException
   */
  public Element writeFeature(final Feature feature, final Document rootDocument, boolean fragment)
      throws ParserConfigurationException {

    final Document document;
    if (rootDocument == null) {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      // then we have to create document-loader:
      factory.setNamespaceAware(false);
      DocumentBuilder loader = factory.newDocumentBuilder();

      // creating a new DOM-document...
      document = loader.newDocument();
    } else {
      document = rootDocument;
    }

    // the root element of the xml document (type of the feature)
    final FeatureType type = feature.getType();
    final Name typeName = type.getName();
    final String namespace = typeName.getNamespaceURI();
    final String localPart = typeName.getLocalPart();

    final Element rootElement;
    final Prefix prefix;
    if (namespace != null) {
      prefix = getPrefix(namespace);
      rootElement = document.createElementNS(namespace, localPart);
      rootElement.setPrefix(prefix.prefix);

    } else {
      rootElement = document.createElement(localPart);
      prefix = null;
    }
    // if main document set the xmlns
    if (!fragment) {
      rootElement.setAttributeNS(
          "http://www.w3.org/2000/xmlns/", "xmlns:gml", "http://www.opengis.net/gml");
    }
    final Attr idAttr = document.createAttributeNS(Namespaces.GML, "id");
    idAttr.setValue(feature.getIdentifier().getID());
    idAttr.setPrefix("gml");
    rootElement.setAttributeNodeNS(idAttr);

    if (rootDocument == null) {
      document.appendChild(rootElement);
    }
    // write properties in the type order
    for (final PropertyDescriptor desc : type.getDescriptors()) {
      final Collection<Property> props = feature.getProperties(desc.getName());
      for (Property a : props) {
        final Object valueA = a.getValue();
        final PropertyType typeA = a.getType();
        final Name nameA = a.getName();
        final String nameProperty = nameA.getLocalPart();
        String namespaceProperty = nameA.getNamespaceURI();
        if (valueA instanceof Collection && !(typeA instanceof GeometryType)) {
          for (Object value : (Collection) valueA) {
            final Element element;
            if (namespaceProperty != null) {
              element = document.createElementNS(namespaceProperty, nameProperty);
            } else {
              element = document.createElement(nameProperty);
            }
            element.setTextContent(Utils.getStringValue(value));
            if (prefix != null) {
              element.setPrefix(prefix.prefix);
            }
            rootElement.appendChild(element);
          }

        } else if (valueA instanceof Map && !(typeA instanceof GeometryType)) {
          final Map<?, ?> map = (Map) valueA;
          for (Entry<?, ?> entry : map.entrySet()) {
            final Element element;
            if (namespaceProperty != null) {
              element = document.createElementNS(namespaceProperty, nameProperty);
            } else {
              element = document.createElement(nameProperty);
            }
            final Object key = entry.getKey();
            if (key != null) {
              element.setAttribute("name", (String) key);
            }
            element.setTextContent(Utils.getStringValue(entry.getValue()));
            if (prefix != null) {
              element.setPrefix(prefix.prefix);
            }
            rootElement.appendChild(element);
          }

        } else if (!(typeA instanceof GeometryType)) {
          String value = Utils.getStringValue(valueA);
          if (value != null || (value == null && !a.isNillable())) {

            if ((nameProperty.equals("name") || nameProperty.equals("description"))
                && !Namespaces.GML.equals(namespaceProperty)) {
              namespaceProperty = Namespaces.GML;
              LOGGER.warning(
                  "the property name and description of a feature must have the GML namespace");
            }
            final Element element;
            if (namespaceProperty != null) {
              element = document.createElementNS(namespaceProperty, nameProperty);
            } else {
              element = document.createElement(nameProperty);
            }
            if (value != null) {
              element.setTextContent(value);
            }
            if (prefix != null) {
              element.setPrefix(prefix.prefix);
            }
            rootElement.appendChild(element);
          }

          // we add the geometry
        } else {

          if (valueA != null) {
            final Element element;
            if (namespaceProperty != null) {
              element = document.createElementNS(namespaceProperty, nameProperty);
            } else {
              element = document.createElement(nameProperty);
            }
            if (prefix != null) {
              element.setPrefix(prefix.prefix);
            }
            Geometry isoGeometry =
                JTSUtils.toISO(
                    (com.vividsolutions.jts.geom.Geometry) valueA,
                    type.getCoordinateReferenceSystem());
            Marshaller marshaller = null;
            try {
              marshaller = POOL.acquireMarshaller();
              marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
              marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false);
              marshaller.marshal(OBJECT_FACTORY.buildAnyGeometry(isoGeometry), element);
            } catch (JAXBException ex) {
              LOGGER.log(
                  Level.WARNING,
                  "JAXB Exception while marshalling the iso geometry: " + ex.getMessage(),
                  ex);
            } finally {
              if (marshaller != null) {
                POOL.release(marshaller);
              }
            }
            rootElement.appendChild(element);
          }
        }
      }
    }

    // writer.writeEndElement();
    return rootElement;
  }

  /**
   * @param featureCollection
   * @param writer
   * @param fragment : true if we write in a stream, dont write start and end elements
   * @throws DataStoreException
   */
  public Element writeFeatureCollection(
      final FeatureCollection featureCollection, final boolean fragment, final boolean wfs)
      throws DataStoreException, ParserConfigurationException {

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    // then we have to create document-loader:
    factory.setNamespaceAware(false);
    DocumentBuilder loader = factory.newDocumentBuilder();

    // creating a new DOM-document...
    Document document = loader.newDocument();

    // the XML header
    if (!fragment) {
      document.setXmlVersion("1.0");
      // writer.writeStartDocument("UTF-8", "1.0");
    }

    // the root Element
    final Element rootElement;
    if (wfs) {
      rootElement = document.createElementNS("http://www.opengis.net/wfs", "FeatureCollection");
      rootElement.setPrefix("wfs");
    } else {
      rootElement = document.createElementNS("http://www.opengis.net/gml", "FeatureCollection");
      rootElement.setPrefix("gml");
    }

    document.appendChild(rootElement);

    String collectionID = "";
    if (featureCollection.getID() != null) {
      collectionID = featureCollection.getID();
    }
    final Attr idAttribute = document.createAttributeNS(Namespaces.GML, "id");
    idAttribute.setValue(collectionID);
    idAttribute.setPrefix("gml");
    rootElement.setAttributeNodeNS(idAttribute);

    if (schemaLocation != null && !schemaLocation.equals("")) {
      rootElement.setAttributeNS(
          "http://www.w3.org/2001/XMLSchema-instance", "schemaLocation", schemaLocation);
    }

    /*FeatureType type = featureCollection.getFeatureType();
    if (type != null && type.getName() != null) {
        String namespace = type.getName().getNamespaceURI();
        if (namespace != null && !namespace.equals(Namespaces.GML)) {
            Prefix prefix    = getPrefix(namespace);
            writer.writeNamespace(prefix.prefix, namespace);
        }
    }*/
    /*
     * The boundedby part
     */
    final Element boundElement = writeBounds(featureCollection.getEnvelope(), document);
    if (boundElement != null) {
      rootElement.appendChild(boundElement);
    }

    // we write each feature member of the collection
    FeatureIterator iterator = featureCollection.iterator();
    try {
      while (iterator.hasNext()) {
        final Feature f = iterator.next();
        final Element memberElement = document.createElementNS(Namespaces.GML, "featureMember");
        memberElement.setPrefix("gml");
        memberElement.appendChild(writeFeature(f, document, true));
        rootElement.appendChild(memberElement);
      }

    } finally {
      // we close the stream
      iterator.close();
    }
    return rootElement;
  }

  private Element writeBounds(final Envelope bounds, final Document document) {
    if (bounds != null) {

      String srsName = null;
      if (bounds.getCoordinateReferenceSystem() != null) {
        try {
          srsName =
              IdentifiedObjects.lookupIdentifier(
                  Citations.URN_OGC, bounds.getCoordinateReferenceSystem(), true);
        } catch (FactoryException ex) {
          LOGGER.log(Level.WARNING, null, ex);
        }
      }
      final Element boundElement = document.createElementNS(Namespaces.GML, "boundedBy");
      boundElement.setPrefix("gml");
      final Element envElement = document.createElementNS(Namespaces.GML, "Envelope");
      envElement.setPrefix("gml");
      if (srsName != null) {
        envElement.setAttribute("srsName", srsName);
      } else {
        envElement.setAttribute("srsName", "");
      }

      // lower corner
      final Element lower = document.createElementNS(Namespaces.GML, "lowerCorner");
      String lowValue =
          bounds.getLowerCorner().getOrdinate(0) + " " + bounds.getLowerCorner().getOrdinate(1);
      lower.setTextContent(lowValue);
      lower.setPrefix("gml");
      envElement.appendChild(lower);

      // upper corner
      final Element upper = document.createElementNS(Namespaces.GML, "upperCorner");
      String uppValue =
          bounds.getUpperCorner().getOrdinate(0) + " " + bounds.getUpperCorner().getOrdinate(1);
      upper.setTextContent(uppValue);
      upper.setPrefix("gml");
      envElement.appendChild(upper);

      boundElement.appendChild(envElement);
      return boundElement;
    }
    return null;
  }

  /**
   * Returns the prefix for the given namespace.
   *
   * @param namespace The namespace for which we want the prefix.
   */
  private Prefix getPrefix(final String namespace) {
    String prefix = Namespaces.getPreferredPrefix(namespace, null);
    boolean unknow = false;
    if (prefix == null) {
      prefix = unknowNamespaces.get(namespace);
      if (prefix == null) {
        prefix = "ns" + lastUnknowPrefix;
        lastUnknowPrefix++;
        unknow = true;
        unknowNamespaces.put(namespace, prefix);
      }
    }
    return new Prefix(unknow, prefix);
  }

  /** Inner class for handling prefix and if it is already known. */
  private final class Prefix {
    public boolean unknow;
    public String prefix;

    public Prefix(final boolean unknow, final String prefix) {
      this.prefix = prefix;
      this.unknow = unknow;
    }
  }
}
コード例 #6
0
/**
 * @author Johann Sorel (Puzzle-GIS)
 * @module pending
 */
public class A3DCanvas extends ReferencedCanvas {

  private static final Logger LOGGER = Logging.getLogger(A3DCanvas.class);

  public static final String CAMERA_POSITION = "camera_position";

  private final LogicalLayer logicalLayer = new LogicalLayer();
  private final A3DContainer container = new A3DContainer(this);
  private final A3DController controller;
  private final JScrollPane swingPane;
  private final LwjglAwtCanvas canvas;

  public A3DCanvas(final CoordinateReferenceSystem objectiveCRS, final Hints hints)
      throws LWJGLException {
    super(objectiveCRS, hints);
    canvas = initContext();
    controller = new A3DController(this, logicalLayer);
    controller.init();

    swingPane = new JScrollPane(canvas);
    swingPane.setBorder(null);
    swingPane.setWheelScrollingEnabled(false);
    swingPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
    swingPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

    swingPane.addComponentListener(
        new ComponentAdapter() {
          @Override
          public void componentResized(ComponentEvent e) {

            CanvasRenderer canvasRenderer = canvas.getCanvasRenderer();

            if (canvasRenderer.getCamera() != null) {
              System.out.println("resized");
              // tell our camera the correct new size
              canvasRenderer.getCamera().resize(canvas.getWidth(), canvas.getHeight());

              // keep our aspect ratio the same.
              canvasRenderer
                  .getCamera()
                  .setFrustumPerspective(
                      45.0, canvas.getWidth() / (float) canvas.getHeight(), 1, 5000);
            }
          }
        });

    Thread updater = new A3DPaintingUpdater(canvas, controller);
    updater.setPriority(Thread.MAX_PRIORITY);
    updater.start();
  }

  @Override
  public synchronized void setObjectiveCRS(final CoordinateReferenceSystem crs)
      throws TransformException {
    throw new TransformException("You are not allowed to change CRS after creation on 3D canvas");
  }

  @Override
  public A3DController getController() {
    return controller;
  }

  public A3DContainer getContainer2() {
    return container;
  }

  @Override
  public AbstractContainer getContainer() {
    return null;
  }

  public JComponent getComponent() {
    return swingPane;
  }

  public LwjglAwtCanvas getNativeCanvas() {
    return canvas;
  }

  private LwjglAwtCanvas initContext() throws LWJGLException {
    //        refresher.addUpdater(controller);

    LwjglCanvasRenderer renderer = new LwjglCanvasRenderer(container);
    final DisplaySettings settings = new DisplaySettings(1, 1, 0, 0, 0, 32, 0, 4, false, false);
    final LwjglAwtCanvas canvas = new LwjglAwtCanvas(settings, renderer);
    canvas.setSize(new Dimension(100, 100));
    canvas.setPreferredSize(new Dimension(1, 1));
    canvas.setVisible(true);

    final AwtMouseWrapper mouseWrapper = new AwtMouseWrapper(canvas);
    final AwtKeyboardWrapper keyboardWrapper = new AwtKeyboardWrapper(canvas);
    final AwtFocusWrapper focusWrapper = new AwtFocusWrapper(canvas);
    final AwtMouseManager mouseManager = new AwtMouseManager(canvas);

    final PhysicalLayer pl = new PhysicalLayer(keyboardWrapper, mouseWrapper, focusWrapper);

    logicalLayer.registerInput(canvas, pl);

    logicalLayer.registerTrigger(
        new InputTrigger(
            new KeyPressedCondition(Key.H),
            new TriggerAction() {
              @Override
              public void perform(Canvas source, TwoInputStates arg1, double arg2) {
                if (source != canvas) {
                  return;
                }
              }
            }));
    logicalLayer.registerTrigger(
        new InputTrigger(
            new KeyPressedCondition(Key.J),
            new TriggerAction() {

              @Override
              public void perform(Canvas source, TwoInputStates arg1, double arg2) {
                if (source != canvas) {
                  return;
                }
                mouseManager.setCursor(MouseCursor.SYSTEM_DEFAULT);
              }
            }));

    //        refresher.addCanvas(canvas);

    return canvas;
  }

  @Override
  protected RenderingContext getRenderingContext() {
    throw new UnsupportedOperationException("Not supported yet.");
  }
}
コード例 #7
0
/**
 * Lenght mesure handler
 *
 * @author Johann Sorel (Puzzle-GIS)
 * @module pending
 */
public class LenghtHandler implements CanvasHandler {

  private static final Logger LOGGER = Logging.getLogger(LenghtHandler.class);

  private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();

  public static final List<Unit> UNITS = new ArrayList<Unit>();

  static {
    UNITS.add(SI.KILOMETRE);
    UNITS.add(SI.METRE);
    UNITS.add(NonSI.MILE);
    UNITS.add(NonSI.INCH);
  }

  private final MouseListen mouseInputListener;

  private final List<Coordinate> coords = new ArrayList<Coordinate>();
  private final LenghtDecoration deco = new LenghtDecoration();
  private final JMap2D map;

  public LenghtHandler(final JMap2D map) {
    this.map = map;
    mouseInputListener = new MouseListen();
  }

  /** {@inheritDoc } */
  @Override
  public void install(final Component component) {
    mouseInputListener.install(component);
    map.addDecoration(0, deco);
  }

  /** {@inheritDoc } */
  @Override
  public void uninstall(final Component component) {
    mouseInputListener.uninstall(component);
    map.removeDecoration(deco);
  }

  private void updateGeometry() {
    final List<Geometry> geoms = new ArrayList<Geometry>();
    if (coords.size() == 1) {
      // single point
      geoms.add(GEOMETRY_FACTORY.createPoint(coords.get(0)));
    } else if (coords.size() > 1) {
      // line
      geoms.add(GEOMETRY_FACTORY.createLineString(coords.toArray(new Coordinate[coords.size()])));
    }

    deco.setGeometries(geoms);
  }

  @Override
  public J2DCanvas getCanvas() {
    return map.getCanvas();
  }

  // ---------------------PRIVATE CLASSES--------------------------------------
  private class MouseListen extends MouseNavigatonListener {

    MouseListen() {
      super(map);
    }

    @Override
    public void mouseClicked(final MouseEvent e) {
      super.mouseClicked(e);

      final int mousebutton = e.getButton();
      if (mousebutton == MouseEvent.BUTTON1) {
        // add a coordinate
        final AffineTransform2D trs = map.getCanvas().getController().getTransform();
        try {
          final AffineTransform dispToObj = trs.createInverse();
          final double[] crds = new double[] {e.getX(), e.getY()};
          dispToObj.transform(crds, 0, crds, 0, 1);
          coords.add(new Coordinate(crds[0], crds[1]));
          updateGeometry();
        } catch (NoninvertibleTransformException ex) {
          LOGGER.log(Level.WARNING, null, ex);
        }

      } else if (mousebutton == MouseEvent.BUTTON3) {
        // erase coordiantes
        coords.clear();
        updateGeometry();
      }
    }

    @Override
    public void mouseEntered(final MouseEvent e) {
      map.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
    }
  }
}
コード例 #8
0
/**
 * @author Johann Sorel (Geomatys)
 * @module pending
 */
public class DefaultGetTile extends AbstractRequest implements GetTileRequest {

  /** Default logger for all GetMap requests. */
  protected static final Logger LOGGER = Logging.getLogger(DefaultGetTile.class);

  /** The version to use for this webservice request. */
  private int scale = 0;

  private int row = 0;
  private int col = 0;
  private String extension = ".png";

  /**
   * Defines the server url for this request.
   *
   * @param serverURL The server url.
   */
  protected DefaultGetTile(final String serverURL, final ClientSecurity security) {
    super(serverURL, security, null);
  }

  @Override
  protected String getSubPath() {
    final StringBuilder sb = new StringBuilder();
    final String baseSub = super.getSubPath();
    if (baseSub != null) {
      sb.append(baseSub);
    }
    sb.append('/').append(scale).append('/').append(col).append('/').append(row).append(extension);
    return sb.toString();
  }

  @Override
  public int getScaleLevel() {
    return scale;
  }

  @Override
  public void setScaleLevel(final int level) {
    this.scale = level;
  }

  @Override
  public int getTileRow() {
    return row;
  }

  @Override
  public void setTileRow(final int row) {
    this.row = row;
  }

  @Override
  public int getTileCol() {
    return col;
  }

  @Override
  public void setTileCol(final int col) {
    this.col = col;
  }

  @Override
  public String getExtension() {
    return extension;
  }

  @Override
  public void setExtension(final String ext) {
    this.extension = ext;
  }
}