/* * Constellation - An open source and standard compliant SDI * http://www.constellation-sdi.org * * Copyright 2014 Geomatys. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.constellation.map.featureinfo; import com.vividsolutions.jts.geom.Geometry; import org.apache.sis.geometry.GeneralDirectPosition; import org.apache.sis.storage.DataStoreException; import org.apache.sis.util.logging.Logging; import org.constellation.provider.Data; import org.constellation.ws.MimeType; import org.geotoolkit.coverage.GridSampleDimension; import org.geotoolkit.display.PortrayalException; import org.geotoolkit.display2d.canvas.RenderingContext2D; import org.geotoolkit.display2d.primitive.ProjectedCoverage; import org.geotoolkit.display2d.primitive.ProjectedFeature; import org.geotoolkit.display2d.primitive.SearchAreaJ2D; import org.geotoolkit.display2d.service.CanvasDef; import org.geotoolkit.display2d.service.SceneDef; import org.geotoolkit.display2d.service.ViewDef; import org.geotoolkit.feature.ComplexAttribute; import org.geotoolkit.feature.Feature; import org.geotoolkit.feature.GeometryAttribute; import org.geotoolkit.feature.Property; import org.geotoolkit.feature.type.FeatureType; import org.geotoolkit.geometry.jts.JTSEnvelope2D; import org.geotoolkit.map.FeatureMapLayer; import org.geotoolkit.ows.xml.GetFeatureInfo; import org.geotoolkit.util.DateRange; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import java.awt.*; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; import org.geotoolkit.storage.coverage.CoverageReference; import org.opengis.util.GenericName; /** * A generic FeatureInfoFormat that produce XML output for Features and Coverages. * Supported mimeTypes are : * <ul> * <li>application/vnd.ogc.xml</li> * <li>text/xml</li> * </ul> * * @author Quentin Boileau (Geomatys) */ public class XMLFeatureInfoFormat extends AbstractTextFeatureInfoFormat { private static final Logger LOGGER = Logging.getLogger("org.constellation.map.featureinfo"); private GetFeatureInfo gfi; public XMLFeatureInfoFormat() { } /** * {@inheritDoc} */ @Override protected void nextProjectedCoverage(ProjectedCoverage coverage, RenderingContext2D context, SearchAreaJ2D queryArea) { final List<Map.Entry<GridSampleDimension,Object>> results = FeatureInfoUtilities.getCoverageValues(coverage, context, queryArea); if (results == null) { return; } final CoverageReference ref = coverage.getLayer().getCoverageReference(); final GenericName fullLayerName = ref.getName(); String layerName = fullLayerName.tip().toString(); StringBuilder builder = new StringBuilder(); String margin = "\t"; builder.append(margin).append("<Coverage>\n"); margin += "\t"; builder.append(margin).append("<Layer>").append(encodeXML(layerName)).append("</Layer>\n"); builder.append(coverageToXML(coverage, results, margin, gfi, getLayersDetails())); margin = margin.substring(1); builder.append(margin).append("</Coverage>\n"); if (builder.length() > 0) { List<String> strs = coverages.get(layerName); if (strs == null) { strs = new ArrayList<>(); coverages.put(layerName, strs); } strs.add(builder.toString()); } } protected static String coverageToXML(final ProjectedCoverage coverage, final List<Map.Entry<GridSampleDimension,Object>> results, String margin, final GetFeatureInfo gfi, final List<Data> layerDetailsList) { StringBuilder builder = new StringBuilder(); final CoverageReference ref = coverage.getLayer().getCoverageReference(); final GenericName fullLayerName = ref.getName(); Data layerPostgrid = null; for (Data layer : layerDetailsList) { if (layer.getType().equals(Data.TYPE.COVERAGE) && layer.getName().equals(fullLayerName)) { layerPostgrid = layer; } } final Envelope objEnv; List<Date> time; Double elevation; if (gfi != null && gfi instanceof org.geotoolkit.wms.xml.GetFeatureInfo) { org.geotoolkit.wms.xml.GetFeatureInfo wmsGFI = (org.geotoolkit.wms.xml.GetFeatureInfo) gfi; objEnv = wmsGFI.getEnvelope2D(); time = wmsGFI.getTime(); elevation = wmsGFI.getElevation(); } else { objEnv = null; time = null; elevation = null; } // if (objEnv != null) { // final CoordinateReferenceSystem crs = objEnv.getCoordinateReferenceSystem(); // final GeneralDirectPosition pos = getPixelCoordinates(gfi); // if (pos != null) { // builder.append("<gml:boundedBy>").append("\n"); // String crsName; // try { // crsName = IdentifiedObjects.lookupIdentifier(crs, true); // } catch (FactoryException ex) { // LOGGER.log(Level.INFO, ex.getLocalizedMessage(), ex); // crsName = crs.getName().getCode(); // } // builder.append("\t\t\t\t<gml:Box srsName=\"").append(crsName).append("\">\n"); // builder.append("\t\t\t\t\t<gml:coordinates>"); // builder.append(pos.getOrdinate(0)).append(",").append(pos.getOrdinate(1)).append(" ") // .append(pos.getOrdinate(0)).append(",").append(pos.getOrdinate(1)); // builder.append("</gml:coordinates>").append("\n"); // builder.append("\t\t\t\t</gml:Box>").append("\n"); // builder.append("\t\t\t</gml:boundedBy>").append("\n"); // builder.append("\t\t\t<x>").append(pos.getOrdinate(0)).append("</x>").append("\n") // .append("\t\t\t<y>").append(pos.getOrdinate(1)).append("</y>").append("\n"); // } // } if (time == null) { /* * Get the date of the last slice in this layer. Don't invoke * layerPostgrid.getAvailableTimes().last() because getAvailableTimes() is very * costly. The layerPostgrid.getEnvelope() method is much cheaper, since it can * leverage the database index. */ DateRange dates = null; if (layerPostgrid != null) { try { dates = layerPostgrid.getDateRange(); } catch (DataStoreException ex) { LOGGER.log(Level.INFO, ex.getLocalizedMessage(), ex); } } if (dates != null && !(dates.isEmpty())) { if (dates.getMaxValue() != null) { time = Collections.singletonList(dates.getMaxValue()); } } } if (time != null && !time.isEmpty()) { // TODO : Manage periods. final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); df.setTimeZone(TimeZone.getTimeZone("UTC")); builder.append(margin).append("<time>").append(encodeXML(df.format(time.get(time.size()-1)))).append("</time>").append("\n"); } if (elevation == null) { SortedSet<Number> elevs = null; if (layerPostgrid != null) { try { elevs = layerPostgrid.getAvailableElevations(); } catch (DataStoreException ex) { LOGGER.log(Level.INFO, ex.getLocalizedMessage(), ex); elevs = null; } } if (elevs != null && !(elevs.isEmpty())) { elevation = elevs.first().doubleValue(); } } if (elevation != null) { builder.append(margin).append("<elevation>").append(elevation).append("</elevation>").append("\n"); } builder.append(margin).append("<values>").append("\n"); margin += "\t"; int index = 0; for (final Map.Entry<GridSampleDimension,Object> entry : results) { final Object value = entry.getValue(); if (value == null) { continue; } String bandName = "band_"+index; String unit = entry.getKey().getUnits() != null ? entry.getKey().getUnits().toString() : null; if (unit != null) { builder.append(margin).append("<").append(bandName).append(" unit =\"").append(unit).append("\">"); } else { builder.append(margin).append("<").append(bandName).append(">"); } builder.append(value) .append("</").append(encodeXML(bandName)).append(">").append("\n"); } margin = margin.substring(1); builder.append(margin).append("</values>").append("\n"); // // if (!results.isEmpty()) { // builder.append("\t\t\t<variable>") // .append(results.get(0).getKey().getDescription()) // .append("</variable>").append("\n"); // } // // MeasurementRange[] ranges = null; // if (layerPostgrid != null) { // ranges = layerPostgrid.getSampleValueRanges(); // } // if (ranges != null && ranges.length > 0) { // final MeasurementRange range = ranges[0]; // if (range != null) { // final Unit unit = range.getUnits(); // if (unit != null && !unit.toString().isEmpty()) { // builder.append("\t\t\t<unit>").append(unit.toString()) // .append("</unit>").append("\n"); // } // } // } // builder.append("\t\t\t<value>").append(result) // .append("</value>").append("\n") // .append("\t\t</").append(layerName).append("_feature").append(endMark) // .append("\t</").append(layerName).append("_layer").append(endMark); return builder.toString(); } /** * {@inheritDoc} */ @Override protected void nextProjectedFeature(ProjectedFeature graphic, RenderingContext2D context, SearchAreaJ2D queryArea) { final StringBuilder builder = new StringBuilder(); final FeatureMapLayer layer = graphic.getLayer(); final Feature feature = graphic.getCandidate(); final FeatureType featureType = feature.getType(); String margin = "\t"; // feature member mark builder.append(margin).append("<Feature>\n"); margin += "\t"; // featureType mark if (featureType != null) { String ftLocal = featureType.getName().tip().toString(); builder.append(margin).append("<Layer>").append(encodeXML(layer.getName())).append("</Layer>\n"); builder.append(margin).append("<Name>").append(encodeXML(ftLocal)).append("</Name>\n"); builder.append(margin).append("<ID>").append(encodeXML(feature.getIdentifier().getID())).append("</ID>\n"); complexAttributetoXML(builder, feature, margin); } else { LOGGER.warning("The feature type is null"); } // end feature member mark margin = margin.substring(1); builder.append(margin).append("</Feature>\n"); final String result = builder.toString(); if (builder.length() > 0) { final String layerName = layer.getName(); List<String> strs = features.get(layerName); if (strs == null) { strs = new ArrayList<>(); features.put(layerName, strs); } strs.add(result.substring(0, result.length())); } } protected static void complexAttributetoXML(final StringBuilder builder, final ComplexAttribute complexAtt, String margin) { for (final Property prop : complexAtt.getProperties()) { if (prop == null) { continue; } final GenericName propName = prop.getName(); if (propName == null) { continue; } String pLocal = propName.tip().toString(); if (pLocal.startsWith("@")) { pLocal = pLocal.substring(1); } if (Geometry.class.isAssignableFrom(prop.getType().getBinding())) { builder.append(margin).append('<').append(pLocal).append(">\n"); Geometry geom = (Geometry) prop.getValue(); builder.append(encodeXML(geom.toText())); builder.append(margin).append("</").append(pLocal).append(">\n"); } else { if (prop instanceof ComplexAttribute) { final ComplexAttribute complex = (ComplexAttribute) prop; builder.append(margin).append('<').append(pLocal).append(">\n"); margin += "\t"; complexAttributetoXML(builder, complex, margin); margin = margin.substring(1); builder.append(margin).append("</").append(pLocal).append(">\n"); } else { //simple final Object value = prop.getValue(); if (value != null) { final String strValue = encodeXML(value.toString()); builder.append(margin).append('<').append(pLocal).append(">") .append(strValue) .append("</").append(pLocal).append(">\n"); } else { builder.append(margin).append('<').append(pLocal).append("/>\n"); } } } } } /** * {@inheritDoc} */ @Override public Object getFeatureInfo(SceneDef sdef, ViewDef vdef, CanvasDef cdef, Rectangle searchArea, GetFeatureInfo getFI) throws PortrayalException { this.gfi = getFI; final StringBuilder builder = new StringBuilder(); final String mimeType = getFI.getInfoFormat(); //fill coverages and features maps getCandidates(sdef, vdef, cdef, searchArea, -1); builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append("\n") .append("<FeatureInfo>").append("\n"); final Map<String, List<String>> values = new HashMap<>(); values.putAll(features); values.putAll(coverages); // optimization move this filter to getCandidates Integer maxValue = getFeatureCount(getFI); if (maxValue == null) { maxValue = 1; } int cpt = 0; for (String layerName : values.keySet()) { for (final String record : values.get(layerName)) { builder.append(record); cpt++; if (cpt >= maxValue) break; } } builder.append("</FeatureInfo>"); return builder.toString(); } /** * {@inheritDoc} */ @Override public List<String> getSupportedMimeTypes() { final List<String> mimes = new ArrayList<>(); //will return map server GML mimes.add(MimeType.APP_XML); mimes.add(MimeType.TEXT_XML); return mimes; } /** * Returns the coordinates of the requested pixel in the image, expressed in the * {@linkplain CoordinateReferenceSystem crs} defined in the request. */ private GeneralDirectPosition getPixelCoordinates(final GetFeatureInfo gfi) { if (gfi != null) { JTSEnvelope2D objEnv = new JTSEnvelope2D(); int width = 0; int height = 0; int pixelX = 0; int pixelY = 0; if(gfi instanceof org.geotoolkit.wms.xml.GetFeatureInfo) { org.geotoolkit.wms.xml.GetFeatureInfo wmsGFI = (org.geotoolkit.wms.xml.GetFeatureInfo) gfi; objEnv = new JTSEnvelope2D(wmsGFI.getEnvelope2D()); width = wmsGFI.getSize().width; height = wmsGFI.getSize().height; pixelX = wmsGFI.getX(); pixelY = wmsGFI.getY(); } else if (gfi instanceof org.geotoolkit.wmts.xml.v100.GetFeatureInfo) { org.geotoolkit.wmts.xml.v100.GetFeatureInfo wmtsGFI = (org.geotoolkit.wmts.xml.v100.GetFeatureInfo) gfi; objEnv = new JTSEnvelope2D(); //gfi.getEnvelope()); width = 0; // gfi.getSize().width; height = 0; // gfi.getSize().height; pixelX = wmtsGFI.getI(); pixelY = wmtsGFI.getJ(); } final double widthEnv = objEnv.getSpan(0); final double heightEnv = objEnv.getSpan(1); final double resX = widthEnv / width; final double resY = -1 * heightEnv / height; final double geoX = (pixelX + 0.5) * resX + objEnv.getMinimum(0); final double geoY = (pixelY + 0.5) * resY + objEnv.getMaximum(1); final GeneralDirectPosition position = new GeneralDirectPosition(geoX, geoY); position.setCoordinateReferenceSystem(objEnv.getCoordinateReferenceSystem()); return position; } return null; } }