Source: formats/shapefile/PrjFile.js

/*
 * Copyright 2015-2017 WorldWind Contributors
 *
 * 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 PrjFile
 */
define(['../../error/ArgumentError',
        '../../util/Logger',
        '../../error/NotYetImplementedError'
    ],

    function (ArgumentError,
              Logger,
              NotYetImplementedError) {
        "use strict";

        /**
         * Constructs an object for a projection descriptor file at a specified URL.
         * Applications typically do not call this constructor.
         * It is called by {@link Shapefile} to read the projection descriptor.
         * @alias PrjFile
         * @constructor
         * @classdesc Parses a projection descriptor file.
         * @param {String} url The location of the dBase file.
         * @throws {ArgumentError} If the specified URL is null or undefined.
         */
        var PrjFile = function(url) {
            if (!url) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "DBaseFile", "constructor", "missingUrl"));
            }

            // Internal use only. Intentionally not documented.
            this._url = url;

            // Internal use only. Intentionally not documented.
            this._completionCallback = null;

            // Internal use only. Intentionally not documented.
            this._params = null;
        };

        Object.defineProperties(PrjFile.prototype, {
            /**
             * The URL as specified to this projection file's constructor.
             * @memberof PrjFile.prototype
             * @type {String}
             * @readonly
             */
            url: {
                get: function () {
                    return this._url;
                }
            },
            /**
             * The OGC coordinate system descriptor.
             * @member PrjFile.prototype
             * @type {Object}
             * @readonly
             */
            coordinateSystem: {
                get: function () {
                    if (!this.params) {
                        return null;
                    }
                    else {
                        return this.params[PrjFile.COORDINATE_SYSTEM];
                    }
                }
            },
            /**
             * The full parameter descriptor.
             * @member PrjFile.prototype
             * @type {Object}
             * @readonly
             */
            params: {
                get: function () {
                    return this._params;
                }
            }
        });

        PrjFile.prototype.load = function(completionCallback) {
            this._completionCallback = completionCallback;

            this.requestUrl(this._url);
        };
        /**
         * TODO: this common code; refactor!
         * Internal use only.
         * Request data from the URL.
         * @param {String} url The URL for the requested data.
         */
        PrjFile.prototype.requestUrl = function(url) {
            var xhr = new XMLHttpRequest();

            xhr.open("GET", url, true);
            xhr.responseType = 'arraybuffer';
            xhr.onreadystatechange = (function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        var text = String.fromCharCode.apply(null, new Uint8Array(xhr.response));
                        this._params = this.decodeOGCCoordinateSystem(text);
                    }
                    else {
                        Logger.log(Logger.LEVEL_WARNING,
                            "PrjFile retrieval failed (" + xhr.statusText + "): " + url);
                    }

                    if (!!this._completionCallback) {
                        this._completionCallback(this);
                    }
                }
            }).bind(this);

            xhr.onerror = (function () {
                Logger.log(Logger.LEVEL_WARNING, "PrjFile retrieval failed: " + url);

                if (!!this._completionCallback) {
                    this._completionCallback(this);
                }
            }).bind(this);

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

                if (!!this._completionCallback) {
                    this._completionCallback(this);
                }
            }).bind(this);

            xhr.send(null);
        };

        /**
         * Retrieves the coordinate system and its parameters from an OGC coordinate system encoded as well-known text. For
         * details, see to the OGC Coordinate Transform Service (CT) specification at <a
         * href="https://www.opengeospatial.org/standards/ct">https://www.opengeospatial.org/standards/ct</a>. This recognizes
         * Geographic and UTM coordinate systems.
         *
         * If an exception occurs while parsing the coordinate system text, the parameter list is left unchanged.
         *
         * @param {String} text   A string containing an OGC coordinate system in well-known text format.
         *
         * @return {Object} An object containing key/value pairs extracted from the PRJ data.
         *
         * @throws ArgumentError If text is null.
         */
        PrjFile.prototype.decodeOGCCoordinateSystem = function(text) {
            if (!text) {
                new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "PrjFile", "decodeOGCCoordinateSystem", "missingText")
                );
            }

            var params = {};

            // Convert the coordinate system text to upper case. The coordinate system regular expressions match against
            // upper case characters.
            text = text.trim().toUpperCase();

            if (PrjFile.GEOGCS_WKT_PATTERN.test(text)) {
                params[PrjFile.COORDINATE_SYSTEM] = PrjFile.COORDINATE_SYSTEM_GEOGRAPHIC;
            }
            else {
                var result = text.match(PrjFile.PROJCS_WKT_PATTERN);
                if (!!result) {
                    params[PrfFile.COORDINATE_SYSTEM] = PrjFile.COORDINATE_SYSTEM_PROJECTED;

                    throw new NotYetImplementedError(Logger.log(Logger.LEVEL_SEVERE,
                        "PrjFile implementation  for projected coordinate systems in incomplete."));
                    // TODO: Complete the implementation; the Java implementation is summarized below.
                    //String
                    //csText = csMatcher.group(1);
                    //Matcher
                    //projMatcher = UTM_NAME_WKT_PATTERN.matcher(csText);
                    //if (projMatcher.matches()) {
                    //    params.setValue(AVKey.PROJECTION_NAME, AVKey.PROJECTION_UTM);
                    //
                    //    // Parse the UTM zone from the coordinate system name.
                    //    String
                    //    s = projMatcher.group(1);
                    //    if (s != null) {
                    //        Integer
                    //        i = WWUtil.makeInteger(s.trim());
                    //        if (i != null && i >= 1 && i <= 60)
                    //            params.setValue(AVKey.PROJECTION_ZONE, i);
                    //    }
                    //
                    //    if (params.getValue(AVKey.PROJECTION_ZONE) == null)
                    //        Logging.logger().warning(Logging.getMessage("generic.ZoneIsInvalid", s));
                    //
                    //    // Parse the UTM hemisphere form the coordinate system name.
                    //    s = projMatcher.group(2);
                    //    if (s != null) {
                    //        s = s.trim();
                    //        if (s.startsWith("N") || s.startsWith("n"))
                    //            params.setValue(AVKey.PROJECTION_HEMISPHERE, AVKey.NORTH);
                    //        else if (s.startsWith("S") || s.startsWith("s"))
                    //            params.setValue(AVKey.PROJECTION_HEMISPHERE, AVKey.SOUTH);
                    //    }
                    //
                    //    if (params.getValue(AVKey.PROJECTION_HEMISPHERE) == null)
                    //        Logging.logger().warning(Logging.getMessage("generic.HemisphereIsInvalid", s));
                    //}
                    //else {
                    //    params.setValue(AVKey.PROJECTION_NAME, AVKey.PROJECTION_UNKNOWN);
                    //}
                }
                else {
                    params[PrjFile.COORDINATE_SYSTEM] = PrjFile.COORDINATE_SYSTEM_UNKNOWN;
                }
            }

            return params;
        };

        /**
         * Indicates that an unknown coordinate system was encountered.
         * @returns {Boolean} True if an unknown coordinate system was encountered.
         */
        PrjFile.prototype.isUnknownCoordinateSystem = function() {
            return !this.params || this.params.coordinateSystem === PrjFile.COORDINATE_SYSTEM_UNKNOWN;
        };

        /**
         * Indicates that a known coordinate system was encountered.
         * @returns {Boolean} True if a known coordinate system was encountered.
         */
        PrjFile.prototype.isKnownCoordinateSystem = function() {
            return !!this.params && this.params.coordinateSystem !== PrjFile.COORDINATE_SYSTEM_UNKNOWN;
        };

        /**
         * Indicates that a geographic coordinate system was encountered.
         * @returns {Boolean} True if a geographic coordinate system was encountered.
         */
        PrjFile.prototype.isGeographicCoordinateSystem = function() {
            return !!this.params && this.params.coordinateSystem === PrjFile.COORDINATE_SYSTEM_GEOGRAPHIC;
        };

        /**
         * Indicates that a projected coordinate system was encountered.
         * @returns {boolean} True if a projected coordinate system was encountered.
         */
        PrjFile.prototype.isProjectedCoordinateSystem = function() {
            return !!this.params && this.params.coordinateSystem === PrjFile.COORDINATE_SYSTEM_PROJECTED;
        };

        /** Pattern matching the geographic coordinate system keyword in an OGC coordinate system well-known text. */
        PrjFile.GEOGCS_WKT_PATTERN = new RegExp("\\{*GEOGCS[\\[\\(](.*)[\\]\\)]\\}*");

        /** Pattern matching the projected coordinate system keyword in an OGC coordinate system well-known text. */
        PrjFile.PROJCS_WKT_PATTERN = new RegExp("\\{*PROJCS[\\[\\(](.*)[\\]\\)]\\}*");

        /** Pattern matching the UTM name in an projected coordinate system's well-known text. */
        PrjFile.UTM_NAME_WKT_PATTERN = new RegExp(".*UTM.*ZONE.*?(\\d+).*?([\\w\\s]+).*?");

        /**
         * A key for a coordinate system description.
         * @type {String}
         */
        PrjFile.COORDINATE_SYSTEM = 'Coordinate_system';

        /**
         * A geographic coordinate system description.
         * @type {String}
         */
        PrjFile.COORDINATE_SYSTEM_GEOGRAPHIC = 'Coordinate_system_geographic';

        /**
         * A projected coordinate system description.
         * @type {String}
         */
        PrjFile.COORDINATE_SYSTEM_PROJECTED = 'Coordinate_system_projected';

        /**
         * An unknown coordinate system.
         * @type {String}
         */
        PrjFile.COORDINATE_SYSTEM_UNKNOWN = 'Coordinate_system_unknown';

        /**
         * The key for the name of the projection.
         * @type {String}
         */
        PrjFile.PROJECTION_NAME = 'Projection_name';

        /**
         * A UTM projection descriptor.
         * @type {String}
         */
        PrjFile.PROJECTION_UTM = 'Projection_UTM';

        /**
         * The key for the UTM projection zone.
         * @type {String}
         */
        PrjFile.PROJECTION_ZONE = 'Projection_zone';

        /**
         * The key for the hemisphere descriptor.
         * @type {String}
         */
        PrjFile.PROJECTION_HEMISPHERE = 'Projection_hemisphere';

        /**
         * The descriptor for the northern hemisphere.
         * @type {String}
         */
        PrjFile.PROJECTION_HEMISPHERE_NORTH = 'Projection_hemisphere_north';

        /**
         * The descriptor for the southern hemisphere.
         * @type {String}
         */
        PrjFile.PROJECTION_HEMISPHERE_SOUTH = 'Projection_hemisphere_south';


        return PrjFile;
    }
);