Source: formats/aaigrid/AAIGridReader.js

/*
 * Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
 * National Aeronautics and Space Administration. All rights reserved.
 *
 * The NASAWorldWind/WebWorldWind platform is 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.
 */
/**
 * @exports AAIGridReader
 */
define([
        './AAIGridConstants',
        './AAIGridMetadata',
        '../../error/ArgumentError',
        '../../util/Logger'
    ],
    function (AAIGridConstants,
              AAIGridMetadata,
              ArgumentError,
              Logger) {
        'use strict';

        /**
         * Constructs an AAIGrid reader object for the specified data source.
         * Call [getImageData]{@link AAIGridReader#getImageData} to retrieve the data as a typed array.
         * Use [metadata]{@link AAIGridReader#metadata} to access the metadata of this AAIGrid.
         * @alias AAIGridReader
         * @constructor
         * @classdesc Parses an AAIGrid and creates a typed array with its values.
         * @param {String|ArrayBuffer} dataSource The data source for the AAIGrid.
         * @throws {ArgumentError} If the specified data source is not a string or an array buffer.
         */
        var AAIGridReader = function (dataSource) {
            if (!dataSource) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AAIGridReader", "constructor", "missingDataSource")
                );
            }

            if (typeof dataSource !== 'string' && !(dataSource instanceof ArrayBuffer)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AAIGridReader", "constructor", "invalidDataSource")
                );
            }

            //Internal. Contains the numerical values of the AAIGrid.
            this._values = null;

            //Documented in defineProperties below.
            this._metadata = new AAIGridMetadata();

            var dataString = this.decodeData(dataSource);
            this.parse(dataString);
        };

        Object.defineProperties(AAIGridReader.prototype, {
            /**
             * An object containing the metadata of the AAIGrid file.
             * @memberof AAIGridReader.prototype
             * @type {AAIGridMetadata}
             * @readonly
             */
            metadata: {
                get: function () {
                    return this._metadata;
                }
            }
        });

        /**
         * Returns the content of the AAIGrid as an Int16Array or Float32Array.
         *
         * @return {Int16Array|Float32Array} The content of the AAIGrid.
         */
        AAIGridReader.prototype.getImageData = function () {
            return this._values;
        };

        /**
         * Internal. Applications should not call this method.
         * Decodes an arrayBuffer as a string.
         * If the dataSource is a string, no decoding takes place, the string is immediately returned.
         *
         * @param {String|ArrayBuffer} dataSource The data source to decode.
         * @return {String} The decoded array buffer.
         * @throws {ArgumentError} If the specified data source is not a string or an array buffer.
         */
        AAIGridReader.prototype.decodeData = function (dataSource) {
            if (typeof dataSource !== 'string' && !(dataSource instanceof ArrayBuffer)) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AAIGridReader", "decodeData", "invalidDataSource")
                );
            }

            if (typeof dataSource === 'string') {
                return dataSource;
            }

            if (typeof window.TextDecoder === 'function') {
                var decoder = new TextDecoder('utf-8');
                return decoder.decode(dataSource);
            }

            // The procedure below is for old browsers.
            var dataString = '';
            var bufferView = new Uint8Array(dataSource);

            var step = 65535; // The maximum number of arguments a function can take
            for (var i = 0, len = bufferView.length; i < len; i += step) {
                if (i + step > len) {
                    step = len - i;
                }
                dataString += String.fromCharCode.apply(null, bufferView.subarray(i, i + step));
            }

            return dataString;
        };

        /**
         * Internal. Applications should not call this method.
         * Parses the AAIGrid.
         *
         * @param {String} dataString The string to parse.
         * @throws {ArgumentError} If the specified data source is not a string.
         */
        AAIGridReader.prototype.parse = function (dataString) {
            if (typeof dataString !== 'string') {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AAIGridReader", "parse", "invalidDataString")
                );
            }

            var lines = dataString.replace(/\r?\n|\r/g, '\n').split('\n');

            var values = [];
            var isInt = true;
            for (var i = 0, len = lines.length; i < len; i++) {
                var line = lines[i];

                if (!line) {
                    continue;
                }

                var words = line
                    .trim()
                    .split(' ')
                    .map(function (word) {
                        return word.trim();
                    })
                    .filter(function (word) {
                        return word !== '';
                    });

                if (words[0] === AAIGridConstants.N_COLS) {
                    this.metadata.ncols = +words[1];
                }
                else if (words[0] === AAIGridConstants.N_ROWS) {
                    this.metadata.nrows = +words[1];
                }
                else if (words[0] === AAIGridConstants.X_LL_CORNER) {
                    this.metadata.xllcorner = +words[1];
                }
                else if (words[0] === AAIGridConstants.Y_LL_CORNER) {
                    this.metadata.yllcorner = +words[1];
                }
                else if (words[0] === AAIGridConstants.CELL_SIZE) {
                    this.metadata.cellsize = +words[1];
                }
                else if (words[0] === AAIGridConstants.NO_DATA_VALUE) {
                    this.metadata.NODATA_value = +words[1];
                }
                else if (isFinite(+words[0])) {
                    var numbers = words.map(function (word) {
                        var number = +word;
                        if (Math.floor(number) !== number) {
                            isInt = false;
                        }
                        return number;
                    });
                    values = values.concat(numbers);
                }
            }

            this._values = isInt ? new Int16Array(values) : new Float32Array(values);
        };

        /**
         * Attempts to retrieve the AAIGrid data from the provided URL, parse the data and return an AAIGridReader
         * using the provided parserCompletionCallback.
         *
         * @param {String} url An URL pointing to an external resource.
         * @param {Function} parserCompletionCallback A function to execute when the retrieval finishes, taking two
         * arguments:
         * <ul>
         *     <li>AAIGridReader instance in case of success, otherwise <code>null</code></li>
         *     <li>XMLHttpRequest instance</li>
         * </ul>
         */
        AAIGridReader.retrieveFromUrl = function (url, parserCompletionCallback) {
            if (!url) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AAIGridReader", "retrieveFromUrl", "missingUrl"));
            }

            if (!parserCompletionCallback) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "AAIGridReader", "retrieveFromUrl",
                        "The specified callback is null or undefined."));
            }

            var xhr = new XMLHttpRequest();

            xhr.onload = function () {
                if (this.status >= 200 && this.status < 300) {
                    parserCompletionCallback(new AAIGridReader(this.response), xhr);
                }
                else {
                    Logger.log(Logger.LEVEL_WARNING, "AAIGrid retrieval failed (" + xhr.statusText + "): " + url);
                    parserCompletionCallback(null, xhr);
                }
            };

            xhr.onerror = function () {
                Logger.log(Logger.LEVEL_WARNING, "AAIGrid retrieval failed: " + url);
                parserCompletionCallback(null, xhr);
            };

            xhr.ontimeout = function () {
                Logger.log(Logger.LEVEL_WARNING, "AAIGrid retrieval timed out: " + url);
                parserCompletionCallback(null, xhr);
            };

            xhr.open("GET", url, true);
            xhr.responseType = 'arraybuffer';
            xhr.send(null);
        };

        return AAIGridReader;
    });