mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Server OAPIF temporal extent
This commit is contained in:
parent
c9df6aee25
commit
358c814722
@ -72,6 +72,26 @@ datetime = date-time / interval
|
||||
%End
|
||||
|
||||
|
||||
|
||||
static QVariantList temporalExtentList( const QgsVectorLayer *layer ) /PyName=temporalExtent/;
|
||||
%Docstring
|
||||
temporalExtent returns a json array with an array of [min, max] temporal extent for the given ``layer``.
|
||||
In case multiple temporal dimensions are available in the layer, a union of all dimensions is returned.
|
||||
|
||||
From specifications: http://schemas.opengis.net/ogcapi/features/part1/1.0/openapi/schemas/extent.yaml
|
||||
|
||||
One or more time intervals that describe the temporal extent of the dataset.
|
||||
The value `null` is supported and indicates an open time interval.
|
||||
|
||||
In the Core only a single time interval is supported. Extensions may support
|
||||
multiple intervals. If multiple intervals are provided, the union of the
|
||||
intervals describes the temporal extent.
|
||||
|
||||
:return: An array of intervals
|
||||
|
||||
.. versionadded:: 3.12
|
||||
%End
|
||||
|
||||
static QgsCoordinateReferenceSystem parseCrs( const QString &bboxCrs );
|
||||
%Docstring
|
||||
Parses the CRS URI ``bboxCrs`` (example: "http://www.opengis.net/def/crs/OGC/1.3/CRS84") into a QGIS CRS object
|
||||
|
@ -188,6 +188,40 @@
|
||||
"items" : {
|
||||
"type" : "number"
|
||||
}
|
||||
},
|
||||
"temporal" : {
|
||||
"description" : "The temporal extent of the features in the collection.",
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"interval" : {
|
||||
"description" : "One or more time intervals that describe the temporal extent of the dataset.\nThe value `null` is supported and indicates an open time interval.\nIn the Core only a single time interval is supported. Extensions may support multiple intervals. If multiple intervals are provided, the union of the intervals describes the temporal extent.",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items" : {
|
||||
"description" : "Begin and end times of the time interval. The timestamps\nare in the coordinate reference system specified in `trs`. By default\nthis is the Gregorian calendar.",
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": {
|
||||
"type" : "string",
|
||||
"format" : "date-time",
|
||||
"nullable" : true,
|
||||
"example" : [
|
||||
"2011-11-11T12:22:11Z",
|
||||
null
|
||||
],
|
||||
"trs" : {
|
||||
"description": "Coordinate reference system of the coordinates in the temporal extent\n(property `interval`). The default reference system is the Gregorian calendar.\nIn the Core this is the only supported temporal reference system.\nExtensions may support additional temporal reference systems and add\nadditional enum values.",
|
||||
"type": "string",
|
||||
"enum" : [
|
||||
"http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
|
||||
],
|
||||
"default" : "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -415,6 +415,72 @@ json QgsServerApiUtils::layerExtent( const QgsVectorLayer *layer )
|
||||
return {{ extent.xMinimum(), extent.yMinimum(), extent.xMaximum(), extent.yMaximum() }};
|
||||
}
|
||||
|
||||
json QgsServerApiUtils::temporalExtent( const QgsVectorLayer *layer )
|
||||
{
|
||||
// Helper to get min/max from a dimension
|
||||
auto range = [ & ]( const QgsVectorLayerServerProperties::WmsDimensionInfo & dimInfo ) -> QgsDateTimeRange
|
||||
{
|
||||
QgsDateTimeRange result;
|
||||
// min
|
||||
int fieldIdx { layer->fields().lookupField( dimInfo.fieldName )};
|
||||
if ( fieldIdx < 0 )
|
||||
{
|
||||
return result;
|
||||
}
|
||||
QDateTime min { layer->minimumValue( fieldIdx ).toDateTime() };
|
||||
QDateTime max { layer->maximumValue( fieldIdx ).toDateTime() };
|
||||
if ( ! dimInfo.endFieldName.isEmpty() )
|
||||
{
|
||||
fieldIdx = layer->fields().lookupField( dimInfo.endFieldName );
|
||||
if ( fieldIdx >= 0 )
|
||||
{
|
||||
QDateTime minEnd { layer->minimumValue( fieldIdx ).toDateTime() };
|
||||
QDateTime maxEnd { layer->maximumValue( fieldIdx ).toDateTime() };
|
||||
if ( minEnd.isValid() )
|
||||
{
|
||||
min = std::min<QDateTime>( min, layer->minimumValue( fieldIdx ).toDateTime() );
|
||||
}
|
||||
if ( maxEnd.isValid() )
|
||||
{
|
||||
max = std::max<QDateTime>( max, layer->maximumValue( fieldIdx ).toDateTime() );
|
||||
}
|
||||
}
|
||||
}
|
||||
return { min, max };
|
||||
};
|
||||
|
||||
const QList<QgsVectorLayerServerProperties::WmsDimensionInfo> dimensions { QgsServerApiUtils::temporalDimensions( layer ) };
|
||||
if ( dimensions.isEmpty() )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
QgsDateTimeRange extent;
|
||||
for ( const auto &dimension : dimensions )
|
||||
{
|
||||
// Get min/max for dimension
|
||||
extent.extend( range( dimension ) );
|
||||
}
|
||||
json ret = json::array();
|
||||
const QString beginVal { extent.begin().toString( Qt::DateFormat::ISODate ) };
|
||||
const QString endVal { extent.end().toString( Qt::DateFormat::ISODate ) };
|
||||
ret.push_back(
|
||||
{
|
||||
beginVal.isEmpty() ? nullptr : beginVal.toStdString(),
|
||||
endVal.isEmpty() ? nullptr : endVal.toStdString()
|
||||
} );
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList QgsServerApiUtils::temporalExtentList( const QgsVectorLayer *layer ) SIP_PYNAME( temporalExtent )
|
||||
{
|
||||
QVariantList list;
|
||||
list.push_back( QgsJsonUtils::parseArray( QString::fromStdString( temporalExtent( layer )[0].dump() ) ) );
|
||||
return list;
|
||||
}
|
||||
|
||||
QgsCoordinateReferenceSystem QgsServerApiUtils::parseCrs( const QString &bboxCrs )
|
||||
{
|
||||
QgsCoordinateReferenceSystem crs;
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "qgsserverexception.h"
|
||||
#include "qgsvectorlayerserverproperties.h"
|
||||
#include "qgsrange.h"
|
||||
#include "qgsjsonutils.h"
|
||||
|
||||
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
||||
#include "qgsaccesscontrol.h"
|
||||
@ -105,6 +106,43 @@ class SERVER_EXPORT QgsServerApiUtils
|
||||
*/
|
||||
static json layerExtent( const QgsVectorLayer *layer ) SIP_SKIP;
|
||||
|
||||
/**
|
||||
* temporalExtent returns a json array with an array of [min, max] temporal extent for the given \a layer.
|
||||
* In case multiple temporal dimensions are available in the layer, a union of all dimensions is returned.
|
||||
*
|
||||
* From specifications: http://schemas.opengis.net/ogcapi/features/part1/1.0/openapi/schemas/extent.yaml
|
||||
*
|
||||
* One or more time intervals that describe the temporal extent of the dataset.
|
||||
* The value `null` is supported and indicates an open time interval.
|
||||
*
|
||||
* In the Core only a single time interval is supported. Extensions may support
|
||||
* multiple intervals. If multiple intervals are provided, the union of the
|
||||
* intervals describes the temporal extent.
|
||||
*
|
||||
* \return An array of intervals
|
||||
* \note not available in Python bindings
|
||||
* \since QGIS 3.12
|
||||
*/
|
||||
static json temporalExtent( const QgsVectorLayer *layer ) SIP_SKIP;
|
||||
|
||||
/**
|
||||
* temporalExtent returns a json array with an array of [min, max] temporal extent for the given \a layer.
|
||||
* In case multiple temporal dimensions are available in the layer, a union of all dimensions is returned.
|
||||
*
|
||||
* From specifications: http://schemas.opengis.net/ogcapi/features/part1/1.0/openapi/schemas/extent.yaml
|
||||
*
|
||||
* One or more time intervals that describe the temporal extent of the dataset.
|
||||
* The value `null` is supported and indicates an open time interval.
|
||||
*
|
||||
* In the Core only a single time interval is supported. Extensions may support
|
||||
* multiple intervals. If multiple intervals are provided, the union of the
|
||||
* intervals describes the temporal extent.
|
||||
*
|
||||
* \return An array of intervals
|
||||
* \since QGIS 3.12
|
||||
*/
|
||||
static QVariantList temporalExtentList( const QgsVectorLayer *layer ) SIP_PYNAME( temporalExtent );
|
||||
|
||||
/**
|
||||
* Parses the CRS URI \a bboxCrs (example: "http://www.opengis.net/def/crs/OGC/1.3/CRS84") into a QGIS CRS object
|
||||
*/
|
||||
|
@ -494,6 +494,12 @@ void QgsWfs3CollectionsHandler::handleRequest( const QgsServerApiContext &contex
|
||||
"spatial", {
|
||||
{ "bbox", QgsServerApiUtils::layerExtent( layer ) },
|
||||
{ "crs", "http://www.opengis.net/def/crs/OGC/1.3/CRS84" },
|
||||
},
|
||||
},
|
||||
{
|
||||
"temporal", {
|
||||
{ "interval", QgsServerApiUtils::temporalExtent( layer ) },
|
||||
{ "trs", "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian" },
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -524,6 +530,7 @@ void QgsWfs3CollectionsHandler::handleRequest( const QgsServerApiContext &contex
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
json navigation = json::array();
|
||||
const QUrl url { context.request()->url() };
|
||||
navigation.push_back( {{ "title", "Landing page" }, { "href", parentLink( url, 1 ).toStdString() }} ) ;
|
||||
@ -660,6 +667,12 @@ void QgsWfs3DescribeCollectionHandler::handleRequest( const QgsServerApiContext
|
||||
{ "bbox", QgsServerApiUtils::layerExtent( mapLayer ) },
|
||||
{ "crs", "http://www.opengis.net/def/crs/OGC/1.3/CRS84" },
|
||||
}
|
||||
},
|
||||
{
|
||||
"temporal", {
|
||||
{ "interval", QgsServerApiUtils::temporalExtent( mapLayer ) },
|
||||
{ "trs", "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian" },
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -94,6 +94,39 @@ class QgsServerAPIUtilsTest(QgsServerTestBase):
|
||||
path = QgsServerApiUtils.appendMapParameter('/wfs3', QtCore.QUrl('https://www.qgis.org/wfs3?MAP=/some/path'))
|
||||
self.assertEqual(path, '/wfs3?MAP=/some/path')
|
||||
|
||||
def test_temporal_extent(self):
|
||||
|
||||
project = QgsProject()
|
||||
base_path = unitTestDataPath('qgis_server') + '/test_project_api_timefilters.qgs'
|
||||
project.read(base_path)
|
||||
|
||||
layer = list(project.mapLayers().values())[0]
|
||||
|
||||
layer.serverProperties().removeWmsDimension('date')
|
||||
layer.serverProperties().removeWmsDimension('time')
|
||||
self.assertTrue(layer.serverProperties().addWmsDimension(QgsVectorLayerServerProperties.WmsDimensionInfo('time', 'updated_string')))
|
||||
self.assertEqual(QgsServerApiUtils.temporalExtent(layer), [['2010-01-01T01:01:01', '2020-01-01T01:01:01']])
|
||||
|
||||
layer.serverProperties().removeWmsDimension('date')
|
||||
layer.serverProperties().removeWmsDimension('time')
|
||||
self.assertTrue(layer.serverProperties().addWmsDimension(QgsVectorLayerServerProperties.WmsDimensionInfo('date', 'created')))
|
||||
self.assertEqual(QgsServerApiUtils.temporalExtent(layer), [['2010-01-01T00:00:00', '2019-01-01T00:00:00']])
|
||||
|
||||
layer.serverProperties().removeWmsDimension('date')
|
||||
layer.serverProperties().removeWmsDimension('time')
|
||||
self.assertTrue(layer.serverProperties().addWmsDimension(QgsVectorLayerServerProperties.WmsDimensionInfo('date', 'created_string')))
|
||||
self.assertEqual(QgsServerApiUtils.temporalExtent(layer), [['2010-01-01T00:00:00', '2019-01-01T00:00:00']])
|
||||
|
||||
layer.serverProperties().removeWmsDimension('date')
|
||||
layer.serverProperties().removeWmsDimension('time')
|
||||
self.assertTrue(layer.serverProperties().addWmsDimension(QgsVectorLayerServerProperties.WmsDimensionInfo('time', 'updated')))
|
||||
self.assertEqual(QgsServerApiUtils.temporalExtent(layer), [['2010-01-01T01:01:01Z', '2022-01-01T01:01:01Z']])
|
||||
|
||||
layer.serverProperties().removeWmsDimension('date')
|
||||
layer.serverProperties().removeWmsDimension('time')
|
||||
self.assertTrue(layer.serverProperties().addWmsDimension(QgsVectorLayerServerProperties.WmsDimensionInfo('date', 'begin', 'end')))
|
||||
self.assertEqual(QgsServerApiUtils.temporalExtent(layer), [['2010-01-01T00:00:00', '2022-01-01T00:00:00']])
|
||||
|
||||
|
||||
class API(QgsServerApi):
|
||||
|
||||
@ -359,6 +392,13 @@ class QgsServerAPITest(QgsServerAPITestBase):
|
||||
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/testlayer%20èé')
|
||||
self.compareApi(request, project, 'test_wfs3_collection_testlayer_èé.json')
|
||||
|
||||
def test_wfs3_collection_temporal_extent_json(self):
|
||||
"""Test collection with timefilter"""
|
||||
project = QgsProject()
|
||||
project.read(unitTestDataPath('qgis_server') + '/test_project_api_timefilters.qgs')
|
||||
request = QgsBufferServerRequest('http://server.qgis.org/wfs3/collections/points')
|
||||
self.compareApi(request, project, 'test_wfs3_collection_points_timefilters.json')
|
||||
|
||||
def test_wfs3_collection_html(self):
|
||||
"""Test WFS3 API collection"""
|
||||
project = QgsProject()
|
||||
|
@ -280,6 +280,40 @@ Content-Type: application/openapi+json;version=3.0
|
||||
"maxItems": 6,
|
||||
"minItems": 4,
|
||||
"type": "array"
|
||||
},
|
||||
"temporal": {
|
||||
"description": "The temporal extent of the features in the collection.",
|
||||
"properties": {
|
||||
"interval": {
|
||||
"description": "One or more time intervals that describe the temporal extent of the dataset.\nThe value `null` is supported and indicates an open time interval.\nIn the Core only a single time interval is supported. Extensions may support multiple intervals. If multiple intervals are provided, the union of the intervals describes the temporal extent.",
|
||||
"items": {
|
||||
"description": "Begin and end times of the time interval. The timestamps\nare in the coordinate reference system specified in `trs`. By default\nthis is the Gregorian calendar.",
|
||||
"items": {
|
||||
"example": [
|
||||
"2011-11-11T12:22:11Z",
|
||||
null
|
||||
],
|
||||
"format": "date-time",
|
||||
"nullable": true,
|
||||
"trs": {
|
||||
"default": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian",
|
||||
"description": "Coordinate reference system of the coordinates in the temporal extent\n(property `interval`). The default reference system is the Gregorian calendar.\nIn the Core this is the only supported temporal reference system.\nExtensions may support additional temporal reference systems and add\nadditional enum values.",
|
||||
"enum": [
|
||||
"http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 2,
|
||||
"minItems": 2,
|
||||
"type": "array"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
60
tests/testdata/qgis_server/api/test_wfs3_collection_points_timefilters.json
vendored
Normal file
60
tests/testdata/qgis_server/api/test_wfs3_collection_points_timefilters.json
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"crs": [
|
||||
"http://www.opengis.net/def/crs/OGC/1.3/CRS84",
|
||||
"http://www.opengis.net/def/crs/EPSG/9.6.2/4326",
|
||||
"http://www.opengis.net/def/crs/EPSG/9.6.2/3857"
|
||||
],
|
||||
"extent": {
|
||||
"spatial": {
|
||||
"bbox": [
|
||||
[
|
||||
7.15826,
|
||||
44.7977,
|
||||
7.30356,
|
||||
44.8216
|
||||
]
|
||||
],
|
||||
"crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
||||
},
|
||||
"temporal": {
|
||||
"interval": [
|
||||
[
|
||||
"2010-01-01T00:00:00",
|
||||
"2022-01-01T01:01:01Z"
|
||||
]
|
||||
],
|
||||
"trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
|
||||
}
|
||||
},
|
||||
"id": "points",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://server.qgis.org/wfs3/collections/points.json",
|
||||
"rel": "self",
|
||||
"title": "Feature collection as JSON",
|
||||
"type": "application/json"
|
||||
},
|
||||
{
|
||||
"href": "http://server.qgis.org/wfs3/collections/points.html",
|
||||
"rel": "alternate",
|
||||
"title": "Feature collection as HTML",
|
||||
"type": "text/html"
|
||||
},
|
||||
{
|
||||
"href": "http://server.qgis.org/wfs3/collections/points/items.json",
|
||||
"rel": "items",
|
||||
"title": "points",
|
||||
"type": "application/json"
|
||||
},
|
||||
{
|
||||
"href": "http://server.qgis.org/wfs3/collections/points/items.html",
|
||||
"rel": "items",
|
||||
"title": "points",
|
||||
"type": "text/html"
|
||||
}
|
||||
],
|
||||
"timeStamp": "2019-10-20T16:29:01Z",
|
||||
"title": "points"
|
||||
}
|
@ -17,6 +17,10 @@ Content-Type: application/json
|
||||
]
|
||||
],
|
||||
"crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
||||
},
|
||||
"temporal": {
|
||||
"interval": null,
|
||||
"trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
|
||||
}
|
||||
},
|
||||
"id": "testlayer èé",
|
||||
|
@ -20,6 +20,10 @@ Content-Type: application/json
|
||||
]
|
||||
],
|
||||
"crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
||||
},
|
||||
"temporal": {
|
||||
"interval": null,
|
||||
"trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
|
||||
}
|
||||
},
|
||||
"id": "testlayer èé",
|
||||
@ -57,6 +61,10 @@ Content-Type: application/json
|
||||
]
|
||||
],
|
||||
"crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
||||
},
|
||||
"temporal": {
|
||||
"interval": null,
|
||||
"trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
|
||||
}
|
||||
},
|
||||
"id": "layer1_with_short_name",
|
||||
@ -94,6 +102,10 @@ Content-Type: application/json
|
||||
]
|
||||
],
|
||||
"crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
||||
},
|
||||
"temporal": {
|
||||
"interval": null,
|
||||
"trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
|
||||
}
|
||||
},
|
||||
"id": "exclude_attribute",
|
||||
@ -131,6 +143,10 @@ Content-Type: application/json
|
||||
]
|
||||
],
|
||||
"crs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
|
||||
},
|
||||
"temporal": {
|
||||
"interval": null,
|
||||
"trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
|
||||
}
|
||||
},
|
||||
"id": "fields_alias",
|
||||
|
Binary file not shown.
@ -68,10 +68,10 @@
|
||||
<projectlayers>
|
||||
<maplayer minScale="1e+08" simplifyLocal="1" autoRefreshEnabled="0" maxScale="0" autoRefreshTime="0" wkbType="Point" labelsEnabled="0" simplifyDrawingTol="1.2" geometry="Point" readOnly="0" styleCategories="AllStyleCategories" simplifyAlgorithm="0" type="vector" refreshOnNotifyEnabled="0" simplifyDrawingHints="0" hasScaleBasedVisibilityFlag="0" refreshOnNotifyMessage="" simplifyMaxScale="1">
|
||||
<extent>
|
||||
<xmin>7.15825748443603516</xmin>
|
||||
<ymin>44.79768753051757813</ymin>
|
||||
<xmax>7.30355501174926758</xmax>
|
||||
<ymax>44.82162857055664063</ymax>
|
||||
<xmin>7.15826000000000029</xmin>
|
||||
<ymin>44.79769999999999897</ymin>
|
||||
<xmax>7.30356000000000005</xmax>
|
||||
<ymax>44.82159999999999656</ymax>
|
||||
</extent>
|
||||
<id>points_47ad3bc8_35bd_4392_8994_2dc5ff04be60</id>
|
||||
<datasource>./test_project_api_timefilters.gpkg|layername=points</datasource>
|
||||
@ -119,7 +119,7 @@
|
||||
<description></description>
|
||||
<projectionacronym></projectionacronym>
|
||||
<ellipsoidacronym></ellipsoidacronym>
|
||||
<geographicflag>true</geographicflag>
|
||||
<geographicflag>false</geographicflag>
|
||||
</spatialrefsys>
|
||||
</crs>
|
||||
<extent>
|
||||
@ -187,7 +187,9 @@
|
||||
<sizescale/>
|
||||
</renderer-v2>
|
||||
<customproperties>
|
||||
<property key="dualview/previewExpressions" value="name"/>
|
||||
<property key="dualview/previewExpressions">
|
||||
<value>name</value>
|
||||
</property>
|
||||
<property key="embeddedWidgets/count" value="0"/>
|
||||
<property key="variableNames"/>
|
||||
<property key="variableValues"/>
|
||||
@ -328,7 +330,7 @@
|
||||
<column type="field" name="updated" hidden="0" width="497"/>
|
||||
<column type="field" name="begin" hidden="0" width="-1"/>
|
||||
<column type="field" name="end" hidden="0" width="-1"/>
|
||||
<column type="field" name="updated_string" hidden="0" width="-1"/>
|
||||
<column type="field" name="updated_string" hidden="0" width="371"/>
|
||||
<column type="field" name="created_string" hidden="0" width="-1"/>
|
||||
</columns>
|
||||
</attributetableconfig>
|
||||
|
Loading…
x
Reference in New Issue
Block a user