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