geospatiale/coords/equirectangular_coords.js

//Initialise functions
{
	if (!global.Geospatiale) global.Geospatiale = {};
	
	/**
	 * Checks the nearest equirectangular pixel from a raster image that fulfills a boolean condition.
	 *
	 * @param {number} arg0_lng
	 * @param {number} arg1_lat
	 * @param {Object} [arg2_options]
	 *  @param {Object} [arg2_options.input_raster]
	 *  @param {function} [arg2_options.special_function](arg0_raster:{@link Object}, arg1_x:number, arg2_y:number) | boolean - Returns true if condition is met.
	 */
	Geospatiale.getEquirectangularNearestPixelWith = function (arg0_lng, arg1_lat, arg2_options) {
		let lng = parseFloat(arg0_lng);
		let lat = parseFloat(arg1_lat);
		let options = arg2_options || {};
		let input_raster = options.input_raster;
		let special_function = options.special_function;
		
		let pixel_coords = Geospatiale.getEquirectangularCoordsPixel(lng, lat, {
			height: input_raster.height,
			width: input_raster.width
		});
		
		if (special_function(options.input_raster, pixel_coords[0], pixel_coords[1]))
			return pixel_coords; //Internal guard clause if the direct pixel matches the condition
		
		let width = input_raster.width;
		let height = input_raster.height;
		let start_x = pixel_coords[0];
		let start_y = pixel_coords[1];
		let max_radius = Math.max(width, height);
		
		for (let r = 1; r < max_radius; r++) {
			let candidates = [];
			
			let check_pixel = (cx, cy) => {
				if (cy < 0 || cy >= height) return;
				let wrapped_x = (cx % width + width) % width;
				if (special_function(options.input_raster, wrapped_x, cy)) {
					candidates.push([wrapped_x, cy]);
				}
			};
			
			for (let dx = -r; dx <= r; dx++) {
				check_pixel(start_x + dx, start_y - r);
				check_pixel(start_x + dx, start_y + r);
			}
			for (let dy = -r + 1; dy <= r - 1; dy++) {
				check_pixel(start_x - r, start_y + dy);
				check_pixel(start_x + r, start_y + dy);
			}
			
			if (candidates.length > 0) {
				let min_dist = Infinity;
				let best_pixel = null;
				
				for (let i = 0; i < candidates.length; i++) {
					let [cx, cy] = candidates[i];
					let c_lng = ((cx + 0.5)/width)*360 - 180;
					let c_lat = 90 - ((cy + 0.5)/height)*180;
					let dist = Geospatiale.haversineDistance([lng, lat], [c_lng, c_lat]);
					
					if (dist < min_dist) {
						min_dist = dist;
						best_pixel = [cx, cy];
					}
				}
				if (best_pixel) return best_pixel;
			}
		}
	};
	
	/**
	 * Returns a [lat, lng] array for a given pixel coordinate.
	 * 
	 * @param {number} arg0_x
	 * @param {number} arg1_y
	 * @param {number} [arg2_width=4320]
	 * @param {number} [arg3_height=2160]
	 * 
	 * @returns {number[]}
	 */
	Geospatiale.getEquirectangularPixelCoords = function (arg0_x, arg1_y, arg2_width, arg3_height) {
		//Convert from parameters
		let x_coord = arg0_x;
		let y_coord = arg1_y;
		let width = Math.returnSafeNumber(arg2_width, 4320);
		let height = Math.returnSafeNumber(arg3_height, 2160);
		
		//Declare local instance variables
		let lat = 90 - (y_coord/height)*180;
		let lng = (x_coord/width)*360 - 180;
		
		//Return statement
		return [lng, lat];
	};
	
	/**
	 * Fetches the x, y coordinate pair for a given pixel given latitude and longitude coordinates for WGS84 Equirectangular.
	 * @alias Geospatiale.getEquirectangularCoordsPixel
	 * 
	 * @param {number} arg0_lng
	 * @param {number} arg1_lat
	 * @param {Object} [arg2_options]
	 *  @param {number} [arg2_options.height=2160] - The height of the image in pixels.
	 *  @param {number} [arg2_options.width=4320] - The width of the image in pixels.
	 *  @param {boolean} [arg2_options.return_object=false] - Whether to return a structured object instead.
	 *
	 * @returns {Array<number, number>|{x_coord: number, y_coord: number}}
	 */
	Geospatiale.getEquirectangularCoordsPixel = function (arg0_lng, arg1_lat, arg2_options) {
		//Convert from parameters
		let longitude = Math.returnSafeNumber(arg0_lng);
		let latitude = Math.returnSafeNumber(arg1_lat);
		let options = (arg2_options) ? arg2_options : {};
		
		//Initialise options
		options.height = Math.returnSafeNumber(options.height, 2160); //5-arcminute resolution default
		options.width = Math.returnSafeNumber(options.width, 4320); //5-arcminute resolution default
		
		//Declare local instance variables
		let bbox = (options.bbox) ? options.bbox : [-180, -90, 180, 90]; //Full Earth latlng
		let x_coord = Math.floor(((longitude - bbox[0])/(bbox[2] - bbox[0]))*options.width);
		let y_coord = Math.floor(((bbox[3] - latitude)/(bbox[3] - bbox[1]))*options.height);
		
		//Clamp coordinates to image boundaries
		x_coord = Math.min(options.width - 1, Math.max(0, x_coord));
		y_coord = Math.min(options.height - 1, Math.max(0, y_coord));
		
		//Return statement
		return (!options.return_object) ?
			[x_coord, y_coord] : { x_coord, y_coord };
	};
}