"use strict"; var Promise = require("bluebird"); var logger = require('../logger'); var functions = require('../Functions'); var AliasInfoWindow = require('./window/AliasInfoWindow'); var AliasSurface = require('./surface/AliasSurface'); var GuiConnector = require('../GuiConnector'); var IdentifiedElement = require('./data/IdentifiedElement'); var ObjectWithListeners = require('../ObjectWithListeners'); var PointInfoWindow = require('./window/PointInfoWindow'); var ReactionInfoWindow = require('./window/ReactionInfoWindow'); var ReactionSurface = require('./surface/ReactionSurface'); var MarkerSurfaceCollection = require('./marker/MarkerSurfaceCollection'); /** * Default constructor. */ function AbstractCustomMap(model, options) { // call super constructor ObjectWithListeners.call(this); if (model === undefined) { throw Error("Model must be defined"); } this.registerListenerType("onZoomChanged"); this.setElement(options.getElement()); this.setConfiguration(options.getConfiguration()); this.setProject(options.getProject()); this.setModel(model); // this array contains elements that are presented on a specific layout (set // of google map object representing lines/areas that are associated with // layout) this.selectedLayoutOverlays = []; // following fields are used in conversion between x,y coordinates and lat,lng // coordinates this.pixelOrigin_ = new google.maps.Point(this.getTileSize() / 2, this.getTileSize() / 2); this.pixelsPerLonDegree_ = this.getTileSize() / 360; this.pixelsPerLonRadian_ = this.getTileSize() / (2 * Math.PI); /* jshint bitwise: false */ this.zoomFactor = this.getPictureSize() / (this.getTileSize() / (1 << this.getMinZoom())); // array with info windows for Marker pointing to aliases this._aliasInfoWindow = []; // array with info windows for Marker pointing to points this._pointInfoWindow = []; // array with info windows for reactions this._reactionInfoWindow = []; this._markerSurfaceCollection = new MarkerSurfaceCollection({map: this}); // this is google.maps.drawing.DrawingManager that will allow user to draw // elements in the client this._drawingManager = null; // this is the polygon that was selected (clicked) last time on the map this._selectedArea = null; // markers should be optimized by default, // however, for testing purpose this function could be turned of by javascript // the other possibility is that it should be off in the touch mode // (bigButtons=true) this._markerOptimization = options.isMarkerOptimization(); this._bigLogo = options.isBigLogo(); this._customTouchInterface = options.isCustomTouchInterface(); this.setDebug(options.isDebug()); } // define super constructor AbstractCustomMap.prototype = Object.create(ObjectWithListeners.prototype); AbstractCustomMap.prototype.constructor = AbstractCustomMap; AbstractCustomMap.prototype.getMarkerSurfaceCollection = function () { return this._markerSurfaceCollection; }; /** * Assigns layouts with images to the google map (which set of images should be * used by google maps api for which layout). * */ AbstractCustomMap.prototype.setupLayouts = function () { var overlays = this.getProject().getDataOverlays(); for (var i = 0; i < overlays.length; i++) { var overlay = overlays[i]; var typeOptions = this.createTypeOptions(overlay); var mapType = new google.maps.ImageMapType(typeOptions); this.getGoogleMap().mapTypes.set(overlay.getId().toString(), mapType); } this.getGoogleMap().setMapTypeId(overlays[0].getId().toString()); }; /** * Creates general google maps options used in this map. * */ AbstractCustomMap.prototype.createMapOptions = function () { var self = this; var model = self.getModel(); var centerPoint = this.getModel().getCenterLatLng(); var zoom = ServerConnector.getSessionData(self.getProject()).getZoomLevel(model); if (zoom === undefined) { zoom = this.getMinZoom(); } // if we have coordinate data stored in session then restore it var point = ServerConnector.getSessionData(self.getProject()).getCenter(model); if (point !== undefined) { centerPoint = self.fromPointToLatLng(point); // if we have default coordinates defined for model } else if (model.getDefaultCenterX() !== undefined && model.getDefaultCenterY() !== undefined && model.getDefaultZoomLevel() !== undefined && model.getDefaultCenterX() !== null && model.getDefaultCenterY() !== null && model.getDefaultZoomLevel() !== null) { centerPoint = self.fromPointToLatLng(new google.maps.Point(model.getDefaultCenterX(), model.getDefaultCenterY())); zoom = model.getDefaultZoomLevel(); } return { center: centerPoint, rotateControl: true, panControl: true, mapTypeControl: false, zoom: zoom, streetViewControl: false, fullscreenControl: false, panControlOptions: { position: google.maps.ControlPosition.LEFT_TOP }, zoomControlOptions: { style: google.maps.ZoomControlStyle.LARGE, position: google.maps.ControlPosition.LEFT_TOP } }; }; /** * Create google maps configuration options object for a specific layout. * * @param param * object with information about layout */ AbstractCustomMap.prototype.createTypeOptions = function (param) { var self = this; return { // this is a function that will retrieve valid url to png images for // tiles on different zoom levels getTileUrl: function (coord, zoom) { // we have 1 tile on self.getConfiguration().MIN_ZOOM and // therefore must limit tails according to this /* jshint bitwise: false */ var maxTileRange = 1 << (zoom - self.getMinZoom()); var maxTileXRange = maxTileRange; var maxTileYRange = maxTileRange; var width = self.getModel().getWidth(); var height = self.getModel().getHeight(); if (width > height) { maxTileYRange = height / width * maxTileRange; } else if (width < height) { maxTileXRange = width / height * maxTileRange; } if (coord.y < 0 || coord.y >= maxTileYRange || coord.x < 0 || coord.x >= maxTileXRange) { return null; } return "../map_images/" + self.getProject().getDirectory() + "/" + param.getImagesDirectory(self.getId()) + "/" + zoom + "/" + coord.x + "/" + coord.y + ".PNG"; }, tileSize: new google.maps.Size(self.getTileSize(), self.getTileSize()), maxZoom: self.getMaxZoom(), minZoom: self.getMinZoom(), radius: 360, name: param.name }; }; /** * Sets maximum zoom level on google map. * */ AbstractCustomMap.prototype.setMaxZoomLevel = function () { this.getGoogleMap().setZoom(this.getMaxZoom()); }; /** * Returns mouse coordinate on the map in lat,lng system. * */ AbstractCustomMap.prototype.getMouseLatLng = function () { // this method is tricky, the main problem is how to map mouse coordinate to // google map // to do that we need a point of reference in both systems (x,y and lat,lng) // this will be center of the map that is currently visible // next, we will have to find distance from this point in x,y coordinates and // transform it to lat,lng var self = this; // center point visible on the map var latLngCoordinates = self.getGoogleMap().getCenter(); var point = self.fromLatLngToPoint(latLngCoordinates); // this is magic :) // find offset of the div where google map is located related to top left // corner of the browser var el = self.getGoogleMap().getDiv(); for (var lx = 0, ly = 0; el !== null && el !== undefined; lx += el.offsetLeft, ly += el.offsetTop, el = el.offsetParent) { } var offset = { x: lx, y: ly }; var center = { x: self.getGoogleMap().getDiv().offsetWidth / 2, y: self.getGoogleMap().getDiv().offsetHeight / 2 }; // and now find how far from center point we are (in pixels) var relativeDist = { x: (GuiConnector.xPos - offset.x - center.x), y: (GuiConnector.yPos - offset.y - center.y) }; // transform pixels into x,y distance var pointDist = self.fromPixelsToPoint(relativeDist, self.getGoogleMap().getZoom()); // now we have offset in x,y and center point on the map in x,y, so we have // final position in x,y var newCoordinates = new google.maps.Point(point.x + pointDist.x, point.y + pointDist.y); // change it to lat,lng var latLngResult = self.fromPointToLatLng(newCoordinates); return latLngResult; }; /** * Transform distance (coordinates) in pixels into x,y distance on the map. * * @param pixels * x,y distance in pixels * @param zoomLevel * at which zoom level this pixels where measured * */ AbstractCustomMap.prototype.fromPixelsToPoint = function (pixels, zoomLevel) { var zoomScale = this.getPictureSize() / (Math.pow(2, zoomLevel - this.getMinZoom()) * this.getTileSize()); var pointX = pixels.x * zoomScale; var pointY = pixels.y * zoomScale; return new google.maps.Point(pointX, pointY); }; /** * Transforms coordinates on the map from google.maps.LatLng to * google.maps.Point * * @param latLng * in lat,lng format * @param coordinates in x,y format * */ AbstractCustomMap.prototype.fromLatLngToPoint = function (latLng) { var me = this; var point = new google.maps.Point(0, 0); var origin = me.pixelOrigin_; point.x = origin.x + latLng.lng() * me.pixelsPerLonDegree_; // Truncating to 0.9999 effectively limits latitude to 89.189. This is // about a third of a tile past the edge of the world tile. var siny = functions.bound(Math.sin(functions.degreesToRadians(latLng.lat())), -0.9999, 0.9999); point.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) * -me.pixelsPerLonRadian_; // rescale the point (all computations are done assuming that we work on // TILE_SIZE square) point.x *= me.zoomFactor; point.y *= me.zoomFactor; return point; }; /** * Transforms coordinates on the map from google.maps.Point to * google.maps.LatLng * * @param point * coordinates in standard x,y format * @return coordinates in lat,lng format */ AbstractCustomMap.prototype.fromPointToLatLng = function (point) { var me = this; // rescale the point (all computations are done assuming that we work on // TILE_SIZE square) var p = new google.maps.Point(point.x / me.zoomFactor, point.y / me.zoomFactor); var origin = me.pixelOrigin_; var lng = (p.x - origin.x) / me.pixelsPerLonDegree_; var latRadians = (p.y - origin.y) / -me.pixelsPerLonRadian_; var lat = functions.radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2); return new google.maps.LatLng(lat, lng); }; /** * Transforms google.maps.LatLng to tile coordinate (for instance on which tile * mouse clicked). * * * @param latLng * coordinates in latlng format * @param z * zoom level at which we want to find coordinates of tile * @return coordinates of a tile */ AbstractCustomMap.prototype.latLngToTile = function (latLng, z) { var worldCoordinate = this.fromLatLngToPoint(latLng); var pixelCoordinate = new google.maps.Point(worldCoordinate.x * Math.pow(2, z), worldCoordinate.y * Math.pow(2, z)); var tileCoordinate = new google.maps.Point(Math.floor(pixelCoordinate.x / this.getTileSize()), Math .floor(pixelCoordinate.y / this.getTileSize())); return tileCoordinate; }; /** * Register events responsible for click events */ AbstractCustomMap.prototype.registerMapClickEvents = function () { // find top map (CustomMap) // var customMap = this.getTopMap(); var self = this; // search event google.maps.event.addListener(this.getGoogleMap(), 'click', function (mouseEvent) { var point = self.fromLatLngToPoint(mouseEvent.latLng); var searchDb = customMap.getOverlayByName('search'); if (searchDb !== undefined) { return searchDb.searchByCoordinates({ modelId: self.getModel().getId(), coordinates: point, zoom: self.getGoogleMap().getZoom() }).then(null, GuiConnector.alert); } else { logger.warn("Search is impossible because search db is not present"); } }); // select last clicked map google.maps.event.addListener(this.getGoogleMap(), 'click', function (mouseEvent) { customMap.setActiveSubmapId(self.getId()); customMap.setActiveSubmapClickCoordinates(self.fromLatLngToPoint(mouseEvent.latLng)); }); // select last clicked map google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function (mouseEvent) { customMap.setActiveSubmapId(self.getId()); customMap.setActiveSubmapClickCoordinates(self.fromLatLngToPoint(mouseEvent.latLng)); }); // prepare for image export google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function () { var bounds = self.getGoogleMap().getBounds(); var polygon = ""; var ne = bounds.getNorthEast(); var sw = bounds.getSouthWest(); var westLng = sw.lng(); var eastLng = ne.lng(); if (westLng > 0) { westLng = -180; } if (eastLng - westLng > 90) { eastLng = -90; } else if (eastLng > -90) { eastLng = -90; } polygon += ne.lat() + "," + westLng + ";"; polygon += ne.lat() + "," + eastLng + ";"; polygon += sw.lat() + "," + eastLng + ";"; polygon += sw.lat() + "," + westLng + ";"; self.getTopMap().setSelectedPolygon(polygon); }); // context menu event google.maps.event.addListener(this.getGoogleMap(), 'rightclick', function () { self.getTopMap().getContextMenu().open(GuiConnector.xPos, GuiConnector.yPos, new Date().getTime()); }); }; /** * It toggle drawing manager used on the map: if it's on then it will turn it * off, if it's off it will turn it on * */ AbstractCustomMap.prototype._turnOnOffDrawing = function () { if (this.isDrawingOn()) { this.turnOffDrawing(); } else { this.turnOnDrawing(); } }; /** * Checks if the drawing manager for the map is on. * */ AbstractCustomMap.prototype.isDrawingOn = function () { return this._drawingManager !== null; }; /** * Turns on drawing manager on the map. */ AbstractCustomMap.prototype.turnOnDrawing = function () { if (this.isDrawingOn()) { logger.warn("Trying to turn on drawing manager, but it is already available."); return; } var customMap = this.getTopMap(); var self = this; this._drawingManager = new google.maps.drawing.DrawingManager({ drawingMode: google.maps.drawing.OverlayType.MARKER, drawingControl: true, drawingControlOptions: { position: google.maps.ControlPosition.TOP_CENTER, drawingModes: [google.maps.drawing.OverlayType.POLYGON] }, markerOptions: { icon: 'images/beachflag.png' }, circleOptions: { fillColor: '#ffff00', fillOpacity: 1, strokeWeight: 5, clickable: false, editable: true, zIndex: 1 } }); this._drawingManager.setMap(this.getGoogleMap()); this._drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON); google.maps.event.addListener(this._drawingManager, 'overlaycomplete', function (e) { if (e.type !== google.maps.drawing.OverlayType.MARKER) { // Switch back to non-drawing mode after drawing a shape. self._drawingManager.setDrawingMode(null); // Add an event listener that selects the newly-drawn shape when the // user mouses down on it. var newShape = e.overlay; newShape.type = e.type; google.maps.event.addListener(newShape, 'rightclick', function (e) { // select map that was clicked customMap.setActiveSubmapId(self.getId()); self.setSelectedArea(newShape); newShape.position = e.latLng; self.getTopMap().setSelectedPolygon(self.areaToString(newShape)); self.getTopMap().getSelectionContextMenu().open(GuiConnector.xPos, GuiConnector.yPos, new Date().getTime()); }); } }); }; /** * Sets selectedArea on this map. * */ AbstractCustomMap.prototype.setSelectedArea = function (area) { this._selectedArea = area; }; /** * Returns selectedArea on this map. * */ AbstractCustomMap.prototype.getSelectedArea = function () { return this._selectedArea; }; /** * Transforms google.maps.Polygon into string with coordinates. * */ AbstractCustomMap.prototype.areaToString = function (area) { var len = area.getPath().length; var path = area.getPath(); var res = ""; for (var i = 0; i < len; i++) { var latLng = path.getAt(i); res += latLng.lat() + "," + latLng.lng() + ";"; } return res; }; /** * Removes selected area (polygon) from the map. */ AbstractCustomMap.prototype._removeSelection = function () { if (this._selectedArea) { this._selectedArea.setMap(null); this._selectedArea = null; } else { logger.warn("Cannot remove selected area. No area was selected"); } }; /** * Turns off drawing manager on the map. */ AbstractCustomMap.prototype.turnOffDrawing = function () { if (this.isDrawingOn()) { this._drawingManager.setMap(null); this._drawingManager = null; } else { logger.warn("Trying to turn off drawing manager, but it is not available."); } }; /** * Returns top map. TODO implementation of this function should be probably * moved to Submap and CustomMap classes and here only abstract function * definition * * @returns {CustomMap} */ AbstractCustomMap.prototype.getTopMap = function () { logger.fatal("Not implemented"); }; /** * Method that should be called when number of layouts to visualize changed to * modify boundaries of the elements to visualize. When few layouts are * visualized at the same time then index contains information where this new * layout is placed in the list (starting from 0) and length contains * information how many layouts we visualize in total. * * @param layoutId * identifier of a layout * @param index * when visualizing more than one layout at the same time index * contains information at which position in the list this layout is * placed * @param length * number of layouts that are currently visualized */ AbstractCustomMap.prototype._resizeSelectedDataOverlay = function (layoutId, index, length) { var self = this; return new Promise(function (resolve) { // if map is not initialized then don't perform this operation if (!self.initialized) { logger.debug("Model " + self.getId() + " not initialized"); resolve(); } logger.debug("Resize layout: " + layoutId); // start ratio var startX = index * (1.0 / length); // end ratio var endX = (index + 1) * (1.0 / length); for (var i = 0; i < self.selectedLayoutOverlays[layoutId].length; i++) { self.selectedLayoutOverlays[layoutId][i].setBoundsForAlias(startX, endX); } resolve(); }); }; /** * Shows all elements from a given layout. When few layouts are visualized at * the same time then index contains information where this new layout is placed * in the list (starting from 0) and length contains information how many * layouts we visualize in total. * * @param layoutId * identifier of a layout * @param index * when visualizing more than one layout at the same time index * contains information at which position in the list this layout is * placed * @param length * number of layouts that are currently visualized */ AbstractCustomMap.prototype._showSelectedDataOverlay = function (layoutId, index, length) { var self = this; // if map is not initialized then don't perform this operation return new Promise(function (resolve, reject) { if (!self.initialized) { logger.debug("Model " + self.getId() + " not initialized"); resolve(); return; } else { logger.debug("Showing model " + self.getId()); } self.selectedLayoutOverlays[layoutId] = []; // start ratio var startX = index * (1.0 / length); // end ratio var endX = (index + 1) * (1.0 / length); var layoutAliases; var layoutReactions; return self.getProject().getDataOverlayById(layoutId).then(function (layout) { layoutAliases = layout.getAliases(); layoutReactions = layout.getReactions(); var identifiedElements = []; var i; for (i = 0; i < layoutAliases.length; i++) { if (layoutAliases[i].getModelId() === self.getId()) { identifiedElements.push(new IdentifiedElement(layoutAliases[i])); } } var reactionIds = []; for (i = 0; i < layoutReactions.length; i++) { if (layoutReactions[i].getModelId() === self.getId()) { identifiedElements.push(new IdentifiedElement(layoutReactions[i])); } } return self.getModel().getByIdentifiedElements(identifiedElements, false); }).then(function () { return Promise.each(layoutAliases, function (layoutAlias) { if (layoutAlias.getModelId() === self.getId()) { var surface; var element; return self.getModel().getAliasById(layoutAlias.getId()).then(function (aliasData) { element = new IdentifiedElement(aliasData); return AliasSurface.create({ overlayAlias: layoutAlias, alias: aliasData, map: self, startX: startX, endX: endX, onClick: [function () { return self.getTopMap().getOverlayByName("search").searchByTarget(element); }, function () { return self.getTopMap().callListeners("onBioEntityClick", element); }] }); }).then(function (result) { surface = result; self.selectedLayoutOverlays[layoutId].push(surface); }); } }); }).then(function () { return Promise.each(layoutReactions, function (layoutReaction) { if (layoutReaction.getModelId() === self.getId()) { return self.getModel().getReactionById(layoutReaction.getId()).then(function (reactionData) { var surface; var element = new IdentifiedElement(reactionData); return ReactionSurface.create({ layoutReaction: layoutReaction, reaction: reactionData, map: self, onClick: [function () { return self.getTopMap().getOverlayByName("search").searchByTarget(element); }, function () { return self.getTopMap().callListeners("onBioEntityClick", element); }], customized: (length === 1) }).then(function (result) { surface = result; self.selectedLayoutOverlays[layoutId].push(surface); surface.show(); }); }); } }); }).then(function () { resolve(); }).then(null, reject); }); }; /** * Hides all elements from layout. * * @param layoutId * identifier of a layout */ AbstractCustomMap.prototype._hideSelectedLayout = function (layoutId) { // if map is not initialized then don't perform this operation if (!this.initialized) { logger.debug("Model " + this.getId() + " not initialized"); return; } for (var i = 0; i < this.selectedLayoutOverlays[layoutId].length; i++) { this.selectedLayoutOverlays[layoutId][i].setMap(null); } this.selectedLayoutOverlays[layoutId] = []; }; /** * Opens {@link AliasInfoWindow} for given alias. * * @param aliasId * identifier of the alias */ AbstractCustomMap.prototype._openInfoWindowForAlias = function (alias, googleMarker) { var self = this; var infoWindow = this.getAliasInfoWindowById(alias.getId()); if (infoWindow !== null && infoWindow !== undefined) { if (!infoWindow.isOpened()) { infoWindow.open(googleMarker); } else { logger.warn("Info window for alias: " + alias.getId() + " is already opened"); } return Promise.resolve(infoWindow); } else { self._aliasInfoWindow[alias.getId()] = new AliasInfoWindow({ alias: alias, map: self, marker: googleMarker, }); return self._aliasInfoWindow[alias.getId()].init(); } }; /** * Returns promise of a list of {@link LayoutAlias} information for a given * {@link Alias} in all currently visualized layouts. * * @param aliasId * identifier of the {@link Alias} * @returns promise of an {Array} with list of {@link LayoutAlias} information * for a given {@link Alias} in all currently visualized layouts */ AbstractCustomMap.prototype.getAliasVisibleLayoutsData = function (aliasId) { var self = this; return self.getTopMap().getVisibleDataOverlays().then(function (visibleDataOverlays) { var result = []; for (var i = 0; i < visibleDataOverlays.length; i++) { var layout = visibleDataOverlays[i]; result.push(layout.getFullAliasById(aliasId)); } return Promise.all(result); }); }; /** * Refresh content of all {@link AliasInfoWindow} in this map. */ AbstractCustomMap.prototype._refreshInfoWindows = function () { var promises = []; for (var key in this._pointInfoWindow) { if (this._pointInfoWindow.hasOwnProperty(key)) { if (this._pointInfoWindow[key].isOpened()) { promises.push(this._pointInfoWindow[key].update()); } } } for (var aliasKey in this._aliasInfoWindow) { if (this._aliasInfoWindow.hasOwnProperty(aliasKey)) { if (this._aliasInfoWindow[aliasKey].isOpened()) { promises.push(this._aliasInfoWindow[aliasKey].update()); } } } return Promise.all(promises); }; /** * Opens {@link ReactionInfoWindow} for given reaction identifier. * * @param reactionId * reaction identifier */ AbstractCustomMap.prototype._openInfoWindowForReaction = function (reaction, googleMarker) { var infoWindow = this.getReactionInfoWindowById(reaction.getId()); var self = this; if (infoWindow !== null && infoWindow !== undefined) { if (!infoWindow.isOpened()) { infoWindow.open(googleMarker); } else { logger.warn("Info window for reaction: " + reaction.getId() + " is already opened"); } return Promise.resolve(infoWindow); } else { return self.getModel().getReactionById(reaction.getId()).then(function (reaction) { infoWindow = new ReactionInfoWindow({ reaction: reaction, map: self, marker: googleMarker }); self._reactionInfoWindow[reaction.getId()] = infoWindow; return infoWindow.init(); }).then(function () { return infoWindow.open(); }).then(function () { return infoWindow; }); } }; AbstractCustomMap.prototype._openInfoWindowForPoint = function (pointData, googleMarker) { var self = this; var infoWindow = this.getPointInfoWindowById(pointData.getId()); if (infoWindow !== null && infoWindow !== undefined) { if (!infoWindow.isOpened()) { infoWindow.open(googleMarker); } else { logger.warn("Info window for point: " + pointData.getId() + " is already opened"); } } else { infoWindow = new PointInfoWindow({ point: pointData, map: self, marker: googleMarker, }); this._pointInfoWindow[pointData.getId()] = infoWindow; } return Promise.resolve(infoWindow); }; /** * Opens {@link AbstractInfoWindow} for a marker. * * @param marker * marker for which we are opening window */ AbstractCustomMap.prototype.returnInfoWindowForIdentifiedElement = function (element) { var markerId = element.getId(); if (element.getType() === "ALIAS") { return this.getAliasInfoWindowById(markerId); } else if (element.getType() === "POINT") { return this.getPointInfoWindowById(markerId); } else if (element.getType() === "REACTION") { return this.getReactionInfoWindowById(markerId); } else { throw new Error("Unknown marker type: ", element); } }; /** * Returns identifier. * * @returns identifier */ AbstractCustomMap.prototype.getId = function () { return this.getModel().getId(); }; AbstractCustomMap.prototype.getConfiguration = function () { return this._configuration; }; AbstractCustomMap.prototype.setConfiguration = function (configuration) { this._configuration = configuration; }; AbstractCustomMap.prototype._createMapChangedCallbacks = function () { var self = this; var sessionData = ServerConnector.getSessionData(self.getTopMap().getProject()); // listener for changing zoom level google.maps.event.addListener(this.getGoogleMap(), 'zoom_changed', function () { sessionData.setZoomLevel(self.getModel(), self.getGoogleMap().getZoom()); }); google.maps.event.addListener(this.getGoogleMap(), 'zoom_changed', function () { return self.callListeners("onZoomChanged", self.getGoogleMap().getZoom()); }); // listener for changing location of the map (moving left/right/top/bottom google.maps.event.addListener(this.getGoogleMap(), 'center_changed', function () { var coord = self.getGoogleMap().getCenter(); var point = self.fromLatLngToPoint(coord); sessionData.setCenter(self.getModel(), point); }); }; AbstractCustomMap.prototype.addCenterButton = function () { var self = this; var centerDiv = functions.createElement({ type: "div", style: "padding:5px" }); var centerButton = functions.createElement({ type: "a", content: "<i class='fa fa-crosshairs' style='font-size:24px;color:grey'></i> ", title: "center map", href: "#", onclick: function () { var bounds = new google.maps.LatLngBounds(); bounds.extend(self.getTopLeftLatLng()); bounds.extend(self.getBottomRightLatLng()); self.getGoogleMap().fitBounds(bounds); return false; }, xss: false }); centerDiv.appendChild(centerButton); self.getGoogleMap().controls[google.maps.ControlPosition.RIGHT_TOP].push(centerDiv); }; /** * Returns {@link ReactionInfoWindow} for given reaction identifier * * @param reactionId * reaction identifier * @returns {@link ReactionInfoWindow} for given reaction identifier */ AbstractCustomMap.prototype.getReactionInfoWindowById = function (reactionId) { return this._reactionInfoWindow[reactionId]; }; /** * Returns {@link AliasInfoWindow} for given alias identifier * * @param aliasId * alias identifier * @returns {@link AliasInfoWindow} for given alias identifier */ AbstractCustomMap.prototype.getAliasInfoWindowById = function (aliasId) { return this._aliasInfoWindow[aliasId]; }; /** * Returns {@link PointInfoWindow} for given point identifier * * @param pointId * point identifier * @returns {@link PointInfoWindow} for given point identifier */ AbstractCustomMap.prototype.getPointInfoWindowById = function (pointId) { return this._pointInfoWindow[pointId]; }; AbstractCustomMap.prototype.getModel = function () { return this._model; }; AbstractCustomMap.prototype.setModel = function (model) { this._model = model; }; AbstractCustomMap.prototype.getTileSize = function () { return this.getModel().getTileSize(); }; AbstractCustomMap.prototype.getMinZoom = function () { return this.getModel().getMinZoom(); }; AbstractCustomMap.prototype.getMaxZoom = function () { return this.getModel().getMaxZoom(); }; AbstractCustomMap.prototype.getPictureSize = function () { return this.getModel().getPictureSize(); }; /** * Returns array containing elements that are presented on a specific layout * (set of google map objects representing lines/areas that are associated with * layout). * * @returns {Array} containing elements that are presented on a specific * layout (set of google map objects representing lines/areas that are * associated with layout). */ AbstractCustomMap.prototype.getSelectedLayoutOverlays = function () { return this.selectedLayoutOverlays; }; /** * Returns google.maps.map object used to representing data. * * @returns google.maps.map object used to representing data */ AbstractCustomMap.prototype.getGoogleMap = function () { return this._map; }; /** * Sets google.maps.map object used to representing data. * */ AbstractCustomMap.prototype.setGoogleMap = function (googleMap) { this._map = googleMap; }; AbstractCustomMap.prototype.isMarkerOptimization = function () { return this._markerOptimization; }; AbstractCustomMap.prototype.isBigLogo = function () { return this._bigLogo; }; AbstractCustomMap.prototype.isCustomTouchInterface = function () { return this._customTouchInterface; }; AbstractCustomMap.prototype.setDebug = function (debug) { if (debug !== undefined) { if (typeof debug !== "boolean") { logger.warn("param must be boolean"); } this._debug = debug; } }; AbstractCustomMap.prototype.isDebug = function () { return this._debug === true; }; AbstractCustomMap.prototype.getTopLeftLatLng = function () { return this.getModel().getTopLeftLatLng(); }; AbstractCustomMap.prototype.getBottomRightLatLng = function () { return this.getModel().getBottomRightLatLng(); }; AbstractCustomMap.prototype.getElement = function () { return this._element; }; AbstractCustomMap.prototype.setElement = function (element) { this._element = element; }; /** * Sets center point for google maps. * * @param coordinates * new center point on map */ AbstractCustomMap.prototype.setCenter = function (coordinates) { if (coordinates instanceof google.maps.Point) { coordinates = this.fromPointToLatLng(coordinates); } if (this.initialized) { return Promise.resolve(this.getGoogleMap().setCenter(coordinates)); } else { logger.warn("cannot center map that is not opened yet"); return Promise.resolve(); } }; /** * Sets zoom level for google maps. * * @param mapIdentifier * id of the model for which we change zoom level * @param zoom * new zoom level on map */ AbstractCustomMap.prototype.setZoom = function (zoom) { if (this.initialized) { return Promise.resolve(this.getGoogleMap().setZoom(zoom)); } else { logger.warn("cannot center map that is not opened yet"); return Promise.resolve(); } }; AbstractCustomMap.prototype.fitBounds = function (markers) { var self = this; var map = self.getGoogleMap(); if (map !== undefined) { var bounds = new google.maps.LatLngBounds(); for (var i = 0; i < markers.length; i++) { var marker = markers[i]; if (marker.getModelId() === self.getId()) { var markerBounds = marker.getBounds(); bounds.extend(markerBounds.getNorthEast()); bounds.extend(markerBounds.getSouthWest()); } } if (!bounds.isEmpty()) { map.fitBounds(bounds); } } }; AbstractCustomMap.prototype.setProject = function (project) { this._project = project; }; AbstractCustomMap.prototype.getProject = function () { return this._project; }; module.exports = AbstractCustomMap;