diff --git a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java index 1553498199..9066281919 100644 --- a/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java +++ b/OsmAnd/src/net/osmand/aidl/OsmandAidlApi.java @@ -6,6 +6,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.view.View; +import net.osmand.aidl.maplayer.AMapLayer; +import net.osmand.aidl.maplayer.point.AMapPoint; import net.osmand.aidl.mapmarker.AMapMarker; import net.osmand.aidl.mapwidget.AMapWidget; import net.osmand.data.LatLon; @@ -14,7 +16,9 @@ import net.osmand.plus.MapMarkersHelper; import net.osmand.plus.MapMarkersHelper.MapMarker; import net.osmand.plus.OsmandApplication; import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.views.AidlMapLayer; import net.osmand.plus.views.MapInfoLayer; +import net.osmand.plus.views.OsmandMapLayer; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.mapwidgets.MapWidgetRegistry.MapWidgetRegInfo; import net.osmand.plus.views.mapwidgets.TextInfoWidget; @@ -27,17 +31,25 @@ import java.util.concurrent.ConcurrentHashMap; public class OsmandAidlApi { private static final String AIDL_REFRESH_MAP = "aidl_refresh_map"; + private static final String AIDL_OBJECT_ID = "aidl_object_id"; + private static final String AIDL_ADD_MAP_WIDGET = "aidl_add_map_widget"; private static final String AIDL_REMOVE_MAP_WIDGET = "aidl_remove_map_widget"; - private static final String AIDL_MAP_WIDGET_ID = "aidl_map_widget_id"; + + private static final String AIDL_ADD_MAP_LAYER = "aidl_add_map_layer"; + private static final String AIDL_REMOVE_MAP_LAYER = "aidl_remove_map_layer"; private OsmandApplication app; private Map widgets = new ConcurrentHashMap<>(); private Map widgetControls = new ConcurrentHashMap<>(); + private Map layers = new ConcurrentHashMap<>(); + private Map mapLayers = new ConcurrentHashMap<>(); private BroadcastReceiver refreshMapReceiver; private BroadcastReceiver addMapWidgetReceiver; private BroadcastReceiver removeMapWidgetReceiver; + private BroadcastReceiver addMapLayerReceiver; + private BroadcastReceiver removeMapLayerReceiver; public OsmandAidlApi(OsmandApplication app) { this.app = app; @@ -47,12 +59,15 @@ public class OsmandAidlApi { registerRefreshMapReceiver(mapActivity); registerAddMapWidgetReceiver(mapActivity); registerRemoveMapWidgetReceiver(mapActivity); + registerAddMapLayerReceiver(mapActivity); + registerRemoveMapLayerReceiver(mapActivity); } public void onDestroyMapActivity(final MapActivity mapActivity) { if (refreshMapReceiver != null) { mapActivity.unregisterReceiver(refreshMapReceiver); } + if (addMapWidgetReceiver != null) { mapActivity.unregisterReceiver(addMapWidgetReceiver); } @@ -60,6 +75,13 @@ public class OsmandAidlApi { mapActivity.unregisterReceiver(removeMapWidgetReceiver); } widgetControls.clear(); + + if (addMapLayerReceiver != null) { + mapActivity.unregisterReceiver(addMapLayerReceiver); + } + if (removeMapLayerReceiver != null) { + mapActivity.unregisterReceiver(removeMapLayerReceiver); + } } private void registerRefreshMapReceiver(final MapActivity mapActivity) { @@ -84,7 +106,7 @@ public class OsmandAidlApi { addMapWidgetReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String widgetId = intent.getStringExtra(AIDL_MAP_WIDGET_ID); + String widgetId = intent.getStringExtra(AIDL_OBJECT_ID); if (widgetId != null) { AMapWidget widget = widgets.get(widgetId); if (widget != null) { @@ -112,7 +134,7 @@ public class OsmandAidlApi { removeMapWidgetReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String widgetId = intent.getStringExtra(AIDL_MAP_WIDGET_ID); + String widgetId = intent.getStringExtra(AIDL_OBJECT_ID); if (widgetId != null) { MapInfoLayer layer = mapActivity.getMapLayers().getMapInfoLayer(); TextInfoWidget widgetControl = widgetControls.get(widgetId); @@ -144,13 +166,64 @@ public class OsmandAidlApi { } } + private void registerAddMapLayerReceiver(final MapActivity mapActivity) { + addMapLayerReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String layerId = intent.getStringExtra(AIDL_OBJECT_ID); + if (layerId != null) { + AMapLayer layer = layers.get(layerId); + if (layer != null) { + OsmandMapLayer mapLayer = mapLayers.get(layerId); + if (mapLayer != null) { + mapActivity.getMapView().removeLayer(mapLayer); + } + mapLayer = new AidlMapLayer(mapActivity, layer); + mapActivity.getMapView().addLayer(mapLayer, layer.getZOrder()); + mapLayers.put(layerId, mapLayer); + } + } + } + }; + mapActivity.registerReceiver(addMapLayerReceiver, new IntentFilter(AIDL_ADD_MAP_LAYER)); + } + + private void registerRemoveMapLayerReceiver(final MapActivity mapActivity) { + removeMapLayerReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String layerId = intent.getStringExtra(AIDL_OBJECT_ID); + if (layerId != null) { + OsmandMapLayer mapLayer = mapLayers.remove(layerId); + if (mapLayer != null) { + mapActivity.getMapView().removeLayer(mapLayer); + mapActivity.refreshMap(); + } + } + } + }; + mapActivity.registerReceiver(removeMapLayerReceiver, new IntentFilter(AIDL_REMOVE_MAP_LAYER)); + } + + public void registerMapLayers(MapActivity mapActivity) { + for (AMapLayer layer : layers.values()) { + OsmandMapLayer mapLayer = mapLayers.get(layer.getId()); + if (mapLayer != null) { + mapActivity.getMapView().removeLayer(mapLayer); + } + mapLayer = new AidlMapLayer(mapActivity, layer); + mapActivity.getMapView().addLayer(mapLayer, layer.getZOrder()); + mapLayers.put(layer.getId(), mapLayer); + } + } + private void refreshMap() { Intent intent = new Intent(); intent.setAction(AIDL_REFRESH_MAP); app.sendBroadcast(intent); } - public TextInfoWidget createWidgetControl(final MapActivity mapActivity, final String widgetId) { + private TextInfoWidget createWidgetControl(final MapActivity mapActivity, final String widgetId) { final TextInfoWidget control = new TextInfoWidget(mapActivity) { @Override @@ -248,7 +321,7 @@ public class OsmandAidlApi { widgets.put(widget.getId(), widget); Intent intent = new Intent(); intent.setAction(AIDL_ADD_MAP_WIDGET); - intent.putExtra(AIDL_MAP_WIDGET_ID, widget.getId()); + intent.putExtra(AIDL_OBJECT_ID, widget.getId()); app.sendBroadcast(intent); } refreshMap(); @@ -263,7 +336,7 @@ public class OsmandAidlApi { widgets.remove(widgetId); Intent intent = new Intent(); intent.setAction(AIDL_REMOVE_MAP_WIDGET); - intent.putExtra(AIDL_MAP_WIDGET_ID, widgetId); + intent.putExtra(AIDL_OBJECT_ID, widgetId); app.sendBroadcast(intent); return true; } else { @@ -280,4 +353,69 @@ public class OsmandAidlApi { return false; } } + + boolean addMapLayer(AMapLayer layer) { + if (layer != null) { + if (layers.containsKey(layer.getId())) { + updateMapLayer(layer); + } else { + layers.put(layer.getId(), layer); + Intent intent = new Intent(); + intent.setAction(AIDL_ADD_MAP_LAYER); + intent.putExtra(AIDL_OBJECT_ID, layer.getId()); + app.sendBroadcast(intent); + } + refreshMap(); + return true; + } else { + return false; + } + } + + boolean removeMapLayer(String layerId) { + if (!Algorithms.isEmpty(layerId) && layers.containsKey(layerId)) { + layers.remove(layerId); + Intent intent = new Intent(); + intent.setAction(AIDL_REMOVE_MAP_LAYER); + intent.putExtra(AIDL_OBJECT_ID, layerId); + app.sendBroadcast(intent); + return true; + } else { + return false; + } + } + + boolean updateMapLayer(AMapLayer layer) { + if (layer != null && layers.containsKey(layer.getId())) { + layers.put(layer.getId(), layer); + refreshMap(); + return true; + } else { + return false; + } + } + + boolean putMapPoint(String layerId, AMapPoint point) { + if (point != null) { + AMapLayer layer = layers.get(layerId); + if (layer != null) { + layer.putPoint(point); + refreshMap(); + return true; + } + } + return false; + } + + boolean removeMapPoint(String layerId, String pointId) { + if (pointId != null) { + AMapLayer layer = layers.get(layerId); + if (layer != null) { + layer.removePoint(pointId); + refreshMap(); + return true; + } + } + return false; + } } diff --git a/OsmAnd/src/net/osmand/aidl/OsmandAidlService.java b/OsmAnd/src/net/osmand/aidl/OsmandAidlService.java index a1df7c460f..9dceef778e 100644 --- a/OsmAnd/src/net/osmand/aidl/OsmandAidlService.java +++ b/OsmAnd/src/net/osmand/aidl/OsmandAidlService.java @@ -94,32 +94,56 @@ public class OsmandAidlService extends Service { @Override public boolean addMapPoint(AddMapPointParams params) throws RemoteException { - return false; + try { + return params != null && getApi().putMapPoint(params.getLayerId(), params.getPoint()); + } catch (Exception e) { + return false; + } } @Override public boolean removeMapPoint(RemoveMapPointParams params) throws RemoteException { - return false; + try { + return params != null && getApi().removeMapPoint(params.getLayerId(), params.getPointId()); + } catch (Exception e) { + return false; + } } @Override public boolean updateMapPoint(UpdateMapPointParams params) throws RemoteException { - return false; + try { + return params != null && getApi().putMapPoint(params.getLayerId(), params.getPoint()); + } catch (Exception e) { + return false; + } } @Override public boolean addMapLayer(AddMapLayerParams params) throws RemoteException { - return false; + try { + return params != null && getApi().addMapLayer(params.getLayer()); + } catch (Exception e) { + return false; + } } @Override public boolean removeMapLayer(RemoveMapLayerParams params) throws RemoteException { - return false; + try { + return params != null && getApi().removeMapLayer(params.getId()); + } catch (Exception e) { + return false; + } } @Override public boolean updateMapLayer(UpdateMapLayerParams params) throws RemoteException { - return false; + try { + return params != null && getApi().updateMapLayer(params.getLayer()); + } catch (Exception e) { + return false; + } } @Override diff --git a/OsmAnd/src/net/osmand/aidl/maplayer/AMapLayer.java b/OsmAnd/src/net/osmand/aidl/maplayer/AMapLayer.java index 1c9d5b6833..79f82d97bc 100644 --- a/OsmAnd/src/net/osmand/aidl/maplayer/AMapLayer.java +++ b/OsmAnd/src/net/osmand/aidl/maplayer/AMapLayer.java @@ -5,17 +5,26 @@ import android.os.Parcelable; import net.osmand.aidl.maplayer.point.AMapPoint; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class AMapLayer implements Parcelable { private String id; private String name; - private HashMap points = new HashMap<>(); + private float zOrder = 5.5f; + private Map points = new ConcurrentHashMap<>(); - public AMapLayer(String id, String name, HashMap points) { + public AMapLayer(String id, String name, float zOrder, List pointList) { this.id = id; this.name = name; - this.points = points; + this.zOrder = zOrder; + if (pointList != null) { + for (AMapPoint p : pointList) { + this.points.put(p.getId(), p); + } + } } public AMapLayer(Parcel in) { @@ -41,20 +50,42 @@ public class AMapLayer implements Parcelable { return name; } - public HashMap getPoints() { - return points; + public float getZOrder() { + return zOrder; + } + + public List getPoints() { + return new ArrayList<>(points.values()); + } + + public boolean hasPoint(String pointId) { + return points.containsKey(pointId); + } + + public void putPoint(AMapPoint point) { + points.put(point.getId(), point); + } + + public void removePoint(String pointId) { + points.remove(pointId); } public void writeToParcel(Parcel out, int flags) { out.writeString(id); out.writeString(name); - out.writeMap(points); + out.writeFloat(zOrder); + out.writeTypedList(new ArrayList<>(points.values())); } private void readFromParcel(Parcel in) { id = in.readString(); name = in.readString(); - in.readMap(points, HashMap.class.getClassLoader()); + zOrder = in.readFloat(); + List pointList = new ArrayList<>(); + in.readTypedList(pointList, AMapPoint.CREATOR); + for (AMapPoint p : pointList) { + this.points.put(p.getId(), p); + } } public int describeContents() { diff --git a/OsmAnd/src/net/osmand/aidl/maplayer/AddMapLayerParams.java b/OsmAnd/src/net/osmand/aidl/maplayer/AddMapLayerParams.java index 9c6611d577..38e9e53667 100644 --- a/OsmAnd/src/net/osmand/aidl/maplayer/AddMapLayerParams.java +++ b/OsmAnd/src/net/osmand/aidl/maplayer/AddMapLayerParams.java @@ -2,6 +2,7 @@ package net.osmand.aidl.maplayer; import android.os.Parcel; import android.os.Parcelable; +import android.os.Parcelable.Creator; public class AddMapLayerParams implements Parcelable { private AMapLayer layer; @@ -14,8 +15,8 @@ public class AddMapLayerParams implements Parcelable { readFromParcel(in); } - public static final Parcelable.Creator CREATOR = new - Parcelable.Creator() { + public static final Creator CREATOR = new + Creator() { public AddMapLayerParams createFromParcel(Parcel in) { return new AddMapLayerParams(in); } diff --git a/OsmAnd/src/net/osmand/aidl/maplayer/RemoveMapLayerParams.java b/OsmAnd/src/net/osmand/aidl/maplayer/RemoveMapLayerParams.java index 0eb01b0827..3e87627445 100644 --- a/OsmAnd/src/net/osmand/aidl/maplayer/RemoveMapLayerParams.java +++ b/OsmAnd/src/net/osmand/aidl/maplayer/RemoveMapLayerParams.java @@ -2,6 +2,7 @@ package net.osmand.aidl.maplayer; import android.os.Parcel; import android.os.Parcelable; +import android.os.Parcelable.Creator; public class RemoveMapLayerParams implements Parcelable { private String id; @@ -14,8 +15,8 @@ public class RemoveMapLayerParams implements Parcelable { readFromParcel(in); } - public static final Parcelable.Creator CREATOR = new - Parcelable.Creator() { + public static final Creator CREATOR = new + Creator() { public RemoveMapLayerParams createFromParcel(Parcel in) { return new RemoveMapLayerParams(in); } diff --git a/OsmAnd/src/net/osmand/aidl/maplayer/UpdateMapLayerParams.java b/OsmAnd/src/net/osmand/aidl/maplayer/UpdateMapLayerParams.java index 9414eb35c0..a2f6e16498 100644 --- a/OsmAnd/src/net/osmand/aidl/maplayer/UpdateMapLayerParams.java +++ b/OsmAnd/src/net/osmand/aidl/maplayer/UpdateMapLayerParams.java @@ -2,6 +2,7 @@ package net.osmand.aidl.maplayer; import android.os.Parcel; import android.os.Parcelable; +import android.os.Parcelable.Creator; public class UpdateMapLayerParams implements Parcelable { private AMapLayer layer; @@ -14,8 +15,8 @@ public class UpdateMapLayerParams implements Parcelable { readFromParcel(in); } - public static final Parcelable.Creator CREATOR = new - Parcelable.Creator() { + public static final Creator CREATOR = new + Creator() { public UpdateMapLayerParams createFromParcel(Parcel in) { return new UpdateMapLayerParams(in); } diff --git a/OsmAnd/src/net/osmand/aidl/maplayer/point/AMapPoint.java b/OsmAnd/src/net/osmand/aidl/maplayer/point/AMapPoint.java index 1e96029978..1c731b653a 100644 --- a/OsmAnd/src/net/osmand/aidl/maplayer/point/AMapPoint.java +++ b/OsmAnd/src/net/osmand/aidl/maplayer/point/AMapPoint.java @@ -24,8 +24,8 @@ public class AMapPoint implements Parcelable { readFromParcel(in); } - public static final Parcelable.Creator CREATOR = new - Parcelable.Creator() { + public static final Creator CREATOR = new + Creator() { public AMapPoint createFromParcel(Parcel in) { return new AMapPoint(in); } @@ -66,7 +66,7 @@ public class AMapPoint implements Parcelable { private void readFromParcel(Parcel in) { id = in.readString(); shortName = in.readString(); - shortName = in.readString(); + fullName = in.readString(); color = in.readInt(); location = in.readParcelable(ALatLon.class.getClassLoader()); } diff --git a/OsmAnd/src/net/osmand/aidl/maplayer/point/AddMapPointParams.java b/OsmAnd/src/net/osmand/aidl/maplayer/point/AddMapPointParams.java index 79cb96914c..0d575a38d9 100644 --- a/OsmAnd/src/net/osmand/aidl/maplayer/point/AddMapPointParams.java +++ b/OsmAnd/src/net/osmand/aidl/maplayer/point/AddMapPointParams.java @@ -16,8 +16,8 @@ public class AddMapPointParams implements Parcelable { readFromParcel(in); } - public static final Parcelable.Creator CREATOR = new - Parcelable.Creator() { + public static final Creator CREATOR = new + Creator() { public AddMapPointParams createFromParcel(Parcel in) { return new AddMapPointParams(in); } diff --git a/OsmAnd/src/net/osmand/aidl/maplayer/point/RemoveMapPointParams.java b/OsmAnd/src/net/osmand/aidl/maplayer/point/RemoveMapPointParams.java index 3c7f2105f0..797556b1e3 100644 --- a/OsmAnd/src/net/osmand/aidl/maplayer/point/RemoveMapPointParams.java +++ b/OsmAnd/src/net/osmand/aidl/maplayer/point/RemoveMapPointParams.java @@ -16,8 +16,8 @@ public class RemoveMapPointParams implements Parcelable { readFromParcel(in); } - public static final Parcelable.Creator CREATOR = new - Parcelable.Creator() { + public static final Creator CREATOR = new + Creator() { public RemoveMapPointParams createFromParcel(Parcel in) { return new RemoveMapPointParams(in); } diff --git a/OsmAnd/src/net/osmand/aidl/maplayer/point/UpdateMapPointParams.java b/OsmAnd/src/net/osmand/aidl/maplayer/point/UpdateMapPointParams.java index 013d650622..178af20b1d 100644 --- a/OsmAnd/src/net/osmand/aidl/maplayer/point/UpdateMapPointParams.java +++ b/OsmAnd/src/net/osmand/aidl/maplayer/point/UpdateMapPointParams.java @@ -16,8 +16,8 @@ public class UpdateMapPointParams implements Parcelable { readFromParcel(in); } - public static final Parcelable.Creator CREATOR = new - Parcelable.Creator() { + public static final Creator CREATOR = new + Creator() { public UpdateMapPointParams createFromParcel(Parcel in) { return new UpdateMapPointParams(in); } diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java b/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java index e9bf55dcde..287c3a7ef1 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java @@ -187,6 +187,7 @@ public class MapActivityLayers { OsmandPlugin.createLayers(mapView, activity); app.getAppCustomization().createLayers(mapView, activity); + app.getAidlApi().registerMapLayers(activity); } diff --git a/OsmAnd/src/net/osmand/plus/views/AidlMapLayer.java b/OsmAnd/src/net/osmand/plus/views/AidlMapLayer.java new file mode 100644 index 0000000000..3135098c02 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/views/AidlMapLayer.java @@ -0,0 +1,111 @@ +package net.osmand.plus.views; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.DisplayMetrics; +import android.view.WindowManager; + +import net.osmand.aidl.ALatLon; +import net.osmand.aidl.maplayer.AMapLayer; +import net.osmand.aidl.maplayer.point.AMapPoint; +import net.osmand.data.RotatedTileBox; +import net.osmand.plus.R; +import net.osmand.plus.activities.MapActivity; + +import java.util.List; + +public class AidlMapLayer extends OsmandMapLayer { + private static int POINT_OUTER_COLOR = 0x88555555; + private static int PAINT_TEXT_ICON_COLOR = Color.BLACK; + + private final MapActivity map; + private AMapLayer aidlLayer; + private OsmandMapTileView view; + private Paint pointInnerCircle; + private Paint pointOuter; + private final static float startZoom = 7; + private Paint paintTextIcon; + + public AidlMapLayer(MapActivity map, AMapLayer aidlLayer) { + this.map = map; + this.aidlLayer = aidlLayer; + } + + @Override + public void initLayer(OsmandMapTileView view) { + this.view = view; + DisplayMetrics dm = new DisplayMetrics(); + WindowManager wmgr = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE); + wmgr.getDefaultDisplay().getMetrics(dm); + + pointInnerCircle = new Paint(); + pointInnerCircle.setColor(view.getApplication().getResources().getColor(R.color.poi_background)); + pointInnerCircle.setStyle(Paint.Style.FILL); + pointInnerCircle.setAntiAlias(true); + + paintTextIcon = new Paint(); + paintTextIcon.setTextSize(10 * view.getDensity()); + paintTextIcon.setTextAlign(Paint.Align.CENTER); + paintTextIcon.setFakeBoldText(true); + paintTextIcon.setColor(PAINT_TEXT_ICON_COLOR); + paintTextIcon.setAntiAlias(true); + + pointOuter = new Paint(); + pointOuter.setColor(POINT_OUTER_COLOR); + pointOuter.setAntiAlias(true); + pointOuter.setStyle(Paint.Style.FILL_AND_STROKE); + } + + private int getRadiusPoi(RotatedTileBox tb) { + int r; + final double zoom = tb.getZoom(); + if (zoom < startZoom) { + r = 0; + } else if (zoom <= 11) { + r = 10; + } else if (zoom <= 14) { + r = 12; + } else { + r = 14; + } + return (int) (r * tb.getDensity()); + } + + @Override + public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings nightMode) { + final int r = getRadiusPoi(tileBox); + canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); + List points = aidlLayer.getPoints(); + for (AMapPoint point : points) { + ALatLon l = point.getLocation(); + if (l != null) { + int x = (int) tileBox.getPixXFromLatLon(l.getLatitude(), l.getLongitude()); + int y = (int) tileBox.getPixYFromLatLon(l.getLatitude(), l.getLongitude()); + pointInnerCircle.setColor(point.getColor()); + pointOuter.setColor(POINT_OUTER_COLOR); + paintTextIcon.setColor(PAINT_TEXT_ICON_COLOR); + canvas.drawCircle(x, y, r + (float)Math.ceil(tileBox.getDensity()), pointOuter); + canvas.drawCircle(x, y, r - (float)Math.ceil(tileBox.getDensity()), pointInnerCircle); + paintTextIcon.setTextSize(r * 3 / 2); + canvas.drawText(point.getShortName(), x, y + r / 2, paintTextIcon); + } + } + } + + @Override + public void destroyLayer() { + } + + @Override + public boolean drawInScreenPixels() { + return false; + } + + public void refresh() { + if (view != null) { + view.refreshMap(); + } + } +}