Fixing DD layers and Open Details popup

This commit is contained in:
Francesco Mangiacrapa 2022-10-25 17:01:24 +02:00
parent 05196b2294
commit 4dff83b63f
7 changed files with 256 additions and 147 deletions

View File

@ -1,5 +1,6 @@
package org.gcube.portlets.user.geoportaldataviewer.client;
import java.util.LinkedHashMap;
import java.util.List;
import org.gcube.application.geoportalcommon.shared.GNADataViewerConfigProfile;
@ -50,9 +51,10 @@ public interface GeoportalDataViewerService extends RemoteService {
* @param maxWFSFeature the max WFS feature
* @param zoomLevel the zoom level
* @return the data result
* @throws Exception
*/
List<GeoNaSpatialQueryResult> getDataResult(List<LayerObject> layerObjects, String mapSrsName, BoundsMap mapBBOX,
int maxWFSFeature, double zoomLevel);
int maxWFSFeature, double zoomLevel) throws Exception;
/**
* Gets the concessione for id.
@ -191,4 +193,14 @@ public interface GeoportalDataViewerService extends RemoteService {
ViewerConfiguration getInitialConfiguration() throws Exception;
/**
* Gets the entry sets document for project ID.
*
* @param profileID the profile ID
* @param projectID the project ID
* @param limit the limit
* @return the entry sets document for project ID
*/
LinkedHashMap<String, Object> getEntrySetsDocumentForProjectID(String profileID, String projectID, int limit);
}

View File

@ -1,5 +1,6 @@
package org.gcube.portlets.user.geoportaldataviewer.client;
import java.util.LinkedHashMap;
import java.util.List;
import org.gcube.application.geoportalcommon.shared.GNADataViewerConfigProfile;
@ -77,4 +78,7 @@ public interface GeoportalDataViewerServiceAsync {
void getInitialConfiguration(AsyncCallback<ViewerConfiguration> callback);
void getEntrySetsDocumentForProjectID(String profileID, String projectID, int limit,
AsyncCallback<LinkedHashMap<String, Object>> callback);
}

View File

@ -8,7 +8,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.gcube.application.geoportalcommon.shared.geoportal.materialization.GCubeSDIViewerLayerDV;
@ -43,7 +42,6 @@ import org.gcube.portlets.user.geoportaldataviewer.shared.gis.wms.GeoInformation
import org.gcube.portlets.user.geoportaldataviewer.shared.gis.wms.ZAxis;
import com.github.gwtbootstrap.client.ui.Button;
import com.github.gwtbootstrap.client.ui.Heading;
import com.github.gwtbootstrap.client.ui.Label;
import com.github.gwtbootstrap.client.ui.constants.ButtonType;
import com.github.gwtbootstrap.client.ui.constants.IconType;
@ -345,7 +343,9 @@ public class LayerManager {
true,
OLMapManager.LAYER_DETAIL_MIN_RESOLUTION,
OLMapManager.LAYER_DETAIL_MAX_RESOLUTION,
theProfileID, theProductID, geoNaDataObject.getSourceLayerObject().getProjectDV());
theProfileID, theProductID,
geoNaDataObject.getSourceLayerObject()
.getProjectDV());
}
}
@ -465,12 +465,14 @@ public class LayerManager {
* @param maxResolution the max resolution
* @param profileID the profile ID
* @param projectID the project ID
* @param projectDV the project DV
* @param projectDV the project DV
*/
public void addLayer(final LayerObjectType lot, final String layerTitle, final String layerName,
final String wmsLink, final boolean isBase, final boolean displayInLayerSwitcher, final String UUID,
final boolean asDetailLayer, Double minResolution, Double maxResolution, final String profileID,
final String projectID, final ProjectDV projectDV) {
GWT.log("addLayer called for profileID: " + profileID + ", projectID: " + projectID + ", projectDV: "
+ projectDV);
final LayerType featureType = isBase ? LayerType.RASTER_BASELAYER : LayerType.FEATURE_TYPE;
@ -520,7 +522,7 @@ public class LayerManager {
if (theLo == null) {
theLo = lo;
mapIndexLayerObjects.put(layerNameKey, theLo);
GWT.log("mapIndexLayerObjects is: " + mapIndexLayerObjects);
GWT.log("INDEX_LAYER mapIndexLayerObjects is: " + mapIndexLayerObjects);
olMap.addWMSLayer(layerItem);
} else {
GWT.log("Skipping " + lo.getType() + " layer " + theLo.getLayerItem().getName()
@ -532,7 +534,7 @@ public class LayerManager {
if (theLo == null) {
theLo = lo;
mapOtherLayerObjects.put(layerNameKey, theLo);
GWT.log("mapOtherLayerObjects is: " + mapIndexLayerObjects);
GWT.log("PROJECT_LAYER mapOtherLayerObjects is: " + mapOtherLayerObjects);
olMap.addWMSDetailLayer(layerItem);
overlayLayerManager.addLayerItem(theLo);
} else {
@ -569,11 +571,10 @@ public class LayerManager {
});
}
/**
* Adds the index layer.
*
* @param layer the layer
* @param layer the layer
* @param profileID the profile ID
* @param projectDV the project DV
*/
@ -756,16 +757,20 @@ public class LayerManager {
// : newProjectID;
//
ProjectDV projectDV = lo.getProjectDV();
Entry<String, Object> firstEntry = projectDV.getTheDocument().getFirstEntryOfMap();
String htmlMsg = ProjectUtil.toHMLCode(projectDV.getTheDocument());
String projectIntro = htmlMsg.length() > 100 ? StringUtil.ellipsize(htmlMsg, 100)
: htmlMsg;
Heading heading = new Heading(4, lo.getProjectDV().getProfileName());
heading.setTitle("Project ID: "+newProjectID);
heading.getElement().getStyle().setMarginBottom(10, Unit.PX);
flowPanel.add(heading);
String projectIntro = htmlMsg.length() > 100 ? StringUtil.ellipsize(htmlMsg, 100) : htmlMsg;
// Heading heading = new Heading(4, lo.getProjectDV().getProfileName());
// heading.setTitle("Project ID: "+newProjectID);
// heading.getElement().getStyle().setMarginBottom(10, Unit.PX);
Label headingProfileName = new Label(lo.getProjectDV().getProfileName());
headingProfileName.setType(LabelType.WARNING);
headingProfileName.setTitle("Project ID: " + newProjectID);
headingProfileName.getElement().getStyle().setMarginBottom(10, Unit.PX);
FlowPanel headingPanel = new FlowPanel();
headingPanel.add(headingProfileName);
flowPanel.add(headingPanel);
flowPanel.add(new HTML(projectIntro));
Button buttOpenProject = new Button("Open Project");

View File

@ -92,9 +92,9 @@ public class ProjectViewer extends Composite {
final String theTitle = projectView.getTheProjectDV().getProfileName() != null
? projectView.getTheProjectDV().getProfileName()
: projectView.getTheProjectDV().getId();
: "Project ID: "+projectView.getTheProjectDV().getId();
headerPanel.add(new HTML("Project: " + theTitle));
headerPanel.add(new HTML(theTitle));
shareButton.setType(ButtonType.LINK);
shareButton.setIcon(IconType.SHARE);

View File

@ -1,10 +1,12 @@
package org.gcube.portlets.user.geoportaldataviewer.client.ui.dandd;
import java.util.LinkedHashMap;
import org.gcube.portlets.user.geoportaldataviewer.client.GeoportalDataViewerServiceAsync;
import org.gcube.portlets.user.geoportaldataviewer.client.events.DoActionOnDetailLayersEvent;
import org.gcube.portlets.user.geoportaldataviewer.client.events.DoActionOnDetailLayersEvent.DO_LAYER_ACTION;
import org.gcube.portlets.user.geoportaldataviewer.client.events.DoActionOnDetailLayersEvent.SwapLayer;
import org.gcube.portlets.user.geoportaldataviewer.client.resources.GNAImages;
import org.gcube.portlets.user.geoportaldataviewer.client.ui.cms.project.ProjectUtil;
import org.gcube.portlets.user.geoportaldataviewer.client.util.StringUtil;
import org.gcube.portlets.user.geoportaldataviewer.shared.gis.LayerItem;
import org.gcube.portlets.user.geoportaldataviewer.shared.gis.LayerObject;
@ -27,6 +29,7 @@ import com.google.gwt.event.dom.client.DragStartHandler;
import com.google.gwt.event.dom.client.DropEvent;
import com.google.gwt.event.dom.client.DropHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
@ -46,6 +49,7 @@ public class DragDropLayer extends FlowPanel {
private HandlerManager applicationBus;
private LayerObject layerObject;
private LayerItem layerItem;
private com.google.gwt.user.client.ui.Label labelProject;
private boolean layerVisibility = true;
private HTML buttonLayerVisibility = new HTML();
@ -53,24 +57,45 @@ public class DragDropLayer extends FlowPanel {
this.applicationBus = applicationBus;
this.layerObject = layerObject;
this.layerItem = layerObject.getLayerItem();
GWT.log("DragDropLayer for projectDV: " + layerObject.getProjectDV());
String referProject = "Project ID: " + layerObject.getProjectDV().getId();
labelProject = new com.google.gwt.user.client.ui.Label("");
labelProject.setTitle(referProject);
GeoportalDataViewerServiceAsync.Util.getInstance().getEntrySetsDocumentForProjectID(
layerObject.getProjectDV().getProfileID(), layerObject.getProjectDV().getId(), 1,
new AsyncCallback<LinkedHashMap<String, Object>>() {
@Override
public void onSuccess(LinkedHashMap<String, Object> result) {
String referProject = "";
if (result != null) {
for (String key : result.keySet()) {
result.get(key);
referProject = key + ": " + result.get(key);
break;
}
}
if (referProject != null && !referProject.isEmpty()) {
labelProject.setText(StringUtil.ellipsize(referProject, 40));
labelProject.setTitle(referProject);
}
}
@Override
public void onFailure(Throwable caught) {
labelProject.setText(StringUtil.ellipsize(referProject, 40));
labelProject.setTitle(referProject);
}
});
String layerName = StringUtil.fullNameToLayerName(layerItem.getName(), ":");
this.labelLayerName = new Label(layerName);
this.labelLayerName.setTitle(layerItem.getName());
this.labelLayerName.setType(LabelType.INFO);
String referProject = null;
GWT.log("DragDropLayer for projectDV: "+ layerObject.getProjectDV());
if(layerObject.getProjectDV()!=null && layerObject.getProjectDV().getTheDocument()!=null) {
referProject = ProjectUtil.toHMLCode(layerObject.getProjectDV().getTheDocument());
}
referProject = referProject!=null?referProject:layerObject.getLayerItem().getName();
//LayerItem refConcessione = layerObject.getLayerItem();
String nameConcessione = StringUtil.ellipsize(referProject, 40);
com.google.gwt.user.client.ui.Label labelConcessione = new com.google.gwt.user.client.ui.Label(nameConcessione);
labelConcessione.setTitle(referProject);
getElement().getStyle().setMarginTop(5, Unit.PX);
getElement().getStyle().setMarginBottom(10, Unit.PX);
@ -102,7 +127,7 @@ public class DragDropLayer extends FlowPanel {
// labelLayerName.getElement().getStyle().setMarginLeft(10, Unit.PX);
ft.setWidget(0, 1, buttonLayerVisibility);
ft.setWidget(0, 2, labelConcessione);
ft.setWidget(0, 2, labelProject);
ft.setWidget(1, 2, labelLayerName);
ft.setWidget(2, 2, new SimplePanel(rs));
add(ft);

View File

@ -89,7 +89,6 @@ import com.google.gwt.user.server.rpc.RemoteServiceServlet;
@SuppressWarnings("serial")
public class GeoportalDataViewerServiceImpl extends RemoteServiceServlet implements GeoportalDataViewerService {
private static final Logger LOG = LoggerFactory.getLogger(GeoportalDataViewerServiceImpl.class);
private static final String CACHE_IMAGE_PREVIEW_FOR_CONCESSIONE = "MAP_IMAGE_PREVIEW_FOR_CONCESSIONE";
@ -859,7 +858,6 @@ public class GeoportalDataViewerServiceImpl extends RemoteServiceServlet impleme
} catch (Exception e) {
LOG.error("Error on loading the " + GcubeProfilesPerUCDIdCache.class.getSimpleName() + " for scope: "
+ scope, e);
e.printStackTrace();
}
super.onBeforeRequestDeserialized(serializedRequest);
}
@ -994,6 +992,15 @@ public class GeoportalDataViewerServiceImpl extends RemoteServiceServlet impleme
}
/**
* Gets the images for id.
*
* @param profileID the profile ID
* @param projectID the project ID
* @param limitToFirstOneFound the limit to first one found
* @return the images for id
* @throws Exception the exception
*/
protected List<PayloadDV> getImagesForId(String profileID, String projectID, boolean limitToFirstOneFound)
throws Exception {
LOG.info("getImagesForId [profileID: " + profileID + ", projectID: " + projectID + ", limitToFirstOneFound: "
@ -1078,7 +1085,7 @@ public class GeoportalDataViewerServiceImpl extends RemoteServiceServlet impleme
ProjectView projectView = Geoportal_JSON_Mapper.loadProjectView(theProjectDV, scope, userName);
if (LOG.isDebugEnabled()) {
if (LOG.isTraceEnabled()) {
Geoportal_JSON_Mapper.prettyPrintProjectView(projectView);
}
@ -1102,151 +1109,203 @@ public class GeoportalDataViewerServiceImpl extends RemoteServiceServlet impleme
* @param maxWFSFeature the max WFS feature
* @param zoomLevel the zoom level
* @return the data result
* @throws Exception
*/
@Override
public List<GeoNaSpatialQueryResult> getDataResult(List<LayerObject> layerObjects, String mapSrsName,
BoundsMap selectBBOX, int maxWFSFeature, double zoomLevel) {
BoundsMap selectBBOX, int maxWFSFeature, double zoomLevel) throws Exception {
LOG.info("getDataResult called for layerObjects: " + layerObjects);
if (LOG.isDebugEnabled()) {
LOG.info("getDataResult parmeters layerObjects: " + layerObjects,
", mapSrsName: " + mapSrsName + ", selectBBOX: " + selectBBOX + ", maxWFSFeature: " + maxWFSFeature
+ ", zoomLevel: " + zoomLevel);
}
List<GeoNaSpatialQueryResult> listDAO = new ArrayList<GeoNaSpatialQueryResult>(layerObjects.size());
for (LayerObject layerObject : layerObjects) {
GeoNaSpatialQueryResult geoDAO = new GeoNaSpatialQueryResult();
List<FeatureRow> features = FeatureParser.getWFSFeatures(layerObject.getLayerItem(), mapSrsName, selectBBOX,
maxWFSFeature);
LOG.debug("For layer name: " + layerObject.getLayerItem().getName() + " got features: " + features);
geoDAO.setFeatures(features);
try {
new GeoportalServiceIdentityProxy(this.getThreadLocalRequest());
if (features != null && features.size() > 0) {
for (LayerObject layerObject : layerObjects) {
GeoNaSpatialQueryResult geoDAO = new GeoNaSpatialQueryResult();
List<FeatureRow> features = FeatureParser.getWFSFeatures(layerObject.getLayerItem(), mapSrsName,
selectBBOX, maxWFSFeature);
LOG.debug("For layer name: " + layerObject.getLayerItem().getName() + " got features: " + features);
geoDAO.setFeatures(features);
LayerObjectType loType = layerObject.getType();
if (loType == null)
loType = LayerObjectType.GENERIC_LAYER;
if (features != null && features.size() > 0) {
switch (layerObject.getType()) {
case INDEX_LAYER: {
LayerObjectType loType = layerObject.getType();
if (loType == null)
loType = LayerObjectType.GENERIC_LAYER;
// Expected 1 feature
FeatureRow fRow = features.get(0);
if (fRow.getMapProperties() != null) {
List<String> productIDs = fRow.getMapProperties().get(GeoportalDataViewerConstants.PROJECT_ID_KEY_FEATURE);
if (productIDs != null && productIDs.size() > 0) {
String projectID = productIDs.get(0);
layerObject.setProjectID(projectID);
String profileID = layerObject.getProfileID();
switch (layerObject.getType()) {
case INDEX_LAYER: {
List<PayloadDV> images;
// Loading images for profileID and projectID
try {
images = getImagesForId(profileID, projectID, true);
Map<String, List<PayloadDV>> mapImages = new LinkedHashMap<String, List<PayloadDV>>();
mapImages.put(projectID, images);
// mapImages.put(cId, listUI);
geoDAO.setMapImages(mapImages);
} catch (Exception e) {
LOG.warn("Error on loading images for projectID: " + projectID + " profileID: "
+ profileID);
}
// Sets only profileID and profileName into ProjectDV
if (layerObject.getProjectDV() == null) {
QueryRequest request = new QueryRequest();
request.setFilter(Document.parse(
"{\"" + UseCaseDescriptor.ID + "\" : " + "{\"$eq\" : \"" + profileID + "\"}}"));
request.setProjection(Document.parse("{\"" + UseCaseDescriptor.NAME + "\" : " + "1}"));
// Expected 1 feature
FeatureRow fRow = features.get(0);
if (fRow.getMapProperties() != null) {
List<String> productIDs = fRow.getMapProperties()
.get(GeoportalDataViewerConstants.PROJECT_ID_KEY_FEATURE);
if (productIDs != null && productIDs.size() > 0) {
String projectID = productIDs.get(0);
layerObject.setProjectID(projectID);
String profileID = layerObject.getProfileID();
List<PayloadDV> images;
// Loading images for profileID and projectID
try {
useCaseDescriptors().build().query(request).forEachRemaining(u -> {
try {
LOG.debug("UCD for id" + u.getId() + " returend name: " + u.getName());
ProjectDV projectDV = new ProjectDV();
projectDV.setId(projectID);
projectDV.setProfileName(u.getName());
projectDV.setProfileID(u.getId());
layerObject.setProjectDV(projectDV);
} catch (Exception e) {
LOG.warn("Invalid UCD, UCID : " + u.getId());
}
});
images = getImagesForId(profileID, projectID, true);
Map<String, List<PayloadDV>> mapImages = new LinkedHashMap<String, List<PayloadDV>>();
mapImages.put(projectID, images);
// mapImages.put(cId, listUI);
geoDAO.setMapImages(mapImages);
} catch (Exception e) {
// silent
LOG.warn("Error on loading images for projectID: " + projectID + " profileID: "
+ profileID);
}
// Sets only profileID and profileName into ProjectDV
if (layerObject.getProjectDV() == null) {
ProjectDV projectDV = minimaProjectDV(profileID, projectID);
layerObject.setProjectDV(projectDV);
}
}
}
geoDAO.setSourceLayerObject(layerObject);
LOG.info("For layer name: " + layerObject.getLayerItem().getName() + " got " + features.size()
+ " feature/s");
listDAO.add(geoDAO);
break;
}
geoDAO.setSourceLayerObject(layerObject);
LOG.info("For layer name: " + layerObject.getLayerItem().getName() + " got " + features.size()
+ " feature/s");
listDAO.add(geoDAO);
case PROJECT_LAYER:
case GENERIC_LAYER: {
LOG.debug("The LayerObject is a of kind: " + layerObject.getType());
// TODO CHECK THIS
// Getting the projectid from WFS features, but limiting to the first one, Do we
// have more than one centroid in the same position??
break;
}
case PROJECT_LAYER:
case GENERIC_LAYER: {
LOG.debug("The LayerObject is a of kind: " + layerObject.getType());
// TODO CHECK THIS
// Getting the projectid from WFS features, but limiting to the first one, Do we
// have more than one centroid in the same position??
//Mininal set of info for displaying into popup
if (layerObject.getProjectDV() == null) {
ProjectDV projectDV = new ProjectDV();
projectDV.setId(layerObject.getProjectID());
projectDV.setProfileName(layerObject.getProfileID());
layerObject.setProjectDV(projectDV);
}
if(layerObject.getProjectDV().getTheDocument()==null) {
Project theProject;
try {
theProject = GeoportalClientCaller.projects().getProjectByID(layerObject.getProfileID(),
// Mininal set of info for displaying into popup
if (layerObject.getProjectDV() == null) {
ProjectDV projectDV = minimaProjectDV(layerObject.getProfileID(),
layerObject.getProjectID());
LinkedHashMap<String, Object> documentAsMap = new LinkedHashMap<String, Object>(1);
Entry<String, Object> firstEntry = null;
try {
firstEntry = theProject.getTheDocument().entrySet().iterator().next();
documentAsMap.put(firstEntry.getKey(), firstEntry.getValue());
}catch (Exception e) {
//Silent
}
DocumentDV documentDV = new DocumentDV();
documentDV.setDocumentAsMap(documentAsMap);
layerObject.getProjectDV().setTheDocument(documentDV);
} catch (Exception e) {
String erroMsg = "Error occurred on loading document for profileID "+layerObject.getProfileID()+" and projectID "+layerObject.getProjectID();
LOG.warn(erroMsg, e);
layerObject.setProjectDV(projectDV);
}
if (layerObject.getProjectDV().getTheDocument() == null) {
try {
LinkedHashMap<String, Object> documentAsMap = getEntrySetsDocumentForProjectID(
layerObject.getProfileID(), layerObject.getProjectID(), 1);
DocumentDV documentDV = new DocumentDV();
documentDV.setDocumentAsMap(documentAsMap);
layerObject.getProjectDV().setTheDocument(documentDV);
} catch (Exception e) {
String erroMsg = "Error occurred on loading document for profileID "
+ layerObject.getProfileID() + " and projectID " + layerObject.getProjectID();
LOG.warn(erroMsg, e);
}
}
LOG.debug("Case PROJECT_LAYER/GENERIC_LAYER setting layerObject: " + layerObject);
geoDAO.setSourceLayerObject(layerObject);
LOG.info("For layer name: " + layerObject.getLayerItem().getName() + " got " + features.size()
+ " feature/s");
listDAO.add(geoDAO);
}
LOG.debug("Case PROJECT_LAYER/GENERIC_LAYER setting layerObject: " + layerObject);
geoDAO.setSourceLayerObject(layerObject);
LOG.info("For layer name: " + layerObject.getLayerItem().getName() + " got " + features.size()
+ " feature/s");
listDAO.add(geoDAO);
default:
break;
}
}
default:
break;
}
}
} catch (Exception e) {
String erroMsg = "Error occurred on getting data results";
LOG.error(erroMsg, e);
throw new Exception(erroMsg);
}
LOG.info("returning " + listDAO + " geona data objects");
return listDAO;
}
private ProjectDV minimaProjectDV(String profileID, String projectID) {
QueryRequest request = new QueryRequest();
request.setFilter(
Document.parse("{\"" + UseCaseDescriptor.ID + "\" : " + "{\"$eq\" : \"" + profileID + "\"}}"));
request.setProjection(Document.parse("{\"" + UseCaseDescriptor.NAME + "\" : " + "1}"));
ProjectDV projectDV = new ProjectDV();
try {
useCaseDescriptors().build().query(request).forEachRemaining(u -> {
try {
LOG.debug("UCD for id" + u.getId() + " returend name: " + u.getName());
projectDV.setId(projectID);
projectDV.setProfileName(u.getName());
projectDV.setProfileID(u.getId());
} catch (Exception e) {
LOG.warn("Invalid UCD, UCID : " + u.getId());
}
});
} catch (Exception e) {
// silent
}
return projectDV;
}
/**
* Gets the entry sets document for project ID.
*
* @param profileID the profile ID
* @param projectID the project ID
* @param limit the limit
* @return the entry sets document for project ID
*/
@Override
public LinkedHashMap<String, Object> getEntrySetsDocumentForProjectID(String profileID, String projectID,
int limit) {
LinkedHashMap<String, Object> documentAsMap = new LinkedHashMap<String, Object>(limit);
try {
Project theProject = GeoportalClientCaller.projects().getProjectByID(profileID, projectID);
try {
Iterator<Entry<String, Object>> entrySetsIt = theProject.getTheDocument().entrySet().iterator();
int i = 0;
while (entrySetsIt.hasNext()) {
if (i > limit)
break;
Entry<String, Object> entry = entrySetsIt.next();
documentAsMap.put(entry.getKey(), entry.getValue());
i++;
}
} catch (Exception e) {
throw e;
}
return documentAsMap;
} catch (Exception e) {
String erroMsg = "Error occurred on loading EntrySets document for profileID " + profileID
+ " and projectID " + projectID;
LOG.warn(erroMsg, e);
}
return documentAsMap;
}
}

View File

@ -530,4 +530,8 @@ body {
padding-right: 15px;
width: 120px;
}
.my-html-table td:last-child {
text-align: justify;
}