/**
* @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.
*
*/
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.
*
*
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.
*
* @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
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$[name]\n$[description]
\n$[geDirections]
",
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 0) {
break;
} else {
return [{coordinates: []}];
}
}
for (var j=0; j 0)) {
var style = processStyle(node, '{inline}', '{inline}');
processStyleID(style);
if (style) placemark.style = style;
}
if (/^https?:\/\//.test(placemark.description)) {
placemark.description = ['', placemark.description, ''].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= 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);
}
}
}
// 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]))
}
};
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);
if (!!parserOptions.createOverlay) {
// User-defined overlay handler
parserOptions.createOverlay(groundOverlay, doc);
} else {
// 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) {
// Call the built-in overlay creator
overlay = createOverlay(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 (!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 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
}
aY = scaled.h - rnd( ((icon.hotSpot.yunits === 'fraction') ? icon.dim.h : 1) * scaled.aY ); // insetPixels Y = pixels Y
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});
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 path = [];
var bounds = new google.maps.LatLngBounds();
for (var j=0; jTo Here - From Here';
}
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: '' + iwText + '
',
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(""+iW.getContent()+"
");
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.
*
* 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.
*/
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 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 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.
*
* 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.
*
* @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.
*
* 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.
*
* @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.
*
* 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.
*
* @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("WARNING") == 0) { // non-fatal, but still might be useful
geoXML3.log('HTTP/ZIP warning 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 ' + mdUrl + ': ' + 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);
}
});
};
/**
* 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;iRequired because IE8 doesn't define it.
*
* @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.
*
* Required because MSXML 6.0 will treat this function as a NS-qualified function,
* despite the missing NS parameter.
*
* @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;
};