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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +42 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/geoxml-rails.gemspec +26 -0
- data/geoxml-test/.gitignore +17 -0
- data/geoxml-test/Gemfile +46 -0
- data/geoxml-test/Gemfile.lock +169 -0
- data/geoxml-test/README.rdoc +28 -0
- data/geoxml-test/Rakefile +6 -0
- data/geoxml-test/app/assets/images/.keep +0 -0
- data/geoxml-test/app/assets/javascripts/application.js +17 -0
- data/geoxml-test/app/assets/javascripts/application.jscurl +1 -0
- data/geoxml-test/app/assets/stylesheets/application.css +15 -0
- data/geoxml-test/app/controllers/application_controller.rb +5 -0
- data/geoxml-test/app/controllers/concerns/.keep +0 -0
- data/geoxml-test/app/helpers/application_helper.rb +2 -0
- data/geoxml-test/app/mailers/.keep +0 -0
- data/geoxml-test/app/models/.keep +0 -0
- data/geoxml-test/app/models/concerns/.keep +0 -0
- data/geoxml-test/app/views/layouts/application.html.erb +14 -0
- data/geoxml-test/bin/bundle +3 -0
- data/geoxml-test/bin/rails +8 -0
- data/geoxml-test/bin/rake +8 -0
- data/geoxml-test/bin/setup +29 -0
- data/geoxml-test/bin/spring +15 -0
- data/geoxml-test/config/application.rb +26 -0
- data/geoxml-test/config/boot.rb +3 -0
- data/geoxml-test/config/database.yml +25 -0
- data/geoxml-test/config/environment.rb +5 -0
- data/geoxml-test/config/environments/development.rb +41 -0
- data/geoxml-test/config/environments/production.rb +79 -0
- data/geoxml-test/config/environments/test.rb +42 -0
- data/geoxml-test/config/initializers/assets.rb +11 -0
- data/geoxml-test/config/initializers/backtrace_silencers.rb +7 -0
- data/geoxml-test/config/initializers/cookies_serializer.rb +3 -0
- data/geoxml-test/config/initializers/filter_parameter_logging.rb +4 -0
- data/geoxml-test/config/initializers/inflections.rb +16 -0
- data/geoxml-test/config/initializers/mime_types.rb +4 -0
- data/geoxml-test/config/initializers/session_store.rb +3 -0
- data/geoxml-test/config/initializers/wrap_parameters.rb +14 -0
- data/geoxml-test/config/locales/en.yml +23 -0
- data/geoxml-test/config/routes.rb +56 -0
- data/geoxml-test/config/secrets.yml +22 -0
- data/geoxml-test/config.ru +4 -0
- data/geoxml-test/db/seeds.rb +7 -0
- data/geoxml-test/lib/assets/.keep +0 -0
- data/geoxml-test/lib/tasks/.keep +0 -0
- data/geoxml-test/log/.keep +0 -0
- data/geoxml-test/public/404.html +67 -0
- data/geoxml-test/public/422.html +67 -0
- data/geoxml-test/public/500.html +66 -0
- data/geoxml-test/public/favicon.ico +0 -0
- data/geoxml-test/public/robots.txt +5 -0
- data/geoxml-test/test/controllers/.keep +0 -0
- data/geoxml-test/test/fixtures/.keep +0 -0
- data/geoxml-test/test/helpers/.keep +0 -0
- data/geoxml-test/test/integration/.keep +0 -0
- data/geoxml-test/test/mailers/.keep +0 -0
- data/geoxml-test/test/models/.keep +0 -0
- data/geoxml-test/test/test_helper.rb +10 -0
- data/geoxml-test/vendor/assets/javascripts/.keep +0 -0
- data/geoxml-test/vendor/assets/stylesheets/.keep +0 -0
- data/lib/geoxml/rails/version.rb +5 -0
- data/lib/geoxml/rails.rb +8 -0
- data/vendor/assets/javascripts/geoxml3.js +1955 -0
- 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 <Style> 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
|
+
};
|