geospatiale/files/asc_framework.js

//Initialise functions
{
	if (!global.GeoASC)
		/**
		 * Handles ASC files. Part of Geospatiale III.
		 * 
		 * @namespace GeoASC
		 */
		global.GeoASC = {};
	
	/**
	 * Converts an .asc file to .png.
	 * @alias GeoASC.convertToPNG
	 * 
	 * @param {string} arg0_input_file_path
	 * @param {string} arg1_output_file_path
	 * @param {Object} [arg2_options]
	 *  @param {string} [arg2_options.mode="number"] - Either 'number'/'percentage'.
	 *  @param {boolean} [arg2_options.ceil=true] - Whether to ceiling ASC integers.
	 *  @param {function(local_index, local_value)} [arg2_options.special_function] - Any function to pass to the iterative loop when processing. Must return a {@link number}.
	 *  
	 * @returns {{dataframe: Object, max_value: number}}
	 */
	GeoASC.convertToPNG = async function (arg0_input_file_path, arg1_output_file_path, arg2_options) {
		//Convert from parameters
		let input_file_path = arg0_input_file_path;
		let output_file_path = arg1_output_file_path;
		let options = (arg2_options) ? arg2_options : {};
		
		//Initialise options
		if (!options.mode) options.mode = "number";
		if (options.ceil === undefined) options.ceil = true;
		
		//Declare local instance variables
		let asc_dataframe = GeoASC.readFile(input_file_path);
		let image_columns = asc_dataframe[0].length;
		let image_rows = asc_dataframe.length;
		let max_value = Array.getMaximum(asc_dataframe);
		
		//Log image_columns; image_rows
		console.log(`Converting .asc file ${input_file_path} to ${output_file_path} with dimensions ${image_columns}x${image_rows}. Maximum value in dataframe: ${max_value}`);
		
		let png = new pngjs.PNG({
			height: image_rows,
			width: image_columns,
			filterType: -1
		});
		
		//Iterate over all rows and columns
		for (let i = 0; i < image_rows; i++)
			for (let x = 0; x < image_columns; x++) {
				let local_index = (i*image_columns + x)*4; //RGBA index
				let local_value = asc_dataframe[i][x];
				let rgba;
				
				if (options.special_function)
					local_value = options.special_function(local_index, local_value);
				
				if (local_value !== undefined && local_value !== -9999) {
					if (options.mode === "number") {
						//Encode full 32-bit integer value into RGBA
						if (options.ceil)
							local_value = Math.ceil(local_value);
						rgba = Colour.encodeNumberAsRGBA(local_value);
					} else if (options.mode === "percentage") {
						//Scale using percentage mode (0-100 mapped to G channel)
						let local_g = Math.min(Math.round((local_value/max_value)*255), 255);
						rgba = [0, local_g, 0, 255];
					}
				} else {
					//NODATA values are fully transparent
					rgba = [0, 0, 0, 0];
				}
				
				//Set pixel values
				png.data[local_index] = rgba[0];
				png.data[local_index + 1] = rgba[1];
				png.data[local_index + 2] = rgba[2];
				png.data[local_index + 3] = rgba[3];
			}
		
		//Write PNG file
		png.pack().pipe(fs.createWriteStream(output_file_path))
			.on("finish", () => console.log(`.PNG output file written to ${output_file_path}`));
		
		//Return statement
		return {
			dataframe: asc_dataframe,
			max_value: max_value
		};
	};
	
	/**
	 * Reads an ASC file into a 2D Array/dataframe.
	 * @alias GeoASC.readFile
	 * 
	 * @param {string} arg0_file_path
	 * 
	 * @returns {Array.<any[]>}
	 */
	GeoASC.readFile = function (arg0_file_path) {
		//Convert from parameters
		let file_path = arg0_file_path;
		
		//Declare local instance variables
		let checks_passed = 0;
		let data_columns = 0;
		let data_frame = [];
		let data_rows = 0;
		let is_header = true;
		let no_data_value = undefined;
		let raw_data = fs.readFileSync(file_path, "utf8").split("\n");
		let row_index = 0;
		
		//Iterate over all raw_data lines
		for (let i = 0; i < raw_data.length; i++) {
			let local_line = raw_data[i].trim();
			
			//Internal guard clause; skip empty lines
			if (local_line === "") continue;
			
			//Header parsing
			if (is_header) {
				if (checks_passed < 3) {
					let split_line = local_line.split(" ");
					
					if (local_line.startsWith("ncols")) {
						data_columns = parseInt(split_line[1]);
						checks_passed++;
					} else if (local_line.startsWith("nrows")) {
						data_rows = parseInt(split_line[1]);
						checks_passed++;
					} else if (local_line.startsWith("NODATA_value")) {
						no_data_value = split_line[1];
						checks_passed++;
					}
				} else {
					//End of header; initialise data_frame
					is_header = false;
					data_frame = new Array(data_rows);
					
					for (let x = 0; x < data_rows; x++)
						data_frame[x] = new Array(data_columns);
				}
				continue;
			}
			
			//Data parsing
			let local_values = local_line.split(/\s+/);
			
			for (let x = 0; x < data_columns; x++)
				data_frame[row_index][x] = (local_values[x] === no_data_value) ?
					undefined : parseFloat(local_values[x]);
			
			row_index++;
			if (row_index >= data_rows) break; //Stop processing once all rows are read
		}
		
		//Return statement
		return data_frame;
	};
}