/*global console, document, window*/
/** @license
*
* Copyright (C) 2012 K. Arthur Endsley (kaendsle@mtu.edu)
* Michigan Tech Research Institute (MTRI)
* 3600 Green Court, Suite 100, Ann Arbor, MI, 48105
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* @desc The Wkt namespace.
* @property {String} delimiter - The default delimiter for separating components of atomic geometry (coordinates)
* @namespace
* @global
*/
var Wkt = (function () { // Execute function immediately
return {
/**
* The default delimiter for separating components of atomic geometry (coordinates)
* @ignore
*/
delimiter: ' ',
/**
* Determines whether or not the passed Object is an Array.
* @param obj {Object} The Object in question
* @return {Boolean}
* @member Wkt.isArray
* @method
*/
isArray: function (obj) {
return !!(obj && obj.constructor === Array);
},
/**
* An object for reading WKT strings and writing geographic features
* @constructor Wkt.Wkt
* @param initializer {String} An optional WKT string for immediate read
* @property {Array} components - Holder for atomic geometry objects (internal representation of geometric components)
* @property {String} delimiter - The default delimiter for separating components of atomic geometry (coordinates)
* @property {Object} regExes - Some regular expressions copied from OpenLayers.Format.WKT.js
* @property {Boolean} wrapVerticies - True to wrap vertices in MULTIPOINT geometries; If true: MULTIPOINT((30 10),(10 30),(40 40)); If false: MULTIPOINT(30 10,10 30,40 40)
* @return {Wkt.Wkt}
* @memberof Wkt
*/
Wkt: function (initializer) {
var beginsWith, endsWith, trim;
/**
* Returns true if the substring is found at the beginning of the string.
* @param str {String} The String to search
* @param sub {String} The substring of interest
* @return {Boolean}
* @private
*/
beginsWith = function (str, sub) {
return str.substring(0, sub.length) === sub;
};
/**
* Returns true if the substring is found at the end of the string.
* @param str {String} The String to search
* @param sub {String} The substring of interest
* @return {Boolean}
* @private
*/
endsWith = function (str, sub) {
return str.substring(str.length - sub.length) === sub;
};
/**
* Removes given character String(s) from a String.
* @param str {String} The String to search
* @param sub {String} The String character(s) to trim
* @return {String} The trimmed string
* @private
*/
trim = function (str, sub) {
sub = sub || ' '; // Defaults to trimming spaces
// Trim beginning spaces
while (beginsWith(str, sub)) {
str = str.substring(1);
}
// Trim ending spaces
while (endsWith(str, sub)) {
str = str.substring(0, str.length - 1);
}
return str;
};
/**
* The default delimiter between X and Y coordinates.
* @ignore
*/
this.delimiter = Wkt.delimiter;
/**
* Configuration parameter for controlling how Wicket seralizes
* MULTIPOINT strings. Examples; both are valid WKT:
* If true: MULTIPOINT((30 10),(10 30),(40 40))
* If false: MULTIPOINT(30 10,10 30,40 40)
* @ignore
*/
this.wrapVertices = true;
/**
* Some regular expressions copied from OpenLayers.Format.WKT.js
* @ignore
*/
this.regExes = {
'typeStr': /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,
'spaces': /\s+|\+/, // Matches the '+' or the empty space
'numeric': /-*\d+\.*\d+/,
'comma': /\s*,\s*/,
'parenComma': /\)\s*,\s*\(/,
'coord': /-*\d+\.*\d+ -*\d+\.*\d+/, // e.g. "24 -14"
'doubleParenComma': /\)\s*\)\s*,\s*\(\s*\(/,
'trimParens': /^\s*\(?(.*?)\)?\s*$/
};
/**
* The internal representation of geometry--the "components" of geometry.
* @ignore
*/
this.components = undefined;
/**
* Returns true if the internal geometry is a collection of geometries.
* @return {Boolean} Returns true when it is a collection
* @memberof Wkt.Wkt
* @method
*/
this.isCollection = function () {
switch (this.type.slice(0, 5)) {
case 'multi':
// Trivial; any multi-geometry is a collection
return true;
case 'polyg':
// Polygons with holes are "collections" of rings
return true;
default:
// Any other geometry is not a collection
return false;
}
};
/**
* Compares two x,y coordinates for equality.
* @param a {Object} An object with x and y properties
* @param b {Object} An object with x and y properties
* @return {Boolean}
* @memberof Wkt.Wkt
* @method
*/
this.sameCoords = function (a, b) {
return (a.x === b.x && a.y === b.y);
};
/**
* Sets internal geometry (components) from framework geometry (e.g.
* Google Polygon objects or google.maps.Polygon).
* @param obj {Object} The framework-dependent geometry representation
* @return {Wkt.Wkt} The object itself
* @memberof Wkt.Wkt
* @method
*/
this.fromObject = function (obj) {
var result = this.deconstruct.call(this, obj);
this.components = result.components;
this.isRectangle = result.isRectangle || false;
this.type = result.type;
return this;
};
/**
* Creates external geometry objects based on a plug-in framework's
* construction methods and available geometry classes.
* @param config {Object} An optional framework-dependent properties specification
* @return {Object} The framework-dependent geometry representation
* @memberof Wkt.Wkt
* @method
*/
this.toObject = function (config) {
return this.construct[this.type].call(this, config);
};
/**
* Reads a WKT string, validating and incorporating it.
* @param wkt {String} A WKT string
* @return {Array} An Array of internal geometry objects
* @memberof Wkt.Wkt
* @method
*/
this.read = function (wkt) {
var matches;
matches = this.regExes.typeStr.exec(wkt);
if (matches) {
this.type = matches[1].toLowerCase();
this.base = matches[2];
if (this.ingest[this.type]) {
this.components = this.ingest[this.type].apply(this, [this.base]);
}
} else {
console.log("Invalid WKT string provided to read()");
throw {
name: "WKTError",
message: "Invalid WKT string provided to read()"
};
}
return this.components;
}; // eo readWkt
/**
* Writes a WKT string.
* @param components {Array} An Array of internal geometry objects
* @return {String} The corresponding WKT representation
* @memberof Wkt.Wkt
* @method
*/
this.write = function (components) {
var i, pieces, data;
components = components || this.components;
pieces = [];
pieces.push(this.type.toUpperCase() + '(');
for (i = 0; i < components.length; i += 1) {
if (this.isCollection() && i > 0) {
pieces.push(',');
}
// There should be an extract function for the named type
if (!this.extract[this.type]) {
return null;
}
data = this.extract[this.type].apply(this, [components[i]]);
if (this.isCollection() && this.type !== 'multipoint') {
pieces.push('(' + data + ')');
} else {
pieces.push(data);
// If not at the end of the components, add a comma
if (i !== (components.length - 1) && this.type !== 'multipoint') {
pieces.push(',');
}
}
}
pieces.push(')');
return pieces.join('');
};
/**
* This object contains functions as property names that extract WKT
* strings from the internal representation.
* @memberof Wkt.Wkt
* @namespace Wkt.Wkt.extract
* @instance
*/
this.extract = {
/**
* Return a WKT string representing atomic (point) geometry
* @param point {Object} An object with x and y properties
* @return {String} The WKT representation
* @memberof Wkt.Wkt.extract
* @instance
*/
point: function (point) {
return point.x + this.delimiter + point.y;
},
/**
* Return a WKT string representing multiple atoms (points)
* @param multipoint {Array} Multiple x-and-y objects
* @return {String} The WKT representation
* @memberof Wkt.Wkt.extract
* @instance
*/
multipoint: function (multipoint) {
var i, parts = [], s;
for (i = 0; i < multipoint.length; i += 1) {
s = this.extract.point.apply(this, [multipoint[i]]);
if (this.wrapVertices) {
s = '(' + s + ')';
}
parts.push(s);
}
return parts.join(',');
},
/**
* Return a WKT string representing a chain (linestring) of atoms
* @param linestring {Array} Multiple x-and-y objects
* @return {String} The WKT representation
* @memberof Wkt.Wkt.extract
* @instance
*/
linestring: function (linestring) {
// Extraction of linestrings is the same as for points
return this.extract.point.apply(this, [linestring]);
},
/**
* Return a WKT string representing multiple chains (multilinestring) of atoms
* @param multilinestring {Array} Multiple of multiple x-and-y objects
* @return {String} The WKT representation
* @memberof Wkt.Wkt.extract
* @instance
*/
multilinestring: function (multilinestring) {
var i, parts = [];
for (i = 0; i < multilinestring.length; i += 1) {
parts.push(this.extract.linestring.apply(this, [multilinestring[i]]));
}
return parts.join(',');
},
/**
* Return a WKT string representing multiple atoms in closed series (polygon)
* @param polygon {Array} Collection of ordered x-and-y objects
* @return {String} The WKT representation
* @memberof Wkt.Wkt.extract
* @instance
*/
polygon: function (polygon) {
// Extraction of polygons is the same as for multilinestrings
return this.extract.multilinestring.apply(this, [polygon]);
},
/**
* Return a WKT string representing multiple closed series (multipolygons) of multiple atoms
* @param multipolygon {Array} Collection of ordered x-and-y objects
* @return {String} The WKT representation
* @memberof Wkt.Wkt.extract
* @instance
*/
multipolygon: function (multipolygon) {
var i, parts = [];
for (i = 0; i < multipolygon.length; i += 1) {
parts.push('(' + this.extract.polygon.apply(this, [multipolygon[i]]) + ')');
}
return parts.join(',');
},
geometrycollection: function (str) {
console.log('The geometrycollection WKT type is not yet supported.');
}
};
/**
* This object contains functions as property names that ingest WKT
* strings into the internal representation.
* @memberof Wkt.Wkt
* @namespace Wkt.Wkt.ingest
* @instance
*/
this.ingest = {
/**
* Return point feature given a point WKT fragment.
* @param str {String} A WKT fragment representing the point
* @memberof Wkt.Wkt.ingest
* @instance
*/
point: function (str) {
var coords = trim(str).split(this.regExes.spaces);
// In case a parenthetical group of coordinates is passed...
return [{ // ...Search for numeric substrings
x: parseFloat(this.regExes.numeric.exec(coords[0])[0]),
y: parseFloat(this.regExes.numeric.exec(coords[1])[0])
}];
},
/**
* Return a multipoint feature given a multipoint WKT fragment.
* @param str {String} A WKT fragment representing the multipoint
* @memberof Wkt.Wkt.ingest
* @instance
*/
multipoint: function (str) {
var i, components, points;
components = [];
points = trim(str).split(this.regExes.comma);
for (i = 0; i < points.length; i += 1) {
components.push(this.ingest.point.apply(this, [points[i]]));
}
return components;
},
/**
* Return a linestring feature given a linestring WKT fragment.
* @param str {String} A WKT fragment representing the linestring
* @memberof Wkt.Wkt.ingest
* @instance
*/
linestring: function (str) {
var i, multipoints, components;
// In our x-and-y representation of components, parsing
// multipoints is the same as parsing linestrings
multipoints = this.ingest.multipoint.apply(this, [str]);
// However, the points need to be joined
components = [];
for (i = 0; i < multipoints.length; i += 1) {
components = components.concat(multipoints[i]);
}
return components;
},
/**
* Return a multilinestring feature given a multilinestring WKT fragment.
* @param str {String} A WKT fragment representing the multilinestring
* @memberof Wkt.Wkt.ingest
* @instance
*/
multilinestring: function (str) {
var i, components, line, lines;
components = [];
lines = trim(str).split(this.regExes.doubleParenComma);
if (lines.length === 1) { // If that didn't work...
lines = trim(str).split(this.regExes.parenComma);
}
for (i = 0; i < lines.length; i += 1) {
line = lines[i].replace(this.regExes.trimParens, '$1');
components.push(this.ingest.linestring.apply(this, [line]));
}
return components;
},
/**
* Return a polygon feature given a polygon WKT fragment.
* @param str {String} A WKT fragment representing the polygon
* @memberof Wkt.Wkt.ingest
* @instance
*/
polygon: function (str) {
var i, j, components, subcomponents, ring, rings;
rings = trim(str).split(this.regExes.parenComma);
components = []; // Holds one or more rings
for (i = 0; i < rings.length; i += 1) {
ring = rings[i].replace(this.regExes.trimParens, '$1').split(this.regExes.comma);
subcomponents = []; // Holds the outer ring and any inner rings (holes)
for (j = 0; j < ring.length; j += 1) {
// Split on the empty space or '+' character (between coordinates)
subcomponents.push({
x: parseFloat(ring[j].split(this.regExes.spaces)[0]),
y: parseFloat(ring[j].split(this.regExes.spaces)[1])
});
}
components.push(subcomponents);
}
return components;
},
/**
* Return a multipolygon feature given a multipolygon WKT fragment.
* @param str {String} A WKT fragment representing the multipolygon
* @memberof Wkt.Wkt.ingest
* @instance
*/
multipolygon: function (str) {
var i, components, polygon, polygons;
components = [];
polygons = trim(str).split(this.regExes.doubleParenComma);
for (i = 0; i < polygons.length; i += 1) {
polygon = polygons[i].replace(this.regExes.trimParens, '$1');
components.push(this.ingest.polygon.apply(this, [polygon]));
}
return components;
},
/**
* Return an array of features given a geometrycollection WKT fragment.
* @param str {String} A WKT fragment representing the geometry collection
* @memberof Wkt.Wkt.ingest
* @instance
*/
geometrycollection: function (str) {
console.log('The geometrycollection WKT type is not yet supported.');
}
}; // eo ingest
// An initial WKT string may be provided
if (initializer && typeof initializer === 'string') {
this.read(initializer);
} else if (this.fromGeometry) { // Or, an initial geometry object to be read
this.fromGeometry(initializer);
}
} // eo WKt.Wkt
}; // eo return
}()); // eo Wkt