Using geotools version 11.2
import java.awt.Rectangle
import com.vividsolutions.jts.geom.Envelope
import org.geotools.coverage.grid.{GeneralGridEnvelope, GridGeometry2D}
import org.geotools.geometry.jts.ReferencedEnvelope
import org.geotools.referencing.crs.DefaultGeographicCRS.WGS84
val minX = -78.523
val minY = 38.010
val maxX = -78.451
val maxY = 38.069
val width = 400
val height = 300
val bounds = new ReferencedEnvelope(new Envelope(minX, maxX, minY, maxY), WGS84)
val rect = new Rectangle(0, 0, width, height)
val ggEnvelope = new GeneralGridEnvelope(rect, bounds.getDimension)
val gm = new GridGeometry2D(ggEnvelope, bounds)
////////////////// This should be [0, 299] but is [-1, 299]//////////////////////////////
gm.worldToGrid(bounds.getLowerCorner)
Ended up using this function. You can see something similar for maxX and minY in the source code for GridGeometry2D.java worldToGrid function
val TOL = 1.0E-6
val values = Array(0.0)
def safeWorldAccess(grid: GridCoverage2D, point: DirectPosition2D): Double = {
val trPoint = point.clone()
val gridGeom = grid.getGridGeometry
if (Math.abs(trPoint.getX - gridGeom.getEnvelope2D.getMinX) <= TOL) {
trPoint.setLocation(trPoint.getX + TOL, trPoint.getY)
}
if (Math.abs(trPoint.getY - gridGeom.getEnvelope2D.getMaxY) <= TOL) {
trPoint.setLocation(trPoint.getX, trPoint.getY - TOL)
}
val gc: GridCoordinates2D = gridGeom.worldToGrid(trPoint)
grid.evaluate(gc, values).head
}