svg/quadFeature.js

var inherit = require('../inherit');
var registerFeature = require('../registry').registerFeature;
var quadFeature = require('../quadFeature');

/**
 * Create a new instance of class quadFeature.
 *
 * @class
 * @alias geo.svg.quadFeature
 * @param {geo.quadFeature.spec} arg Options object.
 * @extends geo.quadFeature
 * @returns {geo.svg.quadFeature}
 */
var svg_quadFeature = function (arg) {
  'use strict';
  if (!(this instanceof svg_quadFeature)) {
    return new svg_quadFeature(arg);
  }

  var $ = require('jquery');
  var d3 = require('./svgRenderer').d3;
  var object = require('./object');

  quadFeature.call(this, arg);
  object.call(this);

  var m_this = this,
      s_exit = this._exit,
      s_init = this._init,
      s_update = this._update,
      m_quads;

  /**
   * Build this feature.
   */
  this._build = function () {
    if (!this.position()) {
      return;
    }
    var renderer = m_this.renderer(),
        map = renderer.layer().map();

    m_quads = m_this._generateQuads();

    var data = [];
    $.each(m_quads.clrQuads, function (idx, quad) {
      data.push({type: 'clr', quad: quad, zIndex: quad.pos[2]});
    });
    $.each(m_quads.imgQuads, function (idx, quad) {
      if (quad.image) {
        data.push({type: 'img', quad: quad, zIndex: quad.pos[2]});
      }
    });

    var feature = {
      id: m_this._svgid(),
      data: data,
      dataIndex: function (d) {
        return d.quad.quadId;
      },
      append: function (d) {
        var ns = this.namespaceURI,
            element = d.type === 'clr' ? 'polygon' : 'image';
        return (
          ns ? document.createElementNS(ns, element) :
            document.createElement(element));
      },
      attributes: {
        fill: function (d) {
          if (d.type === 'clr') {
            return d3.rgb(255 * d.quad.color.r, 255 * d.quad.color.g,
                          255 * d.quad.color.b);
          }
          /* set some styles here */
          if (d.quad.opacity !== 1) {
            d3.select(this).style('opacity', d.quad.opacity);
          }
        },
        height: function (d) {
          return d.type === 'clr' ? undefined : 1;
        },
        points: function (d) {
          if (d.type === 'clr' && !d.points) {
            var points = [], i;
            for (i = 0; i < d.quad.pos.length; i += 3) {
              var p = {
                x: d.quad.pos[i],
                y: d.quad.pos[i + 1],
                z: d.quad.pos[i + 2]
              };
              /* We don't use 'p = m_this.featureGcsToDisplay(p);' because the
               * quads have already been converted to the map's gcs (no longer
               * the feature's gcs or map's ingcs). */
              p = map.gcsToDisplay(p, null);
              p = renderer.baseToLocal(p);
              points.push('' + p.x + ',' + p.y);
            }
            d.points = (points[0] + ' ' + points[1] + ' ' + points[3] + ' ' +
                        points[2]);
          }
          return d.type === 'clr' ? d.points : undefined;
        },
        preserveAspectRatio: function (d) {
          return d.type === 'clr' ? undefined : 'none';
        },
        reference: function (d) {
          return d.quad.reference;
        },
        stroke: false,
        transform: function (d) {
          if (d.type === 'img' && d.quad.image && !d.svgTransform) {
            var pos = [], area, maxarea = -1, maxv, i, imgscale,
                imgw = d.quad.image.width, imgh = d.quad.image.height;
            for (i = 0; i < d.quad.pos.length; i += 3) {
              var p = {
                x: d.quad.pos[i],
                y: d.quad.pos[i + 1],
                z: d.quad.pos[i + 2]
              };
              /* We don't use 'p = m_this.featureGcsToDisplay(p);' because the
               * quads have already been converted to the map's gcs (no longer
               * the feature's gcs or map's ingcs). */
              p = map.gcsToDisplay(p, null);
              p = renderer.baseToLocal(p);
              pos.push(p);
            }
            /* We can only fit three corners of the quad to the image, but we
             * get to pick which three.  We choose to always include the
             * largest of the triangles formed by a set of three vertices.  The
             * image is always rendered as a parallelogram, so it may be larger
             * than desired, and, for convex quads, miss some of the intended
             * area. */
            for (i = 0; i < 4; i += 1) {
              area = Math.abs(
                pos[(i + 1) % 4].x * (pos[(i + 2) % 4].y - pos[(i + 3) % 4].y) +
                pos[(i + 2) % 4].x * (pos[(i + 3) % 4].y - pos[(i + 1) % 4].y) +
                pos[(i + 3) % 4].x * (pos[(i + 1) % 4].y - pos[(i + 2) % 4].y)) / 2;
              if (area > maxarea) {
                maxarea = area;
                maxv = i;
              }
            }
            d.svgTransform = [
              maxv === 3 || maxv === 2 ? pos[1].x - pos[0].x : pos[3].x - pos[2].x,
              maxv === 3 || maxv === 2 ? pos[1].y - pos[0].y : pos[3].y - pos[2].y,
              maxv === 0 || maxv === 2 ? pos[1].x - pos[3].x : pos[0].x - pos[2].x,
              maxv === 0 || maxv === 2 ? pos[1].y - pos[3].y : pos[0].y - pos[2].y,
              maxv === 2 ? pos[3].x + pos[0].x - pos[1].x : pos[2].x,
              maxv === 2 ? pos[3].y + pos[0].y - pos[1].y : pos[2].y
            ];
            if (Math.abs(d.svgTransform[1] / imgw) < 1e-6 &&
                Math.abs(d.svgTransform[2] / imgh) < 1e-6) {
              imgscale = d.svgTransform[0] / imgw;
              d.svgTransform[4] = Math.round(d.svgTransform[4] / imgscale) * imgscale;
              imgscale = d.svgTransform[3] / imgh;
              d.svgTransform[5] = Math.round(d.svgTransform[5] / imgscale) * imgscale;
            }
          }
          return (
            (d.type !== 'img' || !d.quad.image) ? undefined :
              'matrix(' + d.svgTransform.join(' ') + ')');
        },
        width: function (d) {
          return d.type === 'clr' ? undefined : 1;
        },
        x: function (d) {
          return d.type === 'clr' ? undefined : 0;
        },
        'xlink:href': function (d) {
          return (
            (d.type === 'clr' || !d.quad.image) ? undefined : d.quad.image.src);
        },
        y: function (d) {
          return d.type === 'clr' ? undefined : 0;
        }
      },
      style: {
        fillOpacity: function (d) {
          return d.type === 'clr' ? d.quad.opacity : undefined;
        }
      },
      onlyRenderNew: !this.style('previewColor') && !this.style('previewImage'),
      sortByZ: true,
      visible: m_this.visible,
      classes: ['svgQuadFeature']
    };
    renderer._drawFeatures(feature);

    m_this.buildTime().modified();
  };

  /**
   * Update the feature.
   *
   * @returns {this}
   */
  this._update = function () {
    s_update.call(m_this);
    if (m_this.buildTime().timestamp() <= m_this.dataTime().timestamp() ||
        m_this.buildTime().timestamp() < m_this.timestamp()) {
      m_this._build();
    }
    return m_this;
  };

  /**
   * Initialize.
   */
  this._init = function () {
    s_init.call(m_this, arg);
  };

  /**
   * Destroy.
   */
  this._exit = function () {
    s_exit.call(m_this);
  };

  m_this._init(arg);
  return this;
};

inherit(svg_quadFeature, quadFeature);

// Now register it
var capabilities = {};
capabilities[quadFeature.capabilities.color] = true;
capabilities[quadFeature.capabilities.image] = true;
capabilities[quadFeature.capabilities.imageCrop] = false;
capabilities[quadFeature.capabilities.imageFixedScale] = false;
capabilities[quadFeature.capabilities.imageFull] = false;
capabilities[quadFeature.capabilities.canvas] = false;
capabilities[quadFeature.capabilities.video] = false;

registerFeature('svg', 'quad', svg_quadFeature, capabilities);
module.exports = svg_quadFeature;