mapstraction-rails 2.0.18.1

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 (65) hide show
  1. data/Gemfile +4 -0
  2. data/Gemfile.lock +14 -0
  3. data/LICENSE +22 -0
  4. data/README.md +32 -0
  5. data/Rakefile +2 -0
  6. data/lib/mapstraction/engine.rb +4 -0
  7. data/lib/mapstraction/version.rb +3 -0
  8. data/lib/mapstraction.rb +5 -0
  9. data/mapstraction/.gitignore +17 -0
  10. data/mapstraction.gemspec +17 -0
  11. data/vendor/.DS_Store +0 -0
  12. data/vendor/assets/.DS_Store +0 -0
  13. data/vendor/assets/javascripts/mxn-min.js +15 -0
  14. data/vendor/assets/javascripts/mxn.cartociudad.geocoder-min.js +15 -0
  15. data/vendor/assets/javascripts/mxn.cartociudad.geocoder.js +46 -0
  16. data/vendor/assets/javascripts/mxn.cloudmade.core-min.js +15 -0
  17. data/vendor/assets/javascripts/mxn.cloudmade.core.js +383 -0
  18. data/vendor/assets/javascripts/mxn.core-min.js +15 -0
  19. data/vendor/assets/javascripts/mxn.core.js +1892 -0
  20. data/vendor/assets/javascripts/mxn.geocoder-min.js +15 -0
  21. data/vendor/assets/javascripts/mxn.geocoder.js +74 -0
  22. data/vendor/assets/javascripts/mxn.geocommons.core-min.js +15 -0
  23. data/vendor/assets/javascripts/mxn.geocommons.core.js +343 -0
  24. data/vendor/assets/javascripts/mxn.google.core-min.js +15 -0
  25. data/vendor/assets/javascripts/mxn.google.core.js +536 -0
  26. data/vendor/assets/javascripts/mxn.google.geocoder-min.js +15 -0
  27. data/vendor/assets/javascripts/mxn.google.geocoder.js +87 -0
  28. data/vendor/assets/javascripts/mxn.googleearth.core-min.js +15 -0
  29. data/vendor/assets/javascripts/mxn.googleearth.core.js +350 -0
  30. data/vendor/assets/javascripts/mxn.googlev3.core-min.js +15 -0
  31. data/vendor/assets/javascripts/mxn.googlev3.core.js +630 -0
  32. data/vendor/assets/javascripts/mxn.googlev3.geocoder-min.js +15 -0
  33. data/vendor/assets/javascripts/mxn.googlev3.geocoder.js +101 -0
  34. data/vendor/assets/javascripts/mxn.js +581 -0
  35. data/vendor/assets/javascripts/mxn.leaflet.core-min.js +15 -0
  36. data/vendor/assets/javascripts/mxn.leaflet.core.js +398 -0
  37. data/vendor/assets/javascripts/mxn.mapquest.geocoder-min.js +15 -0
  38. data/vendor/assets/javascripts/mxn.mapquest.geocoder.js +63 -0
  39. data/vendor/assets/javascripts/mxn.microsoft.core-min.js +15 -0
  40. data/vendor/assets/javascripts/mxn.microsoft.core.js +429 -0
  41. data/vendor/assets/javascripts/mxn.microsoft7.core-min.js +15 -0
  42. data/vendor/assets/javascripts/mxn.microsoft7.core.js +458 -0
  43. data/vendor/assets/javascripts/mxn.microsoft7.geocoder-min.js +15 -0
  44. data/vendor/assets/javascripts/mxn.microsoft7.geocoder.js +66 -0
  45. data/vendor/assets/javascripts/mxn.multimap.core-min.js +15 -0
  46. data/vendor/assets/javascripts/mxn.multimap.core.js +473 -0
  47. data/vendor/assets/javascripts/mxn.openlayers.core-min.js +15 -0
  48. data/vendor/assets/javascripts/mxn.openlayers.core.js +630 -0
  49. data/vendor/assets/javascripts/mxn.openlayers.geocoder-min.js +15 -0
  50. data/vendor/assets/javascripts/mxn.openlayers.geocoder.js +106 -0
  51. data/vendor/assets/javascripts/mxn.openmq.core-min.js +15 -0
  52. data/vendor/assets/javascripts/mxn.openmq.core.js +364 -0
  53. data/vendor/assets/javascripts/mxn.openspace.core-min.js +15 -0
  54. data/vendor/assets/javascripts/mxn.openspace.core.js +460 -0
  55. data/vendor/assets/javascripts/mxn.ovi.core-min.js +15 -0
  56. data/vendor/assets/javascripts/mxn.ovi.core.js +508 -0
  57. data/vendor/assets/javascripts/mxn.ovi.geocoder-min.js +15 -0
  58. data/vendor/assets/javascripts/mxn.ovi.geocoder.js +129 -0
  59. data/vendor/assets/javascripts/mxn.yahoo.core-min.js +15 -0
  60. data/vendor/assets/javascripts/mxn.yahoo.core.js +406 -0
  61. data/vendor/assets/javascripts/mxn.yandex.core-min.js +15 -0
  62. data/vendor/assets/javascripts/mxn.yandex.core.js +527 -0
  63. data/vendor/assets/javascripts/mxn.yandex.geocoder-min.js +15 -0
  64. data/vendor/assets/javascripts/mxn.yandex.geocoder.js +66 -0
  65. metadata +110 -0
@@ -0,0 +1,1892 @@
1
+ /*
2
+ MAPSTRACTION v2.0.18 http://www.mapstraction.com
3
+
4
+ Copyright (c) 2012 Tom Carden, Steve Coast, Mikel Maron, Andrew Turner, Henri Bergius, Rob Moran, Derek Fowler, Gary Gale
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
11
+ * Neither the name of the Mapstraction nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
+ */
15
+ (function(){
16
+
17
+ /**
18
+ * @exports mxn.util.$m as $m
19
+ */
20
+ var $m = mxn.util.$m;
21
+
22
+ /**
23
+ * Initialise our provider. This function should only be called
24
+ * from within mapstraction code, not exposed as part of the API.
25
+ * @private
26
+ */
27
+ var init = function() {
28
+ this.invoker.go('init', [ this.currentElement, this.api ]);
29
+ this.applyOptions();
30
+ };
31
+
32
+ /**
33
+ * Mapstraction instantiates a map with some API choice into the HTML element given
34
+ * @name mxn.Mapstraction
35
+ * @constructor
36
+ * @param {String} element The HTML element to replace with a map
37
+ * @param {String} api The API to use, one of 'google', 'googlev3', 'yahoo', 'microsoft', 'openstreetmap', 'multimap', 'map24', 'openlayers', 'mapquest'. If omitted, first loaded provider implementation is used.
38
+ * @param {Bool} debug optional parameter to turn on debug support - this uses alert panels for unsupported actions
39
+ * @exports Mapstraction as mxn.Mapstraction
40
+ */
41
+ var Mapstraction = mxn.Mapstraction = function(element, api, debug) {
42
+ if (!api){
43
+ api = mxn.util.getAvailableProviders()[0];
44
+ }
45
+
46
+ /**
47
+ * The name of the active API.
48
+ * @name mxn.Mapstraction#api
49
+ * @type {String}
50
+ */
51
+ this.api = api;
52
+
53
+ this.maps = {};
54
+
55
+ /**
56
+ * The DOM element containing the map.
57
+ * @name mxn.Mapstraction#currentElement
58
+ * @property
59
+ * @type {DOMElement}
60
+ */
61
+ this.currentElement = $m(element);
62
+
63
+ this.eventListeners = [];
64
+
65
+ /**
66
+ * The array of all layers that have been added to the map.
67
+ */
68
+ this.tileLayers = [];
69
+
70
+ /**
71
+ * The markers currently loaded.
72
+ * @name mxn.Mapstraction#markers
73
+ * @property
74
+ * @type {Array}
75
+ */
76
+ this.markers = [];
77
+
78
+ /**
79
+ * The polylines currently loaded.
80
+ * @name mxn.Mapstraction#polylines
81
+ * @property
82
+ * @type {Array}
83
+ */
84
+ this.polylines = [];
85
+
86
+ this.images = [];
87
+ this.controls = [];
88
+ this.loaded = {};
89
+ this.onload = {};
90
+ //this.loaded[api] = true; // FIXME does this need to be true? -ajturner
91
+ this.onload[api] = [];
92
+
93
+ /**
94
+ * The original element value passed to the constructor.
95
+ * @name mxn.Mapstraction#element
96
+ * @property
97
+ * @type {String|DOMElement}
98
+ */
99
+ this.element = element;
100
+
101
+ /**
102
+ * Options defaults.
103
+ * @name mxn.Mapstraction#options
104
+ * @property {Object}
105
+ */
106
+ this.options = {
107
+ enableScrollWheelZoom: false,
108
+ enableDragging: true
109
+ };
110
+
111
+ this.addControlsArgs = {};
112
+
113
+ // set up our invoker for calling API methods
114
+ this.invoker = new mxn.Invoker(this, 'Mapstraction', function(){ return this.api; });
115
+
116
+ // Adding our events
117
+ mxn.addEvents(this, [
118
+
119
+ /**
120
+ * Map has loaded
121
+ * @name mxn.Mapstraction#load
122
+ * @event
123
+ */
124
+ 'load',
125
+
126
+ /**
127
+ * Map is clicked {location: LatLonPoint}
128
+ * @name mxn.Mapstraction#click
129
+ * @event
130
+ */
131
+ 'click',
132
+
133
+ /**
134
+ * Map is panned
135
+ * @name mxn.Mapstraction#endPan
136
+ * @event
137
+ */
138
+ 'endPan',
139
+
140
+ /**
141
+ * Zoom is changed
142
+ * @name mxn.Mapstraction#changeZoom
143
+ * @event
144
+ */
145
+ 'changeZoom',
146
+
147
+ /**
148
+ * Marker is added {marker: Marker}
149
+ * @name mxn.Mapstraction#markerAdded
150
+ * @event
151
+ */
152
+ 'markerAdded',
153
+
154
+ /**
155
+ * Marker is removed {marker: Marker}
156
+ * @name mxn.Mapstraction#markerRemoved
157
+ * @event
158
+ */
159
+ 'markerRemoved',
160
+
161
+ /**
162
+ * Polyline is added {polyline: Polyline}
163
+ * @name mxn.Mapstraction#polylineAdded
164
+ * @event
165
+ */
166
+ 'polylineAdded',
167
+
168
+ /**
169
+ * Polyline is removed {polyline: Polyline}
170
+ * @name mxn.Mapstraction#polylineRemoved
171
+ * @event
172
+ */
173
+ 'polylineRemoved'
174
+ ]);
175
+
176
+ // finally initialize our proper API map
177
+ init.apply(this);
178
+ };
179
+
180
+ // Map type constants
181
+ Mapstraction.ROAD = 1;
182
+ Mapstraction.SATELLITE = 2;
183
+ Mapstraction.HYBRID = 3;
184
+ Mapstraction.PHYSICAL = 4;
185
+
186
+ // methods that have no implementation in mapstraction core
187
+ mxn.addProxyMethods(Mapstraction, [
188
+ /**
189
+ * Adds a large map panning control and zoom buttons to the map
190
+ * @name mxn.Mapstraction#addLargeControls
191
+ * @function
192
+ */
193
+ 'addLargeControls',
194
+
195
+ /**
196
+ * Adds a map type control to the map (streets, aerial imagery etc)
197
+ * @name mxn.Mapstraction#addMapTypeControls
198
+ * @function
199
+ */
200
+ 'addMapTypeControls',
201
+
202
+ /**
203
+ * Adds a GeoRSS or KML overlay to the map
204
+ * some flavors of GeoRSS and KML are not supported by some of the Map providers
205
+ * @name mxn.Mapstraction#addOverlay
206
+ * @function
207
+ * @param {String} url GeoRSS or KML feed URL
208
+ * @param {Boolean} autoCenterAndZoom Set true to auto center and zoom after the feed is loaded
209
+ */
210
+ 'addOverlay',
211
+
212
+ /**
213
+ * Adds a small map panning control and zoom buttons to the map
214
+ * @name mxn.Mapstraction#addSmallControls
215
+ * @function
216
+ */
217
+ 'addSmallControls',
218
+
219
+ /**
220
+ * Applies the current option settings
221
+ * @name mxn.Mapstraction#applyOptions
222
+ * @function
223
+ */
224
+ 'applyOptions',
225
+
226
+ /**
227
+ * Gets the BoundingBox of the map
228
+ * @name mxn.Mapstraction#getBounds
229
+ * @function
230
+ * @returns {BoundingBox} The bounding box for the current map state
231
+ */
232
+ 'getBounds',
233
+
234
+ /**
235
+ * Gets the central point of the map
236
+ * @name mxn.Mapstraction#getCenter
237
+ * @function
238
+ * @returns {LatLonPoint} The center point of the map
239
+ */
240
+ 'getCenter',
241
+
242
+ /**
243
+ * Gets the imagery type for the map.
244
+ * The type can be one of:
245
+ * mxn.Mapstraction.ROAD
246
+ * mxn.Mapstraction.SATELLITE
247
+ * mxn.Mapstraction.HYBRID
248
+ * mxn.Mapstraction.PHYSICAL
249
+ * @name mxn.Mapstraction#getMapType
250
+ * @function
251
+ * @returns {Number}
252
+ */
253
+ 'getMapType',
254
+
255
+ /**
256
+ * Returns a ratio to turn distance into pixels based on current projection
257
+ * @name mxn.Mapstraction#getPixelRatio
258
+ * @function
259
+ * @returns {Float} ratio
260
+ */
261
+ 'getPixelRatio',
262
+
263
+ /**
264
+ * Returns the zoom level of the map
265
+ * @name mxn.Mapstraction#getZoom
266
+ * @function
267
+ * @returns {Integer} The zoom level of the map
268
+ */
269
+ 'getZoom',
270
+
271
+ /**
272
+ * Returns the best zoom level for bounds given
273
+ * @name mxn.Mapstraction#getZoomLevelForBoundingBox
274
+ * @function
275
+ * @param {BoundingBox} bbox The bounds to fit
276
+ * @returns {Integer} The closest zoom level that contains the bounding box
277
+ */
278
+ 'getZoomLevelForBoundingBox',
279
+
280
+ /**
281
+ * Displays the coordinates of the cursor in the HTML element
282
+ * @name mxn.Mapstraction#mousePosition
283
+ * @function
284
+ * @param {String} element ID of the HTML element to display the coordinates in
285
+ */
286
+ 'mousePosition',
287
+
288
+ /**
289
+ * Resize the current map to the specified width and height
290
+ * (since it is actually on a child div of the mapElement passed
291
+ * as argument to the Mapstraction constructor, the resizing of this
292
+ * mapElement may have no effect on the size of the actual map)
293
+ * @name mxn.Mapstraction#resizeTo
294
+ * @function
295
+ * @param {Integer} width The width the map should be.
296
+ * @param {Integer} height The width the map should be.
297
+ */
298
+ 'resizeTo',
299
+
300
+ /**
301
+ * Sets the map to the appropriate location and zoom for a given BoundingBox
302
+ * @name mxn.Mapstraction#setBounds
303
+ * @function
304
+ * @param {BoundingBox} bounds The bounding box you want the map to show
305
+ */
306
+ 'setBounds',
307
+
308
+ /**
309
+ * setCenter sets the central point of the map
310
+ * @name mxn.Mapstraction#setCenter
311
+ * @function
312
+ * @param {LatLonPoint} point The point at which to center the map
313
+ * @param {Object} options Optional parameters
314
+ * @param {Boolean} options.pan Whether the map should move to the locations using a pan or just jump straight there
315
+ */
316
+ 'setCenter',
317
+
318
+ /**
319
+ * Centers the map to some place and zoom level
320
+ * @name mxn.Mapstraction#setCenterAndZoom
321
+ * @function
322
+ * @param {LatLonPoint} point Where the center of the map should be
323
+ * @param {Integer} zoom The zoom level where 0 is all the way out.
324
+ */
325
+ 'setCenterAndZoom',
326
+
327
+ /**
328
+ * Sets the imagery type for the map
329
+ * The type can be one of:
330
+ * mxn.Mapstraction.ROAD
331
+ * mxn.Mapstraction.SATELLITE
332
+ * mxn.Mapstraction.HYBRID
333
+ * mxn.Mapstraction.PHYSICAL
334
+ * @name mxn.Mapstraction#setMapType
335
+ * @function
336
+ * @param {Number} type
337
+ */
338
+ 'setMapType',
339
+
340
+ /**
341
+ * Sets the zoom level for the map
342
+ * MS doesn't seem to do zoom=0, and Gg's sat goes closer than it's maps, and MS's sat goes closer than Y!'s
343
+ * TODO: Mapstraction.prototype.getZoomLevels or something.
344
+ * @name mxn.Mapstraction#setZoom
345
+ * @function
346
+ * @param {Number} zoom The (native to the map) level zoom the map to.
347
+ */
348
+ 'setZoom',
349
+
350
+ /**
351
+ * Turns a Tile Layer on or off
352
+ * @name mxn.Mapstraction#toggleTileLayer
353
+ * @function
354
+ * @param {tile_url} url of the tile layer that was created.
355
+ */
356
+ 'toggleTileLayer'
357
+ ]);
358
+
359
+ /**
360
+ * Sets the current options to those specified in oOpts and applies them
361
+ * @param {Object} oOpts Hash of options to set
362
+ */
363
+ Mapstraction.prototype.setOptions = function(oOpts){
364
+ mxn.util.merge(this.options, oOpts);
365
+ this.applyOptions();
366
+ };
367
+
368
+ /**
369
+ * Sets an option and applies it.
370
+ * @param {String} sOptName Option name
371
+ * @param vVal Option value
372
+ */
373
+ Mapstraction.prototype.setOption = function(sOptName, vVal){
374
+ this.options[sOptName] = vVal;
375
+ this.applyOptions();
376
+ };
377
+
378
+ /**
379
+ * Enable scroll wheel zooming
380
+ * @deprecated Use setOption instead.
381
+ */
382
+ Mapstraction.prototype.enableScrollWheelZoom = function() {
383
+ this.setOption('enableScrollWheelZoom', true);
384
+ };
385
+
386
+ /**
387
+ * Enable/disable dragging of the map
388
+ * @param {Boolean} on
389
+ * @deprecated Use setOption instead.
390
+ */
391
+ Mapstraction.prototype.dragging = function(on) {
392
+ this.setOption('enableDragging', on);
393
+ };
394
+
395
+ /**
396
+ * Change the current api on the fly
397
+ * @param {String} api The API to swap to
398
+ * @param element
399
+ */
400
+ Mapstraction.prototype.swap = function(element,api) {
401
+ if (this.api === api) {
402
+ return;
403
+ }
404
+
405
+ var center = this.getCenter();
406
+ var zoom = this.getZoom();
407
+
408
+ this.currentElement.style.visibility = 'hidden';
409
+ this.currentElement.style.display = 'none';
410
+
411
+ this.currentElement = $m(element);
412
+ this.currentElement.style.visibility = 'visible';
413
+ this.currentElement.style.display = 'block';
414
+
415
+ this.api = api;
416
+ this.onload[api] = [];
417
+
418
+ if (this.maps[this.api] === undefined) {
419
+ init.apply(this);
420
+
421
+ for (var i = 0; i < this.markers.length; i++) {
422
+ this.addMarker(this.markers[i], true);
423
+ }
424
+
425
+ for (var j = 0; j < this.polylines.length; j++) {
426
+ this.addPolyline( this.polylines[j], true);
427
+ }
428
+
429
+ this.setCenterAndZoom(center,zoom);
430
+ }
431
+ else {
432
+
433
+ //sync the view
434
+ this.setCenterAndZoom(center,zoom);
435
+
436
+ //TODO synchronize the markers and polylines too
437
+ // (any overlays created after api instantiation are not sync'd)
438
+ }
439
+
440
+ this.addControls(this.addControlsArgs);
441
+
442
+ };
443
+
444
+ /**
445
+ * Returns the loaded state of a Map Provider
446
+ * @param {String} api Optional API to query for. If not specified, returns state of the originally created API
447
+ */
448
+ Mapstraction.prototype.isLoaded = function(api){
449
+ if (api === null) {
450
+ api = this.api;
451
+ }
452
+ return this.loaded[api];
453
+ };
454
+
455
+ /**
456
+ * Set the debugging on or off - shows alert panels for functions that don't exist in Mapstraction
457
+ * @param {Boolean} debug true to turn on debugging, false to turn it off
458
+ */
459
+ Mapstraction.prototype.setDebug = function(debug){
460
+ if(debug !== null) {
461
+ this.debug = debug;
462
+ }
463
+ return this.debug;
464
+ };
465
+
466
+ /**
467
+ * Set the api call deferment on or off - When it's on, mxn.invoke will queue up provider API calls until
468
+ * runDeferred is called, at which time everything in the queue will be run in the order it was added.
469
+ * @param {Boolean} set deferred to true to turn on deferment
470
+ */
471
+ Mapstraction.prototype.setDefer = function(deferred){
472
+ this.loaded[this.api] = !deferred;
473
+ };
474
+
475
+ /**
476
+ * Run any queued provider API calls for the methods defined in the provider's implementation.
477
+ * For example, if defferable in mxn.[provider].core.js is set to {getCenter: true, setCenter: true}
478
+ * then any calls to map.setCenter or map.getCenter will be queued up in this.onload. When the provider's
479
+ * implementation loads the map, it calls this.runDeferred and any queued calls will be run.
480
+ */
481
+ Mapstraction.prototype.runDeferred = function(){
482
+ while(this.onload[this.api].length > 0) {
483
+ this.onload[this.api].shift().apply(this); //run deferred calls
484
+ }
485
+ };
486
+
487
+ /////////////////////////
488
+ //
489
+ // Event Handling
490
+ //
491
+ // FIXME need to consolidate some of these handlers...
492
+ //
493
+ ///////////////////////////
494
+
495
+ // Click handler attached to native API
496
+ Mapstraction.prototype.clickHandler = function(lat, lon, me) {
497
+ this.callEventListeners('click', {
498
+ location: new LatLonPoint(lat, lon)
499
+ });
500
+ };
501
+
502
+ // Move and zoom handler attached to native API
503
+ Mapstraction.prototype.moveendHandler = function(me) {
504
+ this.callEventListeners('moveend', {});
505
+ };
506
+
507
+ /**
508
+ * Add a listener for an event.
509
+ * @param {String} type Event type to attach listener to
510
+ * @param {Function} func Callback function
511
+ * @param {Object} caller Callback object
512
+ */
513
+ Mapstraction.prototype.addEventListener = function() {
514
+ var listener = {};
515
+ listener.event_type = arguments[0];
516
+ listener.callback_function = arguments[1];
517
+
518
+ // added the calling object so we can retain scope of callback function
519
+ if(arguments.length == 3) {
520
+ listener.back_compat_mode = false;
521
+ listener.callback_object = arguments[2];
522
+ }
523
+ else {
524
+ listener.back_compat_mode = true;
525
+ listener.callback_object = null;
526
+ }
527
+ this.eventListeners.push(listener);
528
+ };
529
+
530
+ /**
531
+ * Call listeners for a particular event.
532
+ * @param {String} sEventType Call listeners of this event type
533
+ * @param {Object} oEventArgs Event args object to pass back to the callback
534
+ */
535
+ Mapstraction.prototype.callEventListeners = function(sEventType, oEventArgs) {
536
+ oEventArgs.source = this;
537
+ for(var i = 0; i < this.eventListeners.length; i++) {
538
+ var evLi = this.eventListeners[i];
539
+ if(evLi.event_type == sEventType) {
540
+ // only two cases for this, click and move
541
+ if(evLi.back_compat_mode) {
542
+ if(evLi.event_type == 'click') {
543
+ evLi.callback_function(oEventArgs.location);
544
+ }
545
+ else {
546
+ evLi.callback_function();
547
+ }
548
+ }
549
+ else {
550
+ var scope = evLi.callback_object || this;
551
+ evLi.callback_function.call(scope, oEventArgs);
552
+ }
553
+ }
554
+ }
555
+ };
556
+
557
+
558
+ ////////////////////
559
+ //
560
+ // map manipulation
561
+ //
562
+ /////////////////////
563
+
564
+
565
+ /**
566
+ * addControls adds controls to the map. You specify which controls to add in
567
+ * the associative array that is the only argument.
568
+ * addControls can be called multiple time, with different args, to dynamically change controls.
569
+ *
570
+ * args = {
571
+ * pan: true,
572
+ * zoom: 'large' || 'small',
573
+ * overview: true,
574
+ * scale: true,
575
+ * map_type: true,
576
+ * }
577
+ * @param {array} args Which controls to switch on
578
+ */
579
+ Mapstraction.prototype.addControls = function( args ) {
580
+ this.addControlsArgs = args;
581
+ this.invoker.go('addControls', arguments);
582
+ };
583
+
584
+ /**
585
+ * Adds a marker pin to the map
586
+ * @param {Marker} marker The marker to add
587
+ * @param {Boolean} old If true, doesn't add this marker to the markers array. Used by the "swap" method
588
+ */
589
+ Mapstraction.prototype.addMarker = function(marker, old) {
590
+ marker.mapstraction = this;
591
+ marker.api = this.api;
592
+ marker.location.api = this.api;
593
+ marker.map = this.maps[this.api];
594
+ var propMarker = this.invoker.go('addMarker', arguments);
595
+ marker.setChild(propMarker);
596
+ if (!old) {
597
+ this.markers.push(marker);
598
+ }
599
+ this.markerAdded.fire({'marker': marker});
600
+ };
601
+
602
+ /**
603
+ * addMarkerWithData will addData to the marker, then add it to the map
604
+ * @param {Marker} marker The marker to add
605
+ * @param {Object} data A data has to add
606
+ */
607
+ Mapstraction.prototype.addMarkerWithData = function(marker, data) {
608
+ marker.addData(data);
609
+ this.addMarker(marker);
610
+ };
611
+
612
+ /**
613
+ * addPolylineWithData will addData to the polyline, then add it to the map
614
+ * @param {Polyline} polyline The polyline to add
615
+ * @param {Object} data A data has to add
616
+ */
617
+ Mapstraction.prototype.addPolylineWithData = function(polyline, data) {
618
+ polyline.addData(data);
619
+ this.addPolyline(polyline);
620
+ };
621
+
622
+ /**
623
+ * removeMarker removes a Marker from the map
624
+ * @param {Marker} marker The marker to remove
625
+ */
626
+ Mapstraction.prototype.removeMarker = function(marker) {
627
+ var current_marker;
628
+ for(var i = 0; i < this.markers.length; i++){
629
+ current_marker = this.markers[i];
630
+ if(marker == current_marker) {
631
+ this.invoker.go('removeMarker', arguments);
632
+ marker.onmap = false;
633
+ this.markers.splice(i, 1);
634
+ this.markerRemoved.fire({'marker': marker});
635
+ break;
636
+ }
637
+ }
638
+ };
639
+
640
+ /**
641
+ * removeAllMarkers removes all the Markers on a map
642
+ */
643
+ Mapstraction.prototype.removeAllMarkers = function() {
644
+ var current_marker;
645
+ while(this.markers.length > 0) {
646
+ current_marker = this.markers.pop();
647
+ this.invoker.go('removeMarker', [current_marker]);
648
+ }
649
+ };
650
+
651
+ /**
652
+ * Declutter the markers on the map, group together overlapping markers.
653
+ * @param {Object} opts Declutter options
654
+ */
655
+ Mapstraction.prototype.declutterMarkers = function(opts) {
656
+ if(this.loaded[this.api] === false) {
657
+ var me = this;
658
+ this.onload[this.api].push( function() {
659
+ me.declutterMarkers(opts);
660
+ } );
661
+ return;
662
+ }
663
+
664
+ var map = this.maps[this.api];
665
+
666
+ switch(this.api)
667
+ {
668
+ // case 'yahoo':
669
+ //
670
+ // break;
671
+ // case 'google':
672
+ //
673
+ // break;
674
+ // case 'openstreetmap':
675
+ //
676
+ // break;
677
+ // case 'microsoft':
678
+ //
679
+ // break;
680
+ // case 'openlayers':
681
+ //
682
+ // break;
683
+ case 'multimap':
684
+ /*
685
+ * Multimap supports quite a lot of decluttering options such as whether
686
+ * to use an accurate of fast declutter algorithm and what icon to use to
687
+ * represent a cluster. Using all this would mean abstracting all the enums
688
+ * etc so we're only implementing the group name function at the moment.
689
+ */
690
+ map.declutterGroup(opts.groupName);
691
+ break;
692
+ // case 'mapquest':
693
+ //
694
+ // break;
695
+ // case 'map24':
696
+ //
697
+ // break;
698
+ case ' dummy':
699
+ break;
700
+ default:
701
+ if(this.debug) {
702
+ alert(this.api + ' not supported by Mapstraction.declutterMarkers');
703
+ }
704
+ }
705
+ };
706
+
707
+ /**
708
+ * Add a polyline to the map
709
+ * @param {Polyline} polyline The Polyline to add to the map
710
+ * @param {Boolean} old If true replaces an existing Polyline
711
+ */
712
+ Mapstraction.prototype.addPolyline = function(polyline, old) {
713
+ polyline.api = this.api;
714
+ polyline.map = this.maps[this.api];
715
+ var propPoly = this.invoker.go('addPolyline', arguments);
716
+ polyline.setChild(propPoly);
717
+ if(!old) {
718
+ this.polylines.push(polyline);
719
+ }
720
+ this.polylineAdded.fire({'polyline': polyline});
721
+ };
722
+
723
+ // Private remove implementation
724
+ var removePolylineImpl = function(polyline) {
725
+ this.invoker.go('removePolyline', arguments);
726
+ polyline.onmap = false;
727
+ this.polylineRemoved.fire({'polyline': polyline});
728
+ };
729
+
730
+ /**
731
+ * Remove the polyline from the map
732
+ * @param {Polyline} polyline The Polyline to remove from the map
733
+ */
734
+ Mapstraction.prototype.removePolyline = function(polyline) {
735
+ var current_polyline;
736
+ for(var i = 0; i < this.polylines.length; i++){
737
+ current_polyline = this.polylines[i];
738
+ if(polyline == current_polyline) {
739
+ this.polylines.splice(i, 1);
740
+ removePolylineImpl.call(this, polyline);
741
+ break;
742
+ }
743
+ }
744
+ };
745
+
746
+ /**
747
+ * Removes all polylines from the map
748
+ */
749
+ Mapstraction.prototype.removeAllPolylines = function() {
750
+ var current_polyline;
751
+ while(this.polylines.length > 0) {
752
+ current_polyline = this.polylines.pop();
753
+ removePolylineImpl.call(this, current_polyline);
754
+ }
755
+ };
756
+
757
+ var collectPoints = function(bMarkers, bPolylines, predicate) {
758
+ var points = [];
759
+
760
+ if (bMarkers) {
761
+ for (var i = 0; i < this.markers.length; i++) {
762
+ var mark = this.markers[i];
763
+ if (!predicate || predicate(mark)) {
764
+ points.push(mark.location);
765
+ }
766
+ }
767
+ }
768
+
769
+ if (bPolylines) {
770
+ for(i = 0; i < this.polylines.length; i++) {
771
+ var poly = this.polylines[i];
772
+ if (!predicate || predicate(poly)) {
773
+ for (var j = 0; j < poly.points.length; j++) {
774
+ points.push(poly.points[j]);
775
+ }
776
+ }
777
+ }
778
+ }
779
+
780
+ return points;
781
+ };
782
+
783
+ /**
784
+ * Sets the center and zoom of the map to the smallest bounding box
785
+ * containing all markers and polylines
786
+ */
787
+ Mapstraction.prototype.autoCenterAndZoom = function() {
788
+ var points = collectPoints.call(this, true, true);
789
+
790
+ this.centerAndZoomOnPoints(points);
791
+ };
792
+
793
+ /**
794
+ * centerAndZoomOnPoints sets the center and zoom of the map from an array of points
795
+ *
796
+ * This is useful if you don't want to have to add markers to the map
797
+ */
798
+ Mapstraction.prototype.centerAndZoomOnPoints = function(points) {
799
+ var bounds = new BoundingBox(90, 180, -90, -180);
800
+
801
+ for (var i = 0, len = points.length; i < len; i++) {
802
+ bounds.extend(points[i]);
803
+ }
804
+
805
+ this.setBounds(bounds);
806
+ };
807
+
808
+ /**
809
+ * Sets the center and zoom of the map to the smallest bounding box
810
+ * containing all visible markers and polylines
811
+ * will only include markers and polylines with an attribute of "visible"
812
+ */
813
+ Mapstraction.prototype.visibleCenterAndZoom = function() {
814
+ var predicate = function(obj) {
815
+ return obj.getAttribute("visible");
816
+ };
817
+ var points = collectPoints.call(this, true, true, predicate);
818
+
819
+ this.centerAndZoomOnPoints(points);
820
+ };
821
+
822
+ /**
823
+ * Automatically sets center and zoom level to show all polylines
824
+ * @param {Number} padding Optional number of kilometers to pad around polyline
825
+ */
826
+ Mapstraction.prototype.polylineCenterAndZoom = function(padding) {
827
+ padding = padding || 0;
828
+
829
+ var points = collectPoints.call(this, false, true);
830
+
831
+ if (padding > 0) {
832
+ var padPoints = [];
833
+ for (var i = 0; i < points.length; i++) {
834
+ var point = points[i];
835
+
836
+ var kmInOneDegreeLat = point.latConv();
837
+ var kmInOneDegreeLon = point.lonConv();
838
+
839
+ var latPad = padding / kmInOneDegreeLat;
840
+ var lonPad = padding / kmInOneDegreeLon;
841
+
842
+ var ne = new LatLonPoint(point.lat + latPad, point.lon + lonPad);
843
+ var sw = new LatLonPoint(point.lat - latPad, point.lon - lonPad);
844
+
845
+ padPoints.push(ne, sw);
846
+ }
847
+ points = points.concat(padPoints);
848
+ }
849
+
850
+ this.centerAndZoomOnPoints(points);
851
+ };
852
+
853
+ /**
854
+ * addImageOverlay layers an georeferenced image over the map
855
+ * @param {id} unique DOM identifier
856
+ * @param {src} url of image
857
+ * @param {opacity} opacity 0-100
858
+ * @param {west} west boundary
859
+ * @param {south} south boundary
860
+ * @param {east} east boundary
861
+ * @param {north} north boundary
862
+ */
863
+ Mapstraction.prototype.addImageOverlay = function(id, src, opacity, west, south, east, north) {
864
+
865
+ var b = document.createElement("img");
866
+ b.style.display = 'block';
867
+ b.setAttribute('id',id);
868
+ b.setAttribute('src',src);
869
+ b.style.position = 'absolute';
870
+ b.style.zIndex = 1;
871
+ b.setAttribute('west',west);
872
+ b.setAttribute('south',south);
873
+ b.setAttribute('east',east);
874
+ b.setAttribute('north',north);
875
+
876
+ var oContext = {
877
+ imgElm: b
878
+ };
879
+
880
+ this.invoker.go('addImageOverlay', arguments, { context: oContext });
881
+ };
882
+
883
+ Mapstraction.prototype.setImageOpacity = function(id, opacity) {
884
+ if (opacity < 0) {
885
+ opacity = 0;
886
+ }
887
+ if (opacity >= 100) {
888
+ opacity = 100;
889
+ }
890
+ var c = opacity / 100;
891
+ var d = document.getElementById(id);
892
+ if(typeof(d.style.filter)=='string'){
893
+ d.style.filter='alpha(opacity:'+opacity+')';
894
+ }
895
+ if(typeof(d.style.KHTMLOpacity)=='string'){
896
+ d.style.KHTMLOpacity=c;
897
+ }
898
+ if(typeof(d.style.MozOpacity)=='string'){
899
+ d.style.MozOpacity=c;
900
+ }
901
+ if(typeof(d.style.opacity)=='string'){
902
+ d.style.opacity=c;
903
+ }
904
+ };
905
+
906
+ Mapstraction.prototype.setImagePosition = function(id) {
907
+ var imgElement = document.getElementById(id);
908
+ var oContext = {
909
+ latLng: {
910
+ top: imgElement.getAttribute('north'),
911
+ left: imgElement.getAttribute('west'),
912
+ bottom: imgElement.getAttribute('south'),
913
+ right: imgElement.getAttribute('east')
914
+ },
915
+ pixels: { top: 0, right: 0, bottom: 0, left: 0 }
916
+ };
917
+
918
+ this.invoker.go('setImagePosition', arguments, { context: oContext });
919
+
920
+ imgElement.style.top = oContext.pixels.top.toString() + 'px';
921
+ imgElement.style.left = oContext.pixels.left.toString() + 'px';
922
+ imgElement.style.width = (oContext.pixels.right - oContext.pixels.left).toString() + 'px';
923
+ imgElement.style.height = (oContext.pixels.bottom - oContext.pixels.top).toString() + 'px';
924
+ };
925
+
926
+ Mapstraction.prototype.addJSON = function(json) {
927
+ var features;
928
+ if (typeof(json) == "string") {
929
+ features = eval('(' + json + ')');
930
+ } else {
931
+ features = json;
932
+ }
933
+ features = features.features;
934
+ var map = this.maps[this.api];
935
+ var html = "";
936
+ var item;
937
+ var polyline;
938
+ var marker;
939
+ var markers = [];
940
+
941
+ if(features.type == "FeatureCollection") {
942
+ this.addJSON(features.features);
943
+ }
944
+
945
+ for (var i = 0; i < features.length; i++) {
946
+ item = features[i];
947
+ switch(item.geometry.type) {
948
+ case "Point":
949
+ html = "<strong>" + item.title + "</strong><p>" + item.description + "</p>";
950
+ marker = new Marker(new LatLonPoint(item.geometry.coordinates[1],item.geometry.coordinates[0]));
951
+ markers.push(marker);
952
+ this.addMarkerWithData(marker,{
953
+ infoBubble : html,
954
+ label : item.title,
955
+ date : "new Date(\""+item.date+"\")",
956
+ iconShadow : item.icon_shadow,
957
+ marker : item.id,
958
+ iconShadowSize : item.icon_shadow_size,
959
+ icon : item.icon,
960
+ iconSize : item.icon_size,
961
+ category : item.source_id,
962
+ draggable : false,
963
+ hover : false
964
+ });
965
+ break;
966
+ case "Polygon":
967
+ var points = [];
968
+ polyline = new Polyline(points);
969
+ mapstraction.addPolylineWithData(polyline,{
970
+ fillColor : item.poly_color,
971
+ date : "new Date(\""+item.date+"\")",
972
+ category : item.source_id,
973
+ width : item.line_width,
974
+ opacity : item.line_opacity,
975
+ color : item.line_color,
976
+ polygon : true
977
+ });
978
+ markers.push(polyline);
979
+ break;
980
+ default:
981
+ // console.log("Geometry: " + features.items[i].geometry.type);
982
+ }
983
+ }
984
+ return markers;
985
+ };
986
+
987
+ /**
988
+ * Adds a Tile Layer to the map
989
+ *
990
+ * Requires providing a parameterized tile url. Use {Z}, {X}, and {Y} to specify where the parameters
991
+ * should go in the URL.
992
+ *
993
+ * For example, the OpenStreetMap tiles are:
994
+ * m.addTileLayer("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png", 1.0, "OSM", 1, 19, true);
995
+ *
996
+ * @param {tile_url} template url of the tiles.
997
+ * @param {opacity} opacity of the tile layer - 0 is transparent, 1 is opaque. (default=0.6)
998
+ * @param {copyright_text} copyright text to use for the tile layer. (default=Mapstraction)
999
+ * @param {min_zoom} Minimum (furtherest out) zoom level that tiles are available (default=1)
1000
+ * @param {max_zoom} Maximum (closest) zoom level that the tiles are available (default=18)
1001
+ * @param {map_type} Should the tile layer be a selectable map type in the layers palette (default=false)
1002
+ */
1003
+ Mapstraction.prototype.addTileLayer = function(tile_url, opacity, copyright_text, min_zoom, max_zoom, map_type) {
1004
+ if(!tile_url) {
1005
+ return;
1006
+ }
1007
+
1008
+ opacity = opacity || 0.6;
1009
+ copyright_text = copyright_text || "Mapstraction";
1010
+ min_zoom = min_zoom || 1;
1011
+ max_zoom = max_zoom || 18;
1012
+ map_type = map_type || false;
1013
+
1014
+ return this.invoker.go('addTileLayer', [ tile_url, opacity, copyright_text, min_zoom, max_zoom, map_type] );
1015
+ };
1016
+
1017
+ /**
1018
+ * addFilter adds a marker filter
1019
+ * @param {field} name of attribute to filter on
1020
+ * @param {operator} presently only "ge" or "le"
1021
+ * @param {value} the value to compare against
1022
+ */
1023
+ Mapstraction.prototype.addFilter = function(field, operator, value) {
1024
+ if (!this.filters) {
1025
+ this.filters = [];
1026
+ }
1027
+ this.filters.push( [field, operator, value] );
1028
+ };
1029
+
1030
+ /**
1031
+ * Remove the specified filter
1032
+ * @param {Object} field
1033
+ * @param {Object} operator
1034
+ * @param {Object} value
1035
+ */
1036
+ Mapstraction.prototype.removeFilter = function(field, operator, value) {
1037
+ if (!this.filters) {
1038
+ return;
1039
+ }
1040
+
1041
+ var del;
1042
+ for (var f=0; f<this.filters.length; f++) {
1043
+ if (this.filters[f][0] == field &&
1044
+ (! operator || (this.filters[f][1] == operator && this.filters[f][2] == value))) {
1045
+ this.filters.splice(f,1);
1046
+ f--; //array size decreased
1047
+ }
1048
+ }
1049
+ };
1050
+
1051
+ /**
1052
+ * Delete the current filter if present; otherwise add it
1053
+ * @param {Object} field
1054
+ * @param {Object} operator
1055
+ * @param {Object} value
1056
+ */
1057
+ Mapstraction.prototype.toggleFilter = function(field, operator, value) {
1058
+ if (!this.filters) {
1059
+ this.filters = [];
1060
+ }
1061
+
1062
+ var found = false;
1063
+ for (var f = 0; f < this.filters.length; f++) {
1064
+ if (this.filters[f][0] == field && this.filters[f][1] == operator && this.filters[f][2] == value) {
1065
+ this.filters.splice(f,1);
1066
+ f--; //array size decreased
1067
+ found = true;
1068
+ }
1069
+ }
1070
+
1071
+ if (! found) {
1072
+ this.addFilter(field, operator, value);
1073
+ }
1074
+ };
1075
+
1076
+ /**
1077
+ * removeAllFilters
1078
+ */
1079
+ Mapstraction.prototype.removeAllFilters = function() {
1080
+ this.filters = [];
1081
+ };
1082
+
1083
+ /**
1084
+ * doFilter executes all filters added since last call
1085
+ * Now supports a callback function for when a marker is shown or hidden
1086
+ * @param {Function} showCallback
1087
+ * @param {Function} hideCallback
1088
+ * @returns {Int} count of visible markers
1089
+ */
1090
+ Mapstraction.prototype.doFilter = function(showCallback, hideCallback) {
1091
+ var map = this.maps[this.api];
1092
+ var visibleCount = 0;
1093
+ var f;
1094
+ if (this.filters) {
1095
+ switch (this.api) {
1096
+ case 'multimap':
1097
+ /* TODO polylines aren't filtered in multimap */
1098
+ var mmfilters = [];
1099
+ for (f=0; f<this.filters.length; f++) {
1100
+ mmfilters.push( new MMSearchFilter( this.filters[f][0], this.filters[f][1], this.filters[f][2] ));
1101
+ }
1102
+ map.setMarkerFilters( mmfilters );
1103
+ map.redrawMap();
1104
+ break;
1105
+ case ' dummy':
1106
+ break;
1107
+ default:
1108
+ var vis;
1109
+ for (var m=0; m<this.markers.length; m++) {
1110
+ vis = true;
1111
+ for (f = 0; f < this.filters.length; f++) {
1112
+ if (! this.applyFilter(this.markers[m], this.filters[f])) {
1113
+ vis = false;
1114
+ }
1115
+ }
1116
+ if (vis) {
1117
+ visibleCount ++;
1118
+ if (showCallback){
1119
+ showCallback(this.markers[m]);
1120
+ }
1121
+ else {
1122
+ this.markers[m].show();
1123
+ }
1124
+ }
1125
+ else {
1126
+ if (hideCallback){
1127
+ hideCallback(this.markers[m]);
1128
+ }
1129
+ else {
1130
+ this.markers[m].hide();
1131
+ }
1132
+ }
1133
+
1134
+ this.markers[m].setAttribute("visible", vis);
1135
+ }
1136
+ break;
1137
+ }
1138
+ }
1139
+ return visibleCount;
1140
+ };
1141
+
1142
+ Mapstraction.prototype.applyFilter = function(o, f) {
1143
+ var vis = true;
1144
+ switch (f[1]) {
1145
+ case 'ge':
1146
+ if (o.getAttribute( f[0] ) < f[2]) {
1147
+ vis = false;
1148
+ }
1149
+ break;
1150
+ case 'le':
1151
+ if (o.getAttribute( f[0] ) > f[2]) {
1152
+ vis = false;
1153
+ }
1154
+ break;
1155
+ case 'eq':
1156
+ if (o.getAttribute( f[0] ) == f[2]) {
1157
+ vis = false;
1158
+ }
1159
+ break;
1160
+ }
1161
+
1162
+ return vis;
1163
+ };
1164
+
1165
+ /**
1166
+ * getAttributeExtremes returns the minimum/maximum of "field" from all markers
1167
+ * @param {field} name of "field" to query
1168
+ * @returns {array} of minimum/maximum
1169
+ */
1170
+ Mapstraction.prototype.getAttributeExtremes = function(field) {
1171
+ var min;
1172
+ var max;
1173
+ for (var m=0; m<this.markers.length; m++) {
1174
+ if (! min || min > this.markers[m].getAttribute(field)) {
1175
+ min = this.markers[m].getAttribute(field);
1176
+ }
1177
+ if (! max || max < this.markers[m].getAttribute(field)) {
1178
+ max = this.markers[m].getAttribute(field);
1179
+ }
1180
+ }
1181
+ for (var p=0; m<this.polylines.length; m++) {
1182
+ if (! min || min > this.polylines[p].getAttribute(field)) {
1183
+ min = this.polylines[p].getAttribute(field);
1184
+ }
1185
+ if (! max || max < this.polylines[p].getAttribute(field)) {
1186
+ max = this.polylines[p].getAttribute(field);
1187
+ }
1188
+ }
1189
+
1190
+ return [min, max];
1191
+ };
1192
+
1193
+ /**
1194
+ * getMap returns the native map object that mapstraction is talking to
1195
+ * @returns the native map object mapstraction is using
1196
+ */
1197
+ Mapstraction.prototype.getMap = function() {
1198
+ // FIXME in an ideal world this shouldn't exist right?
1199
+ return this.maps[this.api];
1200
+ };
1201
+
1202
+
1203
+ //////////////////////////////
1204
+ //
1205
+ // LatLonPoint
1206
+ //
1207
+ /////////////////////////////
1208
+
1209
+ /**
1210
+ * LatLonPoint is a point containing a latitude and longitude with helper methods
1211
+ * @name mxn.LatLonPoint
1212
+ * @constructor
1213
+ * @param {double} lat is the latitude
1214
+ * @param {double} lon is the longitude
1215
+ * @exports LatLonPoint as mxn.LatLonPoint
1216
+ */
1217
+ var LatLonPoint = mxn.LatLonPoint = function(lat, lon) {
1218
+ this.lat = Number(lat); // force to be numeric
1219
+ this.lon = Number(lon);
1220
+ this.lng = this.lon; // lets be lon/lng agnostic
1221
+
1222
+ this.invoker = new mxn.Invoker(this, 'LatLonPoint');
1223
+ };
1224
+
1225
+ mxn.addProxyMethods(LatLonPoint, [
1226
+ /**
1227
+ * Retrieve the lat and lon values from a proprietary point.
1228
+ * @name mxn.LatLonPoint#fromProprietary
1229
+ * @function
1230
+ * @param {String} apiId The API ID of the proprietary point.
1231
+ * @param {Object} point The proprietary point.
1232
+ */
1233
+ 'fromProprietary',
1234
+
1235
+ /**
1236
+ * Converts the current LatLonPoint to a proprietary one for the API specified by apiId.
1237
+ * @name mxn.LatLonPoint#toProprietary
1238
+ * @function
1239
+ * @param {String} apiId The API ID of the proprietary point.
1240
+ * @returns A proprietary point.
1241
+ */
1242
+ 'toProprietary'
1243
+ ], true);
1244
+
1245
+ /**
1246
+ * toString returns a string represntation of a point
1247
+ * @returns a string like '51.23, -0.123'
1248
+ * @type String
1249
+ */
1250
+ LatLonPoint.prototype.toString = function() {
1251
+ return this.lat + ', ' + this.lon;
1252
+ };
1253
+
1254
+ /**
1255
+ * distance returns the distance in kilometers between two points
1256
+ * @param {LatLonPoint} otherPoint The other point to measure the distance from to this one
1257
+ * @returns the distance between the points in kilometers
1258
+ * @type double
1259
+ */
1260
+ LatLonPoint.prototype.distance = function(otherPoint) {
1261
+ // Uses Haversine formula from http://www.movable-type.co.uk
1262
+ var rads = Math.PI / 180;
1263
+ var diffLat = (this.lat-otherPoint.lat) * rads;
1264
+ var diffLon = (this.lon-otherPoint.lon) * rads;
1265
+ var a = Math.sin(diffLat / 2) * Math.sin(diffLat / 2) +
1266
+ Math.cos(this.lat*rads) * Math.cos(otherPoint.lat*rads) *
1267
+ Math.sin(diffLon/2) * Math.sin(diffLon/2);
1268
+ return 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)) * 6371; // Earth's mean radius in km
1269
+ };
1270
+
1271
+ /**
1272
+ * equals tests if this point is the same as some other one
1273
+ * @param {LatLonPoint} otherPoint The other point to test with
1274
+ * @returns true or false
1275
+ * @type boolean
1276
+ */
1277
+ LatLonPoint.prototype.equals = function(otherPoint) {
1278
+ return this.lat == otherPoint.lat && this.lon == otherPoint.lon;
1279
+ };
1280
+
1281
+ /**
1282
+ * Returns latitude conversion based on current projection
1283
+ * @returns {Float} conversion
1284
+ */
1285
+ LatLonPoint.prototype.latConv = function() {
1286
+ return this.distance(new LatLonPoint(this.lat + 0.1, this.lon))*10;
1287
+ };
1288
+
1289
+ /**
1290
+ * Returns longitude conversion based on current projection
1291
+ * @returns {Float} conversion
1292
+ */
1293
+ LatLonPoint.prototype.lonConv = function() {
1294
+ return this.distance(new LatLonPoint(this.lat, this.lon + 0.1))*10;
1295
+ };
1296
+
1297
+
1298
+ //////////////////////////
1299
+ //
1300
+ // BoundingBox
1301
+ //
1302
+ //////////////////////////
1303
+
1304
+ /**
1305
+ * BoundingBox creates a new bounding box object
1306
+ * @name mxn.BoundingBox
1307
+ * @constructor
1308
+ * @param {double} swlat the latitude of the south-west point
1309
+ * @param {double} swlon the longitude of the south-west point
1310
+ * @param {double} nelat the latitude of the north-east point
1311
+ * @param {double} nelon the longitude of the north-east point
1312
+ * @exports BoundingBox as mxn.BoundingBox
1313
+ */
1314
+ var BoundingBox = mxn.BoundingBox = function(swlat, swlon, nelat, nelon) {
1315
+ //FIXME throw error if box bigger than world
1316
+ this.sw = new LatLonPoint(swlat, swlon);
1317
+ this.ne = new LatLonPoint(nelat, nelon);
1318
+ };
1319
+
1320
+ /**
1321
+ * getSouthWest returns a LatLonPoint of the south-west point of the bounding box
1322
+ * @returns the south-west point of the bounding box
1323
+ * @type LatLonPoint
1324
+ */
1325
+ BoundingBox.prototype.getSouthWest = function() {
1326
+ return this.sw;
1327
+ };
1328
+
1329
+ /**
1330
+ * getNorthEast returns a LatLonPoint of the north-east point of the bounding box
1331
+ * @returns the north-east point of the bounding box
1332
+ * @type LatLonPoint
1333
+ */
1334
+ BoundingBox.prototype.getNorthEast = function() {
1335
+ return this.ne;
1336
+ };
1337
+
1338
+ /**
1339
+ * isEmpty finds if this bounding box has zero area
1340
+ * @returns whether the north-east and south-west points of the bounding box are the same point
1341
+ * @type boolean
1342
+ */
1343
+ BoundingBox.prototype.isEmpty = function() {
1344
+ return this.ne == this.sw; // is this right? FIXME
1345
+ };
1346
+
1347
+ /**
1348
+ * contains finds whether a given point is within a bounding box
1349
+ * @param {LatLonPoint} point the point to test with
1350
+ * @returns whether point is within this bounding box
1351
+ * @type boolean
1352
+ */
1353
+ BoundingBox.prototype.contains = function(point){
1354
+ return point.lat >= this.sw.lat && point.lat <= this.ne.lat && point.lon >= this.sw.lon && point.lon <= this.ne.lon;
1355
+ };
1356
+
1357
+ /**
1358
+ * toSpan returns a LatLonPoint with the lat and lon as the height and width of the bounding box
1359
+ * @returns a LatLonPoint containing the height and width of this bounding box
1360
+ * @type LatLonPoint
1361
+ */
1362
+ BoundingBox.prototype.toSpan = function() {
1363
+ return new LatLonPoint( Math.abs(this.sw.lat - this.ne.lat), Math.abs(this.sw.lon - this.ne.lon) );
1364
+ };
1365
+
1366
+
1367
+
1368
+ /**
1369
+ * extend extends the bounding box to include the new point
1370
+ */
1371
+ BoundingBox.prototype.extend = function(point) {
1372
+ if (this.sw.lat > point.lat) {
1373
+ this.sw.lat = point.lat;
1374
+ }
1375
+ if (this.sw.lon > point.lon) {
1376
+ this.sw.lon = point.lon;
1377
+ }
1378
+ if (this.ne.lat < point.lat) {
1379
+ this.ne.lat = point.lat;
1380
+ }
1381
+ if (this.ne.lon < point.lon) {
1382
+ this.ne.lon = point.lon;
1383
+ }
1384
+ return;
1385
+ };
1386
+
1387
+ //////////////////////////////
1388
+ //
1389
+ // Marker
1390
+ //
1391
+ ///////////////////////////////
1392
+
1393
+ /**
1394
+ * Marker create's a new marker pin
1395
+ * @name mxn.Marker
1396
+ * @constructor
1397
+ * @param {LatLonPoint} point the point on the map where the marker should go
1398
+ * @exports Marker as mxn.Marker
1399
+ */
1400
+ var Marker = mxn.Marker = function(point) {
1401
+ this.api = null;
1402
+ this.location = point;
1403
+ this.onmap = false;
1404
+ this.proprietary_marker = false;
1405
+ this.attributes = [];
1406
+ this.invoker = new mxn.Invoker(this, 'Marker', function(){return this.api;});
1407
+ mxn.addEvents(this, [
1408
+ 'openInfoBubble', // Info bubble opened
1409
+ 'closeInfoBubble', // Info bubble closed
1410
+ 'click' // Marker clicked
1411
+ ]);
1412
+ };
1413
+
1414
+ mxn.addProxyMethods(Marker, [
1415
+ /**
1416
+ * Retrieve the settings from a proprietary marker.
1417
+ * @name mxn.Marker#fromProprietary
1418
+ * @function
1419
+ * @param {String} apiId The API ID of the proprietary point.
1420
+ * @param {Object} marker The proprietary marker.
1421
+ */
1422
+ 'fromProprietary',
1423
+
1424
+ /**
1425
+ * Hide the marker.
1426
+ * @name mxn.Marker#hide
1427
+ * @function
1428
+ */
1429
+ 'hide',
1430
+
1431
+ /**
1432
+ * Open the marker's info bubble.
1433
+ * @name mxn.Marker#openBubble
1434
+ * @function
1435
+ */
1436
+ 'openBubble',
1437
+
1438
+ /**
1439
+ * Closes the marker's info bubble.
1440
+ * @name mxn.Marker#closeBubble
1441
+ * @function
1442
+ */
1443
+ 'closeBubble',
1444
+
1445
+ /**
1446
+ * Show the marker.
1447
+ * @name mxn.Marker#show
1448
+ * @function
1449
+ */
1450
+ 'show',
1451
+
1452
+ /**
1453
+ * Converts the current Marker to a proprietary one for the API specified by apiId.
1454
+ * @name mxn.Marker#toProprietary
1455
+ * @function
1456
+ * @param {String} apiId The API ID of the proprietary marker.
1457
+ * @returns A proprietary marker.
1458
+ */
1459
+ 'toProprietary',
1460
+
1461
+ /**
1462
+ * Updates the Marker with the location of the attached proprietary marker on the map.
1463
+ * @name mxn.Marker#update
1464
+ * @function
1465
+ */
1466
+ 'update'
1467
+ ]);
1468
+
1469
+ Marker.prototype.setChild = function(some_proprietary_marker) {
1470
+ this.proprietary_marker = some_proprietary_marker;
1471
+ some_proprietary_marker.mapstraction_marker = this;
1472
+ this.onmap = true;
1473
+ };
1474
+
1475
+ Marker.prototype.setLabel = function(labelText) {
1476
+ this.labelText = labelText;
1477
+ };
1478
+
1479
+ /**
1480
+ * addData conviniently set a hash of options on a marker
1481
+ * @param {Object} options An object literal hash of key value pairs. Keys are: label, infoBubble, icon, iconShadow, infoDiv, draggable, hover, hoverIcon, openBubble, groupName.
1482
+ */
1483
+ Marker.prototype.addData = function(options){
1484
+ for(var sOptKey in options) {
1485
+ if(options.hasOwnProperty(sOptKey)){
1486
+ switch(sOptKey) {
1487
+ case 'label':
1488
+ this.setLabel(options.label);
1489
+ break;
1490
+ case 'infoBubble':
1491
+ this.setInfoBubble(options.infoBubble);
1492
+ break;
1493
+ case 'icon':
1494
+ if(options.iconSize && options.iconAnchor) {
1495
+ this.setIcon(options.icon, options.iconSize, options.iconAnchor);
1496
+ }
1497
+ else if(options.iconSize) {
1498
+ this.setIcon(options.icon, options.iconSize);
1499
+ }
1500
+ else {
1501
+ this.setIcon(options.icon);
1502
+ }
1503
+ break;
1504
+ case 'iconShadow':
1505
+ if(options.iconShadowSize) {
1506
+ this.setShadowIcon(options.iconShadow, [ options.iconShadowSize[0], options.iconShadowSize[1] ]);
1507
+ }
1508
+ else {
1509
+ this.setIcon(options.iconShadow);
1510
+ }
1511
+ break;
1512
+ case 'infoDiv':
1513
+ this.setInfoDiv(options.infoDiv[0],options.infoDiv[1]);
1514
+ break;
1515
+ case 'draggable':
1516
+ this.setDraggable(options.draggable);
1517
+ break;
1518
+ case 'hover':
1519
+ this.setHover(options.hover);
1520
+ this.setHoverIcon(options.hoverIcon);
1521
+ break;
1522
+ case 'hoverIcon':
1523
+ this.setHoverIcon(options.hoverIcon);
1524
+ break;
1525
+ case 'openBubble':
1526
+ this.openBubble();
1527
+ break;
1528
+ case 'closeBubble':
1529
+ this.closeBubble();
1530
+ break;
1531
+ case 'groupName':
1532
+ this.setGroupName(options.groupName);
1533
+ break;
1534
+ default:
1535
+ // don't have a specific action for this bit of
1536
+ // data so set a named attribute
1537
+ this.setAttribute(sOptKey, options[sOptKey]);
1538
+ break;
1539
+ }
1540
+ }
1541
+ }
1542
+ };
1543
+
1544
+ /**
1545
+ * Sets the html/text content for a bubble popup for a marker
1546
+ * @param {String} infoBubble the html/text you want displayed
1547
+ */
1548
+ Marker.prototype.setInfoBubble = function(infoBubble) {
1549
+ this.infoBubble = infoBubble;
1550
+ };
1551
+
1552
+ /**
1553
+ * Sets the text and the id of the div element where to the information
1554
+ * useful for putting information in a div outside of the map
1555
+ * @param {String} infoDiv the html/text you want displayed
1556
+ * @param {String} div the element id to use for displaying the text/html
1557
+ */
1558
+ Marker.prototype.setInfoDiv = function(infoDiv,div){
1559
+ this.infoDiv = infoDiv;
1560
+ this.div = div;
1561
+ };
1562
+
1563
+ /**
1564
+ * Sets the icon for a marker
1565
+ * @param {String} iconUrl The URL of the image you want to be the icon
1566
+ */
1567
+ Marker.prototype.setIcon = function(iconUrl, iconSize, iconAnchor) {
1568
+ this.iconUrl = iconUrl;
1569
+ if(iconSize) {
1570
+ this.iconSize = iconSize;
1571
+ }
1572
+ if(iconAnchor) {
1573
+ this.iconAnchor = iconAnchor;
1574
+ }
1575
+ };
1576
+
1577
+ /**
1578
+ * Sets the size of the icon for a marker
1579
+ * @param {Array} iconSize The array size in pixels of the marker image: [ width, height ]
1580
+ */
1581
+ Marker.prototype.setIconSize = function(iconSize){
1582
+ if(iconSize) {
1583
+ this.iconSize = iconSize;
1584
+ }
1585
+ };
1586
+
1587
+ /**
1588
+ * Sets the anchor point for a marker
1589
+ * @param {Array} iconAnchor The array offset in pixels of the anchor point from top left: [ right, down ]
1590
+ */
1591
+ Marker.prototype.setIconAnchor = function(iconAnchor){
1592
+ if(iconAnchor) {
1593
+ this.iconAnchor = iconAnchor;
1594
+ }
1595
+ };
1596
+
1597
+ /**
1598
+ * Sets the icon for a marker
1599
+ * @param {String} iconUrl The URL of the image you want to be the icon
1600
+ */
1601
+ Marker.prototype.setShadowIcon = function(iconShadowUrl, iconShadowSize){
1602
+ this.iconShadowUrl = iconShadowUrl;
1603
+ if(iconShadowSize) {
1604
+ this.iconShadowSize = iconShadowSize;
1605
+ }
1606
+ };
1607
+
1608
+ Marker.prototype.setHoverIcon = function(hoverIconUrl){
1609
+ this.hoverIconUrl = hoverIconUrl;
1610
+ };
1611
+
1612
+ /**
1613
+ * Sets the draggable state of the marker
1614
+ * @param {Bool} draggable set to true if marker should be draggable by the user
1615
+ */
1616
+ Marker.prototype.setDraggable = function(draggable) {
1617
+ this.draggable = draggable;
1618
+ };
1619
+
1620
+ /**
1621
+ * Sets that the marker info is displayed on hover
1622
+ * @param {Boolean} hover set to true if marker should display info on hover
1623
+ */
1624
+ Marker.prototype.setHover = function(hover) {
1625
+ this.hover = hover;
1626
+ };
1627
+
1628
+ /**
1629
+ * Markers are grouped up by this name. declutterGroup makes use of this.
1630
+ */
1631
+ Marker.prototype.setGroupName = function(sGrpName) {
1632
+ this.groupName = sGrpName;
1633
+ };
1634
+
1635
+ /**
1636
+ * Set an arbitrary key/value pair on a marker
1637
+ * @param {String} key
1638
+ * @param value
1639
+ */
1640
+ Marker.prototype.setAttribute = function(key,value) {
1641
+ this.attributes[key] = value;
1642
+ };
1643
+
1644
+ /**
1645
+ * getAttribute: gets the value of "key"
1646
+ * @param {String} key
1647
+ * @returns value
1648
+ */
1649
+ Marker.prototype.getAttribute = function(key) {
1650
+ return this.attributes[key];
1651
+ };
1652
+
1653
+
1654
+ ///////////////
1655
+ // Polyline ///
1656
+ ///////////////
1657
+
1658
+ /**
1659
+ * Instantiates a new Polyline.
1660
+ * @name mxn.Polyline
1661
+ * @constructor
1662
+ * @param {Point[]} points Points that make up the Polyline.
1663
+ * @exports Polyline as mxn.Polyline
1664
+ */
1665
+ var Polyline = mxn.Polyline = function(points) {
1666
+ this.api = null;
1667
+ this.points = points;
1668
+ this.attributes = [];
1669
+ this.onmap = false;
1670
+ this.proprietary_polyline = false;
1671
+ this.pllID = "mspll-"+new Date().getTime()+'-'+(Math.floor(Math.random()*Math.pow(2,16)));
1672
+ this.invoker = new mxn.Invoker(this, 'Polyline', function(){return this.api;});
1673
+ };
1674
+
1675
+ mxn.addProxyMethods(Polyline, [
1676
+
1677
+ /**
1678
+ * Retrieve the settings from a proprietary polyline.
1679
+ * @name mxn.Polyline#fromProprietary
1680
+ * @function
1681
+ * @param {String} apiId The API ID of the proprietary polyline.
1682
+ * @param {Object} polyline The proprietary polyline.
1683
+ */
1684
+ 'fromProprietary',
1685
+
1686
+ /**
1687
+ * Hide the polyline.
1688
+ * @name mxn.Polyline#hide
1689
+ * @function
1690
+ */
1691
+ 'hide',
1692
+
1693
+ /**
1694
+ * Show the polyline.
1695
+ * @name mxn.Polyline#show
1696
+ * @function
1697
+ */
1698
+ 'show',
1699
+
1700
+ /**
1701
+ * Converts the current Polyline to a proprietary one for the API specified by apiId.
1702
+ * @name mxn.Polyline#toProprietary
1703
+ * @function
1704
+ * @param {String} apiId The API ID of the proprietary polyline.
1705
+ * @returns A proprietary polyline.
1706
+ */
1707
+ 'toProprietary',
1708
+
1709
+ /**
1710
+ * Updates the Polyline with the path of the attached proprietary polyline on the map.
1711
+ * @name mxn.Polyline#update
1712
+ * @function
1713
+ */
1714
+ 'update'
1715
+ ]);
1716
+
1717
+ /**
1718
+ * addData conviniently set a hash of options on a polyline
1719
+ * @param {Object} options An object literal hash of key value pairs. Keys are: color, width, opacity, closed, fillColor.
1720
+ */
1721
+ Polyline.prototype.addData = function(options){
1722
+ for(var sOpt in options) {
1723
+ if(options.hasOwnProperty(sOpt)){
1724
+ switch(sOpt) {
1725
+ case 'color':
1726
+ this.setColor(options.color);
1727
+ break;
1728
+ case 'width':
1729
+ this.setWidth(options.width);
1730
+ break;
1731
+ case 'opacity':
1732
+ this.setOpacity(options.opacity);
1733
+ break;
1734
+ case 'closed':
1735
+ this.setClosed(options.closed);
1736
+ break;
1737
+ case 'fillColor':
1738
+ this.setFillColor(options.fillColor);
1739
+ break;
1740
+ default:
1741
+ this.setAttribute(sOpt, options[sOpt]);
1742
+ break;
1743
+ }
1744
+ }
1745
+ }
1746
+ };
1747
+
1748
+ Polyline.prototype.setChild = function(some_proprietary_polyline) {
1749
+ this.proprietary_polyline = some_proprietary_polyline;
1750
+ this.onmap = true;
1751
+ };
1752
+
1753
+ /**
1754
+ * in the form: #RRGGBB
1755
+ * Note map24 insists on upper case, so we convert it.
1756
+ */
1757
+ Polyline.prototype.setColor = function(color){
1758
+ this.color = (color.length==7 && color[0]=="#") ? color.toUpperCase() : color;
1759
+ };
1760
+
1761
+ /**
1762
+ * Stroke width of the polyline
1763
+ * @param {Integer} width
1764
+ */
1765
+ Polyline.prototype.setWidth = function(width){
1766
+ this.width = width;
1767
+ };
1768
+
1769
+ /**
1770
+ * A float between 0.0 and 1.0
1771
+ * @param {Float} opacity
1772
+ */
1773
+ Polyline.prototype.setOpacity = function(opacity){
1774
+ this.opacity = opacity;
1775
+ };
1776
+
1777
+ /**
1778
+ * Marks the polyline as a closed polygon
1779
+ * @param {Boolean} bClosed
1780
+ */
1781
+ Polyline.prototype.setClosed = function(bClosed){
1782
+ this.closed = bClosed;
1783
+ };
1784
+
1785
+ /**
1786
+ * Fill color for a closed polyline as HTML color value e.g. #RRGGBB
1787
+ * @param {String} sFillColor HTML color value #RRGGBB
1788
+ */
1789
+ Polyline.prototype.setFillColor = function(sFillColor) {
1790
+ this.fillColor = sFillColor;
1791
+ };
1792
+
1793
+
1794
+ /**
1795
+ * Set an arbitrary key/value pair on a polyline
1796
+ * @param {String} key
1797
+ * @param value
1798
+ */
1799
+ Polyline.prototype.setAttribute = function(key,value) {
1800
+ this.attributes[key] = value;
1801
+ };
1802
+
1803
+ /**
1804
+ * Gets the value of "key"
1805
+ * @param {String} key
1806
+ * @returns value
1807
+ */
1808
+ Polyline.prototype.getAttribute = function(key) {
1809
+ return this.attributes[key];
1810
+ };
1811
+
1812
+ /**
1813
+ * Simplifies a polyline, averaging and reducing the points
1814
+ * @param {Number} tolerance (1.0 is a good starting point)
1815
+ */
1816
+ Polyline.prototype.simplify = function(tolerance) {
1817
+ var reduced = [];
1818
+
1819
+ // First point
1820
+ reduced[0] = this.points[0];
1821
+
1822
+ var markerPoint = 0;
1823
+
1824
+ for (var i = 1; i < this.points.length-1; i++){
1825
+ if (this.points[i].distance(this.points[markerPoint]) >= tolerance)
1826
+ {
1827
+ reduced[reduced.length] = this.points[i];
1828
+ markerPoint = i;
1829
+ }
1830
+ }
1831
+
1832
+ // Last point
1833
+ reduced[reduced.length] = this.points[this.points.length-1];
1834
+
1835
+ // Revert
1836
+ this.points = reduced;
1837
+ };
1838
+
1839
+ ///////////////
1840
+ // Radius //
1841
+ ///////////////
1842
+
1843
+ /**
1844
+ * Creates a new radius object for drawing circles around a point, does a lot of initial calculation to increase load time
1845
+ * @name mxn.Radius
1846
+ * @constructor
1847
+ * @param {LatLonPoint} center LatLonPoint of the radius
1848
+ * @param {Number} quality Number of points that comprise the approximated circle (20 is a good starting point)
1849
+ * @exports Radius as mxn.Radius
1850
+ */
1851
+ var Radius = mxn.Radius = function(center, quality) {
1852
+ this.center = center;
1853
+ var latConv = center.latConv();
1854
+ var lonConv = center.lonConv();
1855
+
1856
+ // Create Radian conversion constant
1857
+ var rad = Math.PI / 180;
1858
+ this.calcs = [];
1859
+
1860
+ for(var i = 0; i < 360; i += quality){
1861
+ this.calcs.push([Math.cos(i * rad) / latConv, Math.sin(i * rad) / lonConv]);
1862
+ }
1863
+ };
1864
+
1865
+ /**
1866
+ * Returns polyline of a circle around the point based on new radius
1867
+ * @param {Radius} radius
1868
+ * @param {Color} color
1869
+ * @returns {Polyline} Polyline
1870
+ */
1871
+ Radius.prototype.getPolyline = function(radius, color) {
1872
+ var points = [];
1873
+
1874
+ for(var i = 0; i < this.calcs.length; i++){
1875
+ var point = new LatLonPoint(
1876
+ this.center.lat + (radius * this.calcs[i][0]),
1877
+ this.center.lon + (radius * this.calcs[i][1])
1878
+ );
1879
+ points.push(point);
1880
+ }
1881
+
1882
+ // Add first point
1883
+ points.push(points[0]);
1884
+
1885
+ var line = new Polyline(points);
1886
+ line.setColor(color);
1887
+
1888
+ return line;
1889
+ };
1890
+
1891
+
1892
+ })();