Spade
Mini Shell
/**
* @fileOverview Renders KML on the Google Maps JavaScript API Version 3
* @name GeoXML3
* @author Sterling Udell, Larry Ross, Brendan Byrd
* @see http://code.google.com/p/geoxml3/
*
* geoxml3.js
*
* Renders KML on the Google Maps JavaScript API Version 3
* http://code.google.com/p/geoxml3/
*
* Copyright 2010 Sterling Udell, Larry Ross
*
* 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.
*
*/
/**
* A MultiGeometry object that will allow multiple polylines in a
MultiGeometry
* containing LineStrings to be treated as a single object
*
* @param {MutiGeometryOptions} anonymous object. Available properties:
* map: The map on which to attach the MultiGeometry
* paths: the individual polylines
* polylineOptions: options to use when constructing all the polylines
*
* @constructor
*/
// only if Google Maps API included
if (!!window.google && !! google.maps) {
function MultiGeometry(multiGeometryOptions) {
function createPolyline(polylineOptions, mg) {
var polyline = new google.maps.Polyline(polylineOptions);
google.maps.event.addListener(polyline,'click',
function(evt) { google.maps.event.trigger(mg,'click',evt);});
google.maps.event.addListener(polyline,'dblclick',
function(evt) { google.maps.event.trigger(mg, 'dblclick',
evt);});
google.maps.event.addListener(polyline,'mousedown',
function(evt) { google.maps.event.trigger(mg, 'mousedown',
evt);});
google.maps.event.addListener(polyline,'mousemove',
function(evt) { google.maps.event.trigger(mg, 'mousemove',
evt);});
google.maps.event.addListener(polyline,'mouseout',
function(evt) { google.maps.event.trigger(mg, 'mouseout',
evt);});
google.maps.event.addListener(polyline,'mouseover',
function(evt) { google.maps.event.trigger(mg, 'mouseover',
evt);});
google.maps.event.addListener(polyline,'mouseup',
function(evt) { google.maps.event.trigger(mg, 'mouseup', evt);});
google.maps.event.addListener(polyline,'rightclick',
function(evt) { google.maps.event.trigger(mg, 'rightclick',
evt);});
return polyline;
}
this.setValues(multiGeometryOptions);
this.polylines = [];
for (i=0; i<this.paths.length;i++) {
var polylineOptions = multiGeometryOptions;
polylineOptions.path = this.paths[i];
var polyline = createPolyline(polylineOptions,this);
// Bind the polyline properties to the MultiGeometry properties
this.polylines.push(polyline);
}
}
MultiGeometry.prototype = new google.maps.MVCObject();
MultiGeometry.prototype.changed = function(key) {
// alert(key+" changed");
if (this.polylines) {
for (var i=0; i<this.polylines.length; i++) {
this.polylines[i].set(key,this.get(key));
}
}
};
MultiGeometry.prototype.setMap = function(map) {
this.set('map',map); };
MultiGeometry.prototype.getMap = function() { return
this.get('map'); };
}
// Extend the global String object with a method to remove leading and
trailing whitespace
if (!String.prototype.trim) {
/**
* Remove leading and trailing whitespace.
*
* @augments String
* @return {String}
*/
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, '');
};
}
/**
* @namespace The GeoXML3 namespace.
*/
geoXML3 = window.geoXML3 || {instances: []};
/**
* Constructor for the root KML parser object.
*
* <p>All top-level objects and functions are declared under a
namespace of geoXML3.
* The core object is geoXML3.parser; typically, you'll instantiate a
one parser
* per map.</p>
*
* @class Main XML parser.
* @param {geoXML3.parserOptions} options
*/
geoXML3.parser = function (options) {
// Inherit from Google MVC Object to include event handling
google.maps.MVCObject.call(this);
// Private variables
var parserOptions = new geoXML3.parserOptions(options);
var docs = []; // Individual KML documents
var docsByUrl = {}; // Same docs as an hash by cleanURL
var kmzMetaData = {}; // Extra files from KMZ data
var styles = {}; // Global list of styles
var lastPlacemark;
var parserName;
if (!parserOptions.infoWindow && parserOptions.singleInfoWindow)
parserOptions.infoWindow = new google.maps.InfoWindow();
var parseKmlString = function (kmlString, docSet) {
// Internal values for the set of documents as a whole
var internals = {
parser: this,
docSet: docSet || [],
remaining: 1,
parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
};
thisDoc = new Object();
thisDoc.internals = internals;
internals.docSet.push(thisDoc);
render(geoXML3.xmlParse(kmlString),thisDoc);
}
var parse = function (urls, docSet) {
// Process one or more KML documents
if (!parserName) {
parserName = 'geoXML3.instances[' +
(geoXML3.instances.push(this) - 1) + ']';
}
if (typeof urls === 'string') {
// Single KML document
urls = [urls];
}
// Internal values for the set of documents as a whole
var internals = {
parser: this,
docSet: docSet || [],
remaining: urls.length,
parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
};
var thisDoc, j;
for (var i = 0; i < urls.length; i++) {
var baseUrl = cleanURL(defileURL(location.pathname), urls[i]);
if (docsByUrl[baseUrl]) {
// Reloading an existing document
thisDoc = docsByUrl[baseUrl];
thisDoc.reload = true;
}
else {
thisDoc = new Object();
thisDoc.baseUrl = baseUrl;
internals.docSet.push(thisDoc);
}
thisDoc.url = urls[i];
thisDoc.internals = internals;
fetchDoc(thisDoc.url, thisDoc);
}
};
function fetchDoc(url, doc, resFunc) {
resFunc = resFunc || function (responseXML) { render(responseXML, doc);
};
if (typeof ZipFile === 'function' && typeof JSIO ===
'object' && typeof JSIO.guessFileType ===
'function') { // KMZ support requires these modules loaded
// if url is a data URI scheme, do not guess type based on extension.
if (/^data:[^,]*(kmz)/.test(doc.baseUrl)) {
contentType = JSIO.FileType.Binary;
} else if (/^data:[^,]*(kml|xml)/.test(doc.baseUrl)) {
contentType = JSIO.FileType.XML;
} else if (/^data:/.test(doc.baseUrl)) {
contentType = JSIO.FileType.Unknown;
} else if (parserOptions.forceZip) {
contentType = JSIO.FileType.Binary;
} else {
contentType = JSIO.guessFileType(doc.baseUrl);
}
if (contentType == JSIO.FileType.Binary || contentType ==
JSIO.FileType.Unknown) {
doc.isCompressed = true;
doc.baseDir = doc.baseUrl + '/';
geoXML3.fetchZIP(url, resFunc, doc.internals.parser);
return;
}
}
doc.isCompressed = false;
doc.baseDir = defileURL(doc.baseUrl);
geoXML3.fetchXML(url, resFunc);
}
var hideDocument = function (doc) {
if (!doc) doc = docs[0];
// Hide the map objects associated with a document
var i;
if (!!doc.markers) {
for (i = 0; i < doc.markers.length; i++) {
if(!!doc.markers[i].infoWindow) doc.markers[i].infoWindow.close();
doc.markers[i].setVisible(false);
}
}
if (!!doc.ggroundoverlays) {
for (i = 0; i < doc.ggroundoverlays.length; i++) {
doc.ggroundoverlays[i].setOpacity(0);
}
}
if (!!doc.gpolylines) {
for (i=0;i<doc.gpolylines.length;i++) {
if(!!doc.gpolylines[i].infoWindow)
doc.gpolylines[i].infoWindow.close();
doc.gpolylines[i].setMap(null);
}
}
if (!!doc.gpolygons) {
for (i=0;i<doc.gpolygons.length;i++) {
if(!!doc.gpolygons[i].infoWindow)
doc.gpolygons[i].infoWindow.close();
doc.gpolygons[i].setMap(null);
}
}
};
var showDocument = function (doc) {
if (!doc) doc = docs[0];
// Show the map objects associated with a document
var i;
if (!!doc.markers) {
for (i = 0; i < doc.markers.length; i++) {
doc.markers[i].setVisible(true);
}
}
if (!!doc.ggroundoverlays) {
for (i = 0; i < doc.ggroundoverlays.length; i++) {
doc.ggroundoverlays[i].setOpacity(doc.ggroundoverlays[i].percentOpacity_);
}
}
if (!!doc.gpolylines) {
for (i=0;i<doc.gpolylines.length;i++) {
doc.gpolylines[i].setMap(parserOptions.map);
}
}
if (!!doc.gpolygons) {
for (i=0;i<doc.gpolygons.length;i++) {
doc.gpolygons[i].setMap(parserOptions.map);
}
}
};
var defaultStyle = {
balloon: {
bgColor: 'ffffffff',
textColor: 'ff000000',
text:
"<h3>$[name]</h3>\n<div>$[description]</div>\n<div>$[geDirections]</div>",
displayMode: 'default'
},
icon: {
scale: 1.0,
dim: {
x: 0,
y: 0,
w: -1,
h: -1
},
hotSpot: {
x: 0.5,
y: 0.5,
xunits: 'fraction',
yunits: 'fraction'
}
},
line: {
color: 'ffffffff', // white (KML default)
colorMode: 'normal',
width: 1.0
},
poly: {
color: 'ffffffff', // white (KML default)
colorMode: 'normal',
fill: true,
outline: true
}
};
var kmlNS = 'http://www.opengis.net/kml/2.2';
var gxNS = 'http://www.google.com/kml/ext/2.2';
var nodeValue = geoXML3.nodeValue;
var getBooleanValue = geoXML3.getBooleanValue;
var getElementsByTagNameNS = geoXML3.getElementsByTagNameNS;
var getElementsByTagName = geoXML3.getElementsByTagName;
function processStyleUrl(node) {
var styleUrlStr = nodeValue(getElementsByTagName(node,
'styleUrl')[0]);
if (!!styleUrlStr && styleUrlStr.indexOf('#') != -1)
var styleUrl = styleUrlStr.split('#');
else var styleUrl = ["",""];
return styleUrl;
}
function processStyle(thisNode, baseUrl, styleID, baseDir) {
var style = (baseUrl === '{inline}') ? clone(defaultStyle) :
(styles[baseUrl][styleID] = styles[baseUrl][styleID] ||
clone(defaultStyle));
var styleNodes = getElementsByTagName(thisNode,
'BalloonStyle');
if (!!styleNodes && styleNodes.length > 0) {
style.balloon.bgColor =
nodeValue(getElementsByTagName(styleNodes[0], 'bgColor')[0],
style.balloon.bgColor);
style.balloon.textColor =
nodeValue(getElementsByTagName(styleNodes[0], 'textColor')[0],
style.balloon.textColor);
style.balloon.text =
nodeValue(getElementsByTagName(styleNodes[0], 'text')[0],
style.balloon.text);
style.balloon.displayMode =
nodeValue(getElementsByTagName(styleNodes[0], 'displayMode')[0],
style.balloon.displayMode);
}
// style.list = (unsupported; doesn't make sense in Google Maps)
var styleNodes = getElementsByTagName(thisNode, 'IconStyle');
if (!!styleNodes && styleNodes.length > 0) {
var icon = style.icon;
icon.scale = parseFloat(nodeValue(getElementsByTagName(styleNodes[0],
'scale')[0], icon.scale));
// style.icon.heading = (unsupported; not supported in API)
// style.icon.color = (unsupported; not supported in API)
// style.icon.colorMode = (unsupported; not supported in API)
styleNodes = getElementsByTagName(styleNodes[0],
'hotSpot');
if (!!styleNodes && styleNodes.length > 0) {
icon.hotSpot = {
x: styleNodes[0].getAttribute('x'),
y: styleNodes[0].getAttribute('y'),
xunits: styleNodes[0].getAttribute('xunits'),
yunits: styleNodes[0].getAttribute('yunits')
};
}
styleNodes = getElementsByTagName(thisNode, 'Icon');
if (!!styleNodes && styleNodes.length > 0) {
icon.href = nodeValue(getElementsByTagName(styleNodes[0],
'href')[0]);
icon.url = cleanURL(baseDir, icon.href);
// Detect images buried in KMZ files (and use a base64 encoded URL)
if (kmzMetaData[icon.url]) icon.url =
kmzMetaData[icon.url].dataUrl;
// Support for icon palettes and exact size dimensions
icon.dim = {
x: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS,
'x')[0], icon.dim.x)),
y: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS,
'y')[0], icon.dim.y)),
w: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS,
'w')[0], icon.dim.w)),
h: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS,
'h')[0], icon.dim.h))
};
// certain occasions where we need the pixel size of the image
(like the default settings...)
// (NOTE: Scale is applied to entire image, not just the section of
the icon palette. So,
// if we need scaling, we'll need the img dimensions no
matter what.)
if (true /* (icon.dim.w < 0 || icon.dim.h < 0) &&
(icon.xunits != 'pixels' || icon.yunits == 'fraction')
|| icon.scale != 1.0 */) {
// (hopefully, this will load by the time we need it...)
icon.img = new Image();
icon.img.onload = function() {
if (icon.dim.w < 0 || icon.dim.h < 0) {
icon.dim.w = this.width;
icon.dim.h = this.height;
} else {
icon.dim.th = this.height;
}
};
icon.img.src = icon.url;
// sometimes the file is already cached and it never calls onLoad
if (icon.img.width > 0) {
if (icon.dim.w < 0 || icon.dim.h < 0) {
icon.dim.w = icon.img.width;
icon.dim.h = icon.img.height;
} else {
icon.dim.th = icon.img.height;
}
}
}
}
}
// style.label = (unsupported; may be possible but not with API)
styleNodes = getElementsByTagName(thisNode, 'LineStyle');
if (!!styleNodes && styleNodes.length > 0) {
style.line.color = nodeValue(getElementsByTagName(styleNodes[0],
'color')[0], style.line.color);
style.line.colorMode = nodeValue(getElementsByTagName(styleNodes[0],
'colorMode')[0], style.line.colorMode);
style.line.width = nodeValue(getElementsByTagName(styleNodes[0],
'width')[0], style.line.width);
// style.line.outerColor = (unsupported; not supported in API)
// style.line.outerWidth = (unsupported; not supported in API)
// style.line.physicalWidth = (unsupported; unneccesary in Google
Maps)
// style.line.labelVisibility = (unsupported; possible to implement)
}
styleNodes = getElementsByTagName(thisNode, 'PolyStyle');
if (!!styleNodes && styleNodes.length > 0) {
style.poly.color = nodeValue(
getElementsByTagName(styleNodes[0], 'color')[0],
style.poly.color);
style.poly.colorMode = nodeValue(
getElementsByTagName(styleNodes[0], 'colorMode')[0],
style.poly.colorMode);
style.poly.outline =
getBooleanValue(getElementsByTagName(styleNodes[0],
'outline')[0], style.poly.outline);
style.poly.fill =
getBooleanValue(getElementsByTagName(styleNodes[0], 'fill')[0],
style.poly.fill);
}
return style;
}
// from
http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object
// http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
function clone(obj){
if(obj == null || typeof(obj) != 'object') return obj;
if (obj.cloneNode) return obj.cloneNode(true);
var temp = new obj.constructor();
for(var key in obj) temp[key] = clone(obj[key]);
return temp;
}
function processStyleMap(thisNode, baseUrl, styleID, baseDir) {
var pairs = getElementsByTagName(thisNode, 'Pair');
var map = new Object();
// add each key to the map
for (var pr=0;pr<pairs.length;pr++) {
var pairKey = nodeValue(getElementsByTagName(pairs[pr],
'key')[0]);
var pairStyle = nodeValue(getElementsByTagName(pairs[pr],
'Style')[0]);
var pairStyleUrl = processStyleUrl(pairs[pr]);
var pairStyleBaseUrl = pairStyleUrl[0] ? cleanURL(baseDir,
pairStyleUrl[0]) : baseUrl;
var pairStyleID = pairStyleUrl[1];
if (!!pairStyle) {
map[pairKey] = processStyle(pairStyle, pairStyleBaseUrl,
pairStyleID);
} else if (!!pairStyleID &&
!!styles[pairStyleBaseUrl][pairStyleID]) {
map[pairKey] = clone(styles[pairStyleBaseUrl][pairStyleID]);
}
}
if (!!map["normal"]) {
styles[baseUrl][styleID] = clone(map["normal"]);
} else {
styles[baseUrl][styleID] = clone(defaultStyle);
}
if (!!map["highlight"] &&
!!parserOptions.processStyles) {
processStyleID(map["highlight"]);
}
styles[baseUrl][styleID].map = clone(map);
}
function processPlacemarkCoords(node, tag) {
var parent = getElementsByTagName(node, tag);
var coordListA = [];
for (var i=0; i<parent.length; i++) {
var coordNodes = getElementsByTagName(parent[i],
'coordinates');
if (!coordNodes) {
if (coordListA.length > 0) {
break;
} else {
return [{coordinates: []}];
}
}
for (var j=0; j<coordNodes.length;j++) {
var coords = nodeValue(coordNodes[j]).trim();
coords = coords.replace(/,\s+/g, ',');
var path = coords.split(/\s+/g);
var pathLength = path.length;
var coordList = [];
for (var k = 0; k < pathLength; k++) {
coords = path[k].split(',');
if (!isNaN(coords[0]) && !isNaN(coords[1])) {
coordList.push({
lat: parseFloat(coords[1]),
lng: parseFloat(coords[0]),
alt: parseFloat(coords[2])
});
}
}
coordListA.push({coordinates: coordList});
}
}
return coordListA;
}
var render = function (responseXML, doc) {
// Callback for retrieving a KML document: parse the KML and display it
on the map
if (!responseXML || responseXML == "failed parse") {
// Error retrieving the data
geoXML3.log('Unable to retrieve ' + doc.url);
if (parserOptions.failedParse) parserOptions.failedParse(doc);
doc.failed = true;
return;
} else if (responseXML.parseError &&
responseXML.parseError.errorCode != 0) {
// IE parse error
var err = responseXML.parseError;
var msg = 'Parse error in line ' + err.line + ', col
' + err.linePos + ' (error code: ' + err.errorCode +
")\n" +
"\nError Reason: " + err.reason +
'Error Line: ' + err.srcText;
geoXML3.log('Unable to retrieve ' + doc.url + ':
' + msg);
if (parserOptions.failedParse) parserOptions.failedParse(doc);
doc.failed = true;
return;
} else if (responseXML.documentElement &&
responseXML.documentElement.nodeName == 'parsererror') {
// Firefox parse error
geoXML3.log('Unable to retrieve ' + doc.url + ':
' + responseXML.documentElement.childNodes[0].nodeValue);
if (parserOptions.failedParse) parserOptions.failedParse(doc);
doc.failed = true;
return;
} else if (!doc) {
throw 'geoXML3 internal error: render called with null
document';
} else { //no errors
var i;
doc.placemarks = [];
doc.groundoverlays = [];
doc.ggroundoverlays = [];
doc.networkLinks = [];
doc.gpolygons = [];
doc.gpolylines = [];
// Check for dependent KML files
var nodes = getElementsByTagName(responseXML, 'styleUrl');
var docSet = doc.internals.docSet;
for (var i = 0; i < nodes.length; i++) {
var url = nodeValue(nodes[i]).split('#')[0];
if (!url) continue; // #id (inside doc)
var rUrl = cleanURL( doc.baseDir, url );
if (rUrl === doc.baseUrl) continue; // self
if (docsByUrl[rUrl]) continue; // already loaded
var thisDoc;
var j = docSet.indexOfObjWithItem('baseUrl', rUrl);
if (j != -1) {
// Already listed to be loaded, but probably in the wrong order.
// Load it right away to immediately resolve dependency.
thisDoc = docSet[j];
if (thisDoc.failed) continue; // failed to load last time;
don't retry it again
}
else {
// Not listed at all; add it in
thisDoc = new Object();
thisDoc.url = rUrl; // url can't be trusted inside
KMZ files, since it may .. outside of the archive
thisDoc.baseUrl = rUrl;
thisDoc.internals = doc.internals;
doc.internals.docSet.push(thisDoc);
doc.internals.remaining++;
}
// render dependent KML first then re-run renderer
fetchDoc(rUrl, thisDoc, function (thisResXML) {
render(thisResXML, thisDoc);
render(responseXML, doc);
});
// to prevent cross-dependency issues, just load the one
// file first and re-check the rest later
return;
}
// Parse styles
doc.styles = styles[doc.baseUrl] = styles[doc.baseUrl] || {};
var styleID, styleNodes;
nodes = getElementsByTagName(responseXML, 'Style');
nodeCount = nodes.length;
for (i = 0; i < nodeCount; i++) {
thisNode = nodes[i];
var styleID = thisNode.getAttribute('id');
if (!!styleID) processStyle(thisNode, doc.baseUrl, styleID,
doc.baseDir);
}
// Parse StyleMap nodes
nodes = getElementsByTagName(responseXML, 'StyleMap');
for (i = 0; i < nodes.length; i++) {
thisNode = nodes[i];
var styleID = thisNode.getAttribute('id');
if (!!styleID) processStyleMap(thisNode, doc.baseUrl, styleID,
doc.baseDir);
}
if (!!parserOptions.processStyles || !parserOptions.createMarker) {
// Convert parsed styles into GMaps equivalents
processStyles(doc);
}
// Parse placemarks
if (!!doc.reload && !!doc.markers) {
for (i = 0; i < doc.markers.length; i++) {
doc.markers[i].active = false;
}
}
var placemark, node, coords, path, marker, poly;
var pathLength, marker, polygonNodes, coordList;
var placemarkNodes = getElementsByTagName(responseXML,
'Placemark');
for (pm = 0; pm < placemarkNodes.length; pm++) {
// Init the placemark object
node = placemarkNodes[pm];
var styleUrl = processStyleUrl(node);
placemark = {
name: nodeValue(getElementsByTagName(node,
'name')[0]),
description: nodeValue(getElementsByTagName(node,
'description')[0]),
styleUrl: styleUrl.join('#'),
styleBaseUrl: styleUrl[0] ? cleanURL(doc.baseDir, styleUrl[0]) :
doc.baseUrl,
styleID: styleUrl[1],
visibility: getBooleanValue(getElementsByTagName(node,
'visibility')[0], true),
balloonVisibility: getBooleanValue(getElementsByTagNameNS(node,
gxNS, 'balloonVisibility')[0],
!parserOptions.suppressInfoWindows),
id: node.getAttribute('id')
};
placemark.style = (styles[placemark.styleBaseUrl] &&
styles[placemark.styleBaseUrl][placemark.styleID]) || clone(defaultStyle);
// inline style overrides shared style
var inlineStyles = getElementsByTagName(node, 'Style');
if (inlineStyles && (inlineStyles.length > 0)) {
var style = processStyle(node, '{inline}',
'{inline}');
processStyleID(style);
if (style) placemark.style = style;
}
if (/^https?:\/\//.test(placemark.description)) {
placemark.description = ['<a href="',
placemark.description, '">', placemark.description,
'</a>'].join('');
}
// record list of variables for substitution
placemark.vars = {
display: {
name: 'Name',
description: 'Description',
address: 'Street Address',
id: 'ID',
Snippet: 'Snippet',
geDirections: 'Directions'
},
val: {
name: placemark.name || '',
description: placemark.description || '',
address: nodeValue(getElementsByTagName(node,
'address')[0], ''),
id: node.getAttribute('id') || '',
Snippet: nodeValue(getElementsByTagName(node,
'Snippet')[0], '')
},
directions: [
'f=d',
'source=GeoXML3'
]
};
// add extended data to variables
var extDataNodes = getElementsByTagName(node,
'ExtendedData');
if (!!extDataNodes && extDataNodes.length > 0) {
var dataNodes = getElementsByTagName(extDataNodes[0],
'Data');
for (var d = 0; d < dataNodes.length; d++) {
var dn = dataNodes[d];
var name = dn.getAttribute('name');
if (!name) continue;
var dName = nodeValue(getElementsByTagName(dn,
'displayName')[0], name);
var val = nodeValue(getElementsByTagName(dn,
'value')[0]);
placemark.vars.val[name] = val;
placemark.vars.display[name] = dName;
}
}
// process MultiGeometry
var GeometryNodes = getElementsByTagName(node,
'coordinates');
var Geometry = null;
if (!!GeometryNodes && (GeometryNodes.length > 0)) {
for (var gn=0;gn<GeometryNodes.length;gn++) {
if (GeometryNodes[gn].parentNode &&
GeometryNodes[gn].parentNode.nodeName) {
var GeometryPN = GeometryNodes[gn].parentNode;
Geometry = GeometryPN.nodeName;
// Extract the coordinates
// What sort of placemark?
switch(Geometry) {
case "Point":
placemark.Point = processPlacemarkCoords(node,
"Point")[0];
placemark.latlng = new
google.maps.LatLng(placemark.Point.coordinates[0].lat,
placemark.Point.coordinates[0].lng);
pathLength = 1;
break;
case "LinearRing":
// Polygon/line
polygonNodes = getElementsByTagName(node,
'Polygon');
// Polygon
if (!placemark.Polygon)
placemark.Polygon = [{
outerBoundaryIs: {coordinates: []},
innerBoundaryIs: [{coordinates: []}]
}];
for (var pg=0;pg<polygonNodes.length;pg++) {
placemark.Polygon[pg] = {
outerBoundaryIs: {coordinates: []},
innerBoundaryIs: [{coordinates: []}]
}
placemark.Polygon[pg].outerBoundaryIs =
processPlacemarkCoords(polygonNodes[pg], "outerBoundaryIs");
placemark.Polygon[pg].innerBoundaryIs =
processPlacemarkCoords(polygonNodes[pg], "innerBoundaryIs");
}
coordList = placemark.Polygon[0].outerBoundaryIs;
break;
case "LineString":
pathLength = 0;
placemark.LineString =
processPlacemarkCoords(node,"LineString");
break;
default:
break;
}
}
}
}
// parse MultiTrack/Track
var TrackNodes =
getElementsByTagNameNS(node,gxNS,"Track");
var coordListA = [];
if (TrackNodes.length > 0) {
for (var i=0; i<TrackNodes.length; i++) {
var coordNodes =
getElementsByTagNameNS(TrackNodes[i],gxNS,"coord");
var coordList = [];
for (var j=0; j<coordNodes.length;j++) {
var coords = geoXML3.nodeValue(coordNodes[j]).trim();
coords = coords.split(/\s+/g);
if (!isNaN(coords[0]) && !isNaN(coords[1])) {
coordList.push({
lat: parseFloat(coords[1]),
lng: parseFloat(coords[0]),
alt: parseFloat(coords[2])
});
}
}
coordListA.push({coordinates:coordList});
}
placemark.Track = coordListA;
}
// call the custom placemark parse function if it is defined
if (!!parserOptions.pmParseFn) parserOptions.pmParseFn(node,
placemark);
doc.placemarks.push(placemark);
// single marker
if (placemark.Point) {
if (!!google.maps) {
doc.bounds = doc.bounds || new google.maps.LatLngBounds();
doc.bounds.extend(placemark.latlng);
}
// Potential user-defined marker handler
var pointCreateFunc = parserOptions.createMarker || createMarker;
var found = false;
if (!parserOptions.createMarker) {
// Check to see if this marker was created on a previous load
of this document
if (!!doc) {
doc.markers = doc.markers || [];
if (doc.reload) {
for (var j = 0; j < doc.markers.length; j++) {
if ((doc.markers[j].id == placemark.id) ||
// if no id, check position
(!doc.markers[j].id &&
(doc.markers[j].getPosition().equals(placemark.latlng)))) {
found = doc.markers[j].active = true;
break;
}
}
}
}
}
if (!found) {
// Call the marker creator
var marker = pointCreateFunc(placemark, doc);
if (marker) {
marker.active = placemark.visibility;
marker.id = placemark.id;
}
}
}
// polygon/line
var poly, line;
if (!!doc) {
if (placemark.Polygon) doc.gpolygons = doc.gpolygons || [];
if (placemark.LineString) doc.gpolylines = doc.gpolylines || [];
if (placemark.Track) doc.gpolylines = doc.gpolylines || [];
}
var polyCreateFunc = parserOptions.createPolygon ||
createPolygon;
var lineCreateFunc = parserOptions.createLineString ||
createPolyline;
if (placemark.Polygon) {
poly = polyCreateFunc(placemark,doc);
if (poly) poly.active = placemark.visibility;
}
if (placemark.LineString) {
line = lineCreateFunc(placemark,doc);
if (line) line.active = placemark.visibility;
}
if (placemark.Track) { // gx:Track polyline
line = lineCreateFunc(placemark,doc);
if (line) line.active = placemark.visibility;
}
if (!!google.maps) {
doc.bounds = doc.bounds || new google.maps.LatLngBounds();
if (poly) doc.bounds.union(poly.bounds);
if (line) doc.bounds.union(line.bounds);
}
} // placemark loop
if (!!doc.reload && !!doc.markers) {
for (i = doc.markers.length - 1; i >= 0 ; i--) {
if (!doc.markers[i].active) {
if (!!doc.markers[i].infoWindow) {
doc.markers[i].infoWindow.close();
}
doc.markers[i].setMap(null);
doc.markers.splice(i, 1);
}
}
}
var overlayCreateFunc = parserOptions.createOverlay || createOverlay;
// Parse ground overlays
if (!!doc.reload && !!doc.groundoverlays) {
for (i = 0; i < doc.groundoverlays.length; i++) {
doc.groundoverlays[i].active = false;
}
}
if (!!doc) {
doc.groundoverlays = doc.groundoverlays || [];
}
// doc.groundoverlays =[];
var groundOverlay, color, transparency, overlay;
var groundNodes = getElementsByTagName(responseXML,
'GroundOverlay');
for (i = 0; i < groundNodes.length; i++) {
node = groundNodes[i];
// Detect images buried in KMZ files (and use a base64 encoded URL)
var gnUrl = cleanURL( doc.baseDir,
nodeValue(getElementsByTagName(node, 'href')[0]) );
if (kmzMetaData[gnUrl]) gnUrl = kmzMetaData[gnUrl].dataUrl;
// Init the ground overlay object
groundOverlay = {
name: nodeValue(getElementsByTagName(node,
'name')[0]),
description: nodeValue(getElementsByTagName(node,
'description')[0]),
icon: { href: gnUrl },
latLonBox: {
north: parseFloat(nodeValue(getElementsByTagName(node,
'north')[0])),
east: parseFloat(nodeValue(getElementsByTagName(node,
'east')[0])),
south: parseFloat(nodeValue(getElementsByTagName(node,
'south')[0])),
west: parseFloat(nodeValue(getElementsByTagName(node,
'west')[0]))
},
rotation: -1 * parseFloat(nodeValue(getElementsByTagName(node,
'rotation')[0]))
};
if (!!google.maps) {
doc.bounds = doc.bounds || new google.maps.LatLngBounds();
doc.bounds.union(new google.maps.LatLngBounds(
new google.maps.LatLng(groundOverlay.latLonBox.south,
groundOverlay.latLonBox.west),
new google.maps.LatLng(groundOverlay.latLonBox.north,
groundOverlay.latLonBox.east)
));
}
// Opacity is encoded in the color node
var colorNode = getElementsByTagName(node, 'color');
if (colorNode && colorNode.length > 0) {
groundOverlay.opacity =
geoXML3.getOpacity(nodeValue(colorNode[0]));
} else {
groundOverlay.opacity = 1.0; // KML default
}
doc.groundoverlays.push(groundOverlay);
// Check to see if this overlay was created on a previous load of
this document
var found = false;
if (!!doc) {
doc.groundoverlays = doc.groundoverlays || [];
if (doc.reload) {
overlayBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(groundOverlay.latLonBox.south,
groundOverlay.latLonBox.west),
new google.maps.LatLng(groundOverlay.latLonBox.north,
groundOverlay.latLonBox.east)
);
var overlays = doc.groundoverlays;
for (i = overlays.length; i--;) {
if ((overlays[i].bounds().equals(overlayBounds)) &&
(overlays.url_ === groundOverlay.icon.href)) {
found = overlays[i].active = true;
break;
}
}
}
if (!found) {
overlay = overlayCreateFunc(groundOverlay, doc);
overlay.active = true;
}
}
if (!!doc.reload && !!doc.groundoverlays &&
!!doc.groundoverlays.length) {
var overlays = doc.groundoverlays;
for (i = overlays.length; i--;) {
if (!overlays[i].active) {
overlays[i].remove();
overlays.splice(i, 1);
}
}
doc.groundoverlays = overlays;
}
}
// Parse network links
var networkLink;
var docPath = document.location.pathname.split('/');
docPath = docPath.splice(0, docPath.length - 1).join('/');
var linkNodes = getElementsByTagName(responseXML,
'NetworkLink');
for (i = 0; i < linkNodes.length; i++) {
node = linkNodes[i];
// Init the network link object
networkLink = {
name: nodeValue(getElementsByTagName(node, 'name')[0]),
link: {
href: nodeValue(getElementsByTagName(node,
'href')[0]),
refreshMode: nodeValue(getElementsByTagName(node,
'refreshMode')[0])
}
};
// Establish the specific refresh mode
if (!networkLink.link.refreshMode) {
networkLink.link.refreshMode = 'onChange';
}
if (networkLink.link.refreshMode === 'onInterval') {
networkLink.link.refreshInterval =
parseFloat(nodeValue(getElementsByTagName(node,
'refreshInterval')[0]));
if (isNaN(networkLink.link.refreshInterval)) {
networkLink.link.refreshInterval = 0;
}
} else if (networkLink.link.refreshMode === 'onChange') {
networkLink.link.viewRefreshMode =
nodeValue(getElementsByTagName(node, 'viewRefreshMode')[0]);
if (!networkLink.link.viewRefreshMode) {
networkLink.link.viewRefreshMode = 'never';
}
if (networkLink.link.viewRefreshMode === 'onStop') {
networkLink.link.viewRefreshTime =
nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
networkLink.link.viewFormat =
nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
if (!networkLink.link.viewFormat) {
networkLink.link.viewFormat =
'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]';
}
}
}
if (!/^[\/|http]/.test(networkLink.link.href)) {
// Fully-qualify the HREF
networkLink.link.href = docPath + '/' +
networkLink.link.href;
}
// Apply the link
if ((networkLink.link.refreshMode === 'onInterval')
&&
(networkLink.link.refreshInterval > 0)) {
// Reload at regular intervals
setInterval(parserName + '.parse("' +
networkLink.link.href + '")',
1000 * networkLink.link.refreshInterval);
} else if (networkLink.link.refreshMode === 'onChange') {
if (networkLink.link.viewRefreshMode === 'never') {
// Load the link just once
doc.internals.parser.parse(networkLink.link.href,
doc.internals.docSet);
} else if (networkLink.link.viewRefreshMode ===
'onStop') {
// Reload when the map view changes
}
}
}
}
if (!!doc.bounds) {
doc.internals.bounds = doc.internals.bounds || new
google.maps.LatLngBounds();
doc.internals.bounds.union(doc.bounds);
}
if (!!doc.markers || !!doc.groundoverlays || !!doc.gpolylines ||
!!doc.gpolygons) {
doc.internals.parseOnly = false;
}
if (!doc.internals.parseOnly) {
// geoXML3 is not being used only as a real-time parser, so keep the
processed documents around
if (doc.baseUrl){ // handle case from parseKmlString (no doc.baseUrl)
if (!docsByUrl[doc.baseUrl]) {
docs.push(doc);
docsByUrl[doc.baseUrl] = doc;
} else {
// internal replacement, which keeps the same memory ref loc in
docs and docsByUrl
for (var i in docsByUrl[doc.baseUrl]) {
docsByUrl[doc.baseUrl][i] = doc[i];
}
}
}
}
doc.internals.remaining--;
if (doc.internals.remaining === 0) {
// We're done processing this set of KML documents
// Options that get invoked after parsing completes
if (parserOptions.zoom && !!doc.internals.bounds &&
!doc.internals.bounds.isEmpty() && !!parserOptions.map) {
parserOptions.map.fitBounds(doc.internals.bounds);
}
if (parserOptions.afterParse) {
parserOptions.afterParse(doc.internals.docSet);
}
google.maps.event.trigger(doc.internals.parser, 'parsed');
}
};
var kmlColor = function (kmlIn, colorMode) {
var kmlColor = {};
kmlIn = kmlIn || 'ffffffff'; // white (KML 2.2 default)
var aa = kmlIn.substr(0,2);
var bb = kmlIn.substr(2,2);
var gg = kmlIn.substr(4,2);
var rr = kmlIn.substr(6,2);
kmlColor.opacity = parseInt(aa, 16) / 256;
kmlColor.color = (colorMode === 'random') ? randomColor(rr,
gg, bb) : '#' + rr + gg + bb;
return kmlColor;
};
// Implemented per KML 2.2 <ColorStyle> specs
var randomColor = function(rr, gg, bb) {
var col = { rr: rr, gg: gg, bb: bb };
for (var k in col) {
var v = col[k];
if (v == null) v = 'ff';
// RGB values are limiters for random numbers (ie: 7f would be a
random value between 0 and 7f)
v = Math.round(Math.random() * parseInt(rr, 16)).toString(16);
if (v.length === 1) v = '0' + v;
col[k] = v;
}
return '#' + col.rr + col.gg + col.bb;
};
var processStyleID = function (style) {
var icon = style.icon;
if (!icon || !icon.href) return;
if (icon.img && !icon.img.complete && (icon.dim.w <
0) && (icon.dim.h < 0) ) {
// we're still waiting on the image loading (probably because
we've been blocking since the declaration)
// so, let's queue this function on the onload stack
icon.markerBacklog = [];
icon.img.onload = function() {
if (icon.dim.w < 0 || icon.dim.h < 0) {
icon.dim.w = this.width;
icon.dim.h = this.height;
} else {
icon.dim.th = this.height;
}
processStyleID(style);
// we will undoubtedly get some createMarker queuing, so set this
up in advance
for (var i = 0; i < icon.markerBacklog.length; i++) {
var p = icon.markerBacklog[i][0];
var d = icon.markerBacklog[i][1];
createMarker(p, d);
if (p.marker) p.marker.active = true;
}
delete icon.markerBacklog;
};
return;
}
else { //if (icon.dim.w < 0 || icon.dim.h < 0) {
if (icon.img && icon.img.complete) {
// sometimes the file is already cached and it never calls onLoad
if (icon.dim.w < 0 || icon.dim.h < 0) {
icon.dim.w = icon.img.width;
icon.dim.h = icon.img.height;
} else {
icon.dim.th = icon.img.height;
}
}
else {
// settle for a default of 32x32
icon.dim.whGuess = true;
icon.dim.w = 32;
icon.dim.h = 32;
icon.dim.th = 32;
}
}
// pre-scaled variables
var rnd = Math.round;
var y = icon.dim.y;
if (typeof icon.dim.th !== 'undefined' && icon.dim.th
!= icon.dim.h) { // palette - reverse kml y for maps
y = Math.abs(y - (icon.dim.th - icon.dim.h));
}
var scaled = {
x: icon.dim.x * icon.scale,
y: y * icon.scale,
w: icon.dim.w * icon.scale,
h: icon.dim.h * icon.scale,
aX:icon.hotSpot.x * icon.scale,
aY:icon.hotSpot.y * icon.scale,
iW:(icon.img ? icon.img.width : icon.dim.w) * icon.scale,
iH:(icon.img ? icon.img.height : icon.dim.h) * icon.scale
};
// Figure out the anchor spot
// Origins, anchor positions and coordinates of the marker increase in
the X direction to the right and in
// the Y direction down.
var aX, aY;
switch (icon.hotSpot.xunits) {
case 'fraction': aX = rnd(scaled.aX * icon.dim.w);
break;
case 'insetPixels': aX = rnd(icon.dim.w * icon.scale -
scaled.aX); break;
default: aX = rnd(scaled.aX); break; // already pixels
}
switch(icon.hotSpot.yunits) {
case 'fraction': aY = scaled.h - rnd(icon.dim.h *
scaled.aY); break;
case 'insetPixels': aY = rnd(scaled.aY); break;
default: aY = rnd(icon.dim.h * icon.scale - scaled.aY);
break;
}
var iconAnchor = new google.maps.Point(aX, aY);
// Sizes
// (NOTE: Scale is applied to entire image, not just the section of the
icon palette.)
var iconSize = icon.dim.whGuess ? null : new
google.maps.Size(rnd(scaled.w), rnd(scaled.h));
var iconScale = icon.scale == 1.0 ? null :
icon.dim.whGuess ? new
google.maps.Size(rnd(scaled.w), rnd(scaled.h))
: new
google.maps.Size(rnd(scaled.iW), rnd(scaled.iH));
var iconOrigin = new google.maps.Point(rnd(scaled.x), rnd(scaled.y));
// Detect images buried in KMZ files (and use a base64 encoded URL)
if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
// Init the style object with the KML icon
icon.marker = {
url: icon.url, // url
size: iconSize, // size
origin: iconOrigin, // origin
anchor: iconAnchor, // anchor
scaledSize: iconScale // scaledSize
};
// Look for a predictable shadow
var stdRegEx =
/\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/;
var shadowSize = new google.maps.Size(59, 32);
var shadowPoint = new google.maps.Point(16, 32);
if (stdRegEx.test(icon.href)) {
// A standard GMap-style marker icon
icon.shadow = {
url:
'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png',
// url
size: shadowSize, // size
origin: null, // origin
anchor: shadowPoint, // anchor
scaledSize: shadowSize // scaledSize
};
} else if (icon.href.indexOf('-pushpin.png') > -1) {
// Pushpin marker icon
icon.shadow = {
url:
'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png',
// url
size: shadowSize, // size
origin: null, // origin
anchor: shadowPoint, // anchor
scaledSize: shadowSize // scaledSize
};
} /* else {
// Other MyMaps KML standard icon
icon.shadow = new google.maps.MarkerImage(
icon.href.replace('.png', '.shadow.png'),
// url
shadowSize, //
size
null, //
origin
anchorPoint, //
anchor
shadowSize //
scaledSize
);
} */
}
var processStyles = function (doc) {
for (var styleID in doc.styles) {
processStyleID(doc.styles[styleID]);
}
};
var createMarker = function (placemark, doc) {
// create a Marker to the map from a placemark KML object
var icon = placemark.style.icon;
if ( !icon.marker && icon.img ) {
// yay, single point of failure is holding up multiple markers...
icon.markerBacklog = icon.markerBacklog || [];
icon.markerBacklog.push([placemark, doc]);
return;
}
// Load basic marker properties
var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions,
{
map: parserOptions.map,
position: new google.maps.LatLng(placemark.Point.coordinates[0].lat,
placemark.Point.coordinates[0].lng),
title: placemark.name,
zIndex: Math.round(placemark.Point.coordinates[0].lat *
-100000)<<5,
icon: icon.marker,
shadow: icon.shadow,
flat: !icon.shadow,
visible: placemark.visibility
});
// Create the marker on the map
var marker = new google.maps.Marker(markerOptions);
if (!!doc) doc.markers.push(marker);
// Set up and create the infowindow if it is not suppressed
createInfoWindow(placemark, doc, marker);
placemark.marker = marker;
return marker;
};
var createOverlay = function (groundOverlay, doc) {
// Add a ProjectedOverlay to the map from a groundOverlay KML object
if (!window.ProjectedOverlay) {
throw 'geoXML3 error: ProjectedOverlay not found while rendering
GroundOverlay from KML';
}
var bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(groundOverlay.latLonBox.south,
groundOverlay.latLonBox.west),
new google.maps.LatLng(groundOverlay.latLonBox.north,
groundOverlay.latLonBox.east)
);
var overlayOptions =
geoXML3.combineOptions(parserOptions.overlayOptions, {
percentOpacity: groundOverlay.opacity*100,
rotation: groundOverlay.rotation
});
var overlay = new ProjectedOverlay(parserOptions.map,
groundOverlay.icon.href, bounds, overlayOptions);
if (!!doc) {
doc.ggroundoverlays = doc.ggroundoverlays || [];
doc.ggroundoverlays.push(overlay);
}
return overlay;
};
// Create Polyline
var createPolyline = function(placemark, doc) {
var paths = [];
var bounds = new google.maps.LatLngBounds();
if (placemark.LineString) {
for (var j=0; j<placemark.LineString.length; j++) {
var path = [];
var coords = placemark.LineString[j].coordinates;
for (var i=0;i<coords.length;i++) {
var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
path.push(pt);
bounds.extend(pt);
}
paths.push(path);
}
} else if (placemark.Track) {
for (var j=0; j<placemark.Track.length; j++) {
var path = [];
var coords = placemark.Track[j].coordinates;
for (var i=0;i<coords.length;i++) {
var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
path.push(pt);
bounds.extend(pt);
}
paths.push(path);
}
}
// point to open the infowindow if triggered
var point = paths[0][Math.floor(path.length/2)];
// Load basic polyline properties
var kmlStrokeColor = kmlColor(placemark.style.line.color,
placemark.style.line.colorMode);
var polyOptions = geoXML3.combineOptions(parserOptions.polylineOptions,
{
map: parserOptions.map,
path: path,
strokeColor: kmlStrokeColor.color,
strokeWeight: placemark.style.line.width,
strokeOpacity: kmlStrokeColor.opacity,
title: placemark.name,
visible: placemark.visibility
});
if (paths.length > 1) {
polyOptions.paths = paths;
var p = new MultiGeometry(polyOptions);
} else {
polyOptions.path = paths[0];
var p = new google.maps.Polyline(polyOptions);
}
p.bounds = bounds;
// setup and create the infoWindow if it is not suppressed
createInfoWindow(placemark, doc, p);
if (!!doc) doc.gpolylines.push(p);
placemark.polyline = p;
return p;
}
// Create Polygon
var createPolygon = function(placemark, doc) {
var bounds = new google.maps.LatLngBounds();
var pathsLength = 0;
var paths = [];
for (var
polygonPart=0;polygonPart<placemark.Polygon.length;polygonPart++) {
for (var j=0;
j<placemark.Polygon[polygonPart].outerBoundaryIs.length; j++) {
var coords =
placemark.Polygon[polygonPart].outerBoundaryIs[j].coordinates;
var path = [];
for (var i=0;i<coords.length;i++) {
var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
path.push(pt);
bounds.extend(pt);
}
paths.push(path);
pathsLength += path.length;
}
for (var j=0;
j<placemark.Polygon[polygonPart].innerBoundaryIs.length; j++) {
var coords =
placemark.Polygon[polygonPart].innerBoundaryIs[j].coordinates;
var path = [];
for (var i=0;i<coords.length;i++) {
var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
path.push(pt);
bounds.extend(pt);
}
paths.push(path);
pathsLength += path.length;
}
}
// Load basic polygon properties
var kmlStrokeColor = kmlColor(placemark.style.line.color,
placemark.style.line.colorMode);
var kmlFillColor = kmlColor(placemark.style.poly.color,
placemark.style.poly.colorMode);
if (!placemark.style.poly.fill) kmlFillColor.opacity = 0.0;
var strokeWeight = placemark.style.line.width;
if (!placemark.style.poly.outline) {
strokeWeight = 0;
kmlStrokeColor.opacity = 0.0;
}
var polyOptions = geoXML3.combineOptions(parserOptions.polygonOptions,
{
map: parserOptions.map,
paths: paths,
title: placemark.name,
strokeColor: kmlStrokeColor.color,
strokeWeight: strokeWeight,
strokeOpacity: kmlStrokeColor.opacity,
fillColor: kmlFillColor.color,
fillOpacity: kmlFillColor.opacity,
visible: placemark.visibility
});
var p = new google.maps.Polygon(polyOptions);
p.bounds = bounds;
createInfoWindow(placemark, doc, p);
if (!!doc) doc.gpolygons.push(p);
placemark.polygon = p;
return p;
}
var createInfoWindow = function(placemark, doc, gObj) {
var bStyle = placemark.style.balloon;
var vars = placemark.vars;
if (!placemark.balloonVisibility || bStyle.displayMode ===
'hide') return;
// define geDirections
if (placemark.latlng &&
(!parserOptions.suppressDirections ||
!parserOptions.suppressDirections)) {
vars.directions.push('sll=' +
placemark.latlng.toUrlValue());
var url = 'http://maps.google.com/maps?' +
vars.directions.join('&');
var address = encodeURIComponent( vars.val.address ||
placemark.latlng.toUrlValue() ).replace(/\%20/g, '+');
vars.val.geDirections = '<a href="' + url +
'&daddr=' + address + '" target=_blank>To
Here</a> - <a href="' + url + '&saddr=' +
address + '" target=_blank>From Here</a>';
}
else vars.val.geDirections = '';
// add in the variables
var iwText = bStyle.text.replace(/\$\[(\w+(\/displayName)?)\]/g,
function(txt, n, dn) { return dn ? vars.display[n] : vars.val[n]; });
var classTxt = 'geoxml3_infowindow geoxml3_style_' +
placemark.styleID;
// color styles
var styleArr = [];
if (bStyle.bgColor != 'ffffffff')
styleArr.push('background: ' + kmlColor(bStyle.bgColor ).color +
';');
if (bStyle.textColor != 'ff000000')
styleArr.push('color: ' + kmlColor(bStyle.textColor).color +
';');
var styleProp = styleArr.length ? ' style="' +
styleArr.join(' ') + '"' : '';
var infoWindowOptions =
geoXML3.combineOptions(parserOptions.infoWindowOptions, {
content: '<div class="' + classTxt +
'"' + styleProp + '>' + iwText +
'</div>',
pixelOffset: new google.maps.Size(0, 2)
});
gObj.infoWindow = parserOptions.infoWindow || new
google.maps.InfoWindow(infoWindowOptions);
gObj.infoWindowOptions = infoWindowOptions;
// Info Window-opening event handler
google.maps.event.addListener(gObj, 'click', function(e) {
var iW = this.infoWindow;
iW.close();
iW.setOptions(this.infoWindowOptions);
if (e && e.latLng) iW.setPosition(e.latLng);
else if (this.bounds) iW.setPosition(this.bounds.getCenter());
iW.setContent("<div
id='geoxml3_infowindow'>"+iW.getContent()+"</div>");
google.maps.event.addListenerOnce(iW, "domready",
function() {
var node = document.getElementById('geoxml3_infowindow');
var imgArray = node.getElementsByTagName('img');
for (var i = 0; i < imgArray.length; i++)
{
var imgUrlIE = imgArray[i].getAttribute("src");
var imgUrl = cleanURL(doc.baseDir, imgUrlIE);
if (kmzMetaData[imgUrl]) {
imgArray[i].src = kmzMetaData[imgUrl].dataUrl;
} else if (kmzMetaData[imgUrlIE]) {
imgArray[i].src = kmzMetaData[imgUrlIE].dataUrl;
}
}
});
iW.open(this.map, this.bounds ? null : this);
});
}
return {
// Expose some properties and methods
options: parserOptions,
docs: docs,
docsByUrl: docsByUrl,
kmzMetaData: kmzMetaData,
parse: parse,
render: render,
parseKmlString: parseKmlString,
hideDocument: hideDocument,
showDocument: showDocument,
processStyles: processStyles,
createMarker: createMarker,
createOverlay: createOverlay,
createPolyline: createPolyline,
createPolygon: createPolygon
};
};
// End of KML Parser
// Helper objects and functions
geoXML3.getOpacity = function (kmlColor) {
// Extract opacity encoded in a KML color value. Returns a number between
0 and 1.
if (!!kmlColor &&
(kmlColor !== '') &&
(kmlColor.length == 8)) {
var transparency = parseInt(kmlColor.substr(0, 2), 16);
return transparency / 255;
} else {
return 1;
}
};
// Log a message to the debugging console, if one exists
geoXML3.log = function(msg) {
if (!!window.console) {
console.log(msg);
} else { alert("log:"+msg); }
};
/**
* Creates a new parserOptions object.
* @class GeoXML3 parser options.
* @param {Object} overrides Any options you want to declare outside of the
defaults should be included here.
* @property {google.maps.Map} map The API map on which geo objects should
be rendered.
* @property {google.maps.MarkerOptions} markerOptions If the parser is
adding Markers to the map itself, any options specified here will be
applied to them.
* @property {google.maps.InfoWindowOptions} infoWindowOptions If the
parser is adding Markers to the map itself, any options specified here will
be applied to their attached InfoWindows.
* @property {ProjectedOverlay.options} overlayOptions If the parser is
adding ProjectedOverlays to the map itself, any options specified here will
be applied to them.
*/
geoXML3.parserOptions = function (overrides) {
this.map = null,
/** If true, the parser will automatically move the map to a best-fit of
the geodata after parsing of a KML document completes.
* @type Boolean
* @default true
*/
this.zoom = true,
/**#@+ @type Boolean
* @default false */
/** If true, only a single Marker created by the parser will be able to
have its InfoWindow open at once (simulating the behavior of GMaps API v2).
*/
this.singleInfoWindow = false,
/** If true, suppresses the rendering of info windows. */
this.suppressInfoWindows = false,
/**
* Control whether to process styles now or later.
*
* <p>By default, the parser only processes KML
<Style> elements into their GMaps equivalents
* if it will be creating its own Markers (the createMarker option is
null). Setting this option
* to true will force such processing to happen anyway, useful if
you're going to be calling parser.createMarker
* yourself later. OTOH, leaving this option false removes runtime
dependency on the GMaps API, enabling
* the use of geoXML3 as a standalone KML parser.</p>
*/
this.processStyles = false,
/**#@-*/
this.markerOptions = {},
this.infoWindowOptions = {},
this.overlayOptions = {},
/**#@+ @event */
/** This function will be called when parsing of a KML document is
complete.
* @param {geoXML3.parser#docs} doc Parsed KML data. */
this.afterParse = null,
/** This function will be called when parsing of a KML document is
complete.
* @param {geoXML3.parser#docs} doc Parsed KML data. */
this.failedParse = null,
/**
* If supplied, this function will be called once for each marker
<Placemark> in the KML document, instead of the parser adding its own
Marker to the map.
* @param {geoXML3.parser.render#placemark} placemark Placemark object.
* @param {geoXML3.parser#docs} doc Parsed KML data.
*/
this.createMarker = null,
/**
* If supplied, this function will be called once for each
<GroundOverlay> in the KML document, instead of the parser adding its
own ProjectedOverlay to the map.
* @param {geoXML3.parser.render#groundOverlay} groundOverlay
GroundOverlay object.
* @param {geoXML3.parser#docs} doc Parsed KML data.
*/
this.createOverlay = null
/**#@-*/
if (overrides) {
for (var prop in overrides) {
if (overrides.hasOwnProperty(prop)) this[prop] = overrides[prop];
}
}
return this;
};
/**
* Combine two options objects: a set of default values and a set of
override values.
*
* @deprecated This has been replaced with {@link
geoXML3.parserOptions#combineOptions}.
* @param {geoXML3.parserOptions|Object} overrides Override values.
* @param {geoXML3.parserOptions|Object} defaults Default values.
* @return {geoXML3.parserOptions} Combined result.
*/
geoXML3.combineOptions = function (overrides, defaults) {
var result = {};
if (!!overrides) {
for (var prop in overrides) {
if (overrides.hasOwnProperty(prop))
result[prop] = overrides[prop];
}
}
if (!!defaults) {
for (prop in defaults) {
if (defaults.hasOwnProperty(prop) && result[prop] ===
undefined) result[prop] = defaults[prop];
}
}
return result;
};
/**
* Combine two options objects: a set of default values and a set of
override values.
*
* @function
* @param {geoXML3.parserOptions|Object} overrides Override values.
* @param {geoXML3.parserOptions|Object} defaults Default values.
* @return {geoXML3.parserOptions} Combined result.
*/
geoXML3.parserOptions.prototype.combineOptions = geoXML3.combineOptions;
// Retrieve an XML document from url and pass it to callback as a DOM
document
geoXML3.fetchers = [];
/**
* Parses a XML string.
*
* <p>Parses the given XML string and returns the parsed document in
a
* DOM data structure. This function will return an empty DOM node if
* XML parsing is not supported in this browser.</p>
*
* @param {String} str XML string.
* @return {Element|Document} DOM.
*/
geoXML3.xmlParse = function (str) {
if ((typeof ActiveXObject != 'undefined') ||
("ActiveXObject" in window)) {
var doc = new ActiveXObject('Microsoft.XMLDOM');
doc.loadXML(str);
return doc;
}
if (typeof DOMParser != 'undefined') {
return (new DOMParser()).parseFromString(str, 'text/xml');
}
return document.createElement('div', null);
}
/**
* Checks for XML parse error.
*
* @param {xmlDOM} XML DOM.
* @return boolean.
*/
// from
http://stackoverflow.com/questions/11563554/how-do-i-detect-xml-parsing-errors-when-using-javascripts-domparser-in-a-cross
geoXML3.isParseError = function(parsedDocument) {
if ((typeof ActiveXObject != 'undefined') ||
("ActiveXObject" in window))
return false;
// parser and parsererrorNS could be cached on startup for efficiency
var p = new DOMParser(),
errorneousParse = p.parseFromString('<',
'text/xml'),
parsererrorNS =
errorneousParse.getElementsByTagName("parsererror")[0].namespaceURI;
if (parsererrorNS === 'http://www.w3.org/1999/xhtml') {
// In PhantomJS the parseerror element doesn't seem to have a
special namespace, so we are just guessing here :(
return
parsedDocument.getElementsByTagName("parsererror").length > 0;
}
return parsedDocument.getElementsByTagNameNS(parsererrorNS,
'parsererror').length > 0;
};
/**
* Fetches a XML document.
*
* <p>Fetches/parses the given XML URL and passes the parsed document
(in a
* DOM data structure) to the given callback. Documents are downloaded
* and parsed asynchronously.</p>
*
* @param {String} url URL of XML document. Must be uncompressed XML only.
* @param {Function(Document)} callback Function to call when the document
is processed.
*/
geoXML3.fetchXML = function (url, callback) {
function timeoutHandler() { callback(); };
var xhrFetcher = new Object();
if (!!geoXML3.fetchers.length) xhrFetcher = geoXML3.fetchers.pop();
else if (!!window.XMLHttpRequest) xhrFetcher.fetcher = new
window.XMLHttpRequest(); // Most browsers
else if (!!window.ActiveXObject) {
// Some IE
// the many versions of IE's XML fetchers
var AXOs = [
'MSXML2.XMLHTTP.6.0',
'MSXML2.XMLHTTP.5.0',
'MSXML2.XMLHTTP.4.0',
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP',
'MSXML.XMLHTTP'
];
for (var i = 0; i < AXOs.length; i++) {
try { xhrFetcher.fetcher = new ActiveXObject(AXOs[i]); break; }
catch(e) { continue; }
}
if (!xhrFetcher.fetcher) {
geoXML3.log('Unable to create XHR object');
callback(null);
return null;
}
}
xhrFetcher.fetcher.open('GET', url, true);
if (!!xhrFetcher.fetcher.overrideMimeType)
xhrFetcher.fetcher.overrideMimeType('text/xml');
xhrFetcher.fetcher.onreadystatechange = function () {
if (xhrFetcher.fetcher.readyState === 4) {
// Retrieval complete
if (!!xhrFetcher.xhrtimeout) clearTimeout(xhrFetcher.xhrtimeout);
if (xhrFetcher.fetcher.status >= 400) {
geoXML3.log('HTTP error ' + xhrFetcher.fetcher.status +
' retrieving ' + url);
callback();
}
// Returned successfully
else {
if (xhrFetcher.fetcher.responseXML) {
// Sometimes IE will get the data, but won't bother loading it
as an XML doc
var xml = xhrFetcher.fetcher.responseXML;
if (xml && !xml.documentElement &&
!xml.ownerElement) {
xml.loadXML(xhrFetcher.fetcher.responseText);
}
} else {// handle valid xml sent with wrong MIME type
xml=geoXML3.xmlParse(xhrFetcher.fetcher.responseText);
}
// handle parse errors
if (xml.parseError && (xml.parseError.errorCode != 0)) {
geoXML3.log("XML parse error
"+xml.parseError.errorCode+",
"+xml.parseError.reason+"\nLine:"+xml.parseError.line+",
Position:"+xml.parseError.linepos+",
srcText:"+xml.parseError.srcText);
xml = "failed parse"
} else if (geoXML3.isParseError(xml)) {
geoXML3.log("XML parse error");
xml = "failed parse"
}
callback(xml);
}
// We're done with this fetcher object
geoXML3.fetchers.push(xhrFetcher);
}
};
xhrFetcher.xhrtimeout = setTimeout(timeoutHandler, 60000);
xhrFetcher.fetcher.send(null);
return null;
};
var IEversion = function() {
//
http://msdn.microsoft.com/workshop/author/dhtml/overview/browserdetection.asp
// Returns the version of Internet Explorer or a -1
// (indicating the use of another browser).
var rv = -1; // Return value assumes failure
if (navigator.appName == 'Microsoft Internet Explorer') {
var ua = navigator.userAgent;
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
if (re.exec(ua) != null) {
rv = parseFloat( RegExp.$1 );
}
}
return rv;
};
/**
* Fetches a KMZ document.
*
* <p>Fetches/parses the given ZIP URL, parses each image file, and
passes
* the parsed KML document to the given callback. Documents are downloaded
* and parsed asynchronously, though the KML file is always passed after
the
* images have been processed, in case the callback requires the image
data.</p>
*
* @requires ZipFile.complete.js
* @param {String} url URL of KMZ document. Must be a valid KMZ/ZIP
archive.
* @param {Function(Document)} callback Function to call when the document
is processed.
* @param {geoXML3.parser} parser A geoXML3.parser object. This is used to
populate the KMZ image data.
* @author Brendan Byrd
* @see http://code.google.com/apis/kml/documentation/kmzarchives.html
*/
geoXML3.fetchZIP = function (url, callback, parser) {
// Just need a single 'new' declaration with a really long
function...
var zipFile = new ZipFile(url, function (zip) {
// Retrieval complete
// Check for ERRORs in zip.status
for (var i = 0; i < zip.status.length; i++) {
var msg = zip.status[i];
if (msg.indexOf("ERROR") == 0) {
geoXML3.log('HTTP/ZIP error retrieving ' + url + ':
' + msg);
callback();
return;
}
else if (msg.indexOf("EXCEPTION") == 0) {
geoXML3.log('HTTP/ZIP exception retrieving ' + url +
': ' + msg);
callback();
return;
} else if (msg.indexOf("WARNING") == 0) { // non-fatal,
but still might be useful
geoXML3.log('HTTP/ZIP warning retrieving ' + url +
': ' + msg);
} else if (msg.indexOf("INFO") == 0) { // non-fatal, but
still might be useful
geoXML3.log('HTTP/ZIP info retrieving ' + url + ':
' + msg);
}
}
// Make sure KMZ structure is according to spec (with a single KML file
in the root dir)
var KMLCount = 0;
var KML;
for (var i = 0; i < zip.entries.length; i++) {
var name = zip.entries[i].name;
if (!/\.kml$/.test(name)) continue;
KMLCount++;
if (KMLCount == 1) KML = i;
else {
geoXML3.log('KMZ warning retrieving ' + url + ':
found extra KML "' + name + '" in KMZ;
discarding...');
}
}
// Returned successfully, but still needs extracting
var baseUrl = cleanURL(defileURL(url), url) + '/';
var kmlProcessing = { // this is an object just so it gets passed
properly
timer: null,
extractLeft: 0,
timerCalls: 0
};
var extractCb = function(entry, entryContent) {
var mdUrl = cleanURL(baseUrl, entry.name);
var ext = entry.name.substring(entry.name.lastIndexOf(".")
+ 1).toLowerCase();
kmlProcessing.extractLeft--;
if ((typeof entryContent.description == "string")
&& (entryContent.name == "Error")) {
geoXML3.log('KMZ error extracting ' + mdUrl + ':
' + entryContent.description);
callback();
return;
}
// MIME types that can be used in KML
var mime;
if (ext === 'jpg') ext = 'jpeg';
if (/^(gif|jpeg|png)$/.test(ext)) mime = 'image/' + ext;
else if (ext === 'mp3') mime =
'audio/mpeg';
else if (ext === 'm4a') mime =
'audio/mp4';
else if (ext === 'm4a') mime =
'audio/MP4-LATM';
else mime =
'application/octet-stream';
parser.kmzMetaData[mdUrl] = {};
parser.kmzMetaData[mdUrl].entry = entry;
// data:image/gif;base64,R0lGODlhEAAOALMA...
parser.kmzMetaData[mdUrl].dataUrl = 'data:' + mime +
';base64,' + base64Encode(entryContent);
// IE cannot handle GET requests beyond 2071 characters, even if
it's an inline image
if (/msie/i.test(navigator.userAgent) &&
!/opera/i.test(navigator.userAgent))
{
if (((IEversion() < 8.0) &&
(parser.kmzMetaData[mdUrl].dataUrl.length > 2071)) ||
((IEversion < 9.0) &&
(parser.kmzMetaData[mdUrl].dataUrl.length > 32767))) {
parser.kmzMetaData[mdUrl].dataUrl =
// this is a simple IE icon; to hint at the problem...
'data:image/gif;base64,R0lGODlhDwAQAOMPADBPvSpQ1Dpoyz1p6FhwvU2A6ECP63CM04CWxYCk+V6x+UK++Jao3rvC3fj7+v///yH5BAEKAA8ALAAAAAAPABAAAASC8Mk5mwCAUMlWwcLRHEelLA'
+
'oGDMgzSsiyGCAhCETDPMh5XQCBwYBrNBIKWmg0MCQHj8MJU5IoroYCY6AAAgrDIbbQDGIK6DR5UPhlNo0JAlSUNAiDgH7eNAxEDWAKCQM2AAFheVxYAA0AIkFOJ1gBcQQaUQKKA5w7LpcEBwkJaKMUEQA7';
}
}
parser.kmzMetaData[internalSrc(entry.name)]=parser.kmzMetaData[mdUrl];
};
var kmlExtractCb = function(entry, entryContent) {
if ((typeof entryContent.description == "string")
&& (entryContent.name == "Error")) {
geoXML3.log('KMZ error extracting ' + entry.name +
': ' + entryContent.description);
callback();
return;
}
// check to see if the KML is the last file extracted
clearTimeout(kmlProcessing.timer);
if (kmlProcessing.extractLeft <= 1) {
kmlProcessing.extractLeft--;
callback(geoXML3.xmlParse(entryContent));
return;
}
else {
// KML file isn't last yet; it may need to use those files, so
wait a bit (100ms)
kmlProcessing.timerCalls++;
if (kmlProcessing.timerCalls < 100) {
kmlProcessing.timer = setTimeout(function() { kmlExtractCb(entry,
entryContent); }, 100);
}
else {
geoXML3.log('KMZ warning extracting ' + url + ':
entire ZIP has not been extracted after 10 seconds; running through KML,
anyway...');
kmlProcessing.extractLeft--;
callback(geoXML3.xmlParse(entryContent));
}
}
return;
};
for (var i = 0; i < zip.entries.length; i++) {
var entry = zip.entries[i];
var ext = entry.name.substring(entry.name.lastIndexOf(".")
+ 1).toLowerCase();
if (!/^(gif|jpe?g|png|kml)$/.test(ext)) continue; // not going to
bother to extract files we don't support
if (ext === "kml" && i != KML) continue;
// extra KMLs get discarded
if (!parser && ext != "kml") continue;
// cannot store images without a parser object
// extract asynchronously
kmlProcessing.extractLeft++;
if (ext === "kml") entry.extract(kmlExtractCb);
else entry.extract(extractCb);
}
}); //,3 for most verbose logging
};
/**
* Extract the text value of a DOM node, with leading and trailing
whitespace trimmed.
*
* @param {Element} node XML node/element.
* @param {Any} delVal Default value if the node doesn't exist.
* @return {String|Null}
*/
geoXML3.nodeValue = function(node, defVal) {
var retStr="";
if (!node) {
return (typeof defVal === 'undefined' || defVal === null) ?
null : defVal;
}
if(node.nodeType==3||node.nodeType==4||node.nodeType==2){
retStr+=node.nodeValue;
}else if(node.nodeType==1||node.nodeType==9||node.nodeType==11){
for(var i=0;i<node.childNodes.length;++i){
retStr+=arguments.callee(node.childNodes[i]);
}
}
return retStr;
};
/**
* Loosely translate various values of a DOM node to a boolean.
*
* @param {Element} node XML node/element.
* @param {Boolean} delVal Default value if the node doesn't exist.
* @return {Boolean|Null}
*/
geoXML3.getBooleanValue = function(node, defVal) {
var nodeContents = geoXML3.nodeValue(node);
if (nodeContents === null) return defVal || false;
nodeContents = parseInt(nodeContents);
if (isNaN(nodeContents)) return true;
if (nodeContents == 0) return false;
else return true;
}
/**
* Browser-normalized version of getElementsByTagNameNS.
*
* <p>Required because IE8 doesn't define it.</p>
*
* @param {Element|Document} node DOM object.
* @param {String} namespace Full namespace URL to search against.
* @param {String} tagname XML local tag name.
* @return {Array of Elements}
* @author Brendan Byrd
*/
geoXML3.getElementsByTagNameNS = function(node, namespace, tagname) {
if (node && typeof node.getElementsByTagNameNS !=
'undefined') return node.getElementsByTagNameNS(namespace,
tagname);
if (!node) return [];
var root = node.documentElement || node.ownerDocument &&
node.ownerDocument.documentElement;
if (!root || !root.attributes) return [];
// search for namespace prefix
for (var i = 0; i < root.attributes.length; i++) {
var attr = root.attributes[i];
if (attr.prefix === 'xmlns' && attr.nodeValue
=== namespace) return node.getElementsByTagName(attr.baseName +
':' + tagname);
else if (attr.nodeName === 'xmlns' && attr.nodeValue
=== namespace) {
// default namespace
if (typeof node.selectNodes != 'undefined') {
// Newer IEs have the SelectionNamespace property that can be used
with selectNodes
if
(!root.ownerDocument.getProperty('SelectionNamespaces'))
root.ownerDocument.setProperty('SelectionNamespaces',
"xmlns:defaultNS='" + namespace + "'");
return node.selectNodes('.//defaultNS:' + tagname);
}
else {
// Otherwise, you can still try to tack on the 'xmlns'
attribute to root
root.setAttribute('xmlns:defaultNS', namespace);
return node.getElementsByTagName('defaultNS:' + tagname);
}
}
}
return geoXML3.getElementsByTagName(node, tagname); // try the
unqualified version
};
/**
* Browser-normalized version of getElementsByTagName.
*
* <p>Required because MSXML 6.0 will treat this function as a
NS-qualified function,
* despite the missing NS parameter.</p>
*
* @param {Element|Document} node DOM object.
* @param {String} tagname XML local tag name.
* @return {Array of Elements}
* @author Brendan Byrd
*/
geoXML3.getElementsByTagName = function(node, tagname) {
if (node && typeof node.getElementsByTagNameNS !=
'undefined') return node.getElementsByTagName(tagname); // if it
has both functions, it should be accurate
// if (node && typeof node.selectNodes != 'undefined')
return node.selectNodes(".//*[local-name()='" +
tagname + "']");
return node.getElementsByTagName(tagname); // hope for the best...
}
/**
* Turn a directory + relative URL into an absolute one.
*
* @private
* @param {String} d Base directory.
* @param {String} s Relative URL.
* @return {String} Absolute URL.
* @author Brendan Byrd
*/
var toAbsURL = function (d, s) {
var p, f, i;
var h = location.protocol + "://" + location.host;
if (!s.length) return '';
if (/^\w+:/.test(s)) return s;
if (s.indexOf('/') == 0) return h + s;
p = d.replace(/\/[^\/]*$/, '');
f = s.match(/\.\.\//g);
if (f) {
s = s.substring(f.length * 3);
for (i = f.length; i--;) { p = p.substring(0,
p.lastIndexOf('/')); }
}
return h + p + '/' + s;
}
var internalSrc = function(src) {
//this gets the full url
var url = document.location.href;
//this removes everything after the last slash in the path
url = url.substring(0,url.lastIndexOf("/") + 1);
var internalPath= url+src;
return internalPath;
}
/**
* Remove current host from URL
*
* @private
* @param {String} s Absolute or relative URL.
* @return {String} Root-based relative URL.
* @author Brendan Byrd
*/
var dehostURL = function (s) {
var h = location.protocol + "://" + location.host;
h = h.replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1'); //
quotemeta
return s.replace(new RegExp('^' + h, 'i'),
'');
}
/**
* Removes all query strings, #IDs, '../' references, and
* hosts from a URL.
*
* @private
* @param {String} d Base directory.
* @param {String} s Absolute or relative URL.
* @return {String} Root-based relative URL.
* @author Brendan Byrd
*/
var cleanURL = function (d, s) { return dehostURL(toAbsURL(d ?
d.split('#')[0].split('?')[0] :
defileURL(location.pathname), s ?
s.split('#')[0].split('?')[0] : '')); }
/**
* Remove filename from URL
*
* @private
* @param {String} s Relative URL.
* @return {String} Base directory.
* @author Brendan Byrd
*/
var defileURL = function (s) { return s ? s.substr(0,
s.lastIndexOf('/') + 1) : '/'; }
// Some extra Array subs for ease of use
//
http://stackoverflow.com/questions/143847/best-way-to-find-an-item-in-a-javascript-array
Array.prototype.hasObject = (
!Array.indexOf ? function (obj) {
var l = this.length + 1;
while (l--) {
if (this[l - 1] === obj) return true;
}
return false;
} : function (obj) {
return (this.indexOf(obj) !== -1);
}
);
Array.prototype.hasItemInObj = function (name, item) {
var l = this.length + 1;
while (l--) {
if (this[l - 1][name] === item) return true;
}
return false;
};
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (obj, fromIndex) {
if (fromIndex == null) {
fromIndex = 0;
} else if (fromIndex < 0) {
fromIndex = Math.max(0, this.length + fromIndex);
}
for (var i = fromIndex, j = this.length; i < j; i++) {
if (this[i] === obj) return i;
}
return -1;
};
}
Array.prototype.indexOfObjWithItem = function (name, item, fromIndex) {
if (fromIndex == null) {
fromIndex = 0;
} else if (fromIndex < 0) {
fromIndex = Math.max(0, this.length + fromIndex);
}
for (var i = fromIndex, j = this.length; i < j; i++) {
if (this[i][name] === item) return i;
}
return -1;
};
/**
* Borrowed from jquery.base64.js, with some "Array as input"
corrections
*
* @private
* @param {Array of charCodes} input An array of byte ASCII codes (0-255).
* @return {String} A base64-encoded string.
* @author Brendan Byrd
*/
var base64Encode = function(input) {
var keyString =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
while (i < input.length) {
chr1 = input[i++];
chr2 = input[i++];
chr3 = input[i++];
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (chr2 == undefined) enc3 = enc4 = 64;
else if (chr3 == undefined) enc4 = 64;
output = output + keyString.charAt(enc1) + keyString.charAt(enc2) +
keyString.charAt(enc3) + keyString.charAt(enc4);
}
return output;
};