add FadeStep for fading BitmapTileLayer depending on map scale
add setAlpha method to TileRenderLayer to fade bitmap tiles
This commit is contained in:
parent
daf43858bb
commit
2aa2683581
@ -340,12 +340,15 @@ public class TileManager {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * @param tiles ...
|
* @param tiles ...
|
||||||
// */
|
*/
|
||||||
// public void releaseTiles(TileSet tiles) {
|
public void releaseTiles(TileSet tileSet) {
|
||||||
//
|
// unlock previously active tiles
|
||||||
// }
|
for (int i = 0, n = tileSet.cnt; i < n; i++)
|
||||||
|
tileSet.tiles[i].unlock();
|
||||||
|
tileSet.cnt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* package */MapTile addTile(int x, int y, int zoomLevel) {
|
/* package */MapTile addTile(int x, int y, int zoomLevel) {
|
||||||
MapTile tile;
|
MapTile tile;
|
||||||
|
@ -39,16 +39,24 @@ public class TileRenderLayer extends RenderLayer {
|
|||||||
mUploadSerial = 0;
|
mUploadSerial = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean mFaded;
|
private int mOverdraw = 0;
|
||||||
|
private float mAlpha = 1;
|
||||||
|
|
||||||
public void setFaded(boolean faded) {
|
public void setOverdrawColor(int color) {
|
||||||
mFaded = faded;
|
mOverdraw = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBitmapAlpha(float alpha) {
|
||||||
|
mAlpha = alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(MapPosition pos, boolean positionChanged, Matrices m) {
|
public void update(MapPosition pos, boolean positionChanged, Matrices m) {
|
||||||
|
|
||||||
//mMapPosition.copy(pos);
|
if (mAlpha == 0){
|
||||||
|
mTileManager.releaseTiles(mDrawTiles);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
boolean tilesChanged;
|
boolean tilesChanged;
|
||||||
synchronized (tilelock) {
|
synchronized (tilelock) {
|
||||||
@ -74,7 +82,7 @@ public class TileRenderLayer extends RenderLayer {
|
|||||||
BufferObject.checkBufferUsage(false);
|
BufferObject.checkBufferUsage(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
TileRenderer.draw(tiles, tileCnt, pos, m, mFaded);
|
TileRenderer.draw(tiles, tileCnt, pos, m, mAlpha, mOverdraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -327,5 +335,4 @@ public class TileRenderLayer extends RenderLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ import org.oscim.utils.quadtree.QuadTree;
|
|||||||
* This class is for rendering the Line- and PolygonLayers of visible MapTiles.
|
* This class is for rendering the Line- and PolygonLayers of visible MapTiles.
|
||||||
* For visible tiles that do not have data available yet its parent in children
|
* For visible tiles that do not have data available yet its parent in children
|
||||||
* tiles are rendered when available.
|
* tiles are rendered when available.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class TileRenderer {
|
public class TileRenderer {
|
||||||
//private final static String TAG = TileRenderer.class.getName();
|
//private final static String TAG = TileRenderer.class.getName();
|
||||||
@ -50,14 +49,28 @@ public class TileRenderer {
|
|||||||
private static int mDrawSerial = 0;
|
private static int mDrawSerial = 0;
|
||||||
|
|
||||||
private static Matrices mMatrices;
|
private static Matrices mMatrices;
|
||||||
private static boolean mFaded;
|
|
||||||
|
private static float mFade;
|
||||||
|
private static int mOverdraw;
|
||||||
|
|
||||||
private static final Matrix4 mProjMatrix = new Matrix4();
|
private static final Matrix4 mProjMatrix = new Matrix4();
|
||||||
|
|
||||||
static void draw(MapTile[] tiles, int tileCnt, MapPosition pos, Matrices m, boolean fade) {
|
/**
|
||||||
|
* Draw tiles:
|
||||||
|
*
|
||||||
|
* @param fade
|
||||||
|
* alpha value for bitmap tiles
|
||||||
|
* @param overdrawColor
|
||||||
|
* draw color on top, e.g. to darken the layer temporarily
|
||||||
|
*/
|
||||||
|
static void draw(MapTile[] tiles, int tileCnt, MapPosition pos, Matrices m, float fade,
|
||||||
|
int overdrawColor) {
|
||||||
|
|
||||||
mOffsetCnt = -2048;
|
mOffsetCnt = -2048;
|
||||||
mMatrices = m;
|
mMatrices = m;
|
||||||
mFaded = fade;
|
|
||||||
|
mFade = fade;
|
||||||
|
mOverdraw = overdrawColor;
|
||||||
|
|
||||||
mProjMatrix.copy(m.proj);
|
mProjMatrix.copy(m.proj);
|
||||||
// discard depth projection from tilt, we use depth buffer
|
// discard depth projection from tilt, we use depth buffer
|
||||||
@ -197,7 +210,7 @@ public class TileRenderer {
|
|||||||
//GLState.test(false, false);
|
//GLState.test(false, false);
|
||||||
switch (l.type) {
|
switch (l.type) {
|
||||||
case Layer.BITMAP:
|
case Layer.BITMAP:
|
||||||
l = BitmapRenderer.draw(l, 1, m);
|
l = BitmapRenderer.draw(l, m, 1, mFade);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -205,11 +218,7 @@ public class TileRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear clip-region and could also draw 'fade-effect'
|
PolygonRenderer.drawOver(m, mOverdraw);
|
||||||
if (mFaded)
|
|
||||||
PolygonRenderer.drawOver(m, true, 0x22000000);
|
|
||||||
else
|
|
||||||
PolygonRenderer.drawOver(m, false, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int drawProxyChild(MapTile tile, MapPosition pos) {
|
private static int drawProxyChild(MapTile tile, MapPosition pos) {
|
||||||
|
@ -53,4 +53,9 @@ public abstract class AbstractTileSource implements TileSource {
|
|||||||
result = prime * result + this.port;
|
result = prime * result + this.port;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FadeStep[] getFadeSteps() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,27 +20,63 @@ import java.net.URL;
|
|||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
import org.oscim.view.MapView;
|
|
||||||
import org.oscim.backend.CanvasAdapter;
|
import org.oscim.backend.CanvasAdapter;
|
||||||
import org.oscim.backend.canvas.Bitmap;
|
import org.oscim.backend.canvas.Bitmap;
|
||||||
|
import org.oscim.core.MapPosition;
|
||||||
import org.oscim.core.Tile;
|
import org.oscim.core.Tile;
|
||||||
import org.oscim.layers.tile.MapTile;
|
import org.oscim.layers.tile.MapTile;
|
||||||
import org.oscim.layers.tile.TileLayer;
|
import org.oscim.layers.tile.TileLayer;
|
||||||
import org.oscim.layers.tile.TileLoader;
|
import org.oscim.layers.tile.TileLoader;
|
||||||
import org.oscim.layers.tile.TileManager;
|
import org.oscim.layers.tile.TileManager;
|
||||||
|
import org.oscim.layers.tile.bitmap.TileSource.FadeStep;
|
||||||
import org.oscim.renderer.sublayers.BitmapLayer;
|
import org.oscim.renderer.sublayers.BitmapLayer;
|
||||||
import org.oscim.renderer.sublayers.Layers;
|
import org.oscim.renderer.sublayers.Layers;
|
||||||
|
import org.oscim.utils.FastMath;
|
||||||
|
import org.oscim.view.MapView;
|
||||||
|
|
||||||
|
|
||||||
public class BitmapTileLayer extends TileLayer<TileLoader> {
|
public class BitmapTileLayer extends TileLayer<TileLoader> {
|
||||||
private static final int TIMEOUT_CONNECT = 5000;
|
private static final int TIMEOUT_CONNECT = 5000;
|
||||||
private static final int TIMEOUT_READ = 10000;
|
private static final int TIMEOUT_READ = 10000;
|
||||||
|
protected static final String TAG = BitmapTileLayer.class.getName();
|
||||||
|
|
||||||
final TileSource mTileSource;
|
final TileSource mTileSource;
|
||||||
|
private final FadeStep[] mFade;
|
||||||
|
|
||||||
public BitmapTileLayer(MapView mapView, TileSource tileSource) {
|
public BitmapTileLayer(MapView mapView, TileSource tileSource) {
|
||||||
super(mapView, tileSource.getZoomLevelMin(), tileSource.getZoomLevelMax(), 100);
|
super(mapView, tileSource.getZoomLevelMin(), tileSource.getZoomLevelMax(), 100);
|
||||||
mTileSource = tileSource;
|
mTileSource = tileSource;
|
||||||
|
mFade = mTileSource.getFadeSteps();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(MapPosition pos, boolean changed, boolean clear) {
|
||||||
|
super.onUpdate(pos, changed, clear);
|
||||||
|
|
||||||
|
if (mFade == null) {
|
||||||
|
mRenderLayer.setBitmapAlpha(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float alpha = 0;
|
||||||
|
for (FadeStep f : mFade) {
|
||||||
|
if (pos.scale < f.scaleStart || pos.scale > f.scaleEnd)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (f.alphaStart == f.alphaEnd) {
|
||||||
|
alpha = f.alphaStart;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
double range = f.scaleEnd / f.scaleStart;
|
||||||
|
float a = (float)((range - (pos.scale / f.scaleStart)) / range);
|
||||||
|
a = FastMath.clamp(a, 0, 1);
|
||||||
|
// interpolate alpha between start and end
|
||||||
|
alpha = a * f.alphaStart + (1 - a) * f.alphaEnd;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mRenderLayer.setBitmapAlpha(alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -61,7 +97,6 @@ public class BitmapTileLayer extends TileLayer<TileLoader> {
|
|||||||
l.setBitmap(bitmap, Tile.SIZE, Tile.SIZE);
|
l.setBitmap(bitmap, Tile.SIZE, Tile.SIZE);
|
||||||
|
|
||||||
tile.layers.textureLayers = l;
|
tile.layers.textureLayers = l;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
|
@ -20,10 +20,11 @@ import java.net.URL;
|
|||||||
import org.oscim.core.Tile;
|
import org.oscim.core.Tile;
|
||||||
|
|
||||||
public class NaturalEarth extends AbstractTileSource {
|
public class NaturalEarth extends AbstractTileSource {
|
||||||
public static final NaturalEarth INSTANCE = new NaturalEarth("city.informatik.uni-bremen.de", 80);
|
public static final NaturalEarth INSTANCE = new NaturalEarth("city.informatik.uni-bremen.de",
|
||||||
|
80);
|
||||||
private static final int PARALLEL_REQUESTS_LIMIT = 4;
|
private static final int PARALLEL_REQUESTS_LIMIT = 4;
|
||||||
private static final String PROTOCOL = "http";
|
private static final String PROTOCOL = "http";
|
||||||
private static final int ZOOM_LEVEL_MAX = 6;
|
private static final int ZOOM_LEVEL_MAX = 8;
|
||||||
private static final int ZOOM_LEVEL_MIN = 0;
|
private static final int ZOOM_LEVEL_MIN = 0;
|
||||||
|
|
||||||
public NaturalEarth(String hostName, int port) {
|
public NaturalEarth(String hostName, int port) {
|
||||||
@ -38,12 +39,14 @@ public class NaturalEarth extends AbstractTileSource {
|
|||||||
@Override
|
@Override
|
||||||
public URL getTileUrl(Tile tile) throws MalformedURLException {
|
public URL getTileUrl(Tile tile) throws MalformedURLException {
|
||||||
StringBuilder stringBuilder = new StringBuilder(32);
|
StringBuilder stringBuilder = new StringBuilder(32);
|
||||||
stringBuilder.append("/osci/ne_image2/");
|
|
||||||
|
stringBuilder.append("/tiles/ne/");
|
||||||
|
//stringBuilder.append("/osci/ne_image2/");
|
||||||
stringBuilder.append(tile.zoomLevel);
|
stringBuilder.append(tile.zoomLevel);
|
||||||
stringBuilder.append('/');
|
stringBuilder.append('/');
|
||||||
stringBuilder.append(tile.tileX);
|
stringBuilder.append(tile.tileX);
|
||||||
stringBuilder.append('/');
|
stringBuilder.append('/');
|
||||||
stringBuilder.append((1 << tile.zoomLevel) - tile.tileY - 1);
|
stringBuilder.append(tile.tileY);
|
||||||
stringBuilder.append(".png");
|
stringBuilder.append(".png");
|
||||||
|
|
||||||
return new URL(PROTOCOL, this.hostName, this.port, stringBuilder.toString());
|
return new URL(PROTOCOL, this.hostName, this.port, stringBuilder.toString());
|
||||||
@ -58,4 +61,14 @@ public class NaturalEarth extends AbstractTileSource {
|
|||||||
public byte getZoomLevelMin() {
|
public byte getZoomLevelMin() {
|
||||||
return ZOOM_LEVEL_MIN;
|
return ZOOM_LEVEL_MIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FadeStep[] getFadeSteps() {
|
||||||
|
return new FadeStep[] {
|
||||||
|
new FadeStep(ZOOM_LEVEL_MIN, ZOOM_LEVEL_MAX - 1, 1, 0.7f),
|
||||||
|
// dont fade between zoom-min/max
|
||||||
|
// fade above zoom max + 2, interpolate 1 to 0
|
||||||
|
new FadeStep(ZOOM_LEVEL_MAX - 1, ZOOM_LEVEL_MAX + 1, 0.7f, 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,4 +39,18 @@ public interface TileSource {
|
|||||||
* @return the minimum zoom level which this {@code TileSource} supports.
|
* @return the minimum zoom level which this {@code TileSource} supports.
|
||||||
*/
|
*/
|
||||||
byte getZoomLevelMin();
|
byte getZoomLevelMin();
|
||||||
|
|
||||||
|
FadeStep[] getFadeSteps();
|
||||||
|
|
||||||
|
public class FadeStep{
|
||||||
|
public final double scaleStart, scaleEnd;
|
||||||
|
public final float alphaStart, alphaEnd;
|
||||||
|
|
||||||
|
public FadeStep(int zoomStart, int zoomEnd, float alphaStart, float alphaEnd) {
|
||||||
|
this.scaleStart = 1 << zoomStart;
|
||||||
|
this.scaleEnd = 1 << zoomEnd;
|
||||||
|
this.alphaStart = alphaStart;
|
||||||
|
this.alphaEnd = alphaEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ public abstract class BasicRenderLayer extends RenderLayer {
|
|||||||
for (Layer l = layers.textureLayers; l != null;) {
|
for (Layer l = layers.textureLayers; l != null;) {
|
||||||
switch (l.type) {
|
switch (l.type) {
|
||||||
case Layer.BITMAP:
|
case Layer.BITMAP:
|
||||||
l = BitmapRenderer.draw(l, 1, m);
|
l = BitmapRenderer.draw(l, m, 1, 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -92,6 +92,6 @@ public class BitmapRenderLayer extends BasicRenderLayer {
|
|||||||
@Override
|
@Override
|
||||||
public synchronized void render(MapPosition pos, Matrices m) {
|
public synchronized void render(MapPosition pos, Matrices m) {
|
||||||
m.useScreenCoordinates(false, 8);
|
m.useScreenCoordinates(false, 8);
|
||||||
BitmapRenderer.draw(layers.textureLayers, 1, m);
|
BitmapRenderer.draw(layers.textureLayers, m, 1, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,8 @@ public final class BitmapRenderer {
|
|||||||
private static int hTextureScreenScale;
|
private static int hTextureScreenScale;
|
||||||
private static int hTextureTexCoord;
|
private static int hTextureTexCoord;
|
||||||
|
|
||||||
|
private static int hAlpha;
|
||||||
|
|
||||||
public final static int INDICES_PER_SPRITE = 6;
|
public final static int INDICES_PER_SPRITE = 6;
|
||||||
final static int VERTICES_PER_SPRITE = 4;
|
final static int VERTICES_PER_SPRITE = 4;
|
||||||
final static int SHORTS_PER_VERTICE = 6;
|
final static int SHORTS_PER_VERTICE = 6;
|
||||||
@ -53,9 +55,10 @@ public final class BitmapRenderer {
|
|||||||
hTextureScreenScale = GL.glGetUniformLocation(mTextureProgram, "u_swidth");
|
hTextureScreenScale = GL.glGetUniformLocation(mTextureProgram, "u_swidth");
|
||||||
hTextureVertex = GL.glGetAttribLocation(mTextureProgram, "vertex");
|
hTextureVertex = GL.glGetAttribLocation(mTextureProgram, "vertex");
|
||||||
hTextureTexCoord = GL.glGetAttribLocation(mTextureProgram, "tex_coord");
|
hTextureTexCoord = GL.glGetAttribLocation(mTextureProgram, "tex_coord");
|
||||||
|
hAlpha = GL.glGetUniformLocation(mTextureProgram, "u_alpha");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Layer draw(Layer layer, float scale, Matrices m) {
|
public static Layer draw(Layer layer, Matrices m, float scale, float alpha) {
|
||||||
//GLState.test(false, false);
|
//GLState.test(false, false);
|
||||||
GLState.blend(true);
|
GLState.blend(true);
|
||||||
|
|
||||||
@ -70,7 +73,9 @@ public final class BitmapRenderer {
|
|||||||
else
|
else
|
||||||
GL.glUniform1f(hTextureScale, 1);
|
GL.glUniform1f(hTextureScale, 1);
|
||||||
|
|
||||||
|
|
||||||
GL.glUniform1f(hTextureScreenScale, 1f / GLRenderer.screenWidth);
|
GL.glUniform1f(hTextureScreenScale, 1f / GLRenderer.screenWidth);
|
||||||
|
GL.glUniform1f(hAlpha, alpha);
|
||||||
|
|
||||||
m.proj.setAsUniform(hTextureProjMatrix);
|
m.proj.setAsUniform(hTextureProjMatrix);
|
||||||
|
|
||||||
@ -109,9 +114,6 @@ public final class BitmapRenderer {
|
|||||||
return layer.next;
|
return layer.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
//private final static double TEX_COORD_DIV = 1.0 / (1024 * COORD_SCALE);
|
|
||||||
//private final static double COORD_DIV = 1.0 / GLRenderer.COORD_SCALE;
|
|
||||||
|
|
||||||
private final static String textVertexShader = ""
|
private final static String textVertexShader = ""
|
||||||
+ "precision mediump float; "
|
+ "precision mediump float; "
|
||||||
+ "attribute vec4 vertex;"
|
+ "attribute vec4 vertex;"
|
||||||
@ -121,18 +123,17 @@ public final class BitmapRenderer {
|
|||||||
+ "uniform float u_scale;"
|
+ "uniform float u_scale;"
|
||||||
+ "uniform float u_swidth;"
|
+ "uniform float u_swidth;"
|
||||||
+ "varying vec2 tex_c;"
|
+ "varying vec2 tex_c;"
|
||||||
//+ "const vec2 div = vec2(" + TEX_COORD_DIV + "," + TEX_COORD_DIV + ");"
|
|
||||||
//+ "const float coord_scale = " + COORD_DIV + ";"
|
|
||||||
+ "void main() {"
|
+ "void main() {"
|
||||||
+ " gl_Position = u_mv * vec4(vertex.xy, 0.0, 1.0);"
|
+ " gl_Position = u_mv * vec4(vertex.xy, 0.0, 1.0);"
|
||||||
+ " tex_c = tex_coord;" // * div;"
|
+ " tex_c = tex_coord;"
|
||||||
+ "}";
|
+ "}";
|
||||||
|
|
||||||
private final static String textFragmentShader = ""
|
private final static String textFragmentShader = ""
|
||||||
+ "precision mediump float;"
|
+ "precision mediump float;"
|
||||||
+ "uniform sampler2D tex;"
|
+ "uniform sampler2D tex;"
|
||||||
|
+ "uniform float u_alpha;"
|
||||||
+ "varying vec2 tex_c;"
|
+ "varying vec2 tex_c;"
|
||||||
+ "void main() {"
|
+ "void main() {"
|
||||||
+ " gl_FragColor = texture2D(tex, tex_c.xy);"
|
+ " gl_FragColor = texture2D(tex, tex_c.xy) * u_alpha;"
|
||||||
+ "}";
|
+ "}";
|
||||||
}
|
}
|
||||||
|
@ -338,7 +338,7 @@ public final class PolygonRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void drawOver(Matrices m, boolean drawColor, int color) {
|
public static void drawOver(Matrices m, int color) {
|
||||||
setShader(polyShader, m);
|
setShader(polyShader, m);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -346,7 +346,7 @@ public final class PolygonRenderer {
|
|||||||
* a quad with func 'always' and op 'zero'
|
* a quad with func 'always' and op 'zero'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (drawColor) {
|
if (color != 0) {
|
||||||
GlUtils.setColor(hPolygonColor[0], color, 1);
|
GlUtils.setColor(hPolygonColor[0], color, 1);
|
||||||
GLState.blend(true);
|
GLState.blend(true);
|
||||||
} else {
|
} else {
|
||||||
@ -364,7 +364,7 @@ public final class PolygonRenderer {
|
|||||||
|
|
||||||
GL.glDrawArrays(GL20.GL_TRIANGLE_STRIP, 0, 4);
|
GL.glDrawArrays(GL20.GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
|
||||||
if (!drawColor)
|
if (color == 0)
|
||||||
GL.glColorMask(true, true, true, true);
|
GL.glColorMask(true, true, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user