8 Commits

Author SHA1 Message Date
Emux
54b7884fa3 0.11.1 2019-04-12 19:45:34 +03:00
Emux
e5bc7c7564 OpenGL ES 3.0 is supported with Android 4.3 (API level 18) and higher, fix #713 2019-04-12 11:29:48 +03:00
Emux
cf49591d4f Update changelog 2019-04-10 12:49:35 +03:00
Andrea Antonello
71f94f3f79 MBTiles raster tile source (#708) 2019-04-10 12:49:35 +03:00
Gustl22
db6be82ce2 S3DBUtils: adjust coordinate system (RHS to LHS) (#710) 2019-04-10 12:46:15 +03:00
Gustl22
ba65786b5a GeometryUtils improvements (#709) 2019-04-10 12:41:50 +03:00
Emux
036d63c9dd Update changelog 2019-04-02 20:11:44 +03:00
Emux
07e86d24f6 OpenMapTilesMvtTileSource: update URL 2019-04-02 20:10:48 +03:00
13 changed files with 533 additions and 75 deletions

View File

@@ -1,5 +1,12 @@
# Changelog
## Version 0.11.1 (2019-04-12)
- MBTiles raster tile source (Android) [#708](https://github.com/mapsforge/vtm/pull/708)
- Fix Android 4.2 crash [#713](https://github.com/mapsforge/vtm/issues/713)
- Many other minor improvements and bug fixes
- [Solved issues](https://github.com/mapsforge/vtm/issues?q=is%3Aclosed+milestone%3A0.11.1)
## Version 0.11.0 (2019-03-25)
- Render themes: tag transform [#420](https://github.com/mapsforge/vtm/issues/420)

View File

@@ -91,6 +91,9 @@
<activity
android:name=".MarkerOverlayActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".MBTilesBitmapTileActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".MultiMapViewActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />

View File

@@ -25,5 +25,8 @@
<string name="search_key">\'*\' or OSM key</string>
<string name="search_value">void or value</string>
<string name="now">Now</string>
<string name="warning">Warning</string>
<string name="startup_message_mbtiles">To run this sample activity, you need any MBTiles with filename test.mbtiles installed on storage.\n\nadb push file.mbtiles /sdcard/test.mbtiles</string>
<string name="exit">Exit</string>
</resources>

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2019 Andrea Antonello
* Copyright 2019 devemux86
*
* 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.android.test;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Environment;
import org.oscim.android.tiling.source.mbtiles.MBTilesBitmapTileSource;
import org.oscim.core.BoundingBox;
import org.oscim.core.MapPosition;
import org.oscim.core.Tile;
import org.oscim.layers.tile.bitmap.BitmapTileLayer;
import java.io.File;
/**
* An example activity making use of MBTiles.
*/
public class MBTilesBitmapTileActivity extends BitmapTileActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
File file = new File(Environment.getExternalStorageDirectory(), "test.mbtiles");
if (!file.exists()) {
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle(R.string.warning)
.setMessage(R.string.startup_message_mbtiles)
.setPositiveButton(R.string.exit, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
builder.show();
return;
}
MBTilesBitmapTileSource tileSource = new MBTilesBitmapTileSource(file.getAbsolutePath(), 128, null);
BitmapTileLayer bitmapLayer = new BitmapTileLayer(mMap, tileSource);
mMap.layers().add(bitmapLayer);
/* set initial position on first run */
MapPosition pos = new MapPosition();
mMap.getMapPosition(pos);
if (pos.x == 0.5 && pos.y == 0.5) {
BoundingBox bbox = tileSource.getBounds();
if (bbox != null) {
pos.setByBoundingBox(bbox, Tile.SIZE * 4, Tile.SIZE * 4);
mMap.setMapPosition(pos);
}
}
}
}

View File

@@ -8,6 +8,7 @@
* Copyright 2017 nebular
* Copyright 2018 boldtrn
* Copyright 2018-2019 Gustl22
* Copyright 2019 Andrea Antonello
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@@ -115,6 +116,7 @@ public class Samples extends Activity {
linearLayout.addView(createLabel("Raster Maps"));
linearLayout.addView(createButton(BitmapTileActivity.class));
linearLayout.addView(createButton(MBTilesBitmapTileActivity.class));
linearLayout.addView(createLabel("Overlays"));
linearLayout.addView(createButton(MarkerOverlayActivity.class));

View File

@@ -28,7 +28,6 @@ import android.util.DisplayMetrics;
import android.view.Display;
import android.view.GestureDetector;
import android.view.WindowManager;
import org.oscim.android.canvas.AndroidGraphics;
import org.oscim.android.gl.AndroidGL;
import org.oscim.android.gl.AndroidGL30;
@@ -47,11 +46,10 @@ import org.oscim.utils.Parameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The MapView,
@@ -127,12 +125,16 @@ public class MapView extends GLSurfaceView {
mMap = new AndroidMap(this);
/* Initialize Renderer */
try {
setEGLContextFactory(new GlContextFactory());
} catch (Throwable t) {
log.error("Falling back to GLES 2", t);
// OpenGL ES 3.0 is supported with Android 4.3 (API level 18) and higher
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
try {
setEGLContextFactory(new GlContextFactory());
} catch (Throwable t) {
log.error("Falling back to GLES 2", t);
setEGLContextClientVersion(2);
}
} else
setEGLContextClientVersion(2);
}
setEGLConfigChooser(new GlConfigChooser());
if (GLAdapter.debug)

View File

@@ -0,0 +1,228 @@
/*
* Copyright 2019 Andrea Antonello
* Copyright 2019 devemux86
*
* 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.android.tiling.source.mbtiles;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import org.oscim.backend.CanvasAdapter;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.core.BoundingBox;
import org.oscim.core.MercatorProjection;
import org.oscim.layers.tile.MapTile;
import org.oscim.map.Viewport;
import org.oscim.tiling.ITileDataSink;
import org.oscim.tiling.ITileDataSource;
import org.oscim.tiling.QueryResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Map;
/**
* A tile data source for MBTiles raster databases.
*/
public class MBTilesBitmapTileDataSource implements ITileDataSource {
private static final Logger log = LoggerFactory.getLogger(MBTilesBitmapTileDataSource.class);
private static final String TABLE_TILES = "tiles";
private static final String COL_TILES_ZOOM_LEVEL = "zoom_level";
private static final String COL_TILES_TILE_COLUMN = "tile_column";
private static final String COL_TILES_TILE_ROW = "tile_row";
private static final String COL_TILES_TILE_DATA = "tile_data";
private static final String SELECT_TILES = "SELECT " + COL_TILES_TILE_DATA + " from " + TABLE_TILES + " where "
+ COL_TILES_ZOOM_LEVEL + "=? AND " + COL_TILES_TILE_COLUMN + "=? AND " + COL_TILES_TILE_ROW + "=?";
private static final String TABLE_METADATA = "metadata";
private static final String COL_METADATA_NAME = "name";
private static final String COL_METADATA_VALUE = "value";
private static final String SELECT_METADATA = "select " + COL_METADATA_NAME + "," + COL_METADATA_VALUE + " from "
+ TABLE_METADATA;
private final Integer mAlpha;
private final SQLiteDatabase mDatabase;
private Map<String, String> mMetadata;
private final Integer mTransparentColor;
/**
* Create a MBTiles tile data source.
*
* @param path the path to the MBTiles database.
* @param alpha an optional alpha value [0-255] to make the tiles transparent.
* @param transparentColor an optional color that will be made transparent in the bitmap.
*/
MBTilesBitmapTileDataSource(String path, Integer alpha, Integer transparentColor) {
mDatabase = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
mAlpha = alpha;
mTransparentColor = transparentColor;
}
@Override
public void cancel() {
mDatabase.close();
}
@Override
public void dispose() {
mDatabase.close();
}
String getAttribution() {
return getMetadata().get("attribution");
}
BoundingBox getBounds() {
String bounds = getMetadata().get("bounds");
if (bounds != null) {
String[] split = bounds.split(",");
double w = Double.parseDouble(split[0]);
double s = Double.parseDouble(split[1]);
double e = Double.parseDouble(split[2]);
double n = Double.parseDouble(split[3]);
return new BoundingBox(s, w, n, e);
}
return null;
}
public String getDescription() {
return getMetadata().get("description");
}
/**
* @return the image format (jpg, png)
*/
public String getFormat() {
return getMetadata().get("format");
}
int getMaxZoom() {
String maxZoom = getMetadata().get("maxzoom");
if (maxZoom != null)
return Integer.parseInt(maxZoom);
return Viewport.MAX_ZOOM_LEVEL;
}
private Map<String, String> getMetadata() {
if (mMetadata == null) {
mMetadata = new HashMap<>();
Cursor cursor = null;
try {
cursor = mDatabase.rawQuery(SELECT_METADATA, null);
while (cursor.moveToNext()) {
String key = cursor.getString(0);
String value = cursor.getString(1);
mMetadata.put(key, value);
}
} finally {
if (cursor != null)
cursor.close();
}
}
return mMetadata;
}
int getMinZoom() {
String minZoom = getMetadata().get("minzoom");
if (minZoom != null)
return Integer.parseInt(minZoom);
return Viewport.MIN_ZOOM_LEVEL;
}
String getName() {
return getMetadata().get("name");
}
public String getVersion() {
return getMetadata().get("version");
}
private static android.graphics.Bitmap processAlpha(android.graphics.Bitmap bitmap, int alpha) {
android.graphics.Bitmap newBitmap = android.graphics.Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), android.graphics.Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);
Paint paint = new Paint();
paint.setAlpha(alpha);
canvas.drawBitmap(bitmap, 0, 0, paint);
return newBitmap;
}
private static android.graphics.Bitmap processTransparentColor(android.graphics.Bitmap bitmap, int colorToRemove) {
int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
for (int i = 0; i < pixels.length; i++) {
if (pixels[i] == colorToRemove)
pixels[i] = Color.alpha(Color.TRANSPARENT);
}
android.graphics.Bitmap newBitmap = android.graphics.Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), android.graphics.Bitmap.Config.ARGB_8888);
newBitmap.setPixels(pixels, 0, newBitmap.getWidth(), 0, 0, newBitmap.getWidth(), newBitmap.getHeight());
return newBitmap;
}
@Override
public void query(MapTile tile, ITileDataSink sink) {
QueryResult res = QueryResult.FAILED;
try {
byte[] bytes = readTile(tile.tileX, tile.tileY, tile.zoomLevel);
if (mTransparentColor != null || mAlpha != null) {
android.graphics.Bitmap androidBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
if (mTransparentColor != null)
androidBitmap = processTransparentColor(androidBitmap, mTransparentColor);
if (mAlpha != null)
androidBitmap = processAlpha(androidBitmap, mAlpha);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
androidBitmap.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, bos);
bytes = bos.toByteArray();
}
Bitmap bitmap = CanvasAdapter.decodeBitmap(new ByteArrayInputStream(bytes));
sink.setTileImage(bitmap);
res = QueryResult.SUCCESS;
} catch (Exception e) {
log.debug("{} invalid bitmap", tile);
} finally {
sink.completed(res);
}
}
/**
* Read a Tile's image bytes from the MBTiles database.
*
* @param tileX the x tile index.
* @param tileY the y tile index (OSM notation).
* @param zoomLevel the zoom level.
* @return the tile image bytes.
*/
private byte[] readTile(int tileX, int tileY, byte zoomLevel) {
Cursor cursor = null;
try {
long tmsTileY = MercatorProjection.tileYToTMS(tileY, zoomLevel);
cursor = mDatabase.rawQuery(SELECT_TILES, new String[]{String.valueOf(zoomLevel), String.valueOf(tileX), String.valueOf(tmsTileY)});
if (cursor.moveToFirst())
return cursor.getBlob(0);
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2019 Andrea Antonello
* Copyright 2019 devemux86
*
* 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.android.tiling.source.mbtiles;
import org.oscim.core.BoundingBox;
import org.oscim.tiling.ITileDataSource;
import org.oscim.tiling.TileSource;
/**
* A tile source for MBTiles raster databases.
*/
public class MBTilesBitmapTileSource extends TileSource {
private final MBTilesBitmapTileDataSource mTileDataSource;
/**
* Create a MBTiles tile source.
*
* @param path the path to the MBTiles database.
*/
public MBTilesBitmapTileSource(String path) {
this(path, null, null);
}
/**
* Create a MBTiles tile source.
*
* @param path the path to the MBTiles database.
* @param alpha an optional alpha value [0-255] to make the tiles transparent.
* @param transparentColor an optional color that will be made transparent in the bitmap.
*/
public MBTilesBitmapTileSource(String path, Integer alpha, Integer transparentColor) {
mTileDataSource = new MBTilesBitmapTileDataSource(path, alpha, transparentColor);
}
@Override
public void close() {
mTileDataSource.dispose();
}
public String getAttribution() {
return mTileDataSource.getAttribution();
}
public BoundingBox getBounds() {
return mTileDataSource.getBounds();
}
@Override
public ITileDataSource getDataSource() {
return mTileDataSource;
}
public String getDescription() {
return mTileDataSource.getDescription();
}
/**
* @return the image format (jpg, png)
*/
public String getFormat() {
return mTileDataSource.getFormat();
}
public int getMaxZoom() {
return mTileDataSource.getMaxZoom();
}
public int getMinZoom() {
return mTileDataSource.getMinZoom();
}
public String getName() {
return mTileDataSource.getName();
}
public String getVersion() {
return mTileDataSource.getVersion();
}
@Override
public OpenResult open() {
return OpenResult.SUCCESS;
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2013 Hannes Janetzek
* Copyright 2016-2018 devemux86
* Copyright 2016-2019 devemux86
* Copyright 2018 boldtrn
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
@@ -25,7 +25,7 @@ import org.oscim.tiling.source.UrlTileSource;
public class OpenMapTilesMvtTileSource extends UrlTileSource {
private static final String DEFAULT_URL = "https://maps.tilehosting.com/data/v3";
private static final String DEFAULT_URL = "https://api.maptiler.com/tiles/v3";
private static final String DEFAULT_PATH = "/{Z}/{X}/{Y}.pbf";
public static class Builder<T extends Builder<T>> extends UrlTileSource.Builder<T> {

View File

@@ -7,17 +7,36 @@ import org.oscim.utils.geom.GeometryUtils;
public class GeometryTest {
@Test
public void testIsTrisClockwise() {
// Coordinate system is LHS
float[] pA = new float[]{0, 0};
float[] pB = new float[]{1, 0};
float[] pC = new float[]{1, 1};
public void testClosestPointOnLine2D() {
float[] pP = {4, 1};
float[] pL = {1, 2};
float[] vL = {1, -1};
float area = GeometryUtils.isTrisClockwise(pA, pB, pC);
Assert.assertTrue(area > 0);
float[] point = GeometryUtils.closestPointOnLine2D(pP, pL, vL);
Assert.assertEquals(point[0], 3, 0.00001);
Assert.assertEquals(point[1], 0, 0.00001);
}
area = GeometryUtils.isTrisClockwise(pA, pC, pB);
Assert.assertTrue(area < 0);
@Test
public void testDistancePointLine2D() {
float[] pP = {1, 0};
float[] pL = {0, 0};
float[] vL = {2, 2};
float distance = GeometryUtils.distancePointLine2D(pP, pL, vL);
Assert.assertEquals(distance, Math.sqrt(2) / 2, 0.00001);
}
@Test
public void testDotProduct() {
float[] p = {-1, 0, 0, 0, 0, 0};
for (int i = 0; i < 9; i++) {
p[4] = (float) Math.cos(Math.toRadians(i * 45));
p[5] = (float) Math.sin(Math.toRadians(i * 45));
System.out.println("\n> " + (i * 45) + " " + p[3] + ":" + p[4] + "\n="
+ GeometryUtils.dotProduct(p, 0, 2, 4));
}
}
@Test
@@ -34,14 +53,16 @@ public class GeometryTest {
}
@Test
public void testDotProduct() {
float[] p = {-1, 0, 0, 0, 0, 0};
public void testIsTrisClockwise() {
// Coordinate system is LHS
float[] pA = new float[]{0, 0};
float[] pB = new float[]{1, 0};
float[] pC = new float[]{1, 1};
for (int i = 0; i < 9; i++) {
p[4] = (float) Math.cos(Math.toRadians(i * 45));
p[5] = (float) Math.sin(Math.toRadians(i * 45));
System.out.println("\n> " + (i * 45) + " " + p[3] + ":" + p[4] + "\n="
+ GeometryUtils.dotProduct(p, 0, 2, 4));
}
float area = GeometryUtils.isTrisClockwise(pA, pB, pC);
Assert.assertTrue(area > 0);
area = GeometryUtils.isTrisClockwise(pA, pC, pB);
Assert.assertTrue(area < 0);
}
}

View File

@@ -2,7 +2,8 @@
* Copyright 2010, 2011, 2012 mapsforge.org
* Copyright 2012 Hannes Janetzek
* Copyright 2014 Ludwig M Brinckmann
* Copyright 2016-2017 devemux86
* Copyright 2016-2019 devemux86
* Copyright 2019 Andrea Antonello
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@@ -577,6 +578,17 @@ public final class MercatorProjection {
return pixelYToLatitude(tileY * Tile.SIZE, getMapSize(zoomLevel));
}
/**
* Converts a tile Y number at a certain zoom level to TMS notation.
*
* @param tileY the tile Y number that should be converted.
* @param zoomLevel the zoom level at which the number should be converted.
* @return the TMS value of the tile Y number.
*/
public static long tileYToTMS(long tileY, byte zoomLevel) {
return (long) (zoomLevelToScale(zoomLevel) - tileY - 1);
}
public static double toLatitude(double y) {
return 90 - 360 * Math.atan(Math.exp((y - 0.5) * (2 * Math.PI))) / Math.PI;
}

View File

@@ -27,13 +27,7 @@ import org.oscim.utils.math.MathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.*;
/**
* Provides utils for S3DB layers.
@@ -413,7 +407,7 @@ public final class S3DBUtils {
int countConcavAngles = 0;
for (Byte simpleAngle : simpleAngles) {
if (simpleAngle > 1)
if (simpleAngle < -1)
countConcavAngles++;
}
@@ -447,14 +441,14 @@ public final class S3DBUtils {
byte direction = simpleAngles.get(shift);
if (direction == 0) {
continue; // direction is similar to last one
} else if (direction > 0) {
// If shape turns right
} else if (direction < 0) {
// If shape turns left (concave)
float[] positionRidgeA = null;
float[] positionRidgeB = null;
// Check two previous corners
Integer indexPrevious = getIndexPreviousLeftTurn(shift, simpleAngles);
Integer indexPrevious2 = getIndexPreviousLeftTurn(indexPrevious == null ? shift - 1 : indexPrevious, simpleAngles);
Integer indexPrevious = getIndexPreviousConvexTurn(shift, simpleAngles);
Integer indexPrevious2 = getIndexPreviousConvexTurn(indexPrevious == null ? shift - 1 : indexPrevious, simpleAngles);
if (indexPrevious != null && indexPrevious2 != null) {
// Write two previous
@@ -477,8 +471,8 @@ public final class S3DBUtils {
}
// Check two next corners
Integer indexNext = getIndexNextLeftTurn(shift, simpleAngles);
Integer indexNext2 = getIndexNextLeftTurn(indexNext == null ? shift + 1 : indexNext, simpleAngles);
Integer indexNext = getIndexNextConvexTurn(shift, simpleAngles);
Integer indexNext2 = getIndexNextConvexTurn(indexNext == null ? shift + 1 : indexNext, simpleAngles);
if (indexNext != null && indexNext2 != null) {
if (ridgePoints.get(indexNext) == null) {
@@ -535,7 +529,7 @@ public final class S3DBUtils {
// Set opposite ridge, if only one concave corner
if (countConcavAngles == 1) {
Integer opposite = getIndexNextLeftTurn(indexNext2, simpleAngles);
Integer opposite = getIndexNextConvexTurn(indexNext2, simpleAngles);
if (opposite != null) {
if (isGabled)
gablePoints.remove(opposite);
@@ -548,12 +542,12 @@ public final class S3DBUtils {
isOdd = false;
continue;
}
// Regular right angle (left turn)
// Regular right angle (convex turn)
if (isOdd) {
isOdd = false;
continue;
}
if (simpleAngles.get(shift) < -1) {
if (simpleAngles.get(shift) > 1) {
isOdd = true;
}
if (ridgePoints.containsKey(shift) && ridgeLines.containsKey(shift)) {
@@ -563,7 +557,7 @@ public final class S3DBUtils {
if (currentRidgeInd != null) {
float[] intersection;
// If is gabled, then use the normal line as intersection instead of bisection, but if the angle is not right, this is usually not a gable point
if (isGabled && direction < -1) {
if (isGabled && direction > 1) {
if (ridgePoints.get(currentRidgeInd) == null) {
log.debug("Gabled intersection calc failed");
currentRidgeInd = null;
@@ -588,7 +582,7 @@ public final class S3DBUtils {
currentRidgeInd = shift;
}
} else {
Integer indexNext = getIndexNextLeftTurn(shift, simpleAngles);
Integer indexNext = getIndexNextConvexTurn(shift, simpleAngles);
if (indexNext == null) continue;
if (!ridgeLines.containsKey(shift)) {
ridgeLines.put(shift, normVectors.get(indexNext));
@@ -620,7 +614,7 @@ public final class S3DBUtils {
}
// Only remove ridgePoint at concave corners
if (!isGabled || simpleAngles.get(key) > 0) {
if (!isGabled || simpleAngles.get(key) < 0) {
boolean isIn = GeometryUtils.pointInPoly(ridgeEntry.getValue()[0], ridgeEntry.getValue()[1], points, points.length, 0);
if (!isIn) {
// FIXME can improve shapes with concaves that intersect each other and remove shapes which have ridgepoints outside the outline
@@ -827,7 +821,7 @@ public final class S3DBUtils {
int indexStart = getIndicesLongestSide(simpleAngles, lengths, null)[0];
if (orientationAcross) {
Integer tmp = getIndexPreviousLeftTurn(indexStart, simpleAngles);
Integer tmp = getIndexPreviousConvexTurn(indexStart, simpleAngles);
if (tmp == null) {
tmp = getIndexNextTurn(indexStart, simpleAngles);
}
@@ -1081,14 +1075,14 @@ public final class S3DBUtils {
}
/**
* @return the index of next left turn after specified index
* @return the index of convex turn after specified index or null, if it's concave.
*/
private static Integer getIndexNextLeftTurn(int index, List<Byte> simpleAngles) {
private static Integer getIndexNextConvexTurn(int index, List<Byte> simpleAngles) {
for (int i = index + 1; i < simpleAngles.size() + index; i++) {
int iMod = i % simpleAngles.size();
if (simpleAngles.get(iMod) < 0) {
if (simpleAngles.get(iMod) > 0) {
return iMod;
} else if (simpleAngles.get(iMod) > 0) {
} else if (simpleAngles.get(iMod) < 0) {
return null;
}
}
@@ -1109,14 +1103,14 @@ public final class S3DBUtils {
}
/**
* @return the index of previous left turn at specified index
* @return the index of previous convex turn at specified index
*/
private static Integer getIndexPreviousLeftTurn(int index, List<Byte> simpleAngles) {
private static Integer getIndexPreviousConvexTurn(int index, List<Byte> simpleAngles) {
for (int i = simpleAngles.size() + index - 1; i >= 0; i--) {
int iMod = i % simpleAngles.size();
if (simpleAngles.get(iMod) < 0) {
if (simpleAngles.get(iMod) > 0) {
return iMod;
} else if (simpleAngles.get(iMod) > 0) {
} else if (simpleAngles.get(iMod) < 0) {
return null;
}
}
@@ -1132,10 +1126,10 @@ public final class S3DBUtils {
Integer concaveStart = null;
for (int i = 0; i < size; i++) {
if (indexStart != null && concaveStart != null) break;
if (indexStart == null && simpleAngles.get(i) < -1) {
if (indexStart == null && simpleAngles.get(i) > 1) {
// Use first angle as start index;
indexStart = i;
} else if (concaveStart == null && simpleAngles.get(i) > 1) {
} else if (concaveStart == null && simpleAngles.get(i) < -1) {
// A real concave corner
concaveStart = i;
}
@@ -1156,9 +1150,9 @@ public final class S3DBUtils {
// Calculate longest side with right angle next to it.
int[] iLongSide = getIndicesLongestSide(simpleAngles, lengths, indexStart);
if (simpleAngles.get(iLongSide[1]) > -2) {
if (simpleAngles.get(iLongSide[1]) < 2) {
// If angle is not good to start a ridge use previous
indexStart = getIndexPreviousLeftTurn(iLongSide[0], simpleAngles);
indexStart = getIndexPreviousConvexTurn(iLongSide[0], simpleAngles);
} else {
indexStart = iLongSide[1]; // Get side next to longest one
}
@@ -1180,8 +1174,8 @@ public final class S3DBUtils {
int size = simpleAngles.size();
if (indexStart == null) {
for (int i = 0; i < size; i++) {
if (simpleAngles.get(i) < 0) {
// Use first angle as start index;
if (simpleAngles.get(i) > 0) {
// Use first convex angle as start index;
indexStart = i;
break;
}
@@ -1302,8 +1296,12 @@ public final class S3DBUtils {
* @param normVectors the normalized vectors
* @return a list of simple angles:
* 0 straight
* (+/-) 1 (right/left) obtuse angle
* (+/-) 2 (right/left) right angle (or acute angle)
* (+/-) 1 (convex/concave) obtuse angle
* (+/-) 2 (convex/concave) right angle (or acute angle)
* <p>
* Note lhs coordinate system.
* convex: turns right
* concave: turns left
*/
private static List<Byte> getSimpleAngles(List<float[]> normVectors) {
int size = normVectors.size();
@@ -1319,8 +1317,8 @@ public final class S3DBUtils {
float angle = (float) Math.acos(Math.abs(val) > 1 ? Math.signum(val) : val);
// angles.add(angle);
// Positive is turns right, negative turns left
byte simpAngle = (byte) Math.signum(v1[0] * (-v2[1]) + v1[1] * v2[0]);
// Positive turns right (convex), negative turns left (concave)
byte simpAngle = (byte) Math.signum(v1[0] * v2[1] - v1[1] * v2[0]);
if (angle > (MathUtils.PI / 2) - threshold) {
// Right angle
simpAngle *= 2;

View File

@@ -131,13 +131,9 @@ public final class GeometryUtils {
if (out == null)
out = new float[2];
// Calculate center
for (int i = 0; i < n; i += 2, pointPos += 2) {
float x = points[pointPos];
float y = points[pointPos + 1];
out[0] += x;
out[1] += y;
out[0] += points[pointPos];
out[1] += points[pointPos + 1];
}
out[0] = out[0] * 2 / n;
out[1] = out[1] * 2 / n;
@@ -145,6 +141,21 @@ public final class GeometryUtils {
return out;
}
/**
* Calculate the closest point on a line.
* See: https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2
*
* @param pP point
* @param pL point of line
* @param vL vector of line
* @return the closest point on line
*/
public static float[] closestPointOnLine2D(float[] pP, float[] pL, float[] vL) {
float[] vPL = diffVec(pL, pP);
float[] vPS = diffVec(vPL, scale(vL, dotProduct(vPL, vL) / dotProduct(vL, vL)));
return sumVec(pP, vPS);
}
/**
* @param a first vector
* @param b second vector
@@ -222,6 +233,9 @@ public final class GeometryUtils {
}
/**
* Calculate the distance from a point to a line.
* See: https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2
*
* @param pP point
* @param pL point of line
* @param vL vector of line
@@ -229,7 +243,7 @@ public final class GeometryUtils {
*/
public static float distancePointLine2D(float[] pP, float[] pL, float[] vL) {
float[] vPL = diffVec(pL, pP);
float[] vPS = diffVec(vPL, scale(vL, dotProduct(vPL, vL)));
float[] vPS = diffVec(vPL, scale(vL, dotProduct(vPL, vL) / dotProduct(vL, vL)));
return (float) Math.sqrt(dotProduct(vPS, vPS));
}