"use strict"; /** * This js file defines an abstract map API. * This abstract map API is used by location related features, to replace previous direct usage of Google Maps API. * The API design is almost as same as Google Maps API and some 3rd party Google Maps Plugins API (RichMarker, InfoBubble, MarkerClusterer). * Note that this abstract map API is NOT a full set of API replacement to Google Maps API, it only contains corresponding APIs used by our code. */ (function () { var bmcMaps = { /** * Identifiers for common MapTypes. */ MapTypeId: { /** * This map type displays a normal street map. */ ROADMAP: 'roadmap', /** * This map type displays satellite images. */ SATELLITE: 'satellite', /** * This map type displays a transparent layer of major streets on satellite images. */ HYBRID: 'hybrid' }, /** * Identifiers used to specify the placement of controls on the map. */ ControlPosition: { /** * Elements are positioned on the left, above bottom-left elements, and flow upwards. */ LEFT_BOTTOM: 6 }, /** * Identifiers for the zoom control. */ ZoomControlStyle: { /** * The larger control, with the zoom slider in addition to +/- buttons. */ LARGE: 2 } }; /** * Namespace for adapters static methods */ bmcMaps.adapters = {}; bmcMaps._adapters = {}; // Load multiple scripts bmcMaps.adapters.loadScripts = function (scripts) { var promises = []; for (var i = 0, l = scripts.length; i < l; i++) { promises.push($.getScript(scripts[i])); } return $.when.apply($, promises); }; bmcMaps.adapters.saveMapsAvailability = function (availability) { angular.mapsAvailable = availability; }; bmcMaps.adapters.getMapAdapterLink = function (adapterId) { return 'scripts/app/location/map-api-adapters/map-adapter-' + adapterId + '.js'; }; bmcMaps.adapters.loadAdapter = function (adapterId, callback) { var mapAdapterLink = this.getMapAdapterLink(adapterId); $.ajax({ url: mapAdapterLink, dataType: "script", success: callback, scriptAttrs: {} //$.getScript issue for jQuery v3.4.1 in Karma }); }; bmcMaps.adapters.init = function (data) { var adapterId = data.MAP_API || 'google'; var deferred = $.Deferred(); this.loadAdapter(adapterId, function () { var adapter = bmcMaps.adapters.activate(adapterId); // init specific adapter adapter.init(data, deferred); }); return deferred.promise(); }; /** * Register a map adapter * @param {string} id Identifier of the map adapter to be registered * @param {Object} adapter Map adapter object */ bmcMaps.adapters.register = function (id, adapter) { bmcMaps._adapters[id] = adapter; }; /** * Set active map adapter * @param {string} id Identifier of the map adapter to be active */ bmcMaps.adapters.activate = function (id) { bmcMaps.adapter = bmcMaps._adapters[id]; return bmcMaps.adapter; }; bmcMaps.mapInit = function (map) { if (_.isFunction(bmcMaps.adapter.mapInit)) { bmcMaps.adapter.mapInit(map.adapter); } }; /** * Namespace for event static methods */ bmcMaps.event = {}; /** * Add the given listener function to the given event name for the given object instance. * @param {bmcMaps.MVCObject} abstraction Abstract map object instance * @param {string} eventType Event name to listen on * @param {Function} listener Listener function callback */ bmcMaps.event.addListener = function (abstraction, eventType, listener) { bmcMaps.adapter.event.addListener(abstraction.adapter, eventType, listener); }; /** * Trigger the given event on given object instance. * @param {bmcMaps.MVCObject} abstraction Abstract map object instance * @param {string} eventType Event name to listen on */ bmcMaps.event.trigger = function (abstraction, eventType) { bmcMaps.adapter.event.trigger(abstraction.adapter || { impl: abstraction.impl || abstraction }, eventType); }; /** * Cross browser event handler registration. * @param {Node} element HTML node element * @param {string} eventType Event name to listen on * @param {Function} listener Listener function callback */ bmcMaps.event.addDomListener = function (element, eventType, listener) { bmcMaps.adapter.event.addDomListener(element, eventType, listener); }; /** * Like addListener, but the handler removes itself after handling the first event. * @param {bmcMaps.MVCObject} abstraction Abstract map object instance * @param {string} eventType Event name to listen on * @param {Function} listener Listener function callback */ bmcMaps.event.addListenerOnce = function (abstraction, eventType, listener) { bmcMaps.adapter.event.addListenerOnce(abstraction.adapter, eventType, listener); }; /** * Removes all event listeners from element * @param {bmcMaps.MVCObject} abstraction Abstract map object instance */ bmcMaps.event.clearInstanceListeners = function (abstraction) { bmcMaps.adapter.event.clearInstanceListeners(abstraction.adapter); }; /** * Base class wrapping key-value pairs object. */ bmcMaps.MVCObject = function () { }; /** * Set a collection of key-value pairs. * @param {Object} options Object with key-value pairs */ bmcMaps.MVCObject.prototype.setValues = function (options) { for (var key in options) { if (this[key] === undefined) { this[key] = options[key]; } } }; bmcMaps.geocoder = {}; bmcMaps.geocoder.geocode = function (address, successCb, failCB) { bmcMaps.adapter.geocoder.geocode(address, successCb, failCB); }; bmcMaps.getDirectionUrl = function (address) { return bmcMaps.adapter.getDirectionUrl(address); }; /** * Map class. This class extends MVCObject. * @param {Node} element HTML node element as map container * @param {Object} options Map options with key-value pairs * @constructor */ bmcMaps.Map = function (element, options) { this.setValues(options); this.adapter = bmcMaps.adapter.map(element, options); this.mapTypes = new bmcMaps.MapTypeRegistry(this); }; bmcMaps.Map.prototype = new bmcMaps.MVCObject(); /** * Map type registry class. * @param {bmcMaps.Map} map Atract map object * @constructor */ bmcMaps.MapTypeRegistry = function (map) { this.adapter = map.adapter.getMapTypeRegistry(); }; /** * Set a custom map type. * @param {string} id Identifier of map type * @param {Object} mapType Object to specify a custom map type */ bmcMaps.MapTypeRegistry.prototype.set = function (id, mapType) { this[id] = mapType; this.adapter.set(id, mapType); }; /** * Add Marker to the map based on location. * @param {location} location of the marker */ bmcMaps.Map.prototype.addMarker = function (location, icon) { this.adapter.addMarker(location, icon); }; /** * Set the position displayed at the center of the map. * @param {bmcMaps.LatLng} latlng Position to be the center of the map */ bmcMaps.Map.prototype.setCenter = function (latlng) { this.adapter.setCenter(latlng); }; /** * Set the map type to be displayed * @param {bmcMaps.MapTypeId|String} mapTypeId Identifier of map type to be displayed */ bmcMaps.Map.prototype.setMapTypeId = function (mapTypeId) { this.mapTypeId = mapTypeId; this.adapter.setMapTypeId(mapTypeId); }; /** * Return the position displayed at the center of the map. * @returns {bmcMaps.LatLng} Position of the map center */ bmcMaps.Map.prototype.getCenter = function () { return this.adapter.getCenter(); }; /** * Set the viewport to contain the given bounds. * @param {bmcMaps.LatLngBounds} bounds */ bmcMaps.Map.prototype.fitBounds = function (bounds) { this.adapter.fitBounds(bounds); }; /** * Set the zoom level of the map. * @param {number} zoom */ bmcMaps.Map.prototype.setZoom = function (zoom) { this.adapter.setZoom(zoom); }; /** * Get the zoom level of the map. * @return {number} */ bmcMaps.Map.prototype.getZoom = function () { return this.adapter.getZoom(); }; /** * Returns the lat/lng bounds of the current viewport. * @return {bmcMaps.LatLngBounds} */ bmcMaps.Map.prototype.getBounds = function () { return this.adapter.getBounds(); }; /** * Returns the current Projection. * @return {bmcMaps.Projection} */ bmcMaps.Map.prototype.getProjection = function () { return new bmcMaps.Projection(this); }; /** * Map projection class. * @param {bmcMaps.Map} map Atract map object * @constructor */ bmcMaps.Projection = function (map) { this.adapter = map.adapter.getProjection(); }; /** * Translates from world coordinates on a map projection to LatLng values. * @param {bmcMaps.Point} point World coordinates point * @return {bmcMaps.LatLng} */ bmcMaps.Projection.prototype.fromPointToLatLng = function (point) { return this.adapter.fromPointToLatLng(point); }; /** * Pans the map by the minimum amount necessary to contain the given LatLngBounds. * @param {bmcMaps.LatLngBounds} bounds */ bmcMaps.Map.prototype.panToBounds = function (bounds) { this.adapter.panToBounds(bounds); }; /** * Changes the center of the map to the given LatLng. * @param {bmcMaps.LatLng} latlng */ bmcMaps.Map.prototype.panTo = function (latlng) { this.adapter.panTo(latlng); }; /** * A point on a two-dimensional plane. * @param {number} x x-axis value * @param {number} y y-axis value */ bmcMaps.Point = function (x, y) { this.x = x; this.y = y; }; /** * Two-dimensonal size, where width is the distance on the x-axis, and height is the distance on the y-axis. * @param {number} width * @param {number} height */ bmcMaps.Size = function (width, height) { this.width = width; this.height = height; }; /** * A LatLng is a point in geographical coordinates: latitude and longitude. * Latitude is specified in degrees within the range [-90, 90]. Longitude is specified in degrees within the range [-180, 180] * @param {number} lat latitude * @param {number} lng longitude */ bmcMaps.LatLng = function (lat, lng) { this._lat = lat; this._lng = lng; }; /** * Returns the latitude in degrees. * @return {number} */ bmcMaps.LatLng.prototype.lat = function () { return this._lat; }; /** * Returns the longitude in degrees. * @return {number} */ bmcMaps.LatLng.prototype.lng = function () { return this._lng; }; /** * A LatLngBounds instance represents a rectangle in geographical coordinates. * Constructs a rectangle from the points at its south-west and north-east corners. * @param {bmcMaps.LatLng} sw South-west corner * @param {bmcMaps.LatLng} ne North-east corner */ bmcMaps.LatLngBounds = function (sw, ne) { if (sw) { this.sw = new bmcMaps.LatLng(sw._lat, sw._lng); if (!ne) { this.ne = new bmcMaps.LatLng(sw._lat, sw._lng); } } if (ne) { this.ne = new bmcMaps.LatLng(ne._lat, ne._lng); if (!sw) { this.sw = new bmcMaps.LatLng(ne._lat, ne._lng); } } }; /** * Returns true if the given lat/lng is in this bounds. * @param {bmcMaps.LatLng} latlng position to be tested * @return {boolean} */ bmcMaps.LatLngBounds.prototype.contains = function (latlng) { return latlng._lat >= this.sw._lat && latlng._lat <= this.ne._lat && (this.sw._lng <= this.ne._lng && latlng._lng >= this.sw._lng && latlng._lng <= this.ne._lng || this.sw._lng > this.ne._lng && (latlng._lng >= this.sw._lng || latlng._lng <= this.ne._lng)); }; /** * Extends this bounds to contain the given point. * @param {bmcMaps.LatLng} latlng position to be contained */ bmcMaps.LatLngBounds.prototype.extend = function (latlng) { if (!this.sw && !this.ne) { this.sw = new bmcMaps.LatLng(latlng._lat, latlng._lng); this.ne = new bmcMaps.LatLng(latlng._lat, latlng._lng); return; } if (this.sw._lat > latlng._lat) { this.sw._lat = latlng._lat; } if (this.sw._lng > latlng._lng) { this.sw._lng = latlng._lng; } if (this.ne._lat < latlng._lat) { this.ne._lat = latlng._lat; } if (this.ne._lng < latlng._lng) { this.ne._lng = latlng._lng; } }; /** * Computes the center of this LatLngBounds * @return {bmcMaps.LatLng} */ bmcMaps.LatLngBounds.prototype.getCenter = function () { if (!this.sw && !this.ne) { return undefined; } return new bmcMaps.LatLng((this.sw._lat + this.ne._lat) / 2, (this.sw._lng + this.ne._lng) / 2); }; /** * An overlay that looks like a bubble and is often connected to a marker. This class extends MVCObject. * @param {Object} options Optional properties to set * @constructor */ bmcMaps.InfoWindow = function (options) { this.setValues(options); if (options.map) { options.map = options.map.adapter; } this.adapter = bmcMaps.adapter.infoWindow(options); }; bmcMaps.InfoWindow.prototype = new bmcMaps.MVCObject(); /** * Opens this InfoWindow on the given map. */ bmcMaps.InfoWindow.prototype.open = function () { this.adapter.open(arguments); }; /** * A RichMarker that allows any HTML/DOM to be added to a map and be draggable. This class extends MVCObject. * * @param {Object} options Optional properties to set * @constructor */ bmcMaps.RichMarker = function (options) { this.setValues(options); if (options.map) { options.map = options.map.adapter; } this.adapter = bmcMaps.adapter.richMarker(options); this.content = this.adapter.content; }; bmcMaps.RichMarker.prototype = new bmcMaps.MVCObject(); /** * Set position to display the marker * @param {bmcMaps.LatLng} latlng position The position to set */ bmcMaps.RichMarker.prototype.setPosition = function (latlng) { this.position = latlng; this.adapter.setPosition(latlng); }; /** * Get position of the marker * @return {bmcMaps.LatLng} The position of the marker */ bmcMaps.RichMarker.prototype.getPosition = function () { return this.position; }; /** * Sets the visiblility state of the marker. * * @param {boolean} visible The visiblilty of the marker */ bmcMaps.RichMarker.prototype.setVisible = function (visible) { this.adapter.setVisible(visible); }; /** * Destroy the marker instance */ bmcMaps.RichMarker.prototype.destroy = function () { this.adapter.destroy(); }; /** * A CSS3 InfoBubble. This class extends MVCObject. * @param {Object} options Optional properties to set * @constructor */ bmcMaps.InfoBubble = function (options) { this.setValues(options); if (options.map) { options.map = options.map.adapter; } this.adapter = bmcMaps.adapter.infoBubble(options); }; bmcMaps.InfoBubble.prototype = new bmcMaps.MVCObject(); /** * Open the InfoBubble. * * @param {bmcMaps.Map} map Map to open on. * @param {bmcMaps.MVCObject} anchor Optional anchor to position at. */ bmcMaps.InfoBubble.prototype.open = function (map, anchor) { this.adapter.open(map.adapter, anchor ? anchor.adapter : undefined); }; /** * Close the InfoBubble */ bmcMaps.InfoBubble.prototype.close = function () { this.adapter.close(); }; /** * Get the content of the infobubble. * * @return {string|Node} The marker content. */ bmcMaps.InfoBubble.prototype.getContent = function () { return this.adapter.getContent(); }; /** * Sets the content of the infobubble. * * @param {string|Node} content The content to set. */ bmcMaps.InfoBubble.prototype.setContent = function (content) { this.adapter.setContent(content); }; /** * Creates a MarkerClusterer object with the specified options. This class extends MVCObject. * A MarkerClusterer object manages specified markers and displays groups of markers as a single marker with a number when necessary. * @param {bmcMaps.Map} map Abstract map to attach to. * @param {Array.} markers The markers to be added to the cluster. * @param {Object} options The optional parameters. * @constructor */ bmcMaps.MarkerClusterer = function (map, markers, options) { this.setValues(options); var markersAdapter = []; for (var i = 0; i < markers.length; i++) { markersAdapter.push(markers[i].adapter); } this.adapter = bmcMaps.adapter.markerClusterer(map.adapter, markersAdapter, options); }; bmcMaps.MarkerClusterer.prototype = new bmcMaps.MVCObject(); /** * Removes all clusters and markers from the map and also removes all markers managed by the clusterer. */ bmcMaps.MarkerClusterer.prototype.clearMarkers = function () { this.adapter.clearMarkers(); }; /** * Declar 'bmcMaps' in window scope so that it can be used same way as 'google.maps' namespace */ window.bmcMaps = bmcMaps; })();