summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js')
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js3398
1 files changed, 1809 insertions, 1589 deletions
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js
index 6cc72d79..83d5929c 100644
--- a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js
@@ -24,17 +24,67 @@
* limitations under the License.
*
*/
-
-if (!String.prototype.trim) {
/**
- * Remove leading and trailing whitespace.
+ * A MultiGeometry object that will allow multiple polylines in a MultiGeometry
+ * containing LineStrings to be treated as a single object
*
- * @augments String
- * @return {String}
+ * @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
*/
- String.prototype.trim = function () {
- return this.replace(/^\s+|\s+$/g, '');
- };
+// 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, '');
+ };
}
/**
@@ -53,977 +103,1049 @@ geoXML3 = window.geoXML3 || {instances: []};
* @param {geoXML3.parserOptions} options
*/
geoXML3.parser = function (options) {
- // 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<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(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))
- };
-
- styleNodes = getElementsByTagName(styleNodes[0], 'hotSpot')[0];
- 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')
- };
- }
-
- // 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 ( (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;
- }
- };
- icon.img.src = icon.url;
-
- // sometimes the file is already cached and it never calls onLoad
- if (icon.img.width > 0) {
- icon.dim.w = icon.img.width;
- icon.dim.h = 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; unnecessary 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"]) {
- 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) {
- // 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 placemark, coords, path, 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)
- };
- 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;
- }
- }
- }
- }
-
- // 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].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;
- }
- }
- // polygon/line
- var poly, line;
- if (!!doc) {
- if (placemark.Polygon) doc.gpolygons = doc.gpolygons || [];
- if (placemark.LineString) 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 (!!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);
- }
- }
- }
-
- // 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) {
- parserOptions.map.fitBounds(doc.internals.bounds);
- }
- if (parserOptions.afterParse) {
- parserOptions.afterParse(doc.internals.docSet);
- }
- }
- };
-
- 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.href) return;
-
- if (icon.img && !icon.img.complete) {
- // 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;
- }
- 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
- icon.dim.w = icon.img.width;
- icon.dim.h = icon.img.height;
- }
- else {
- // settle for a default of 32x32
- icon.dim.whGuess = true;
- icon.dim.w = 32;
- icon.dim.h = 32;
- }
- }
-
- // pre-scaled variables
- var rnd = Math.round;
- var scaled = {
- x: icon.dim.x * icon.scale,
- y: icon.dim.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
- 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 = 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 = new google.maps.MarkerImage(
- icon.url, // url
- iconSize, // size
- iconOrigin, // origin
- iconAnchor, // anchor
- 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 = new google.maps.MarkerImage(
- 'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', // url
- shadowSize, // size
- null, // origin
- shadowPoint, // anchor
- shadowSize // scaledSize
- );
- } else if (icon.href.indexOf('-pushpin.png') > -1) {
- // Pushpin marker icon
- icon.shadow = new google.maps.MarkerImage(
- 'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png', // url
- shadowSize, // size
- null, // origin
- shadowPoint, // anchor
- shadowSize // scaledSize
- );
- } /* else {
+ // 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
@@ -1033,251 +1155,293 @@ function processStyleUrl(node) {
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 = [];
- for (var j=0; j<placemark.LineString.length; j++) {
- var coords = placemark.LineString[j].coordinates;
- var bounds = new google.maps.LatLngBounds();
- 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);
- }
- }
- // point to open the infowindow if triggered
- var point = path[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
- });
- 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) {
- 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.open(this.map, this.bounds ? null : this);
- });
-
- }
-
- return {
- // Expose some properties and methods
-
- options: parserOptions,
- docs: docs,
- docsByUrl: docsByUrl,
- kmzMetaData: kmzMetaData,
-
- parse: parse,
- parseKmlString: parseKmlString,
- hideDocument: hideDocument,
- showDocument: showDocument,
- processStyles: processStyles,
- createMarker: createMarker,
- createOverlay: createOverlay,
- createPolyline: createPolyline,
- createPolygon: createPolygon
- };
+ }
+
+ 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;
- }
+ // 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); }
+ if (!!window.console) {
+ console.log(msg);
+ } else { alert("log:"+msg); }
};
/**
@@ -1290,61 +1454,61 @@ geoXML3.log = function(msg) {
* @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 &lt;Style&gt; 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;
+ 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 &lt;Style&gt; 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;
};
/**
@@ -1356,18 +1520,18 @@ geoXML3.parserOptions = function (overrides) {
* @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;
+ 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;
};
/**
@@ -1394,35 +1558,43 @@ geoXML3.fetchers = [];
* @return {Element|Document} DOM.
*/
geoXML3.xmlParse = function (str) {
- if (!!window.DOMParser) return (new DOMParser()).parseFromString(str, 'text/xml');
- else if (!!window.ActiveXObject) {
- var doc;
-
- // the many versions of IE's DOM parsers
- var AXOs = [
- 'MSXML2.DOMDocument.6.0',
- 'MSXML2.DOMDocument.5.0',
- 'MSXML2.DOMDocument.4.0',
- 'MSXML2.DOMDocument.3.0',
- 'MSXML2.DOMDocument',
- 'Microsoft.XMLDOM',
- 'MSXML.DOMDocument'
- ];
- for (var i = 0; i < AXOs.length; i++) {
- try { doc = new ActiveXObject(AXOs[i]); break; }
- catch(e) { continue; }
- }
- if (!doc) return createElement('div', null);
-
- if (doc.async) doc.async = false;
- doc.loadXML(str);
- return doc;
- }
-
- return createElement('div', null);
+ 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
@@ -1433,62 +1605,87 @@ geoXML3.xmlParse = function (str) {
* @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;
- }
- }
-
- if (!!xhrFetcher.fetcher.overrideMimeType) xhrFetcher.fetcher.overrideMimeType('text/xml');
- xhrFetcher.fetcher.open('GET', url, true);
- 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 xmlDoc = xhrFetcher.fetcher.responseXML;
- if (xmlDoc && !xmlDoc.documentElement && !xmlDoc.ownerElement) xmlDoc.loadXML(xhrFetcher.fetcher.responseText);
- callback(xmlDoc);
- } else // handle valid xml sent with wrong MIME type
- callback(geoXML3.xmlParse(xhrFetcher.fetcher.responseText));
- }
-
- // We're done with this fetcher object
- geoXML3.fetchers.push(xhrFetcher);
- }
- };
-
- xhrFetcher.xhrtimeout = setTimeout(timeoutHandler, 60000);
- xhrFetcher.fetcher.send(null);
- return null;
+ 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;
};
/**
@@ -1507,117 +1704,131 @@ geoXML3.fetchXML = function (url, callback) {
* @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) && parser.kmzMetaData[mdUrl].dataUrl.length > 2071)
- 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';
- };
- 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);
- }
- });
+ // 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
};
@@ -1629,18 +1840,18 @@ geoXML3.fetchZIP = function (url, callback, parser) {
* @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;
+ 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;
};
/**
@@ -1651,12 +1862,12 @@ geoXML3.nodeValue = function(node, defVal) {
* @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;
+ 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;
}
/**
@@ -1671,32 +1882,32 @@ geoXML3.getBooleanValue = function(node, defVal) {
* @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
+ 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
};
/**
@@ -1711,9 +1922,9 @@ geoXML3.getElementsByTagNameNS = function(node, namespace, tagname) {
* @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.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...
+ return node.getElementsByTagName(tagname); // hope for the best...
}
/**
@@ -1726,21 +1937,30 @@ geoXML3.getElementsByTagName = function(node, tagname) {
* @author Brendan Byrd
*/
var toAbsURL = function (d, s) {
- var p, f, i;
- var h = location.protocol + "://" + location.host;
+ 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;
- 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('/')); }
+ }
- 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;
+}
- 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;
}
/**
@@ -1752,9 +1972,9 @@ var toAbsURL = function (d, s) {
* @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'), '');
+ var h = location.protocol + "://" + location.host;
+ h = h.replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1'); // quotemeta
+ return s.replace(new RegExp('^' + h, 'i'), '');
}
/**
@@ -1782,46 +2002,46 @@ 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.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;
+ 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.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;
+ 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;
};
/**
@@ -1833,23 +2053,23 @@ Array.prototype.indexOfObjWithItem = function (name, item, fromIndex) {
* @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;
+ 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;
};