From 37ace257cf0ad1b4a7fa6373f6b4463cdb8006bb Mon Sep 17 00:00:00 2001
From: Emux <devemux86@gmail.com>
Date: Thu, 7 Jul 2016 20:16:33 +0300
Subject: [PATCH] Create vtm-jts module, closes #53

---
 settings.gradle                               |   1 +
 vtm-android-example/build.gradle              |   1 +
 .../android/test/PathOverlayActivity.java     |   6 +-
 vtm-jts/build.gradle                          |  18 +
 .../src/org/oscim/layers/JtsPathLayer.java    | 204 ++++++++++
 .../org/oscim/layers/vector/JtsConverter.java |   0
 .../org/oscim/layers/vector/VectorLayer.java  |   0
 .../vector/geometries/CircleDrawable.java     |   0
 .../layers/vector/geometries/Drawable.java    |   0
 .../vector/geometries/HexagonDrawable.java    |   0
 .../layers/vector/geometries/JtsDrawable.java |   0
 .../vector/geometries/LineDrawable.java       |   0
 .../vector/geometries/PointDrawable.java      |   0
 .../vector/geometries/PolygonDrawable.java    |   0
 .../vector/geometries/RectangleDrawable.java  |   0
 .../oscim/layers/vector/geometries/Style.java |   0
 .../src/org/oscim/utils/geom/GeomBuilder.java |   0
 vtm-playground/build.gradle                   |   1 +
 .../src/org/oscim/test/PathLayerTest.java     |   8 +-
 vtm/build.gradle                              |   1 -
 vtm/src/org/oscim/layers/PathLayer.java       | 368 ++++++++++++++----
 21 files changed, 519 insertions(+), 89 deletions(-)
 create mode 100644 vtm-jts/build.gradle
 create mode 100644 vtm-jts/src/org/oscim/layers/JtsPathLayer.java
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/JtsConverter.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/VectorLayer.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/geometries/CircleDrawable.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/geometries/Drawable.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/geometries/HexagonDrawable.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/geometries/JtsDrawable.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/geometries/LineDrawable.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/geometries/PointDrawable.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/geometries/PolygonDrawable.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/geometries/RectangleDrawable.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/layers/vector/geometries/Style.java (100%)
 rename {vtm => vtm-jts}/src/org/oscim/utils/geom/GeomBuilder.java (100%)

diff --git a/settings.gradle b/settings.gradle
index 3d9573e1..0237e353 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,6 @@
 rootProject.name = 'vtm-parent'
 include ':vtm'
+include ':vtm-jts'
 include ':vtm-tests'
 include ':vtm-extras'
 include ':vtm-android'
diff --git a/vtm-android-example/build.gradle b/vtm-android-example/build.gradle
index c534c83d..b6d8379f 100644
--- a/vtm-android-example/build.gradle
+++ b/vtm-android-example/build.gradle
@@ -4,6 +4,7 @@ dependencies {
     compile project(':vtm-android')
     compile project(':vtm-extras')
     compile project(':vtm-jeo')
+    compile project(':vtm-jts')
     compile project(':vtm-themes')
     compile 'com.android.support:support-v4:24.0.0'
     compile 'com.android.support:appcompat-v7:24.0.0'
diff --git a/vtm-android-example/src/org/oscim/android/test/PathOverlayActivity.java b/vtm-android-example/src/org/oscim/android/test/PathOverlayActivity.java
index 810d09cf..b5b54aa5 100644
--- a/vtm-android-example/src/org/oscim/android/test/PathOverlayActivity.java
+++ b/vtm-android-example/src/org/oscim/android/test/PathOverlayActivity.java
@@ -24,7 +24,7 @@ import java.util.ArrayList;
 import org.oscim.backend.canvas.Color;
 import org.oscim.core.MapPosition;
 import org.oscim.event.Event;
-import org.oscim.layers.PathLayer;
+import org.oscim.layers.JtsPathLayer;
 import org.oscim.map.Map.UpdateListener;
 
 import android.os.Bundle;
@@ -47,7 +47,7 @@ public class PathOverlayActivity extends BitmapTileMapActivity {
 		mMap.setMapPosition(0, 0, 1 << 2);
 		for (double lat = -90; lat <= 90; lat += 5) {
 			int c = Color.fade(Color.rainbow((float) (lat + 90) / 180), 0.5f);
-			PathLayer pathLayer = new PathLayer(mMap, c, 6);
+			JtsPathLayer pathLayer = new JtsPathLayer(mMap, c, 6);
 			mMap.layers().add(pathLayer);
 			mPathLayers.add(pathLayer);
 		}
@@ -74,7 +74,7 @@ public class PathOverlayActivity extends BitmapTileMapActivity {
 		mMap.setMapPosition(0, 0, 1 << 2);
 	}
 
-	ArrayList<PathLayer> mPathLayers = new ArrayList<PathLayer>();
+	ArrayList<JtsPathLayer> mPathLayers = new ArrayList<JtsPathLayer>();
 
 	void createLayers(float pos) {
 
diff --git a/vtm-jts/build.gradle b/vtm-jts/build.gradle
new file mode 100644
index 00000000..7ee9b784
--- /dev/null
+++ b/vtm-jts/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'java'
+apply plugin: 'maven'
+
+dependencies {
+    compile project(':vtm')
+    compile 'com.vividsolutions:jts:1.13'
+}
+
+sourceSets {
+    main.java.srcDirs = ['src']
+}
+
+// Automated Gradle project deployment to Sonatype OSSRH
+if (isReleaseVersion && project.hasProperty("SONATYPE_USERNAME")) {
+    afterEvaluate {
+        project.apply from: "${rootProject.projectDir}/deploy.gradle"
+    }
+}
diff --git a/vtm-jts/src/org/oscim/layers/JtsPathLayer.java b/vtm-jts/src/org/oscim/layers/JtsPathLayer.java
new file mode 100644
index 00000000..0e544f51
--- /dev/null
+++ b/vtm-jts/src/org/oscim/layers/JtsPathLayer.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2012 osmdroid authors: Viesturs Zarins, Martin Pearman
+ * Copyright 2012 Hannes Janetzek
+ *
+ * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
+ *
+ * This program 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, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.oscim.layers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.oscim.core.GeoPoint;
+import org.oscim.layers.vector.VectorLayer;
+import org.oscim.layers.vector.geometries.LineDrawable;
+import org.oscim.layers.vector.geometries.Style;
+import org.oscim.map.Map;
+import org.oscim.utils.geom.GeomBuilder;
+
+import com.vividsolutions.jts.geom.LineString;
+
+/** This class draws a path line in given color. */
+public class JtsPathLayer extends VectorLayer {
+
+	protected final ArrayList<GeoPoint> mPoints;
+
+	protected Style mStyle;
+	protected LineDrawable mDrawable;
+
+	public JtsPathLayer(Map map, int lineColor, float lineWidth) {
+		super(map);
+		mStyle = Style.builder()
+		    .strokeColor(lineColor)
+		    .strokeWidth(lineWidth)
+		    .build();
+
+		mPoints = new ArrayList<GeoPoint>();
+	}
+
+	public JtsPathLayer(Map map, int lineColor) {
+		this(map, lineColor, 2);
+	}
+
+	public void setStyle(int lineColor, float lineWidth) {
+		mStyle = Style.builder()
+		    .strokeColor(lineColor)
+		    .strokeWidth(lineWidth)
+		    .build();
+	}
+
+	public void clearPath() {
+		if (!mPoints.isEmpty())
+			mPoints.clear();
+
+		updatePoints();
+	}
+
+	public void setPoints(List<GeoPoint> pts) {
+		mPoints.clear();
+		mPoints.addAll(pts);
+		updatePoints();
+	}
+
+	public void addPoint(GeoPoint pt) {
+		mPoints.add(pt);
+		updatePoints();
+	}
+
+	public void addPoint(int latitudeE6, int longitudeE6) {
+		mPoints.add(new GeoPoint(latitudeE6, longitudeE6));
+		updatePoints();
+	}
+
+	private void updatePoints() {
+		synchronized (this) {
+
+			if (mDrawable != null) {
+				remove(mDrawable);
+				mDrawable = null;
+			}
+
+			if (!mPoints.isEmpty()) {
+				mDrawable = new LineDrawable(mPoints, mStyle);
+				if (mDrawable.getGeometry() == null)
+					mDrawable = null;
+				else
+					add(mDrawable);
+			}
+		}
+		mWorker.submit(0);
+	}
+
+	public List<GeoPoint> getPoints() {
+		return mPoints;
+	}
+
+	/**
+	 * Draw a great circle. Calculate a point for every 100km along the path.
+	 * 
+	 * @param startPoint
+	 *            start point of the great circle
+	 * @param endPoint
+	 *            end point of the great circle
+	 */
+	public void addGreatCircle(GeoPoint startPoint, GeoPoint endPoint) {
+		synchronized (mPoints) {
+
+			/* get the great circle path length in meters */
+			double length = startPoint.distanceTo(endPoint);
+
+			/* add one point for every 100kms of the great circle path */
+			int numberOfPoints = (int) (length / 100000);
+
+			addGreatCircle(startPoint, endPoint, numberOfPoints);
+		}
+	}
+
+	/**
+	 * Draw a great circle.
+	 * 
+	 * @param startPoint
+	 *            start point of the great circle
+	 * @param endPoint
+	 *            end point of the great circle
+	 * @param numberOfPoints
+	 *            number of points to calculate along the path
+	 */
+	public void addGreatCircle(GeoPoint startPoint, GeoPoint endPoint,
+	        final int numberOfPoints) {
+		/* adapted from page
+		 * http://compastic.blogspot.co.uk/2011/07/how-to-draw-great-circle-on-map
+		 * -in.html
+		 * which was adapted from page http://maps.forum.nu/gm_flight_path.html */
+
+		GeomBuilder gb = new GeomBuilder();
+
+		/* convert to radians */
+		double lat1 = startPoint.getLatitude() * Math.PI / 180;
+		double lon1 = startPoint.getLongitude() * Math.PI / 180;
+		double lat2 = endPoint.getLatitude() * Math.PI / 180;
+		double lon2 = endPoint.getLongitude() * Math.PI / 180;
+
+		double d = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1 - lat2) / 2), 2)
+		        + Math.cos(lat1) * Math.cos(lat2)
+		        * Math.pow(Math.sin((lon1 - lon2) / 2), 2)));
+		double bearing = Math.atan2(
+		                            Math.sin(lon1 - lon2) * Math.cos(lat2),
+		                            Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
+		                                    * Math.cos(lat2)
+		                                    * Math.cos(lon1 - lon2))
+		        / -(Math.PI / 180);
+		bearing = bearing < 0 ? 360 + bearing : bearing;
+
+		for (int i = 0, j = numberOfPoints + 1; i < j; i++) {
+			double f = 1.0 / numberOfPoints * i;
+			double A = Math.sin((1 - f) * d) / Math.sin(d);
+			double B = Math.sin(f * d) / Math.sin(d);
+			double x = A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2)
+			        * Math.cos(lon2);
+			double y = A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2)
+			        * Math.sin(lon2);
+			double z = A * Math.sin(lat1) + B * Math.sin(lat2);
+
+			double latN = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
+			double lonN = Math.atan2(y, x);
+
+			gb.point(latN / (Math.PI / 180), lonN / (Math.PI / 180));
+		}
+
+		setLineString(gb.toLineString());
+	}
+
+	public void setLineString(LineString path) {
+		synchronized (this) {
+			if (mDrawable != null)
+				remove(mDrawable);
+			mDrawable = new LineDrawable(path, mStyle);
+			add(mDrawable);
+		}
+		mWorker.submit(0);
+	}
+
+	public void setLineString(double[] lonLat) {
+		synchronized (this) {
+			if (mDrawable != null)
+				remove(mDrawable);
+			mDrawable = new LineDrawable(lonLat, mStyle);
+			add(mDrawable);
+		}
+		mWorker.submit(0);
+	}
+
+}
diff --git a/vtm/src/org/oscim/layers/vector/JtsConverter.java b/vtm-jts/src/org/oscim/layers/vector/JtsConverter.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/JtsConverter.java
rename to vtm-jts/src/org/oscim/layers/vector/JtsConverter.java
diff --git a/vtm/src/org/oscim/layers/vector/VectorLayer.java b/vtm-jts/src/org/oscim/layers/vector/VectorLayer.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/VectorLayer.java
rename to vtm-jts/src/org/oscim/layers/vector/VectorLayer.java
diff --git a/vtm/src/org/oscim/layers/vector/geometries/CircleDrawable.java b/vtm-jts/src/org/oscim/layers/vector/geometries/CircleDrawable.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/geometries/CircleDrawable.java
rename to vtm-jts/src/org/oscim/layers/vector/geometries/CircleDrawable.java
diff --git a/vtm/src/org/oscim/layers/vector/geometries/Drawable.java b/vtm-jts/src/org/oscim/layers/vector/geometries/Drawable.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/geometries/Drawable.java
rename to vtm-jts/src/org/oscim/layers/vector/geometries/Drawable.java
diff --git a/vtm/src/org/oscim/layers/vector/geometries/HexagonDrawable.java b/vtm-jts/src/org/oscim/layers/vector/geometries/HexagonDrawable.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/geometries/HexagonDrawable.java
rename to vtm-jts/src/org/oscim/layers/vector/geometries/HexagonDrawable.java
diff --git a/vtm/src/org/oscim/layers/vector/geometries/JtsDrawable.java b/vtm-jts/src/org/oscim/layers/vector/geometries/JtsDrawable.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/geometries/JtsDrawable.java
rename to vtm-jts/src/org/oscim/layers/vector/geometries/JtsDrawable.java
diff --git a/vtm/src/org/oscim/layers/vector/geometries/LineDrawable.java b/vtm-jts/src/org/oscim/layers/vector/geometries/LineDrawable.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/geometries/LineDrawable.java
rename to vtm-jts/src/org/oscim/layers/vector/geometries/LineDrawable.java
diff --git a/vtm/src/org/oscim/layers/vector/geometries/PointDrawable.java b/vtm-jts/src/org/oscim/layers/vector/geometries/PointDrawable.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/geometries/PointDrawable.java
rename to vtm-jts/src/org/oscim/layers/vector/geometries/PointDrawable.java
diff --git a/vtm/src/org/oscim/layers/vector/geometries/PolygonDrawable.java b/vtm-jts/src/org/oscim/layers/vector/geometries/PolygonDrawable.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/geometries/PolygonDrawable.java
rename to vtm-jts/src/org/oscim/layers/vector/geometries/PolygonDrawable.java
diff --git a/vtm/src/org/oscim/layers/vector/geometries/RectangleDrawable.java b/vtm-jts/src/org/oscim/layers/vector/geometries/RectangleDrawable.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/geometries/RectangleDrawable.java
rename to vtm-jts/src/org/oscim/layers/vector/geometries/RectangleDrawable.java
diff --git a/vtm/src/org/oscim/layers/vector/geometries/Style.java b/vtm-jts/src/org/oscim/layers/vector/geometries/Style.java
similarity index 100%
rename from vtm/src/org/oscim/layers/vector/geometries/Style.java
rename to vtm-jts/src/org/oscim/layers/vector/geometries/Style.java
diff --git a/vtm/src/org/oscim/utils/geom/GeomBuilder.java b/vtm-jts/src/org/oscim/utils/geom/GeomBuilder.java
similarity index 100%
rename from vtm/src/org/oscim/utils/geom/GeomBuilder.java
rename to vtm-jts/src/org/oscim/utils/geom/GeomBuilder.java
diff --git a/vtm-playground/build.gradle b/vtm-playground/build.gradle
index 00c97b70..b36d7f24 100644
--- a/vtm-playground/build.gradle
+++ b/vtm-playground/build.gradle
@@ -5,6 +5,7 @@ dependencies {
     compile project(':vtm-desktop')
     compile project(':vtm-extras')
     compile project(':vtm-jeo')
+    compile project(':vtm-jts')
     runtime 'com.squareup.okhttp:okhttp:1.5.2'
 }
 
diff --git a/vtm-playground/src/org/oscim/test/PathLayerTest.java b/vtm-playground/src/org/oscim/test/PathLayerTest.java
index b6f3073a..80e06179 100644
--- a/vtm-playground/src/org/oscim/test/PathLayerTest.java
+++ b/vtm-playground/src/org/oscim/test/PathLayerTest.java
@@ -8,7 +8,7 @@ import org.oscim.core.GeoPoint;
 import org.oscim.core.MapPosition;
 import org.oscim.event.Event;
 import org.oscim.gdx.GdxMapApp;
-import org.oscim.layers.PathLayer;
+import org.oscim.layers.JtsPathLayer;
 import org.oscim.map.Map;
 import org.oscim.map.Map.UpdateListener;
 
@@ -33,7 +33,7 @@ public class PathLayerTest extends GdxMapApp {
 		});
 	}
 
-	ArrayList<PathLayer> mPathLayers = new ArrayList<PathLayer>();
+	ArrayList<JtsPathLayer> mPathLayers = new ArrayList<JtsPathLayer>();
 
 	void createLayers(float pos, boolean init) {
 
@@ -61,10 +61,10 @@ public class PathLayerTest extends GdxMapApp {
 
 				pts.add(new GeoPoint(latitude, longitude));
 			}
-			PathLayer pathLayer;
+			JtsPathLayer pathLayer;
 			if (init) {
 				int c = Color.fade(Color.rainbow((float) (lat + 90) / 180), 0.5f);
-				pathLayer = new PathLayer(mMap, c, 6);
+				pathLayer = new JtsPathLayer(mMap, c, 6);
 				mMap.layers().add(pathLayer);
 				mPathLayers.add(pathLayer);
 			} else {
diff --git a/vtm/build.gradle b/vtm/build.gradle
index f55afe2c..9b2c0d40 100644
--- a/vtm/build.gradle
+++ b/vtm/build.gradle
@@ -4,7 +4,6 @@ apply plugin: 'maven'
 configurations { providedCompile }
 
 dependencies {
-    compile 'com.vividsolutions:jts:1.13'
     compile 'org.slf4j:slf4j-api:1.7.21'
     providedCompile 'com.google.code.findbugs:annotations:2.0.1'
     providedCompile 'com.squareup.okhttp:okhttp:1.5.2'
diff --git a/vtm/src/org/oscim/layers/PathLayer.java b/vtm/src/org/oscim/layers/PathLayer.java
index b1a97d1b..47fbcf29 100644
--- a/vtm/src/org/oscim/layers/PathLayer.java
+++ b/vtm/src/org/oscim/layers/PathLayer.java
@@ -21,30 +21,39 @@ package org.oscim.layers;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.oscim.backend.canvas.Paint.Cap;
 import org.oscim.core.GeoPoint;
-import org.oscim.layers.vector.VectorLayer;
-import org.oscim.layers.vector.geometries.LineDrawable;
-import org.oscim.layers.vector.geometries.Style;
+import org.oscim.core.GeometryBuffer;
+import org.oscim.core.MapPosition;
+import org.oscim.core.MercatorProjection;
+import org.oscim.core.Tile;
 import org.oscim.map.Map;
-import org.oscim.utils.geom.GeomBuilder;
-
-import com.vividsolutions.jts.geom.LineString;
+import org.oscim.renderer.BucketRenderer;
+import org.oscim.renderer.GLViewport;
+import org.oscim.renderer.bucket.LineBucket;
+import org.oscim.renderer.bucket.RenderBuckets;
+import org.oscim.theme.styles.LineStyle;
+import org.oscim.utils.FastMath;
+import org.oscim.utils.async.SimpleWorker;
+import org.oscim.utils.geom.LineClipper;
 
 /** This class draws a path line in given color. */
-public class PathLayer extends VectorLayer {
+public class PathLayer extends Layer {
 
+	/** Stores points, converted to the map projection. */
 	protected final ArrayList<GeoPoint> mPoints;
+	protected boolean mUpdatePoints;
 
-	protected Style mStyle;
-	protected LineDrawable mDrawable;
+	/** Line style */
+	LineStyle mLineStyle;
+
+	final Worker mWorker;
 
 	public PathLayer(Map map, int lineColor, float lineWidth) {
 		super(map);
-		mStyle = Style.builder()
-		    .strokeColor(lineColor)
-		    .strokeWidth(lineWidth)
-		    .build();
-
+		mWorker = new Worker(map);
+		mLineStyle = new LineStyle(lineColor, lineWidth, Cap.BUTT);
+		mRenderer = new RenderPath();
 		mPoints = new ArrayList<GeoPoint>();
 	}
 
@@ -52,62 +61,63 @@ public class PathLayer extends VectorLayer {
 		this(map, lineColor, 2);
 	}
 
-	public void setStyle(int lineColor, float lineWidth) {
-		mStyle = Style.builder()
-		    .strokeColor(lineColor)
-		    .strokeWidth(lineWidth)
-		    .build();
-	}
-
 	public void clearPath() {
-		if (!mPoints.isEmpty())
-			mPoints.clear();
+		if (mPoints.isEmpty())
+			return;
 
+		synchronized (mPoints) {
+			mPoints.clear();
+		}
 		updatePoints();
 	}
 
 	public void setPoints(List<GeoPoint> pts) {
-		mPoints.clear();
-		mPoints.addAll(pts);
+		synchronized (mPoints) {
+			mPoints.clear();
+			mPoints.addAll(pts);
+		}
 		updatePoints();
 	}
 
 	public void addPoint(GeoPoint pt) {
-		mPoints.add(pt);
+		synchronized (mPoints) {
+			mPoints.add(pt);
+		}
 		updatePoints();
 	}
 
 	public void addPoint(int latitudeE6, int longitudeE6) {
-		mPoints.add(new GeoPoint(latitudeE6, longitudeE6));
+		synchronized (mPoints) {
+			mPoints.add(new GeoPoint(latitudeE6, longitudeE6));
+		}
 		updatePoints();
 	}
 
 	private void updatePoints() {
-		synchronized (this) {
-
-			if (mDrawable != null) {
-				remove(mDrawable);
-				mDrawable = null;
-			}
-
-			if (!mPoints.isEmpty()) {
-				mDrawable = new LineDrawable(mPoints, mStyle);
-				if (mDrawable.getGeometry() == null)
-					mDrawable = null;
-				else
-					add(mDrawable);
-			}
-		}
-		mWorker.submit(0);
+		mWorker.submit(10);
+		mUpdatePoints = true;
 	}
 
 	public List<GeoPoint> getPoints() {
 		return mPoints;
 	}
 
+	/**
+	 * FIXME To be removed
+	 *
+	 * @deprecated
+	 *
+	 */
+	public void setGeom(GeometryBuffer geom) {
+		mGeom = geom;
+		mWorker.submit(10);
+	}
+
+	GeometryBuffer mGeom;
+
 	/**
 	 * Draw a great circle. Calculate a point for every 100km along the path.
-	 * 
+	 *
 	 * @param startPoint
 	 *            start point of the great circle
 	 * @param endPoint
@@ -128,7 +138,7 @@ public class PathLayer extends VectorLayer {
 
 	/**
 	 * Draw a great circle.
-	 * 
+	 *
 	 * @param startPoint
 	 *            start point of the great circle
 	 * @param endPoint
@@ -137,29 +147,26 @@ public class PathLayer extends VectorLayer {
 	 *            number of points to calculate along the path
 	 */
 	public void addGreatCircle(GeoPoint startPoint, GeoPoint endPoint,
-	        final int numberOfPoints) {
-		/* adapted from page
-		 * http://compastic.blogspot.co.uk/2011/07/how-to-draw-great-circle-on-map
-		 * -in.html
-		 * which was adapted from page http://maps.forum.nu/gm_flight_path.html */
+							   final int numberOfPoints) {
+		// adapted from page
+		// http://compastic.blogspot.co.uk/2011/07/how-to-draw-great-circle-on-map-in.html
+		// which was adapted from page http://maps.forum.nu/gm_flight_path.html
 
-		GeomBuilder gb = new GeomBuilder();
-
-		/* convert to radians */
+		// convert to radians
 		double lat1 = startPoint.getLatitude() * Math.PI / 180;
 		double lon1 = startPoint.getLongitude() * Math.PI / 180;
 		double lat2 = endPoint.getLatitude() * Math.PI / 180;
 		double lon2 = endPoint.getLongitude() * Math.PI / 180;
 
 		double d = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1 - lat2) / 2), 2)
-		        + Math.cos(lat1) * Math.cos(lat2)
-		        * Math.pow(Math.sin((lon1 - lon2) / 2), 2)));
+				+ Math.cos(lat1) * Math.cos(lat2)
+				* Math.pow(Math.sin((lon1 - lon2) / 2), 2)));
 		double bearing = Math.atan2(
-		                            Math.sin(lon1 - lon2) * Math.cos(lat2),
-		                            Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
-		                                    * Math.cos(lat2)
-		                                    * Math.cos(lon1 - lon2))
-		        / -(Math.PI / 180);
+				Math.sin(lon1 - lon2) * Math.cos(lat2),
+				Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
+						* Math.cos(lat2)
+						* Math.cos(lon1 - lon2))
+				/ -(Math.PI / 180);
 		bearing = bearing < 0 ? 360 + bearing : bearing;
 
 		for (int i = 0, j = numberOfPoints + 1; i < j; i++) {
@@ -167,38 +174,237 @@ public class PathLayer extends VectorLayer {
 			double A = Math.sin((1 - f) * d) / Math.sin(d);
 			double B = Math.sin(f * d) / Math.sin(d);
 			double x = A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2)
-			        * Math.cos(lon2);
+					* Math.cos(lon2);
 			double y = A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2)
-			        * Math.sin(lon2);
+					* Math.sin(lon2);
 			double z = A * Math.sin(lat1) + B * Math.sin(lat2);
 
 			double latN = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
 			double lonN = Math.atan2(y, x);
-
-			gb.point(latN / (Math.PI / 180), lonN / (Math.PI / 180));
+			addPoint((int) (latN / (Math.PI / 180) * 1E6), (int) (lonN / (Math.PI / 180) * 1E6));
 		}
-
-		setLineString(gb.toLineString());
 	}
 
-	public void setLineString(LineString path) {
-		synchronized (this) {
-			if (mDrawable != null)
-				remove(mDrawable);
-			mDrawable = new LineDrawable(path, mStyle);
-			add(mDrawable);
+	/***
+	 * everything below runs on GL- and Worker-Thread
+	 ***/
+	final class RenderPath extends BucketRenderer {
+
+		public RenderPath() {
+
+			buckets.addLineBucket(0, mLineStyle);
+		}
+
+		private int mCurX = -1;
+		private int mCurY = -1;
+		private int mCurZ = -1;
+
+		@Override
+		public synchronized void update(GLViewport v) {
+			int tz = 1 << v.pos.zoomLevel;
+			int tx = (int) (v.pos.x * tz);
+			int ty = (int) (v.pos.y * tz);
+
+			// update layers when map moved by at least one tile
+			if ((tx != mCurX || ty != mCurY || tz != mCurZ)) {
+				mWorker.submit(100);
+				mCurX = tx;
+				mCurY = ty;
+				mCurZ = tz;
+			}
+
+			Task t = mWorker.poll();
+			if (t == null)
+				return;
+
+			// keep position to render relative to current state
+			mMapPosition.copy(t.pos);
+
+			// compile new layers
+			buckets.set(t.bucket.get());
+			compile();
 		}
-		mWorker.submit(0);
 	}
 
-	public void setLineString(double[] lonLat) {
-		synchronized (this) {
-			if (mDrawable != null)
-				remove(mDrawable);
-			mDrawable = new LineDrawable(lonLat, mStyle);
-			add(mDrawable);
-		}
-		mWorker.submit(0);
+	final static class Task {
+		RenderBuckets bucket = new RenderBuckets();
+		MapPosition pos = new MapPosition();
 	}
 
+	final class Worker extends SimpleWorker<Task> {
+
+		// limit coords
+		private final int max = 2048;
+
+		public Worker(Map map) {
+			super(map, 0, new Task(), new Task());
+			mClipper = new LineClipper(-max, -max, max, max);
+			mPPoints = new float[0];
+		}
+
+		private static final int MIN_DIST = 3;
+
+		// pre-projected points
+		private double[] mPreprojected = new double[2];
+
+		// projected points
+		private float[] mPPoints;
+		private final LineClipper mClipper;
+		private int mNumPoints;
+
+		@Override
+		public boolean doWork(Task task) {
+
+			int size = mNumPoints;
+
+			if (mUpdatePoints) {
+				synchronized (mPoints) {
+					mUpdatePoints = false;
+					mNumPoints = size = mPoints.size();
+
+					ArrayList<GeoPoint> geopoints = mPoints;
+					double[] points = mPreprojected;
+
+					if (size * 2 >= points.length) {
+						points = mPreprojected = new double[size * 2];
+						mPPoints = new float[size * 2];
+					}
+
+					for (int i = 0; i < size; i++)
+						MercatorProjection.project(geopoints.get(i), points, i);
+				}
+
+			} else if (mGeom != null) {
+				GeometryBuffer geom = mGeom;
+				mGeom = null;
+				size = geom.index[0];
+
+				double[] points = mPreprojected;
+
+				if (size > points.length) {
+					points = mPreprojected = new double[size * 2];
+					mPPoints = new float[size * 2];
+				}
+
+				for (int i = 0; i < size; i += 2)
+					MercatorProjection.project(geom.points[i + 1],
+							geom.points[i], points,
+							i >> 1);
+				mNumPoints = size = size >> 1;
+
+			}
+			if (size == 0) {
+				if (task.bucket.get() != null) {
+					task.bucket.clear();
+					mMap.render();
+				}
+				return true;
+			}
+
+			RenderBuckets layers = task.bucket;
+
+			LineBucket ll = layers.getLineBucket(0);
+			ll.line = mLineStyle;
+			ll.scale = ll.line.width;
+
+			mMap.getMapPosition(task.pos);
+
+			int zoomlevel = task.pos.zoomLevel;
+			task.pos.scale = 1 << zoomlevel;
+
+			double mx = task.pos.x;
+			double my = task.pos.y;
+			double scale = Tile.SIZE * task.pos.scale;
+
+			// flip around dateline
+			int flip = 0;
+			int maxx = Tile.SIZE << (zoomlevel - 1);
+
+			int x = (int) ((mPreprojected[0] - mx) * scale);
+			int y = (int) ((mPreprojected[1] - my) * scale);
+
+			if (x > maxx) {
+				x -= (maxx * 2);
+				flip = -1;
+			} else if (x < -maxx) {
+				x += (maxx * 2);
+				flip = 1;
+			}
+
+			mClipper.clipStart(x, y);
+
+			float[] projected = mPPoints;
+			int i = addPoint(projected, 0, x, y);
+
+			float prevX = x;
+			float prevY = y;
+
+			float[] segment = null;
+
+			for (int j = 2; j < size * 2; j += 2) {
+				x = (int) ((mPreprojected[j + 0] - mx) * scale);
+				y = (int) ((mPreprojected[j + 1] - my) * scale);
+
+				int flipDirection = 0;
+				if (x > maxx) {
+					x -= maxx * 2;
+					flipDirection = -1;
+				} else if (x < -maxx) {
+					x += maxx * 2;
+					flipDirection = 1;
+				}
+
+				if (flip != flipDirection) {
+					flip = flipDirection;
+					if (i > 2)
+						ll.addLine(projected, i, false);
+
+					mClipper.clipStart(x, y);
+					i = addPoint(projected, 0, x, y);
+					continue;
+				}
+
+				int clip = mClipper.clipNext(x, y);
+				if (clip < 1) {
+					if (i > 2)
+						ll.addLine(projected, i, false);
+
+					if (clip < 0) {
+						/* add line segment */
+						segment = mClipper.getLine(segment, 0);
+						ll.addLine(segment, 4, false);
+						prevX = mClipper.outX2;
+						prevY = mClipper.outY2;
+					}
+					i = 0;
+					continue;
+				}
+
+				float dx = x - prevX;
+				float dy = y - prevY;
+				if ((i == 0) || FastMath.absMaxCmp(dx, dy, MIN_DIST)) {
+					projected[i++] = prevX = x;
+					projected[i++] = prevY = y;
+				}
+			}
+			if (i > 2)
+				ll.addLine(projected, i, false);
+
+			// trigger redraw to let renderer fetch the result.
+			mMap.render();
+
+			return true;
+		}
+
+		@Override
+		public void cleanup(Task task) {
+			task.bucket.clear();
+		}
+
+		private int addPoint(float[] points, int i, int x, int y) {
+			points[i++] = x;
+			points[i++] = y;
+			return i;
+		}
+	}
 }