/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2016, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.renderer.lite; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.List; import org.geotools.geometry.jts.Decimator; import org.geotools.geometry.jts.JTS; import org.geotools.geometry.jts.LiteShape2; import org.geotools.referencing.operation.transform.AffineTransform2D; import org.geotools.renderer.style.LineStyle2D; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; /** * Support class that fills a given rectangle with a set of parallel lines derived from * a sample mark that would generate sets of parallel lines when tiled * * @author Andrea Aime */ public class ParallelLinesFiller { /** * The four types of orientation that can give birth to a parallel line set * from a given mark */ enum LineType { HORIZONTAL, VERTICAL, SLASH, BACKSLASH}; static class Line { LineType type; double initialOffset; public Line(LineType type, double initialOffset) { this.type = type; this.initialOffset = initialOffset; } } /** * The set of parallel line definitions to paint */ List<Line> lines; /** * The repetition pattern */ double xStep; double yStep; public ParallelLinesFiller(List<Line> lines, double xStep, double yStep) { this.lines = lines; this.xStep = xStep; this.yStep = yStep; } /** * Builds a line filler from a stipple shape, if at all possible, or returns * null otherwise * * @param shape * @return */ public static ParallelLinesFiller fromStipple(Shape shape) { Rectangle2D bounds = shape.getBounds2D(); // turn to a geometry, it's easier to visit, and see if we can extract only // line segments Geometry geometry = JTS.toGeometry(shape); LinesExtractor extractor = new LinesExtractor(); geometry.apply(extractor); // if not only straight line segments included, we cannot turn this into a filler List<Line2D> segments = extractor.getLines(); if(!extractor.isSimple() || segments.isEmpty()) { return null; } // check each line and see if it forms a repeating pattern double by1 = bounds.getMinY(); double by2 = bounds.getMaxY(); double bx1 = bounds.getMinX(); double bx2 = bounds.getMaxX(); if(equals(by1, by2)) { double w = bx2 - bx1; by1 -= w / 2; by2 += w / 2; } else if(equals(bx1, bx2)) { double h = by2 - by1; bx1 -= h / 2; bx2 += h / 2; } List<Line> lines = new ArrayList<>(); for (Line2D segment : segments) { // vertical line? final double x1 = segment.getX1(); final double x2 = segment.getX2(); final double y1 = segment.getY1(); final double y2 = segment.getY2(); if(equals(x1, x2)) { if(equals(y1, by1) && equals(y2, by2)) { lines.add(new Line(LineType.VERTICAL, x1 - bx1)); } else { // not a line crossing the bounds, does not form a repeating pattern return null; } } else { if(equals(y1, y2)) { if(equals(x1, bx1) && equals(x2, bx2)) { lines.add(new Line(LineType.HORIZONTAL, y1 - by1)); } else { // not a line crossing the bounds, does not form a repeating pattern return null; } } else if((equals(x1, bx1) && equals(y1, by1) && equals(x2, bx2) && equals(y2, by2)) || (equals(x1, bx2) && equals(y1, by2)) && equals(x2, bx1) && equals(y2, by1)) { lines.add(new Line(LineType.SLASH, 0)); } else if((equals(x1, bx1) && equals(y1, by2) && equals(x2, bx2) && equals(y2, by1)) || (equals(x1, bx2) && equals(y1, by1)) && equals(x2, bx1) && equals(y2, by2)) { lines.add(new Line(LineType.BACKSLASH, 0)); } else { // not a line crossing the bounds and forming a repeating pattern return null; } } } double stepX = bx2 - bx1; double stepY = by2 - by1; if(stepX > 0 && stepY > 0) { return new ParallelLinesFiller(lines, stepX, stepY); } else { return null; } } /** * Checks if two doubles are equal with a small tolerance * * @param d1 * @param d2 * @return */ private static boolean equals(double d1, double d2) { return Math.abs(d1 - d2) < 1e-3; } /** * Fills the specified rectangle with parallel lines * @param bounds * @param painter * @param graphics * @param ls2d */ public void fillRectangle(Rectangle2D bounds, StyledShapePainter painter, Graphics2D graphics, LineStyle2D ls2d) { // the shape painter works only with liteshape, prepare objects so that we don't end up re-creating them // over and over AffineTransform2D identityTransf = new AffineTransform2D(new AffineTransform()); Decimator nullDecimator = new Decimator(-1, -1); GeometryFactory geomFactory = new GeometryFactory(); Coordinate stippleCoord1 = new Coordinate(0, 0); Coordinate stippleCoord2 = new Coordinate(0, 0); LineString stippleLine = geomFactory.createLineString(new Coordinate[] {stippleCoord1, stippleCoord2}); for (Line line : lines) { if(line.type == LineType.HORIZONTAL) { stippleCoord1.x = bounds.getMinX(); stippleCoord2.x = bounds.getMaxX(); for(double y = bounds.getMinY() + line.initialOffset; y < bounds.getMaxY(); y += yStep) { stippleCoord1.y = stippleCoord2.y = y; paintLine(painter, graphics, ls2d, identityTransf, nullDecimator, stippleLine); } } else if(line.type == LineType.VERTICAL) { stippleCoord1.y = bounds.getMinY(); stippleCoord2.y = bounds.getMaxY(); for(double x = bounds.getMinX() + line.initialOffset; x < bounds.getMaxX(); x += xStep) { stippleCoord1.x = stippleCoord2.x = x; paintLine(painter, graphics, ls2d, identityTransf, nullDecimator, stippleLine); } } else if(line.type == LineType.BACKSLASH) { stippleCoord1.x = bounds.getMinX(); stippleCoord1.y = bounds.getMinY(); for(double x = bounds.getMinX(); x <= bounds.getMaxX(); x += xStep) { int xSteps = (int) Math.ceil((bounds.getMaxX() - x) / xStep); int ySteps = (int) Math.ceil((bounds.getMaxY() - bounds.getMinY()) / yStep); int steps = Math.min(xSteps, ySteps); stippleCoord1.x = x; stippleCoord2.x = stippleCoord1.x + steps * xStep; stippleCoord2.y = stippleCoord1.y + steps * yStep; paintLine(painter, graphics, ls2d, identityTransf, nullDecimator, stippleLine); } stippleCoord1.x = bounds.getMinX(); for(double y = bounds.getMinY() + yStep; y <= bounds.getMaxY(); y += yStep) { int xSteps = (int) Math.ceil((bounds.getMaxX() - bounds.getMinX()) / xStep); int ySteps = (int) Math.ceil((bounds.getMaxY() - y) / yStep); int steps = Math.min(xSteps, ySteps); stippleCoord1.y = y; stippleCoord2.x = stippleCoord1.x + steps * xStep; stippleCoord2.y = stippleCoord1.y + steps * yStep; paintLine(painter, graphics, ls2d, identityTransf, nullDecimator, stippleLine); } } else if(line.type == LineType.SLASH) { stippleCoord1.x = bounds.getMinX(); stippleCoord1.y = bounds.getMinY(); for(double x = bounds.getMinX() + xStep; x <= bounds.getMaxX(); x += xStep) { int xSteps = (int) Math.ceil((x - bounds.getMinX()) / xStep); int ySteps = (int) Math.ceil((bounds.getMaxY() - bounds.getMinY()) / yStep); int steps = Math.min(xSteps, ySteps); stippleCoord1.x = x; stippleCoord2.x = stippleCoord1.x - steps * xStep; stippleCoord2.y = stippleCoord1.y + steps * yStep; paintLine(painter, graphics, ls2d, identityTransf, nullDecimator, stippleLine); } stippleCoord1.x += xStep; for(double y = bounds.getMinY(); y <= bounds.getMaxY(); y += yStep) { int xSteps = (int) Math.ceil((stippleCoord1.x - bounds.getMinX()) / xStep); int ySteps = (int) Math.ceil((bounds.getMaxY() - y) / yStep); int steps = Math.min(xSteps, ySteps); stippleCoord1.y = y; stippleCoord2.x = stippleCoord1.x - steps * xStep; stippleCoord2.y = stippleCoord1.y + steps * yStep; paintLine(painter, graphics, ls2d, identityTransf, nullDecimator, stippleLine); } } } } private void paintLine(StyledShapePainter painter, Graphics2D graphics, LineStyle2D lineStyle, AffineTransform2D identityTransf, Decimator nullDecimator, LineString stippleLine) { stippleLine.geometryChanged(); LiteShape2 stippleShape; try { stippleShape = new LiteShape2(stippleLine, identityTransf, nullDecimator, false); } catch(Exception e) { throw new RuntimeException("Unxpected exception building lite shape", e); } painter.paintLineStyle(graphics, stippleShape, lineStyle, false, 0); } }