package org.gcube.portlets.user.geoportaldataviewer.client.ui.crossfiltering; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import org.gcube.application.geoportalcommon.shared.geoportal.config.GroupedLayersDV; import org.gcube.application.geoportalcommon.shared.geoportal.config.layers.CrossFilteringLayerDV; import org.gcube.application.geoportalcommon.shared.geoportal.config.layers.LayerIDV; import org.gcube.application.geoportalcommon.shared.geoportal.materialization.GCubeSDILayer; import org.gcube.application.geoportalcommon.shared.geoportal.materialization.IndexLayerDV; import org.gcube.application.geoportalcommon.shared.geoportal.materialization.innerobject.BBOXDV; import org.gcube.portlets.user.geoportaldataviewer.client.GeoportalDataViewerConstants.MAP_PROJECTION; import org.gcube.portlets.user.geoportaldataviewer.client.GeoportalDataViewerServiceAsync; import org.gcube.portlets.user.geoportaldataviewer.client.events.ApplyCQLToLayerOnMapEvent; import org.gcube.portlets.user.geoportaldataviewer.client.events.FitMapToExtentEvent; import org.gcube.portlets.user.geoportaldataviewer.client.gis.ExtentWrapped; import org.gcube.portlets.user.geoportaldataviewer.client.gis.MapUtils; import org.gcube.portlets.user.geoportaldataviewer.client.ui.util.OLGeoJSONUtil; import org.gcube.portlets.user.geoportaldataviewer.client.util.LoaderIcon; import org.gcube.portlets.user.geoportaldataviewer.client.util.URLUtil; import org.gcube.portlets.user.geoportaldataviewer.client.util.WFSMakerUtil; import org.gcube.portlets.user.geoportaldataviewer.shared.GCubeCollection; import org.gcube.portlets.user.geoportaldataviewer.shared.gis.LayerItem; import org.gcube.portlets.user.geoportaldataviewer.shared.gis.wms.GeoInformationForWMSRequest; import com.github.gwtbootstrap.client.ui.Alert; import com.github.gwtbootstrap.client.ui.Button; import com.github.gwtbootstrap.client.ui.CheckBox; import com.github.gwtbootstrap.client.ui.ControlGroup; import com.github.gwtbootstrap.client.ui.ControlLabel; import com.github.gwtbootstrap.client.ui.Controls; import com.github.gwtbootstrap.client.ui.DropdownButton; import com.github.gwtbootstrap.client.ui.Fieldset; import com.github.gwtbootstrap.client.ui.ListBox; import com.github.gwtbootstrap.client.ui.constants.AlertType; import com.github.gwtbootstrap.client.ui.constants.ButtonType; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.http.client.URL; import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONParser; import com.google.gwt.json.client.JSONValue; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Widget; import jsinterop.base.JsPropertyMap; import ol.Coordinate; import ol.Extent; import ol.Feature; import ol.OLFactory; /** * The Class CrossFilteringLayerPanel. * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * May 15, 2023 */ public class CrossFilteringLayerPanel extends Composite { private static final String ITEM_LABEL = "centroid"; public static String COLORSCALERANGE = "COLORSCALERANGE"; @UiField Fieldset fieldSet; @UiField HTMLPanel panelResults; @UiField Button buttonReset; private CheckBox checkbox; private GeoInformationForWMSRequest geoInformation; private static CrossFilteringLayerPanelUiBinder uiBinder = GWT.create(CrossFilteringLayerPanelUiBinder.class); private HandlerManager applicationBus; private LayerItem layerItem; private LinkedHashMap> mapInnestedFiltering = new LinkedHashMap>(); private LinkedHashMap> mapInnestedFeatures = new LinkedHashMap>(); private LinkedHashMap> mapInnestedListBoxes = new LinkedHashMap>(); private GroupedLayersDV groupedLayersDV; private GCubeCollection gCubeCollection; private DropdownButton filterButton; /** * The Interface LayerCollectionPanelUiBinder. * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * Jan 16, 2023 */ interface CrossFilteringLayerPanelUiBinder extends UiBinder { } /** * Instantiates a new overlay custom layer panel. * * @param gCubeCollection the g cube collection * @param groupedLayersDV the grouped layers DV * @param applicationBus the application bus */ public CrossFilteringLayerPanel(GCubeCollection gCubeCollection, GroupedLayersDV groupedLayersDV, HandlerManager applicationBus) { initWidget(uiBinder.createAndBindUi(this)); this.applicationBus = applicationBus; this.groupedLayersDV = groupedLayersDV; this.gCubeCollection = gCubeCollection; GWT.log("Building cross-filtering for: " + groupedLayersDV); buttonReset.setType(ButtonType.LINK); int level = 0; // Building mapInnestedFiltering recursivelyBuildSelectableLevels(level, groupedLayersDV.getListCustomLayers()); GWT.log("cross-filtering mapInnestedFiltering: " + mapInnestedFiltering); for (Integer theLevel : mapInnestedFiltering.keySet()) { addListBoxesLevelToPanel(theLevel, mapInnestedFiltering.get(theLevel)); } fillSelectableLevel(level, null); bindEvents(); } /** * Bind events. */ private void bindEvents() { buttonReset.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { panelResults.clear(); for (int i = 1; i < mapInnestedListBoxes.size(); i++) { resetListBoxLevel(i); } // Selecting the placeholder of the fist list box List listBox = mapInnestedListBoxes.get(0); listBox.get(0).setSelectedIndex(0); // Resetting CQL filtering applicationBus.fireEvent(new ApplyCQLToLayerOnMapEvent(gCubeCollection.getIndexes().get(0), null)); setFilterHighLighted(false); } }); } /** * Recursively build selectable levels. * * @param level the level * @param layersIDV the layers IDV */ private void recursivelyBuildSelectableLevels(int level, List layersIDV) { if (layersIDV == null) return; mapInnestedFiltering.put(level, layersIDV); for (LayerIDV layerIDV : layersIDV) { if (layerIDV instanceof CrossFilteringLayerDV) { CrossFilteringLayerDV crossFilteringLayer = (CrossFilteringLayerDV) layerIDV; if (crossFilteringLayer.getRelated_to() != null) { recursivelyBuildSelectableLevels(++level, crossFilteringLayer.getRelated_to()); } } } } /** * Fill selectable level. * * @param level the level * @param selectedItem the selected item */ private void fillSelectableLevel(int level, SelectableItem selectedItem) { List layersIDV = mapInnestedFiltering.get(level); List listBoxes = mapInnestedListBoxes.get(level); if (layersIDV == null) return; final HashMap mapSelectableFeatures = new HashMap(); LayerIDV layerIDV = layersIDV.get(0); // Expected one if (layerIDV instanceof CrossFilteringLayerDV) { CrossFilteringLayerDV crossFilteringLayer = (CrossFilteringLayerDV) layerIDV; String wfsURL = crossFilteringLayer.getWFS_URL(); if (selectedItem != null) { String cqlFilterValue = crossFilteringLayer.getTable_parent_key_field() + "=" + selectedItem.keyValue; wfsURL += "&" + WFSMakerUtil.CQL_FILTER_PARAMETER + "=" + URL.encode(cqlFilterValue); } GWT.log("wfsURL request: " + wfsURL); GeoportalDataViewerServiceAsync.Util.getInstance().getHTTPResponseForURL(wfsURL, new AsyncCallback() { @Override public void onSuccess(String response) { GWT.log("wfsURL response: " + response); Feature[] features = OLGeoJSONUtil.readGeoJsonFeatures(MAP_PROJECTION.EPSG_4326, response); for (Feature feature : features) { JsPropertyMap properties = feature.getProperties(); Object keyValue = properties.get(crossFilteringLayer.getTable_key_field()); Object itemField = properties.get(crossFilteringLayer.getTable_show_field()); Object parentKey = null; if (crossFilteringLayer.getTable_parent_key_field() != null && !crossFilteringLayer.getTable_parent_key_field().isEmpty()) parentKey = properties.get(crossFilteringLayer.getTable_parent_key_field()); parentKey = parentKey == null ? "" : parentKey; SelectableItem selectableItem = new SelectableItem( crossFilteringLayer.getTable_key_field() + "", keyValue + "", crossFilteringLayer.getTable_parent_key_field(), itemField + "", crossFilteringLayer.getName(), crossFilteringLayer.getTable_geometry_name()); GWT.log("selectableItem: " + selectableItem); String pathFeatureKey = pathFeatureKey(selectableItem); mapSelectableFeatures.put(pathFeatureKey, selectableItem); } GWT.log("mapSelectableFeatures: " + mapSelectableFeatures); mapInnestedFeatures.put(level, mapSelectableFeatures); String placholder = placeholderLayer(layersIDV.get(0)); // Expected one fillListBoxLevel(level, mapSelectableFeatures, listBoxes, placholder); } @Override public void onFailure(Throwable caught) { panelResults.clear(); HTML error = new HTML( "Sorry, an issue is occurred on loading data for cross-filtering facility. Error is: " + caught.getMessage()); panelResults.add(error); } }); } } /** * Path feature key. * * @param selectableItem the selectable item * @return the string */ private String pathFeatureKey(SelectableItem selectableItem) { return "root_" + selectableItem.parentKeyField + "_" + selectableItem.keyField + "_" + selectableItem.keyValue; } /** * Placeholder layer. * * @param layersIDV the layers IDV * @return the string */ private String placeholderLayer(LayerIDV layersIDV) { String placeholder = "Select"; // if (layersIDV != null) // placeholder += " " + layersIDV.getTitle(); placeholder += " ..."; return placeholder; } /** * Adds the list boxes level to panel. * * @param level the level * @param layersIDV the layers IDV */ private void addListBoxesLevelToPanel(int level, List layersIDV) { if (layersIDV == null) return; List listBoxes = new ArrayList(layersIDV.size()); for (LayerIDV layerIDV : layersIDV) { ControlGroup cg = new ControlGroup(); ControlLabel cl = new ControlLabel(layerIDV.getTitle()); Controls controls = new Controls(); ListBox listBox = new ListBox(); String placeholder = placeholderLayer(layerIDV); listBox.addItem(placeholder); setEnabledBox(listBox, false); listBoxes.add(listBox); controls.add(listBox); cg.add(cl); cg.add(controls); fieldSet.add(cg); } mapInnestedListBoxes.put(level, listBoxes); } /** * Sets the enabled box. * * @param listBox the list box * @param bool the bool */ public void setEnabledBox(ListBox listBox, boolean bool) { listBox.setEnabled(bool); } /** * Clear list box. * * @param listBox the list box * @param bool the bool */ public void clearListBox(ListBox listBox, boolean bool) { listBox.setEnabled(bool); } /** * Reset list box level. * * @param level the level */ private void resetListBoxLevel(int level) { List listBoxes = mapInnestedListBoxes.get(level); if (listBoxes != null) { for (ListBox listBox : listBoxes) { listBox.clear(); setEnabledBox(listBox, false); } } } /** * Fill list box level. * * @param level the level * @param mapSelectableFeatures the map selectable features * @param listBoxes the list boxes * @param placeholder the placeholder */ private void fillListBoxLevel(int level, HashMap mapSelectableFeatures, List listBoxes, String placeholder) { // GWT.log("fillBox level: " + level + " map: " + mapSelectableFeatures); if (mapSelectableFeatures == null) return; ListBox listBox = listBoxes.get(0); // Expected one listBox.clear(); listBox.addItem(placeholder); listBox.getElement().getFirstChildElement().setAttribute("disabled", "disabled"); for (String key : mapSelectableFeatures.keySet()) { SelectableItem selItem = mapSelectableFeatures.get(key); listBox.addItem(selItem.itemValue, key); } listBox.addChangeHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { loadInnerLevel(level, listBox.getSelectedValue()); for (int i = level + 1; i < mapInnestedListBoxes.size(); i++) { resetListBoxLevel(i); } String key = listBox.getSelectedValue(); SelectableItem selectableItem = mapSelectableFeatures.get(key); buildCrossFilteringCQLAndFireEvent(selectableItem); setFilterHighLighted(true); } }); setEnabledBox(listBox, true); } /** * Builds the cross filtering CQL and fire event. * * @param selectableItem the selectable item */ private void buildCrossFilteringCQLAndFireEvent(SelectableItem selectableItem) { String setCqlFilter = "INTERSECTS(geom,querySingle('" + selectableItem.layername + "','" + selectableItem.theGeometryName + "','" + selectableItem.keyField + "=''" + selectableItem.keyValue + "'''))"; // setCqlFilter = URL.encode(setCqlFilter); GWT.log("CQL FILTER built: " + setCqlFilter); IndexLayerDV indexLayer = gCubeCollection.getIndexes().get(0); try { GCubeSDILayer layer = indexLayer.getLayer(); String wmsLink = layer.getOgcLinks().get("wms"); String layerName = URLUtil.extractValueOfParameterFromURL("layers", wmsLink); String serviceURL = URLUtil.getPathURL(wmsLink); String toLServiceURL = serviceURL.toLowerCase(); if (toLServiceURL.endsWith("wms")) { toLServiceURL = toLServiceURL.substring(0, toLServiceURL.length() - 3) + "ows"; } String wfsCrossFilteringQuery = WFSMakerUtil.buildWFSRequest(toLServiceURL, layerName, 1000, "geom", setCqlFilter); GWT.log("wfsCrossFilteringQuery: " + wfsCrossFilteringQuery); showCountResultsOfWFSCrossFiltering(wfsCrossFilteringQuery); } catch (Exception e) { // TODO: handle exception } applicationBus.fireEvent(new ApplyCQLToLayerOnMapEvent(indexLayer, setCqlFilter)); } public void showCountResultsOfWFSCrossFiltering(String wfsQuery) { panelResults.clear(); panelResults.add(new HTML("
")); panelResults.add(new LoaderIcon("Applying spatial filter...")); GeoportalDataViewerServiceAsync.Util.getInstance().getHTTPResponseForURL(wfsQuery, new AsyncCallback() { @Override public void onFailure(Throwable caught) { panelResults.clear(); panelResults.add(new HTML("
")); Alert alert = new Alert("Error on returning number of items"); alert.setType(AlertType.ERROR); alert.setClose(false); panelResults.add(alert); } @Override public void onSuccess(String response) { Feature[] features = OLGeoJSONUtil.readGeoJsonFeatures(MAP_PROJECTION.EPSG_4326, response); if (features != null) { int dataCount = features.length; panelResults.clear(); panelResults.add(new HTML("
")); FlexTable flexTable = new FlexTable(); String message = ""; if (dataCount <= 0) { message = "No "+ITEM_LABEL+" found"; } else { message = "Found " + dataCount; message += dataCount > 1 ? " " + ITEM_LABEL + "s" : " " + ITEM_LABEL; } HTML resultMessage = new HTML(); resultMessage.getElement().addClassName("search_result_msg"); resultMessage.setHTML(message); flexTable.setWidget(0, 0, resultMessage.asWidget()); panelResults.add(flexTable); try { JSONObject jObject = (JSONObject) JSONParser.parseStrict(response); JSONArray bbox = (JSONArray) jObject.get("bbox"); double[] coords = new double[bbox.size()]; for (int i = 0; i < bbox.size(); i++) { JSONValue coord = bbox.get(i); coords[i] = Double.parseDouble(coord.toString()); } //Inverting coordinate to lat/long for EPSG:3857 Pseudo-Mercator Coordinate lower = OLFactory.createCoordinate(coords[1], coords[0]); Coordinate lowerCoord = MapUtils.transformCoordiante(lower, MAP_PROJECTION.EPSG_4326.getName(), MAP_PROJECTION.EPSG_3857.getName()); Coordinate upper = OLFactory.createCoordinate(coords[3], coords[2]); Coordinate upperCoord = MapUtils.transformCoordiante(upper, MAP_PROJECTION.EPSG_4326.getName(), MAP_PROJECTION.EPSG_3857.getName()); final Extent transfExtent = new ExtentWrapped(lowerCoord.getX(), lowerCoord.getY(), upperCoord.getX(), upperCoord.getY()); GWT.log("Zoom to selected - transf extent: "+transfExtent); Button selectTo = new Button("Zoom to selected"); selectTo.setType(ButtonType.DEFAULT); selectTo.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { applicationBus.fireEvent(new FitMapToExtentEvent(transfExtent)); } }); flexTable.setWidget(1,0,selectTo); //panelResults.add(selectTo); }catch (Exception e) { // TODO: handle exception } } } }); } public static final BBOXDV fromGeoJSON(double[] coords) { BBOXDV toReturn = new BBOXDV(); toReturn.setMaxX(coords[0]); toReturn.setMinY(coords[1]); if (coords.length == 6) { // 3D toReturn.setMinZ(coords[2]); toReturn.setMinX(coords[3]); toReturn.setMaxY(coords[4]); toReturn.setMaxZ(coords[5]); } else { toReturn.setMinX(coords[2]); toReturn.setMaxY(coords[3]); } return toReturn; } /** * Load inner level. * * @param level the level * @param selectableItemValue the selectable item value */ private void loadInnerLevel(int level, String selectableItemValue) { // GWT.log("selected level " + level + " selectableItemValue " + // selectableItemValue); HashMap mapSelectableItem = mapInnestedFeatures.get(level); SelectableItem selectedItem = mapSelectableItem.get(selectableItemValue); // GWT.log("selected selectableItem " + selectedItem); int innerLevel = level + 1; List selectedLayers = mapInnestedFiltering.get(innerLevel); // if layer filtering exists at this level if (selectedLayers != null) { List selectedListBoxes = mapInnestedListBoxes.get(innerLevel); GWT.log("loading inner layers " + selectedLayers); GWT.log("loading selectedListBoxes " + selectedListBoxes); fillSelectableLevel(innerLevel, selectedItem); } } /** * Sets the filter high lighted. * * @param bool the new filter high lighted */ private void setFilterHighLighted(boolean bool) { if (bool) { filterButton.getElement().addClassName("highlight-button"); } else { filterButton.getElement().removeClassName("highlight-button"); } } /** * Sets the search button. * * @param searchFacilityButton the new search button */ public void setFilterButton(DropdownButton searchFacilityButton) { this.filterButton = searchFacilityButton; } /** * The Class SelectableItem. * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * May 31, 2023 */ public class SelectableItem { protected String keyField; protected String keyValue; protected String parentKeyField; protected String itemValue; protected String layername; protected String theGeometryName; /** * Instantiates a new selectable item. */ SelectableItem() { } /** * Instantiates a new selectable item. * * @param keyField the key field * @param keyValue the key value * @param parentKeyField the parent key field * @param itemValue the item value * @param layername the layername * @param theGeometryName the the geometry name */ public SelectableItem(String keyField, String keyValue, String parentKeyField, String itemValue, String layername, String theGeometryName) { super(); this.keyField = keyField; this.keyValue = keyValue; this.parentKeyField = parentKeyField; this.itemValue = itemValue; this.layername = layername; this.theGeometryName = theGeometryName; } /** * To string. * * @return the string */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("SelectableItem [keyField="); builder.append(keyField); builder.append(", keyValue="); builder.append(keyValue); builder.append(", parentKeyField="); builder.append(parentKeyField); builder.append(", itemValue="); builder.append(itemValue); builder.append(", layername="); builder.append(layername); builder.append(", theGeometryName="); builder.append(theGeometryName); builder.append("]"); return builder.toString(); } } }