2020-10-26 11:01:07 +01:00
|
|
|
package org.gcube.portlets.user.geoportaldataviewer.client.gis;
|
|
|
|
|
|
|
|
import org.gcube.portlets.user.geoportaldataviewer.client.GeoportalDataViewerConstants;
|
2020-10-29 15:18:14 +01:00
|
|
|
import org.gcube.portlets.user.geoportaldataviewer.client.events.QueryDataEvent;
|
|
|
|
import org.gcube.portlets.user.geoportaldataviewer.shared.gis.GeoQuery;
|
|
|
|
import org.gcube.portlets.user.geoportaldataviewer.shared.gis.GeoQuery.TYPE;
|
2020-10-23 18:18:06 +02:00
|
|
|
|
2020-10-28 11:19:12 +01:00
|
|
|
import com.google.gwt.core.client.GWT;
|
2020-10-29 15:18:14 +01:00
|
|
|
import com.google.gwt.event.shared.HandlerManager;
|
2020-10-27 10:19:32 +01:00
|
|
|
import com.google.gwt.user.client.DOM;
|
|
|
|
import com.google.gwt.user.client.Element;
|
|
|
|
import com.google.gwt.user.client.Event;
|
|
|
|
|
2020-10-26 17:40:38 +01:00
|
|
|
import ol.Collection;
|
2020-10-23 18:18:06 +02:00
|
|
|
import ol.Coordinate;
|
|
|
|
import ol.Map;
|
2020-10-26 17:40:38 +01:00
|
|
|
import ol.MapBrowserEvent;
|
2020-10-23 18:18:06 +02:00
|
|
|
import ol.MapOptions;
|
|
|
|
import ol.OLFactory;
|
2020-10-26 17:40:38 +01:00
|
|
|
import ol.Overlay;
|
|
|
|
import ol.OverlayOptions;
|
2020-10-23 18:18:06 +02:00
|
|
|
import ol.View;
|
|
|
|
import ol.ViewOptions;
|
|
|
|
import ol.control.Attribution;
|
2020-10-26 17:40:38 +01:00
|
|
|
import ol.event.EventListener;
|
|
|
|
import ol.interaction.Draw;
|
|
|
|
import ol.interaction.DrawOptions;
|
|
|
|
import ol.interaction.Extent;
|
|
|
|
import ol.interaction.ExtentOptions;
|
|
|
|
import ol.interaction.Interaction;
|
2020-10-23 18:18:06 +02:00
|
|
|
import ol.interaction.KeyboardPan;
|
|
|
|
import ol.interaction.KeyboardZoom;
|
2020-10-29 15:18:14 +01:00
|
|
|
import ol.layer.Base;
|
2020-10-23 18:18:06 +02:00
|
|
|
import ol.layer.Image;
|
|
|
|
import ol.layer.LayerOptions;
|
|
|
|
import ol.layer.Tile;
|
|
|
|
import ol.proj.Projection;
|
|
|
|
import ol.proj.ProjectionOptions;
|
|
|
|
import ol.source.ImageWms;
|
|
|
|
import ol.source.ImageWmsOptions;
|
|
|
|
import ol.source.ImageWmsParams;
|
|
|
|
import ol.source.Osm;
|
2020-10-26 17:40:38 +01:00
|
|
|
import ol.source.Vector;
|
2020-10-23 18:18:06 +02:00
|
|
|
import ol.source.XyzOptions;
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
|
|
|
|
// TODO: Auto-generated Javadoc
|
2020-10-23 18:18:06 +02:00
|
|
|
/**
|
2020-10-27 16:04:34 +01:00
|
|
|
* The Class OpenLayerOSM.
|
2020-10-23 18:18:06 +02:00
|
|
|
*
|
2020-10-27 16:04:34 +01:00
|
|
|
* @author Francesco Mangiacrapa at ISTI-CNR (francesco.mangiacrapa@isti.cnr.it)
|
|
|
|
*
|
|
|
|
* Oct 27, 2020
|
2020-10-23 18:18:06 +02:00
|
|
|
*/
|
2020-10-27 16:04:34 +01:00
|
|
|
public class OpenLayerOSM {
|
2020-10-23 18:18:06 +02:00
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/** The map. */
|
2020-10-23 18:18:06 +02:00
|
|
|
private Map map;
|
2020-10-27 16:04:34 +01:00
|
|
|
|
|
|
|
/** The view. */
|
2020-10-23 18:18:06 +02:00
|
|
|
private View view;
|
2020-10-27 16:04:34 +01:00
|
|
|
|
|
|
|
/** The view options. */
|
2020-10-23 18:18:06 +02:00
|
|
|
private ViewOptions viewOptions = OLFactory.createOptions();
|
2020-10-27 16:04:34 +01:00
|
|
|
|
|
|
|
/** The projection options. */
|
2020-10-23 18:18:06 +02:00
|
|
|
private ProjectionOptions projectionOptions = OLFactory.createOptions();
|
2020-10-26 17:40:38 +01:00
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/** The point draw. */
|
2020-10-28 11:19:12 +01:00
|
|
|
private Draw queryPoint;
|
|
|
|
|
|
|
|
private Extent queryBox;
|
2020-10-27 16:04:34 +01:00
|
|
|
|
|
|
|
/** The popup overlay. */
|
2020-10-27 10:19:32 +01:00
|
|
|
private Overlay popupOverlay;
|
2020-10-23 18:18:06 +02:00
|
|
|
|
2020-10-29 15:18:14 +01:00
|
|
|
private HandlerManager eventBus;
|
|
|
|
|
|
|
|
private boolean isQueryBoxActive;
|
|
|
|
|
|
|
|
private boolean isQueryPointActive;
|
|
|
|
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/**
|
|
|
|
* Instantiates a new open layer OSM.
|
|
|
|
*
|
|
|
|
* @param divTargetId the div target id
|
2020-10-29 15:18:14 +01:00
|
|
|
* @param eventBus the event bus
|
2020-10-27 16:04:34 +01:00
|
|
|
*/
|
2020-10-23 18:18:06 +02:00
|
|
|
/* (non-Javadoc)
|
|
|
|
* @see de.desjardins.ol3.demo.client.example.Example#show()
|
|
|
|
*/
|
2020-10-29 15:18:14 +01:00
|
|
|
public OpenLayerOSM(String divTargetId, HandlerManager eventBus) {
|
|
|
|
this.eventBus = eventBus;
|
2020-10-23 18:18:06 +02:00
|
|
|
|
|
|
|
// create a OSM-layer
|
|
|
|
XyzOptions osmSourceOptions = OLFactory.createOptions();
|
|
|
|
|
|
|
|
Osm osmSource = new Osm(osmSourceOptions);
|
|
|
|
LayerOptions osmLayerOptions = OLFactory.createOptions();
|
|
|
|
osmLayerOptions.setSource(osmSource);
|
|
|
|
|
|
|
|
Tile osmLayer = new Tile(osmLayerOptions);
|
|
|
|
// create a projection
|
|
|
|
projectionOptions.setCode(GeoportalDataViewerConstants.EPSG_3857);
|
|
|
|
projectionOptions.setUnits("m");
|
|
|
|
|
|
|
|
Projection projection = new Projection(projectionOptions);
|
|
|
|
viewOptions.setProjection(projection);
|
2020-10-26 11:01:07 +01:00
|
|
|
viewOptions.setMaxZoom(19);
|
|
|
|
//viewOptions.setExtent(new Extent(-180, -90, 180, 90));
|
2020-10-23 18:18:06 +02:00
|
|
|
|
|
|
|
// create a view
|
|
|
|
view = new View(viewOptions);
|
|
|
|
|
2020-10-26 11:01:07 +01:00
|
|
|
//EPSG_4326_TO_ITALY
|
|
|
|
Coordinate centerCoordinate = OLFactory.createCoordinate(12.45, 42.98);
|
2020-10-23 18:18:06 +02:00
|
|
|
Coordinate transformedCenterCoordinate = Projection.transform(centerCoordinate, GeoportalDataViewerConstants.EPSG_4326, GeoportalDataViewerConstants.EPSG_3857);
|
|
|
|
|
2020-10-26 11:01:07 +01:00
|
|
|
view.setCenter(transformedCenterCoordinate);
|
|
|
|
view.setZoom(5);
|
|
|
|
|
2020-10-23 18:18:06 +02:00
|
|
|
// create the map
|
|
|
|
MapOptions mapOptions = OLFactory.createOptions();
|
|
|
|
mapOptions.setTarget(divTargetId);
|
|
|
|
mapOptions.setView(view);
|
|
|
|
|
|
|
|
map = new Map(mapOptions);
|
|
|
|
|
|
|
|
map.addLayer(osmLayer);
|
|
|
|
//map.addLayer(tileDebugLayer);
|
|
|
|
|
|
|
|
// add some controls
|
|
|
|
map.addControl(OLFactory.createScaleLine());
|
|
|
|
DemoUtils.addDefaultControls(map.getControls());
|
|
|
|
|
|
|
|
Attribution attribution = new Attribution();
|
|
|
|
attribution.setCollapsed(true);
|
2020-10-26 17:40:38 +01:00
|
|
|
|
|
|
|
map.addClickListener(new EventListener<MapBrowserEvent>() {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onEvent(MapBrowserEvent event) {
|
|
|
|
// TODO Auto-generated method stub
|
2020-10-27 10:19:32 +01:00
|
|
|
Coordinate coordinate = event.getCoordinate();
|
2020-10-29 15:18:14 +01:00
|
|
|
if(isQueryPointActive) {
|
2020-10-28 11:19:12 +01:00
|
|
|
|
|
|
|
double lon = coordinate.getX();
|
|
|
|
double lat = coordinate.getY();
|
|
|
|
|
|
|
|
int w = (int) map.getSize().getWidth();
|
|
|
|
int h = (int) map.getSize().getHeight();
|
|
|
|
// handler.clickOnMap(x, y, w, h);
|
|
|
|
|
|
|
|
// ratio - mapPixelWeight : bboxWeight = 10px : geoRectangleWidth
|
|
|
|
// where 10px is the pixel diameter dimension of the clicked point
|
|
|
|
double bboxWidth = Math.abs(getExtent().getLowerLeftX() - getExtent().getUpperRightX());
|
|
|
|
double geoWidth = bboxWidth / w * 10 / 2;
|
|
|
|
double x1 = Math.min(lon+geoWidth, lon-geoWidth);
|
|
|
|
double x2 = Math.max(lon+geoWidth, lon-geoWidth);
|
|
|
|
double y1 = Math.min(lat+geoWidth, lat-geoWidth);
|
|
|
|
double y2 = Math.max(lat+geoWidth, lat-geoWidth);
|
2020-10-29 15:18:14 +01:00
|
|
|
//GWT.log("("+x1+","+y1+")("+x2+","+y2+")");
|
|
|
|
|
|
|
|
// Point pt = new Point(coordinate);
|
|
|
|
// ol.Extent extent = pt.getExtent();
|
|
|
|
// //new ClickDataInfo(x1, y1, x2, y2)
|
|
|
|
// SelectDataInfo selectDataInfo
|
|
|
|
//selectBox(new GeoQuery(x1, y1, x2, y2, GeoQuery.TYPE.POINT));
|
|
|
|
GeoQuery select = new GeoQuery(x1, y1, x2, y2, TYPE.POINT);
|
|
|
|
eventBus.fireEvent(new QueryDataEvent(select,coordinate));
|
2020-10-28 11:19:12 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-10-29 15:18:14 +01:00
|
|
|
|
2020-10-26 17:40:38 +01:00
|
|
|
}
|
|
|
|
});
|
2020-10-23 18:18:06 +02:00
|
|
|
|
|
|
|
map.addControl(attribution);
|
|
|
|
|
|
|
|
// add some interactions
|
|
|
|
map.addInteraction(new KeyboardPan());
|
|
|
|
map.addInteraction(new KeyboardZoom());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-10-29 15:18:14 +01:00
|
|
|
public void showPopup(String html, Coordinate coordinate) {
|
|
|
|
if(popupOverlay==null) {
|
|
|
|
Element elPopup = DOM.getElementById("popup");
|
|
|
|
popupOverlay = addOverlay(elPopup);
|
|
|
|
addPopupCloserHandelr(popupOverlay);
|
|
|
|
}
|
|
|
|
Element popContent = DOM.getElementById("popup-content");
|
|
|
|
popContent.setInnerHTML(html);
|
|
|
|
popupOverlay.setPosition(coordinate);
|
2020-10-28 11:19:12 +01:00
|
|
|
}
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/**
|
|
|
|
* Adds the popup closer handelr.
|
|
|
|
*
|
|
|
|
* @param popupOverlay the popup overlay
|
|
|
|
*/
|
2020-10-27 10:19:32 +01:00
|
|
|
private void addPopupCloserHandelr(Overlay popupOverlay) {
|
|
|
|
Element elPopupCloser = DOM.getElementById("popup-closer");
|
|
|
|
Event.sinkEvents(elPopupCloser, Event.ONCLICK);
|
|
|
|
Event.setEventListener(elPopupCloser, new com.google.gwt.user.client.EventListener() {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onBrowserEvent(Event event) {
|
|
|
|
if (Event.ONCLICK == event.getTypeInt()) {
|
|
|
|
popupOverlay.setPosition(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/**
|
|
|
|
* Handler popu closer.
|
|
|
|
*
|
|
|
|
* @param divId the div id
|
|
|
|
* @param overlayId the overlay id
|
|
|
|
*/
|
2020-10-27 10:19:32 +01:00
|
|
|
public static native void handlerPopuCloser(String divId, String overlayId) /*-{
|
|
|
|
var closer = $doc.getElementById(divId);
|
|
|
|
var overlay = $doc.getElementById(overlayId);
|
|
|
|
closer.onclick = function() {
|
|
|
|
overlay.setPosition(undefined);
|
|
|
|
closer.blur();
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
}-*/;
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/**
|
|
|
|
* Adds the WMS layer.
|
2020-10-29 15:18:14 +01:00
|
|
|
*
|
|
|
|
* @param mapServerHost the map server host
|
|
|
|
* @param layerName the layer name
|
2020-10-27 16:04:34 +01:00
|
|
|
*/
|
|
|
|
public void addWMSLayer(String mapServerHost, String layerName) {
|
2020-10-23 18:18:06 +02:00
|
|
|
|
|
|
|
ImageWmsParams imageWMSParams = OLFactory.createOptions();
|
2020-10-27 16:04:34 +01:00
|
|
|
imageWMSParams.setLayers(layerName);
|
2020-10-23 18:18:06 +02:00
|
|
|
|
|
|
|
ImageWmsOptions imageWMSOptions = OLFactory.createOptions();
|
2020-10-27 16:04:34 +01:00
|
|
|
imageWMSOptions.setUrl(mapServerHost);
|
2020-10-23 18:18:06 +02:00
|
|
|
imageWMSOptions.setParams(imageWMSParams);
|
|
|
|
//imageWMSOptions.setRatio(1.5f);
|
|
|
|
|
|
|
|
ImageWms imageWMSSource = new ImageWms(imageWMSOptions);
|
|
|
|
|
|
|
|
LayerOptions layerOptions = OLFactory.createOptions();
|
|
|
|
layerOptions.setSource(imageWMSSource);
|
|
|
|
|
|
|
|
Image wmsLayer = new Image(layerOptions);
|
2020-10-29 15:18:14 +01:00
|
|
|
|
|
|
|
//visibleLayerItems
|
|
|
|
|
2020-10-23 18:18:06 +02:00
|
|
|
map.addLayer(wmsLayer);
|
|
|
|
}
|
2020-10-26 17:40:38 +01:00
|
|
|
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/**
|
|
|
|
* Adds the point vector source.
|
|
|
|
*
|
|
|
|
* @return the draw
|
|
|
|
*/
|
2020-10-26 17:40:38 +01:00
|
|
|
public Draw addPointVectorSource() {
|
2020-10-28 11:19:12 +01:00
|
|
|
if(queryPoint==null)
|
2020-10-26 17:40:38 +01:00
|
|
|
initPointInteraction();
|
|
|
|
|
2020-10-28 11:19:12 +01:00
|
|
|
map.addInteraction(queryPoint);
|
2020-10-29 15:18:14 +01:00
|
|
|
isQueryPointActive = true;
|
2020-10-28 11:19:12 +01:00
|
|
|
return queryPoint;
|
2020-10-26 17:40:38 +01:00
|
|
|
}
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/**
|
|
|
|
* Inits the point interaction.
|
|
|
|
*/
|
2020-10-26 17:40:38 +01:00
|
|
|
private void initPointInteraction() {
|
|
|
|
Vector vectorSource = new Vector();
|
|
|
|
DrawOptions drawOptions = new DrawOptions();
|
|
|
|
drawOptions.setSource(vectorSource);
|
|
|
|
drawOptions.setType("Point");
|
|
|
|
drawOptions.setMaxPoints(1);
|
|
|
|
drawOptions.setMinPoints(1);
|
|
|
|
drawOptions.setWrapX(false);
|
2020-10-28 11:19:12 +01:00
|
|
|
queryPoint = new Draw(drawOptions);
|
|
|
|
|
|
|
|
queryPoint.addChangeListener(new EventListener<ol.events.Event>() {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onEvent(ol.events.Event event) {
|
|
|
|
GWT.log(event.getType());
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
2020-10-26 17:40:38 +01:00
|
|
|
}
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/**
|
|
|
|
* Removes the interaction.
|
|
|
|
*
|
|
|
|
* @param interaction the interaction
|
|
|
|
*/
|
2020-10-26 17:40:38 +01:00
|
|
|
public void removeInteraction(Interaction interaction) {
|
|
|
|
map.removeInteraction(interaction);
|
|
|
|
}
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/**
|
|
|
|
* Removes the interactions.
|
|
|
|
*/
|
2020-10-28 11:19:12 +01:00
|
|
|
public void removeQueryInteractions() {
|
2020-10-26 17:40:38 +01:00
|
|
|
Collection<Interaction> interactions = map.getInteractions();
|
|
|
|
if(interactions!=null) {
|
2020-10-28 11:19:12 +01:00
|
|
|
map.removeInteraction(queryBox);
|
|
|
|
map.removeInteraction(queryPoint);
|
2020-10-29 15:18:14 +01:00
|
|
|
isQueryBoxActive = false;
|
|
|
|
isQueryPointActive = false;
|
2020-10-26 17:40:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/**
|
|
|
|
* Adds the extent interaction.
|
|
|
|
*
|
|
|
|
* @return the extent
|
|
|
|
*/
|
2020-10-26 17:40:38 +01:00
|
|
|
public Extent addExtentInteraction() {
|
|
|
|
ExtentOptions extentOptions = new ExtentOptions();
|
|
|
|
extentOptions.setWrapX(false);
|
|
|
|
//StyleOptions styleOptions = new StyleOptions();
|
|
|
|
//styleOptions.setStroke(stroke);
|
|
|
|
//styleOptions.set
|
|
|
|
//extentOptions.setBoxStyle(new ol.style.Style(styleOptions));
|
2020-10-28 11:19:12 +01:00
|
|
|
queryBox = new Extent(extentOptions);
|
|
|
|
map.addInteraction(queryBox);
|
2020-10-29 15:18:14 +01:00
|
|
|
isQueryBoxActive = true;
|
2020-10-28 11:19:12 +01:00
|
|
|
return queryBox;
|
2020-10-26 17:40:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-27 16:04:34 +01:00
|
|
|
/**
|
|
|
|
* Adds the overlay.
|
|
|
|
*
|
|
|
|
* @param element the element
|
|
|
|
* @return the overlay
|
|
|
|
*/
|
2020-10-27 10:19:32 +01:00
|
|
|
private Overlay addOverlay(Element element) {
|
2020-10-26 17:40:38 +01:00
|
|
|
/**
|
|
|
|
* Create an overlay to anchor the popup to the map.
|
|
|
|
*/
|
|
|
|
OverlayOptions overlayOptions = new OverlayOptions();
|
|
|
|
overlayOptions.setAutoPan(true);
|
|
|
|
Overlay overlay = new Overlay(overlayOptions);
|
2020-10-27 10:19:32 +01:00
|
|
|
overlay.setElement(element);
|
2020-10-26 17:40:38 +01:00
|
|
|
map.addOverlay(overlay);
|
2020-10-27 10:19:32 +01:00
|
|
|
return overlay;
|
2020-10-26 17:40:38 +01:00
|
|
|
}
|
2020-10-29 15:18:14 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if is layer visible.
|
|
|
|
*
|
|
|
|
* @param layerName the layer name
|
|
|
|
* @return true, if is layer visible
|
|
|
|
*/
|
|
|
|
public boolean isLayerVisible(String layerName) {
|
|
|
|
|
|
|
|
Collection<Base> layers = map.getLayers();
|
|
|
|
|
|
|
|
if(layers!=null) {
|
|
|
|
Base[] layersArr = layers.getArray();
|
|
|
|
for (int i = 0; i < layersArr.length; i++) {
|
|
|
|
Base layer = layersArr[i];
|
|
|
|
String theLayerName = (String) layer.get("name");
|
|
|
|
GWT.log("The Layer name is: "+layerName);
|
|
|
|
if(theLayerName!=null && theLayerName.equals(layerName))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2020-10-26 17:40:38 +01:00
|
|
|
|
2020-10-28 11:19:12 +01:00
|
|
|
|
2020-10-29 15:18:14 +01:00
|
|
|
/**
|
|
|
|
* Gets the projection code.
|
|
|
|
*
|
|
|
|
* @return the projection code
|
|
|
|
*/
|
|
|
|
public String getProjectionCode() {
|
|
|
|
return map.getView().getProjection().getCode();
|
|
|
|
}
|
|
|
|
|
2020-10-28 11:19:12 +01:00
|
|
|
|
2020-10-29 15:18:14 +01:00
|
|
|
/**
|
|
|
|
* Gets the current zoom level.
|
|
|
|
*
|
|
|
|
* @return the current zoom level
|
|
|
|
*/
|
|
|
|
public double getCurrentZoomLevel() {
|
|
|
|
return map.getView().getZoom();
|
2020-10-28 11:19:12 +01:00
|
|
|
}
|
|
|
|
|
2020-10-26 17:40:38 +01:00
|
|
|
|
|
|
|
|
2020-10-29 15:18:14 +01:00
|
|
|
/**
|
|
|
|
* Gets the bbox.
|
|
|
|
*
|
|
|
|
* @return the bbox
|
|
|
|
*/
|
|
|
|
public ol.Extent getBBOX() {
|
|
|
|
return getExtent();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the extent.
|
|
|
|
*
|
|
|
|
* @return the extent
|
|
|
|
*/
|
|
|
|
public ol.Extent getExtent() {
|
|
|
|
return this.map.getView().calculateExtent(map.getSize());
|
|
|
|
}
|
|
|
|
|
2020-10-23 18:18:06 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|