Search in sources :

Example 11 with GridGeometry

use of org.apache.sis.coverage.grid.GridGeometry in project sis by apache.

the class RasterResource method read.

/**
 * Loads a subset of the grid coverage represented by this resource.
 *
 * @param  domain  desired grid extent and resolution, or {@code null} for reading the whole domain.
 * @param  range   0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all.
 * @return the grid coverage for the specified domain and range.
 * @throws DataStoreException if an error occurred while reading the grid coverage data.
 */
@Override
public GridCoverage read(final GridGeometry domain, final int... range) throws DataStoreException {
    final long startTime = System.nanoTime();
    final RangeArgument rangeIndices = validateRangeArgument(ranges.length, range);
    final Variable first = data[bandDimension >= 0 ? 0 : rangeIndices.getFirstSpecified()];
    final DataType dataType = first.getDataType();
    if (bandDimension < 0) {
        for (int i = 0; i < rangeIndices.getNumBands(); i++) {
            final Variable variable = data[rangeIndices.getSourceIndex(i)];
            if (!dataType.equals(variable.getDataType())) {
                throw new DataStoreContentException(Resources.forLocale(getLocale()).getString(Resources.Keys.MismatchedVariableType_3, getFilename(), first.getName(), variable.getName()));
            }
        }
    }
    /*
         * At this point the arguments and the state of this resource have been validated.
         * There is three ways to read the data, determined by `bandDimension` value:
         *
         *   • (bandDimension < 0): one variable per band (usual case).
         *   • (bandDimension = 0): one variable containing all bands, with bands in the first dimension.
         *   • (bandDimension > 0): one variable containing all bands, with bands in the last dimension.
         */
    final GridGeometry targetDomain;
    final DataBuffer imageBuffer;
    final SampleDimension[] bands = new SampleDimension[rangeIndices.getNumBands()];
    // By default, all bands start at index 0.
    int[] bandOffsets = null;
    try {
        final GridDerivation targetGeometry = gridGeometry.derive().rounding(GridRoundingMode.ENCLOSING).subgrid((domain != null) ? domain : gridGeometry);
        // Pixel indices of data to read.
        GridExtent areaOfInterest = targetGeometry.getIntersection();
        // Slice to read or subsampling to apply.
        int[] subsampling = targetGeometry.getSubsampling();
        // By default, one variable per band.
        int numBuffers = bands.length;
        // Adjust user-specified domain to data geometry.
        targetDomain = targetGeometry.build();
        if (bandDimension >= 0) {
            areaOfInterest = rangeIndices.insertBandDimension(areaOfInterest, bandDimension);
            subsampling = rangeIndices.insertSubsampling(subsampling, bandDimension);
            if (bandDimension == 0) {
                // Will be set to non-zero values later.
                bandOffsets = new int[numBuffers];
            }
            // One variable for all bands.
            numBuffers = 1;
        }
        /*
             * Iterate over netCDF variables in the order they appear in the file, not in the order requested
             * by the `range` argument.  The intent is to perform sequential I/O as much as possible, without
             * seeking backward. In the (uncommon) case where bands are one of the variable dimension instead
             * than different variables, the reading of the whole variable occurs during the first iteration.
             */
        Buffer[] sampleValues = new Buffer[numBuffers];
        synchronized (lock) {
            for (int i = 0; i < bands.length; i++) {
                // In strictly increasing order.
                int indexInResource = rangeIndices.getSourceIndex(i);
                int indexInRaster = rangeIndices.getTargetIndex(i);
                Variable variable = getVariable(indexInResource);
                SampleDimension b = ranges[indexInResource];
                if (b == null) {
                    ranges[indexInResource] = b = createSampleDimension(rangeIndices.builder(), variable, i);
                }
                bands[indexInRaster] = b;
                if (bandOffsets != null) {
                    bandOffsets[indexInRaster] = i;
                    // Pixels interleaved in one bank: sampleValues.length = 1.
                    indexInRaster = 0;
                }
                if (i < numBuffers)
                    try {
                        // Optional.orElseThrow() below should never fail since Variable.read(…) wraps primitive array.
                        sampleValues[indexInRaster] = variable.read(areaOfInterest, subsampling).buffer().get();
                    } catch (ArithmeticException e) {
                        throw variable.canNotComputePosition(e);
                    }
            }
        }
        /*
             * The following block is executed only if all bands are in a single variable, and the bands dimension is
             * the last one (in "natural" order). In such case, the sample model to construct is a BandedSampleModel.
             * Contrarily to PixelInterleavedSampleModel (the case when the band dimension is first), banded sample
             * model force us to split the buffer in a buffer for each band.
             */
        if (bandDimension > 0) {
            // Really > 0, not >= 0.
            final int stride = Math.toIntExact(data[0].getBandStride());
            Buffer values = sampleValues[0].limit(stride);
            sampleValues = new Buffer[bands.length];
            for (int i = 0; i < sampleValues.length; i++) {
                if (i != 0) {
                    values = JDK9.duplicate(values);
                    final int p = values.limit();
                    values.position(p).limit(Math.addExact(p, stride));
                }
                sampleValues[i] = values;
            }
        }
        /*
             * Convert NIO Buffer into Java2D DataBuffer. May throw various RuntimeException.
             */
        imageBuffer = RasterFactory.wrap(dataType.rasterDataType, sampleValues);
    } catch (IOException | RuntimeException e) {
        throw canNotRead(getFilename(), domain, e);
    }
    /*
         * At this point the I/O operation is completed and sample values have been stored in a NIO buffer.
         * Provide to `Raster` all information needed for building a `RenderedImage` when requested.
         */
    if (imageBuffer == null) {
        throw new DataStoreContentException(Errors.getResources(getLocale()).getString(Errors.Keys.UnsupportedType_1, dataType.name()));
    }
    final Variable main = data[visibleBand];
    final Raster raster = new Raster(targetDomain, UnmodifiableArrayList.wrap(bands), imageBuffer, String.valueOf(identifier), rangeIndices.getPixelStride(), bandOffsets, visibleBand, main.decoder.convention().getColors(main));
    logReadOperation(location, targetDomain, startTime);
    return raster;
}
Also used : Buffer(java.nio.Buffer) DataBuffer(java.awt.image.DataBuffer) GridGeometry(org.apache.sis.coverage.grid.GridGeometry) GridExtent(org.apache.sis.coverage.grid.GridExtent) IOException(java.io.IOException) SampleDimension(org.apache.sis.coverage.SampleDimension) GridDerivation(org.apache.sis.coverage.grid.GridDerivation) DataStoreContentException(org.apache.sis.storage.DataStoreContentException) DataBuffer(java.awt.image.DataBuffer)

Example 12 with GridGeometry

use of org.apache.sis.coverage.grid.GridGeometry in project sis by apache.

the class RasterResource method create.

/**
 * Creates all grid coverage resources from the given decoder.
 * This method shall be invoked in a method synchronized on {@link #lock}.
 *
 * @param  decoder  the implementation used for decoding the netCDF file.
 * @param  lock     the lock to use in {@code synchronized(lock)} statements.
 * @return all grid coverage resources.
 * @throws IOException if an I/O operation was required and failed.
 * @throws DataStoreException if a logical error occurred.
 */
public static List<Resource> create(final Decoder decoder, final Object lock) throws IOException, DataStoreException {
    assert Thread.holdsLock(lock);
    // Needs a clone because may be modified.
    final Variable[] variables = decoder.getVariables().clone();
    // Usually has only 1 element, sometime 2.
    final List<Variable> siblings = new ArrayList<>(4);
    // The raster resources to be returned.
    final List<Resource> resources = new ArrayList<>(variables.length);
    // For detecting name collisions.
    final Map<GenericName, List<RasterResource>> byName = new HashMap<>();
    for (int i = 0; i < variables.length; i++) {
        final Variable variable = variables[i];
        if (!VariableRole.isCoverage(variable)) {
            // Skip variables that are not grid coverages.
            continue;
        }
        final GridGeometry grid = variable.getGridGeometry();
        if (grid == null) {
            // Skip variables that are not grid coverages.
            continue;
        }
        // Variable will the first band of raster.
        siblings.add(variable);
        String name = variable.getStandardName();
        /*
             * At this point we found a variable for which to create a resource. Most of the time, there is nothing else to do;
             * the resource will have a single variable and the same name than that unique variable. The resulting raster will
             * have only one band (sample dimension). However in some cases the raster should have more than one band:
             *
             *   1) if the variable has an extra dimension compared to the grid geometry;
             *   2) of if two or more variables should be grouped together.
             *
             * The following  if {…} else {…}  blocks implement those two cases.
             */
        final List<Dimension> gridDimensions = variable.getGridDimensions();
        final int dataDimension = gridDimensions.size();
        final int gridDimension = grid.getDimension();
        final int bandDimension, numBands;
        if (dataDimension != gridDimension) {
            // One variable dimension is interpreted as bands.
            bandDimension = variable.bandDimension;
            // Note: "natural" → netCDF index conversion.
            Dimension dim = gridDimensions.get(dataDimension - 1 - bandDimension);
            numBands = Math.toIntExact(dim.length());
            if (dataDimension != gridDimension + 1 || (bandDimension > 0 && bandDimension != gridDimension)) {
                /*
                     * One of the following restrictions it not met for the requested data:
                     *
                     *   - Only 1 dimension can be used for bands. Variables with 2 or more band dimensions are not supported.
                     *   - The dimension for bands shall be either the first or the last dimension; it can not be in the middle.
                     */
                throw new DataStoreContentException(Resources.forLocale(decoder.listeners.getLocale()).getString(Resources.Keys.UnmappedDimensions_4, name, decoder.getFilename(), dataDimension, gridDimension));
            }
        } else {
            /*
                 * At this point we found a variable where all dimensions are in the CRS. This is the usual case;
                 * there is no band explicitly declared in the netCDF file. However in some cases, we should put
                 * other variables together with the one we just found. Example:
                 *
                 *    1) baroclinic_eastward_sea_water_velocity
                 *    2) baroclinic_northward_sea_water_velocity
                 *
                 * We use the "eastward" and "northward" keywords for recognizing such pairs, providing that everything else in the
                 * name is the same and the grid geometries are the same.
                 */
            // No dimension to be interpreted as bands.
            bandDimension = -1;
            final DataType type = variable.getDataType();
            for (final String keyword : VECTOR_COMPONENT_NAMES) {
                final int prefixLength = name.indexOf(keyword);
                if (prefixLength >= 0) {
                    int suffixStart = prefixLength + keyword.length();
                    int suffixLength = name.length() - suffixStart;
                    for (int j = i; ++j < variables.length; ) {
                        final Variable candidate = variables[j];
                        if (!VariableRole.isCoverage(candidate)) {
                            // For avoiding to revisit that variable again.
                            variables[j] = null;
                            continue;
                        }
                        final String cn = candidate.getStandardName();
                        if (cn.regionMatches(cn.length() - suffixLength, name, suffixStart, suffixLength) && cn.regionMatches(0, name, 0, prefixLength) && candidate.getDataType() == type && grid.equals(candidate.getGridGeometry())) {
                            /*
                                 * Found another variable with the same name except for the keyword. Verify that the
                                 * keyword is replaced by another word in the vector component keyword list. If this
                                 * is the case, then we consider that those two variables should be kept together.
                                 */
                            for (final String k : VECTOR_COMPONENT_NAMES) {
                                if (cn.startsWith(k, prefixLength)) {
                                    siblings.add(candidate);
                                    variables[j] = null;
                                    break;
                                }
                            }
                        }
                    }
                    /*
                         * If we have more than one variable, omit the keyword from the name. For example instead
                         * of "baroclinic_eastward_sea_water_velocity", construct "baroclinic_sea_water_velocity".
                         * Note that we may need to remove duplicated '_' character after keyword removal.
                         */
                    if (siblings.size() > 1) {
                        if (suffixLength != 0) {
                            final int c = name.codePointAt(suffixStart);
                            if ((prefixLength != 0) ? (c == name.codePointBefore(prefixLength)) : (c == '_')) {
                                suffixStart += Character.charCount(c);
                            }
                        }
                        name = new StringBuilder(name).delete(prefixLength, suffixStart).toString();
                    }
                }
            }
            numBands = siblings.size();
        }
        final RasterResource r = new RasterResource(decoder, name.trim(), grid, siblings, numBands, bandDimension, lock);
        r.addToNameMap(byName);
        resources.add(r);
        siblings.clear();
    }
    /*
         * At this point all resources have been prepared. If the same standard name is used by more than one resource,
         * replace the standard name by the identifier for all resources in collision (an "all or nothing" replacement).
         * The identifiers should be unique, which should resolve the name collision. We nevertheless check again after
         * renaming until there is nothing to change.
         */
    boolean changed;
    do {
        changed = false;
        List<RasterResource> collisions = null;
        for (final Iterator<List<RasterResource>> it = byName.values().iterator(); it.hasNext(); ) {
            final List<RasterResource> rs = it.next();
            if (rs.size() >= 2) {
                // Remove resources before to re-insert them in next loop below.
                it.remove();
                if (collisions == null) {
                    collisions = rs;
                } else {
                    collisions.addAll(rs);
                }
            }
        }
        if (collisions != null) {
            // After above loop because may change the byName` map.
            for (final RasterResource r : collisions) {
                changed |= r.resolveNameCollision(decoder);
                // For checking if new names cause new collisions.
                r.addToNameMap(byName);
            }
        }
    } while (changed);
    return resources;
}
Also used : GridGeometry(org.apache.sis.coverage.grid.GridGeometry) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) UnmodifiableArrayList(org.apache.sis.internal.util.UnmodifiableArrayList) Resource(org.apache.sis.storage.Resource) AbstractGridResource(org.apache.sis.internal.storage.AbstractGridResource) SampleDimension(org.apache.sis.coverage.SampleDimension) GenericName(org.opengis.util.GenericName) DataStoreContentException(org.apache.sis.storage.DataStoreContentException) ArrayList(java.util.ArrayList) UnmodifiableArrayList(org.apache.sis.internal.util.UnmodifiableArrayList) List(java.util.List)

Example 13 with GridGeometry

use of org.apache.sis.coverage.grid.GridGeometry in project sis by apache.

the class Variable method getGridGeometry.

/**
 * Returns the grid geometry for this variable, or {@code null} if this variable is not a data cube.
 * Not all variables have a grid geometry. For example collections of features do not have such grid.
 * The same grid geometry may be shared by many variables.
 * The grid may have fewer {@linkplain Grid#getDimensions() dimensions} than this variable,
 * in which case the additional {@linkplain #getGridDimensions() variable dimensions} can be considered as bands.
 *
 * @return the grid geometry for this variable, or {@code null} if none.
 * @throws IOException if an error occurred while reading the data.
 * @throws DataStoreException if a logical error occurred.
 */
public final GridGeometry getGridGeometry() throws IOException, DataStoreException {
    if (!gridDetermined) {
        // Set first so we don't try twice in case of failure.
        gridDetermined = true;
        final GridMapping gridMapping = GridMapping.forVariable(this);
        final GridAdjustment adjustment = new GridAdjustment();
        final Grid info = getGrid(adjustment);
        if (info != null) {
            /*
                 * This variable may have more dimensions than the grid. We need to reduce the list to the same
                 * dimensions than the ones in the grid.  We can not take Grid.getDimensions() directly because
                 * those dimensions may not have the same length (this mismatch is handled in the next block).
                 */
            // In netCDF order.
            List<Dimension> dimensions = getGridDimensions();
            final int dataDimension = dimensions.size();
            if (dataDimension > info.getSourceDimensions()) {
                boolean copied = false;
                // Also in netCDF order.
                final List<Dimension> toKeep = info.getDimensions();
                final int numToKeep = toKeep.size();
                for (int i = 0; i < numToKeep; i++) {
                    Dimension expected = toKeep.get(i);
                    expected = adjustment.gridToVariable.getOrDefault(expected, expected);
                    /*
                         * At this point, `expected` is a dimension of the variable that we expect to find at
                         * current index `i`. If we do not find that dimension, then the unexpected dimension
                         * is assumed to be a band. We usually remove at most one element. If removal results
                         * in a list too short, it would be a bug in the way we computed `toKeep`.
                         */
                    while (!expected.equals(dimensions.get(i))) {
                        if (!copied) {
                            copied = true;
                            dimensions = new ArrayList<>(dimensions);
                        }
                        /*
                             * It is possible that we never reach this point if the unexpected dimension is last.
                             * However in such case the dimension to declare is the last one in netCDF order,
                             * which corresponds to the first dimension (i.e. dimension 0) in "natural" order.
                             * Since the `bandDimension` field is initialized to zero, its value is correct.
                             */
                        // Convert netCDF order to "natural" order.
                        bandDimension = dataDimension - 1 - i;
                        dimensions.remove(i);
                        for (int j = dimensions.size(); --j >= i; ) {
                            dimensions.set(j, dimensions.get(j).decrementIndex());
                        }
                        if (dimensions.size() < numToKeep) {
                            // Should not happen (see above comment).
                            throw new InternalDataStoreException();
                        }
                    }
                }
            /*
                     * At this point `dimensions` may still be longer than `toKeep` but it does not matter.
                     * We only need that for any index i < numToKeep, dimensions.get(i) corresponds to the
                     * dimension at the same index in the grid.
                     */
            }
            /*
                 * Compare the size of the variable with the size of the localization grid.
                 * If they do not match, then there is a scale factor between the two that
                 * needs to be applied.
                 */
            GridGeometry grid = info.getGridGeometry(decoder);
            if (grid != null) {
                if (grid.isDefined(GridGeometry.EXTENT)) {
                    GridExtent extent = grid.getExtent();
                    final long[] sizes = new long[extent.getDimension()];
                    boolean needsResize = false;
                    for (int i = sizes.length; --i >= 0; ) {
                        // Convert "natural order" index into netCDF index.
                        final int d = (sizes.length - 1) - i;
                        sizes[i] = dimensions.get(d).length();
                        if (!needsResize) {
                            needsResize = (sizes[i] != extent.getSize(i));
                        }
                    }
                    if (needsResize) {
                        final double[] dataToGridIndices = adjustment.dataToGridIndices();
                        if (dataToGridIndices == null || dataToGridIndices.length < sizes.length) {
                            warning(Variable.class, "getGridGeometry", Resources.Keys.ResamplingIntervalNotFound_2, getFilename(), getName());
                            return null;
                        }
                        extent = extent.resize(sizes);
                        grid = GridAdjustment.scale(grid, extent, info.getAnchor(), dataToGridIndices);
                    }
                }
                /*
                     * At this point we finished to build a grid geometry from the information provided by axes.
                     * If there is grid mapping attributes (e.g. "EPSG_code", "ESRI_pe_string", "GeoTransform",
                     * "spatial_ref", etc.), substitute some parts of the grid geometry by the parts built from
                     * those attributes.
                     */
                if (gridMapping != null) {
                    grid = gridMapping.adaptGridCRS(this, grid, info.getAnchor());
                }
            }
            gridGeometry = grid;
        } else if (gridMapping != null) {
            gridGeometry = gridMapping.createGridCRS(this);
        }
    }
    return gridGeometry;
}
Also used : InternalDataStoreException(org.apache.sis.storage.InternalDataStoreException) GridGeometry(org.apache.sis.coverage.grid.GridGeometry) GridExtent(org.apache.sis.coverage.grid.GridExtent)

Example 14 with GridGeometry

use of org.apache.sis.coverage.grid.GridGeometry in project sis by apache.

the class CoverageSubset method clip.

/**
 * Clips the given domain to the area of interest specified by the query. If any grid geometry is null,
 * the other one is returned. The {@code domain} argument should be the domain to read as specified to
 * {@link #read(GridGeometry, int...)}, or the full {@code CoverageSubset} domain if no value were given
 * to the {@code read(…)} method.
 *
 * @param  domain    the domain requested in a read operation, or {@code null}.
 * @param  rounding  whether to clip to nearest box or an enclosing box.
 * @param  clipping  whether to clip the resulting extent to the specified {@code domain} extent.
 * @return intersection of the given grid geometry with the query domain.
 * @throws DataStoreException if the intersection can not be computed.
 */
private GridGeometry clip(final GridGeometry domain, final GridRoundingMode rounding, final GridClippingMode clipping) throws DataStoreException {
    final GridGeometry areaOfInterest = query.getSelection();
    if (domain == null)
        return areaOfInterest;
    if (areaOfInterest == null)
        return domain;
    try {
        final GridDerivation derivation = domain.derive().rounding(rounding).clipping(clipping);
        final int expansion = query.getSourceDomainExpansion();
        if (expansion != 0) {
            final int[] margins = new int[domain.getDimension()];
            Arrays.fill(margins, expansion);
            derivation.margin(margins);
        }
        return derivation.subgrid(areaOfInterest).build();
    } catch (IllegalArgumentException | IllegalStateException e) {
        final String msg = Resources.forLocale(getLocale()).getString(Resources.Keys.CanNotIntersectDataWithQuery_1, getSourceName());
        final Throwable cause = e.getCause();
        if (cause instanceof FactoryException || cause instanceof TransformException) {
            throw new DataStoreReferencingException(msg, cause);
        } else if (e instanceof DisjointExtentException) {
            throw new NoSuchDataException(msg, e);
        } else {
            throw new DataStoreException(msg, e);
        }
    }
}
Also used : GridGeometry(org.apache.sis.coverage.grid.GridGeometry) DisjointExtentException(org.apache.sis.coverage.grid.DisjointExtentException) FactoryException(org.opengis.util.FactoryException) TransformException(org.opengis.referencing.operation.TransformException) GridDerivation(org.apache.sis.coverage.grid.GridDerivation)

Example 15 with GridGeometry

use of org.apache.sis.coverage.grid.GridGeometry in project sis by apache.

the class MapCanvas method repaint.

/**
 * Invoked when the map content needs to be rendered again.
 * It may be because the map has new content, or because the viewed region moved or has been zoomed.
 * This method starts the rendering process immediately, unless a rendering is already in progress.
 *
 * @see #requestRepaint()
 */
final void repaint() {
    assert Platform.isFxApplicationThread();
    /*
         * If a rendering is already in progress, do not send a new request now.
         * Wait for current rendering to finish; a new one will be automatically
         * requested if content changes are detected after the rendering.
         */
    if (renderingInProgress != null) {
        if (renderingInProgress instanceof Delayed) {
            renderingInProgress.cancel(true);
            renderingInProgress = null;
        } else {
            contentChangeCount++;
            return;
        }
    }
    hasError = false;
    // Avoid that `requestRepaint(…)` trig new paints.
    isRendering.set(true);
    renderingStartTime = System.nanoTime();
    try {
        /*
             * If a new canvas size is known, inform the parent `PlanarCanvas` about that.
             * It may cause a recomputation of the "objective to display" transform.
             */
        if (sizeChanged) {
            sizeChanged = false;
            final Pane view = floatingPane;
            Envelope2D bounds = new Envelope2D(null, view.getLayoutX(), view.getLayoutY(), view.getWidth(), view.getHeight());
            if (bounds.isEmpty())
                return;
            setDisplayBounds(bounds);
        }
        /*
             * Compute the `objectiveToDisplay` only before the first rendering, because the display
             * bounds may not be known before (it may be zero at the time `MapCanvas` is initialized).
             * This code is executed only once for a new map.
             */
        if (invalidObjectiveToDisplay) {
            final Envelope2D target = getDisplayBounds();
            if (target == null) {
                // Bounds are still unknown. Another repaint event will happen when they will become known.
                return;
            }
            invalidObjectiveToDisplay = false;
            final GridExtent extent = new GridExtent(null, new long[] { Math.round(target.getMinX()), Math.round(target.getMinY()) }, new long[] { Math.round(target.getMaxX()), Math.round(target.getMaxY()) }, false);
            /*
                 * If `setObjectiveBounds(…)` has been invoked (as it should be), initialize the affine
                 * transform to values which will allow this canvas to contain fully the objective bounds.
                 * Otherwise the transform is initialized to an identity transform (should not happen often).
                 * If a CRS is present, it is used for deciding if we need to swap or flip axes.
                 */
            CoordinateReferenceSystem objectiveCRS;
            final LinearTransform crsToDisplay;
            if (objectiveBounds != null) {
                objectiveCRS = objectiveBounds.getCoordinateReferenceSystem();
                final MatrixSIS m;
                if (objectiveCRS != null) {
                    AxisDirection[] srcAxes = CoordinateSystems.getAxisDirections(objectiveCRS.getCoordinateSystem());
                    m = Matrices.createTransform(objectiveBounds, srcAxes, target, toDisplayDirections(srcAxes));
                } else {
                    m = Matrices.createTransform(objectiveBounds, target);
                }
                Matrices.forceUniformScale(m, 0, new double[] { target.getCenterX(), target.getCenterY() });
                crsToDisplay = MathTransforms.linear(m);
                if (objectiveCRS == null) {
                    objectiveCRS = extent.toEnvelope(crsToDisplay.inverse()).getCoordinateReferenceSystem();
                /*
                         * Above code tried to provide a non-null CRS on a "best effort" basis. The objective CRS
                         * may still be null, there is no obvious answer against that. It is not the display CRS
                         * if the "display to objective" transform is not identity. A grid CRS is not appropriate
                         * neither, otherwise `extent.toEnvelope(…)` would have found it.
                         */
                }
            } else {
                objectiveCRS = getDisplayCRS();
                crsToDisplay = MathTransforms.identity(BIDIMENSIONAL);
            }
            setGridGeometry(new GridGeometry(extent, PixelInCell.CELL_CORNER, crsToDisplay.inverse(), objectiveCRS));
            transform.setToIdentity();
        }
    } catch (TransformException | RenderException ex) {
        restoreCursorAfterPaint();
        isRendering.set(false);
        errorOccurred(ex);
        return;
    }
    /*
         * If a temporary zoom, rotation or translation has been applied using JavaFX transform API,
         * replace that temporary transform by a "permanent" adjustment of the `objectiveToDisplay`
         * transform. It allows SIS to get new data for the new visible area and resolution.
         * Do not reset `transform` to identity now; we need to continue accumulating gestures
         * that may happen while the rendering is done in a background thread.
         */
    changeInProgress.setToTransform(transform);
    if (!transform.isIdentity()) {
        transformDisplayCoordinates(new AffineTransform(transform.getMxx(), transform.getMyx(), transform.getMxy(), transform.getMyy(), transform.getTx(), transform.getTy()));
    }
    /*
         * Invoke `createWorker(…)` only after we finished above configuration, because that method
         * may take a snapshot of current canvas state in preparation for use in background threads.
         * Take the value of `contentChangeCount` only now because above code may have indirect calls
         * to `requestRepaint()`.
         */
    renderedContentStamp = contentChangeCount;
    final Renderer context = createRenderer();
    if (context != null && context.initialize(floatingPane)) {
        final Task<?> worker = createWorker(context);
        assert renderingInProgress == null;
        BackgroundThreads.execute(worker);
        // Set after we know that the task has been scheduled.
        renderingInProgress = worker;
        if (!isMouseChangeScheduled) {
            DelayedExecutor.schedule(new CursorChange());
            isMouseChangeScheduled = true;
        }
    } else {
        if (!hasError) {
            clearError();
        }
        isRendering.set(false);
        restoreCursorAfterPaint();
    }
}
Also used : GridGeometry(org.apache.sis.coverage.grid.GridGeometry) GridExtent(org.apache.sis.coverage.grid.GridExtent) NonInvertibleTransformException(javafx.scene.transform.NonInvertibleTransformException) TransformException(org.opengis.referencing.operation.TransformException) StackPane(javafx.scene.layout.StackPane) Pane(javafx.scene.layout.Pane) Envelope2D(org.apache.sis.geometry.Envelope2D) LinearTransform(org.apache.sis.referencing.operation.transform.LinearTransform) RenderException(org.apache.sis.portrayal.RenderException) AxisDirection(org.opengis.referencing.cs.AxisDirection) AffineTransform(java.awt.geom.AffineTransform) CoordinateReferenceSystem(org.opengis.referencing.crs.CoordinateReferenceSystem) MatrixSIS(org.apache.sis.referencing.operation.matrix.MatrixSIS)

Aggregations

GridGeometry (org.apache.sis.coverage.grid.GridGeometry)25 GridExtent (org.apache.sis.coverage.grid.GridExtent)10 SampleDimension (org.apache.sis.coverage.SampleDimension)6 CoordinateReferenceSystem (org.opengis.referencing.crs.CoordinateReferenceSystem)6 GridCoverage (org.apache.sis.coverage.grid.GridCoverage)5 MathTransform (org.opengis.referencing.operation.MathTransform)5 Test (org.junit.Test)4 TransformException (org.opengis.referencing.operation.TransformException)4 GridDerivation (org.apache.sis.coverage.grid.GridDerivation)3 Point (java.awt.Point)2 RenderedImage (java.awt.image.RenderedImage)2 GridCoverage2D (org.apache.sis.coverage.grid.GridCoverage2D)2 LinearTransform (org.apache.sis.referencing.operation.transform.LinearTransform)2 DataStoreContentException (org.apache.sis.storage.DataStoreContentException)2 FactoryException (org.opengis.util.FactoryException)2 AffineTransform (java.awt.geom.AffineTransform)1 BufferedImage (java.awt.image.BufferedImage)1 DataBuffer (java.awt.image.DataBuffer)1 RasterFormatException (java.awt.image.RasterFormatException)1 IOException (java.io.IOException)1