4 * @exports mxn.util.$m as $m
9 * Initialise our provider. This function should only be called
10 * from within mapstraction code, not exposed as part of the API.
13 var init = function() {
14 this.invoker.go('init', [ this.currentElement, this.api ]);
19 * Mapstraction instantiates a map with some API choice into the HTML element given
20 * @name mxn.Mapstraction
22 * @param {String} element The HTML element to replace with a map
23 * @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.
24 * @param {Bool} debug optional parameter to turn on debug support - this uses alert panels for unsupported actions
25 * @exports Mapstraction as mxn.Mapstraction
27 var Mapstraction = mxn.Mapstraction = function(element, api, debug) {
29 api = mxn.util.getAvailableProviders()[0];
33 this.currentElement = $m(element);
34 this.eventListeners = [];
42 this.element = element;
46 enableScrollWheelZoom: false,
50 this.addControlsArgs = {};
52 // set up our invoker for calling API methods
53 this.invoker = new mxn.Invoker(this, 'Mapstraction', function(){ return this.api; });
60 * @name mxn.Mapstraction#load
66 * Map is clicked {location: LatLonPoint}
67 * @name mxn.Mapstraction#click
74 * @name mxn.Mapstraction#endPan
81 * @name mxn.Mapstraction#changeZoom
87 * Marker is removed {marker: Marker}
88 * @name mxn.Mapstraction#markerAdded
94 * Marker is removed {marker: Marker}
95 * @name mxn.Mapstraction#markerRemoved
101 * Polyline is added {polyline: Polyline}
102 * @name mxn.Mapstraction#polylineAdded
108 * Polyline is removed {polyline: Polyline}
109 * @name mxn.Mapstraction#polylineRemoved
115 // finally initialize our proper API map
119 // Map type constants
120 Mapstraction.ROAD = 1;
121 Mapstraction.SATELLITE = 2;
122 Mapstraction.HYBRID = 3;
124 // methods that have no implementation in mapstraction core
125 mxn.addProxyMethods(Mapstraction, [
127 * Adds a large map panning control and zoom buttons to the map
128 * @name mxn.Mapstraction#addLargeControls
134 * Adds a map type control to the map (streets, aerial imagery etc)
135 * @name mxn.Mapstraction#addMapTypeControls
138 'addMapTypeControls',
141 * Adds a GeoRSS or KML overlay to the map
142 * some flavors of GeoRSS and KML are not supported by some of the Map providers
143 * @name mxn.Mapstraction#addOverlay
145 * @param {String} url GeoRSS or KML feed URL
146 * @param {Boolean} autoCenterAndZoom Set true to auto center and zoom after the feed is loaded
151 * Adds a small map panning control and zoom buttons to the map
152 * @name mxn.Mapstraction#addSmallControls
158 * Applies the current option settings
159 * @name mxn.Mapstraction#applyOptions
165 * Gets the BoundingBox of the map
166 * @name mxn.Mapstraction#getBounds
168 * @returns {BoundingBox} The bounding box for the current map state
173 * Gets the central point of the map
174 * @name mxn.Mapstraction#getCenter
176 * @returns {LatLonPoint} The center point of the map
181 * Gets the imagery type for the map.
182 * The type can be one of:
183 * mxn.Mapstraction.ROAD
184 * mxn.Mapstraction.SATELLITE
185 * mxn.Mapstraction.HYBRID
186 * @name mxn.Mapstraction#getMapType
193 * Returns a ratio to turn distance into pixels based on current projection
194 * @name mxn.Mapstraction#getPixelRatio
196 * @returns {Float} ratio
201 * Returns the zoom level of the map
202 * @name mxn.Mapstraction#getZoom
204 * @returns {Integer} The zoom level of the map
209 * Returns the best zoom level for bounds given
210 * @name mxn.Mapstraction#getZoomLevelForBoundingBox
212 * @param {BoundingBox} bbox The bounds to fit
213 * @returns {Integer} The closest zoom level that contains the bounding box
215 'getZoomLevelForBoundingBox',
218 * Displays the coordinates of the cursor in the HTML element
219 * @name mxn.Mapstraction#mousePosition
221 * @param {String} element ID of the HTML element to display the coordinates in
226 * Resize the current map to the specified width and height
227 * (since it is actually on a child div of the mapElement passed
228 * as argument to the Mapstraction constructor, the resizing of this
229 * mapElement may have no effect on the size of the actual map)
230 * @name mxn.Mapstraction#resizeTo
232 * @param {Integer} width The width the map should be.
233 * @param {Integer} height The width the map should be.
238 * Sets the map to the appropriate location and zoom for a given BoundingBox
239 * @name mxn.Mapstraction#setBounds
241 * @param {BoundingBox} bounds The bounding box you want the map to show
246 * setCenter sets the central point of the map
247 * @name mxn.Mapstraction#setCenter
249 * @param {LatLonPoint} point The point at which to center the map
250 * @param {Object} options Optional parameters
251 * @param {Boolean} options.pan Whether the map should move to the locations using a pan or just jump straight there
256 * Centers the map to some place and zoom level
257 * @name mxn.Mapstraction#setCenterAndZoom
259 * @param {LatLonPoint} point Where the center of the map should be
260 * @param {Integer} zoom The zoom level where 0 is all the way out.
265 * Sets the imagery type for the map
266 * The type can be one of:
267 * mxn.Mapstraction.ROAD
268 * mxn.Mapstraction.SATELLITE
269 * mxn.Mapstraction.HYBRID
270 * @name mxn.Mapstraction#setMapType
272 * @param {Number} type
277 * Sets the zoom level for the map
278 * 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
279 * TODO: Mapstraction.prototype.getZoomLevels or something.
280 * @name mxn.Mapstraction#setZoom
282 * @param {Number} zoom The (native to the map) level zoom the map to.
287 * Turns a Tile Layer on or off
288 * @name mxn.Mapstraction#toggleTileLayer
290 * @param {tile_url} url of the tile layer that was created.
296 * Sets the current options to those specified in oOpts and applies them
297 * @param {Object} oOpts Hash of options to set
299 Mapstraction.prototype.setOptions = function(oOpts){
300 mxn.util.merge(this.options, oOpts);
305 * Sets an option and applies it.
306 * @param {String} sOptName Option name
307 * @param vVal Option value
309 Mapstraction.prototype.setOption = function(sOptName, vVal){
310 this.options[sOptName] = vVal;
315 * Enable scroll wheel zooming
316 * @deprecated Use setOption instead.
318 Mapstraction.prototype.enableScrollWheelZoom = function() {
319 this.setOption('enableScrollWheelZoom', true);
323 * Enable/disable dragging of the map
324 * @param {Boolean} on
325 * @deprecated Use setOption instead.
327 Mapstraction.prototype.dragging = function(on) {
328 this.setOption('enableDragging', on);
332 * Change the current api on the fly
333 * @param {String} api The API to swap to
336 Mapstraction.prototype.swap = function(element,api) {
337 if (this.api === api) {
341 var center = this.getCenter();
342 var zoom = this.getZoom();
344 this.currentElement.style.visibility = 'hidden';
345 this.currentElement.style.display = 'none';
347 this.currentElement = $m(element);
348 this.currentElement.style.visibility = 'visible';
349 this.currentElement.style.display = 'block';
353 if (this.maps[this.api] === undefined) {
356 this.setCenterAndZoom(center,zoom);
358 for (var i = 0; i < this.markers.length; i++) {
359 this.addMarker(this.markers[i], true);
362 for (var j = 0; j < this.polylines.length; j++) {
363 this.addPolyline( this.polylines[j], true);
369 this.setCenterAndZoom(center,zoom);
371 //TODO synchronize the markers and polylines too
372 // (any overlays created after api instantiation are not sync'd)
375 this.addControls(this.addControlsArgs);
380 * Returns the loaded state of a Map Provider
381 * @param {String} api Optional API to query for. If not specified, returns state of the originally created API
383 Mapstraction.prototype.isLoaded = function(api){
387 return this.loaded[api];
391 * Set the debugging on or off - shows alert panels for functions that don't exist in Mapstraction
392 * @param {Boolean} debug true to turn on debugging, false to turn it off
394 Mapstraction.prototype.setDebug = function(debug){
402 /////////////////////////
406 // FIXME need to consolidate some of these handlers...
408 ///////////////////////////
410 // Click handler attached to native API
411 Mapstraction.prototype.clickHandler = function(lat, lon, me) {
412 this.callEventListeners('click', {
413 location: new LatLonPoint(lat, lon)
417 // Move and zoom handler attached to native API
418 Mapstraction.prototype.moveendHandler = function(me) {
419 this.callEventListeners('moveend', {});
423 * Add a listener for an event.
424 * @param {String} type Event type to attach listener to
425 * @param {Function} func Callback function
426 * @param {Object} caller Callback object
428 Mapstraction.prototype.addEventListener = function() {
430 listener.event_type = arguments[0];
431 listener.callback_function = arguments[1];
433 // added the calling object so we can retain scope of callback function
434 if(arguments.length == 3) {
435 listener.back_compat_mode = false;
436 listener.callback_object = arguments[2];
439 listener.back_compat_mode = true;
440 listener.callback_object = null;
442 this.eventListeners.push(listener);
446 * Call listeners for a particular event.
447 * @param {String} sEventType Call listeners of this event type
448 * @param {Object} oEventArgs Event args object to pass back to the callback
450 Mapstraction.prototype.callEventListeners = function(sEventType, oEventArgs) {
451 oEventArgs.source = this;
452 for(var i = 0; i < this.eventListeners.length; i++) {
453 var evLi = this.eventListeners[i];
454 if(evLi.event_type == sEventType) {
455 // only two cases for this, click and move
456 if(evLi.back_compat_mode) {
457 if(evLi.event_type == 'click') {
458 evLi.callback_function(oEventArgs.location);
461 evLi.callback_function();
465 var scope = evLi.callback_object || this;
466 evLi.callback_function.call(scope, oEventArgs);
477 /////////////////////
481 * addControls adds controls to the map. You specify which controls to add in
482 * the associative array that is the only argument.
483 * addControls can be called multiple time, with different args, to dynamically change controls.
487 * zoom: 'large' || 'small',
492 * @param {array} args Which controls to switch on
494 Mapstraction.prototype.addControls = function( args ) {
495 this.addControlsArgs = args;
496 this.invoker.go('addControls', arguments);
500 * Adds a marker pin to the map
501 * @param {Marker} marker The marker to add
502 * @param {Boolean} old If true, doesn't add this marker to the markers array. Used by the "swap" method
504 Mapstraction.prototype.addMarker = function(marker, old) {
505 marker.mapstraction = this;
506 marker.api = this.api;
507 marker.location.api = this.api;
508 marker.map = this.maps[this.api];
509 var propMarker = this.invoker.go('addMarker', arguments);
510 marker.setChild(propMarker);
512 this.markers.push(marker);
514 this.markerAdded.fire({'marker': marker});
518 * addMarkerWithData will addData to the marker, then add it to the map
519 * @param {Marker} marker The marker to add
520 * @param {Object} data A data has to add
522 Mapstraction.prototype.addMarkerWithData = function(marker, data) {
523 marker.addData(data);
524 this.addMarker(marker);
528 * addPolylineWithData will addData to the polyline, then add it to the map
529 * @param {Polyline} polyline The polyline to add
530 * @param {Object} data A data has to add
532 Mapstraction.prototype.addPolylineWithData = function(polyline, data) {
533 polyline.addData(data);
534 this.addPolyline(polyline);
538 * removeMarker removes a Marker from the map
539 * @param {Marker} marker The marker to remove
541 Mapstraction.prototype.removeMarker = function(marker) {
543 for(var i = 0; i < this.markers.length; i++){
544 current_marker = this.markers[i];
545 if(marker == current_marker) {
546 this.invoker.go('removeMarker', arguments);
547 marker.onmap = false;
548 this.markers.splice(i, 1);
549 this.markerRemoved.fire({'marker': marker});
556 * removeAllMarkers removes all the Markers on a map
558 Mapstraction.prototype.removeAllMarkers = function() {
560 while(this.markers.length > 0) {
561 current_marker = this.markers.pop();
562 this.invoker.go('removeMarker', [current_marker]);
567 * Declutter the markers on the map, group together overlapping markers.
568 * @param {Object} opts Declutter options
570 Mapstraction.prototype.declutterMarkers = function(opts) {
571 if(this.loaded[this.api] === false) {
573 this.onload[this.api].push( function() {
574 me.declutterMarkers(opts);
579 var map = this.maps[this.api];
589 // case 'openstreetmap':
595 // case 'openlayers':
600 * Multimap supports quite a lot of decluttering options such as whether
601 * to use an accurate of fast declutter algorithm and what icon to use to
602 * represent a cluster. Using all this would mean abstracting all the enums
603 * etc so we're only implementing the group name function at the moment.
605 map.declutterGroup(opts.groupName);
617 alert(this.api + ' not supported by Mapstraction.declutterMarkers');
623 * Add a polyline to the map
624 * @param {Polyline} polyline The Polyline to add to the map
625 * @param {Boolean} old If true replaces an existing Polyline
627 Mapstraction.prototype.addPolyline = function(polyline, old) {
628 polyline.api = this.api;
629 polyline.map = this.maps[this.api];
630 var propPoly = this.invoker.go('addPolyline', arguments);
631 polyline.setChild(propPoly);
633 this.polylines.push(polyline);
635 this.polylineAdded.fire({'polyline': polyline});
638 // Private remove implementation
639 var removePolylineImpl = function(polyline) {
640 this.invoker.go('removePolyline', arguments);
641 polyline.onmap = false;
642 this.polylineRemoved.fire({'polyline': polyline});
646 * Remove the polyline from the map
647 * @param {Polyline} polyline The Polyline to remove from the map
649 Mapstraction.prototype.removePolyline = function(polyline) {
650 var current_polyline;
651 for(var i = 0; i < this.polylines.length; i++){
652 current_polyline = this.polylines[i];
653 if(polyline == current_polyline) {
654 this.polylines.splice(i, 1);
655 removePolylineImpl.call(this, polyline);
662 * Removes all polylines from the map
664 Mapstraction.prototype.removeAllPolylines = function() {
665 var current_polyline;
666 while(this.polylines.length > 0) {
667 current_polyline = this.polylines.pop();
668 removePolylineImpl.call(this, current_polyline);
673 * autoCenterAndZoom sets the center and zoom of the map to the smallest bounding box
674 * containing all markers
676 Mapstraction.prototype.autoCenterAndZoom = function() {
682 var checkMinMax = function(){
696 for (var i = 0; i < this.markers.length; i++) {
697 lat = this.markers[i].location.lat;
698 lon = this.markers[i].location.lon;
701 for(i = 0; i < this.polylines.length; i++) {
702 for (var j = 0; j < this.polylines[i].points.length; j++) {
703 lat = this.polylines[i].points[j].lat;
704 lon = this.polylines[i].points[j].lon;
708 this.setBounds( new BoundingBox(lat_min, lon_min, lat_max, lon_max) );
712 * centerAndZoomOnPoints sets the center and zoom of the map from an array of points
714 * This is useful if you don't want to have to add markers to the map
716 Mapstraction.prototype.centerAndZoomOnPoints = function(points) {
717 var bounds = new BoundingBox(points[0].lat,points[0].lon,points[0].lat,points[0].lon);
719 for (var i=1, len = points.length ; i<len; i++) {
720 bounds.extend(points[i]);
723 this.setBounds(bounds);
727 * Sets the center and zoom of the map to the smallest bounding box
728 * containing all visible markers and polylines
729 * will only include markers and polylines with an attribute of "visible"
731 Mapstraction.prototype.visibleCenterAndZoom = function() {
737 var checkMinMax = function(){
751 for (var i=0; i<this.markers.length; i++) {
752 if (this.markers[i].getAttribute("visible")) {
753 lat = this.markers[i].location.lat;
754 lon = this.markers[i].location.lon;
759 for (i=0; i<this.polylines.length; i++){
760 if (this.polylines[i].getAttribute("visible")) {
761 for (j=0; j<this.polylines[i].points.length; j++) {
762 lat = this.polylines[i].points[j].lat;
763 lon = this.polylines[i].points[j].lon;
769 this.setBounds(new BoundingBox(lat_min, lon_min, lat_max, lon_max));
773 * Automatically sets center and zoom level to show all polylines
774 * Takes into account radious of polyline
775 * @param {Int} radius
777 Mapstraction.prototype.polylineCenterAndZoom = function(radius) {
783 for (var i=0; i < mapstraction.polylines.length; i++)
785 for (var j=0; j<mapstraction.polylines[i].points.length; j++)
787 lat = mapstraction.polylines[i].points[j].lat;
788 lon = mapstraction.polylines[i].points[j].lon;
790 latConv = lonConv = radius;
794 latConv = (radius / mapstraction.polylines[i].points[j].latConv());
795 lonConv = (radius / mapstraction.polylines[i].points[j].lonConv());
798 if ((lat + latConv) > lat_max) {
799 lat_max = (lat + latConv);
801 if ((lat - latConv) < lat_min) {
802 lat_min = (lat - latConv);
804 if ((lon + lonConv) > lon_max) {
805 lon_max = (lon + lonConv);
807 if ((lon - lonConv) < lon_min) {
808 lon_min = (lon - lonConv);
813 this.setBounds(new BoundingBox(lat_min, lon_min, lat_max, lon_max));
817 * addImageOverlay layers an georeferenced image over the map
818 * @param {id} unique DOM identifier
819 * @param {src} url of image
820 * @param {opacity} opacity 0-100
821 * @param {west} west boundary
822 * @param {south} south boundary
823 * @param {east} east boundary
824 * @param {north} north boundary
826 Mapstraction.prototype.addImageOverlay = function(id, src, opacity, west, south, east, north) {
828 var b = document.createElement("img");
829 b.style.display = 'block';
830 b.setAttribute('id',id);
831 b.setAttribute('src',src);
832 b.style.position = 'absolute';
834 b.setAttribute('west',west);
835 b.setAttribute('south',south);
836 b.setAttribute('east',east);
837 b.setAttribute('north',north);
843 this.invoker.go('addImageOverlay', arguments, { context: oContext });
846 Mapstraction.prototype.setImageOpacity = function(id, opacity) {
850 if (opacity >= 100) {
853 var c = opacity / 100;
854 var d = document.getElementById(id);
855 if(typeof(d.style.filter)=='string'){
856 d.style.filter='alpha(opacity:'+opacity+')';
858 if(typeof(d.style.KHTMLOpacity)=='string'){
859 d.style.KHTMLOpacity=c;
861 if(typeof(d.style.MozOpacity)=='string'){
862 d.style.MozOpacity=c;
864 if(typeof(d.style.opacity)=='string'){
869 Mapstraction.prototype.setImagePosition = function(id) {
870 var imgElement = document.getElementById(id);
873 top: imgElement.getAttribute('north'),
874 left: imgElement.getAttribute('west'),
875 bottom: imgElement.getAttribute('south'),
876 right: imgElement.getAttribute('east')
878 pixels: { top: 0, right: 0, bottom: 0, left: 0 }
881 this.invoker.go('setImagePosition', arguments, { context: oContext });
883 imgElement.style.top = oContext.pixels.top.toString() + 'px';
884 imgElement.style.left = oContext.pixels.left.toString() + 'px';
885 imgElement.style.width = (oContext.pixels.right - oContext.pixels.left).toString() + 'px';
886 imgElement.style.height = (oContext.pixels.bottom - oContext.pixels.top).toString() + 'px';
889 Mapstraction.prototype.addJSON = function(json) {
891 if (typeof(json) == "string") {
892 features = eval('(' + json + ')');
896 features = features.features;
897 var map = this.maps[this.api];
904 if(features.type == "FeatureCollection") {
905 this.addJSON(features.features);
908 for (var i = 0; i < features.length; i++) {
910 switch(item.geometry.type) {
912 html = "<strong>" + item.title + "</strong><p>" + item.description + "</p>";
913 marker = new Marker(new LatLonPoint(item.geometry.coordinates[1],item.geometry.coordinates[0]));
914 markers.push(marker);
915 this.addMarkerWithData(marker,{
918 date : "new Date(\""+item.date+"\")",
919 iconShadow : item.icon_shadow,
921 iconShadowSize : item.icon_shadow_size,
922 icon : "http://boston.openguides.org/markers/AQUA.png",
923 iconSize : item.icon_size,
924 category : item.source_id,
931 polyline = new Polyline(points);
932 mapstraction.addPolylineWithData(polyline,{
933 fillColor : item.poly_color,
934 date : "new Date(\""+item.date+"\")",
935 category : item.source_id,
936 width : item.line_width,
937 opacity : item.line_opacity,
938 color : item.line_color,
941 markers.push(polyline);
944 // console.log("Geometry: " + features.items[i].geometry.type);
951 * Adds a Tile Layer to the map
953 * Requires providing a parameterized tile url. Use {Z}, {X}, and {Y} to specify where the parameters
954 * should go in the URL.
956 * For example, the OpenStreetMap tiles are:
957 * m.addTileLayer("http://tile.openstreetmap.org/{Z}/{X}/{Y}.png", 1.0, "OSM", 1, 19, true);
959 * @param {tile_url} template url of the tiles.
960 * @param {opacity} opacity of the tile layer - 0 is transparent, 1 is opaque. (default=0.6)
961 * @param {copyright_text} copyright text to use for the tile layer. (default=Mapstraction)
962 * @param {min_zoom} Minimum (furtherest out) zoom level that tiles are available (default=1)
963 * @param {max_zoom} Maximum (closest) zoom level that the tiles are available (default=18)
964 * @param {map_type} Should the tile layer be a selectable map type in the layers palette (default=false)
966 Mapstraction.prototype.addTileLayer = function(tile_url, opacity, copyright_text, min_zoom, max_zoom, map_type) {
971 this.tileLayers = this.tileLayers || [];
972 opacity = opacity || 0.6;
973 copyright_text = copyright_text || "Mapstraction";
974 min_zoom = min_zoom || 1;
975 max_zoom = max_zoom || 18;
976 map_type = map_type || false;
978 return this.invoker.go('addTileLayer', [ tile_url, opacity, copyright_text, min_zoom, max_zoom, map_type] );
982 * addFilter adds a marker filter
983 * @param {field} name of attribute to filter on
984 * @param {operator} presently only "ge" or "le"
985 * @param {value} the value to compare against
987 Mapstraction.prototype.addFilter = function(field, operator, value) {
991 this.filters.push( [field, operator, value] );
995 * Remove the specified filter
996 * @param {Object} field
997 * @param {Object} operator
998 * @param {Object} value
1000 Mapstraction.prototype.removeFilter = function(field, operator, value) {
1001 if (!this.filters) {
1006 for (var f=0; f<this.filters.length; f++) {
1007 if (this.filters[f][0] == field &&
1008 (! operator || (this.filters[f][1] == operator && this.filters[f][2] == value))) {
1009 this.filters.splice(f,1);
1010 f--; //array size decreased
1016 * Delete the current filter if present; otherwise add it
1017 * @param {Object} field
1018 * @param {Object} operator
1019 * @param {Object} value
1021 Mapstraction.prototype.toggleFilter = function(field, operator, value) {
1022 if (!this.filters) {
1027 for (var f = 0; f < this.filters.length; f++) {
1028 if (this.filters[f][0] == field && this.filters[f][1] == operator && this.filters[f][2] == value) {
1029 this.filters.splice(f,1);
1030 f--; //array size decreased
1036 this.addFilter(field, operator, value);
1043 Mapstraction.prototype.removeAllFilters = function() {
1048 * doFilter executes all filters added since last call
1049 * Now supports a callback function for when a marker is shown or hidden
1050 * @param {Function} showCallback
1051 * @param {Function} hideCallback
1052 * @returns {Int} count of visible markers
1054 Mapstraction.prototype.doFilter = function(showCallback, hideCallback) {
1055 var map = this.maps[this.api];
1056 var visibleCount = 0;
1061 /* TODO polylines aren't filtered in multimap */
1063 for (f=0; f<this.filters.length; f++) {
1064 mmfilters.push( new MMSearchFilter( this.filters[f][0], this.filters[f][1], this.filters[f][2] ));
1066 map.setMarkerFilters( mmfilters );
1073 for (var m=0; m<this.markers.length; m++) {
1075 for (f = 0; f < this.filters.length; f++) {
1076 if (! this.applyFilter(this.markers[m], this.filters[f])) {
1083 showCallback(this.markers[m]);
1086 this.markers[m].show();
1091 hideCallback(this.markers[m]);
1094 this.markers[m].hide();
1098 this.markers[m].setAttribute("visible", vis);
1103 return visibleCount;
1106 Mapstraction.prototype.applyFilter = function(o, f) {
1110 if (o.getAttribute( f[0] ) < f[2]) {
1115 if (o.getAttribute( f[0] ) > f[2]) {
1120 if (o.getAttribute( f[0] ) == f[2]) {
1130 * getAttributeExtremes returns the minimum/maximum of "field" from all markers
1131 * @param {field} name of "field" to query
1132 * @returns {array} of minimum/maximum
1134 Mapstraction.prototype.getAttributeExtremes = function(field) {
1137 for (var m=0; m<this.markers.length; m++) {
1138 if (! min || min > this.markers[m].getAttribute(field)) {
1139 min = this.markers[m].getAttribute(field);
1141 if (! max || max < this.markers[m].getAttribute(field)) {
1142 max = this.markers[m].getAttribute(field);
1145 for (var p=0; m<this.polylines.length; m++) {
1146 if (! min || min > this.polylines[p].getAttribute(field)) {
1147 min = this.polylines[p].getAttribute(field);
1149 if (! max || max < this.polylines[p].getAttribute(field)) {
1150 max = this.polylines[p].getAttribute(field);
1158 * getMap returns the native map object that mapstraction is talking to
1159 * @returns the native map object mapstraction is using
1161 Mapstraction.prototype.getMap = function() {
1162 // FIXME in an ideal world this shouldn't exist right?
1163 return this.maps[this.api];
1167 //////////////////////////////
1171 /////////////////////////////
1174 * LatLonPoint is a point containing a latitude and longitude with helper methods
1175 * @name mxn.LatLonPoint
1177 * @param {double} lat is the latitude
1178 * @param {double} lon is the longitude
1179 * @exports LatLonPoint as mxn.LatLonPoint
1181 var LatLonPoint = mxn.LatLonPoint = function(lat, lon) {
1182 // TODO error if undefined?
1183 // if (lat == undefined) alert('undefined lat');
1184 // if (lon == undefined) alert('undefined lon');
1187 this.lng = lon; // lets be lon/lng agnostic
1189 this.invoker = new mxn.Invoker(this, 'LatLonPoint');
1192 mxn.addProxyMethods(LatLonPoint, [
1193 'fromProprietary', 'toProprietary'
1197 * toString returns a string represntation of a point
1198 * @returns a string like '51.23, -0.123'
1201 LatLonPoint.prototype.toString = function() {
1202 return this.lat + ', ' + this.lon;
1206 * distance returns the distance in kilometers between two points
1207 * @param {LatLonPoint} otherPoint The other point to measure the distance from to this one
1208 * @returns the distance between the points in kilometers
1211 LatLonPoint.prototype.distance = function(otherPoint) {
1212 // Uses Haversine formula from http://www.movable-type.co.uk
1213 var rads = Math.PI / 180;
1214 var diffLat = (this.lat-otherPoint.lat) * rads;
1215 var diffLon = (this.lon-otherPoint.lon) * rads;
1216 var a = Math.sin(diffLat / 2) * Math.sin(diffLat / 2) +
1217 Math.cos(this.lat*rads) * Math.cos(otherPoint.lat*rads) *
1218 Math.sin(diffLon/2) * Math.sin(diffLon/2);
1219 return 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)) * 6371; // Earth's mean radius in km
1223 * equals tests if this point is the same as some other one
1224 * @param {LatLonPoint} otherPoint The other point to test with
1225 * @returns true or false
1228 LatLonPoint.prototype.equals = function(otherPoint) {
1229 return this.lat == otherPoint.lat && this.lon == otherPoint.lon;
1233 * Returns latitude conversion based on current projection
1234 * @returns {Float} conversion
1236 LatLonPoint.prototype.latConv = function() {
1237 return this.distance(new LatLonPoint(this.lat + 0.1, this.lon))*10;
1241 * Returns longitude conversion based on current projection
1242 * @returns {Float} conversion
1244 LatLonPoint.prototype.lonConv = function() {
1245 return this.distance(new LatLonPoint(this.lat, this.lon + 0.1))*10;
1249 //////////////////////////
1253 //////////////////////////
1256 * BoundingBox creates a new bounding box object
1257 * @name mxn.BoundingBox
1259 * @param {double} swlat the latitude of the south-west point
1260 * @param {double} swlon the longitude of the south-west point
1261 * @param {double} nelat the latitude of the north-east point
1262 * @param {double} nelon the longitude of the north-east point
1263 * @exports BoundingBox as mxn.BoundingBox
1265 var BoundingBox = mxn.BoundingBox = function(swlat, swlon, nelat, nelon) {
1266 //FIXME throw error if box bigger than world
1267 //alert('new bbox ' + swlat + ',' + swlon + ',' + nelat + ',' + nelon);
1268 this.sw = new LatLonPoint(swlat, swlon);
1269 this.ne = new LatLonPoint(nelat, nelon);
1273 * getSouthWest returns a LatLonPoint of the south-west point of the bounding box
1274 * @returns the south-west point of the bounding box
1277 BoundingBox.prototype.getSouthWest = function() {
1282 * getNorthEast returns a LatLonPoint of the north-east point of the bounding box
1283 * @returns the north-east point of the bounding box
1286 BoundingBox.prototype.getNorthEast = function() {
1291 * isEmpty finds if this bounding box has zero area
1292 * @returns whether the north-east and south-west points of the bounding box are the same point
1295 BoundingBox.prototype.isEmpty = function() {
1296 return this.ne == this.sw; // is this right? FIXME
1300 * contains finds whether a given point is within a bounding box
1301 * @param {LatLonPoint} point the point to test with
1302 * @returns whether point is within this bounding box
1305 BoundingBox.prototype.contains = function(point){
1306 return point.lat >= this.sw.lat && point.lat <= this.ne.lat && point.lon >= this.sw.lon && point.lon <= this.ne.lon;
1310 * toSpan returns a LatLonPoint with the lat and lon as the height and width of the bounding box
1311 * @returns a LatLonPoint containing the height and width of this bounding box
1314 BoundingBox.prototype.toSpan = function() {
1315 return new LatLonPoint( Math.abs(this.sw.lat - this.ne.lat), Math.abs(this.sw.lon - this.ne.lon) );
1319 * extend extends the bounding box to include the new point
1321 BoundingBox.prototype.extend = function(point) {
1322 if(this.sw.lat > point.lat) {
1323 this.sw.lat = point.lat;
1325 if(this.sw.lon > point.lon) {
1326 this.sw.lon = point.lon;
1328 if(this.ne.lat < point.lat) {
1329 this.ne.lat = point.lat;
1331 if(this.ne.lon < point.lon) {
1332 this.ne.lon = point.lon;
1337 //////////////////////////////
1341 ///////////////////////////////
1344 * Marker create's a new marker pin
1347 * @param {LatLonPoint} point the point on the map where the marker should go
1348 * @exports Marker as mxn.Marker
1350 var Marker = mxn.Marker = function(point) {
1352 this.location = point;
1354 this.proprietary_marker = false;
1355 this.attributes = [];
1356 this.invoker = new mxn.Invoker(this, 'Marker', function(){return this.api;});
1357 mxn.addEvents(this, [
1358 'openInfoBubble', // Info bubble opened
1359 'closeInfoBubble', // Info bubble closed
1360 'click' // Marker clicked
1364 mxn.addProxyMethods(Marker, [
1373 Marker.prototype.setChild = function(some_proprietary_marker) {
1374 this.proprietary_marker = some_proprietary_marker;
1375 some_proprietary_marker.mapstraction_marker = this;
1379 Marker.prototype.setLabel = function(labelText) {
1380 this.labelText = labelText;
1384 * addData conviniently set a hash of options on a marker
1386 Marker.prototype.addData = function(options){
1387 for(var sOptKey in options) {
1388 if(options.hasOwnProperty(sOptKey)){
1391 this.setLabel(options.label);
1394 this.setInfoBubble(options.infoBubble);
1397 if(options.iconSize && options.iconAnchor) {
1398 this.setIcon(options.icon, options.iconSize, options.iconAnchor);
1400 else if(options.iconSize) {
1401 this.setIcon(options.icon, options.iconSize);
1404 this.setIcon(options.icon);
1408 if(options.iconShadowSize) {
1409 this.setShadowIcon(options.iconShadow, [ options.iconShadowSize[0], options.iconShadowSize[1] ]);
1412 this.setIcon(options.iconShadow);
1416 this.setInfoDiv(options.infoDiv[0],options.infoDiv[1]);
1419 this.setDraggable(options.draggable);
1422 this.setHover(options.hover);
1423 this.setHoverIcon(options.hoverIcon);
1426 this.setHoverIcon(options.hoverIcon);
1432 this.setGroupName(options.groupName);
1435 // don't have a specific action for this bit of
1436 // data so set a named attribute
1437 this.setAttribute(sOptKey, options[sOptKey]);
1445 * setInfoBubble sets the html/text content for a bubble popup for a marker
1446 * @param {String} infoBubble the html/text you want displayed
1448 Marker.prototype.setInfoBubble = function(infoBubble) {
1449 this.infoBubble = infoBubble;
1453 * setInfoDiv sets the text and the id of the div element where to the information
1454 * useful for putting information in a div outside of the map
1455 * @param {String} infoDiv the html/text you want displayed
1456 * @param {String} div the element id to use for displaying the text/html
1458 Marker.prototype.setInfoDiv = function(infoDiv,div){
1459 this.infoDiv = infoDiv;
1464 * setIcon sets the icon for a marker
1465 * @param {String} iconUrl The URL of the image you want to be the icon
1467 Marker.prototype.setIcon = function(iconUrl, iconSize, iconAnchor) {
1468 this.iconUrl = iconUrl;
1470 this.iconSize = iconSize;
1473 this.iconAnchor = iconAnchor;
1478 * setIconSize sets the size of the icon for a marker
1479 * @param {String} iconSize The array size in pixels of the marker image
1481 Marker.prototype.setIconSize = function(iconSize){
1483 this.iconSize = iconSize;
1488 * setIconAnchor sets the anchor point for a marker
1489 * @param {String} iconAnchor The array offset of the anchor point
1491 Marker.prototype.setIconAnchor = function(iconAnchor){
1493 this.iconAnchor = iconAnchor;
1498 * setShadowIcon sets the icon for a marker
1499 * @param {String} iconUrl The URL of the image you want to be the icon
1501 Marker.prototype.setShadowIcon = function(iconShadowUrl, iconShadowSize){
1502 this.iconShadowUrl = iconShadowUrl;
1503 if(iconShadowSize) {
1504 this.iconShadowSize = iconShadowSize;
1508 Marker.prototype.setHoverIcon = function(hoverIconUrl){
1509 this.hoverIconUrl = hoverIconUrl;
1513 * setDraggable sets the draggable state of the marker
1514 * @param {Bool} draggable set to true if marker should be draggable by the user
1516 Marker.prototype.setDraggable = function(draggable) {
1517 this.draggable = draggable;
1521 * setHover sets that the marker info is displayed on hover
1522 * @param {Bool} hover set to true if marker should display info on hover
1524 Marker.prototype.setHover = function(hover) {
1529 * Markers are grouped up by this name. declutterGroup makes use of this.
1531 Marker.prototype.setGroupName = function(sGrpName) {
1532 this.groupName = sGrpName;
1536 * setAttribute: set an arbitrary key/value pair on a marker
1540 Marker.prototype.setAttribute = function(key,value) {
1541 this.attributes[key] = value;
1545 * getAttribute: gets the value of "key"
1549 Marker.prototype.getAttribute = function(key) {
1550 return this.attributes[key];
1559 * Instantiates a new Polyline.
1560 * @name mxn.Polyline
1562 * @param {Point[]} points Points that make up the Polyline.
1563 * @exports Polyline as mxn.Polyline
1565 var Polyline = mxn.Polyline = function(points) {
1567 this.points = points;
1568 this.attributes = [];
1570 this.proprietary_polyline = false;
1571 this.pllID = "mspll-"+new Date().getTime()+'-'+(Math.floor(Math.random()*Math.pow(2,16)));
1572 this.invoker = new mxn.Invoker(this, 'Polyline', function(){return this.api;});
1575 mxn.addProxyMethods(Polyline, [
1584 * addData conviniently set a hash of options on a polyline
1586 Polyline.prototype.addData = function(options){
1587 for(var sOpt in options) {
1588 if(options.hasOwnProperty(sOpt)){
1591 this.setColor(options.color);
1594 this.setWidth(options.width);
1597 this.setOpacity(options.opacity);
1600 this.setClosed(options.closed);
1603 this.setFillColor(options.fillColor);
1606 this.setAttribute(sOpt, options[sOpt]);
1613 Polyline.prototype.setChild = function(some_proprietary_polyline) {
1614 this.proprietary_polyline = some_proprietary_polyline;
1619 * in the form: #RRGGBB
1620 * Note map24 insists on upper case, so we convert it.
1622 Polyline.prototype.setColor = function(color){
1623 this.color = (color.length==7 && color[0]=="#") ? color.toUpperCase() : color;
1627 * Stroke width of the polyline
1628 * @param {Integer} width
1630 Polyline.prototype.setWidth = function(width){
1635 * A float between 0.0 and 1.0
1636 * @param {Float} opacity
1638 Polyline.prototype.setOpacity = function(opacity){
1639 this.opacity = opacity;
1643 * Marks the polyline as a closed polygon
1644 * @param {Boolean} bClosed
1646 Polyline.prototype.setClosed = function(bClosed){
1647 this.closed = bClosed;
1651 * Fill color for a closed polyline as HTML color value e.g. #RRGGBB
1652 * @param {String} sFillColor HTML color value #RRGGBB
1654 Polyline.prototype.setFillColor = function(sFillColor) {
1655 this.fillColor = sFillColor;
1660 * setAttribute: set an arbitrary key/value pair on a polyline
1664 Polyline.prototype.setAttribute = function(key,value) {
1665 this.attributes[key] = value;
1669 * getAttribute: gets the value of "key"
1673 Polyline.prototype.getAttribute = function(key) {
1674 return this.attributes[key];
1678 * Simplifies a polyline, averaging and reducing the points
1679 * @param {Integer} tolerance (1.0 is a good starting point)
1681 Polyline.prototype.simplify = function(tolerance) {
1685 reduced[0] = this.points[0];
1687 var markerPoint = 0;
1689 for (var i = 1; i < this.points.length-1; i++){
1690 if (this.points[i].distance(this.points[markerPoint]) >= tolerance)
1692 reduced[reduced.length] = this.points[i];
1698 reduced[reduced.length] = this.points[this.points.length-1];
1701 this.points = reduced;
1709 * Creates a new radius object for drawing circles around a point, does a lot of initial calculation to increase load time
1710 * @returns a new Radius
1713 * @classDescription Radius
1714 * @param {Object} Center LatLonPoint of the radius
1715 * @param {quality} Number of points that comprise the approximated circle (20 is a good starting point)
1717 var Radius = mxn.Radius = function(center, quality) {
1718 this.center = center;
1719 var latConv = center.latConv();
1720 var lonConv = center.lonConv();
1722 // Create Radian conversion constant
1723 var rad = Math.PI / 180;
1726 for(var i = 0; i < 360; i += quality){
1727 this.calcs.push([Math.cos(i * rad) / latConv, Math.sin(i * rad) / lonConv]);
1732 * Returns polyline of a circle around the point based on new radius
1733 * @param {Radius} radius
1734 * @param {Colour} colour
1735 * @returns {Polyline} Polyline
1737 Radius.prototype.getPolyline = function(radius, colour) {
1740 for(var i = 0; i < this.calcs.length; i++){
1741 var point = new LatLonPoint(
1742 this.center.lat + (radius * this.calcs[i][0]),
1743 this.center.lon + (radius * this.calcs[i][1])
1749 points.push(points[0]);
1751 var line = new Polyline(points);
1752 line.setColor(colour);