Google Maps API - Draggable Interpolated Markers

From NoskeWiki
Jump to: navigation, search
Markers you can drag.

About

NOTE: This page is a daughter page of: Google Maps API


This piece of code renders a sequence of markers on the map, then allows you to drag them to create key markers, used to linearly interpolate all the markers in between. It makes use of classes, and also features a multiple selection HTML element which can be used to select multiple pins and change their type between key and interpolated.


Draggable Interpolated Markers

draggable_interpolated_markers.js

/**
 * @fileoverview Sets up map with an array of markers
 * which can be linearly interpolated by dragging them.
 */
 
 
// GLOBAL VARS:
 
var svm = {};
 
/**
 * Set to map itself.
 * @type {google.maps.Map}
 */
var map = null;
 
/**
 * Main pin set (containing array of draggable pins).
 * @type {svm.DraggablePinSet}
 */
var pinSet = null;
 
 
// EXPECTED DIV IDS:
 
var MAP_DIV_ID = 'map-canvas';
var MULTI_SELECT_DIV_ID = 'marker-select';
var SELECTED_TXT_DIV_ID = 'marker-state';
var KEY_CHECKBOX_DIV_ID = 'marker-state-checkbox';
 
 
// CLASSES:
 
 
/**
 * This class/constructor represents a pin on the map which starts at
 * one location, but can be dragged to another.
 * @param {svm.DraggablePinSet} parentSet Parent set containing this marker,
 *     null if none.
 * @param {number} idxInSet Index inside the parentSet.
 * @param {string} label Label for marker.
 * @param {number} initialLat Initial latitute float value.
 * @param {number} initialLng Initial longtidude float value.
 * @constructor
 */
svm.DraggablePin = function(parentSet, idxInSet, label,
    initialLat, initialLng) {
 
  // DATA:
 
  /**
   * Parent array set.
   * @type {svm.DraggablePinSet}
   */
   this.parentSet = null;
 
  /**
   * Index in array.
   * @type {number}
   */
   this.idxInSet = null;
 
  /**
   * Label given to this marker.
   * @type {String}
   */
  this.label = '';
 
  /**
   * Inital location of pin. Note, that you may not actually want to
   * store this, but could be useful if you wanted to see or measure
   * offset, or create a 'reset' option where pin could jump back to
   * original position.
   * @type {google.maps.LatLng}
   */
  this.intialLatLng = null;
 
  /**
   * New location of pin.
   * @type {google.maps.LatLng}
   */
  this.newLatLng = null;
 
  /**
   * If true, location has been adjusted or confirmed by the
   * user, and so it will not be interpolated.
   * @type {boolean}
   */
  this.isKeyMarker = false;
 
  /**
   * Is this marker currently selected by user.
   * @type {boolean}
   */
  this.isSelected = false;
 
 
  // ELEMENT:
 
  /**
   * Marker for this pin.
   * @type {google.maps.Marker}
   */
  this.marker = null;
 
  /**
   * Polygon single line segment connecting to the next point in sequence.
   * @type {google.maps.Polygon}
   */
  this.polygonToPrev = null;
 
  // SETUP:
 
  this.setWithInitialVals(parentSet, idxInSet, label, initialLat, initialLng);
};
 
/**
 * An icon representing a key pin.
 * @type {google.maps.Icon}
 */
svm.DraggablePin.KEY_ICON = {
    url: 'http://andrewnoske.com/hidden/google_maps/icons/blue_dot_6x6.png',
    size: new google.maps.Size(18, 18),   // Icon size.
    origin: new google.maps.Point(0, 0),  // Icon origin.
    anchor: new google.maps.Point(9, 9)   // Anchor in middle.
};
 
/**
 * An icon representing a currently selected key pin.
 * @type {google.maps.Icon}
 */
svm.DraggablePin.KEY_ICON_SELECTED = {
    url: 'http://andrewnoske.com/hidden/google_maps/icons/blue_dot_8x8.png',
    size: new google.maps.Size(18, 18),   // Icon size.
    origin: new google.maps.Point(0, 0),  // Icon origin.
    anchor: new google.maps.Point(9, 9)   // Anchor in middle.
};
 
/**
 * An icon representing a key pin.
 * @type {google.maps.Icon}
 */
svm.DraggablePin.INTERPOLATED_ICON = {
    url: 'http://andrewnoske.com/hidden/google_maps/icons/yellow_dot_6x6.png',
    size: new google.maps.Size(18, 18),   // Icon size.
    origin: new google.maps.Point(0, 0),  // Icon origin.
    anchor: new google.maps.Point(9, 9)   // Anchor in middle.
};
 
/**
 * An icon representing a currently selected key pin.
 * @type {google.maps.Icon}
 */
svm.DraggablePin.INTERPOLATED_ICON_SELECTED = {
    url: 'http://andrewnoske.com/hidden/google_maps/icons/yellow_dot_8x8.png',
    size: new google.maps.Size(18, 18),   // Icon size.
    origin: new google.maps.Point(0, 0),  // Icon origin.
    anchor: new google.maps.Point(9, 9)   // Anchor in middle.
};
 
/**
 * Sets the basic values...
 * @param {svm.DraggablePinSet} parentSet Parent set containing this marker,
 *     null if none.
 * @param {number} idxInSet Index inside the parentSet.
 * @param {string} label Label for marker.
 * @param {number} initialLat Initial latitute float value.
 * @param {number} initialLng Initial longtidude float value.
 */
svm.DraggablePin.prototype.setWithInitialVals = function(
    parentSet, idxInSet, label, initialLat, initialLng) {
  this.parentSet = parentSet;
  this.idxInSet = idxInSet;
  this.label = label;
  this.initialLatLng = new google.maps.LatLng(initialLat, initialLng);
  this.newLatLng = this.initialLatLng;
 
  // Initialize marker:
  this.marker = new google.maps.Marker({
      position: this.newLatLng,
      title: this.label,
      map: map,
      draggable: true
  });
  this.updateMarkerIcon();
 
  // Initialize polgon lines:
  this.polygonToPrev = new google.maps.Polygon({
    paths: [],
    strokeColor: '#0000FF',
    strokeOpacity: 0.6,
    strokeWeight: 2,
    map: map,
  });
 
  // Add dragging event listeners.
  var self = this;
  google.maps.event.addListener(this.marker, 'click', function() {
      self.markerClick();
  });
 
  google.maps.event.addListener(this.marker, 'dragstart', function() {
      self.markerDragStart();
  });
 
  google.maps.event.addListener(this.marker, 'drag', function() {
      self.markerDrag();
  });
 
  google.maps.event.addListener(this.marker, 'dragend', function() {
      self.markerDragEnd();
  });
};
 
 
/**
 * Update markers location to 'newLatLng'.
 * @type {google.maps.LatLng} newLatLng New position.
 * @type {boolean} updateMarker If true, update marker on map.
 */
svm.DraggablePin.prototype.setNewLatLng = function(newLatLng, updateMarker) {
  this.newLatLng = newLatLng;
  if (updateMarker) {
    this.marker.setPosition(this.newLatLng);
  }
};
 
 
/**
 * Handler for marker click.
 */
svm.DraggablePin.prototype.markerClick = function() {
  this.parentSet.clearSelected(this);
  this.setSelected(true);
  updateMultipleSelectBasedOnMap(this.idxInSet);
};
 
 
/**
 * Handler for marker drag starting.
 */
svm.DraggablePin.prototype.markerDragStart = function() {
  this.setKey(true);
  updateMarkerStateTxtBasedOnMap();
};
 
/**
 * Handler for marker drag.
 */
svm.DraggablePin.prototype.markerDrag = function() {
  this.newLatLng = this.marker.position;
};
 
/**
 * Handler for marker drag ending.
 */
svm.DraggablePin.prototype.markerDragEnd = function() {
  this.newLatLng = this.marker.position;
  this.parentSet.refreshInterpolation();
};
 
/**
 * Sets if marker is a key for interpolation.
 * @type {boolean} isKey Is this pin a key for interpolation.
 */
svm.DraggablePin.prototype.setKey = function(isKey) {
  this.isKeyMarker = isKey;
  this.updateMarkerIcon();
};
 
 
/**
 * Sets if marker is selected by the user.
 * @type {boolean} isSelected Is this pin selected by user.
 */
svm.DraggablePin.prototype.setSelected = function(isSelected) {
  this.isSelected = isSelected;
  this.updateMarkerIcon();
};
 
 
/**
 * Updates the marker icon.
 * @type {boolean} isKey Is this pin a key for interpolation.
 */
svm.DraggablePin.prototype.updateMarkerIcon = function() {
  if (this.isKeyMarker) {
    this.marker.setIcon((this.isSelected) ?
                        svm.DraggablePin.KEY_ICON_SELECTED :
                        svm.DraggablePin.KEY_ICON);
  } else {
    this.marker.setIcon((this.isSelected) ?
                        svm.DraggablePin.INTERPOLATED_ICON_SELECTED :
                        svm.DraggablePin.INTERPOLATED_ICON);
  }
};
 
 
/**
 * Returns true if this is a key marker.
 * @return {boolean}
 */
svm.DraggablePin.prototype.isKey = function() {
  return this.isKeyMarker;
};
 
 
/**
 * Sets a line connecting to the previous marker.
 * @param {google.maps.LatLng} prevLatLng Location previous marker.
 */
svm.DraggablePin.prototype.setPolygonToPrevDest = function(prevLatLng) {
  var polygonCoords = [];
  polygonCoords.push(prevLatLng);
  polygonCoords.push(this.newLatLng);
  this.polygonToPrev.setPaths(polygonCoords);
};
 
 
/**
 * This class/constructor represents a set if draggable pins which
 * can be interpolated.
 * @constructor
 */
svm.DraggablePinSet = function() {
  /**
   * Array of pins.
   * @type {svm.DraggablePin}
   */
  this.pins = [];
};
 
 
/**
 * Append a new draggable pin to the array.
 * @param {number} idxInSet Index inside the parentSet.
 * @param {string} label Label for marker.
 * @param {number} initialLat Initial latitute float value.
 * @param {number} initialLng Initial longtidude float value.
 */
svm.DraggablePinSet.prototype.appendDraggablePin = function(
    idxInSet, label, initialLat, initialLng) {
  this.pins.push(new svm.DraggablePin(this, idxInSet, label,
      initialLat, initialLng));
};
 
/**
 * Refreshes interpolation.
 */
svm.DraggablePinSet.prototype.refreshInterpolation = function() {
  var lastKeyPinIdx = -1;
  for (var i = 0; i < this.pins.length; i++) {
     currPin = this.pins[i];
     if (currPin.isKey()) {
        if (lastKeyPinIdx >= 0) {
          this.interpolatePins(lastKeyPinIdx, i);
        }
        lastKeyPinIdx = i;
     }
  }
  this.updateLines();
};
 
/**
 * Linearly interpolates the pins between the given index values.
 * @param {number} startIdx Start index in array (integer).
 * @param {number} endIdx End index in array (integer).
 */
svm.DraggablePinSet.prototype.interpolatePins = function(startIdx, endIdx) {
  if (!this.isValidIdx(startIdx) || !this.isValidIdx(endIdx)) {
    return;
  }
  startLatLng = this.pins[startIdx].newLatLng;
  endLatLng = this.pins[endIdx].newLatLng;
  dist = endIdx - startIdx;
  for (var i = startIdx + 1; i < endIdx; i++) {
    var fract = (i - startIdx) / dist;
    // From '&libraries=geometry' library:
    var newLatLng = google.maps.geometry.spherical.interpolate(
        startLatLng, endLatLng, fract);
    this.pins[i].setNewLatLng(newLatLng, true);
  }
};
 
/**
 * Updates the line running through all markers.
 */
svm.DraggablePinSet.prototype.updateLines = function() {
  for (var i = 1; i < this.pins.length; i++) {
    this.pins[i].setPolygonToPrevDest(this.pins[i - 1].newLatLng);
  }
};
 
/**
 * Sets all markers to unselected.
 */
svm.DraggablePinSet.prototype.clearSelected = function() {
  for (var i = 1; i < this.pins.length; i++) {
    this.pins[i].setSelected(false);
  }
};
 
/**
 * Sets the given marker to selected.
 * @param {number} idx Index (integer) in pins array.
 * @param {boolean} isSelected Is pin selected.
 */
svm.DraggablePinSet.prototype.setSelected = function(idx, isSelected) {
  if (this.isValidIdx(idx)) {
    this.pins[idx].setSelected(isSelected);
  }
};
 
/**
 * Return true if index is valid pin.
 * @param {number} idx Index (integer) in pins array.
 * @return {boolean} True if valid.
 */
svm.DraggablePinSet.prototype.isValidIdx = function(idx) {
  return (idx >= 0 && idx < this.pins.length);
};
 
 
// FUNCTIONS:
 
 
/**
 * Update the position of our 'marker-position' marker.
 * @param {google.maps.LatLng} latLng Maps LatLng object.
 */
function updateMarkerPositionTxt(latLng) {
  document.getElementById('marker-position').innerHTML =
     String(latLng.lat()) + ',' + String(latLng.lng());
}
 
 
/**
 * Looks at what value is selected in the multi-select
 * and marks the matching markers on the map as selected.
 */
function updateMapBasedOnMultiSelect() {
  var selectorDom = document.getElementById(MULTI_SELECT_DIV_ID);
  for (var i = 0; i < selectorDom.options.length; i++) {
    pinSet.setSelected(i, selectorDom.options[i].selected);
  }
  updateMarkerStateTxtBasedOnMap();
}
 
 
/**
 * Updates the multi-select to show was marker on the map
 * was just clicked.
 * @param {number} idx Index value to select.
 */
function updateMultipleSelectBasedOnMap(idx) {
  var selectorDom = document.getElementById(MULTI_SELECT_DIV_ID);
  if (idx < 0 || idx >= selectorDom.options.length) {
    console.log('ERROR: updateMultipleSelectBasedOnMap out of range with ' + idx);
    return;
  }
  for (var i = 0; i < selectorDom.options.length; i++) {
    selectorDom.options[i].selected = false;
  }
  selectorDom.options[idx].selected = true;
  updateMarkerStateTxtBasedOnMap();
}
 
 
/**
 * Changes 'marker-state' and 'marker-state-checkbox' divs to represent what
 * is selected on the map.
 */
function updateMarkerStateTxtBasedOnMap() {
  var numSelected = 0;
  var selectedLabel = 0;
  var isKey = false;
  var areAllKeys = true;  // Are all selected items keys.
  for (var i = 0; i < pinSet.pins.length; i++) {
    if (pinSet.pins[i].isSelected) {
      numSelected += 1;
      selectedLabel = pinSet.pins[i].label;
      isKey = pinSet.pins[i].isKey();
      if (!isKey) { areAllKeys = false; }
    }
  }
 
  // Update text:
  var strToShow = 'none';
  if (numSelected == 1) {
    strToShow = selectedLabel;
  } else if (numSelected > 1) {
    strToShow = 'multiple elements';
    isKey = areAllKeys;
  }
  document.getElementById(SELECTED_TXT_DIV_ID).innerHTML = strToShow;
 
  // Update checkbox:
  document.getElementById(KEY_CHECKBOX_DIV_ID).checked = isKey;
}
 
/**
 * Can change markers key values on the map based on 'marker-state-checkbox'.
 */
function updateKeysBasedOnCheckbox() {
  var isKey = document.getElementById(KEY_CHECKBOX_DIV_ID).checked;
  var selectorDom = document.getElementById(MULTI_SELECT_DIV_ID);
  for (var i = 0; i < selectorDom.options.length && pinSet.pins.length; i++) {
    if (selectorDom.options[i].selected) {
      pinSet.pins[i].setKey(isKey);
    }
  }
}
 
 
/**
 * Sets up Google map into 'map-canvas' div and adds a
 * demo svm.DraggablePinSet.
 */
function initialize() {
  // Setup map:
  var centerLat = 37.2434151284;
  var centerLng = -122.031447712;
  map = new google.maps.Map(document.getElementById(MAP_DIV_ID), {
      zoom: 26,
      center: new google.maps.LatLng(centerLat, centerLng),
      mapTypeId: google.maps.MapTypeId.SATELLITE,
      tilt: 0,
      maxZoom: 50,
      scaleControl: true,
  });
 
  // Create test set of draggable markers:
  pinSet = new svm.DraggablePinSet();
  for (var i = 0; i < 7; i++) {
     var lat = centerLat + (i * 0.00004);
     var lng = centerLng;
     pinSet.appendDraggablePin(i, 'marker ' + String(i), lat, lng);
  }
  pinSet.refreshInterpolation();
 
  // Update multiple select with marker labels:
  var selectorDom = document.getElementById(MULTI_SELECT_DIV_ID);
  for (var i = 0; i < pinSet.pins.length; i++) {
     var newOption = new Option(pinSet.pins[i].label, String(i));
     newOption.selected = false;
     selectorDom.appendChild(newOption);
  }
 
  // Add event listener for multiple select:
  selectorDom.addEventListener('change', function() {
      updateMapBasedOnMultiSelect(); }, false);
 
  // Add event listener for checkbox:
  var selectorDom = document.getElementById(KEY_CHECKBOX_DIV_ID);
  selectorDom.addEventListener('click', function() {
      updateKeysBasedOnCheckbox(); }, false);
}
 
// Onload handler to commence application:
google.maps.event.addDomListener(window, 'load', initialize);



draggable_interpolated_markers.html

<!doctype html>
<html>
<head>
  <title>Draggable Interpolated Markers</title>
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
  <style type="text/css">
  #map-canvas {
    width: 100%;
    height: 500px;
    float: left;
  }
  #info-panel {
    width: 200px;
    float: left;
    margin-left: 10px;
  }
  #marker-select {
    width: 180px;
    height: 200px;
    vertical-align: top;
    font-size: 60%;
  }
  </style>
 
  </head>
  <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&libraries=geometry">
  </script>
  <script type="text/javascript" src="draggable_interpolated_markers.js"></script>
<body>
  <table><tr><td>
  <div id="info-panel">
    <select multiple id="marker-select">
    </select><br>
 
    <b>Selected photo:</b> <div id="marker-state"><i>Click and drag a marker.</i></div>
    <label>
      <input type="checkbox" id="marker-state-checkbox" value="is-key">
      is key
    </label><br>
  </div>
  </td>
  <td width="80%">
  <div id="map-canvas"></div>
  </td></tr>
  </table>
</body>
</html>


See Also


Links