TileIndexCalculator.java

/*
 * Copyright 2021 Global Crop Diversity Trust
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.genesys.util;

/**
 * Raster grid utilities.
 */
public class TileIndexCalculator {

	/**
	 * Gets the 3x3 minute grid cell zero-based index starting in the NW corner
	 * (-180,90) with `tile=0` and ending in the SW corner (180,-90). A 3 minute
	 * grid has `ncol=7200` "columns" and `nrow=3600` "rows".
	 *
	 * @param longitude the longitude
	 * @param latitude the latitude
	 * @return the cell index for a 3 degree grid
	 */
	public static final Integer get3MinuteTileIndex(Number longitude, Number latitude) {
		if (longitude == null || latitude == null)
			return null;

		if (longitude.doubleValue() < -180 || longitude.doubleValue() > 180)
			return null;

		if (latitude.doubleValue() < -90 || latitude.doubleValue() > 90)
			return null;

		final int columns = 7200;
		final int rows = 3600;
		final float xres_inv = columns / 360f;
		final float yres_inv = rows / 180f;

		// yx is offset by 0.5 * yd in
		// https://github.com/rspatial/raster/blob/master/R/rasterFromBIL.R#L92
		// I took two days to find out why math doesn't work out!
		var ymax = 89.99990f; // 90.0d

		int nrow;
		if (latitude.intValue() == -90) {
			nrow = (int) (rows) - 1;
		} else if (latitude.intValue() == 90) {
			nrow = 0;
		} else {
			nrow = (int) ((ymax - latitude.floatValue()) * yres_inv);
		}

		// longitude (-180:180) -> (0:columns-1)
		int ncol = (int) ((longitude.floatValue() + 180f) * xres_inv);
		if (longitude.intValue() == 180) {
			ncol = (int) (columns - 1);
		}

		return nrow * columns + ncol;
	}
}