Mercurial > hg > digilib
view webapp/src/main/webapp/jquery/jquery.digilib.vector.js @ 1610:f425f00bf5e3 geom_prototype
changed digilib.geometry to use new and prototypes instead of adding methods to objects.
author | robcast |
---|---|
date | Sun, 12 Mar 2017 15:53:01 +0100 |
parents | f322ac84adf7 |
children |
line wrap: on
line source
/* * #%L * digilib vector plugin * %% * Copyright (C) 2014 MPIWG Berlin, Bibliotheca Hertziana * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Lesser Public License for more details. * * You should have received a copy of the GNU General Lesser Public * License along with this program. If not, see * <http://www.gnu.org/licenses/lgpl-3.0.html>. * #L% * Authors: Robert Casties, Martin Raspe */ /** * digilib vector plugin. * * Displays vector shapes on top of the image. * * Shapes are objects with "geometry" and "properties" members. * geometry is an object with "type" and "coordinates" members. * Currently supported types: "Point", "Line", "LineString", "Rectangle", "Polygon", "Circle". * coordinates is a list of pairs of relative coordinates. * properties are the SVG properties "stroke", "stroke-width", "fill" and other properties. * A property 'editable':true will display drag-handles to change the shape. * Editing the shape will send a "changeShape"(shape) event. * If a shape has an "id" member its value will be used in SVG. * * shape = { * 'geometry' : { * 'type' : 'Line', * 'coordinates' : [[0.1, 0.2], [0.3, 0.4]] * }, * 'properties' : { * 'stroke' : 'blue', * 'editable' : true * } * } * */ (function ($) { // affine geometry var geom = null; // plugin object with digilib data var digilib = null; // SVG namespace var svgNS = 'http://www.w3.org/2000/svg'; var defaults = { // is vector active? 'isVectorActive' : true, // default SVG stroke 'defaultStroke' : 'red', // default SVG stroke-width 'defaultStrokeWidth' : '2', // default SVG fill 'defaultFill' : 'none', // grab handle size 'editHandleSize' : 10, // handle type (square, diamond, circle, cross) 'editHandleType' : 'square', // add or remove polygon points? 'editPolygonPoints' : true }; var actions = { /** * set list of vector objects (shapes). * * replaces all existing shapes on layer. * * @param data * @param shapes * @param layer */ setShapes : function (data, shapes, layer) { if (layer == null) { // assume shape layer is 0 layer = data.vectorLayers[0]; } layer.shapes = shapes; renderShapes(data); }, /** * add vector object (shape) or create one by clicking. * * For interactive use shape has to be initialized with a shape object with * type but no coordinates, e.g {'geometry':{'type':'Line'}}. * onComplete(data, newShape) will be called when done. * * @param data * @param shape * @param onComplete * @param layer */ addShape : function (data, shape, layer, onComplete) { if (layer == null) { // assume shape layer is 0 layer = data.vectorLayers[0]; } if (layer.shapes == null) { layer.shapes = []; } if (shape.geometry == null) { shape.geometry = {}; } if (shape.geometry.coordinates == null) { // define shape interactively defineShape(data, shape, layer, onComplete); console.debug('addShape', shape); } else { layer.shapes.push(shape); renderShapes(data, layer); } }, /** * get vector object (shape) by id. * * @param data * @param id * @returns shape */ getShapeById : function (data, id, layer) { if (layer == null) { // assume shape layer is 0 layer = data.vectorLayers[0]; } var shapes = layer.shapes; if (shapes == null) return null; for (var i in shapes) { if (shapes[i].id === id) { return shapes[i]; } } return null; }, /** * remove vector object (shape) by id. * * @param data * @param id */ removeShapeById : function (data, id, layer) { if (layer == null) { // assume shape layer is 0 layer = data.vectorLayers[0]; } var shapes = layer.shapes; if (shapes == null) return; for (var i = 0; i < shapes.length; ++i) { if (shapes[i].id === id) { shapes.splice(i, 1); } } renderShapes(data, layer); }, /** * add vector layer. * * Layer is an object with a "projection" member. * projection can be "relative": relative (0..1) coordinates, * "screen": on-screen coordinates (needs renderFn(data, layer)). * A SVG layer is specified by the jQuery-HTML element "$elem" and the SVG-element "svgElem". * * layer : { * projection : relative, * $elem : $(...), * svgElem : ... * } * * @param date * @param layer */ addVectorLayer : function (data, layer) { if (layer.projection === 'relative') { var svg = layer.svgElem; // set defaults for SVG in relative coordinates svg.setAttributeNS(null, 'viewBox', '0 0 1 1'); svg.setAttributeNS(null, 'preserveAspectRatio', 'none'); var $elem = layer.$elem; // set defaults for HTML element $elem.css({'position':'absolute', 'z-index': 9, 'pointer-events':'none'}); $elem.addClass(data.settings.cssPrefix+'overlay'); } // add layer data.vectorLayers.push(layer); renderLayers(data); } }; /** * plugin installation routine, called by digilib on each plugin object. */ var install = function (plugin) { digilib = plugin; console.debug('installing vector plugin. digilib:', digilib); // import geometry classes geom = digilib.fn.geometry; // add defaults, actions, buttons to the main digilib object $.extend(digilib.defaults, defaults); $.extend(digilib.actions, actions); // export functions $.extend(digilib.fn, { vectorDefaultRenderFn: renderShapes, svgElement: svgElement, svgAttr: svgAttr, createScreenCoords: createScreenCoords, startShapeEdit: startShapeEdit, undoShapeEdit: undoShapeEdit, finishShapeEdit: finishShapeEdit, redrawShape: redrawShape }); }; /** * plugin initialization */ var init = function (data) { console.debug('initialising vector plugin. data:', data); var $data = $(data); // create default shapes layer var shapeLayer = { 'projection': 'screen', 'renderFn': renderShapes, 'shapes': [] }; // shapes layer is first data.vectorLayers = [shapeLayer]; // pluggable SVG create functions data.shapeFactory = getShapeFactory(data); setupHandleFactory(data); // install event handlers $data.bind('update', handleUpdate); }; /** * handle update event */ var handleUpdate = function (evt) { console.debug("vector: handleUpdate"); var data = this; if (data.imgTrafo == null || !data.settings.isVectorActive) return; if (data.imgTrafo != data._vectorImgTrafo) { // imgTrafo changed -- redraw renderLayers(data); // save new imgTrafo data._vectorImgTrafo = data.imgTrafo; } }; /** * render all layers on screen */ var renderLayers = function (data) { if (data.imgRect == null) return; for (var i in data.vectorLayers) { var layer = data.vectorLayers[i]; if (layer.projection === 'screen') { // screen layers have render function if (layer.renderFn == null) { // user renderShapes as default layer.renderFn = renderShapes; } layer.renderFn(data, layer); } else if (layer.projection === 'relative') { var svg = layer.svgElem; if (svg != null) { // set current viewBox (jQuery lowercases attributes) svg.setAttribute('viewBox', data.zoomArea.getAsSvg()); } var $elem = layer.$elem; if ($elem != null) { // adjust layer element size and position (doesn't work with .adjustDiv()) $elem.css(data.imgRect.getAsCss()); $elem.show(); } } } }; /** * render all shapes on the layer. * * @param data * @param layer */ var renderShapes = function (data, layer) { if (layer == null) { // assume shape layer is 0 layer = data.vectorLayers[0]; } var shapes = layer.shapes || data.shapes; if (shapes == null || data.imgTrafo == null || !data.settings.isVectorActive) return; // set up shapes for (var i = 0; i < shapes.length; ++i) { var shape = shapes[i]; data.shapeFactory[shape.geometry.type].setup(data, shape); // console.debug('render', shape); } // sort shapes by size descending shapes.sort(function (a, b) { return (b.properties.sorta - a.properties.sorta); }); // set up SVG var $svg = layer.$elem; if ($svg != null) { $svg.remove(); } var svgElem = svgElement('svg', { 'viewBox': data.imgRect.getAsSvg(), 'class': data.settings.cssPrefix+'overlay', 'style': 'position:absolute; z-index:10; pointer-events:none;'}); $svg = $(svgElem); layer.svgElem = svgElem; layer.$elem = $svg; for (var i = 0; i < shapes.length; ++i) { var shape = shapes[i]; renderShape(data, shape, layer); } data.$elem.append($svg); // adjust layer element size and position (doesn't work with .adjustDiv()) $svg.css(data.imgRect.getAsCss()); $svg.show(); }; /** * set standard SVG attributes * * @param data */ var svgAttr = function (data, shape) { var settings = data.settings; var css = settings.cssPrefix; var props = shape.properties; return { 'id': shape.id || digilib.fn.createId(shape.id, css+'svg-'), 'stroke': props['stroke'] || settings.defaultStroke, 'stroke-width' : props['stroke-width'] || settings.defaultStrokeWidth, 'fill' : props['fill'] || settings.defaultFill, 'class' : props['cssclass'], 'style' : props['style'] }; }; /** * setup Shape SVG creation functions * (more functions can be plugged into data.settings.ShapeFactory) * * @param data */ var getShapeFactory = function (data) { var settings = data.settings; var hs = settings.editHandleSize; var factory = { 'Point' : { 'setup' : function (data, shape) { if (shape.properties == null) shape.properties = {}; shape.properties.maxvtx = 1; shape.properties.sorta = 0; }, 'svg' : function (shape) { var $s = $(svgElement('path', svgAttr(data, shape))); $s.place = function () { // point uses pin-like path of size 3*pu var p = shape.properties.screenpos[0]; var pu = hs / 3; this.attr({'d': 'M '+p.x+','+p.y+' l '+2*pu+','+pu+' c '+2*pu+','+pu+' '+0+','+3*pu+' '+(-pu)+','+pu+' Z'}); }; return $s; } }, 'Line' : { 'setup' : function (data, shape) { if (shape.properties == null) shape.properties = {}; shape.properties.maxvtx = 2; shape.properties.bbox = getBboxRect(data, shape); shape.properties.sorta = 0; }, 'svg' : function (shape) { var $s = $(svgElement('line', svgAttr(data, shape))); $s.place = function () { var p = shape.properties.screenpos; this.attr({'x1': p[0].x, 'y1': p[0].y, 'x2': p[1].x, 'y2': p[1].y}); }; return $s; } }, 'Rectangle' : { 'setup' : function (data, shape) { if (shape.properties == null) shape.properties = {}; shape.properties.maxvtx = 2; shape.properties.bbox = getBboxRect(data, shape); if (shape.properties.bbox != null) { shape.properties.sorta = shape.properties.bbox.getArea(); } }, 'svg' : function (shape) { var $s = $(svgElement('rect', svgAttr(data, shape))); $s.place = function () { var p = shape.properties.screenpos; var r = new geom.Rectangle(p[0], p[1]); this.attr({'x': r.x, 'y': r.y, 'width': r.width, 'height': r.height}); }; return $s; } }, 'Polygon' : { 'setup' : function (data, shape) { if (shape.properties == null) shape.properties = {}; shape.properties.bbox = getBboxRect(data, shape); if (shape.properties.bbox != null) { shape.properties.sorta = shape.properties.bbox.getArea(); } }, 'svg' : function (shape) { var $s = $(svgElement('polygon', svgAttr(data, shape))); $s.place = function () { var p = shape.properties.screenpos; this.attr({'points': p.join(" ")}); }; return $s; } }, 'LineString' : { 'setup' : function (data, shape) { if (shape.properties == null) shape.properties = {}; shape.properties.bbox = getBboxRect(data, shape); if (shape.properties.bbox != null) { shape.properties.sorta = shape.properties.bbox.getArea(); } }, 'svg' : function (shape) { var $s = $(svgElement('polyline', svgAttr(data, shape))); $s.place = function () { var p = shape.properties.screenpos; this.attr({'points': p.join(" ")}); }; return $s; } }, 'Circle' : { 'setup' : function (data, shape) { if (shape.properties == null) shape.properties = {}; shape.properties.maxvtx = 2; // TODO: bbox not really accurate shape.properties.bbox = getBboxRect(data, shape); if (shape.properties.bbox != null) { shape.properties.sorta = shape.properties.bbox.getArea(); } }, 'svg' : function (shape) { var $s = $(svgElement('circle', svgAttr(data, shape))); $s.place = function () { var p = shape.properties.screenpos; this.attr({'cx': p[0].x, 'cy': p[0].y, 'r': p[0].distance(p[1])}); }; return $s; } }, 'Ellipse' : { 'setup' : function (data, shape) { if (shape.properties == null) shape.properties = {}; shape.properties.maxvtx = 2; // TODO: bbox not really accurate shape.properties.bbox = getBboxRect(data, shape); if (shape.properties.bbox != null) { shape.properties.sorta = shape.properties.bbox.getArea(); } }, 'svg' : function (shape) { var $s = $(svgElement('ellipse', svgAttr(data, shape))); $s.place = function () { var p = shape.properties.screenpos; this.attr({'cx': p[0].x, 'cy': p[0].y, 'rx' : Math.abs(p[0].x - p[1].x), 'ry' : Math.abs(p[0].y - p[1].y)}); }; return $s; } } }; return factory; }; /** * setup handle creation functions * (more functions can be plugged into data.settings.handleFactory) * * @param data */ var setupHandleFactory = function (data) { var settings = data.settings; var css = settings.cssPrefix; var hs = settings.editHandleSize; var d = hs/2; var attr = { 'stroke': 'darkgrey', 'stroke-width': 1, 'fill': 'none', 'class': css+'svg-handle', 'style': 'pointer-events:all' }; var factory = { 'square' : function () { var $h = $(svgElement('rect', attr)); $h.attr({'width': hs, 'height': hs}); $h.moveTo = function (p) { this.attr({'x': p.x-d, 'y': p.y-d}); }; return $h; }, 'circle' : function () { var $h = $(svgElement('circle', attr)); $h.moveTo = function (p) { this.attr({'cx': p.x, 'cy': p.y, 'r': d}); }; return $h; }, 'diamond' : function () { var $h = $(svgElement('polygon', attr)); $h.moveTo = function (p) { this.attr('points', (p.x-d) +','+ p.y+ ' '+p.x +','+(p.y+d)+' '+(p.x+d)+','+p.y+' '+p.x+','+(p.y-d)); }; return $h; }, 'cross' : function () { var $h = $(svgElement('path', attr)); $h.moveTo = function (p) { this.attr('d', 'M'+(p.x-d) +','+ p.y+ ' L'+(p.x+d)+','+p.y+' M'+p.x+','+(p.y+d)+' L'+p.x+','+(p.y-d)); }; return $h; } }; data.handleFactory = factory; }; /** * add adjustment handles to a shape. * * Creates a SVG element for each screen point and append them to the SVG element. * * @param data * @param shape * @param layer */ var addEditHandles = function (data, shape, layer) { var $svg = $(layer.svgElem); var trafo = data.imgTrafo; // type of handle can be stated in layer or in settings var type = layer.handleType || data.settings.editHandleType; var handles = []; var createHandle = data.handleFactory[type]; var insertHandle = function (i, item) { var p = trafo.transform(new geom.Position(item)); var $handle = createHandle(); $handle.data('vertex', i); $handle.moveTo(p); handles.push($handle); $svg.append($handle); return $handle; }; $.each(shape.geometry.coordinates, insertHandle); shape.$vertexElems = handles; // not needed? var done = function (data, shape, evt) { redrawShape(data, shape, layer); }; // vertexElems must be defined before calling getVertexDragHandler() var attachEvent = function (i, item) { item.one("mousedown.dlVertexDrag", getVertexDragHandler(data, shape, i, done)); }; $.each(handles, attachEvent); }; /** * remove SVG adjustment handles from a shape. * * @param data * @param shape */ var removeEditHandles = function (data, shape) { // remove vertex handles if (shape.$vertexElems != null) { for (var i = 0; i < shape.$vertexElems.length; ++i) { shape.$vertexElems[i].remove(); } delete shape.$vertexElems; } }; /** * make shape editable, add handles and events * * @param data * @param shape */ var startShapeEdit = function (data, shape) { shape.properties.editable = true; shape.savecoords = shape.geometry.coordinates.slice(0); // clone coords redrawShape(data, shape); }; /** * end editing shape * * @param data * @param shape */ var finishShapeEdit = function (data, shape) { shape.properties.editable = false; redrawShape(data, shape); }; /** * restore edited shape to previous state * * @param data * @param shape */ var undoShapeEdit = function (data, shape) { shape.geometry.coordinates = shape.savecoords; finishShapeEdit(data, shape); }; /** * shape has a polygon type * * @param shape */ var hasPolygonType = function (shape) { var type = shape.geometry.type; return (type === 'Polygon' || type === 'LineString'); }; /** * activate polygon edit (add/remove points) * * @param data * @param shape */ var enablePolygonEdit = function (data, shape) { var $w = polygonPointWidget(data); var onHandleMouseDown = function (event) { $w.fadeOut(); }; var onHandleMouseLeave = function (event) { $w.data.timer = setTimeout(onHandleMouseDown, 500); }; var onHandleMouseEnter = function (event) { $w.data({ vertex: $(this).data('vertex'), shape: shape, timer: false }); $w.fadeIn().offset({ left : event.pageX + 5, top : event.pageY + 5 }); }; var addEventsToHandle = function(i, $handle) { $handle .on('mouseenter.handle', onHandleMouseEnter) .on('mouseleave.handle', onHandleMouseLeave) .on('mousedown.handle', onHandleMouseDown); }; $.each(shape.$vertexElems, addEventsToHandle); }; /** * deactivate polygon edit (add/remove points) * * @param data * @param shape */ var disablePolygonEdit = function (data) { var $w = data.$polygonPointWidget; if ($w == null) { return; } $w.data({ vertex: null, shape: null, timer: false }); $w.fadeOut(); }; /** * create HTML div to add/remove polygon points * * @param data */ var polygonPointWidget = function (data) { var css = data.settings.cssPrefix; var $w = data.$polygonPointWidget; if ($w == null) { // setup html var html = '\ <div id="'+css+'polygonPointWidget">\ <div id="'+css+'iconplus"><div class="icon plus" /></div>\ <div id="'+css+'iconminus"><div class="icon minus" /></div>\ </div>'; $w = $(html); $w.appendTo(data.$elem); // setup mouse bindings var onPlusClick = function (event) { var vertex = $w.data('vertex'); var shape = $w.data('shape'); var coords = shape.geometry.coordinates; var v1 = parseInt(vertex) > 0 ? vertex-1 : coords.length-1; var pt = new geom.Position(coords[vertex]).mid(new geom.Position(coords[v1])); console.debug('+ point', coords[vertex], pt); coords.splice(vertex, 0, pt); redrawShape(data, shape); }; var onMinusClick = function (event) { var vertex = $w.data('vertex'); var shape = $w.data('shape'); var coords = shape.geometry.coordinates; if (vertex == null || coords.length < 4) { return; } console.debug('- point', coords[vertex]); coords.splice(vertex, 1); redrawShape(data, shape); }; var onEnter = function (event) { clearTimeout($w.data.timer); }; var onLeave = function (event) { $w.fadeOut(); }; var $plus = $w.children().first(); var $minus = $w.children().last(); $plus.on('click', onPlusClick); $minus.on('click', onMinusClick); $w.on('mouseenter', onEnter).on('mouseleave', onLeave); data.$polygonPointWidget = $w; } return $w; }; /** * calculate screen positions from coordinates for a shape. * * @param data * @param shape */ var createScreenCoords = function (data, shape) { var coords = shape.geometry.coordinates; var trafo = data.imgTrafo; var screenpos = $.map(coords, function (coord) { return trafo.transform(new geom.Position(coord)); }); if (shape.properties == null) { shape.properties = {}; } shape.properties.screenpos = screenpos; return screenpos; }; var getBboxRect = function (data, shape) { var coords = shape.geometry.coordinates; if (coords == null) return null; var xmin = 1; var xmax = 0; var ymin = 1; var ymax = 0; var x, y; for (var i = 0; i < coords.length; ++i) { x = coords[i][0]; y = coords[i][1]; xmin = (x < xmin) ? x : xmin; xmax = (x > xmax) ? x : xmax; ymin = (y < ymin) ? y : ymin; ymax = (y > ymax) ? y : ymax; } return new geom.Rectangle(xmin, ymin, xmax-xmin, ymax-ymin); }; /** * render a shape on screen. * * Creates a SVG element and adds it to the layer. * Puts a reference $elem in the shape object. * * @param data * @param shape * @param layer */ var renderShape = function (data, shape, layer) { // make sure we have a SVG element if (layer.svgElem == null) { renderShapes(data, layer); return; } var shapeType = shape.geometry.type; if (!isSupported(data, shapeType)) { console.error("renderShape: unsupported shape type: "+shapeType); return; } // create the SVG var $elem = data.shapeFactory[shapeType].svg(shape); // let shape know where it is rendered shape.$elem = $elem; shape.layer = layer; // place the SVG on screen createScreenCoords(data, shape); $elem.place(); // render the SVG $(layer.svgElem).append($elem); // add adjustment handles if (shape.properties.editable) { addEditHandles(data, shape, layer); if (hasPolygonType(shape) && data.settings.editPolygonPoints) { enablePolygonEdit(data, shape); } } $(data).trigger("renderShape", shape); }; /** * remove rendered shape from screen. * * Removes the SVG elements from the layer. * * @param data * @param shape */ var unrenderShape = function (data, shape) { removeEditHandles(data, shape); // remove SVG element if (shape.$elem != null) { shape.$elem.remove(); delete shape.$elem; } if (hasPolygonType(shape) && data.settings.editPolygonPoints) { disablePolygonEdit(data); } }; /** * re-render a shape. * * Removes the SVG element from layer and renders the (updated) shape again. * * @param data * @param shape * @param layer */ var redrawShape = function (data, shape, layer) { unrenderShape(data, shape); renderShape(data, shape, layer || shape.layer); }; /** * return a vertexDragHandler function. * * @param data * @param shape shape to drag * @param vtx vertex number on shape * @onComplete function (data, shape) */ var getVertexDragHandler = function (data, shape, vtx, onComplete) { var $document = $(document); var imgRect = data.imgRect; var $shape = shape.$elem; var $handle = (shape.$vertexElems != null) ? shape.$vertexElems[vtx] : null; var shapeType = shape.geometry.type; var props = shape.properties; var pos = props.screenpos; var pStart; // save startpoint var placeHandle = function (i, $handle) { $handle.moveTo(pos[i]); }; var placeHandles = function () { $.each(shape.$vertexElems, placeHandle); }; var dragStart = function (evt) { // start dragging // cancel if not left-click if (evt.which != 1) return; pStart = new geom.Position(evt); props.startpos = pStart; props.vtx = vtx; $(data).trigger('positionShape', shape); $document.on("mousemove.dlVertexDrag", dragMove); $document.on("mouseup.dlVertexDrag", dragEnd); $document.on("dblclick.dlVertexDrag", dragEnd); return false; }; var dragMove = function (evt) { // dragging var pt = new geom.Position(evt); pt.clipTo(imgRect); pos[vtx].moveTo(pt); if (isSupported(data, shapeType)) { // trigger drag event (may manipulate screen position) $(data).trigger('positionShape', shape); // update vertex coords of shape shape.geometry.coordinates[vtx] = data.imgTrafo.invtransform(pos[vtx]).toArray(); // update shape SVG element $shape.place(); // move handles accordingly if (shape.$vertexElems != null) placeHandles(); $(data).trigger('dragShape', shape); } return false; }; var dragEnd = function (evt) { // end dragging var pt = new geom.Position(evt); if ((pt.distance(pStart) < 5) && evt.type === 'mouseup') { // not drag but click to start return false; } dragMove(evt); // remove move/end handler $document.off("mousemove.dlVertexDrag", dragMove); $document.off("mouseup.dlVertexDrag", dragEnd); $document.off("dblclick.dlVertexDrag", dragEnd); // call setup to update bbox data.shapeFactory[shapeType].setup(data, shape); // rearm start handler if ($handle != null) { $handle.one("mousedown.dlVertexDrag", dragStart); } if (onComplete != null) { onComplete(data, shape, evt); } else { $(data).trigger('changeShape', shape); } return false; }; // return drag start handler return dragStart; }; /** * returns true if shapeType is supported * * @param shapeType shapeType to test */ var isSupported = function (data, shapeType) { return data.shapeFactory[shapeType] != null; }; /** * define a shape by click and drag. * * The given shape object has to have a type, but its coordinates will be overwritten. * * @param data * @param shape the shape to define * @param layer the layer to draw on * @onComplete function (data, shape) */ var defineShape = function (data, shape, layer, onComplete) { var shapeType = shape.geometry.type; // call setup to make sure maxvtx is set data.shapeFactory[shapeType].setup(data, shape); var $elem = data.$elem; var $body = $('body'); var bodyRect = new geom.Rectangle($body); // overlay div prevents other elements from reacting to mouse events var $overlayDiv = $('<div class="'+data.settings.cssPrefix+'shapeOverlay" style="position:absolute; z-index:100;"/>'); $elem.append($overlayDiv); bodyRect.adjustDiv($overlayDiv); var shapeStart = function (evt) { var pt = new geom.Position(evt); // setup shape var p1 = data.imgTrafo.invtransform(pt).toArray(); var p2 = p1.slice(0); var vtx = 1; if (shapeType === 'Point') { shape.geometry.coordinates = [p1]; } else if (isSupported(data, shapeType)) { shape.geometry.coordinates = [p1, p2]; } else { console.error("defineShape: unsupported shape type: "+shapeType); $overlayDiv.remove(); return false; } // shape is not editable by default if (shape.properties == null) { shape.properties = {'editable' : false}; } // save first mousedown position shape.properties.screenpos = [pt]; shape.properties.vtx = vtx; // draw shape renderShape(data, shape, layer); // vertex drag end handler var vertexDragDone = function (data, shape, evt) { var coords = shape.geometry.coordinates; var max = shape.properties.maxvtx; if (max == null || vtx < max-1) { // multipoint shape (e. g. Polygon, LineString) if (evt.type === 'mouseup') { // single click adds next point unrenderShape(data, shape); // copy last vertex as starting point coords.push(coords[vtx].slice()); vtx += 1; // draw shape shape.properties.vtx = vtx; renderShape(data, shape, layer); // execute vertex drag handler on next vertex getVertexDragHandler(data, shape, vtx, vertexDragDone)(evt); return false; } else if (evt.type === 'dblclick') { // double click ends multipoint shape var rerender = false; // remove duplicate vertices (from mouseup) while (coords[vtx][0] === coords[vtx-1][0] && coords[vtx][1] === coords[vtx-1][1]) { coords.pop(); vtx -= 1; rerender = true; } if (rerender) { shape.properties.vtx = vtx; redrawShape(data, shape, layer); } } else { console.error("unknown event type!"); return false; } } shapeDone(data, shape); }; if (vtx === shape.properties.maxvtx) { // last vertex shapeDone(data, shape); } else { // execute vertex drag handler on next vertex getVertexDragHandler(data, shape, vtx, vertexDragDone)(evt); } return false; }; var shapeDone = function (data, shape) { // defining shape done unrenderShape(data, shape); // call setup to update bbox data.shapeFactory[shapeType].setup(data, shape); renderShape(data, shape, layer); // save shape layer.shapes.push(shape); $overlayDiv.remove(); if (onComplete != null) { onComplete(data, shape); } }; // start by clicking $overlayDiv.one('mousedown.dlShape', shapeStart); }; /** * create a SVG element with attributes. * * @param name tag name * @param attrs object with attributes */ var svgElement = function (name, attrs) { var elem = document.createElementNS(svgNS, name); if (attrs != null) { for (var att in attrs) { if (attrs[att] != null) { elem.setAttributeNS(null, att, attrs[att]); } }; } return elem; }; // plugin object, containing name, install and init routines // all shared objects are filled by digilib on registration var plugin = { name : 'vector', install : install, init : init, buttons : {}, actions : {}, fn : {}, plugins : {} }; if ($.fn.digilib == null) { $.error("jquery.digilib.vector.js must be loaded after jquery.digilib!"); } else { $.fn.digilib('plugin', plugin); } })(jQuery);