at.ait.dme.yuma.server.annotation.builder.RdfXmlAnnotationBuilder.java Source code

Java tutorial

Introduction

Here is the source code for at.ait.dme.yuma.server.annotation.builder.RdfXmlAnnotationBuilder.java

Source

/*
 * Copyright 2008-2010 Austrian Institute of Technology
 *
 * Licensed under the EUPL, Version 1.1 or - as soon they
 * will be approved by the European Commission - subsequent
 * versions of the EUPL (the "Licence");
 * you may not use this work except in compliance with the
 * Licence.
 * You may obtain a copy of the Licence at:
 *
 * http://ec.europa.eu/idabc/eupl
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the Licence is
 * distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 * See the Licence for the specific language governing
 * permissions and limitations under the Licence.
 */

package at.ait.dme.yuma.server.annotation.builder;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.log4j.Logger;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.RDFParseException;
import org.openrdf.rio.RDFParser;
import org.openrdf.rio.rdfxml.RDFXMLParser;
import org.openrdf.rio.rdfxml.RDFXMLWriter;
import org.openrdf.rio.rdfxml.util.RDFXMLPrettyWriter;
import org.openrdf.sail.memory.MemoryStore;

import at.ait.dme.yuma.client.annotation.SemanticTag;
import at.ait.dme.yuma.client.image.ImageFragment;
import at.ait.dme.yuma.client.image.ImageRect;
import at.ait.dme.yuma.client.image.annotation.ImageAnnotation;
import at.ait.dme.yuma.client.image.shape.Color;
import at.ait.dme.yuma.client.image.shape.Cross;
import at.ait.dme.yuma.client.image.shape.Ellipse;
import at.ait.dme.yuma.client.image.shape.GeoPoint;
import at.ait.dme.yuma.client.image.shape.Line;
import at.ait.dme.yuma.client.image.shape.Point;
import at.ait.dme.yuma.client.image.shape.Polygon;
import at.ait.dme.yuma.client.image.shape.Polyline;
import at.ait.dme.yuma.client.image.shape.Rectangle;
import at.ait.dme.yuma.client.image.shape.Shape;
import at.ait.dme.yuma.server.util.W3CDateTimeParser;

/**
 * converts image annotations to rdf/xml and vice versa.
 * 
 * we use an Annotea (http://www.w3.org/2001/Annotea/) based model to represent annotations and 
 * mpeg21 media pointers as well as SVG (http://www.w3.org/2000/svg) to represent 
 * image fragments.
 * 
 * TODO should be refactored to have a common AnnotationBuilder sub classed by the 
 * media type specific annotation builders like the ImageAnnotationBuilder.
 * 
 * @author Christian Sadilek
 */
public class RdfXmlAnnotationBuilder {
    public static final String ANNOTATION_NS = "http://lemo.mminf.univie.ac.at/annotation-core#";
    public static final String ANNOTATION = ANNOTATION_NS + "Annotation";
    public static final String ANNOTATION_ANNOTATES = ANNOTATION_NS + "annotates";
    public static final String ANNOTATION_CREATED = ANNOTATION_NS + "created";
    public static final String ANNOTATION_MODIFIED = ANNOTATION_NS + "modified";
    public static final String ANNOTATION_FRAGMENT = ANNOTATION_NS + "fragment";
    public static final String ANNOTATION_LABEL = ANNOTATION_NS + "label";
    public static final String ANNOTATION_AUTHOR = ANNOTATION_NS + "author";

    public static final String ANNOTEA_ANNOTATION_NS = "http://www.w3.org/2000/10/annotation-ns#";
    public static final String ANNOTEA_ANNOTATION_BODY = ANNOTEA_ANNOTATION_NS + "body";

    public static final String ANNOTATION_REL_NS = "http://lemo.mminf.univie.ac.at/ann-relationship#";
    public static final String ANNOTATION_LINKED_TO = ANNOTATION_REL_NS + "isLinkedTo";
    public static final String ANNOTATION_LINKED_TO_RESOURCES = ANNOTATION_REL_NS + "isLinkedToResources";
    public static final Namespace ANNOTATION_REL_NAMESPACE = Namespace.getNamespace("rel", ANNOTATION_REL_NS);

    public static final String ANNOTATION_TYPE_NS = "http://www.w3.org/2000/10/annotationType#";
    public static final String ANNOTATION_TYPE_COMMENT = ANNOTATION_TYPE_NS + "Comment";

    public static final String ANNOTATION_IMAGE_NS = "http://lemo.mminf.univie.ac.at/annotation-image#";
    public static final String ANNOTATION_IMAGE_FRAGMENT = ANNOTATION_IMAGE_NS + "svg-fragment";

    public static final String ANNOTATION_SCOPE_NS = "http://lemo.mminf.univie.ac.at/ann-tel#";
    public static final String ANNOTATION_SCOPE = ANNOTATION_SCOPE_NS + "scope";

    public static final String REPLY_TYPE = "http://www.w3.org/2001/12/replyType#";
    public static final String REPLY_TYPE_AGREE = REPLY_TYPE + "Agree";

    public static final String THREAD_NS = "http://www.w3.org/2001/03/thread#";
    public static final String THREAD_REPLY = THREAD_NS + "Reply";
    public static final String THREAD_IN_REPLY_TO = THREAD_NS + "inReplyTo";
    public static final String THREAD_ROOT = THREAD_NS + "root";

    public static final String HTTP_NS = "http://www.w3.org/1999/xx/http#";
    public static final String HTTP_BODY = HTTP_NS + "Body";
    public static final String HTTP_CONTENT_TYPE = HTTP_NS + "ContentType";
    public static final String HTTP_CONTENT_LENGTH = HTTP_NS + "ContentLength";

    public static final String DC = "dc";
    public static final String DUBLIN_CORE_NS = "http://purl.org/dc/elements/1.1/";
    public static final String DUBLIN_CORE_DATE = DUBLIN_CORE_NS + "date";
    public static final String DUBLIN_CORE_CREATOR = DUBLIN_CORE_NS + "creator";
    public static final String DUBLIN_CORE_DESCRIPTION = DUBLIN_CORE_NS + "description";
    public static final String DUBLIN_CORE_TITLE = DUBLIN_CORE_NS + "title";
    public static final String DUBLIN_CORE_FORMAT = DUBLIN_CORE_NS + "format";
    public static final Namespace DUBLIN_CORE_NAMESPACE = Namespace.getNamespace(DC, DUBLIN_CORE_NS);

    public static final String RDFS = "rdfs";
    public static final String RDFS_NS = "http://www.w3.org/2000/01/rdf-schema#";
    public static final String RDFS_SEEALSO = RDFS_NS + "seeAlso";
    public static final Namespace RDFS_NAMESPACE = Namespace.getNamespace(RDFS, RDFS_NS);

    public static final Namespace RDF_NAMESPACE = Namespace.getNamespace("rdf",
            "http://www.w3.org/1999/02/22-rdf-syntax-ns#");

    public static final String PX = "px";
    public static final String SVG = "svg";
    public static final String SVG_X = "x";
    public static final String SVG_Y = "y";
    public static final String SVG_CX = "cx";
    public static final String SVG_CY = "cy";
    public static final String SVG_RX = "rx";
    public static final String SVG_RY = "ry";
    public static final String SVG_X1 = "x1";
    public static final String SVG_Y1 = "y1";
    public static final String SVG_X2 = "x2";
    public static final String SVG_Y2 = "y2";
    public static final String LAT = "lat";
    public static final String LNG = "lng";
    public static final String NAME = "name";

    public static final String SVG_WIDTH = "width";
    public static final String SVG_HEIGHT = "height";
    public static final String SVG_VIEWBOX = "viewbox";
    public static final String SVG_SYMBOL = "symbol";
    public static final String SVG_SYMBOL_ID = "id";
    public static final String SVG_SYMBOL_ID_ELLIPSE = "Ellipse";
    public static final String SVG_SYMBOL_ID_RECTANGLE = "Rectangle";
    public static final String SVG_SYMBOL_ID_POLYGON = "Polygon";
    public static final String SVG_SYMBOL_ID_POLYLINE = "Polyline";
    public static final String SVG_SYMBOL_ID_CROSS = "Cross";
    public static final String SVG_SYMBOL_ID_GEOPOINT = "GeoPoint";
    public static final String SVG_DEFINITIONS = "defs";
    public static final String SVG_USE = "use";
    public static final String SVG_IMAGE = "image";
    public static final String SVG_STROKE = "stroke";
    public static final String SVG_STROKE_WIDTH = "stroke-width";
    public static final String SVG_RECTANGLE = "rect";
    public static final String SVG_ELLIPSE = "ellipse";
    public static final String SVG_POLYGON = "polygon";
    public static final String SVG_POLYLINE = "polyline";
    public static final String SVG_LINE = "line";
    public static final String SVG_POINTS = "points";
    public static final String SVG_FILL = "fill";
    public static final String SVG_PRESERVE_ASPECT_RATIO = "preserveAspectRatio";
    public static final String SVG_NONE = "none";
    public static final Namespace SVG_NS = Namespace.getNamespace(SVG, "http://www.w3.org/2000/svg");

    public static final String XLINK = "xlink";
    public static final String XLINK_HREF = "href";
    public static final Namespace XLINK_NS = Namespace.getNamespace(XLINK, "http://www.w3.org/1999/xlink");

    private static final String MPEG21_MEDIA_POINTER = "mp";
    private static final String MPEG21_REGION_NODE = "region";
    private static final String MPEG21_REGION_RECT = "rect";
    private static final String MPEG21_REGION_ELLIPSE = "ellipse";
    private static final String MPEG21_REGION_POLYGON = "polygon";

    private static Logger logger = Logger.getLogger(RdfXmlAnnotationBuilder.class);

    /**
     * create an rdf/xml representation of the given annotation
     * see example: http://www.w3.org/2001/Annotea/User/Protocol.html .
     * 
     * @param annotation
     * @return string representation
     */
    public static String toRdfXml(ImageAnnotation annotation)
            throws RepositoryException, RDFHandlerException, IOException {
        annotation.setText(StringEscapeUtils.escapeXml(annotation.getText()));
        annotation.setTitle(StringEscapeUtils.escapeXml(annotation.getTitle()));

        Repository repository = new SailRepository(new MemoryStore());
        repository.initialize();
        RepositoryConnection con = repository.getConnection();
        ValueFactory valueFactory = repository.getValueFactory();

        toRdf(con, valueFactory, annotation);

        StringWriter sw = new StringWriter();
        RDFXMLWriter rdfXmlWriter = new RDFXMLPrettyWriter(sw);
        rdfXmlWriter.handleNamespace("a", ANNOTATION_NS);
        rdfXmlWriter.handleNamespace("ann", ANNOTEA_ANNOTATION_NS);
        rdfXmlWriter.handleNamespace("scope", ANNOTATION_SCOPE_NS);
        rdfXmlWriter.handleNamespace("image", ANNOTATION_IMAGE_NS);
        rdfXmlWriter.handleNamespace("rel", ANNOTATION_REL_NS);

        rdfXmlWriter.handleNamespace("dc", DUBLIN_CORE_NS);
        rdfXmlWriter.handleNamespace("h", HTTP_NS);
        rdfXmlWriter.handleNamespace("tr", THREAD_NS);
        rdfXmlWriter.handleNamespace("svg", SVG_NS.getURI());

        con.export(rdfXmlWriter);
        return sw.toString();
    }

    private static void toRdf(RepositoryConnection con, ValueFactory vFactory, ImageAnnotation annotation)
            throws RepositoryException, RDFHandlerException, IOException {
        Resource annotationNode = (annotation.getId() == null) ? vFactory.createBNode()
                : vFactory.createURI(annotation.getId());

        URI annotationType = vFactory.createURI(ANNOTATION);
        URI annotationCommentType = vFactory.createURI(ANNOTATION_TYPE_COMMENT);
        URI replyType = vFactory.createURI(THREAD_REPLY);
        URI agreeType = vFactory.createURI(REPLY_TYPE_AGREE);
        URI annotatesPredicate = vFactory.createURI(ANNOTATION_ANNOTATES);
        URI linkedToPredicate = vFactory.createURI(ANNOTATION_LINKED_TO);
        URI annotationCreatedPredicate = vFactory.createURI(ANNOTATION_CREATED);
        URI annotationModifiedPredicate = vFactory.createURI(ANNOTATION_MODIFIED);
        URI annotationBodyPredicate = vFactory.createURI(ANNOTEA_ANNOTATION_BODY);
        URI annotationLabelPredicate = vFactory.createURI(ANNOTATION_LABEL);
        URI annotationFragmentPredicate = vFactory.createURI(ANNOTATION_FRAGMENT);
        URI annotationLinkedToResourcesPredicate = vFactory.createURI(ANNOTATION_LINKED_TO_RESOURCES);
        URI imageFragmentPredicate = vFactory.createURI(ANNOTATION_IMAGE_FRAGMENT);
        URI annotationAuthorPredicate = vFactory.createURI(ANNOTATION_AUTHOR);
        URI annotationScopePredicate = vFactory.createURI(ANNOTATION_SCOPE);

        URI rdfsSeeAlso = vFactory.createURI(RDFS_SEEALSO);

        URI dcTitlePredicate = vFactory.createURI(DUBLIN_CORE_TITLE);
        URI dcCreatorPredicate = vFactory.createURI(DUBLIN_CORE_CREATOR);
        URI dcDateTypePredicate = vFactory.createURI(DUBLIN_CORE_DATE);
        URI dcFormatPredicate = vFactory.createURI(DUBLIN_CORE_FORMAT);
        URI threadRootPredicate = vFactory.createURI(THREAD_ROOT);
        URI threadInReplyToPredicate = vFactory.createURI(THREAD_IN_REPLY_TO);

        Resource annotationBodyNode = vFactory.createBNode();
        URI htmlContentTypePredicate = vFactory.createURI(HTTP_CONTENT_TYPE);
        URI htmlContentLengthPredicate = vFactory.createURI(HTTP_CONTENT_LENGTH);
        URI htmlBodyPredicate = vFactory.createURI(HTTP_BODY);

        // annotation resource
        con.add(annotationNode, annotatesPredicate, vFactory.createURI(annotation.getImageUrl()));

        // seeAlso
        if (annotation.hasLinks()) {
            for (String link : annotation.getLinks())
                con.add(annotationNode, rdfsSeeAlso, vFactory.createURI(link));
        }

        // linked to
        if (annotation.getExternalObjectId() != null) {
            con.add(annotationNode, linkedToPredicate, vFactory.createLiteral(annotation.getExternalObjectId()));
        }

        // reply?
        if (annotation.getParentId() != null) {
            con.add(annotationNode, RDF.TYPE, replyType);
            con.add(annotationNode, RDF.TYPE, agreeType);
            con.add(annotationNode, threadInReplyToPredicate, vFactory.createURI(annotation.getParentId()));
        } else {
            con.add(annotationNode, RDF.TYPE, annotationType);
            con.add(annotationNode, RDF.TYPE, annotationCommentType);
        }

        // root
        if (annotation.getRootId() != null)
            con.add(annotationNode, threadRootPredicate, vFactory.createURI(annotation.getRootId()));

        // title
        if (annotation.getTitle() != null)
            con.add(annotationNode, dcTitlePredicate, vFactory.createLiteral(annotation.getTitle()));

        // creator and author
        if (annotation.getCreatedBy() != null) {
            con.add(annotationNode, annotationAuthorPredicate, vFactory.createLiteral(annotation.getCreatedBy()));
            con.add(annotationNode, dcCreatorPredicate, vFactory.createLiteral(annotation.getCreatedBy()));
        }

        // date
        String modified = W3CDateTimeParser.formatW3CDateTime(annotation.getModified());
        con.add(annotationNode, annotationModifiedPredicate, vFactory.createLiteral(modified));
        con.add(annotationNode, dcDateTypePredicate, vFactory.createLiteral(modified));
        String date = W3CDateTimeParser.formatW3CDateTime(annotation.getCreated());
        con.add(annotationNode, annotationCreatedPredicate, vFactory.createLiteral(date));

        // format (mime type)
        if (annotation.getMimeType() != null)
            con.add(annotationNode, dcFormatPredicate, vFactory.createLiteral(annotation.getMimeType()));

        // annotated image fragment
        if (annotation.hasFragment()) {
            con.add(annotationNode, annotationFragmentPredicate,
                    vFactory.createLiteral(createMpeg21FragmentURI(annotation)));
            con.add(annotationNode, imageFragmentPredicate,
                    vFactory.createLiteral(createSvgFragment(annotation), RDF.XMLLITERAL));
        }

        // linked to resources
        if (annotation.hasSemanticTags()) {
            con.add(annotationNode, annotationLinkedToResourcesPredicate,
                    vFactory.createLiteral(createLinkedToResourcesFragment(annotation)));
        }

        // body and label
        if (annotation.getText() != null)
            con.add(annotationNode, annotationLabelPredicate, vFactory.createLiteral(annotation.getText()));
        String annotationBody = annotation.toHtml();
        con.add(annotationNode, annotationBodyPredicate, annotationBodyNode);
        con.add(annotationBodyNode, htmlContentTypePredicate, vFactory.createLiteral("text/html"));
        con.add(annotationBodyNode, htmlContentLengthPredicate,
                vFactory.createLiteral(new Integer(annotationBody.length()).toString()));
        con.add(annotationBodyNode, htmlBodyPredicate, vFactory.createLiteral(annotationBody, RDF.XMLLITERAL));

        // scope
        con.add(annotationNode, annotationScopePredicate, vFactory.createLiteral(annotation.getScopeAsString()));

        // replies
        if (annotation.hasReplies()) {
            for (ImageAnnotation reply : annotation.getReplies())
                toRdf(con, vFactory, reply);
        }
    }

    private static String createLinkedToResourcesFragment(ImageAnnotation annotation) throws IOException {

        StringWriter sw = new StringWriter();
        XMLOutputter xmlOutputter = new XMLOutputter();

        if (annotation.hasSemanticTags()) {
            for (SemanticTag t : annotation.getSemanticTags()) {
                Element tagEl = new Element("linked-resource");
                Element title = new Element("title", DUBLIN_CORE_NAMESPACE);
                Element type = new Element("type", DUBLIN_CORE_NAMESPACE);
                Element seeAlso = new Element("seeAlso", RDFS_NAMESPACE);
                Element language = new Element("language", DUBLIN_CORE_NAMESPACE);
                Element description = new Element("description", DUBLIN_CORE_NAMESPACE);

                title.setText(t.getTitle());
                type.setText(t.getType());
                seeAlso.setAttribute(new Attribute("resource", t.getURI(), RDF_NAMESPACE));
                language.setText(StringEscapeUtils.escapeXml(t.getLang()));
                description.setText(StringEscapeUtils.escapeXml(t.getDescription()));

                tagEl.addContent(title);
                tagEl.addContent(type);
                tagEl.addContent(seeAlso);
                tagEl.addContent(language);
                tagEl.addContent(description);

                xmlOutputter.output(tagEl, sw);
            }
        }

        return sw.toString();

    }

    /**
    * create a mpeg21 fragment URI using a media pointer
    * 
    * @param annotation
    * @return URI
    */
    private static String createMpeg21FragmentURI(ImageAnnotation annotation) {
        // TODO zoom level has still to be considered here
        String mpeg21URI = annotation.getImageUrl() + "#";
        ImageFragment fragment = annotation.getFragment();
        Shape shape = fragment.getShape();

        mpeg21URI += MPEG21_MEDIA_POINTER + "(~" + MPEG21_REGION_NODE + "(";
        // for polygons the first pair of coordinates use absolute values, the subsequent coords
        // are relative to the corresponding previous coordinate
        if (shape instanceof Polygon) {
            mpeg21URI += MPEG21_REGION_POLYGON + "(";
            Polygon polygon = (Polygon) shape;
            ArrayList<Point> points = new ArrayList<Point>(polygon.getPoints());
            Point firstPoint = points.remove(0);
            mpeg21URI += String.valueOf(polygon.getLeft() + firstPoint.getX()) + ","
                    + String.valueOf(polygon.getTop() + firstPoint.getY());
            Point previousPoint = firstPoint;
            for (Point point : points) {
                mpeg21URI += "," + String.valueOf(point.getX() - previousPoint.getX()) + ","
                        + String.valueOf(((Point) point).getY() - previousPoint.getY());
                previousPoint = point;
            }
            mpeg21URI += ")";

        } else if (shape instanceof Ellipse) {
            mpeg21URI += MPEG21_REGION_ELLIPSE + "(";
            Ellipse ellipse = (Ellipse) shape;
            mpeg21URI += String.valueOf(ellipse.getCx()) + "," + String.valueOf(ellipse.getCy()) + ",";
            mpeg21URI += String.valueOf(ellipse.getCx() + ellipse.getRx()) + "," + String.valueOf(ellipse.getRy())
                    + ",";
            mpeg21URI += String.valueOf(ellipse.getCx() + ellipse.getRx()) + ","
                    + String.valueOf(ellipse.getCy() + ellipse.getRy());
            mpeg21URI += ")";

            // in case of all other shapes we use rectangles   
        } else {
            mpeg21URI += MPEG21_REGION_RECT + "(";
            mpeg21URI += String.valueOf(shape.getLeft()) + "," + String.valueOf(shape.getTop()) + ",";
            mpeg21URI += String.valueOf(shape.getLeft() + shape.getWidth()) + ","
                    + String.valueOf(shape.getTop() + shape.getHeight());
            mpeg21URI += ")";
        }
        mpeg21URI += "))";

        return mpeg21URI;
    }

    /**
     * create a svg representation of the annotated fragment
     *  
     * example of svg fragment (see also http://www.w3.org/2000/svg):
     * 
     * <svg:svg xmlns:svg="http://www.w3.org/2000/svg" width="499px" height="223px" 
     *       viewBox="0px 0px 499px 223px">
     *  
     *    <svg:defs> 
     *       <svg:symbol id="Ellipse"> 
     *          <svg:ellipse cx="50px" cy="50px" rx="50px" ry="50px" stroke="rgb(0,120,255)" 
     *                stroke-width="1" fill="none" />
     *       </svg:symbol>
     *    </svg:defs>
     * 
     *    <svg:image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://some.jpg" 
     *       x="-20px" y="-30px" width="1000px" height="700px" preserveAspectRatio="none" /> 
     * 
     *    <svg:use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#Ellipse" x="0px" y="0px" 
     *       width="100px" height="100px" />
     * 
     * </svg:svg>
     * 
     * @param annotation
     * @return string svg representation
     */
    private static String createSvgFragment(ImageAnnotation annotation) throws IOException {
        ImageFragment fragment = annotation.getFragment();
        Shape shape = fragment.getShape();
        ImageRect visibleRect = fragment.getVisibleRect();

        /* 
         * <svg:svg xmlns:svg="http://www.w3.org/2000/svg" width="499px" height="223px" 
         *      viewBox="0px 0px 499px 223px">
         */
        Element svg = new Element(SVG, SVG_NS);
        svg.setAttribute(new Attribute(SVG_WIDTH, String.valueOf(visibleRect.getWidth()) + PX));
        svg.setAttribute(new Attribute(SVG_HEIGHT, String.valueOf(visibleRect.getHeight()) + PX));
        svg.setAttribute(new Attribute(SVG_VIEWBOX,
                String.valueOf(visibleRect.getLeft()) + PX + " " + String.valueOf(visibleRect.getTop()) + PX + " "
                        + String.valueOf(visibleRect.getWidth()) + PX + " "
                        + String.valueOf(visibleRect.getHeight()) + PX));

        /*
         * <svg:image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://some.jpg" 
         *      x="-20px" y="-30px" width="1000px" height="700px" preserveAspectRatio="none" />  
         */
        ImageRect imageRect = fragment.getImageRect();
        Element image = new Element(SVG_IMAGE, SVG_NS);
        image.setAttribute(new Attribute(XLINK_HREF, annotation.getImageUrl(), XLINK_NS));
        image.setAttribute(new Attribute(SVG_X, String.valueOf(imageRect.getLeft()) + PX));
        image.setAttribute(new Attribute(SVG_Y, String.valueOf(imageRect.getTop()) + PX));
        image.setAttribute(new Attribute(SVG_WIDTH, String.valueOf(imageRect.getWidth()) + PX));
        image.setAttribute(new Attribute(SVG_HEIGHT, String.valueOf(imageRect.getHeight()) + PX));
        image.setAttribute(new Attribute(SVG_PRESERVE_ASPECT_RATIO, SVG_NONE));

        /*
         * <svg:defs> 
         *       <svg:symbol id="Ellipse"> 
         *          <svg:ellipse cx="50px" cy="50px" rx="50px" ry="50px" stroke="rgb(0,120,255)" 
         *                stroke-width="1" fill="none" />
         *       </svg:symbol>
         *    </svg:defs>
         * 
         * 
         *    <svg:use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#Ellipse" x="0px" y="0px" 
         *       width="100px" height="100px" />
         */
        Element symbol = new Element(SVG_SYMBOL, SVG_NS);
        Element use = new Element(SVG_USE, SVG_NS);
        createSvgSymbol(symbol, use, shape);

        Element defs = new Element(SVG_DEFINITIONS, SVG_NS);
        defs.addContent(symbol);

        svg.addContent(defs);
        svg.addContent(image);
        svg.addContent(use);

        // write jdom tree to string
        StringWriter sw = new StringWriter();
        XMLOutputter xmlOutputter = new XMLOutputter();
        xmlOutputter.output(svg, sw);
        return sw.toString();
    }

    /**
     * creates the svg shape see http://www.w3.org/TR/SVG/shapes.html .
     * 
     * @param symbol
     * @param use
     * @param shape
     */
    private static void createSvgSymbol(Element symbol, Element use, Shape shape) {
        // all coordinates are relative to the svg:use rect
        // see http://www.w3.org/TR/SVG/shapes.html#RectElement
        // NOTE: Why are we doing this ugly instanceof checks here? Instead, we could
        // implement shape.getSvg() and override the method for each type of shape. 
        // The problem is the shapes are also used on the client and therefore compiled 
        // to javascript which is not possible when using jdom. In the future we could
        // change that and use a client side parser.
        Element shapeEl = null;
        if (shape instanceof Rectangle) {
            symbol.setAttribute(SVG_SYMBOL_ID, SVG_SYMBOL_ID_RECTANGLE);
            use.setAttribute(new Attribute(XLINK_HREF, "#" + SVG_SYMBOL_ID_RECTANGLE, XLINK_NS));

            shapeEl = new Element(SVG_RECTANGLE, SVG_NS);
            shapeEl.setAttribute(new Attribute(SVG_X, "0" + PX));
            shapeEl.setAttribute(new Attribute(SVG_Y, "0" + PX));
            shapeEl.setAttribute(new Attribute(SVG_WIDTH, String.valueOf(shape.getWidth()) + PX));
            shapeEl.setAttribute(new Attribute(SVG_HEIGHT, String.valueOf(shape.getHeight()) + PX));
            symbol.addContent(shapeEl);

            // see http://www.w3.org/TR/SVG/shapes.html#EllipseElement
        } else if (shape instanceof Ellipse) {
            symbol.setAttribute(SVG_SYMBOL_ID, SVG_SYMBOL_ID_ELLIPSE);
            use.setAttribute(new Attribute(XLINK_HREF, "#" + SVG_SYMBOL_ID_ELLIPSE, XLINK_NS));

            Ellipse ellipse = (Ellipse) shape;
            shapeEl = new Element(SVG_ELLIPSE, SVG_NS);
            shapeEl.setAttribute(new Attribute(SVG_CX, String.valueOf(ellipse.getCx() - shape.getLeft()) + PX));
            shapeEl.setAttribute(new Attribute(SVG_CY, String.valueOf(ellipse.getCy() - shape.getTop()) + PX));
            shapeEl.setAttribute(new Attribute(SVG_RX, String.valueOf(ellipse.getRx()) + PX));
            shapeEl.setAttribute(new Attribute(SVG_RY, String.valueOf(ellipse.getRy()) + PX));
            symbol.addContent(shapeEl);

            // http://www.w3.org/TR/SVG/shapes.html#PolygonElement
        } else if (shape instanceof Polygon) {
            if (shape instanceof Polyline) {
                symbol.setAttribute(SVG_SYMBOL_ID, SVG_SYMBOL_ID_POLYLINE);
                use.setAttribute(new Attribute(XLINK_HREF, "#" + SVG_SYMBOL_ID_POLYLINE, XLINK_NS));
                shapeEl = new Element(SVG_POLYLINE, SVG_NS);
            } else {
                symbol.setAttribute(SVG_SYMBOL_ID, SVG_SYMBOL_ID_POLYGON);
                use.setAttribute(new Attribute(XLINK_HREF, "#" + SVG_SYMBOL_ID_POLYGON, XLINK_NS));
                shapeEl = new Element(SVG_POLYGON, SVG_NS);
            }

            Polygon polygon = (Polygon) shape;
            String points = "";
            for (Point point : polygon.getPoints()) {
                points += String.valueOf(point.getX() - polygon.getRelativeLeft()) + ","
                        + String.valueOf(((Point) point).getY() - polygon.getRelativeTop()) + " ";
            }
            shapeEl.setAttribute(new Attribute(SVG_POINTS, points));
            symbol.addContent(shapeEl);

            // see http://www.w3.org/TR/SVG/shapes.html#LineElement
            // a cross is presented using 4 lines 
        } else if (shape instanceof Cross) {
            symbol.setAttribute(SVG_SYMBOL_ID, SVG_SYMBOL_ID_CROSS);
            use.setAttribute(new Attribute(XLINK_HREF, "#" + SVG_SYMBOL_ID_CROSS, XLINK_NS));

            Cross cross = (Cross) shape;
            for (Line line : cross.getLines()) {
                Element lineEl = new Element(SVG_LINE, SVG_NS);
                lineEl.setAttribute(new Attribute(SVG_X1, String.valueOf(line.getStart().getX())));
                lineEl.setAttribute(new Attribute(SVG_Y1, String.valueOf(line.getStart().getY())));
                lineEl.setAttribute(new Attribute(SVG_X2, String.valueOf(line.getEnd().getX())));
                lineEl.setAttribute(new Attribute(SVG_Y2, String.valueOf(line.getEnd().getY())));
                lineEl.setAttribute(new Attribute(SVG_STROKE, "rgb(" + shape.getColor().getR() + ","
                        + shape.getColor().getG() + "," + shape.getColor().getB() + ")"));
                lineEl.setAttribute(new Attribute(SVG_STROKE_WIDTH, String.valueOf(shape.getStrokeWidth())));

                symbol.addContent(lineEl);
            }

            // a geopoint is our own invention representing a location on a map and a pixel on an image       
        } else if (shape instanceof GeoPoint) {
            symbol.setAttribute(SVG_SYMBOL_ID, SVG_SYMBOL_ID_GEOPOINT);
            use.setAttribute(new Attribute(XLINK_HREF, "#" + SVG_SYMBOL_ID_GEOPOINT, XLINK_NS));

            GeoPoint point = (GeoPoint) shape;
            shapeEl = new Element(SVG_SYMBOL_ID_GEOPOINT, ANNOTATION_IMAGE_NS);
            shapeEl.setAttribute(new Attribute(SVG_X, new Integer(point.getX()).toString()));
            shapeEl.setAttribute(new Attribute(SVG_Y, new Integer(point.getY()).toString()));
            shapeEl.setAttribute(new Attribute(LAT, new Double(point.getLat()).toString()));
            shapeEl.setAttribute(new Attribute(LNG, new Double(point.getLng()).toString()));
            shapeEl.setAttribute(new Attribute(NAME, point.getName()));
            symbol.addContent(shapeEl);
        }

        // use the defined symbol
        use.setAttribute(new Attribute(SVG_X, String.valueOf(shape.getLeft()) + PX));
        use.setAttribute(new Attribute(SVG_Y, String.valueOf(shape.getTop()) + PX));
        use.setAttribute(new Attribute(SVG_WIDTH, String.valueOf(shape.getWidth()) + PX));
        use.setAttribute(new Attribute(SVG_HEIGHT, String.valueOf(shape.getHeight()) + PX));

        if (shapeEl != null) {
            shapeEl.setAttribute(new Attribute(SVG_STROKE, "rgb(" + shape.getColor().getR() + ","
                    + shape.getColor().getG() + "," + shape.getColor().getB() + ")"));
            shapeEl.setAttribute(new Attribute(SVG_STROKE_WIDTH, String.valueOf(shape.getStrokeWidth())));
            shapeEl.setAttribute(new Attribute(SVG_FILL, SVG_NONE));
        }
    }

    /**
     * parse a svg fragment representation.
     * 
     * @param svgFragment
     * @return image fragment
     * @throws JDOMException
     * @throws IOException
     */
    public static ImageFragment parseSvgFragment(String svgFragment) throws IOException {

        ImageFragment fragment = null;
        Document document = null;
        try {
            fragment = new ImageFragment();
            document = new SAXBuilder().build(new StringReader(svgFragment));

            // visible rect
            ImageRect visibleRect = null;
            Attribute viewBox = document.getRootElement().getAttribute(SVG_VIEWBOX);
            if (viewBox != null) {
                String viewBoxValue = viewBox.getValue();
                String[] values = viewBoxValue.split(" ");
                if (values.length == 4) {
                    int left = new Integer(values[0].replace(PX, ""));
                    int top = new Integer(values[1].replace(PX, ""));
                    int width = new Integer(values[2].replace(PX, ""));
                    int height = new Integer(values[3].replace(PX, ""));
                    visibleRect = new ImageRect(left, top, width, height);
                }
            }
            fragment.setVisibleRect(visibleRect);

            // image rect
            ImageRect imageRect = null;
            Element imageElement = document.getRootElement().getChild(SVG_IMAGE, SVG_NS);
            if (imageElement != null)
                imageRect = parseSvgRect(imageElement);
            fragment.setImageRect(imageRect);

            // shape
            fragment.setShape(parseSvgSymbol(document));
        } catch (JDOMException e) {
            logger.error("invalid fragment", e);
        }

        return fragment;
    }

    /**
     * parse a svg rect.
     * 
     * @param rectElement
     * @return image rect
     */
    private static ImageRect parseSvgRect(Element rectElement) {
        int left = 0, top = 0, width = 0, height = 0;

        String leftValue = rectElement.getAttributeValue(SVG_X);
        if (leftValue != null)
            left = new Integer(leftValue.replace(PX, ""));

        String topValue = rectElement.getAttributeValue(SVG_Y);
        if (topValue != null)
            top = new Integer(topValue.replace(PX, ""));

        String widthValue = rectElement.getAttributeValue(SVG_WIDTH);
        if (widthValue != null)
            width = new Integer(widthValue.replace(PX, ""));

        String heightValue = rectElement.getAttributeValue(SVG_HEIGHT);
        if (heightValue != null)
            height = new Integer(heightValue.replace(PX, ""));

        return new ImageRect(left, top, width, height);
    }

    /**
     * parse a svg stroke color.
     * 
     * @param stroke attribute
     * @return color
     */
    private static Color parseSvgStrokeColor(Attribute stroke) {
        Color color = new Color();
        if (stroke != null) {
            String strokeValue = stroke.getValue();
            strokeValue = strokeValue.replace("rgb(", "").replace(")", "");
            String[] values = strokeValue.split(",");
            if (values.length == 3) {
                color.setR(new Integer(values[0]));
                color.setG(new Integer(values[1]));
                color.setB(new Integer(values[2]));
            }
        }
        return color;
    }

    /**
     * parse svg stroke width.
     * 
     * @param strokeWidth
     * @return width
     */
    private static int parseSvgStrokeWidth(Attribute strokeWidth) {
        String strokeWidthValue = strokeWidth.getValue();
        return new Integer(strokeWidthValue);
    }

    /**
     * parse svg symbol.
     * 
     * @param document
     * @return shape
     * @throws JDOMException
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    private static Shape parseSvgSymbol(Document document) throws JDOMException, IOException {
        Shape shape = null;

        Element defs = document.getRootElement().getChild(SVG_DEFINITIONS, SVG_NS);
        if (defs == null)
            return null;

        Element symbol = defs.getChild(SVG_SYMBOL, SVG_NS);
        Element use = document.getRootElement().getChild(SVG_USE, SVG_NS);
        if (symbol == null || use == null)
            return null;

        ImageRect useRect = parseSvgRect(use);

        String symbolId = symbol.getAttributeValue(SVG_SYMBOL_ID);
        // parse rectangle
        if (symbolId.equals(SVG_SYMBOL_ID_RECTANGLE)) {
            Element rectElement = symbol.getChild(SVG_RECTANGLE, SVG_NS);
            Color color = parseSvgStrokeColor(rectElement.getAttribute(SVG_STROKE));
            int strokeWidth = parseSvgStrokeWidth(rectElement.getAttribute(SVG_STROKE_WIDTH));

            shape = new Rectangle(useRect.getLeft(), useRect.getTop(), useRect.getWidth(), useRect.getHeight(),
                    color, strokeWidth);
            // parse ellipse      
        } else if (symbolId.equals(SVG_SYMBOL_ID_ELLIPSE)) {
            Element ellipseElement = symbol.getChild(SVG_ELLIPSE, SVG_NS);
            Color color = parseSvgStrokeColor(ellipseElement.getAttribute(SVG_STROKE));
            int strokeWidth = parseSvgStrokeWidth(ellipseElement.getAttribute(SVG_STROKE_WIDTH));

            shape = new Ellipse(useRect.getLeft(), useRect.getTop(), useRect.getWidth(), useRect.getHeight(), color,
                    strokeWidth);
            // parse polygon or polyline
        } else if (symbolId.equals(SVG_SYMBOL_ID_POLYGON) || symbolId.equals(SVG_SYMBOL_ID_POLYLINE)) {
            Element polyElement = null;
            if (symbolId.equals(SVG_SYMBOL_ID_POLYGON)) {
                polyElement = symbol.getChild(SVG_POLYGON, SVG_NS);
            } else {
                polyElement = symbol.getChild(SVG_POLYLINE, SVG_NS);
            }

            Color color = parseSvgStrokeColor(polyElement.getAttribute(SVG_STROKE));
            int strokeWidth = parseSvgStrokeWidth(polyElement.getAttribute(SVG_STROKE_WIDTH));
            if (symbolId.equals(SVG_SYMBOL_ID_POLYGON)) {
                shape = new Polygon(useRect.getLeft(), useRect.getTop(), useRect.getWidth(), useRect.getHeight(),
                        color, strokeWidth);
            } else {
                shape = new Polyline(useRect.getLeft(), useRect.getTop(), useRect.getWidth(), useRect.getHeight(),
                        color, strokeWidth);
            }

            String pointsValue = polyElement.getAttributeValue(SVG_POINTS);
            String[] points = pointsValue.split(" ");
            for (String point : points) {
                String[] coords = point.split(",");
                if (coords.length == 2) {
                    Point p = new Point(new Integer(coords[0]), new Integer(coords[1]));
                    ((Polygon) shape).addPoint(p);
                }
            }
            // parse cross   
        } else if (symbolId.equals(SVG_SYMBOL_ID_CROSS)) {
            List<Element> lineElements = symbol.getChildren(SVG_LINE, SVG_NS);
            if (lineElements.size() != 4)
                return null;

            Element oneLineElement = lineElements.get(0);
            List<Line> lines = new ArrayList<Line>();
            for (Element lineElement : lineElements) {
                int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
                String x1Value = lineElement.getAttributeValue(SVG_X1);
                if (x1Value != null)
                    x1 = new Integer(x1Value);
                String y1Value = lineElement.getAttributeValue(SVG_Y1);
                if (y1Value != null)
                    y1 = new Integer(y1Value);
                String x2Value = lineElement.getAttributeValue(SVG_X2);
                if (x2Value != null)
                    x2 = new Integer(x2Value);
                String y2Value = lineElement.getAttributeValue(SVG_Y2);
                if (y2Value != null)
                    y2 = new Integer(y2Value);

                lines.add(new Line(x1, y1, x2, y2));
            }
            Color color = parseSvgStrokeColor(oneLineElement.getAttribute(SVG_STROKE));
            int strokeWidth = parseSvgStrokeWidth(oneLineElement.getAttribute(SVG_STROKE_WIDTH));

            shape = new Cross(useRect.getLeft(), useRect.getTop(), useRect.getWidth(), useRect.getHeight(), color,
                    strokeWidth);
            // parse geopoint
        } else if (symbolId.equals(SVG_SYMBOL_ID_GEOPOINT)) {
            Element pointElement = symbol.getChild(SVG_SYMBOL_ID_GEOPOINT,
                    Namespace.getNamespace(ANNOTATION_IMAGE_NS));
            int x = new Integer(pointElement.getAttributeValue(SVG_X));
            int y = new Integer(pointElement.getAttributeValue(SVG_Y));
            double lat = new Double(pointElement.getAttributeValue(LAT));
            double lng = new Double(pointElement.getAttributeValue(LNG));
            String name = pointElement.getAttributeValue(NAME);

            shape = new GeoPoint(useRect.getLeft(), useRect.getTop(), useRect.getWidth(), useRect.getHeight(), name,
                    new Point(x, y), lat, lng);
        }
        return shape;
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<SemanticTag> parseLinkedResources(String linkedToResources) throws IOException {
        ArrayList<SemanticTag> semanticTags = null;
        Document document = null;
        try {
            semanticTags = new ArrayList<SemanticTag>();
            document = new SAXBuilder().build(new StringReader(linkedToResources));

            List<Element> resources = document.getRootElement().getChildren();
            for (Element e : resources) {
                if (e.getName().equals("linked-resource")) {
                    String title = e.getChildText("title", DUBLIN_CORE_NAMESPACE);
                    String type = e.getChildText("type", DUBLIN_CORE_NAMESPACE);
                    String seeAlso = e.getChild("seeAlso", RDFS_NAMESPACE).getAttributeValue("resource",
                            RDF_NAMESPACE);
                    String language = StringEscapeUtils
                            .unescapeXml(e.getChildText("language", DUBLIN_CORE_NAMESPACE));
                    String description = StringEscapeUtils
                            .unescapeXml(e.getChildText("description", DUBLIN_CORE_NAMESPACE));
                    semanticTags.add(new SemanticTag(title, type, language, description, seeAlso));
                }
            }

        } catch (JDOMException e) {
            logger.error("invalid linkedToResources", e);
        }

        return semanticTags;

    }

    /**
     * convert a rdf/xml representation into an <code>ImageAnnotation</code>. The representation
     * could contain more than one annotation. That is why we return a <code>Collection</code>.
     * 
     * @param rdfXml
     * @return collection of image annotations
     * @throws RDFParseException
     * @throws RDFHandlerException
     * @throws IOException
     */
    public static Collection<ImageAnnotation> fromRdfXml(String rdfXml)
            throws RDFParseException, RDFHandlerException, IOException {

        if (rdfXml == null || rdfXml.isEmpty())
            return new ArrayList<ImageAnnotation>();

        RdfXmlAnnotationHandler annotationRdfHandler = new RdfXmlAnnotationHandler();
        RDFParser parser = new RDFXMLParser();
        parser.setRDFHandler(annotationRdfHandler);
        parser.setVerifyData(true);
        parser.parse(new StringReader(rdfXml), "http://foo/bar");

        return annotationRdfHandler.getAnnotations();
    }
}