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 KochSnowflakeBuilder(GeometryFactory geomFactory)28 public KochSnowflakeBuilder(GeometryFactory geomFactory) 29 { 30 super(geomFactory); 31 } 32 recursionLevelForSize(int numPts)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 getGeometry()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 getBoundary(int level, Coordinate origin, double width)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 addSide(int level, Coordinate p0, Coordinate p1)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 addSegment(Coordinate p0, Coordinate p1)98 private void addSegment(Coordinate p0, Coordinate p1) 99 { 100 coordList.add(p1); 101 } 102 103 } 104