geoxml-rails 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/CODE_OF_CONDUCT.md +13 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +13 -0
  6. data/README.md +42 -0
  7. data/Rakefile +2 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +7 -0
  10. data/geoxml-rails.gemspec +26 -0
  11. data/geoxml-test/.gitignore +17 -0
  12. data/geoxml-test/Gemfile +46 -0
  13. data/geoxml-test/Gemfile.lock +169 -0
  14. data/geoxml-test/README.rdoc +28 -0
  15. data/geoxml-test/Rakefile +6 -0
  16. data/geoxml-test/app/assets/images/.keep +0 -0
  17. data/geoxml-test/app/assets/javascripts/application.js +17 -0
  18. data/geoxml-test/app/assets/javascripts/application.jscurl +1 -0
  19. data/geoxml-test/app/assets/stylesheets/application.css +15 -0
  20. data/geoxml-test/app/controllers/application_controller.rb +5 -0
  21. data/geoxml-test/app/controllers/concerns/.keep +0 -0
  22. data/geoxml-test/app/helpers/application_helper.rb +2 -0
  23. data/geoxml-test/app/mailers/.keep +0 -0
  24. data/geoxml-test/app/models/.keep +0 -0
  25. data/geoxml-test/app/models/concerns/.keep +0 -0
  26. data/geoxml-test/app/views/layouts/application.html.erb +14 -0
  27. data/geoxml-test/bin/bundle +3 -0
  28. data/geoxml-test/bin/rails +8 -0
  29. data/geoxml-test/bin/rake +8 -0
  30. data/geoxml-test/bin/setup +29 -0
  31. data/geoxml-test/bin/spring +15 -0
  32. data/geoxml-test/config/application.rb +26 -0
  33. data/geoxml-test/config/boot.rb +3 -0
  34. data/geoxml-test/config/database.yml +25 -0
  35. data/geoxml-test/config/environment.rb +5 -0
  36. data/geoxml-test/config/environments/development.rb +41 -0
  37. data/geoxml-test/config/environments/production.rb +79 -0
  38. data/geoxml-test/config/environments/test.rb +42 -0
  39. data/geoxml-test/config/initializers/assets.rb +11 -0
  40. data/geoxml-test/config/initializers/backtrace_silencers.rb +7 -0
  41. data/geoxml-test/config/initializers/cookies_serializer.rb +3 -0
  42. data/geoxml-test/config/initializers/filter_parameter_logging.rb +4 -0
  43. data/geoxml-test/config/initializers/inflections.rb +16 -0
  44. data/geoxml-test/config/initializers/mime_types.rb +4 -0
  45. data/geoxml-test/config/initializers/session_store.rb +3 -0
  46. data/geoxml-test/config/initializers/wrap_parameters.rb +14 -0
  47. data/geoxml-test/config/locales/en.yml +23 -0
  48. data/geoxml-test/config/routes.rb +56 -0
  49. data/geoxml-test/config/secrets.yml +22 -0
  50. data/geoxml-test/config.ru +4 -0
  51. data/geoxml-test/db/seeds.rb +7 -0
  52. data/geoxml-test/lib/assets/.keep +0 -0
  53. data/geoxml-test/lib/tasks/.keep +0 -0
  54. data/geoxml-test/log/.keep +0 -0
  55. data/geoxml-test/public/404.html +67 -0
  56. data/geoxml-test/public/422.html +67 -0
  57. data/geoxml-test/public/500.html +66 -0
  58. data/geoxml-test/public/favicon.ico +0 -0
  59. data/geoxml-test/public/robots.txt +5 -0
  60. data/geoxml-test/test/controllers/.keep +0 -0
  61. data/geoxml-test/test/fixtures/.keep +0 -0
  62. data/geoxml-test/test/helpers/.keep +0 -0
  63. data/geoxml-test/test/integration/.keep +0 -0
  64. data/geoxml-test/test/mailers/.keep +0 -0
  65. data/geoxml-test/test/models/.keep +0 -0
  66. data/geoxml-test/test/test_helper.rb +10 -0
  67. data/geoxml-test/vendor/assets/javascripts/.keep +0 -0
  68. data/geoxml-test/vendor/assets/stylesheets/.keep +0 -0
  69. data/lib/geoxml/rails/version.rb +5 -0
  70. data/lib/geoxml/rails.rb +8 -0
  71. data/vendor/assets/javascripts/geoxml3.js +1955 -0
  72. metadata +142 -0
@@ -0,0 +1,1955 @@
1
+ /**
2
+ * @fileOverview Renders KML on the Google Maps JavaScript API Version 3
3
+ * @name GeoXML3
4
+ * @author Sterling Udell, Larry Ross, Brendan Byrd
5
+ * @see http://code.google.com/p/geoxml3/
6
+ *
7
+ * geoxml3.js
8
+ *
9
+ * Renders KML on the Google Maps JavaScript API Version 3
10
+ * http://code.google.com/p/geoxml3/
11
+ *
12
+ * Copyright 2010 Sterling Udell, Larry Ross
13
+ *
14
+ * Licensed under the Apache License, Version 2.0 (the "License");
15
+ * you may not use this file except in compliance with the License.
16
+ * You may obtain a copy of the License at
17
+ *
18
+ * http://www.apache.org/licenses/LICENSE-2.0
19
+ *
20
+ * Unless required by applicable law or agreed to in writing, software
21
+ * distributed under the License is distributed on an "AS IS" BASIS,
22
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
+ * See the License for the specific language governing permissions and
24
+ * limitations under the License.
25
+ *
26
+ */
27
+
28
+ if (!String.prototype.trim) {
29
+ /**
30
+ * Remove leading and trailing whitespace.
31
+ *
32
+ * @augments String
33
+ * @return {String}
34
+ */
35
+ String.prototype.trim = function () {
36
+ return this.replace(/^\s+|\s+$/g, '');
37
+ };
38
+ }
39
+
40
+ /**
41
+ * @namespace The GeoXML3 namespace.
42
+ */
43
+ geoXML3 = window.geoXML3 || {instances: []};
44
+
45
+ /**
46
+ * Constructor for the root KML parser object.
47
+ *
48
+ * <p>All top-level objects and functions are declared under a namespace of geoXML3.
49
+ * The core object is geoXML3.parser; typically, you'll instantiate a one parser
50
+ * per map.</p>
51
+ *
52
+ * @class Main XML parser.
53
+ * @param {geoXML3.parserOptions} options
54
+ */
55
+ geoXML3.parser = function (options) {
56
+ // Inherit from Google MVC Object to include event handling
57
+ google.maps.MVCObject.call(this);
58
+
59
+ // Private variables
60
+ var parserOptions = new geoXML3.parserOptions(options);
61
+ var docs = []; // Individual KML documents
62
+ var docsByUrl = {}; // Same docs as an hash by cleanURL
63
+ var kmzMetaData = {}; // Extra files from KMZ data
64
+ var styles = {}; // Global list of styles
65
+ var lastPlacemark;
66
+ var parserName;
67
+ if (!parserOptions.infoWindow && parserOptions.singleInfoWindow)
68
+ parserOptions.infoWindow = new google.maps.InfoWindow();
69
+
70
+ var parseKmlString = function (kmlString, docSet) {
71
+ // Internal values for the set of documents as a whole
72
+ var internals = {
73
+ parser: this,
74
+ docSet: docSet || [],
75
+ remaining: 1,
76
+ parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
77
+ };
78
+ thisDoc = new Object();
79
+ thisDoc.internals = internals;
80
+ internals.docSet.push(thisDoc);
81
+ render(geoXML3.xmlParse(kmlString),thisDoc);
82
+ }
83
+
84
+ var parse = function (urls, docSet) {
85
+ // Process one or more KML documents
86
+ if (!parserName) {
87
+ parserName = 'geoXML3.instances[' + (geoXML3.instances.push(this) - 1) + ']';
88
+ }
89
+
90
+ if (typeof urls === 'string') {
91
+ // Single KML document
92
+ urls = [urls];
93
+ }
94
+
95
+ // Internal values for the set of documents as a whole
96
+ var internals = {
97
+ parser: this,
98
+ docSet: docSet || [],
99
+ remaining: urls.length,
100
+ parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
101
+ };
102
+ var thisDoc, j;
103
+ for (var i = 0; i < urls.length; i++) {
104
+ var baseUrl = cleanURL(defileURL(location.pathname), urls[i]);
105
+ if (docsByUrl[baseUrl]) {
106
+ // Reloading an existing document
107
+ thisDoc = docsByUrl[baseUrl];
108
+ thisDoc.reload = true;
109
+ }
110
+ else {
111
+ thisDoc = new Object();
112
+ thisDoc.baseUrl = baseUrl;
113
+ internals.docSet.push(thisDoc);
114
+ }
115
+ thisDoc.url = urls[i];
116
+ thisDoc.internals = internals;
117
+ fetchDoc(thisDoc.url, thisDoc);
118
+ }
119
+ };
120
+
121
+ function fetchDoc(url, doc, resFunc) {
122
+ resFunc = resFunc || function (responseXML) { render(responseXML, doc); };
123
+
124
+ if (typeof ZipFile === 'function' && typeof JSIO === 'object' && typeof JSIO.guessFileType === 'function') { // KMZ support requires these modules loaded
125
+ contentType = JSIO.guessFileType(doc.baseUrl);
126
+ if (contentType == JSIO.FileType.Binary || contentType == JSIO.FileType.Unknown) {
127
+ doc.isCompressed = true;
128
+ doc.baseDir = doc.baseUrl + '/';
129
+ geoXML3.fetchZIP(url, resFunc, doc.internals.parser);
130
+ return;
131
+ }
132
+ }
133
+ doc.isCompressed = false;
134
+ doc.baseDir = defileURL(doc.baseUrl);
135
+ geoXML3.fetchXML(url, resFunc);
136
+ }
137
+
138
+ var hideDocument = function (doc) {
139
+ if (!doc) doc = docs[0];
140
+ // Hide the map objects associated with a document
141
+ var i;
142
+ if (!!doc.markers) {
143
+ for (i = 0; i < doc.markers.length; i++) {
144
+ if(!!doc.markers[i].infoWindow) doc.markers[i].infoWindow.close();
145
+ doc.markers[i].setVisible(false);
146
+ }
147
+ }
148
+ if (!!doc.ggroundoverlays) {
149
+ for (i = 0; i < doc.ggroundoverlays.length; i++) {
150
+ doc.ggroundoverlays[i].setOpacity(0);
151
+ }
152
+ }
153
+ if (!!doc.gpolylines) {
154
+ for (i=0;i<doc.gpolylines.length;i++) {
155
+ if(!!doc.gpolylines[i].infoWindow) doc.gpolylines[i].infoWindow.close();
156
+ doc.gpolylines[i].setMap(null);
157
+ }
158
+ }
159
+ if (!!doc.gpolygons) {
160
+ for (i=0;i<doc.gpolygons.length;i++) {
161
+ if(!!doc.gpolygons[i].infoWindow) doc.gpolygons[i].infoWindow.close();
162
+ doc.gpolygons[i].setMap(null);
163
+ }
164
+ }
165
+ };
166
+
167
+ var showDocument = function (doc) {
168
+ if (!doc) doc = docs[0];
169
+ // Show the map objects associated with a document
170
+ var i;
171
+ if (!!doc.markers) {
172
+ for (i = 0; i < doc.markers.length; i++) {
173
+ doc.markers[i].setVisible(true);
174
+ }
175
+ }
176
+ if (!!doc.ggroundoverlays) {
177
+ for (i = 0; i < doc.ggroundoverlays.length; i++) {
178
+ doc.ggroundoverlays[i].setOpacity(doc.ggroundoverlays[i].percentOpacity_);
179
+ }
180
+ }
181
+ if (!!doc.gpolylines) {
182
+ for (i=0;i<doc.gpolylines.length;i++) {
183
+ doc.gpolylines[i].setMap(parserOptions.map);
184
+ }
185
+ }
186
+ if (!!doc.gpolygons) {
187
+ for (i=0;i<doc.gpolygons.length;i++) {
188
+ doc.gpolygons[i].setMap(parserOptions.map);
189
+ }
190
+ }
191
+ };
192
+
193
+ var defaultStyle = {
194
+ balloon: {
195
+ bgColor: 'ffffffff',
196
+ textColor: 'ff000000',
197
+ text: "<h3>$[name]</h3>\n<div>$[description]</div>\n<div>$[geDirections]</div>",
198
+ displayMode: 'default'
199
+ },
200
+ icon: {
201
+ scale: 1.0,
202
+ dim: {
203
+ x: 0,
204
+ y: 0,
205
+ w: -1,
206
+ h: -1
207
+ },
208
+ hotSpot: {
209
+ x: 0.5,
210
+ y: 0.5,
211
+ xunits: 'fraction',
212
+ yunits: 'fraction'
213
+ }
214
+ },
215
+ line: {
216
+ color: 'ffffffff', // white (KML default)
217
+ colorMode: 'normal',
218
+ width: 1.0
219
+ },
220
+ poly: {
221
+ color: 'ffffffff', // white (KML default)
222
+ colorMode: 'normal',
223
+ fill: true,
224
+ outline: true
225
+ }
226
+ };
227
+
228
+ var kmlNS = 'http://www.opengis.net/kml/2.2';
229
+ var gxNS = 'http://www.google.com/kml/ext/2.2';
230
+ var nodeValue = geoXML3.nodeValue;
231
+ var getBooleanValue = geoXML3.getBooleanValue;
232
+ var getElementsByTagNameNS = geoXML3.getElementsByTagNameNS;
233
+ var getElementsByTagName = geoXML3.getElementsByTagName;
234
+
235
+ function processStyleUrl(node) {
236
+ var styleUrlStr = nodeValue(getElementsByTagName(node, 'styleUrl')[0]);
237
+ if (!!styleUrlStr && styleUrlStr.indexOf('#') != -1)
238
+ var styleUrl = styleUrlStr.split('#');
239
+ else var styleUrl = ["",""];
240
+ return styleUrl;
241
+ }
242
+
243
+ function processStyle(thisNode, baseUrl, styleID, baseDir) {
244
+ var style = (baseUrl === '{inline}') ? clone(defaultStyle) : (styles[baseUrl][styleID] = styles[baseUrl][styleID] || clone(defaultStyle));
245
+
246
+ var styleNodes = getElementsByTagName(thisNode, 'BalloonStyle');
247
+ if (!!styleNodes && styleNodes.length > 0) {
248
+ style.balloon.bgColor = nodeValue(getElementsByTagName(styleNodes[0], 'bgColor')[0], style.balloon.bgColor);
249
+ style.balloon.textColor = nodeValue(getElementsByTagName(styleNodes[0], 'textColor')[0], style.balloon.textColor);
250
+ style.balloon.text = nodeValue(getElementsByTagName(styleNodes[0], 'text')[0], style.balloon.text);
251
+ style.balloon.displayMode = nodeValue(getElementsByTagName(styleNodes[0], 'displayMode')[0], style.balloon.displayMode);
252
+ }
253
+
254
+ // style.list = (unsupported; doesn't make sense in Google Maps)
255
+
256
+ var styleNodes = getElementsByTagName(thisNode, 'IconStyle');
257
+ if (!!styleNodes && styleNodes.length > 0) {
258
+ var icon = style.icon;
259
+
260
+ icon.scale = parseFloat(nodeValue(getElementsByTagName(styleNodes[0], 'scale')[0], icon.scale));
261
+ // style.icon.heading = (unsupported; not supported in API)
262
+ // style.icon.color = (unsupported; not supported in API)
263
+ // style.icon.colorMode = (unsupported; not supported in API)
264
+
265
+ styleNodes = getElementsByTagName(styleNodes[0], 'hotSpot');
266
+ if (!!styleNodes && styleNodes.length > 0) {
267
+ icon.hotSpot = {
268
+ x: styleNodes[0].getAttribute('x'),
269
+ y: styleNodes[0].getAttribute('y'),
270
+ xunits: styleNodes[0].getAttribute('xunits'),
271
+ yunits: styleNodes[0].getAttribute('yunits')
272
+ };
273
+ }
274
+
275
+ styleNodes = getElementsByTagName(thisNode, 'Icon');
276
+ if (!!styleNodes && styleNodes.length > 0) {
277
+ icon.href = nodeValue(getElementsByTagName(styleNodes[0], 'href')[0]);
278
+ icon.url = cleanURL(baseDir, icon.href);
279
+ // Detect images buried in KMZ files (and use a base64 encoded URL)
280
+ if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
281
+
282
+ // Support for icon palettes and exact size dimensions
283
+ icon.dim = {
284
+ x: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'x')[0], icon.dim.x)),
285
+ y: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'y')[0], icon.dim.y)),
286
+ w: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'w')[0], icon.dim.w)),
287
+ h: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'h')[0], icon.dim.h))
288
+ };
289
+
290
+ // certain occasions where we need the pixel size of the image (like the default settings...)
291
+ // (NOTE: Scale is applied to entire image, not just the section of the icon palette. So,
292
+ // if we need scaling, we'll need the img dimensions no matter what.)
293
+ if (true /* (icon.dim.w < 0 || icon.dim.h < 0) && (icon.xunits != 'pixels' || icon.yunits == 'fraction') || icon.scale != 1.0 */) {
294
+ // (hopefully, this will load by the time we need it...)
295
+ icon.img = new Image();
296
+ icon.img.onload = function() {
297
+ if (icon.dim.w < 0 || icon.dim.h < 0) {
298
+ icon.dim.w = this.width;
299
+ icon.dim.h = this.height;
300
+ } else {
301
+ icon.dim.th = this.height;
302
+ }
303
+ };
304
+ icon.img.src = icon.url;
305
+
306
+ // sometimes the file is already cached and it never calls onLoad
307
+ if (icon.img.width > 0) {
308
+ if (icon.dim.w < 0 || icon.dim.h < 0) {
309
+ icon.dim.w = icon.img.width;
310
+ icon.dim.h = icon.img.height;
311
+ } else {
312
+ icon.dim.th = icon.img.height;
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+
319
+ // style.label = (unsupported; may be possible but not with API)
320
+
321
+ styleNodes = getElementsByTagName(thisNode, 'LineStyle');
322
+ if (!!styleNodes && styleNodes.length > 0) {
323
+ style.line.color = nodeValue(getElementsByTagName(styleNodes[0], 'color')[0], style.line.color);
324
+ style.line.colorMode = nodeValue(getElementsByTagName(styleNodes[0], 'colorMode')[0], style.line.colorMode);
325
+ style.line.width = nodeValue(getElementsByTagName(styleNodes[0], 'width')[0], style.line.width);
326
+ // style.line.outerColor = (unsupported; not supported in API)
327
+ // style.line.outerWidth = (unsupported; not supported in API)
328
+ // style.line.physicalWidth = (unsupported; unneccesary in Google Maps)
329
+ // style.line.labelVisibility = (unsupported; possible to implement)
330
+ }
331
+
332
+ styleNodes = getElementsByTagName(thisNode, 'PolyStyle');
333
+ if (!!styleNodes && styleNodes.length > 0) {
334
+ style.poly.color = nodeValue( getElementsByTagName(styleNodes[0], 'color')[0], style.poly.color);
335
+ style.poly.colorMode = nodeValue( getElementsByTagName(styleNodes[0], 'colorMode')[0], style.poly.colorMode);
336
+ style.poly.outline = getBooleanValue(getElementsByTagName(styleNodes[0], 'outline')[0], style.poly.outline);
337
+ style.poly.fill = getBooleanValue(getElementsByTagName(styleNodes[0], 'fill')[0], style.poly.fill);
338
+ }
339
+ return style;
340
+ }
341
+
342
+ // from http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object
343
+ // http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
344
+ function clone(obj){
345
+ if(obj == null || typeof(obj) != 'object') return obj;
346
+ if (obj.cloneNode) return obj.cloneNode(true);
347
+ var temp = new obj.constructor();
348
+ for(var key in obj) temp[key] = clone(obj[key]);
349
+ return temp;
350
+ }
351
+
352
+ function processStyleMap(thisNode, baseUrl, styleID, baseDir) {
353
+ var pairs = getElementsByTagName(thisNode, 'Pair');
354
+ var map = new Object();
355
+
356
+ // add each key to the map
357
+ for (var pr=0;pr<pairs.length;pr++) {
358
+ var pairKey = nodeValue(getElementsByTagName(pairs[pr], 'key')[0]);
359
+ var pairStyle = nodeValue(getElementsByTagName(pairs[pr], 'Style')[0]);
360
+ var pairStyleUrl = processStyleUrl(pairs[pr]);
361
+ var pairStyleBaseUrl = pairStyleUrl[0] ? cleanURL(baseDir, pairStyleUrl[0]) : baseUrl;
362
+ var pairStyleID = pairStyleUrl[1];
363
+
364
+ if (!!pairStyle) {
365
+ map[pairKey] = processStyle(pairStyle, pairStyleBaseUrl, pairStyleID);
366
+ } else if (!!pairStyleID && !!styles[pairStyleBaseUrl][pairStyleID]) {
367
+ map[pairKey] = clone(styles[pairStyleBaseUrl][pairStyleID]);
368
+ }
369
+ }
370
+ if (!!map["normal"]) {
371
+ styles[baseUrl][styleID] = clone(map["normal"]);
372
+ } else {
373
+ styles[baseUrl][styleID] = clone(defaultStyle);
374
+ }
375
+ if (!!map["highlight"] && !!parserOptions.processStyles) {
376
+ processStyleID(map["highlight"]);
377
+ }
378
+ styles[baseUrl][styleID].map = clone(map);
379
+ }
380
+
381
+ function processPlacemarkCoords(node, tag) {
382
+ var parent = getElementsByTagName(node, tag);
383
+ var coordListA = [];
384
+ for (var i=0; i<parent.length; i++) {
385
+ var coordNodes = getElementsByTagName(parent[i], 'coordinates');
386
+ if (!coordNodes) {
387
+ if (coordListA.length > 0) {
388
+ break;
389
+ } else {
390
+ return [{coordinates: []}];
391
+ }
392
+ }
393
+
394
+ for (var j=0; j<coordNodes.length;j++) {
395
+ var coords = nodeValue(coordNodes[j]).trim();
396
+ coords = coords.replace(/,\s+/g, ',');
397
+ var path = coords.split(/\s+/g);
398
+ var pathLength = path.length;
399
+ var coordList = [];
400
+ for (var k = 0; k < pathLength; k++) {
401
+ coords = path[k].split(',');
402
+ if (!isNaN(coords[0]) && !isNaN(coords[1])) {
403
+ coordList.push({
404
+ lat: parseFloat(coords[1]),
405
+ lng: parseFloat(coords[0]),
406
+ alt: parseFloat(coords[2])
407
+ });
408
+ }
409
+ }
410
+ coordListA.push({coordinates: coordList});
411
+ }
412
+ }
413
+ return coordListA;
414
+ }
415
+
416
+ var render = function (responseXML, doc) {
417
+ // Callback for retrieving a KML document: parse the KML and display it on the map
418
+ if (!responseXML || responseXML == "failed parse") {
419
+ // Error retrieving the data
420
+ geoXML3.log('Unable to retrieve ' + doc.url);
421
+ if (parserOptions.failedParse) parserOptions.failedParse(doc);
422
+ doc.failed = true;
423
+ return;
424
+ } else if (responseXML.parseError && responseXML.parseError.errorCode != 0) {
425
+ // IE parse error
426
+ var err = responseXML.parseError;
427
+ var msg = 'Parse error in line ' + err.line + ', col ' + err.linePos + ' (error code: ' + err.errorCode + ")\n" +
428
+ "\nError Reason: " + err.reason +
429
+ 'Error Line: ' + err.srcText;
430
+
431
+ geoXML3.log('Unable to retrieve ' + doc.url + ': ' + msg);
432
+ if (parserOptions.failedParse) parserOptions.failedParse(doc);
433
+ doc.failed = true;
434
+ return;
435
+ } else if (responseXML.documentElement && responseXML.documentElement.nodeName == 'parsererror') {
436
+ // Firefox parse error
437
+ geoXML3.log('Unable to retrieve ' + doc.url + ': ' + responseXML.documentElement.childNodes[0].nodeValue);
438
+ if (parserOptions.failedParse) parserOptions.failedParse(doc);
439
+ doc.failed = true;
440
+ return;
441
+ } else if (!doc) {
442
+ throw 'geoXML3 internal error: render called with null document';
443
+ } else { //no errors
444
+ var i;
445
+ doc.placemarks = [];
446
+ doc.groundoverlays = [];
447
+ doc.ggroundoverlays = [];
448
+ doc.networkLinks = [];
449
+ doc.gpolygons = [];
450
+ doc.gpolylines = [];
451
+
452
+ // Check for dependent KML files
453
+ var nodes = getElementsByTagName(responseXML, 'styleUrl');
454
+ var docSet = doc.internals.docSet;
455
+
456
+ for (var i = 0; i < nodes.length; i++) {
457
+ var url = nodeValue(nodes[i]).split('#')[0];
458
+ if (!url) continue; // #id (inside doc)
459
+ var rUrl = cleanURL( doc.baseDir, url );
460
+ if (rUrl === doc.baseUrl) continue; // self
461
+ if (docsByUrl[rUrl]) continue; // already loaded
462
+
463
+ var thisDoc;
464
+ var j = docSet.indexOfObjWithItem('baseUrl', rUrl);
465
+ if (j != -1) {
466
+ // Already listed to be loaded, but probably in the wrong order.
467
+ // Load it right away to immediately resolve dependency.
468
+ thisDoc = docSet[j];
469
+ if (thisDoc.failed) continue; // failed to load last time; don't retry it again
470
+ }
471
+ else {
472
+ // Not listed at all; add it in
473
+ thisDoc = new Object();
474
+ thisDoc.url = rUrl; // url can't be trusted inside KMZ files, since it may .. outside of the archive
475
+ thisDoc.baseUrl = rUrl;
476
+ thisDoc.internals = doc.internals;
477
+
478
+ doc.internals.docSet.push(thisDoc);
479
+ doc.internals.remaining++;
480
+ }
481
+
482
+ // render dependent KML first then re-run renderer
483
+ fetchDoc(rUrl, thisDoc, function (thisResXML) {
484
+ render(thisResXML, thisDoc);
485
+ render(responseXML, doc);
486
+ });
487
+
488
+ // to prevent cross-dependency issues, just load the one
489
+ // file first and re-check the rest later
490
+ return;
491
+ }
492
+
493
+ // Parse styles
494
+ doc.styles = styles[doc.baseUrl] = styles[doc.baseUrl] || {};
495
+ var styleID, styleNodes;
496
+ nodes = getElementsByTagName(responseXML, 'Style');
497
+ nodeCount = nodes.length;
498
+ for (i = 0; i < nodeCount; i++) {
499
+ thisNode = nodes[i];
500
+ var styleID = thisNode.getAttribute('id');
501
+ if (!!styleID) processStyle(thisNode, doc.baseUrl, styleID, doc.baseDir);
502
+ }
503
+ // Parse StyleMap nodes
504
+ nodes = getElementsByTagName(responseXML, 'StyleMap');
505
+ for (i = 0; i < nodes.length; i++) {
506
+ thisNode = nodes[i];
507
+ var styleID = thisNode.getAttribute('id');
508
+ if (!!styleID) processStyleMap(thisNode, doc.baseUrl, styleID, doc.baseDir);
509
+ }
510
+
511
+ if (!!parserOptions.processStyles || !parserOptions.createMarker) {
512
+ // Convert parsed styles into GMaps equivalents
513
+ processStyles(doc);
514
+ }
515
+
516
+ // Parse placemarks
517
+ if (!!doc.reload && !!doc.markers) {
518
+ for (i = 0; i < doc.markers.length; i++) {
519
+ doc.markers[i].active = false;
520
+ }
521
+ }
522
+ var placemark, node, coords, path, marker, poly;
523
+ var pathLength, marker, polygonNodes, coordList;
524
+ var placemarkNodes = getElementsByTagName(responseXML, 'Placemark');
525
+ for (pm = 0; pm < placemarkNodes.length; pm++) {
526
+ // Init the placemark object
527
+ node = placemarkNodes[pm];
528
+ var styleUrl = processStyleUrl(node);
529
+ placemark = {
530
+ name: nodeValue(getElementsByTagName(node, 'name')[0]),
531
+ description: nodeValue(getElementsByTagName(node, 'description')[0]),
532
+ styleUrl: styleUrl.join('#'),
533
+ styleBaseUrl: styleUrl[0] ? cleanURL(doc.baseDir, styleUrl[0]) : doc.baseUrl,
534
+ styleID: styleUrl[1],
535
+ visibility: getBooleanValue(getElementsByTagName(node, 'visibility')[0], true),
536
+ balloonVisibility: getBooleanValue(getElementsByTagNameNS(node, gxNS, 'balloonVisibility')[0], !parserOptions.suppressInfoWindows),
537
+ id: node.getAttribute('id')
538
+ };
539
+ placemark.style = (styles[placemark.styleBaseUrl] && styles[placemark.styleBaseUrl][placemark.styleID]) || clone(defaultStyle);
540
+ // inline style overrides shared style
541
+ var inlineStyles = getElementsByTagName(node, 'Style');
542
+ if (inlineStyles && (inlineStyles.length > 0)) {
543
+ var style = processStyle(node, '{inline}', '{inline}');
544
+ processStyleID(style);
545
+ if (style) placemark.style = style;
546
+ }
547
+
548
+ if (/^https?:\/\//.test(placemark.description)) {
549
+ placemark.description = ['<a href="', placemark.description, '">', placemark.description, '</a>'].join('');
550
+ }
551
+
552
+ // record list of variables for substitution
553
+ placemark.vars = {
554
+ display: {
555
+ name: 'Name',
556
+ description: 'Description',
557
+ address: 'Street Address',
558
+ id: 'ID',
559
+ Snippet: 'Snippet',
560
+ geDirections: 'Directions'
561
+ },
562
+ val: {
563
+ name: placemark.name || '',
564
+ description: placemark.description || '',
565
+ address: nodeValue(getElementsByTagName(node, 'address')[0], ''),
566
+ id: node.getAttribute('id') || '',
567
+ Snippet: nodeValue(getElementsByTagName(node, 'Snippet')[0], '')
568
+ },
569
+ directions: [
570
+ 'f=d',
571
+ 'source=GeoXML3'
572
+ ]
573
+ };
574
+
575
+ // add extended data to variables
576
+ var extDataNodes = getElementsByTagName(node, 'ExtendedData');
577
+ if (!!extDataNodes && extDataNodes.length > 0) {
578
+ var dataNodes = getElementsByTagName(extDataNodes[0], 'Data');
579
+ for (var d = 0; d < dataNodes.length; d++) {
580
+ var dn = dataNodes[d];
581
+ var name = dn.getAttribute('name');
582
+ if (!name) continue;
583
+ var dName = nodeValue(getElementsByTagName(dn, 'displayName')[0], name);
584
+ var val = nodeValue(getElementsByTagName(dn, 'value')[0]);
585
+
586
+ placemark.vars.val[name] = val;
587
+ placemark.vars.display[name] = dName;
588
+ }
589
+ }
590
+
591
+ // process MultiGeometry
592
+ var GeometryNodes = getElementsByTagName(node, 'coordinates');
593
+ var Geometry = null;
594
+ if (!!GeometryNodes && (GeometryNodes.length > 0)) {
595
+ for (var gn=0;gn<GeometryNodes.length;gn++) {
596
+ if (GeometryNodes[gn].parentNode &&
597
+ GeometryNodes[gn].parentNode.nodeName) {
598
+ var GeometryPN = GeometryNodes[gn].parentNode;
599
+ Geometry = GeometryPN.nodeName;
600
+
601
+ // Extract the coordinates
602
+ // What sort of placemark?
603
+ switch(Geometry) {
604
+ case "Point":
605
+ placemark.Point = processPlacemarkCoords(node, "Point")[0];
606
+ placemark.latlng = new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng);
607
+ pathLength = 1;
608
+ break;
609
+ case "LinearRing":
610
+ // Polygon/line
611
+ polygonNodes = getElementsByTagName(node, 'Polygon');
612
+ // Polygon
613
+ if (!placemark.Polygon)
614
+ placemark.Polygon = [{
615
+ outerBoundaryIs: {coordinates: []},
616
+ innerBoundaryIs: [{coordinates: []}]
617
+ }];
618
+ for (var pg=0;pg<polygonNodes.length;pg++) {
619
+ placemark.Polygon[pg] = {
620
+ outerBoundaryIs: {coordinates: []},
621
+ innerBoundaryIs: [{coordinates: []}]
622
+ }
623
+ placemark.Polygon[pg].outerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "outerBoundaryIs");
624
+ placemark.Polygon[pg].innerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "innerBoundaryIs");
625
+ }
626
+ coordList = placemark.Polygon[0].outerBoundaryIs;
627
+ break;
628
+
629
+ case "LineString":
630
+ pathLength = 0;
631
+ placemark.LineString = processPlacemarkCoords(node,"LineString");
632
+ break;
633
+
634
+ default:
635
+ break;
636
+ }
637
+ }
638
+ }
639
+ }
640
+
641
+ // call the custom placemark parse function if it is defined
642
+ if (!!parserOptions.pmParseFn) parserOptions.pmParseFn(node, placemark);
643
+ doc.placemarks.push(placemark);
644
+
645
+ // single marker
646
+ if (placemark.Point) {
647
+ if (!!google.maps) {
648
+ doc.bounds = doc.bounds || new google.maps.LatLngBounds();
649
+ doc.bounds.extend(placemark.latlng);
650
+ }
651
+
652
+ // Potential user-defined marker handler
653
+ var pointCreateFunc = parserOptions.createMarker || createMarker;
654
+ var found = false;
655
+ if (!parserOptions.createMarker) {
656
+ // Check to see if this marker was created on a previous load of this document
657
+ if (!!doc) {
658
+ doc.markers = doc.markers || [];
659
+ if (doc.reload) {
660
+ for (var j = 0; j < doc.markers.length; j++) {
661
+ if ((doc.markers[j].id == placemark.id) ||
662
+ // if no id, check position
663
+ (!doc.markers[j].id &&
664
+ (doc.markers[j].getPosition().equals(placemark.latlng)))) {
665
+ found = doc.markers[j].active = true;
666
+ break;
667
+ }
668
+ }
669
+ }
670
+ }
671
+ }
672
+ if (!found) {
673
+ // Call the marker creator
674
+ var marker = pointCreateFunc(placemark, doc);
675
+ if (marker) {
676
+ marker.active = placemark.visibility;
677
+ marker.id = placemark.id;
678
+ }
679
+ }
680
+ }
681
+ // polygon/line
682
+ var poly, line;
683
+ if (!!doc) {
684
+ if (placemark.Polygon) doc.gpolygons = doc.gpolygons || [];
685
+ if (placemark.LineString) doc.gpolylines = doc.gpolylines || [];
686
+ }
687
+
688
+ var polyCreateFunc = parserOptions.createPolygon || createPolygon;
689
+ var lineCreateFunc = parserOptions.createLineString || createPolyline;
690
+ if (placemark.Polygon) {
691
+ poly = polyCreateFunc(placemark,doc);
692
+ if (poly) poly.active = placemark.visibility;
693
+ }
694
+ if (placemark.LineString) {
695
+ line = lineCreateFunc(placemark,doc);
696
+ if (line) line.active = placemark.visibility;
697
+ }
698
+ if (!!google.maps) {
699
+ doc.bounds = doc.bounds || new google.maps.LatLngBounds();
700
+ if (poly) doc.bounds.union(poly.bounds);
701
+ if (line) doc.bounds.union(line.bounds);
702
+ }
703
+
704
+ } // placemark loop
705
+
706
+ if (!!doc.reload && !!doc.markers) {
707
+ for (i = doc.markers.length - 1; i >= 0 ; i--) {
708
+ if (!doc.markers[i].active) {
709
+ if (!!doc.markers[i].infoWindow) {
710
+ doc.markers[i].infoWindow.close();
711
+ }
712
+ doc.markers[i].setMap(null);
713
+ doc.markers.splice(i, 1);
714
+ }
715
+ }
716
+ }
717
+
718
+ // Parse ground overlays
719
+ if (!!doc.reload && !!doc.groundoverlays) {
720
+ for (i = 0; i < doc.groundoverlays.length; i++) {
721
+ doc.groundoverlays[i].active = false;
722
+ }
723
+ }
724
+
725
+ if (!!doc) {
726
+ doc.groundoverlays = doc.groundoverlays || [];
727
+ }
728
+ // doc.groundoverlays =[];
729
+ var groundOverlay, color, transparency, overlay;
730
+ var groundNodes = getElementsByTagName(responseXML, 'GroundOverlay');
731
+ for (i = 0; i < groundNodes.length; i++) {
732
+ node = groundNodes[i];
733
+
734
+ // Detect images buried in KMZ files (and use a base64 encoded URL)
735
+ var gnUrl = cleanURL( doc.baseDir, nodeValue(getElementsByTagName(node, 'href')[0]) );
736
+ if (kmzMetaData[gnUrl]) gnUrl = kmzMetaData[gnUrl].dataUrl;
737
+
738
+ // Init the ground overlay object
739
+ groundOverlay = {
740
+ name: nodeValue(getElementsByTagName(node, 'name')[0]),
741
+ description: nodeValue(getElementsByTagName(node, 'description')[0]),
742
+ icon: { href: gnUrl },
743
+ latLonBox: {
744
+ north: parseFloat(nodeValue(getElementsByTagName(node, 'north')[0])),
745
+ east: parseFloat(nodeValue(getElementsByTagName(node, 'east')[0])),
746
+ south: parseFloat(nodeValue(getElementsByTagName(node, 'south')[0])),
747
+ west: parseFloat(nodeValue(getElementsByTagName(node, 'west')[0]))
748
+ }
749
+ };
750
+ if (!!google.maps) {
751
+ doc.bounds = doc.bounds || new google.maps.LatLngBounds();
752
+ doc.bounds.union(new google.maps.LatLngBounds(
753
+ new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
754
+ new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
755
+ ));
756
+ }
757
+
758
+ // Opacity is encoded in the color node
759
+ var colorNode = getElementsByTagName(node, 'color');
760
+ if (colorNode && colorNode.length > 0) {
761
+ groundOverlay.opacity = geoXML3.getOpacity(nodeValue(colorNode[0]));
762
+ } else {
763
+ groundOverlay.opacity = 1.0; // KML default
764
+ }
765
+
766
+ doc.groundoverlays.push(groundOverlay);
767
+ if (!!parserOptions.createOverlay) {
768
+ // User-defined overlay handler
769
+ parserOptions.createOverlay(groundOverlay, doc);
770
+ } else {
771
+ // Check to see if this overlay was created on a previous load of this document
772
+ var found = false;
773
+ if (!!doc) {
774
+ doc.groundoverlays = doc.groundoverlays || [];
775
+ if (doc.reload) {
776
+ overlayBounds = new google.maps.LatLngBounds(
777
+ new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
778
+ new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
779
+ );
780
+ var overlays = doc.groundoverlays;
781
+ for (i = overlays.length; i--;) {
782
+ if ((overlays[i].bounds().equals(overlayBounds)) &&
783
+ (overlays.url_ === groundOverlay.icon.href)) {
784
+ found = overlays[i].active = true;
785
+ break;
786
+ }
787
+ }
788
+ }
789
+ }
790
+
791
+ if (!found) {
792
+ // Call the built-in overlay creator
793
+ overlay = createOverlay(groundOverlay, doc);
794
+ overlay.active = true;
795
+ }
796
+ }
797
+ if (!!doc.reload && !!doc.groundoverlays && !!doc.groundoverlays.length) {
798
+ var overlays = doc.groundoverlays;
799
+ for (i = overlays.length; i--;) {
800
+ if (!overlays[i].active) {
801
+ overlays[i].remove();
802
+ overlays.splice(i, 1);
803
+ }
804
+ }
805
+ doc.groundoverlays = overlays;
806
+ }
807
+ }
808
+
809
+ // Parse network links
810
+ var networkLink;
811
+ var docPath = document.location.pathname.split('/');
812
+ docPath = docPath.splice(0, docPath.length - 1).join('/');
813
+ var linkNodes = getElementsByTagName(responseXML, 'NetworkLink');
814
+ for (i = 0; i < linkNodes.length; i++) {
815
+ node = linkNodes[i];
816
+
817
+ // Init the network link object
818
+ networkLink = {
819
+ name: nodeValue(getElementsByTagName(node, 'name')[0]),
820
+ link: {
821
+ href: nodeValue(getElementsByTagName(node, 'href')[0]),
822
+ refreshMode: nodeValue(getElementsByTagName(node, 'refreshMode')[0])
823
+ }
824
+ };
825
+
826
+ // Establish the specific refresh mode
827
+ if (!networkLink.link.refreshMode) {
828
+ networkLink.link.refreshMode = 'onChange';
829
+ }
830
+ if (networkLink.link.refreshMode === 'onInterval') {
831
+ networkLink.link.refreshInterval = parseFloat(nodeValue(getElementsByTagName(node, 'refreshInterval')[0]));
832
+ if (isNaN(networkLink.link.refreshInterval)) {
833
+ networkLink.link.refreshInterval = 0;
834
+ }
835
+ } else if (networkLink.link.refreshMode === 'onChange') {
836
+ networkLink.link.viewRefreshMode = nodeValue(getElementsByTagName(node, 'viewRefreshMode')[0]);
837
+ if (!networkLink.link.viewRefreshMode) {
838
+ networkLink.link.viewRefreshMode = 'never';
839
+ }
840
+ if (networkLink.link.viewRefreshMode === 'onStop') {
841
+ networkLink.link.viewRefreshTime = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
842
+ networkLink.link.viewFormat = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
843
+ if (!networkLink.link.viewFormat) {
844
+ networkLink.link.viewFormat = 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]';
845
+ }
846
+ }
847
+ }
848
+
849
+ if (!/^[\/|http]/.test(networkLink.link.href)) {
850
+ // Fully-qualify the HREF
851
+ networkLink.link.href = docPath + '/' + networkLink.link.href;
852
+ }
853
+
854
+ // Apply the link
855
+ if ((networkLink.link.refreshMode === 'onInterval') &&
856
+ (networkLink.link.refreshInterval > 0)) {
857
+ // Reload at regular intervals
858
+ setInterval(parserName + '.parse("' + networkLink.link.href + '")',
859
+ 1000 * networkLink.link.refreshInterval);
860
+ } else if (networkLink.link.refreshMode === 'onChange') {
861
+ if (networkLink.link.viewRefreshMode === 'never') {
862
+ // Load the link just once
863
+ doc.internals.parser.parse(networkLink.link.href, doc.internals.docSet);
864
+ } else if (networkLink.link.viewRefreshMode === 'onStop') {
865
+ // Reload when the map view changes
866
+
867
+ }
868
+ }
869
+ }
870
+ }
871
+
872
+ if (!!doc.bounds) {
873
+ doc.internals.bounds = doc.internals.bounds || new google.maps.LatLngBounds();
874
+ doc.internals.bounds.union(doc.bounds);
875
+ }
876
+ if (!!doc.markers || !!doc.groundoverlays || !!doc.gpolylines || !!doc.gpolygons) {
877
+ doc.internals.parseOnly = false;
878
+ }
879
+
880
+ if (!doc.internals.parseOnly) {
881
+ // geoXML3 is not being used only as a real-time parser, so keep the processed documents around
882
+ if (!docsByUrl[doc.baseUrl]) {
883
+ docs.push(doc);
884
+ docsByUrl[doc.baseUrl] = doc;
885
+ }
886
+ else {
887
+ // internal replacement, which keeps the same memory ref loc in docs and docsByUrl
888
+ for (var i in docsByUrl[doc.baseUrl]) {
889
+ docsByUrl[doc.baseUrl][i] = doc[i];
890
+ }
891
+ }
892
+ }
893
+
894
+ doc.internals.remaining--;
895
+ if (doc.internals.remaining === 0) {
896
+ // We're done processing this set of KML documents
897
+ // Options that get invoked after parsing completes
898
+ if (parserOptions.zoom && !!doc.internals.bounds &&
899
+ !doc.internals.bounds.isEmpty() && !!parserOptions.map) {
900
+ parserOptions.map.fitBounds(doc.internals.bounds);
901
+ }
902
+ if (parserOptions.afterParse) {
903
+ parserOptions.afterParse(doc.internals.docSet);
904
+ }
905
+ google.maps.event.trigger(doc.internals.parser, 'parsed');
906
+ }
907
+ };
908
+
909
+ var kmlColor = function (kmlIn, colorMode) {
910
+ var kmlColor = {};
911
+ kmlIn = kmlIn || 'ffffffff'; // white (KML 2.2 default)
912
+
913
+ var aa = kmlIn.substr(0,2);
914
+ var bb = kmlIn.substr(2,2);
915
+ var gg = kmlIn.substr(4,2);
916
+ var rr = kmlIn.substr(6,2);
917
+
918
+ kmlColor.opacity = parseInt(aa, 16) / 256;
919
+ kmlColor.color = (colorMode === 'random') ? randomColor(rr, gg, bb) : '#' + rr + gg + bb;
920
+ return kmlColor;
921
+ };
922
+
923
+ // Implemented per KML 2.2 <ColorStyle> specs
924
+ var randomColor = function(rr, gg, bb) {
925
+ var col = { rr: rr, gg: gg, bb: bb };
926
+ for (var k in col) {
927
+ var v = col[k];
928
+ if (v == null) v = 'ff';
929
+
930
+ // RGB values are limiters for random numbers (ie: 7f would be a random value between 0 and 7f)
931
+ v = Math.round(Math.random() * parseInt(rr, 16)).toString(16);
932
+ if (v.length === 1) v = '0' + v;
933
+ col[k] = v;
934
+ }
935
+
936
+ return '#' + col.rr + col.gg + col.bb;
937
+ };
938
+
939
+ var processStyleID = function (style) {
940
+ var icon = style.icon;
941
+ if (!icon || !icon.href) return;
942
+
943
+ if (icon.img && !icon.img.complete && (icon.dim.w < 0) && (icon.dim.h < 0) ) {
944
+ // we're still waiting on the image loading (probably because we've been blocking since the declaration)
945
+ // so, let's queue this function on the onload stack
946
+ icon.markerBacklog = [];
947
+ icon.img.onload = function() {
948
+ if (icon.dim.w < 0 || icon.dim.h < 0) {
949
+ icon.dim.w = this.width;
950
+ icon.dim.h = this.height;
951
+ } else {
952
+ icon.dim.th = this.height;
953
+ }
954
+ processStyleID(style);
955
+
956
+ // we will undoubtedly get some createMarker queuing, so set this up in advance
957
+ for (var i = 0; i < icon.markerBacklog.length; i++) {
958
+ var p = icon.markerBacklog[i][0];
959
+ var d = icon.markerBacklog[i][1];
960
+ createMarker(p, d);
961
+ if (p.marker) p.marker.active = true;
962
+ }
963
+ delete icon.markerBacklog;
964
+ };
965
+ return;
966
+ }
967
+ else { //if (icon.dim.w < 0 || icon.dim.h < 0) {
968
+ if (icon.img && icon.img.complete) {
969
+ // sometimes the file is already cached and it never calls onLoad
970
+ if (icon.dim.w < 0 || icon.dim.h < 0) {
971
+ icon.dim.w = icon.img.width;
972
+ icon.dim.h = icon.img.height;
973
+ } else {
974
+ icon.dim.th = icon.img.height;
975
+ }
976
+ }
977
+ else {
978
+ // settle for a default of 32x32
979
+ icon.dim.whGuess = true;
980
+ icon.dim.w = 32;
981
+ icon.dim.h = 32;
982
+ icon.dim.th = 32;
983
+ }
984
+ }
985
+
986
+ // pre-scaled variables
987
+ var rnd = Math.round;
988
+ var y = icon.dim.y;
989
+ if (typeof icon.dim.th !== 'undefined' && icon.dim.th != icon.dim.h) { // palette - reverse kml y for maps
990
+ y = Math.abs(y - (icon.dim.th - icon.dim.h));
991
+ }
992
+
993
+ var scaled = {
994
+ x: icon.dim.x * icon.scale,
995
+ y: y * icon.scale,
996
+ w: icon.dim.w * icon.scale,
997
+ h: icon.dim.h * icon.scale,
998
+ aX: icon.hotSpot.x * icon.scale,
999
+ aY: icon.hotSpot.y * icon.scale,
1000
+ iW: (icon.img ? icon.img.width : icon.dim.w) * icon.scale,
1001
+ iH: (icon.img ? icon.img.height : icon.dim.h) * icon.scale
1002
+ };
1003
+
1004
+ // Figure out the anchor spot
1005
+ // Origins, anchor positions and coordinates of the marker increase in the X direction to the right and in
1006
+ // the Y direction down.
1007
+ var aX, aY;
1008
+ switch (icon.hotSpot.xunits) {
1009
+ case 'fraction': aX = rnd(scaled.aX * icon.dim.w); break;
1010
+ case 'insetPixels': aX = rnd(icon.dim.w * icon.scale - scaled.aX); break;
1011
+ default: aX = rnd(scaled.aX); break; // already pixels
1012
+ }
1013
+ aY = scaled.h - rnd( ((icon.hotSpot.yunits === 'fraction') ? icon.dim.h : 1) * scaled.aY ); // insetPixels Y = pixels Y
1014
+ var iconAnchor = new google.maps.Point(aX, aY);
1015
+
1016
+ // Sizes
1017
+ // (NOTE: Scale is applied to entire image, not just the section of the icon palette.)
1018
+ var iconSize = icon.dim.whGuess ? null : new google.maps.Size(rnd(scaled.w), rnd(scaled.h));
1019
+ var iconScale = icon.scale == 1.0 ? null :
1020
+ icon.dim.whGuess ? new google.maps.Size(rnd(scaled.w), rnd(scaled.h))
1021
+ : new google.maps.Size(rnd(scaled.iW), rnd(scaled.iH));
1022
+ var iconOrigin = new google.maps.Point(rnd(scaled.x), rnd(scaled.y));
1023
+
1024
+ // Detect images buried in KMZ files (and use a base64 encoded URL)
1025
+ if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
1026
+
1027
+ // Init the style object with the KML icon
1028
+ icon.marker = {
1029
+ url: icon.url, // url
1030
+ size: iconSize, // size
1031
+ origin: iconOrigin, // origin
1032
+ anchor: iconAnchor, // anchor
1033
+ scaledSize: iconScale // scaledSize
1034
+ };
1035
+
1036
+ // Look for a predictable shadow
1037
+ var stdRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/;
1038
+ var shadowSize = new google.maps.Size(59, 32);
1039
+ var shadowPoint = new google.maps.Point(16, 32);
1040
+ if (stdRegEx.test(icon.href)) {
1041
+ // A standard GMap-style marker icon
1042
+ icon.shadow = {
1043
+ url: 'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', // url
1044
+ size: shadowSize, // size
1045
+ origin: null, // origin
1046
+ anchor: shadowPoint, // anchor
1047
+ scaledSize: shadowSize // scaledSize
1048
+ };
1049
+ } else if (icon.href.indexOf('-pushpin.png') > -1) {
1050
+ // Pushpin marker icon
1051
+ icon.shadow = {
1052
+ url: 'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png', // url
1053
+ size: shadowSize, // size
1054
+ origin: null, // origin
1055
+ anchor: shadowPoint, // anchor
1056
+ scaledSize: shadowSize // scaledSize
1057
+ };
1058
+ } /* else {
1059
+ // Other MyMaps KML standard icon
1060
+ icon.shadow = new google.maps.MarkerImage(
1061
+ icon.href.replace('.png', '.shadow.png'), // url
1062
+ shadowSize, // size
1063
+ null, // origin
1064
+ anchorPoint, // anchor
1065
+ shadowSize // scaledSize
1066
+ );
1067
+ } */
1068
+ }
1069
+
1070
+ var processStyles = function (doc) {
1071
+ for (var styleID in doc.styles) {
1072
+ processStyleID(doc.styles[styleID]);
1073
+ }
1074
+ };
1075
+
1076
+ var createMarker = function (placemark, doc) {
1077
+ // create a Marker to the map from a placemark KML object
1078
+ var icon = placemark.style.icon;
1079
+
1080
+ if ( !icon.marker && icon.img ) {
1081
+ // yay, single point of failure is holding up multiple markers...
1082
+ icon.markerBacklog = icon.markerBacklog || [];
1083
+ icon.markerBacklog.push([placemark, doc]);
1084
+ return;
1085
+ }
1086
+
1087
+ // Load basic marker properties
1088
+ var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions, {
1089
+ map: parserOptions.map,
1090
+ position: new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng),
1091
+ title: placemark.name,
1092
+ zIndex: Math.round(placemark.Point.coordinates[0].lat * -100000)<<5,
1093
+ icon: icon.marker,
1094
+ shadow: icon.shadow,
1095
+ flat: !icon.shadow,
1096
+ visible: placemark.visibility
1097
+ });
1098
+
1099
+ // Create the marker on the map
1100
+ var marker = new google.maps.Marker(markerOptions);
1101
+ if (!!doc) doc.markers.push(marker);
1102
+
1103
+ // Set up and create the infowindow if it is not suppressed
1104
+ createInfoWindow(placemark, doc, marker);
1105
+ placemark.marker = marker;
1106
+ return marker;
1107
+ };
1108
+
1109
+ var createOverlay = function (groundOverlay, doc) {
1110
+ // Add a ProjectedOverlay to the map from a groundOverlay KML object
1111
+
1112
+ if (!window.ProjectedOverlay) {
1113
+ throw 'geoXML3 error: ProjectedOverlay not found while rendering GroundOverlay from KML';
1114
+ }
1115
+
1116
+ var bounds = new google.maps.LatLngBounds(
1117
+ new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
1118
+ new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
1119
+ );
1120
+ var overlayOptions = geoXML3.combineOptions(parserOptions.overlayOptions, {percentOpacity: groundOverlay.opacity*100});
1121
+ var overlay = new ProjectedOverlay(parserOptions.map, groundOverlay.icon.href, bounds, overlayOptions);
1122
+
1123
+ if (!!doc) {
1124
+ doc.ggroundoverlays = doc.ggroundoverlays || [];
1125
+ doc.ggroundoverlays.push(overlay);
1126
+ }
1127
+
1128
+ return overlay;
1129
+ };
1130
+
1131
+ // Create Polyline
1132
+ var createPolyline = function(placemark, doc) {
1133
+ var path = [];
1134
+ var bounds = new google.maps.LatLngBounds();
1135
+ for (var j=0; j<placemark.LineString.length; j++) {
1136
+ var coords = placemark.LineString[j].coordinates;
1137
+ for (var i=0;i<coords.length;i++) {
1138
+ var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
1139
+ path.push(pt);
1140
+ bounds.extend(pt);
1141
+ }
1142
+ }
1143
+ // point to open the infowindow if triggered
1144
+ var point = path[Math.floor(path.length/2)];
1145
+ // Load basic polyline properties
1146
+ var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
1147
+ var polyOptions = geoXML3.combineOptions(parserOptions.polylineOptions, {
1148
+ map: parserOptions.map,
1149
+ path: path,
1150
+ strokeColor: kmlStrokeColor.color,
1151
+ strokeWeight: placemark.style.line.width,
1152
+ strokeOpacity: kmlStrokeColor.opacity,
1153
+ title: placemark.name,
1154
+ visible: placemark.visibility
1155
+ });
1156
+ var p = new google.maps.Polyline(polyOptions);
1157
+ p.bounds = bounds;
1158
+
1159
+ // setup and create the infoWindow if it is not suppressed
1160
+ createInfoWindow(placemark, doc, p);
1161
+ if (!!doc) doc.gpolylines.push(p);
1162
+ placemark.polyline = p;
1163
+ return p;
1164
+ }
1165
+
1166
+ // Create Polygon
1167
+ var createPolygon = function(placemark, doc) {
1168
+ var bounds = new google.maps.LatLngBounds();
1169
+ var pathsLength = 0;
1170
+ var paths = [];
1171
+ for (var polygonPart=0;polygonPart<placemark.Polygon.length;polygonPart++) {
1172
+ for (var j=0; j<placemark.Polygon[polygonPart].outerBoundaryIs.length; j++) {
1173
+ var coords = placemark.Polygon[polygonPart].outerBoundaryIs[j].coordinates;
1174
+ var path = [];
1175
+ for (var i=0;i<coords.length;i++) {
1176
+ var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
1177
+ path.push(pt);
1178
+ bounds.extend(pt);
1179
+ }
1180
+ paths.push(path);
1181
+ pathsLength += path.length;
1182
+ }
1183
+ for (var j=0; j<placemark.Polygon[polygonPart].innerBoundaryIs.length; j++) {
1184
+ var coords = placemark.Polygon[polygonPart].innerBoundaryIs[j].coordinates;
1185
+ var path = [];
1186
+ for (var i=0;i<coords.length;i++) {
1187
+ var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
1188
+ path.push(pt);
1189
+ bounds.extend(pt);
1190
+ }
1191
+ paths.push(path);
1192
+ pathsLength += path.length;
1193
+ }
1194
+ }
1195
+
1196
+ // Load basic polygon properties
1197
+ var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
1198
+ var kmlFillColor = kmlColor(placemark.style.poly.color, placemark.style.poly.colorMode);
1199
+ if (!placemark.style.poly.fill) kmlFillColor.opacity = 0.0;
1200
+ var strokeWeight = placemark.style.line.width;
1201
+ if (!placemark.style.poly.outline) {
1202
+ strokeWeight = 0;
1203
+ kmlStrokeColor.opacity = 0.0;
1204
+ }
1205
+ var polyOptions = geoXML3.combineOptions(parserOptions.polygonOptions, {
1206
+ map: parserOptions.map,
1207
+ paths: paths,
1208
+ title: placemark.name,
1209
+ strokeColor: kmlStrokeColor.color,
1210
+ strokeWeight: strokeWeight,
1211
+ strokeOpacity: kmlStrokeColor.opacity,
1212
+ fillColor: kmlFillColor.color,
1213
+ fillOpacity: kmlFillColor.opacity,
1214
+ visible: placemark.visibility
1215
+ });
1216
+ var p = new google.maps.Polygon(polyOptions);
1217
+ p.bounds = bounds;
1218
+
1219
+ createInfoWindow(placemark, doc, p);
1220
+ if (!!doc) doc.gpolygons.push(p);
1221
+ placemark.polygon = p;
1222
+ return p;
1223
+ }
1224
+
1225
+ var createInfoWindow = function(placemark, doc, gObj) {
1226
+ var bStyle = placemark.style.balloon;
1227
+ var vars = placemark.vars;
1228
+
1229
+ if (!placemark.balloonVisibility || bStyle.displayMode === 'hide') return;
1230
+
1231
+ // define geDirections
1232
+ if (placemark.latlng &&
1233
+ (!parserOptions.suppressDirections || !parserOptions.suppressDirections)) {
1234
+ vars.directions.push('sll=' + placemark.latlng.toUrlValue());
1235
+
1236
+ var url = 'http://maps.google.com/maps?' + vars.directions.join('&');
1237
+ var address = encodeURIComponent( vars.val.address || placemark.latlng.toUrlValue() ).replace(/\%20/g, '+');
1238
+
1239
+ vars.val.geDirections = '<a href="' + url + '&daddr=' + address + '" target=_blank>To Here</a> - <a href="' + url + '&saddr=' + address + '" target=_blank>From Here</a>';
1240
+ }
1241
+ else vars.val.geDirections = '';
1242
+
1243
+ // add in the variables
1244
+ var iwText = bStyle.text.replace(/\$\[(\w+(\/displayName)?)\]/g, function(txt, n, dn) { return dn ? vars.display[n] : vars.val[n]; });
1245
+ var classTxt = 'geoxml3_infowindow geoxml3_style_' + placemark.styleID;
1246
+
1247
+ // color styles
1248
+ var styleArr = [];
1249
+ if (bStyle.bgColor != 'ffffffff') styleArr.push('background: ' + kmlColor(bStyle.bgColor ).color + ';');
1250
+ if (bStyle.textColor != 'ff000000') styleArr.push('color: ' + kmlColor(bStyle.textColor).color + ';');
1251
+ var styleProp = styleArr.length ? ' style="' + styleArr.join(' ') + '"' : '';
1252
+
1253
+ var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, {
1254
+ content: '<div class="' + classTxt + '"' + styleProp + '>' + iwText + '</div>',
1255
+ pixelOffset: new google.maps.Size(0, 2)
1256
+ });
1257
+
1258
+ gObj.infoWindow = parserOptions.infoWindow || new google.maps.InfoWindow(infoWindowOptions);
1259
+ gObj.infoWindowOptions = infoWindowOptions;
1260
+
1261
+ // Info Window-opening event handler
1262
+ google.maps.event.addListener(gObj, 'click', function(e) {
1263
+ var iW = this.infoWindow;
1264
+ iW.close();
1265
+ iW.setOptions(this.infoWindowOptions);
1266
+
1267
+ if (e && e.latLng) iW.setPosition(e.latLng);
1268
+ else if (this.bounds) iW.setPosition(this.bounds.getCenter());
1269
+
1270
+ iW.setContent("<div id='geoxml3_infowindow'>"+iW.getContent()+"</div>");
1271
+ google.maps.event.addListenerOnce(iW, "domready", function() {
1272
+ var node = document.getElementById('geoxml3_infowindow');
1273
+ var imgArray = node.getElementsByTagName('img');
1274
+ for (var i = 0; i < imgArray.length; i++)
1275
+ {
1276
+ var imgUrlIE = imgArray[i].getAttribute("src");
1277
+ var imgUrl = cleanURL(doc.baseDir, imgUrlIE);
1278
+
1279
+ if (kmzMetaData[imgUrl]) {
1280
+ imgArray[i].src = kmzMetaData[imgUrl].dataUrl;
1281
+ } else if (kmzMetaData[imgUrlIE]) {
1282
+ imgArray[i].src = kmzMetaData[imgUrlIE].dataUrl;
1283
+ }
1284
+ }
1285
+ });
1286
+ iW.open(this.map, this.bounds ? null : this);
1287
+ });
1288
+
1289
+ }
1290
+
1291
+ return {
1292
+ // Expose some properties and methods
1293
+
1294
+ options: parserOptions,
1295
+ docs: docs,
1296
+ docsByUrl: docsByUrl,
1297
+ kmzMetaData: kmzMetaData,
1298
+
1299
+ parse: parse,
1300
+ render: render,
1301
+ parseKmlString: parseKmlString,
1302
+ hideDocument: hideDocument,
1303
+ showDocument: showDocument,
1304
+ processStyles: processStyles,
1305
+ createMarker: createMarker,
1306
+ createOverlay: createOverlay,
1307
+ createPolyline: createPolyline,
1308
+ createPolygon: createPolygon
1309
+ };
1310
+ };
1311
+ // End of KML Parser
1312
+
1313
+ // Helper objects and functions
1314
+ geoXML3.getOpacity = function (kmlColor) {
1315
+ // Extract opacity encoded in a KML color value. Returns a number between 0 and 1.
1316
+ if (!!kmlColor &&
1317
+ (kmlColor !== '') &&
1318
+ (kmlColor.length == 8)) {
1319
+ var transparency = parseInt(kmlColor.substr(0, 2), 16);
1320
+ return transparency / 255;
1321
+ } else {
1322
+ return 1;
1323
+ }
1324
+ };
1325
+
1326
+ // Log a message to the debugging console, if one exists
1327
+ geoXML3.log = function(msg) {
1328
+ if (!!window.console) {
1329
+ console.log(msg);
1330
+ } else { alert("log:"+msg); }
1331
+ };
1332
+
1333
+ /**
1334
+ * Creates a new parserOptions object.
1335
+ * @class GeoXML3 parser options.
1336
+ * @param {Object} overrides Any options you want to declare outside of the defaults should be included here.
1337
+ * @property {google.maps.Map} map The API map on which geo objects should be rendered.
1338
+ * @property {google.maps.MarkerOptions} markerOptions If the parser is adding Markers to the map itself, any options specified here will be applied to them.
1339
+ * @property {google.maps.InfoWindowOptions} infoWindowOptions If the parser is adding Markers to the map itself, any options specified here will be applied to their attached InfoWindows.
1340
+ * @property {ProjectedOverlay.options} overlayOptions If the parser is adding ProjectedOverlays to the map itself, any options specified here will be applied to them.
1341
+ */
1342
+ geoXML3.parserOptions = function (overrides) {
1343
+ this.map = null,
1344
+ /** If true, the parser will automatically move the map to a best-fit of the geodata after parsing of a KML document completes.
1345
+ * @type Boolean
1346
+ * @default true
1347
+ */
1348
+ this.zoom = true,
1349
+ /**#@+ @type Boolean
1350
+ * @default false */
1351
+ /** 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). */
1352
+ this.singleInfoWindow = false,
1353
+ /** If true, suppresses the rendering of info windows. */
1354
+ this.suppressInfoWindows = false,
1355
+ /**
1356
+ * Control whether to process styles now or later.
1357
+ *
1358
+ * <p>By default, the parser only processes KML &lt;Style&gt; elements into their GMaps equivalents
1359
+ * if it will be creating its own Markers (the createMarker option is null). Setting this option
1360
+ * to true will force such processing to happen anyway, useful if you're going to be calling parser.createMarker
1361
+ * yourself later. OTOH, leaving this option false removes runtime dependency on the GMaps API, enabling
1362
+ * the use of geoXML3 as a standalone KML parser.</p>
1363
+ */
1364
+ this.processStyles = false,
1365
+ /**#@-*/
1366
+
1367
+ this.markerOptions = {},
1368
+ this.infoWindowOptions = {},
1369
+ this.overlayOptions = {},
1370
+
1371
+ /**#@+ @event */
1372
+ /** This function will be called when parsing of a KML document is complete.
1373
+ * @param {geoXML3.parser#docs} doc Parsed KML data. */
1374
+ this.afterParse = null,
1375
+ /** This function will be called when parsing of a KML document is complete.
1376
+ * @param {geoXML3.parser#docs} doc Parsed KML data. */
1377
+ this.failedParse = null,
1378
+ /**
1379
+ * 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.
1380
+ * @param {geoXML3.parser.render#placemark} placemark Placemark object.
1381
+ * @param {geoXML3.parser#docs} doc Parsed KML data.
1382
+ */
1383
+ this.createMarker = null,
1384
+ /**
1385
+ * 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.
1386
+ * @param {geoXML3.parser.render#groundOverlay} groundOverlay GroundOverlay object.
1387
+ * @param {geoXML3.parser#docs} doc Parsed KML data.
1388
+ */
1389
+ this.createOverlay = null
1390
+ /**#@-*/
1391
+
1392
+ if (overrides) {
1393
+ for (var prop in overrides) {
1394
+ if (overrides.hasOwnProperty(prop)) this[prop] = overrides[prop];
1395
+ }
1396
+ }
1397
+ return this;
1398
+ };
1399
+
1400
+ /**
1401
+ * Combine two options objects: a set of default values and a set of override values.
1402
+ *
1403
+ * @deprecated This has been replaced with {@link geoXML3.parserOptions#combineOptions}.
1404
+ * @param {geoXML3.parserOptions|Object} overrides Override values.
1405
+ * @param {geoXML3.parserOptions|Object} defaults Default values.
1406
+ * @return {geoXML3.parserOptions} Combined result.
1407
+ */
1408
+ geoXML3.combineOptions = function (overrides, defaults) {
1409
+ var result = {};
1410
+ if (!!overrides) {
1411
+ for (var prop in overrides) {
1412
+ if (overrides.hasOwnProperty(prop)) result[prop] = overrides[prop];
1413
+ }
1414
+ }
1415
+ if (!!defaults) {
1416
+ for (prop in defaults) {
1417
+ if (defaults.hasOwnProperty(prop) && result[prop] === undefined) result[prop] = defaults[prop];
1418
+ }
1419
+ }
1420
+ return result;
1421
+ };
1422
+
1423
+ /**
1424
+ * Combine two options objects: a set of default values and a set of override values.
1425
+ *
1426
+ * @function
1427
+ * @param {geoXML3.parserOptions|Object} overrides Override values.
1428
+ * @param {geoXML3.parserOptions|Object} defaults Default values.
1429
+ * @return {geoXML3.parserOptions} Combined result.
1430
+ */
1431
+ geoXML3.parserOptions.prototype.combineOptions = geoXML3.combineOptions;
1432
+
1433
+ // Retrieve an XML document from url and pass it to callback as a DOM document
1434
+ geoXML3.fetchers = [];
1435
+
1436
+ /**
1437
+ * Parses a XML string.
1438
+ *
1439
+ * <p>Parses the given XML string and returns the parsed document in a
1440
+ * DOM data structure. This function will return an empty DOM node if
1441
+ * XML parsing is not supported in this browser.</p>
1442
+ *
1443
+ * @param {String} str XML string.
1444
+ * @return {Element|Document} DOM.
1445
+ */
1446
+ geoXML3.xmlParse = function (str) {
1447
+ if ((typeof ActiveXObject != 'undefined') || ("ActiveXObject" in window)) {
1448
+ var doc = new ActiveXObject('Microsoft.XMLDOM');
1449
+ doc.loadXML(str);
1450
+ return doc;
1451
+ }
1452
+
1453
+ if (typeof DOMParser != 'undefined') {
1454
+ return (new DOMParser()).parseFromString(str, 'text/xml');
1455
+ }
1456
+
1457
+ return document.createElement('div', null);
1458
+ }
1459
+
1460
+ /**
1461
+ * Checks for XML parse error.
1462
+ *
1463
+ * @param {xmlDOM} XML DOM.
1464
+ * @return boolean.
1465
+ */
1466
+ // from http://stackoverflow.com/questions/11563554/how-do-i-detect-xml-parsing-errors-when-using-javascripts-domparser-in-a-cross
1467
+ geoXML3.isParseError = function(parsedDocument) {
1468
+ if ((typeof ActiveXObject != 'undefined') || ("ActiveXObject" in window))
1469
+ return false;
1470
+ // parser and parsererrorNS could be cached on startup for efficiency
1471
+ var p = new DOMParser(),
1472
+ errorneousParse = p.parseFromString('<', 'text/xml'),
1473
+ parsererrorNS = errorneousParse.getElementsByTagName("parsererror")[0].namespaceURI;
1474
+
1475
+ if (parsererrorNS === 'http://www.w3.org/1999/xhtml') {
1476
+ // In PhantomJS the parseerror element doesn't seem to have a special namespace, so we are just guessing here :(
1477
+ return parsedDocument.getElementsByTagName("parsererror").length > 0;
1478
+ }
1479
+
1480
+ return parsedDocument.getElementsByTagNameNS(parsererrorNS, 'parsererror').length > 0;
1481
+ };
1482
+
1483
+ /**
1484
+ * Fetches a XML document.
1485
+ *
1486
+ * <p>Fetches/parses the given XML URL and passes the parsed document (in a
1487
+ * DOM data structure) to the given callback. Documents are downloaded
1488
+ * and parsed asynchronously.</p>
1489
+ *
1490
+ * @param {String} url URL of XML document. Must be uncompressed XML only.
1491
+ * @param {Function(Document)} callback Function to call when the document is processed.
1492
+ */
1493
+ geoXML3.fetchXML = function (url, callback) {
1494
+ function timeoutHandler() { callback(); };
1495
+
1496
+ var xhrFetcher = new Object();
1497
+ if (!!geoXML3.fetchers.length) xhrFetcher = geoXML3.fetchers.pop();
1498
+ else if (!!window.XMLHttpRequest) xhrFetcher.fetcher = new window.XMLHttpRequest(); // Most browsers
1499
+ else if (!!window.ActiveXObject) { // Some IE
1500
+ // the many versions of IE's XML fetchers
1501
+ var AXOs = [
1502
+ 'MSXML2.XMLHTTP.6.0',
1503
+ 'MSXML2.XMLHTTP.5.0',
1504
+ 'MSXML2.XMLHTTP.4.0',
1505
+ 'MSXML2.XMLHTTP.3.0',
1506
+ 'MSXML2.XMLHTTP',
1507
+ 'Microsoft.XMLHTTP',
1508
+ 'MSXML.XMLHTTP'
1509
+ ];
1510
+ for (var i = 0; i < AXOs.length; i++) {
1511
+ try { xhrFetcher.fetcher = new ActiveXObject(AXOs[i]); break; }
1512
+ catch(e) { continue; }
1513
+ }
1514
+ if (!xhrFetcher.fetcher) {
1515
+ geoXML3.log('Unable to create XHR object');
1516
+ callback(null);
1517
+ return null;
1518
+ }
1519
+ }
1520
+
1521
+ xhrFetcher.fetcher.open('GET', url, true);
1522
+ if (!!xhrFetcher.fetcher.overrideMimeType) xhrFetcher.fetcher.overrideMimeType('text/xml');
1523
+ xhrFetcher.fetcher.onreadystatechange = function () {
1524
+ if (xhrFetcher.fetcher.readyState === 4) {
1525
+ // Retrieval complete
1526
+ if (!!xhrFetcher.xhrtimeout) clearTimeout(xhrFetcher.xhrtimeout);
1527
+ if (xhrFetcher.fetcher.status >= 400) {
1528
+ geoXML3.log('HTTP error ' + xhrFetcher.fetcher.status + ' retrieving ' + url);
1529
+ callback();
1530
+ }
1531
+ // Returned successfully
1532
+ else {
1533
+ if (xhrFetcher.fetcher.responseXML) {
1534
+ // Sometimes IE will get the data, but won't bother loading it as an XML doc
1535
+ var xml = xhrFetcher.fetcher.responseXML;
1536
+ if (xml && !xml.documentElement && !xml.ownerElement) {
1537
+ xml.loadXML(xhrFetcher.fetcher.responseText);
1538
+ }
1539
+ } else {// handle valid xml sent with wrong MIME type
1540
+ xml=geoXML3.xmlParse(xhrFetcher.fetcher.responseText);
1541
+ }
1542
+ // handle parse errors
1543
+ if (xml.parseError && (xml.parseError.errorCode != 0)) {
1544
+ geoXML3.log("XML parse error "+xml.parseError.errorCode+", "+xml.parseError.reason+"\nLine:"+xml.parseError.line+", Position:"+xml.parseError.linepos+", srcText:"+xml.parseError.srcText);
1545
+ xml = "failed parse"
1546
+ } else if (geoXML3.isParseError(xml)) {
1547
+ geoXML3.log("XML parse error");
1548
+ xml = "failed parse"
1549
+ }
1550
+ callback(xml);
1551
+ }
1552
+ // We're done with this fetcher object
1553
+ geoXML3.fetchers.push(xhrFetcher);
1554
+ }
1555
+ };
1556
+
1557
+ xhrFetcher.xhrtimeout = setTimeout(timeoutHandler, 60000);
1558
+ xhrFetcher.fetcher.send(null);
1559
+ return null;
1560
+ };
1561
+
1562
+ var IEversion = function() {
1563
+ // http://msdn.microsoft.com/workshop/author/dhtml/overview/browserdetection.asp
1564
+ // Returns the version of Internet Explorer or a -1
1565
+ // (indicating the use of another browser).
1566
+ var rv = -1; // Return value assumes failure
1567
+ if (navigator.appName == 'Microsoft Internet Explorer') {
1568
+ var ua = navigator.userAgent;
1569
+ var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
1570
+ if (re.exec(ua) != null) {
1571
+ rv = parseFloat( RegExp.$1 );
1572
+ }
1573
+ }
1574
+ return rv;
1575
+ };
1576
+
1577
+ /**
1578
+ * Fetches a KMZ document.
1579
+ *
1580
+ * <p>Fetches/parses the given ZIP URL, parses each image file, and passes
1581
+ * the parsed KML document to the given callback. Documents are downloaded
1582
+ * and parsed asynchronously, though the KML file is always passed after the
1583
+ * images have been processed, in case the callback requires the image data.</p>
1584
+ *
1585
+ * @requires ZipFile.complete.js
1586
+ * @param {String} url URL of KMZ document. Must be a valid KMZ/ZIP archive.
1587
+ * @param {Function(Document)} callback Function to call when the document is processed.
1588
+ * @param {geoXML3.parser} parser A geoXML3.parser object. This is used to populate the KMZ image data.
1589
+ * @author Brendan Byrd
1590
+ * @see http://code.google.com/apis/kml/documentation/kmzarchives.html
1591
+ */
1592
+ geoXML3.fetchZIP = function (url, callback, parser) {
1593
+ // Just need a single 'new' declaration with a really long function...
1594
+ var zipFile = new ZipFile(url, function (zip) {
1595
+ // Retrieval complete
1596
+
1597
+ // Check for ERRORs in zip.status
1598
+ for (var i = 0; i < zip.status.length; i++) {
1599
+ var msg = zip.status[i];
1600
+ if (msg.indexOf("ERROR") == 0) {
1601
+ geoXML3.log('HTTP/ZIP error retrieving ' + url + ': ' + msg);
1602
+ callback();
1603
+ return;
1604
+ }
1605
+ else if (msg.indexOf("WARNING") == 0) { // non-fatal, but still might be useful
1606
+ geoXML3.log('HTTP/ZIP warning retrieving ' + url + ': ' + msg);
1607
+ }
1608
+ }
1609
+
1610
+ // Make sure KMZ structure is according to spec (with a single KML file in the root dir)
1611
+ var KMLCount = 0;
1612
+ var KML;
1613
+ for (var i = 0; i < zip.entries.length; i++) {
1614
+ var name = zip.entries[i].name;
1615
+ if (!/\.kml$/.test(name)) continue;
1616
+
1617
+ KMLCount++;
1618
+ if (KMLCount == 1) KML = i;
1619
+ else {
1620
+ geoXML3.log('KMZ warning retrieving ' + url + ': found extra KML "' + name + '" in KMZ; discarding...');
1621
+ }
1622
+ }
1623
+
1624
+ // Returned successfully, but still needs extracting
1625
+ var baseUrl = cleanURL(defileURL(url), url) + '/';
1626
+ var kmlProcessing = { // this is an object just so it gets passed properly
1627
+ timer: null,
1628
+ extractLeft: 0,
1629
+ timerCalls: 0
1630
+ };
1631
+ var extractCb = function(entry, entryContent) {
1632
+ var mdUrl = cleanURL(baseUrl, entry.name);
1633
+ var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
1634
+ kmlProcessing.extractLeft--;
1635
+
1636
+ if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
1637
+ geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
1638
+ callback();
1639
+ return;
1640
+ }
1641
+
1642
+ // MIME types that can be used in KML
1643
+ var mime;
1644
+ if (ext === 'jpg') ext = 'jpeg';
1645
+ if (/^(gif|jpeg|png)$/.test(ext)) mime = 'image/' + ext;
1646
+ else if (ext === 'mp3') mime = 'audio/mpeg';
1647
+ else if (ext === 'm4a') mime = 'audio/mp4';
1648
+ else if (ext === 'm4a') mime = 'audio/MP4-LATM';
1649
+ else mime = 'application/octet-stream';
1650
+
1651
+ parser.kmzMetaData[mdUrl] = {};
1652
+ parser.kmzMetaData[mdUrl].entry = entry;
1653
+ // data:image/gif;base64,R0lGODlhEAAOALMA...
1654
+ parser.kmzMetaData[mdUrl].dataUrl = 'data:' + mime + ';base64,' + base64Encode(entryContent);
1655
+ // IE cannot handle GET requests beyond 2071 characters, even if it's an inline image
1656
+ if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent))
1657
+ {
1658
+ if (((IEversion() < 8.0) &&
1659
+ (parser.kmzMetaData[mdUrl].dataUrl.length > 2071)) ||
1660
+ ((IEversion < 9.0) &&
1661
+ (parser.kmzMetaData[mdUrl].dataUrl.length > 32767))) {
1662
+ parser.kmzMetaData[mdUrl].dataUrl =
1663
+ // this is a simple IE icon; to hint at the problem...
1664
+ 'data:image/gif;base64,R0lGODlhDwAQAOMPADBPvSpQ1Dpoyz1p6FhwvU2A6ECP63CM04CWxYCk+V6x+UK++Jao3rvC3fj7+v///yH5BAEKAA8ALAAAAAAPABAAAASC8Mk5mwCAUMlWwcLRHEelLA' +
1665
+ 'oGDMgzSsiyGCAhCETDPMh5XQCBwYBrNBIKWmg0MCQHj8MJU5IoroYCY6AAAgrDIbbQDGIK6DR5UPhlNo0JAlSUNAiDgH7eNAxEDWAKCQM2AAFheVxYAA0AIkFOJ1gBcQQaUQKKA5w7LpcEBwkJaKMUEQA7';
1666
+ }
1667
+ }
1668
+ parser.kmzMetaData[internalSrc(entry.name)]=parser.kmzMetaData[mdUrl];
1669
+
1670
+ };
1671
+ var kmlExtractCb = function(entry, entryContent) {
1672
+ if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
1673
+ geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
1674
+ callback();
1675
+ return;
1676
+ }
1677
+
1678
+ // check to see if the KML is the last file extracted
1679
+ clearTimeout(kmlProcessing.timer);
1680
+ if (kmlProcessing.extractLeft <= 1) {
1681
+ kmlProcessing.extractLeft--;
1682
+ callback(geoXML3.xmlParse(entryContent));
1683
+ return;
1684
+ }
1685
+ else {
1686
+ // KML file isn't last yet; it may need to use those files, so wait a bit (100ms)
1687
+ kmlProcessing.timerCalls++;
1688
+ if (kmlProcessing.timerCalls < 100) {
1689
+ kmlProcessing.timer = setTimeout(function() { kmlExtractCb(entry, entryContent); }, 100);
1690
+ }
1691
+ else {
1692
+ geoXML3.log('KMZ warning extracting ' + url + ': entire ZIP has not been extracted after 10 seconds; running through KML, anyway...');
1693
+ kmlProcessing.extractLeft--;
1694
+ callback(geoXML3.xmlParse(entryContent));
1695
+ }
1696
+ }
1697
+ return;
1698
+ };
1699
+ for (var i = 0; i < zip.entries.length; i++) {
1700
+ var entry = zip.entries[i];
1701
+ var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
1702
+ if (!/^(gif|jpe?g|png|kml)$/.test(ext)) continue; // not going to bother to extract files we don't support
1703
+ if (ext === "kml" && i != KML) continue; // extra KMLs get discarded
1704
+ if (!parser && ext != "kml") continue; // cannot store images without a parser object
1705
+
1706
+ // extract asynchronously
1707
+ kmlProcessing.extractLeft++;
1708
+ if (ext === "kml") entry.extract(kmlExtractCb);
1709
+ else entry.extract(extractCb);
1710
+ }
1711
+ });
1712
+
1713
+ };
1714
+
1715
+ /**
1716
+ * Extract the text value of a DOM node, with leading and trailing whitespace trimmed.
1717
+ *
1718
+ * @param {Element} node XML node/element.
1719
+ * @param {Any} delVal Default value if the node doesn't exist.
1720
+ * @return {String|Null}
1721
+ */
1722
+ geoXML3.nodeValue = function(node, defVal) {
1723
+ var retStr="";
1724
+ if (!node) {
1725
+ return (typeof defVal === 'undefined' || defVal === null) ? null : defVal;
1726
+ }
1727
+ if(node.nodeType==3||node.nodeType==4||node.nodeType==2){
1728
+ retStr+=node.nodeValue;
1729
+ }else if(node.nodeType==1||node.nodeType==9||node.nodeType==11){
1730
+ for(var i=0;i<node.childNodes.length;++i){
1731
+ retStr+=arguments.callee(node.childNodes[i]);
1732
+ }
1733
+ }
1734
+ return retStr;
1735
+ };
1736
+
1737
+ /**
1738
+ * Loosely translate various values of a DOM node to a boolean.
1739
+ *
1740
+ * @param {Element} node XML node/element.
1741
+ * @param {Boolean} delVal Default value if the node doesn't exist.
1742
+ * @return {Boolean|Null}
1743
+ */
1744
+ geoXML3.getBooleanValue = function(node, defVal) {
1745
+ var nodeContents = geoXML3.nodeValue(node);
1746
+ if (nodeContents === null) return defVal || false;
1747
+ nodeContents = parseInt(nodeContents);
1748
+ if (isNaN(nodeContents)) return true;
1749
+ if (nodeContents == 0) return false;
1750
+ else return true;
1751
+ }
1752
+
1753
+ /**
1754
+ * Browser-normalized version of getElementsByTagNameNS.
1755
+ *
1756
+ * <p>Required because IE8 doesn't define it.</p>
1757
+ *
1758
+ * @param {Element|Document} node DOM object.
1759
+ * @param {String} namespace Full namespace URL to search against.
1760
+ * @param {String} tagname XML local tag name.
1761
+ * @return {Array of Elements}
1762
+ * @author Brendan Byrd
1763
+ */
1764
+ geoXML3.getElementsByTagNameNS = function(node, namespace, tagname) {
1765
+ if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagNameNS(namespace, tagname);
1766
+ if (!node) return [];
1767
+
1768
+ var root = node.documentElement || node.ownerDocument && node.ownerDocument.documentElement;
1769
+ if (!root || !root.attributes) return [];
1770
+
1771
+ // search for namespace prefix
1772
+ for (var i = 0; i < root.attributes.length; i++) {
1773
+ var attr = root.attributes[i];
1774
+ if (attr.prefix === 'xmlns' && attr.nodeValue === namespace) return node.getElementsByTagName(attr.baseName + ':' + tagname);
1775
+ else if (attr.nodeName === 'xmlns' && attr.nodeValue === namespace) {
1776
+ // default namespace
1777
+ if (typeof node.selectNodes != 'undefined') {
1778
+ // Newer IEs have the SelectionNamespace property that can be used with selectNodes
1779
+ if (!root.ownerDocument.getProperty('SelectionNamespaces'))
1780
+ root.ownerDocument.setProperty('SelectionNamespaces', "xmlns:defaultNS='" + namespace + "'");
1781
+ return node.selectNodes('.//defaultNS:' + tagname);
1782
+ }
1783
+ else {
1784
+ // Otherwise, you can still try to tack on the 'xmlns' attribute to root
1785
+ root.setAttribute('xmlns:defaultNS', namespace);
1786
+ return node.getElementsByTagName('defaultNS:' + tagname);
1787
+ }
1788
+ }
1789
+ }
1790
+ return geoXML3.getElementsByTagName(node, tagname); // try the unqualified version
1791
+ };
1792
+
1793
+ /**
1794
+ * Browser-normalized version of getElementsByTagName.
1795
+ *
1796
+ * <p>Required because MSXML 6.0 will treat this function as a NS-qualified function,
1797
+ * despite the missing NS parameter.</p>
1798
+ *
1799
+ * @param {Element|Document} node DOM object.
1800
+ * @param {String} tagname XML local tag name.
1801
+ * @return {Array of Elements}
1802
+ * @author Brendan Byrd
1803
+ */
1804
+ geoXML3.getElementsByTagName = function(node, tagname) {
1805
+ if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagName(tagname); // if it has both functions, it should be accurate
1806
+ // if (node && typeof node.selectNodes != 'undefined') return node.selectNodes(".//*[local-name()='" + tagname + "']");
1807
+ return node.getElementsByTagName(tagname); // hope for the best...
1808
+ }
1809
+
1810
+ /**
1811
+ * Turn a directory + relative URL into an absolute one.
1812
+ *
1813
+ * @private
1814
+ * @param {String} d Base directory.
1815
+ * @param {String} s Relative URL.
1816
+ * @return {String} Absolute URL.
1817
+ * @author Brendan Byrd
1818
+ */
1819
+ var toAbsURL = function (d, s) {
1820
+ var p, f, i;
1821
+ var h = location.protocol + "://" + location.host;
1822
+
1823
+ if (!s.length) return '';
1824
+ if (/^\w+:/.test(s)) return s;
1825
+ if (s.indexOf('/') == 0) return h + s;
1826
+
1827
+ p = d.replace(/\/[^\/]*$/, '');
1828
+ f = s.match(/\.\.\//g);
1829
+ if (f) {
1830
+ s = s.substring(f.length * 3);
1831
+ for (i = f.length; i--;) { p = p.substring(0, p.lastIndexOf('/')); }
1832
+ }
1833
+
1834
+ return h + p + '/' + s;
1835
+ }
1836
+
1837
+ var internalSrc = function(src) {
1838
+ //this gets the full url
1839
+ var url = document.location.href;
1840
+ //this removes everything after the last slash in the path
1841
+ url = url.substring(0,url.lastIndexOf("/") + 1);
1842
+ var internalPath= url+src;
1843
+ return internalPath;
1844
+ }
1845
+
1846
+ /**
1847
+ * Remove current host from URL
1848
+ *
1849
+ * @private
1850
+ * @param {String} s Absolute or relative URL.
1851
+ * @return {String} Root-based relative URL.
1852
+ * @author Brendan Byrd
1853
+ */
1854
+ var dehostURL = function (s) {
1855
+ var h = location.protocol + "://" + location.host;
1856
+ h = h.replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1'); // quotemeta
1857
+ return s.replace(new RegExp('^' + h, 'i'), '');
1858
+ }
1859
+
1860
+ /**
1861
+ * Removes all query strings, #IDs, '../' references, and
1862
+ * hosts from a URL.
1863
+ *
1864
+ * @private
1865
+ * @param {String} d Base directory.
1866
+ * @param {String} s Absolute or relative URL.
1867
+ * @return {String} Root-based relative URL.
1868
+ * @author Brendan Byrd
1869
+ */
1870
+ var cleanURL = function (d, s) { return dehostURL(toAbsURL(d ? d.split('#')[0].split('?')[0] : defileURL(location.pathname), s ? s.split('#')[0].split('?')[0] : '')); }
1871
+ /**
1872
+ * Remove filename from URL
1873
+ *
1874
+ * @private
1875
+ * @param {String} s Relative URL.
1876
+ * @return {String} Base directory.
1877
+ * @author Brendan Byrd
1878
+ */
1879
+ var defileURL = function (s) { return s ? s.substr(0, s.lastIndexOf('/') + 1) : '/'; }
1880
+
1881
+
1882
+ // Some extra Array subs for ease of use
1883
+ // http://stackoverflow.com/questions/143847/best-way-to-find-an-item-in-a-javascript-array
1884
+ Array.prototype.hasObject = (
1885
+ !Array.indexOf ? function (obj) {
1886
+ var l = this.length + 1;
1887
+ while (l--) {
1888
+ if (this[l - 1] === obj) return true;
1889
+ }
1890
+ return false;
1891
+ } : function (obj) {
1892
+ return (this.indexOf(obj) !== -1);
1893
+ }
1894
+ );
1895
+ Array.prototype.hasItemInObj = function (name, item) {
1896
+ var l = this.length + 1;
1897
+ while (l--) {
1898
+ if (this[l - 1][name] === item) return true;
1899
+ }
1900
+ return false;
1901
+ };
1902
+ if (!Array.prototype.indexOf) {
1903
+ Array.prototype.indexOf = function (obj, fromIndex) {
1904
+ if (fromIndex == null) {
1905
+ fromIndex = 0;
1906
+ } else if (fromIndex < 0) {
1907
+ fromIndex = Math.max(0, this.length + fromIndex);
1908
+ }
1909
+ for (var i = fromIndex, j = this.length; i < j; i++) {
1910
+ if (this[i] === obj) return i;
1911
+ }
1912
+ return -1;
1913
+ };
1914
+ }
1915
+ Array.prototype.indexOfObjWithItem = function (name, item, fromIndex) {
1916
+ if (fromIndex == null) {
1917
+ fromIndex = 0;
1918
+ } else if (fromIndex < 0) {
1919
+ fromIndex = Math.max(0, this.length + fromIndex);
1920
+ }
1921
+ for (var i = fromIndex, j = this.length; i < j; i++) {
1922
+ if (this[i][name] === item) return i;
1923
+ }
1924
+ return -1;
1925
+ };
1926
+
1927
+ /**
1928
+ * Borrowed from jquery.base64.js, with some "Array as input" corrections
1929
+ *
1930
+ * @private
1931
+ * @param {Array of charCodes} input An array of byte ASCII codes (0-255).
1932
+ * @return {String} A base64-encoded string.
1933
+ * @author Brendan Byrd
1934
+ */
1935
+ var base64Encode = function(input) {
1936
+ var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
1937
+ var output = "";
1938
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
1939
+ var i = 0;
1940
+ while (i < input.length) {
1941
+ chr1 = input[i++];
1942
+ chr2 = input[i++];
1943
+ chr3 = input[i++];
1944
+ enc1 = chr1 >> 2;
1945
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
1946
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
1947
+ enc4 = chr3 & 63;
1948
+
1949
+ if (chr2 == undefined) enc3 = enc4 = 64;
1950
+ else if (chr3 == undefined) enc4 = 64;
1951
+
1952
+ output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4);
1953
+ }
1954
+ return output;
1955
+ };