Skip to content
Snippets Groups Projects
AbstractCustomMap.js 34.2 KiB
Newer Older
"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;
    // 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 "../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(),
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
    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,
      });
      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);
  });
};

Piotr Gawron's avatar
Piotr Gawron committed
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>&nbsp;",
    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