diff --git a/SeatingMap-addon/pom.xml b/SeatingMap-addon/pom.xml index 0e3aff3..f4be9f9 100644 --- a/SeatingMap-addon/pom.xml +++ b/SeatingMap-addon/pom.xml @@ -6,7 +6,7 @@ SeatingMap jar 0.1-SNAPSHOT - Seating map add-on for showing where people are seated at the office + Seating map 3 diff --git a/SeatingMap-addon/src/main/java/org/percepta/mgrankvi/SeatingMap.java b/SeatingMap-addon/src/main/java/org/percepta/mgrankvi/SeatingMap.java index eb6fc5f..66fce85 100644 --- a/SeatingMap-addon/src/main/java/org/percepta/mgrankvi/SeatingMap.java +++ b/SeatingMap-addon/src/main/java/org/percepta/mgrankvi/SeatingMap.java @@ -58,7 +58,6 @@ public void setVisibleFloor(int floor) { @Override public void itemClick(String roomId, String tableId) { - System.out.println("Got item selection!"); Room clickedRoom = floors.get(visibleFloor).getRoomById(roomId); Table clickedTable = clickedRoom.getTableById(tableId); @@ -75,16 +74,54 @@ public void itemClick(String roomId, String tableId) { }); } + /** + * Add a room to the map for given floor from lines. + * + * @param floor + * floor to add room to + * @param lines + * lines of room + * @return created room object + */ public Room addRoom(int floor, List lines) { FloorMap map = getFloor(floor); return map.addRoom(lines); } + /** + * Add room for given floor to map. + * + * @param floor + * floor to add room to + * @param room + * room to add + */ + public void addRoom(int floor, Room room) { + FloorMap map = getFloor(floor); + map.addComponent(room); + } + + /** + * Add lines to map. These will be drawn, but won't have any special + * functionality. + * + * @param floor + * floor to add lines to + * @param lines + * lines to add + */ public void addLines(int floor, List lines) { FloorMap map = getFloor(floor); map.addLines(lines); } + /** + * Add listener to be notified of click selection for room and table in map. + * + * @param listener + * selection listener + * @return handler to remove listener with + */ public RemoveHandler addSelectionListener(SelectionListener listener) { selectionEvents.add(listener); return () -> selectionEvents.remove(listener); @@ -101,32 +138,6 @@ public void setAutoToggleTableName(boolean autoToggleName) { this.autoToggleName = autoToggleName; } - /** - * Returns first match for name - * - * @param name - * Name to search for - * @return First match or null - */ - private SearchResult getSingleByName(String name) { - SearchResult result = new SearchResult(); - for (FloorMap floor : floors.values()) { - for (Room room : floor.getRooms()) { - for (Table table : room.getTables()) { - if (table.getName().toLowerCase() - .contains(name.toLowerCase())) { - result.setFloor(floor); - result.setRoom(room); - result.setTable(table); - return result; - } - } - } - } - - return null; - } - /** * Get the first match for name * @@ -327,4 +338,30 @@ private FloorMap getFloor(int floor) { } return map; } + + /** + * Returns first match for name + * + * @param name + * Name to search for + * @return First match or null + */ + private SearchResult getSingleByName(String name) { + SearchResult result = new SearchResult(); + for (FloorMap floor : floors.values()) { + for (Room room : floor.getRooms()) { + for (Table table : room.getTables()) { + if (table.getName().toLowerCase() + .contains(name.toLowerCase())) { + result.setFloor(floor); + result.setRoom(room); + result.setTable(table); + return result; + } + } + } + } + + return null; + } } diff --git a/SeatingMap-addon/src/main/java/org/percepta/mgrankvi/util/ImageToLines.java b/SeatingMap-addon/src/main/java/org/percepta/mgrankvi/util/ImageToLines.java index b95ab3a..db5039d 100644 --- a/SeatingMap-addon/src/main/java/org/percepta/mgrankvi/util/ImageToLines.java +++ b/SeatingMap-addon/src/main/java/org/percepta/mgrankvi/util/ImageToLines.java @@ -1,11 +1,5 @@ package org.percepta.mgrankvi.util; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import org.apache.commons.codec.binary.Base64; -import org.percepta.mgrankvi.client.geometry.Line; -import org.percepta.mgrankvi.client.geometry.Point; - import javax.imageio.ImageIO; import java.awt.Color; import java.awt.Graphics2D; @@ -15,12 +9,21 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import java.util.Set; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.apache.commons.codec.binary.Base64; +import org.percepta.mgrankvi.client.geometry.Line; +import org.percepta.mgrankvi.client.geometry.Point; + /** - * Utility to get the lines in a image file to generate lines for the VisibilityMap. + * Utility to get the lines in a image file to generate lines for the + * VisibilityMap. * * Note! Horizontal lines only get the left point marked by the Harris corner, * so a light colored crossing is needed to get the right side! @@ -32,7 +35,8 @@ public class ImageToLines { /** * Get lines for given image. * - * @param imageFile File containing "map" to generate lines from + * @param imageFile + * File containing "map" to generate lines from * @return List of lines found in image. */ public List getLines(String imageFile) { @@ -53,17 +57,71 @@ public List getLines(String imageFile, Point offset) { // Set offset for lines lines.forEach(line -> { - line.start = new Point(line.start.getX()+offset.getX(), line.start.getY()+offset.getY()); - line.end = new Point(line.end.getX()+offset.getX(), line.end.getY()+offset.getY()); + line.start = new Point(line.start.getX() + offset.getX(), + line.start.getY() + offset.getY()); + line.end = new Point(line.end.getX() + offset.getX(), + line.end.getY() + offset.getY()); }); return lines; } + public List> getLineGroups(String imageFile) { + List lines = getLines(imageFile); + + return collectLinesToConvexPolygonGroup(lines, imageFile); + } + + public List> getLineGroups(String imageFile, Point offset) { + List lines = getLines(imageFile, offset); + + return collectLinesToConvexPolygonGroup(lines, imageFile); + } + + private List> collectLinesToConvexPolygonGroup(List lines, + String file) { + LinkedList linesToHandle = new LinkedList<>(lines); + List> groups = new ArrayList<>(); + + while (!linesToHandle.isEmpty()) { + List group = new ArrayList<>(); + Line pop = linesToHandle.pop(); + group.add(pop); + + Point start = pop.start; + Point current = pop.end; + + while (!current.equals(start)) { + Line nextLine = null; + for (Line line : linesToHandle) { + if (line.start.equals(current) + || line.end.equals(current)) { + nextLine = line; + break; + } + } + if (nextLine == null) { + String msg = String.format( + "No continuation line found! Check that all points create a convex polygon in image %s", + file); + throw new IllegalArgumentException(msg); + } + group.add(nextLine); + linesToHandle.remove(nextLine); + current = nextLine.end.equals(current) ? nextLine.start + : nextLine.end; + } + groups.add(group); + } + + return groups; + } + /** * Load the image from classpath or file system. * - * @param filename File to get image for. + * @param filename + * File to get image for. * @return File or null if not found. */ private BufferedImage getImage(String filename) { @@ -83,10 +141,10 @@ private BufferedImage getImage(String filename) { } /** - * Get corner points for image. - * Uses HarrisFast corner detection. + * Get corner points for image. Uses HarrisFast corner detection. * - * @param image Image to search for points + * @param image + * Image to search for points * @return */ private static List getCornerPoints(BufferedImage image) { @@ -102,19 +160,22 @@ private static List getCornerPoints(BufferedImage image) { } /** - * Generate lines from found points by referring to the image to check if a line should be drawn. + * Generate lines from found points by referring to the image to check if a + * line should be drawn. * * @param image * @param points * @return */ - private List getLinesForPoints(BufferedImage image, List points) { + private List getLinesForPoints(BufferedImage image, + List points) { List lines = Lists.newLinkedList(); List pointsLeft = Lists.newArrayList(points); - //Luminance threshold + // Luminance threshold double luminance = 25.0; - // Iterate from point to point and reference to image to see if we have a line between the points. + // Iterate from point to point and reference to image to see if we have + // a line between the points. for (Point p : points) { pointsLeft.remove(p); for (Point p2 : pointsLeft) { @@ -127,7 +188,8 @@ private List getLinesForPoints(BufferedImage image, List points) { min = Math.min(p.getY(), p2.getY()); max = Math.max(p.getY(), p2.getY()); for (double y = min; y < max; y++) { - if (getLuminance(image.getRGB((int) p.getX(), (int) y)) > luminance) { + if (getLuminance(image.getRGB((int) p.getX(), + (int) y)) > luminance) { foundLine = false; break; } @@ -137,13 +199,15 @@ private List getLinesForPoints(BufferedImage image, List points) { min = Math.min(p.getX(), p2.getX()); max = Math.max(p.getX(), p2.getX()); for (double x = min; x < max; x++) { - if (getLuminance(image.getRGB((int) x, (int) p.getY())) > luminance) { + if (getLuminance(image.getRGB((int) x, + (int) p.getY())) > luminance) { foundLine = false; break; } } } else { - // Check line with a slope (eg. not fully vertical or horizontal) + // Check line with a slope (eg. not fully vertical or + // horizontal) Double m = getSlope(p, p2); Double b = intercept(p, m); @@ -151,8 +215,10 @@ private List getLinesForPoints(BufferedImage image, List points) { max = Math.max(p.getX(), p2.getX()); for (double x = min; x <= max; x += 0.1) { double y = m * x + b; - // Not on a line if even one non black pixel is found on line - if (getLuminance(image.getRGB((int) x, (int) y)) > luminance) { + // Not on a line if even one non black pixel is found on + // line + if (getLuminance( + image.getRGB((int) x, (int) y)) > luminance) { foundLine = false; break; } @@ -175,8 +241,10 @@ private List getLinesForPoints(BufferedImage image, List points) { /** * Calculate the slope for the line between two points * - * @param a Point A - * @param b Point B + * @param a + * Point A + * @param b + * Point B * @return Slope of line */ private static Double getSlope(Point a, Point b) { @@ -201,14 +269,18 @@ private static Double intercept(Point p, Double slope) { /** * Scan for corners in image using the Harris Fast Scan algorithm. * - * @param image Image to search for corners + * @param image + * Image to search for corners * @return corners found */ private static List getCorners(BufferedImage image) { - if (image == null) return Lists.newArrayList(); - int width = image.getWidth(); // largeur de l'image - int height = image.getHeight(); // hauteur de l'image - int[][] input = new int[width][height]; // tableau 2D [x][y] contenant l'image en niveau de gris (0-255) + if (image == null) + return Lists.newArrayList(); + int width = image.getWidth(); // largeur de l'image + int height = image.getHeight(); // hauteur de l'image + int[][] input = new int[width][height]; // tableau 2D [x][y] contenant + // l'image en niveau de gris + // (0-255) for (int i = 0; i < width - 1; i++) { for (int j = 0; j < height - 1; j++) { @@ -219,8 +291,8 @@ private static List getCorners(BufferedImage image) { ////////////////////// double sigma = 1.2; // parametre du filtre gaussien - double k = 0.06; // parametre de la formule de la mesure - int spacing = 2; // minimun distance between 2 corners + double k = 0.06; // parametre de la formule de la mesure + int spacing = 2; // minimun distance between 2 corners // Init HarrisFast hf = new HarrisFast(input, width, height); @@ -228,21 +300,24 @@ private static List getCorners(BufferedImage image) { hf.filter(sigma, k, spacing); List corners = hf.corners; - // Remove the last corner as it is always the lower right corner of image enen though there was no point there. + // Remove the last corner as it is always the lower right corner of + // image enen though there was no point there. corners.remove(corners.size() - 1); - // Harris can have corners positioned a bit differently dependent on the scan direction + // Harris can have corners positioned a bit differently dependent on the + // scan direction // Position corners so they align for straighter lines. for (HarrisFast.Corner corner : corners) { moveUntilEnd(image, corner); } // Uncomment if a base64 image is needed for debugging purposes!!! -// outputResult(image, corners); + // outputResult(image, corners); return corners; } - private static void outputResult(BufferedImage image, List corners) { + private static void outputResult(BufferedImage image, + List corners) { BufferedImage bufferedImage = duplicateImage(image); Graphics2D g2d = bufferedImage.createGraphics(); g2d.setColor(Color.RED); @@ -250,7 +325,7 @@ private static void outputResult(BufferedImage image, List co for (HarrisFast.Corner corner : corners) { g2d.fill(new Rectangle2D.Float(corner.x, corner.y, 1, 1)); -// g2d.drawString("" + nr++, corner.x - 15, corner.y - 5); + // g2d.drawString("" + nr++, corner.x - 15, corner.y - 5); } g2d.dispose(); System.out.println(encodeImageToBase64(bufferedImage)); @@ -267,7 +342,8 @@ private static void outputResult(BufferedImage image, List co * @param image * @param corner */ - private static void moveUntilEnd(BufferedImage image, HarrisFast.Corner corner) { + private static void moveUntilEnd(BufferedImage image, + HarrisFast.Corner corner) { int rasterSize = 4; double lum = 25.0; @@ -276,8 +352,10 @@ private static void moveUntilEnd(BufferedImage image, HarrisFast.Corner corner) int x = corner.x - rasterOffset; int y = corner.y - rasterOffset; - if(x < 0) x = 0; - if(y < 0) y = 0; + if (x < 0) + x = 0; + if (y < 0) + y = 0; int[] xS = new int[rasterSize]; int[] yS = new int[rasterSize]; @@ -287,14 +365,20 @@ private static void moveUntilEnd(BufferedImage image, HarrisFast.Corner corner) // collect x positions for (int j = 0; j < rasterSize && j + y < image.getHeight(); j++) { for (int i = 0; i < rasterSize && i + x < image.getWidth(); i++) { - // if X on line Y is "black" and pixel before is also "black" (or no marking yet made) + // if X on line Y is "black" and pixel before is also "black" + // (or no marking yet made) // add pixel in line - if (getLuminance(image.getRGB(i + x, j + y)) < lum && (xS[j] == 0 || getLuminance(image.getRGB(i + x - 1, j + y)) < lum)) { + if (getLuminance(image.getRGB(i + x, j + y)) < lum + && (xS[j] == 0 || getLuminance( + image.getRGB(i + x - 1, j + y)) < lum)) { xS[j]++; } - // if Y on line X is "black" and pixel before is also "black" (or no marking yet made) + // if Y on line X is "black" and pixel before is also "black" + // (or no marking yet made) // add pixel in line - if (getLuminance(image.getRGB(i + x, j + y)) < lum && (yS[i] == 0 || getLuminance(image.getRGB(i + x, j - 1 + y)) < lum)) { + if (getLuminance(image.getRGB(i + x, j + y)) < lum + && (yS[i] == 0 || getLuminance( + image.getRGB(i + x, j - 1 + y)) < lum)) { yS[i]++; } } @@ -314,7 +398,8 @@ private static void moveUntilEnd(BufferedImage image, HarrisFast.Corner corner) } } // cancel positioning if we have a point on a "helper" line - if (yOffset == -1 && xOffset == -1) return; + if (yOffset == -1 && xOffset == -1) + return; // Update corner position corner.x = xOffset + x; @@ -325,7 +410,8 @@ private static void moveUntilEnd(BufferedImage image, HarrisFast.Corner corner) /** * Clones the given BufferedImage * - * @param sourceImage The image to copy + * @param sourceImage + * The image to copy * @return A copy of sourceImage */ public static BufferedImage duplicateImage(BufferedImage sourceImage) { @@ -346,7 +432,8 @@ public static BufferedImage duplicateImage(BufferedImage sourceImage) { /** * Get the luminance value for given rgb value * - * @param rgb RGB value to get luminance for + * @param rgb + * RGB value to get luminance for * @return Luminance */ public static double getLuminance(int rgb) { @@ -360,9 +447,12 @@ public static double getLuminance(int rgb) { /** * Get the luminance value for given rgb value * - * @param r Red value - * @param g Green value - * @param b Blue value + * @param r + * Red value + * @param g + * Green value + * @param b + * Blue value * @return Luminance */ private static double getLuminance(int r, int g, int b) { @@ -372,7 +462,8 @@ private static double getLuminance(int r, int g, int b) { /** * Encode image to a Base64 string. * - * @param image Image to encode + * @param image + * Image to encode * @return encoded image */ public static String encodeImageToBase64(BufferedImage image) { diff --git a/SeatingMap-demo/pom.xml b/SeatingMap-demo/pom.xml index 59f47f7..f26ee10 100644 --- a/SeatingMap-demo/pom.xml +++ b/SeatingMap-demo/pom.xml @@ -6,7 +6,7 @@ SeatingMap-demo war 0.1-SNAPSHOT - MyComponent Add-on Demo + Seating map Add-on Demo 3 diff --git a/SeatingMap-demo/src/main/java/org/percepta/mgrankvi/demo/DemoUI.java b/SeatingMap-demo/src/main/java/org/percepta/mgrankvi/demo/DemoUI.java index 5c43197..dd7b417 100644 --- a/SeatingMap-demo/src/main/java/org/percepta/mgrankvi/demo/DemoUI.java +++ b/SeatingMap-demo/src/main/java/org/percepta/mgrankvi/demo/DemoUI.java @@ -49,21 +49,32 @@ protected void init(VaadinRequest request) { "/org/percepta/mgrankvi/demo/LeftSide.png", new Point(150, 150)); room = component.addRoom(1, thirdLines); - Table table = new Table(imageToLines.getLines( - "/org/percepta/mgrankvi/demo/Table1.png", new Point(150, 150))); + List> lineGroups = imageToLines + .getLineGroups("/org/percepta/mgrankvi/demo/Tables.png", + new Point(150, 150)); + for(List lines: lineGroups) { + Table table = new Table(lines); + room.addComponent(table); + } + Table table = room.getTables().get(0); table.setName("Terhi Testi"); - table.setImageUrl( - "https://vaadin.com/vaadin-theme/images/vaadin/vaadin-logo-small.png"); - room.addComponent(table); - room.addComponent(new Table( - imageToLines.getLines("/org/percepta/mgrankvi/demo/Table2.png", - new Point(150, 150)))); - room.addComponent(new Table( - imageToLines.getLines("/org/percepta/mgrankvi/demo/Table3.png", - new Point(150, 150)))); - room.addComponent(new Table( - imageToLines.getLines("/org/percepta/mgrankvi/demo/Table4.png", - new Point(150, 150)))); + table.setImageUrl( + "https://vaadin.com/vaadin-theme/images/vaadin/vaadin-logo-small.png"); + // Table table = new Table(imageToLines.getLines( +// "/org/percepta/mgrankvi/demo/Table1.png", new Point(150, 150))); +// table.setName("Terhi Testi"); +// table.setImageUrl( +// "https://vaadin.com/vaadin-theme/images/vaadin/vaadin-logo-small.png"); +// room.addComponent(table); +// room.addComponent(new Table( +// imageToLines.getLines("/org/percepta/mgrankvi/demo/Table2.png", +// new Point(150, 150)))); +// room.addComponent(new Table( +// imageToLines.getLines("/org/percepta/mgrankvi/demo/Table3.png", +// new Point(150, 150)))); +// room.addComponent(new Table( +// imageToLines.getLines("/org/percepta/mgrankvi/demo/Table4.png", +// new Point(150, 150)))); thirdLines = imageToLines.getLines( "/org/percepta/mgrankvi/demo/FloorUtilities.png", @@ -76,10 +87,10 @@ protected void init(VaadinRequest request) { component.addPaths(pathLines, 1); component.connectTablesToPaths(); - component.getPath(table.getNodeId(), table2.getNodeId()); +// component.getPath(table.getNodeId(), table2.getNodeId()); component.addSelectionListener(event -> { - System.out.println("Click selection event recieved with payload: " + System.out.println("Click selection event received with payload: " + event.getRoom() + " :: " + event.getTable()); }); diff --git a/SeatingMap-demo/src/main/resources/org/percepta/mgrankvi/demo/Tables.png b/SeatingMap-demo/src/main/resources/org/percepta/mgrankvi/demo/Tables.png new file mode 100644 index 0000000..d592fd8 Binary files /dev/null and b/SeatingMap-demo/src/main/resources/org/percepta/mgrankvi/demo/Tables.png differ