Location texture renderer (#548)

- add LocationTextureLayer with accuracy
- add Android sample for LocationTextureLayer
This commit is contained in:
Longri 2018-06-01 09:43:39 +02:00 committed by Emux
parent befa40e094
commit 0f5af24361
No known key found for this signature in database
GPG Key ID: 64ED9980896038C3
5 changed files with 504 additions and 1 deletions

View File

@ -57,6 +57,9 @@
<activity
android:name=".LocationActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".LocationTextureActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
<activity
android:name=".MapEventLayer2Activity"
android:configChanges="keyboardHidden|orientation|screenSize" />

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" width="249.667" height="254.667" viewBox="0 0 249.67 254.67" enable-background="new 0 0 249.67 254.67" xml:space="preserve">
<linearGradient id="SVGID_Fill1_" gradientUnits="objectBoundingBox" x1="-0.0161654" y1="0.500001" x2="1.01616" y2="0.500001">
<stop offset="0.0344828" stop-color="#FF0000" stop-opacity="1"/>
<stop offset="0.5" stop-color="#FF0000" stop-opacity="1"/>
<stop offset="1" stop-color="#F8E6E6" stop-opacity="1"/>
</linearGradient>
<path fill="url(#SVGID_Fill1_)" stroke-width="5" stroke-linejoin="round" stroke="#000000" stroke-opacity="1" d="M 47.5002,250.834L 124.833,2.50006L 202.167,252.167L 124.167,199.167L 47.5002,250.834 Z "/>
<linearGradient id="SVGID_Fill2_" gradientUnits="objectBoundingBox" x1="0.0255055" y1="0.518124" x2="0.986547" y2="0.518124" gradientTransform="rotate(42.130627 0.025506 0.518124)">
<stop offset="0.301724" stop-color="#ECECEC" stop-opacity="0.345098"/>
<stop offset="0.62931" stop-color="#1C1C1C" stop-opacity="0.776471"/>
<stop offset="0.689655" stop-color="#000000" stop-opacity="0.423529"/>
<stop offset="1" stop-color="#000000" stop-opacity="0.286275"/>
</linearGradient>
<filter id="Filter_GaussianBlur1_" filterUnits="userSpaceOnUse">
<feGaussianBlur stdDeviation="1.88976"/>
</filter>
<path fill="url(#SVGID_Fill2_)" stroke-width="0.2" stroke-linejoin="round" filter="url(#Filter_GaussianBlur1_)" d="M 56.8333,241.5L 124.833,31.0001L 123.5,195.167L 56.8333,241.5 Z "/>
</svg>

View File

@ -0,0 +1,118 @@
/*
* Copyright 2016-2018 devemux86
* Copyright 2018 Longri
*
* 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.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import org.oscim.backend.CanvasAdapter;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.backend.canvas.Color;
import org.oscim.core.MapPosition;
import org.oscim.layers.LocationTextureLayer;
import org.oscim.renderer.atlas.TextureAtlas;
import org.oscim.renderer.atlas.TextureRegion;
import org.oscim.renderer.bucket.TextureItem;
import java.io.IOException;
import java.io.InputStream;
public class LocationTextureActivity extends BitmapTileActivity implements LocationListener {
private LocationTextureLayer locationLayer;
private LocationManager locationManager;
private final MapPosition mapPosition = new MapPosition();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//initial Android locationManager
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
//load a Bitmap/Svg from resources. (North/0° are on top
InputStream is = getResources().openRawResource(R.raw.arrow);
Bitmap bmp = null;
try {
bmp = CanvasAdapter.decodeSvgBitmap(is, 200, 200, 100);
} catch (IOException e) {
e.printStackTrace();
}
//create TextureRegion from Bitmap
TextureRegion textureRegion = new TextureRegion(new TextureItem(bmp), new TextureAtlas.Rect(0, 0, bmp.getWidth(), bmp.getHeight()));
//create LocationTexturLayer with created/loaded TextureRegion
locationLayer = new LocationTextureLayer(mMap, textureRegion);
// you can set the Color of Accuracy circle (Color.BLUE is default)
locationLayer.setAccuracyColor(Color.get(50, 50, 255));
// you can set the Color of Indicator circle (Color.RED is default)
locationLayer.setIndicatorColor(Color.MAGENTA);
locationLayer.setEnabled(false);
mMap.layers().add(locationLayer);
}
@Override
protected void onResume() {
super.onResume();
enableAvailableProviders();
}
@Override
protected void onPause() {
super.onPause();
locationManager.removeUpdates(this);
}
@Override
public void onLocationChanged(Location location) {
locationLayer.setEnabled(true);
locationLayer.setPosition(location.getLatitude(), location.getLongitude(), location.getBearing(), location.getAccuracy());
// Follow location
mMap.getMapPosition(mapPosition);
mapPosition.setPosition(location.getLatitude(), location.getLongitude());
mMap.setMapPosition(mapPosition);
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
private void enableAvailableProviders() {
locationManager.removeUpdates(this);
for (String provider : locationManager.getProviders(true)) {
if (LocationManager.GPS_PROVIDER.equals(provider)
|| LocationManager.NETWORK_PROVIDER.equals(provider)) {
locationManager.requestLocationUpdates(provider, 0, 0, this);
}
}
}
}

View File

@ -2,7 +2,7 @@
* Copyright 2010, 2011, 2012, 2013 mapsforge.org
* Copyright 2013 Hannes Janetzek
* Copyright 2016-2018 devemux86
* Copyright 2017 Longri
* Copyright 2017-2018 Longri
* Copyright 2017 nebular
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
@ -98,6 +98,7 @@ public class Samples extends Activity {
}
}));
linearLayout.addView(createButton(LocationActivity.class));
linearLayout.addView(createButton(LocationTextureActivity.class));
linearLayout.addView(createButton(PoiSearchActivity.class));
linearLayout.addView(createLabel("Vector Features"));

View File

@ -0,0 +1,362 @@
/*
* Copyright 2017-2018 Longri
*
* 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 org.oscim.backend.CanvasAdapter;
import org.oscim.backend.GL;
import org.oscim.backend.canvas.Color;
import org.oscim.core.Box;
import org.oscim.core.MercatorProjection;
import org.oscim.core.Point;
import org.oscim.core.PointF;
import org.oscim.core.Tile;
import org.oscim.map.Map;
import org.oscim.renderer.BucketRenderer;
import org.oscim.renderer.GLShader;
import org.oscim.renderer.GLState;
import org.oscim.renderer.GLUtils;
import org.oscim.renderer.GLViewport;
import org.oscim.renderer.MapRenderer;
import org.oscim.renderer.atlas.TextureRegion;
import org.oscim.renderer.bucket.SymbolBucket;
import org.oscim.renderer.bucket.SymbolItem;
import org.oscim.utils.FastMath;
import org.oscim.utils.geom.GeometryUtils;
import org.oscim.utils.math.Interpolation;
import java.util.Locale;
import static org.oscim.backend.GLAdapter.gl;
public class LocationTextureLayer extends Layer {
private static final PointF CENTER_OFFSET = new PointF(0.5f, 0.5f);
private static final long ANIM_RATE = 50;
private static final long INTERVAL = 2000;
private static final float CIRCLE_SIZE = 30;
private static final int SHOW_ACCURACY_ZOOM = 13;
private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("mac");
private final static String V_SHADER = (""
+ "precision highp float;"
+ "uniform mat4 u_mvp;"
+ "uniform float u_phase;"
+ "uniform float u_scale;"
+ "attribute vec2 a_pos;"
+ "varying vec2 v_tex;"
+ "void main() {"
+ " gl_Position = u_mvp * vec4(a_pos * u_scale * u_phase, 0.0, 1.0);"
+ " v_tex = a_pos;"
+ "}").replace("precision highp float;", IS_MAC ? "" : "precision highp float;");
// only circle without direction
private static final String F_SHADER = (""
+ "precision highp float;"
+ "varying vec2 v_tex;"
+ "uniform float u_scale;"
+ "uniform float u_phase;"
+ "uniform vec4 u_fill;"
+ "void main() {"
+ " float len = 1.0 - length(v_tex);"
/// outer ring
+ " float a = smoothstep(0.0, 2.0 / u_scale, len);"
/// inner ring
+ " float b = 0.8 * smoothstep(3.0 / u_scale, 4.0 / u_scale, len);"
/// center point
+ " float c = 0.5 * (1.0 - smoothstep(14.0 / u_scale, 16.0 / u_scale, 1.0 - len));"
+ " vec2 dir = normalize(v_tex);"
/// - subtract inner from outer to create the outline
/// - multiply by viewshed
/// - add center point
+ " a = (a - (b + c)) + c;"
+ " gl_FragColor = u_fill * a;"
+ "}").replace("precision highp float;", IS_MAC ? "" : "precision highp float;");
private final LocationTextureRenderer locationRenderer;
public LocationTextureLayer(Map map, TextureRegion textureRegion) {
super(map);
mRenderer = locationRenderer = new LocationTextureRenderer(map);
setTextureRegion(textureRegion);
}
public void setPosition(double latitude, double longitude, float heading, double accuracy) {
locationRenderer.setPosition(latitude, longitude, heading, accuracy);
}
public void setAccuracyColor(int color) {
locationRenderer.setAccuracyColor(color);
}
public void setIndicatorColor(int color) {
locationRenderer.setIndicatorColor(color);
}
public void setTextureRegion(TextureRegion region) {
locationRenderer.setTextureRegion(region);
}
private static final class LocationTextureRenderer extends BucketRenderer {
private final SymbolBucket symbolBucket;
private final float[] box = new float[8];
private final Point mapPoint = new Point();
private final Map map;
private boolean initialized;
private boolean locationIsVisible;
private int shaderProgramNumber;
private int hVertexPosition;
private int hMatrixPosition;
private int hScale;
private int hPhase;
private int uFill;
private double accuracyRadius;
private final Point indicatorPosition = new Point();
private final Point screenPoint = new Point();
private final Box boundingBox = new Box();
private boolean runAnim;
private long animStart;
private boolean update;
private float heading;
//properties
private TextureRegion textureRegion;
private int accuracyColor = Color.BLUE;
private int viewShedColor = Color.RED;
private LocationTextureRenderer(Map map) {
symbolBucket = new SymbolBucket();
this.map = map;
}
public void setPosition(double latitude, double longitude, float heading, double accuracy) {
update = true;
this.heading = -heading;
while (this.heading < 0) this.heading += 360;
mapPoint.x = MercatorProjection.longitudeToX(longitude);
mapPoint.y = MercatorProjection.latitudeToY(latitude);
accuracyRadius = accuracy;
}
private void setAccuracyColor(int color) {
this.accuracyColor = color;
}
private void setIndicatorColor(int color) {
this.viewShedColor = color;
}
private void setTextureRegion(TextureRegion region) {
textureRegion = region;
}
@Override
public synchronized void update(GLViewport v) {
if (!v.changed() && !update) return;
//accuracy
if (!initialized) {
init();
initialized = true;
}
setReady(true);
int width = map.getWidth();
int height = map.getHeight();
v.getBBox(boundingBox, 0);
double x = mapPoint.x;
double y = mapPoint.y;
if (!boundingBox.contains(mapPoint)) {
x = FastMath.clamp(x, boundingBox.xmin, boundingBox.xmax);
y = FastMath.clamp(y, boundingBox.ymin, boundingBox.ymax);
}
// get position of Location in pixel relative to
// screen center
v.toScreenPoint(x, y, screenPoint);
x = screenPoint.x + width / 2;
y = screenPoint.y + height / 2;
// clip position to screen boundaries
int visible = 0;
if (x > width - 5)
x = width;
else if (x < 5)
x = 0;
else
visible++;
if (y > height - 5)
y = height;
else if (y < 5)
y = 0;
else
visible++;
locationIsVisible = (visible == 2);
if (locationIsVisible) {
animate(false);
} else {
animate(true);
}
// set location indicator position
v.fromScreenPoint(x, y, indicatorPosition);
//Texture
mMapPosition.copy(v.pos);
double mx = v.pos.x;
double my = v.pos.y;
double scale = Tile.SIZE * v.pos.scale;
map.viewport().getMapExtents(box, 100);
long flip = (long) (Tile.SIZE * v.pos.scale) >> 1;
/* check visibility */
float symbolX = (float) ((mapPoint.x - mx) * scale);
float symbolY = (float) ((mapPoint.y - my) * scale);
if (symbolX > flip)
symbolX -= (flip << 1);
else if (symbolX < -flip)
symbolX += (flip << 1);
buckets.clear();
if (!GeometryUtils.pointInPoly(symbolX, symbolY, box, 8, 0)) {
return;
}
mMapPosition.bearing = -mMapPosition.bearing;
if (textureRegion == null) return;
SymbolItem symbolItem = SymbolItem.pool.get();
symbolItem.set(symbolX, symbolY, textureRegion, this.heading, true);
symbolItem.offset = CENTER_OFFSET;
symbolBucket.pushSymbol(symbolItem);
buckets.set(symbolBucket);
buckets.prepare();
buckets.compile(true);
compile();
update = false;
}
@Override
public void render(GLViewport v) {
renderAccuracyCircle(v);
super.render(v);
}
private void init() {
int shader = GLShader.createProgram(V_SHADER, F_SHADER);
if (shader == 0)
return;
shaderProgramNumber = shader;
hVertexPosition = gl.getAttribLocation(shader, "a_pos");
hMatrixPosition = gl.getUniformLocation(shader, "u_mvp");
hPhase = gl.getUniformLocation(shader, "u_phase");
hScale = gl.getUniformLocation(shader, "u_scale");
uFill = gl.getUniformLocation(shader, "u_fill");
}
private void renderAccuracyCircle(GLViewport v) {
GLState.useProgram(shaderProgramNumber);
GLState.blend(true);
GLState.test(false, false);
GLState.enableVertexArrays(hVertexPosition, -1);
MapRenderer.bindQuadVertexVBO(hVertexPosition/*, true*/);
float radius = 10;
boolean viewShed = false;
if (!locationIsVisible) {
radius = CIRCLE_SIZE * CanvasAdapter.getScale();
} else {
if (v.pos.zoomLevel >= SHOW_ACCURACY_ZOOM) {
radius = (float) (accuracyRadius / MercatorProjection.groundResolution(v.pos));
}
radius = Math.max(2, radius);
viewShed = true;
}
gl.uniform1f(hScale, radius);
double x = indicatorPosition.x - v.pos.x;
double y = indicatorPosition.y - v.pos.y;
double tileScale = Tile.SIZE * v.pos.scale;
v.mvp.setTransScale((float) (x * tileScale), (float) (y * tileScale), 1);
v.mvp.multiplyMM(v.viewproj, v.mvp);
v.mvp.setAsUniform(hMatrixPosition);
if (!viewShed) {
float phase = Math.abs(animPhase() - 0.5f) * 2;
//phase = Interpolation.fade.apply(phase);
phase = Interpolation.swing.apply(phase);
gl.uniform1f(hPhase, 0.8f + phase * 0.2f);
} else {
gl.uniform1f(hPhase, 1);
}
if (viewShed && locationIsVisible) {
GLUtils.setColor(uFill, accuracyColor, 1);
} else {
GLUtils.setColor(uFill, viewShedColor, 1);
}
gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4);
gl.flush();
}
private void animate(boolean enable) {
if (runAnim == enable)
return;
runAnim = enable;
if (!enable)
return;
final Runnable action = new Runnable() {
private long lastRun;
@Override
public void run() {
if (!runAnim)
return;
long diff = System.currentTimeMillis() - lastRun;
map.postDelayed(this, Math.min(ANIM_RATE, diff));
map.render();
lastRun = System.currentTimeMillis();
}
};
animStart = System.currentTimeMillis();
map.postDelayed(action, ANIM_RATE);
}
private float animPhase() {
return (float) ((MapRenderer.frametime - animStart) % INTERVAL) / INTERVAL;
}
}
}