blob: 77ede9a98bbca06ca66dadf8ba275aa28a85675f [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2007, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
****************************************************************************/
package org.eclipse.gmf.runtime.diagram.ui.render.util;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionDimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gmf.runtime.common.core.command.FileModificationValidator;
import org.eclipse.gmf.runtime.common.core.util.StringStatics;
import org.eclipse.gmf.runtime.common.core.util.Trace;
import org.eclipse.gmf.runtime.diagram.core.preferences.PreferencesHint;
import org.eclipse.gmf.runtime.diagram.core.util.ViewUtil;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
import org.eclipse.gmf.runtime.diagram.ui.image.ImageFileFormat;
import org.eclipse.gmf.runtime.diagram.ui.image.PartPositionInfo;
import org.eclipse.gmf.runtime.diagram.ui.l10n.DiagramUIMessages;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramEditor;
import org.eclipse.gmf.runtime.diagram.ui.render.clipboard.DiagramGenerator;
import org.eclipse.gmf.runtime.diagram.ui.render.internal.DiagramUIRenderPlugin;
import org.eclipse.gmf.runtime.diagram.ui.util.DiagramEditorUtil;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.IMapMode;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.swt.widgets.Shell;
/**
* Implementation of a utility class able to export specified editparts to
* multiple image files, i.e. each file containing a tile of the diagram - all
* tiles combined will compose the complete image of specified editparts. An
* HTML file is generated which builds a table of tiled images of editparts.
* HTML file allows clients to view the complete image of specified editparts.
* The utility is useful to export huge diagrams to an image, when they fail to
* be exported to a single image file.
*
* <li><b>Note:</b> for tiled images method <code>#copyToImage(Diagram, IPath,
* ImageFileFormat, IProgressMonitor, PreferencesHint)}</code> returns a matrix, the
* dimension of which is equal to the total number of tiles rows and columns.
* Each cell of the matrix is a list of <code>PartPositionInfo</code>
* corresponding to the tile with the same index.</li>
*
* @author Alex Boyko
*
*/
public class CopyToHTMLImageUtil extends CopyToImageUtil {
/**
* A Map of image file formats to their corresponding safe tile sizes
*/
private HashMap<ImageFileFormat, Dimension> imageFormatToTileSizeMap = null;
/**
* The delimiter for tiled image filename table indices (filename +
* delimiter + row + delimiter + column)
*/
private String tileImageFileNameIndexDelimiter = StringStatics.UNDER_SCORE;
{
imageFormatToTileSizeMap = new HashMap<ImageFileFormat, Dimension>(
ImageFileFormat.VALUES.length);
imageFormatToTileSizeMap.put(ImageFileFormat.GIF, new Dimension(3000,
3000));
imageFormatToTileSizeMap.put(ImageFileFormat.BMP, new Dimension(3000,
3000));
imageFormatToTileSizeMap.put(ImageFileFormat.JPG, new Dimension(3000,
3000));
imageFormatToTileSizeMap.put(ImageFileFormat.JPEG, new Dimension(3000,
3000));
imageFormatToTileSizeMap.put(ImageFileFormat.PNG, new Dimension(3000,
3000));
imageFormatToTileSizeMap.put(ImageFileFormat.SVG, new Dimension(0, 0));
imageFormatToTileSizeMap.put(ImageFileFormat.PDF, new Dimension(0, 0));
}
/**
* Minimal size of the tile in pixels
*/
private static Dimension minimalTileSize = new Dimension(2, 2);
/*
* (non-Javadoc)
*
* @see org.eclipse.gmf.runtime.diagram.ui.render.util.CopyToImageUtil#copyToImage(org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart,
* org.eclipse.core.runtime.IPath,
* org.eclipse.gmf.runtime.diagram.ui.image.ImageFileFormat,
* org.eclipse.core.runtime.IProgressMonitor)
*/
public DiagramGenerator copyToImage(DiagramEditPart diagramEP,
IPath destination, ImageFileFormat format, IProgressMonitor monitor)
throws CoreException {
ExportInfo exportInfo = copyToImageAndReturnInfo(diagramEP, diagramEP
.getPrimaryEditParts(), destination, format, monitor);
/*
* Create the HTML file
*/
createHTMLFileForTiledImage(destination, exportInfo);
return exportInfo.diagramGenerator;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gmf.runtime.diagram.ui.render.util.CopyToImageUtil#copyToImage(org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart,
* java.util.List, org.eclipse.core.runtime.IPath,
* org.eclipse.gmf.runtime.diagram.ui.image.ImageFileFormat,
* org.eclipse.core.runtime.IProgressMonitor)
*/
public void copyToImage(DiagramEditPart diagramEP, List selection,
IPath destination, ImageFileFormat format, IProgressMonitor monitor)
throws CoreException {
ExportInfo exportInfo = copyToImageAndReturnInfo(diagramEP, selection,
destination, format, monitor);
/*
* Create the HTML file
*/
createHTMLFileForTiledImage(destination, exportInfo);
}
/**
* Generates image files and returns the HTML content as a String
*
* @param diagram diagram model
* @param destination a path to image files with common image file name
* @param format image format
* @param monitor progress monitor
* @return HTML content as a string
* @throws CoreException
*/
public String generateHTMLImage(Diagram diagram, IPath destination, ImageFileFormat format, IProgressMonitor monitor) throws CoreException {
ExportInfo exportInfo = null;
DiagramEditor openedDiagramEditor = DiagramEditorUtil
.findOpenedDiagramEditorForID(ViewUtil.getIdStr(diagram));
if (openedDiagramEditor != null) {
DiagramEditPart diagramEditPart = openedDiagramEditor
.getDiagramEditPart();
exportInfo = copyToImageAndReturnInfo(diagramEditPart,
diagramEditPart.getPrimaryEditParts(), destination, format,
monitor);
} else {
Shell shell = new Shell();
try {
DiagramEditPart diagramEditPart = createDiagramEditPart(
diagram, shell, null);
Assert.isNotNull(diagramEditPart);
exportInfo = copyToImageAndReturnInfo(diagramEditPart,
diagramEditPart.getPrimaryEditParts(), destination,
format, monitor);
} finally {
shell.dispose();
}
}
return createHTMLString(exportInfo);
}
/**
* Export the editparts to an image file or files of supplied image format
* depending on the logical size of the tile. The method return the number
* of created tiles as a <code>Point</code>, where x represents number of
* columns and y number of rows.
*
*
* @param gen
* diagram generator
* @param editParts
* editparts
* @param destinationFolder
* destination folder for image files
* @param fileName
* common part of image file names
* @param imageFormat
* image file format
* @param logTileWidth
* tile width in logical units (not device units)
* @param logTileHeight
* tile height in logical units (not device units)
* @param monitor
* progress monitor
* @param mm map-mode used by the diagram
* @return <code>Point</code>, where x represents number of columns and y
* represents number of rows
* @throws Error
* @throws CoreException
*/
private ExportInfo exportImage(DiagramGenerator gen, List editParts,
IPath destinationFolder, String fileName,
ImageFileFormat imageFormat, Dimension logTileSize,
IProgressMonitor monitor, IMapMode mm) throws Error, CoreException {
org.eclipse.swt.graphics.Rectangle diagramArea = gen
.calculateImageRectangle(editParts);
org.eclipse.swt.graphics.Rectangle sourceRect = null;
int rows = 1, columns = 1;
PrecisionDimension minimalLogicalTileSize = new PrecisionDimension(minimalTileSize);
mm.DPtoLP(minimalLogicalTileSize);
int logTileWidth = logTileSize.width;
int logTileHeight = logTileSize.height;
if (logTileWidth <= 0) {
logTileWidth = diagramArea.width;
}
if (logTileHeight <= 0) {
logTileHeight = diagramArea.height;
}
logTileWidth = Math.max(logTileWidth, minimalLogicalTileSize.width);
logTileHeight = Math.max(logTileHeight, minimalLogicalTileSize.height);
columns = (int) Math.ceil(diagramArea.width / (float)logTileWidth);
rows = (int) Math.ceil(diagramArea.height / (float)logTileHeight);
int jobsToDo = 6 * columns * rows + 1;
monitor
.beginTask(
DiagramUIMessages.CopyToHTMLImageTask_exportingToHTML,
jobsToDo);
for (int i = 0; i < rows; i++) {
int sourceY = i * logTileHeight + diagramArea.y;
int sourceHeight = i != rows - 1 ? logTileHeight : Math.max(diagramArea.height
- logTileHeight * i, minimalLogicalTileSize.height);
for (int j = 0; j < columns; j++) {
int sourceX = diagramArea.x + j * logTileWidth;
int sourceWidth = j != columns - 1 ? logTileWidth : Math.max(diagramArea.width
- logTileWidth * j, minimalLogicalTileSize.width);
String tileFileName = fileName
+ getTileImageFileNameIndexDelimiter() + i
+ getTileImageFileNameIndexDelimiter() + j
+ StringStatics.PERIOD
+ imageFormat.getName().toLowerCase();
IPath tilePath = new Path(destinationFolder.toOSString())
.append(tileFileName);
monitor
.subTask(DiagramUIMessages.CopyToHTMLImageTask_generateImageFile
+ tilePath);
sourceRect = new org.eclipse.swt.graphics.Rectangle(sourceX,
sourceY, sourceWidth, sourceHeight);
copyToImage(gen, editParts, sourceRect, tilePath, imageFormat, monitor);
}
}
return new ExportInfo(gen, new Point(columns, rows), fileName, destinationFolder, imageFormat, new PrecisionDimension(logTileWidth, logTileHeight));
}
/**
* Creates an HTML file that contains a table of image tiles.
*
* @param htmlFileLocation
* location of the HTML file
* @param fileName
* common part of the file name for image tiles
* @param fileExtension
* extension of image files
* @param numRows
* number of rows for the table
* @param numColumns
* number of columns for the table
* @return <code>Status.OK_STATUS</code> if everything went without errors
*/
private IStatus createHTMLFileForTiledImage(IPath htmlFileLocation,
ExportInfo info) {
try {
BufferedWriter out = new BufferedWriter(new FileWriter(
htmlFileLocation.toOSString()));
out
.write(createHTMLString(info));
out.close();
IFile file = ResourcesPlugin.getWorkspace().getRoot()
.getFileForLocation(htmlFileLocation);
if (file != null) {
file.refreshLocal(IResource.DEPTH_ZERO, null);
return FileModificationValidator
.approveFileModification(new IFile[] { file });
}
} catch (IOException e) {
return Status.CANCEL_STATUS;
} catch (CoreException e) {
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
private String createHTMLString(ExportInfo info) {
Assert.isNotNull(info);
StringBuffer buffer = new StringBuffer(
"<html>\n<body>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" align=\"LEFT\">\n");//$NON-NLS-1$
String commonFileNamePath = new Path("file://", info.directory.toString()).append(info.commonTileFileName).makeAbsolute().toString(); //$NON-NLS-1$
for (int i = 0; i < info.tiles.y; i++) {
buffer.append("<tr>\n");//$NON-NLS-1$
for (int j = 0; j < info.tiles.x; j++) {
String fileName = commonFileNamePath
+ getTileImageFileNameIndexDelimiter() + i
+ getTileImageFileNameIndexDelimiter() + j
+ StringStatics.PERIOD
+ info.imageFormat.getName().toLowerCase();
if (ImageFileFormat.SVG.equals(info.imageFormat)) {
buffer.append("\t<td>\n\t\t<object data=\"");//$NON-NLS-1$
buffer.append(fileName);
buffer.append("\" type=\"image/svg+xml\" width=\"");//$NON-NLS-1$
buffer.append(info.tileSize.width);
buffer.append("\" height=\""); //$NON-NLS-1$
buffer.append(info.tileSize.height);
buffer.append("\">\n");//$NON-NLS-1$
buffer.append("\t\t<embed src=\"");//$NON-NLS-1$
buffer.append(fileName);
buffer.append("\" type=\"image/svg+xml\" width=\"");//$NON-NLS-1$
buffer.append(info.tileSize.width);
buffer.append("\" height=\"");//$NON-NLS-1$
buffer.append(info.tileSize.height);
buffer.append("\"/></td>\n"); //$NON-NLS-1$
} else {
buffer.append("\t<td><img src=\"");//$NON-NLS-1$
buffer.append(fileName);
buffer.append("\"/></td>\n");//$NON-NLS-1$
}
}
buffer.append("</tr>\n");//$NON-NLS-1$
}
buffer.append("</table>\n</body>\n</html>");//$NON-NLS-1$
return buffer.toString();
}
/**
* Gets the tile image file name indices delimiter
*
* @return the delimiter
*/
public String getTileImageFileNameIndexDelimiter() {
return tileImageFileNameIndexDelimiter;
}
/**
* Sets the tile image file name indices delimiter. The new value must not
* be empty string or null.
*
* @param tileImageFileNameIndexDelimiter
* new delimiter value
*/
public void setTileImageFileNameIndexDelimiter(
String tileImageFileNameIndexDelimiter) {
if (tileImageFileNameIndexDelimiter == null
|| tileImageFileNameIndexDelimiter.length() == 0)
throw new IllegalArgumentException();
this.tileImageFileNameIndexDelimiter = tileImageFileNameIndexDelimiter;
}
/**
* Gets the map of image file formats to their corresponding safe tile sizes
*
* @return the map
*/
public HashMap<ImageFileFormat, Dimension> getImageFormatToTileSizeMap() {
return imageFormatToTileSizeMap;
}
/**
* Defines the data structure for the info of exported diagram
*
* @author Alex Boyko
*
*/
public class ExportInfo {
final public DiagramGenerator diagramGenerator;
final public Point tiles;
final public String commonTileFileName;
final public IPath directory;
final public ImageFileFormat imageFormat;
final public Dimension tileSize;
ExportInfo(DiagramGenerator diagramGenerator, Point tiles,
String commonTileFileName, IPath directory, ImageFileFormat imageFormat, Dimension tileSize) {
this.diagramGenerator = diagramGenerator;
this.tiles = tiles;
this.commonTileFileName = commonTileFileName;
this.directory = directory;
this.imageFormat = imageFormat;
this.tileSize = tileSize;
}
}
/**
* Exports the diagram to tiled images files and returns the info about the
* exported diagram (total number of rows and columns for tiles, generator
* and common part of image files name, i.e. without indices)
*
* @param diagramEP
* diafram editpart
* @param selection
* selected editparts
* @param destination
* destination path
* @param format
* image format
* @param monitor
* progress monitor
* @return <code>ExportInfo</code> of images
* @throws CoreException
*/
public ExportInfo copyToImageAndReturnInfo(DiagramEditPart diagramEP,
List selection, IPath destination, ImageFileFormat format,
IProgressMonitor monitor) throws CoreException {
DiagramGenerator gen = getDiagramGenerator(diagramEP, format);
IMapMode mm = MapModeUtil.getMapMode(
diagramEP.getFigure());
Dimension dimension = (Dimension) mm.DPtoLP(
new PrecisionDimension(imageFormatToTileSizeMap.get(format)));
/*
* Destination is the destination for HTML file. Hence we need to come
* up with the names for image file(s)
*/
IPath destinationFolder = destination.removeLastSegments(1);
String fileName = destination.removeFileExtension().lastSegment();
/*
* Create image tile files and get the number of tiles, i.e. number of
* rows and columns
*/
ExportInfo info = exportImage(gen, selection, destinationFolder, fileName,
format, dimension, monitor, mm);
/*
* The tile dimension returned with the ExportInfo object is in logical units. We need to translate it
* to the device units - pixels. We can't simply use the pixel tile size from the imageFormatToTilesSizeMap
* since tile size (x,y), where x<=0 and y<=0 will use the diagram width and/or height
*/
mm.LPtoDP(info.tileSize);
return info;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gmf.runtime.diagram.ui.render.util.CopyToImageUtil#copyToImage(org.eclipse.gmf.runtime.notation.Diagram,
* org.eclipse.core.runtime.IPath,
* org.eclipse.gmf.runtime.diagram.ui.image.ImageFileFormat,
* org.eclipse.core.runtime.IProgressMonitor,
* org.eclipse.gmf.runtime.diagram.core.preferences.PreferencesHint)
*/
public List<List<List<PartPositionInfo>>> copyToImage(Diagram diagram, IPath destination,
ImageFileFormat format, IProgressMonitor monitor,
PreferencesHint preferencesHint) throws CoreException {
Trace.trace(DiagramUIRenderPlugin.getInstance(),
"Copy diagram to Image " + destination + " as " + format); //$NON-NLS-1$ //$NON-NLS-2$
ExportInfo exportInfo = null;
DiagramEditor openedDiagramEditor = DiagramEditorUtil
.findOpenedDiagramEditorForID(ViewUtil.getIdStr(diagram));
if (openedDiagramEditor != null) {
DiagramEditPart diagramEditPart = openedDiagramEditor
.getDiagramEditPart();
exportInfo = copyToImageAndReturnInfo(diagramEditPart,
diagramEditPart.getPrimaryEditParts(), destination, format,
monitor);
} else {
Shell shell = new Shell();
try {
DiagramEditPart diagramEditPart = createDiagramEditPart(
diagram, shell, preferencesHint);
Assert.isNotNull(diagramEditPart);
exportInfo = copyToImageAndReturnInfo(diagramEditPart,
diagramEditPart.getPrimaryEditParts(), destination,
format, monitor);
} finally {
shell.dispose();
}
}
/*
* Transform the list of partsInfo for a diagram exported to a single
* image into a matrix of partsInfo lists, the rows of columns of which
* correspond to rows and columns of tiled images
*/
return createTilesPartsInfoList(exportInfo);
}
/**
* Creates and returns a matrix each cell of which correspond to a tiled
* image with the same index. Each cell of a matrix, therefore, is a
* partsInfo list for a tiled image (chunk of a diagram) corresponding to
* the index of the cell.
*
* @param exportInfo
* export info
* @param partsInfo
* partsInfo list for the whole diagram (single image assumed)
* @param format
* image format
* @return A matrix of partsInfo lists, where each cell contains partsInfo
* list for a tiled image of the same index
*/
public static List<List<List<PartPositionInfo>>> createTilesPartsInfoList(
ExportInfo exportInfo) {
List<PartPositionInfo> partsInfo = exportInfo.diagramGenerator.getDiagramPartInfo();
List<List<List<PartPositionInfo>>> tilesPartsInfoList = Collections.emptyList();
if (exportInfo.diagramGenerator != null && exportInfo.tiles.x > 0
&& exportInfo.tiles.y > 0 && partsInfo != null) {
/*
* Create the matrix
*/
tilesPartsInfoList = initializeTilesPartsInfoList(
exportInfo.tiles.y, exportInfo.tiles.x);
/*
* If it's 1 tile only than just use partsInfo list already created
* for it
*/
if (exportInfo.tiles.x == 1 && exportInfo.tiles.y == 1) {
tilesPartsInfoList.get(0).set(0, partsInfo);
} else {
Dimension tileSize = exportInfo.tileSize;
Rectangle defaultTile = new Rectangle(new Point(), tileSize);
/*
* Iterate through each part and split it in different tiles if
* necessary
*/
for (PartPositionInfo info : partsInfo) {
if (info.getPolyline() == null) {
/*
* It's a shape
*/
Point startCell = new Point();
startCell.x = info.getPartX() / tileSize.width;
startCell.y = info.getPartY() / tileSize.height;
Point endCell = new Point();
endCell.x = (info.getPartX() + info.getPartWidth())
/ tileSize.width;
endCell.y = (info.getPartY() + info.getPartHeight())
/ tileSize.height;
for (int i = startCell.y; i <= endCell.y; i++) {
for (int j = startCell.x; j <= endCell.x; j++) {
Rectangle shapeRect = new Rectangle(info
.getPartX(), info.getPartY(), info
.getPartWidth(), info.getPartHeight());
shapeRect.translate(-j * tileSize.width, -i
* tileSize.height);
Rectangle intersection = shapeRect
.intersect(defaultTile);
PartPositionInfo newInfo = new PartPositionInfo();
newInfo.setView(info.getView());
newInfo.setSemanticElement(info
.getSemanticElement());
newInfo.setPartHeight(intersection.height);
newInfo.setPartWidth(intersection.width);
newInfo.setPartX(intersection.x);
newInfo.setPartY(intersection.y);
tilesPartsInfoList.get(i).get(j).add(newInfo);
}
}
} else {
/*
* It's a connection. Connections location info is a
* polygon with points in Clock-Wise (CW) order. We need
* to split the connection in line segments to process
* its info. (@see
* DiagramGenerator#getDiagramPartInfo()) First half of
* points in the list are points offset from the
* original connection bend points in the same
* direction. We need to consider line segments of the
* connection. Hence we need to consider original line
* segment and the offset one and split it between
* different tiles
*/
for (int i = 1; i <= info.getPolyline().size() / 2; i += 2) {
/*
* Upper points are the end points of the offset
* line segment
*/
Point upperStartPt = info.getPolyline().get(i - 1);
Point upperEndPt = info.getPolyline().get(i);
/*
* Lower points are the end points of the original
* line segment
*/
Point lowerEndPt = info.getPolyline().get(
info.getPolyline().size() - 1 - i);
Point lowerStartPt = info.getPolyline().get(
info.getPolyline().size() - 1 - (i + 1));
/*
* A set of cells laying inside and intersected by
* the polygon formed by the end points of the
* original and offset line segments
*/
HashSet<Point> cells = new HashSet<Point>();
/*
* Create maps of line segments (vectors) created by
* intersections of edges of the polygon with tile
* borders. Cell indices are mapped to line segments
* (vectors). The line segments or vectors
* directions are CW such that they match the
* direction of the polygon points in the polyLine
* list from the single image partsInfo list.
*/
HashMap<Point, LineSeg> upperLineSegs = getMapOfLineSegments(
upperStartPt, upperEndPt, tileSize, cells);
HashMap<Point, LineSeg> lowerLineSegs = getMapOfLineSegments(
lowerStartPt, lowerEndPt, tileSize, cells);
HashMap<Point, LineSeg> upperToLowerLineSegs = getMapOfLineSegments(
upperEndPt, lowerStartPt, tileSize, cells);
HashMap<Point, LineSeg> lowerToUpperLineSegs = getMapOfLineSegments(
lowerEndPt, upperStartPt, tileSize, cells);
/*
* Create a connection polygon for each tile
* intersected by the polygon. We assume there are
* no cells completely contained within this
* polygon!!! Tile dimension are much larger than
* the offset of the secondary (referred as offset)
* line segment
*/
for (Iterator<Point> ptItr = cells.iterator(); ptItr
.hasNext();) {
Point cell = ptItr.next();
LineSeg upperSeg = upperLineSegs.get(cell);
LineSeg lowerSeg = lowerLineSegs.get(cell);
LineSeg upperToLowerSeg = upperToLowerLineSegs
.get(cell);
LineSeg lowerToUpperSeg = lowerToUpperLineSegs
.get(cell);
List<LineSeg> cwListOfLineSegs = new ArrayList<LineSeg>(
4);
if (upperSeg != null) {
cwListOfLineSegs.add(upperSeg);
}
if (upperToLowerSeg != null) {
cwListOfLineSegs.add(upperToLowerSeg);
}
if (lowerSeg != null) {
cwListOfLineSegs.add(lowerSeg);
}
if (lowerToUpperSeg != null) {
cwListOfLineSegs.add(lowerToUpperSeg);
}
PartPositionInfo newInfo = new PartPositionInfo();
newInfo.setSemanticElement(info
.getSemanticElement());
newInfo.setPolyline(createCellPolyline(
tileSize, cwListOfLineSegs));
tilesPartsInfoList.get(cell.y).get(cell.x).add(
newInfo);
}
}
}
}
}
}
return tilesPartsInfoList;
}
/**
* Creates a matrix of the dimension given through rows and columns
* parameters. Each cell of the matrix is a <code>PartPositionInfo</code>
* list
*
* @param rows
* roes
* @param columns
* columns
* @return the matrix
*/
private static List<List<List<PartPositionInfo>>> initializeTilesPartsInfoList(
int rows, int columns) {
List<List<List<PartPositionInfo>>> tilesPartsInfoList = new ArrayList<List<List<PartPositionInfo>>>(
rows);
for (int i = 0; i < rows; i++) {
List<List<PartPositionInfo>> row = new ArrayList<List<PartPositionInfo>>(
columns);
for (int j = 0; j < columns; j++) {
row.add(new LinkedList<PartPositionInfo>());
}
tilesPartsInfoList.add(row);
}
return tilesPartsInfoList;
}
/**
* A class to make comparisons between points laying on the same line.
* Comparison criteria is the closeness of points to the segmentOrigin point
*
* @author aboyko
*
*/
private static class LineSegmentPointsComparator implements Comparator<Point> {
private Point segmentOrigin;
public LineSegmentPointsComparator(Point segmentOrigin) {
this.segmentOrigin = segmentOrigin;
}
public int compare(Point p1, Point p2) {
if (p1.x == p2.x) {
return Math.abs(p1.y - segmentOrigin.y)
- Math.abs(p2.y - segmentOrigin.y);
}
return Math.abs(p1.x - segmentOrigin.x)
- Math.abs(p2.x - segmentOrigin.x);
}
}
/**
* Creates a map of cell indices to line segments. Cell indices stand for
* cells intersected by the line segments originating at parameter
* startPoint and ending at endPoint parameter and line segment is the chunk
* of the original line segment laying within a particular tile. Coordinates
* of the start and end point of the segment are relative to the tile. All
* intersected tiles are inserted into <code>cells</code> set
*
* @param startPoint
* line segment's start point
* @param endPoint
* line segment's end point
* @param tileSize
* the tile dimension
* @param cells
* set of cells to be updated
* @return the map of cell indices to line segments contained within
* corresponding cells
*/
private static HashMap<Point, LineSeg> getMapOfLineSegments(Point startPoint,
Point endPoint, Dimension tileSize, HashSet<Point> cells) {
HashMap<Point, LineSeg> map = new HashMap<Point, LineSeg>();
Point startCell = new Point();
startCell.x = startPoint.x / tileSize.width;
startCell.y = startPoint.y / tileSize.height;
Point endCell = new Point();
endCell.x = endPoint.x / tileSize.width;
endCell.y = endPoint.y / tileSize.height;
if (startCell.equals(endCell)) {
/*
* If within the same cell then just translate the points, update
* the cells set and return
*/
map.put(startCell, new LineSeg(startPoint.getCopy().translate(
-startCell.x * tileSize.width,
-startCell.y * tileSize.height), endPoint.getCopy()
.translate(-startCell.x * tileSize.width,
-startCell.y * tileSize.height)));
cells.add(startCell);
} else {
/*
* If the end points are not within the same cell then calculate the
* line equation and, using that equation, calculate the
* intersection points with tile borders
*/
double[] equation = LineSeg.getLineEquation(startPoint.x,
startPoint.y, endPoint.x, endPoint.y);
List<Point> linePoints = new ArrayList<Point>(2
+ Math.abs(startCell.x - endCell.x)
+ Math.abs(startCell.y - endCell.y));
if (equation[1] != 0) {
for (int x = tileSize.width
* (Math.min(startCell.x, endCell.x) + 1); x <= tileSize.width
* Math.max(startCell.x, endCell.x); x += tileSize.width) {
linePoints.add(new Point(x, Math
.round((equation[2] - equation[0] * x)
/ equation[1])));
}
}
if (equation[0] != 0) {
for (int y = tileSize.height
* (Math.min(startCell.y, endCell.y) + 1); y <= tileSize.height
* Math.max(startCell.y, endCell.y); y += tileSize.height) {
linePoints.add(new Point(Math
.round((equation[2] - equation[1] * y)
/ equation[0]), y));
}
}
/*
* Sort the intersection points such that points closer to the start
* of the original line segment are located at the start of the
* list.
*/
Collections.sort(linePoints, new CopyToHTMLImageUtil.LineSegmentPointsComparator(
startPoint));
/*
* Add the ends of the original line segments to the start and end
* of the list
*/
linePoints.add(0, startPoint.getCopy());
linePoints.add(endPoint.getCopy());
/*
* Now calculate the indices of the cells intersected by the
* original line segment and fill the map with the values of line
* segments laying in the corresponding cells. Line segments are
* relative to the corresponding cell
*/
Point currentCell = startCell;
Iterator<Point> pointsItr = linePoints.iterator();
Point originPoint = pointsItr.next();
for (; pointsItr.hasNext();) {
Point terminusPoint = pointsItr.next();
Point translatedOrigin = originPoint.getCopy().translate(
-currentCell.x * tileSize.width,
-currentCell.y * tileSize.height);
Point translatedTerminus = terminusPoint.getCopy().translate(
-currentCell.x * tileSize.width,
-currentCell.y * tileSize.height);
map.put(currentCell.getCopy(), new LineSeg(translatedOrigin,
translatedTerminus));
cells.add(currentCell.getCopy());
if (translatedTerminus.x == 0) {
currentCell.x--;
}
if (translatedTerminus.y == 0) {
currentCell.y--;
}
if (translatedTerminus.x == tileSize.width) {
currentCell.x++;
}
if (translatedTerminus.y == tileSize.height) {
currentCell.y++;
}
originPoint = terminusPoint;
}
}
return map;
}
/**
* It is assumed that segments parameter contains either line segments where
* the end of one segments is the start of the next one or the end of one
* segments and the start of the next one lay on the cell's edges. The
* method in this case will create a polygon by connecting line segments
* that don't meet each other via cell's edges in the clock wise direction
*
* @param cellSize
* dimension of the cell
* @param segments
* list of line segments
* @return a list of polygon points.
*/
private static List<Point> createCellPolyline(Dimension cellSize,
List<LineSeg> segments) {
List<Point> result = new LinkedList<Point>();
if (segments.size() > 0) {
LineSeg currentSegment = segments.get(0);
for (int i = 0; i < segments.size(); i++) {
LineSeg nextSegment = segments.get((i + 1) % segments.size());
result.add(currentSegment.getTerminus());
if (!currentSegment.getTerminus().equals(
nextSegment.getOrigin())) {
List<Point> connectingPoints = connectLineSegmentsEndsViaCellEdges(
cellSize, currentSegment, nextSegment);
for (Iterator<Point> itr = connectingPoints.iterator(); itr
.hasNext();) {
result.add(itr.next());
}
result.add(nextSegment.getOrigin());
}
currentSegment = nextSegment;
}
result.add(result.get(0));
}
return result;
}
/**
* Creates a list of connecting points that help to connect 2 line segments
* via cell's edges clock wise
*
* @param cellSize
* dimesnion of the cell
* @param lineSeg1
* 1st line segment
* @param lineSeg2
* 2nd line segment
* @return the list of connecting points
*/
private static List<Point> connectLineSegmentsEndsViaCellEdges(Dimension cellSize,
LineSeg lineSeg1, LineSeg lineSeg2) {
Point current = lineSeg1.getTerminus();
Point next = lineSeg2.getOrigin();
List<Point> result = new LinkedList<Point>();
List<Point> cwCellVertices = createClockwiseListOfCellVertices(cellSize);
int currentIdx = indexOfCellEdgePointClockwise(cellSize, current);
int nextIdx = indexOfCellEdgePointClockwise(cellSize, next);
for (int i = currentIdx; i != nextIdx;) {
result.add(cwCellVertices.get(i));
i = (i + 1) % cwCellVertices.size();
}
return result;
}
/**
* Creates a list of rectangular cell vertices in the clock wise order
*
* @param cellSize
* cell's dimension
* @return a list of vertices in the clock wise order
*/
private static List<Point> createClockwiseListOfCellVertices(Dimension cellSize) {
List<Point> cellVertices = new ArrayList<Point>(4);
cellVertices.add(new Point());
cellVertices.add(new Point(cellSize.width, 0));
cellVertices.add(new Point(cellSize.width, cellSize.height));
cellVertices.add(new Point(0, cellSize.height));
return cellVertices;
}
/**
* Returns the index of a point in the list of cell's vertices order in the
* clock wise manner
*
* @param cellSize
* cell's dimension
* @param pt
* point
* @return index of the in the list of clock wise vertices
*/
private static int indexOfCellEdgePointClockwise(Dimension cellSize, Point pt) {
if (pt.x == 0) {
return 0;
} else if (pt.y == 0) {
return 1;
} else if (pt.x == cellSize.width) {
return 2;
} else if (pt.y == cellSize.height) {
return 3;
}
return 0;
}
}