Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54b7884fa3 | ||
|
|
e5bc7c7564 | ||
|
|
cf49591d4f | ||
|
|
71f94f3f79 | ||
|
|
db6be82ce2 | ||
|
|
ba65786b5a | ||
|
|
036d63c9dd | ||
|
|
07e86d24f6 |
@@ -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)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user