Class KochSnowflakeBuilder

  1 /*
  2  * Copyright (c) 2016 Martin Davis.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12  
 13 package org.locationtech.jts.shape.fractal;
 14  
 15 import org.locationtech.jts.geom.Coordinate;
 16 import org.locationtech.jts.geom.CoordinateList;
 17 import org.locationtech.jts.geom.Geometry;
 18 import org.locationtech.jts.geom.GeometryFactory;
 19 import org.locationtech.jts.geom.LineSegment;
 20 import org.locationtech.jts.math.Vector2D;
 21 import org.locationtech.jts.shape.GeometricShapeBuilder;
 22  
 23 public class KochSnowflakeBuilder 
 24 extends GeometricShapeBuilder
 25 {
 26     private CoordinateList coordList = new CoordinateList();
 27     
 28     public KochSnowflakeBuilder(GeometryFactory geomFactory)
 29     {
 30         super(geomFactory);
 31     }
 32     
 33     public static int recursionLevelForSize(int numPts)
 34     {
 35         double pow4 = numPts / 3;
 36         double exp = Math.log(pow4)/Math.log(4);
 37         return (int) exp;
 38     }
 39     
 40     public Geometry getGeometry()
 41     {
 42         int level = recursionLevelForSize(numPts);
 43         LineSegment baseLine = getSquareBaseLine();
 44         Coordinate[] pts = getBoundary(level, baseLine.getCoordinate(0), baseLine.getLength());
 45         return geomFactory.createPolygon(
 46                 geomFactory.createLinearRing(pts), null);
 47     }
 48     
 49     /**
 50      * The height of an equilateral triangle of side one
 51      */
 52     private static final double HEIGHT_FACTOR = Math.sin(Math.PI / 3.0);
 53     private static final double ONE_THIRD = 1.0/3.0;
 54     private static final double THIRD_HEIGHT = HEIGHT_FACTOR/3.0;
 55     private static final double TWO_THIRDS = 2.0/3.0;
 56     
 57     private Coordinate[] getBoundary(int level, Coordinate origin, double width) 
 58     {
 59         double y = origin.y;
 60         // for all levels beyond 0 need to vertically shift shape by height of one "arm" to centre it
 61         if (level > 0) {
 62             y += THIRD_HEIGHT * width;
 63         }
 64         
 65         Coordinate p0 = new Coordinate(origin.x, y);
 66         Coordinate p1 = new Coordinate(origin.x + width/2, y + width * HEIGHT_FACTOR);
 67         Coordinate p2 = new Coordinate(origin.x + width, y);
 68         addSide(level, p0, p1);
 69         addSide(level, p1, p2);
 70         addSide(level, p2, p0);
 71         coordList.closeRing();
 72         return coordList.toCoordinateArray();
 73     }
 74  
 75     public void addSide(int level, Coordinate p0, Coordinate p1) {
 76         if (level == 0)
 77             addSegment(p0, p1);
 78         else {
 79             Vector2D base = Vector2D.create(p0, p1);
 80             Coordinate midPt = base.multiply(0.5).translate(p0);
 81             
 82             Vector2D heightVec = base.multiply(THIRD_HEIGHT);
 83             Vector2D offsetVec = heightVec.rotateByQuarterCircle(1);
 84             Coordinate offsetPt = offsetVec.translate(midPt);
 85             
 86             int n2 = level - 1;
 87             Coordinate thirdPt = base.multiply(ONE_THIRD).translate(p0);
 88             Coordinate twoThirdPt = base.multiply(TWO_THIRDS).translate(p0);
 89             
 90             // construct sides recursively
 91             addSide(n2, p0, thirdPt);
 92             addSide(n2, thirdPt, offsetPt);
 93             addSide(n2, offsetPt, twoThirdPt);
 94             addSide(n2, twoThirdPt, p1);
 95         }
 96     }
 97         
 98     private void addSegment(Coordinate p0, Coordinate p1)
 99     {
100         coordList.add(p1);
101     }
102     
103 }
104