diff --git a/doc/en/user/source/extensions/mapml/images/mapml_tile_filter.png b/doc/en/user/source/extensions/mapml/images/mapml_tile_filter.png new file mode 100644 index 00000000000..ddff2b3a0fb Binary files /dev/null and b/doc/en/user/source/extensions/mapml/images/mapml_tile_filter.png differ diff --git a/doc/en/user/source/extensions/mapml/installation.rst b/doc/en/user/source/extensions/mapml/installation.rst index 2c8ee983f92..f1779898a8f 100644 --- a/doc/en/user/source/extensions/mapml/installation.rst +++ b/doc/en/user/source/extensions/mapml/installation.rst @@ -1,3 +1,5 @@ +.. _mapml_installation: + Installation -------------------- @@ -43,7 +45,7 @@ There is also a MapML-specific global WMS setting in the *MapML Extension* secti .. figure:: images/mapml_config_wms.png -If the ``Represent multi-layer requests as multiple elements`` is checked (and the configuration is saved), an individually accessible element will be generated for each requested layer. The default is to represent the layers as a single (hidden) . +If the ``Represent multi-layer requests as multiple elements`` is checked (and the configuration is saved), in the Layer Preview an individually accessible element will be generated for each requested layer. When making a WMS request directly include the `mapmlusemultiextents:true` parameter within FORMAT_OPTIONS. The default is to represent the layers as a single (hidden) . .. figure:: images/mapml_wms_multi_extent.png @@ -109,7 +111,7 @@ Tile Settings Using tiles to access the layer can increase the performance of your web map. This is especially true if there is a tile cache mechanism in use between GeoServer and the browser client. **Use Tiles** - If the "Use Tiles" checkbox is checked, by default the output MapML will define a tile-based reference to the WMS server. Otherwise, an image-based reference will be used. If one or more of the MapML-defined GridSets is referenced by the layer or layer group in its "Tile Caching" profile, GeoServer will generate tile references instead of generating WMS GetMap URLs in the MapML document body. + If you check the "Use Tiles" checkbox and select the MapML format on the Layer Preview page, the output will use tile-based references to the WMS server. For example, if your layer or layer group has a cached tile layer configured, GeoServer will generate tile references (e.g., ) instead of WMS GetMap URLs (e.g., ). Client Requests ^^^^^^^^^^^^^^^ @@ -131,7 +133,7 @@ Vector Settings MapML supports the serving of vector feature representations of the data. This results in a smoother user navigation experience, smaller bandwidth requirements, and more options for dynamic styling on the client-side. **Use Features** - If the "Use Features" checkbox is checked, by default the output MapML will define a feature-based reference to the WMS server. Otherwise, an image-based reference will be used. Note that this option is only available for vector source data. MapML element with a feature link: + If the "Use Features" checkbox is checked, the output MapML on the Layer Preview page will define a feature-based reference to the WMS server. When making WMS request add `mapmlusefeatures:true` to the FORMAT_OPTIONS parameter. Otherwise, an image-based reference will be used. Note that this option is only available for vector source data. MapML element with a feature link: .. code-block:: html @@ -146,7 +148,7 @@ MapML supports the serving of vector feature representations of the data. This -When both "Use Tiles" and "Use Features" are checked, the MapML extension will request tiled maps in ``text/mapml`` format. +When both "Use Tiles" and "Use Features" are set in the FORMAT_OPTIONS parameter (`mapmlusefeatures:true;mapmlusetiles:true`), the MapML extension will request tiled maps in ``text/mapml`` format. The contents of the tiles will be clipped to the requested area, and feature attributes will be skiipped, as the MapML client cannot leverage them for the moment. @@ -245,11 +247,11 @@ WMS GetMap considerations By default, each layer/style pair that is requested via the GetMap parameters is composed into a single ...... structure as exemplified above. -If the 'Represent multi-layer requests as multiple elements' checkbox from the global WMS Settings page is checked as described above, a request for multiple layers or layer groups in MapML format will result in the serialization of a MapML document containing multiple elements. Each layer/style pair is represented by a element in the response. The elements are represented in the client viewer layer control settings as sub-layers, which turn on and off independently of each other, but which are controlled by the parent element's state (checked / unchecked, opacity etc) (right-click or Shift+F10 to obtain context menus): +If the FORMAT_OPTION parameter of the WMS request is configured with `mapmlusemultiextents:true`, a request for multiple layers or layer groups in MapML format on the Layer Preview page will result in the serialization of a MapML document containing multiple elements. Each layer/style pair is represented by a element in the response. The elements are represented in the client viewer layer control settings as sub-layers, which turn on and off independently of each other, but which are controlled by the parent element's state (checked / unchecked, opacity etc) (right-click or Shift+F10 to obtain context menus): .. figure:: images/mapml_wms_multi_extent.png -With 'Represent multi-layer requests as multiple elements' checked, if two or more layers are requested in MapML format via the GetMap 'layers' parameter, the MapML extension serialize each layer's according to its "Use Features" and "Use Tiles" settings. Note that there is currently no "Use Features" setting available for layer groups. +With the FORMAT_OPTION parameter of the WMS request is configured with `mapmlusemultiextents:true`, if two or more layers are requested in MapML format via the GetMap 'layers' parameter, the MapML extension serialize both layer's based on the mapmlusefeatures and mapmlusetiles settings in the FORMAT_OPTION parameter of the WMS request. If these are not set in FORMAT_OPTION they will default to false. Note that there is currently no "Use Features" support available for layer groups. Tile Caching ^^^^^^^^^^^^ @@ -265,6 +267,18 @@ in order to enable WMTS requests. .. figure:: images/mapml_tile_caching_panel_ui.png +In order to properly pass the proper multi-extent, tiling, and feature parameters to the tiling WMS requests used to populate the cache, a FORMAT_OPTIONS parameter filter (see :ref:`gwc_webadmin_layers`) must be created with appropriate default values and a regular expression that matches the MapML requests. + +The Default Value entry should look like this:: + + mapmlusemultiextents:true;mapmlusefeatures:false;mapmlusetiles:true + +Here is an example regular expression that matches the MapML FORMAT_OPTIONS parameters in any order:: + + (?i)\\b(mapmlusemultiextents|mapmlusefeatures|mapmlusetiles):\\s*(true|false)\\b(?:\\s*;\\s*(?i)\\b(mapmlusemultiextents|mapmlusefeatures|mapmlusetiles):\\s*(true|false)\\b)*(?:\\s*;\\s*(?i)\\b(mapmlusemultiextents|mapmlusefeatures|mapmlusetiles):\\s*(true|false)\\b)* + +.. figure:: images/mapml_tile_filter.png + Starting with version 2.26.x of GeoServer, Sharding support and related configuration has been removed Dimension Config diff --git a/doc/en/user/source/installation/upgrade.rst b/doc/en/user/source/installation/upgrade.rst index 4318269e443..354cd340ad6 100644 --- a/doc/en/user/source/installation/upgrade.rst +++ b/doc/en/user/source/installation/upgrade.rst @@ -30,6 +30,14 @@ The general GeoServer upgrade process is as follows: Notes on upgrading specific versions ------------------------------------ +MapML Multi-Layer As Multi-Extent Configuration (GeoServer 2.28 and newer) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As of GeoServer 2.28, the configuration option for MapML Multi-Layer as Multi-Extent has been moved from +the WMS Administration page to the Publishing tab of the Layer Group configuration. Backwards compatibility +with previously configured MapML implementations is maintained through the population of Layer Group metadata +if the option was previously enabled in the WMS Administration page. For more information, see :ref:`mapml_installation`. + FreeMarker Template Method Access (GeoServer 2.27 and newer) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLConstants.java b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLConstants.java index 87eec3a23f6..3dab29dc170 100644 --- a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLConstants.java +++ b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLConstants.java @@ -29,6 +29,23 @@ public final class MapMLConstants { /** format MediaType */ public static final MediaType MAPML_MEDIA_TYPE = new MediaType("text", "mapml", StandardCharsets.UTF_8); + /** Multilayer represented as multi-extent */ + public static final String MAPML_MULTILAYER_AS_MULTIEXTENT = "mapmlusemultiextents"; + + public static final String MAPML_USE_FEATURES_REP = "mapmlusefeatures"; + + public static final Boolean MAPML_USE_FEATURES_REP_DEFAULT = Boolean.FALSE; + + public static final String MAPML_USE_TILES_REP = "mapmlusetiles"; + + public static final Boolean MAPML_USE_TILES_REP_DEFAULT = Boolean.FALSE; + + /** + * The key for the metadata entry that controls whether a multi-layer request is rendered as a single extent or + * multiple extents. + */ + public static final Boolean MAPML_MULTILAYER_AS_MULTIEXTENT_DEFAULT = Boolean.FALSE; + /** format name */ public static final String FORMAT_NAME = "MAPML"; @@ -49,6 +66,9 @@ public final class MapMLConstants { /** MapML layer metadata use tiles */ public static final String MAPML_USE_TILES = "mapml.useTiles"; + /** MapML layer metadata use multiextent */ + public static final String MAPML_MULTIEXTENT = "mapml.multiextent"; + /** MapML layer metadata remote client request */ public static final String MAPML_USE_REMOTE = "mapml.useRemote"; diff --git a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLDocumentBuilder.java b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLDocumentBuilder.java index 9e0a23e2ce8..e5eeac6b71e 100644 --- a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLDocumentBuilder.java +++ b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLDocumentBuilder.java @@ -10,9 +10,11 @@ import static org.geoserver.mapml.MapMLConstants.MAPML_MIME_TYPE; import static org.geoserver.mapml.MapMLConstants.MAPML_SKIP_ATTRIBUTES_FO; import static org.geoserver.mapml.MapMLConstants.MAPML_SKIP_STYLES_FO; -import static org.geoserver.mapml.MapMLConstants.MAPML_USE_FEATURES; +import static org.geoserver.mapml.MapMLConstants.MAPML_USE_FEATURES_REP; +import static org.geoserver.mapml.MapMLConstants.MAPML_USE_FEATURES_REP_DEFAULT; import static org.geoserver.mapml.MapMLConstants.MAPML_USE_REMOTE; -import static org.geoserver.mapml.MapMLConstants.MAPML_USE_TILES; +import static org.geoserver.mapml.MapMLConstants.MAPML_USE_TILES_REP; +import static org.geoserver.mapml.MapMLConstants.MAPML_USE_TILES_REP_DEFAULT; import static org.geoserver.mapml.template.MapMLMapTemplate.MAPML_PREVIEW_HEAD_FTL; import static org.geoserver.mapml.template.MapMLMapTemplate.MAPML_XML_HEAD_FTL; import static org.geoserver.wms.capabilities.DimensionHelper.getDataType; @@ -60,7 +62,6 @@ import org.geoserver.catalog.WMTSStoreInfo; import org.geoserver.catalog.impl.LayerGroupStyle; import org.geoserver.catalog.util.ReaderDimensionsAccessor; -import org.geoserver.config.GeoServer; import org.geoserver.gwc.GWC; import org.geoserver.gwc.layer.GeoServerTileLayer; import org.geoserver.mapml.tcrs.Bounds; @@ -93,7 +94,6 @@ import org.geoserver.wms.GetMapRequest; import org.geoserver.wms.MapLayerInfo; import org.geoserver.wms.WMS; -import org.geoserver.wms.WMSInfo; import org.geoserver.wms.WMSMapContent; import org.geoserver.wms.capabilities.CapabilityUtil; import org.geoserver.wms.capabilities.DimensionHelper; @@ -121,13 +121,6 @@ public class MapMLDocumentBuilder { private static final Pattern ALL_COMMAS = Pattern.compile("^,+$"); - /** - * The key for the metadata entry that controls whether a multi-layer request is rendered as a single extent or - * multiple extents. - */ - public static final String MAPML_MULTILAYER_AS_MULTIEXTENT = "mapmlMultiLayerAsMultiExtent"; - - protected static final Boolean MAPML_MULTILAYER_AS_MULTIEXTENT_DEFAULT = Boolean.FALSE; public static final String MINIMUM_WIDTH_HEIGHT = "1"; private static final int BYTES_PER_PIXEL_TRANSPARENT = 4; private static final int BYTES_PER_KILOBYTE = 1024; @@ -136,7 +129,6 @@ public class MapMLDocumentBuilder { private final WMS wms; private static final String BBOX_PARAMS = "{xmin},{ymin},{xmax},{ymax}"; private static final String BBOX_PARAMS_YX = "{ymin},{xmin},{ymax},{xmax}"; - private final GeoServer geoServer; private final WMSMapContent mapContent; private final HttpServletRequest request; @@ -183,7 +175,9 @@ public class MapMLDocumentBuilder { private Mapml mapml; - private Boolean isMultiExtent = MAPML_MULTILAYER_AS_MULTIEXTENT_DEFAULT; + private Boolean isMultiExtent = MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT_DEFAULT; + private Boolean useFeatures = MapMLConstants.MAPML_USE_FEATURES_REP_DEFAULT; + private Boolean useTiles = MapMLConstants.MAPML_USE_TILES_REP_DEFAULT; private MapMLMapTemplate mapMLMapTemplate = new MapMLMapTemplate(); private boolean forceYX = false; @@ -194,9 +188,8 @@ public class MapMLDocumentBuilder { * @param wms WMS object * @param request HttpServletRequest object */ - public MapMLDocumentBuilder(WMSMapContent mapContent, WMS wms, GeoServer geoServer, HttpServletRequest request) { + public MapMLDocumentBuilder(WMSMapContent mapContent, WMS wms, HttpServletRequest request) { this.wms = wms; - this.geoServer = geoServer; this.request = request; this.mapContent = mapContent; GetMapRequest getMapRequest = mapContent.getRequest(); @@ -291,6 +284,11 @@ private Optional getFormat(GetMapRequest getMapRequest) { return Optional.ofNullable(getMapRequest.getFormatOptions().get(MapMLConstants.MAPML_WMS_MIME_TYPE_OPTION)); } + private Optional getMultiExtent(GetMapRequest getMapRequest) { + return Optional.ofNullable(Boolean.parseBoolean( + (String) getMapRequest.getFormatOptions().get(MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT))); + } + /** * Initialize fields, generate and return MapML document * @@ -309,10 +307,8 @@ public Mapml getMapMLDocument() throws ServiceException { * @throws ServiceException In the event of a service error. */ public void initialize() throws ServiceException { - WMSInfo wmsInfo = geoServer.getService(WMSInfo.class); - isMultiExtent = wmsInfo.getMetadata().get(MAPML_MULTILAYER_AS_MULTIEXTENT, Boolean.class) != null - ? wmsInfo.getMetadata().get(MAPML_MULTILAYER_AS_MULTIEXTENT, Boolean.class) - : MAPML_MULTILAYER_AS_MULTIEXTENT_DEFAULT; + isMultiExtent = Boolean.TRUE.equals( + getMultiExtent(mapContent.getRequest()).orElse(MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT_DEFAULT)); if (isMultiExtent || layers.size() == 1) { for (int i = 0; i < layers.size(); i++) { RawLayer layer = layers.get(i); @@ -354,6 +350,8 @@ public void initialize() throws ServiceException { baseUrl = ResponseUtils.baseURL(request); baseUrlPattern = baseUrl; forceYX = isYX(); + useFeatures = mapMLLayerMetadata.isUseFeatures(); + useTiles = mapMLLayerMetadata.isUseTiles(); } } @@ -409,10 +407,8 @@ private MapMLLayerMetadata layersToOneMapMLLayerMetadata(List layers) MapMLLayerMetadata mapMLLayerMetadata = new MapMLLayerMetadata(); mapMLLayerMetadata.setLayerMeta(new MetadataMap()); mapMLLayerMetadata.setUseTiles(false); - boolean useFeatures = false; if (layers.size() == 1) { - useFeatures = - useFeatures(layers.get(0), layers.get(0).getPublishedInfo().getMetadata()); + useFeatures = useFeatures(layers.get(0), mapContent.getRequest()); } mapMLLayerMetadata.setUseFeatures(useFeatures); mapMLLayerMetadata.setLayerName(layersCommaDelimited); @@ -594,9 +590,9 @@ private MapMLLayerMetadata layerToMapMLLayerMetadata(RawLayer layer, String styl cqlFilter = cql != null ? cql : ""; tileLayerExists = gwc.hasTileLayer(isLayerGroup ? layerGroupInfo : layerInfo) && gwc.getTileLayer(isLayerGroup ? layerGroupInfo : layerInfo).getGridSubset(projType.value()) != null; - boolean useTiles = Boolean.TRUE.equals(layerMeta.get(MAPML_USE_TILES, Boolean.class)); + useTiles = useTiles(layer, mapContent.getRequest()); boolean useRemote = Boolean.TRUE.equals(layerMeta.get(MAPML_USE_REMOTE, Boolean.class)); - boolean useFeatures = useFeatures(layer, layerMeta); + useFeatures = useFeatures(layer, mapContent.getRequest()); return new MapMLLayerMetadata( layerInfo, @@ -623,14 +619,38 @@ private MapMLLayerMetadata layerToMapMLLayerMetadata(RawLayer layer, String styl * Check if layer should be represented as a feature * * @param layer RawLayer - * @param layerMeta MetadataMap for layer - * @return boolean + * @param getMapRequest GetMapRequest + * @return boolean true if layer should be represented as a feature */ - private static boolean useFeatures(RawLayer layer, MetadataMap layerMeta) { - return (Boolean.TRUE.equals(layerMeta.get(MAPML_USE_FEATURES, Boolean.class))) + @SuppressWarnings("unchecked") + private static boolean useFeatures(RawLayer layer, GetMapRequest getMapRequest) { + Optional useFeaturesOptional = Optional.ofNullable(getMapRequest + .getFormatOptions() + .getOrDefault( + MAPML_USE_FEATURES_REP.toUpperCase(), + getMapRequest.getFormatOptions().get(MAPML_USE_FEATURES_REP.toLowerCase()))); + return (Boolean.parseBoolean((String) useFeaturesOptional.orElse(MAPML_USE_FEATURES_REP_DEFAULT.toString()))) && (PublishedType.VECTOR == layer.getPublishedInfo().getType()); } + /** + * Check if layer should be represented with tiles + * + * @param layer RawLayer + * @param getMapRequest GetMapRequest + * @return boolean useTiles + */ + @SuppressWarnings("unchecked") + private static boolean useTiles(RawLayer layer, GetMapRequest getMapRequest) { + Optional useTilesOptional = Optional.ofNullable(getMapRequest + .getFormatOptions() + .getOrDefault( + MAPML_USE_TILES_REP.toUpperCase(), + getMapRequest.getFormatOptions().get(MAPML_USE_TILES_REP.toLowerCase()))); + return Boolean.TRUE.equals( + Boolean.parseBoolean((String) useTilesOptional.orElse(MAPML_USE_TILES_REP_DEFAULT.toString()))); + } + /** * Match the CRS of the layer to the CRS of the bbox * @@ -712,7 +732,11 @@ private HeadContent prepareHead() throws IOException { Base base = new Base(); Map wmsParams = new HashMap<>(); wmsParams.put("format", MapMLConstants.MAPML_MIME_TYPE); - wmsParams.put("format_options", MapMLConstants.MAPML_WMS_MIME_TYPE_OPTION + ":" + imageFormat); + String formatOptions = + MapMLConstants.MAPML_WMS_MIME_TYPE_OPTION + ":" + escapeHtml4((String) format.orElse(imageFormat)) + ";" + + MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT + ":" + isMultiExtent + ";" + + MAPML_USE_TILES_REP + ":" + useTiles + ";" + MAPML_USE_FEATURES_REP + ":" + useFeatures; + wmsParams.put("format_options", formatOptions); wmsParams.put("layers", layersCommaDelimited); wmsParams.put("crs", projType.getCRSCode()); wmsParams.put("version", "1.3.0"); @@ -775,6 +799,7 @@ private HeadContent prepareHead() throws IOException { styleParams.put("width", Integer.toString(width)); styleParams.put("height", Integer.toString(height)); styleParams.put("bbox", bbox); + String url = ResponseUtils.buildURL(baseUrl, "wms", styleParams, URLMangler.URLType.SERVICE); styleLink.setHref(url); links.add(styleLink); @@ -1755,6 +1780,8 @@ public String getMapMLHTMLDocument() { String cqlFilter = ""; Double latitude = 0.0; Double longitude = 0.0; + boolean useTiles = false; + boolean useFeatures = false; ReferencedEnvelope projectedBbox = this.projectedBox; ReferencedEnvelope geographicBox = new ReferencedEnvelope(DefaultGeographicCRS.WGS84); List headerContent = getPreviewTemplates(MAPML_PREVIEW_HEAD_FTL, getFeatureTypes()); @@ -1784,6 +1811,8 @@ public String getMapMLHTMLDocument() { } catch (TransformException | FactoryException e) { throw new ServiceException("Unable to transform bbox to WGS84", e); } + useTiles = mapMLLayerMetadata.isUseTiles(); + useFeatures = mapMLLayerMetadata.isUseFeatures(); } // remove trailing commas layerLabel = layerLabel.replaceAll(",$", ""); @@ -1799,7 +1828,16 @@ public String getMapMLHTMLDocument() { } MapMLHTMLOutput htmlOutput = new MapMLHTMLOutput.HTMLOutputBuilder() .setSourceUrL(buildGetMap( - layer, projectedBbox, width, height, escapeHtml4(proj), styleName, format, cqlFilter)) + layer, + projectedBbox, + width, + height, + escapeHtml4(proj), + styleName, + format, + cqlFilter, + useTiles, + useFeatures)) .setProjType(projType) .setLatitude(latitude) .setLongitude(longitude) @@ -1892,7 +1930,9 @@ private String buildGetMap( String proj, String styleName, Optional format, - String cqlFilter) { + String cqlFilter, + boolean useTiles, + boolean useFeatures) { Map kvp = new LinkedHashMap<>(); kvp.put("LAYERS", escapeHtml4(layer)); kvp.put("BBOX", toCommaDelimitedBbox(projectedBbox)); @@ -1905,7 +1945,9 @@ private String buildGetMap( } kvp.put("FORMAT", MAPML_MIME_TYPE); String formatOptions = - MapMLConstants.MAPML_WMS_MIME_TYPE_OPTION + ":" + escapeHtml4((String) format.orElse(imageFormat)); + MapMLConstants.MAPML_WMS_MIME_TYPE_OPTION + ":" + escapeHtml4((String) format.orElse(imageFormat)) + ";" + + MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT + ":" + isMultiExtent + ";" + + MAPML_USE_TILES_REP + ":" + useTiles + ";" + MAPML_USE_FEATURES_REP + ":" + useFeatures; kvp.put("format_options", formatOptions); kvp.put("SERVICE", "WMS"); kvp.put("REQUEST", "GetMap"); @@ -2013,9 +2055,23 @@ private Map getMapRequestElementsToModel( Request request = Dispatcher.REQUEST.get(); String baseURL = ResponseUtils.baseURL(request.getHttpRequest()); String kvp = request.getKvp().entrySet().stream() - .map(p -> URLEncoder.encode(p.getKey(), StandardCharsets.UTF_8) - + "=" - + URLEncoder.encode(p.getValue().toString(), StandardCharsets.UTF_8)) + .map(entry -> { + if (entry.getValue() instanceof Map) { + Map internalMap = (Map) entry.getValue(); + String internalKvp = internalMap.entrySet().stream() + .filter(e -> !e.getKey().toString().isEmpty()) + .map(e -> URLEncoder.encode(e.getKey().toString(), StandardCharsets.UTF_8) + + "=" + + URLEncoder.encode(e.getValue().toString(), StandardCharsets.UTF_8)) + .reduce((p1, p2) -> p1 + "&" + p2) + .orElse(""); + return URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + "={" + internalKvp + "}"; + } else { + return URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + + "=" + + URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8); + } + }) .reduce((p1, p2) -> p1 + "&" + p2) .orElse(""); String path = request.getPath(); @@ -2706,4 +2762,38 @@ public static boolean isWMSOrWMTSStore(LayerInfo layerInfo) { } return false; } + + public static double calculateBestScaleDenominator( + double minx, + double miny, + double maxx, + double maxy, + List scaleDenominators, + double imageWidth, + double metersPerUnit, + double pixelSize) { + + // Calculate bounding box width + double boundingBoxWidth = maxx - minx; + + // Calculate resolution + double resolution = boundingBoxWidth / imageWidth; + + // Convert resolution to scale denominator + double scaleDenominator = resolution / pixelSize; + + // Find the closest matching scale denominator + double bestMatch = scaleDenominators.get(0); + double minDifference = Math.abs(scaleDenominator - bestMatch); + + for (double sd : scaleDenominators) { + double difference = Math.abs(scaleDenominator - sd); + if (difference < minDifference) { + minDifference = difference; + bestMatch = sd; + } + } + + return bestMatch; + } } diff --git a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLHTMLOutputFormat.java b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLHTMLOutputFormat.java index 2479522501e..8009589710e 100644 --- a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLHTMLOutputFormat.java +++ b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLHTMLOutputFormat.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.Set; import javax.servlet.http.HttpServletRequest; -import org.geoserver.config.GeoServer; import org.geoserver.ows.Dispatcher; import org.geoserver.ows.Request; import org.geoserver.platform.ServiceException; @@ -23,7 +22,6 @@ /** Handles a GetMap request that for a map in MapML HTML format. */ public class MapMLHTMLOutputFormat implements GetMapOutputFormat { private WMS wms; - private GeoServer geoServer; private final Set OUTPUT_FORMATS = Collections.unmodifiableSet(new HashSet<>(List.of(MapMLConstants.MAPML_HTML_MIME_TYPE))); static final MapProducerCapabilities MAPML_CAPABILITIES = new MapProducerCapabilities(false, true, true); @@ -33,17 +31,15 @@ public class MapMLHTMLOutputFormat implements GetMapOutputFormat { * * @param wms the WMS */ - public MapMLHTMLOutputFormat(WMS wms, GeoServer geoServer) { + public MapMLHTMLOutputFormat(WMS wms) { this.wms = wms; - this.geoServer = geoServer; } @Override public WebMap produceMap(WMSMapContent mapContent) throws ServiceException, IOException { Request request = Dispatcher.REQUEST.get(); HttpServletRequest httpServletRequest = request.getHttpRequest(); - MapMLDocumentBuilder mapMLDocumentBuilder = - new MapMLDocumentBuilder(mapContent, wms, geoServer, httpServletRequest); + MapMLDocumentBuilder mapMLDocumentBuilder = new MapMLDocumentBuilder(mapContent, wms, httpServletRequest); return new MapMLHTMLMap(mapContent, mapMLDocumentBuilder.getMapMLHTMLDocument()); } diff --git a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLLayerGroupConfigurationPanel.html b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLLayerGroupConfigurationPanel.html index a8b17f72c40..93d4623dbde 100644 --- a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLLayerGroupConfigurationPanel.html +++ b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLLayerGroupConfigurationPanel.html @@ -25,13 +25,17 @@

MapML Settings
- Tile Config + Representation Config
  • +
  • + + +
diff --git a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLLayerGroupConfigurationPanel.java b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLLayerGroupConfigurationPanel.java index 20bc5b567e7..3284c6ffda9 100644 --- a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLLayerGroupConfigurationPanel.java +++ b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLLayerGroupConfigurationPanel.java @@ -5,8 +5,10 @@ package org.geoserver.mapml; +import static org.geoserver.mapml.MapMLConstants.MAPML_MULTIEXTENT; import static org.geoserver.mapml.MapMLConstants.MAPML_USE_TILES; import static org.geoserver.mapml.MapMLLayerConfigurationPanel.getAvailableMimeTypes; +import static org.geoserver.web.demo.MapMLFormatLink.FORMAT_OPTION_DEFAULT; import java.util.logging.Logger; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -17,8 +19,10 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.model.PropertyModel; import org.geoserver.catalog.LayerGroupInfo; +import org.geoserver.web.GeoServerApplication; import org.geoserver.web.publish.PublishedConfigurationPanel; import org.geoserver.web.util.MapModel; +import org.geoserver.wms.WMSInfo; import org.geotools.util.logging.Logging; /** @@ -54,7 +58,7 @@ public MapMLLayerGroupConfigurationPanel(final String panelId, final IModel useTilesModel = new MapModel<>(new PropertyModel<>(model, METADATA), "mapml.useTiles"); + MapModel useTilesModel = new MapModel<>(new PropertyModel<>(model, METADATA), MAPML_USE_TILES); CheckBox useTiles = new CheckBox("useTiles", useTilesModel); useTiles.add(new OnChangeAjaxBehavior() { @Override @@ -66,6 +70,25 @@ protected void onUpdate(AjaxRequestTarget ajaxRequestTarget) { }); add(useTiles); + // add the checkbox to select multiextent or not + MapModel multiextentModel = new MapModel<>(new PropertyModel<>(model, METADATA), MAPML_MULTIEXTENT); + // in previous versions, the multiextent option was stored in the WMSInfo + if (multiextentModel.getObject() == null) { + WMSInfo wmsInfo = GeoServerApplication.get().getGeoServer().getService(WMSInfo.class); + boolean multiExtent = Boolean.parseBoolean( + wmsInfo.getMetadata().get(MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT) != null + ? wmsInfo.getMetadata() + .get(MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT) + .toString() + : FORMAT_OPTION_DEFAULT); + LayerGroupInfo layerGroupInfo = model.getObject(); + layerGroupInfo.getMetadata().put(MAPML_MULTIEXTENT, multiExtent); + GeoServerApplication.get().getGeoServer().getCatalog().save(layerGroupInfo); + multiextentModel.setObject(multiExtent); + } + CheckBox multiextent = new CheckBox("multiextent", multiextentModel); + add(multiextent); + MapModel mimeModel = new MapModel<>(new PropertyModel<>(model, METADATA), MapMLConstants.MAPML_MIME); boolean useTilesFromModel = Boolean.TRUE.equals(model.getObject().getMetadata().get(MAPML_USE_TILES, Boolean.class)); diff --git a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLMapOutputFormat.java b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLMapOutputFormat.java index 4ecb660804e..3b95846ca7d 100644 --- a/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLMapOutputFormat.java +++ b/src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLMapOutputFormat.java @@ -86,7 +86,7 @@ public WebMap produceMap(WMSMapContent mapContent) throws ServiceException, IOEx mapMLDocument = builder.getMapMLDocument(); } else { MapMLDocumentBuilder mapMLDocumentBuilder = - new MapMLDocumentBuilder(mapContent, wms, geoServer, request.getHttpRequest()); + new MapMLDocumentBuilder(mapContent, wms, request.getHttpRequest()); mapMLDocument = mapMLDocumentBuilder.getMapMLDocument(); } ByteArrayOutputStream bos = new ByteArrayOutputStream(); diff --git a/src/extension/mapml/src/main/java/org/geoserver/mapml/web/MapMLAdminPanel.html b/src/extension/mapml/src/main/java/org/geoserver/mapml/web/MapMLAdminPanel.html deleted file mode 100644 index e62ee788773..00000000000 --- a/src/extension/mapml/src/main/java/org/geoserver/mapml/web/MapMLAdminPanel.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - -
    -
  • -
    - - mapml - -
      -
    • -
    • - -
    • -
    -
    -
  • -
-
- - \ No newline at end of file diff --git a/src/extension/mapml/src/main/java/org/geoserver/mapml/web/MapMLAdminPanel.java b/src/extension/mapml/src/main/java/org/geoserver/mapml/web/MapMLAdminPanel.java deleted file mode 100644 index aebdc82d410..00000000000 --- a/src/extension/mapml/src/main/java/org/geoserver/mapml/web/MapMLAdminPanel.java +++ /dev/null @@ -1,34 +0,0 @@ -/* (c) 2023 Open Source Geospatial Foundation - all rights reserved - * This code is licensed under the GPL 2.0 license, available at the root - * application directory. - */ -package org.geoserver.mapml.web; - -import org.apache.wicket.Component; -import org.apache.wicket.markup.html.form.CheckBox; -import org.apache.wicket.model.IModel; -import org.geoserver.mapml.MapMLDocumentBuilder; -import org.geoserver.web.services.AdminPagePanel; -import org.geoserver.web.util.MapModel; -import org.geoserver.wms.WMSInfo; - -/** Admin panel for MapML service. */ -public class MapMLAdminPanel extends AdminPagePanel { - - private static final long serialVersionUID = -7670555379263411393L; - - /** - * Constructor - * - * @param id component id - * @param model model - */ - public MapMLAdminPanel(String id, IModel model) { - super(id, model); - WMSInfo wmsInfo = (WMSInfo) model.getObject(); - CheckBox multiextent = new CheckBox( - "multiextent", - new MapModel<>(wmsInfo.getMetadata(), MapMLDocumentBuilder.MAPML_MULTILAYER_AS_MULTIEXTENT)); - this.add(new Component[] {multiextent}); - } -} diff --git a/src/extension/mapml/src/main/java/org/geoserver/web/demo/MapMLFormatLink.java b/src/extension/mapml/src/main/java/org/geoserver/web/demo/MapMLFormatLink.java index a635cecd9cd..cfb6b75318b 100644 --- a/src/extension/mapml/src/main/java/org/geoserver/web/demo/MapMLFormatLink.java +++ b/src/extension/mapml/src/main/java/org/geoserver/web/demo/MapMLFormatLink.java @@ -9,10 +9,14 @@ import java.util.Map; import org.apache.wicket.markup.html.link.ExternalLink; import org.apache.wicket.model.StringResourceModel; +import org.geoserver.catalog.LayerInfo; +import org.geoserver.catalog.MetadataMap; +import org.geoserver.catalog.PublishedInfo; import org.geoserver.mapml.MapMLConstants; import org.geoserver.mapml.tcrs.TiledCRSConstants; import org.geoserver.mapml.tcrs.TiledCRSParams; import org.geoserver.wms.GetMapRequest; +import org.geoserver.wms.WMS; import org.geotools.api.referencing.FactoryException; import org.geotools.api.referencing.operation.TransformException; import org.geotools.geometry.jts.ReferencedEnvelope; @@ -20,6 +24,12 @@ public class MapMLFormatLink extends CommonFormatLink { + public static final String FORMAT_OPTIONS = "format_options"; + public static final String FORMAT_OPTION_TRUE = ":true"; + public static final String FORMAT_OPTION_FALSE = ":false"; + public static final String LAYERINFO_LAYERS = "layers"; + public static final String FORMAT_OPTION_DEFAULT = "false"; + @Override public ExternalLink getFormatLink(PreviewLayer layer) { String link = layer.getWmsLink(this::customizeRequest); @@ -32,9 +42,50 @@ public ExternalLink getFormatLink(PreviewLayer layer) { /** Customize the request to use the MapML format and a native MapML CRS if possible */ void customizeRequest(GetMapRequest request, Map params) { + WMS wms = WMS.get(); + PublishedInfo layerInfo; + MetadataMap metadata; + layerInfo = wms.getLayerByName(params.get(LAYERINFO_LAYERS)); + if (layerInfo == null) { + layerInfo = wms.getLayerGroupByName(params.get(LAYERINFO_LAYERS)); + metadata = layerInfo.getMetadata(); + } else { + metadata = ((LayerInfo) layerInfo).getResource().getMetadata(); + } + boolean useFeatures = Boolean.parseBoolean( + metadata.get(MapMLConstants.MAPML_USE_FEATURES) != null + ? metadata.get(MapMLConstants.MAPML_USE_FEATURES).toString() + : FORMAT_OPTION_DEFAULT); + boolean useTiles = Boolean.parseBoolean( + metadata.get(MapMLConstants.MAPML_USE_TILES) != null + ? metadata.get(MapMLConstants.MAPML_USE_TILES).toString() + : FORMAT_OPTION_DEFAULT); + boolean multiExtent = Boolean.parseBoolean( + metadata.get(MapMLConstants.MAPML_MULTIEXTENT) != null + ? metadata.get(MapMLConstants.MAPML_MULTIEXTENT).toString() + : FORMAT_OPTION_DEFAULT); // set the format params.put("format", MapMLConstants.MAPML_HTML_MIME_TYPE); - + // set the format_options + StringBuilder formatOptions = new StringBuilder(); + if (multiExtent) { + formatOptions.append(MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT).append(FORMAT_OPTION_TRUE); + } else { + formatOptions.append(MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT).append(FORMAT_OPTION_FALSE); + } + formatOptions.append(";"); + if (useFeatures) { + formatOptions.append(MapMLConstants.MAPML_USE_FEATURES_REP).append(FORMAT_OPTION_TRUE); + } else { + formatOptions.append(MapMLConstants.MAPML_USE_FEATURES_REP).append(FORMAT_OPTION_FALSE); + } + formatOptions.append(";"); + if (useTiles) { + formatOptions.append(MapMLConstants.MAPML_USE_TILES_REP).append(FORMAT_OPTION_TRUE); + } else { + formatOptions.append(MapMLConstants.MAPML_USE_TILES_REP).append(FORMAT_OPTION_FALSE); + } + params.put(FORMAT_OPTIONS, formatOptions.toString()); // check if we can use a native MapML CRS, otherwise fall back to WGS84 to // have something that can display anyways TiledCRSConstants.tiledCRSDefinitions.values().stream() diff --git a/src/extension/mapml/src/main/resources/GeoServerApplication.properties b/src/extension/mapml/src/main/resources/GeoServerApplication.properties index 934befd8cb0..e6609cfd268 100644 --- a/src/extension/mapml/src/main/resources/GeoServerApplication.properties +++ b/src/extension/mapml/src/main/resources/GeoServerApplication.properties @@ -31,12 +31,13 @@ MapMLLayerGroupConfigurationPanel.mapmlSectionHeading=MapML Settings MapMLLayerGroupConfigurationPanel.mapmlLicenseSection=License Info MapMLLayerGroupConfigurationPanel.mapmlLicenseTitle=License Title MapMLLayerGroupConfigurationPanel.mapmlLicenseLink=License Link -MapMLLayerGroupConfigurationPanel.mapmlTileSection=Tile Settings +MapMLLayerGroupConfigurationPanel.mapmlTileSection=Representation Settings MapMLLayerGroupConfigurationPanel.mapmlVectorSection=Vector Settings MapMLLayerGroupConfigurationPanel.mapmlUseFeatures=Use Features MapMLLayerGroupConfigurationPanel.mapmlUseTiles=Use Tiles MapMLLayerGroupConfigurationPanel.mapmlDefaultMimeSection=Default Mime Type Config MapMLLayerGroupConfigurationPanel.mapmlDefaultMime=Default Mime Type +MapMLLayerGroupConfigurationPanel.multiextent=Represent multi-layer requests as multiple elements MapMLTCRSPanel.title=Gridsets selection to TCRS MapMLTCRSPanel.availableHeader=Available GridSets to be selected as MapML TCRSs @@ -44,6 +45,3 @@ MapMLTCRSPanel.selectedHeader=Selected GridSets MapMLTCRSSettingsPage.title=MapML TCRS Settings MapMLTCRSSettingsPage.description=Administer Settings for MapML TCRS definitions -MapMLAdminPanel.title=MapML Settings -MapMLAdminPanel.mapml=MapML Settings -MapMLAdminPanel.multiextent=Represent multi-layer requests as multiple elements diff --git a/src/extension/mapml/src/main/resources/applicationContext.xml b/src/extension/mapml/src/main/resources/applicationContext.xml index 62e0d126785..7fc5584a1d3 100644 --- a/src/extension/mapml/src/main/resources/applicationContext.xml +++ b/src/extension/mapml/src/main/resources/applicationContext.xml @@ -84,13 +84,6 @@ - - - - - - - diff --git a/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLTestSupport.java b/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLTestSupport.java index 8480aa93298..8cd164fdafd 100644 --- a/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLTestSupport.java +++ b/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLTestSupport.java @@ -74,6 +74,9 @@ public class MapMLWMSRequest { private String height; private String format; private boolean feature; + private boolean featureRep; + private boolean multiExtent; + private boolean tile; public String getName() { return name; @@ -119,6 +122,18 @@ public boolean isFeature() { return feature; } + public boolean isFeatureRep() { + return featureRep; + } + + public boolean isMultiExtent() { + return multiExtent; + } + + public boolean isTile() { + return tile; + } + public MapMLWMSRequest cql(String cql) { this.cql = cql; return this; @@ -174,14 +189,38 @@ public MapMLWMSRequest feature(boolean feature) { return this; } + public MapMLWMSRequest featureRep(boolean featureRep) { + this.featureRep = featureRep; + return this; + } + + public MapMLWMSRequest tile(boolean tile) { + this.tile = tile; + return this; + } + + public MapMLWMSRequest multiExtent(boolean multiExtent) { + this.multiExtent = multiExtent; + return this; + } + /** Get a MapML request */ protected MockHttpServletRequest toHttpRequest() throws Exception { String path = null; cql(getCql() != null ? getCql() : ""); MockHttpServletRequest httpRequest = null; String formatOptions = isFeature() - ? MapMLConstants.MAPML_FEATURE_FO + ":true" - : getFormat() != null ? MapMLConstants.MAPML_WMS_MIME_TYPE_OPTION + ":image/png" : ""; + ? MapMLConstants.MAPML_FEATURE_FO + ":true;" + : getFormat() != null ? MapMLConstants.MAPML_WMS_MIME_TYPE_OPTION + ":image/png;" : ""; + if (isFeatureRep()) { + formatOptions += MapMLConstants.MAPML_USE_FEATURES_REP + ":true;"; + } + if (isMultiExtent()) { + formatOptions += MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT + ":true;"; + } + if (isTile()) { + formatOptions += MapMLConstants.MAPML_USE_TILES_REP + ":true;"; + } if (getKvp() != null) { path = "wms"; httpRequest = createRequest(path, getKvp()); diff --git a/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLWMSTest.java b/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLWMSTest.java index 29fa6458a71..8805585dd72 100644 --- a/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLWMSTest.java +++ b/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLWMSTest.java @@ -138,8 +138,8 @@ public void tearDown() { WMSInfo wms = geoServer.getService(WMSInfo.class); wms.getMetadata() .put( - MapMLDocumentBuilder.MAPML_MULTILAYER_AS_MULTIEXTENT, - MapMLDocumentBuilder.MAPML_MULTILAYER_AS_MULTIEXTENT_DEFAULT); + MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT, + MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT_DEFAULT); // restore default max request memory wms.setMaxRequestMemory(0); geoServer.save(wms); @@ -343,22 +343,15 @@ public void testMapMLMaxImageWidthHeight() throws Exception { @Test public void testMapMLDefaultMimeType() throws Exception { - GeoServer geoServer = getGeoServer(); - WMSInfo wms = geoServer.getService(WMSInfo.class); - wms.getMetadata().put(MapMLDocumentBuilder.MAPML_MULTILAYER_AS_MULTIEXTENT, Boolean.TRUE); - geoServer.save(wms); Catalog cat = getCatalog(); LayerInfo li = cat.getLayerByName(MockData.POLYGONS.getLocalPart()); - li.getResource().getMetadata().put(MAPML_USE_FEATURES, false); - li.getResource().getMetadata().put(MAPML_USE_TILES, false); li.getResource().getMetadata().put(MapMLConstants.MAPML_MIME, "img/jpeg"); - LayerInfo li2 = cat.getLayerByName(MockData.LINES.getLocalPart()); - li2.getResource().getMetadata().put(MAPML_USE_FEATURES, false); - li2.getResource().getMetadata().put(MAPML_USE_TILES, false); cat.save(li); - cat.save(li2); Mapml mapmlExtent = new MapMLWMSRequest() .name(MockData.POLYGONS.getLocalPart()) + .tile(false) + .featureRep(false) + .multiExtent(true) .srs("EPSG:3857") .getAsMapML(); @@ -370,6 +363,9 @@ public void testMapMLDefaultMimeType() throws Exception { imageLinksForSingle.get(0).getTref().contains("format=img/jpeg")); Mapml mapmlExtentWithTwo = new MapMLWMSRequest() .name(MockData.POLYGONS.getLocalPart() + "," + MockData.LINES.getLocalPart()) + .tile(false) + .featureRep(false) + .multiExtent(true) .srs("EPSG:3857") .getAsMapML(); List extentLinksOne = getTypeFromInputOrDataListOrLink( @@ -389,31 +385,13 @@ public void testMapMLDefaultMimeType() throws Exception { @Test public void testMapMLUseFeaturesLinks() throws Exception { - GeoServer geoServer = getGeoServer(); - WMSInfo wms = geoServer.getService(WMSInfo.class); - wms.getMetadata().put(MapMLDocumentBuilder.MAPML_MULTILAYER_AS_MULTIEXTENT, Boolean.TRUE); - geoServer.save(wms); - - Catalog cat = getCatalog(); - LayerInfo li = cat.getLayerByName(MockData.POLYGONS.getLocalPart()); - li.getResource().getMetadata().put(MAPML_USE_FEATURES, true); - li.getResource().getMetadata().put(MAPML_USE_TILES, false); - cat.save(li); - - LayerInfo li2 = cat.getLayerByName(MockData.LINES.getLocalPart()); - li2.getResource().getMetadata().put(MAPML_USE_FEATURES, true); - li2.getResource().getMetadata().put(MAPML_USE_TILES, false); - cat.save(li2); - - LayerInfo li3 = cat.getLayerByName(MockData.WORLD.getLocalPart()); - li3.getResource().getMetadata().put(MAPML_USE_FEATURES, true); - li3.getResource().getMetadata().put(MAPML_USE_TILES, false); - cat.save(li3); - Mapml mapmlExtent = new MapMLWMSRequest() .name(MockData.POLYGONS.getLocalPart() + "," + MockData.LINES.getLocalPart()) .srs("EPSG:3857") .cql("id%3D%27t0002%27;INCLUDE") + .featureRep(true) + .tile(false) + .multiExtent(true) .getAsMapML(); List extentLinks = getTypeFromInputOrDataListOrLink( @@ -429,11 +407,11 @@ public void testMapMLUseFeaturesLinks() throws Exception { "Features link tref should contain CQL_FILTER=id%3D%27t0002%27", imageLinksForSingle.get(0).getTref().contains("cql_filter=id='t0002'")); - // now we change one of the layers to not return features - li.getResource().getMetadata().put(MAPML_USE_FEATURES, false); - cat.save(li); + // now we change to not return features Mapml mapmlOneNotFeatures = new MapMLWMSRequest() .name(MockData.POLYGONS.getLocalPart() + "," + MockData.LINES.getLocalPart()) + .featureRep(false) + .multiExtent(true) .srs("EPSG:3857") .getAsMapML(); @@ -450,23 +428,11 @@ public void testMapMLUseFeaturesLinks() throws Exception { 1, imageLinksForSingleOneNotFeatures.size()); - List extentLinksOneHasFeatures = getTypeFromInputOrDataListOrLink( - mapmlOneNotFeatures.getBody().getExtents().get(1).getInputOrDatalistOrLink(), Link.class); - List featureLinksForSingleOneHasFeatures = getLinkByRelType(extentLinksOneHasFeatures, RelType.FEATURES); - assertEquals( - "Features link should be present in the second extent", 1, featureLinksForSingleOneHasFeatures.size()); - List imageLinksForSingleOneHasFeatures = getLinkByRelType(extentLinksOneHasFeatures, RelType.IMAGE); - assertEquals( - "Image link should not be present when in second extent, which does not have useFeatures", - 0, - imageLinksForSingleOneHasFeatures.size()); - // now we add a raster layer - li.getResource().getMetadata().put(MAPML_USE_FEATURES, true); - cat.save(li); Mapml mapmlOneRaster = new MapMLWMSRequest() .name("layerGroup" + "," + MockData.POLYGONS.getLocalPart() + "," + MockData.WORLD.getLocalPart()) .srs("EPSG:3857") + .featureRep(true) .getAsMapML(); List extentLinksOneRaster = getTypeFromInputOrDataListOrLink( @@ -485,16 +451,12 @@ public void testMapMLUseFeaturesLinks() throws Exception { @Test public void testCQLTiledLinks() throws Exception { - Catalog cat = getCatalog(); - LayerInfo li = cat.getLayerByName(MockData.POLYGONS.getLocalPart()); - li.getResource().getMetadata().put(MAPML_USE_FEATURES, true); - li.getResource().getMetadata().put(MAPML_USE_TILES, true); - cat.save(li); - Mapml mapmlExtent = new MapMLWMSRequest() .name(MockData.POLYGONS.getLocalPart()) .srs("EPSG:3857") .cql("id%3D%27t0002%27;INCLUDE") + .featureRep(true) + .tile(true) .getAsMapML(); List extentLinks = getTypeFromInputOrDataListOrLink( @@ -511,16 +473,13 @@ public void testCQLTiledLinks() throws Exception { public void testCQLTiledCachedLinks() throws Exception { // set up tile caching so that we can check WMTS links Catalog cat = getCatalog(); - LayerInfo li = cat.getLayerByName(MockData.POLYGONS.getLocalPart()); - li.getResource().getMetadata().put(MAPML_USE_FEATURES, true); - li.getResource().getMetadata().put(MAPML_USE_TILES, true); - cat.save(li); - enableTileCaching(MockData.POLYGONS, cat); Mapml mapmlExtent = new MapMLWMSRequest() .name(MockData.POLYGONS.getLocalPart()) .srs("EPSG:3857") + .featureRep(true) + .tile(true) .cql("id%3D%27t0002%27;INCLUDE") .getAsMapML(); @@ -536,25 +495,12 @@ public void testCQLTiledCachedLinks() throws Exception { @Test public void testMapMLMultiLayer() throws Exception { - Catalog cat = getCatalog(); - GeoServer geoServer = getGeoServer(); - WMSInfo wms = geoServer.getService(WMSInfo.class); - wms.getMetadata().put(MapMLDocumentBuilder.MAPML_MULTILAYER_AS_MULTIEXTENT, Boolean.FALSE); - geoServer.save(wms); - - LayerInfo li = cat.getLayerByName(MockData.POLYGONS.getLocalPart()); - ResourceInfo layerMeta = li.getResource(); - li.getResource().getMetadata().put("mapml.useTiles", true); - cat.save(layerMeta); - - LayerGroupInfo lgi = cat.getLayerGroupByName("layerGroup"); - lgi.getMetadata().put("mapml.useTiles", true); - cat.save(lgi); - Mapml mapmlSingleExtent = new MapMLWMSRequest() .name("layerGroup" + "," + MockData.POLYGONS.getLocalPart()) .bbox("-20000000,-20000000,20000000,20000000") .srs("EPSG:3857") + .multiExtent(false) + .tile(true) .getAsMapML(); List selfStyleLinksForSingle = @@ -617,12 +563,11 @@ public void testMapMLMultiLayer() throws Exception { reader.toString().contains("hidden")); // Change To Return Multiple Extents for Multiple Layers - wms.getMetadata().put(MapMLDocumentBuilder.MAPML_MULTILAYER_AS_MULTIEXTENT, Boolean.TRUE); - geoServer.save(wms); - Mapml mapmlMultiExtent = new MapMLWMSRequest() .name("layerGroup" + "," + MockData.POLYGONS.getLocalPart()) .srs("EPSG:3857") + .tile(true) + .multiExtent(true) .getAsMapML(); List selfStyleLinksForMulti = @@ -663,14 +608,20 @@ public void testMapMLMultiLayer() throws Exception { String mapmlString = new MapMLWMSRequest() .name("layerGroup" + "," + MockData.POLYGONS.getLocalPart()) .srs("EPSG:3857") + .multiExtent(true) + .tile(true) .getAsString(); assertFalse("For multi-extent, the extent hidden attribute should be excluded", mapmlString.contains("hidden")); StyleInfo styleInfo = getCatalog().getStyleByName("BasicPolygons"); + Catalog cat = getCatalog(); + LayerInfo li = cat.getLayerByName(MockData.POLYGONS.getLocalPart()); li.getStyles().add(styleInfo); cat.save(li); Mapml mapmlMultiExtentWithMultiStyles = new MapMLWMSRequest() .name(MockData.POLYGONS.getLocalPart() + "," + "layerGroup") .srs("EPSG:3857") + .multiExtent(true) + .tile(true) .styles("BasicPolygons,") .getAsMapML(); @@ -691,14 +642,12 @@ public void testMapMLZoomLayer() throws Exception { li.getStyles().add(cat.getStyleByName("scaleRange")); li.setDefaultStyle(cat.getStyleByName("scaleRange")); cat.save(li); - ResourceInfo layerMeta = li.getResource(); - layerMeta.getMetadata().put("mapml.useTiles", true); - cat.save(layerMeta); Mapml mapmlSingleLayer = new MapMLWMSRequest() .name(MockData.POLYGONS.getLocalPart()) .srs("EPSG:4326") .styles("scaleRange") + .tile(true) .getAsMapML(); List inputs = getTypeFromInputOrDataListOrLink( @@ -721,6 +670,7 @@ public void testMapMLZoomLayer() throws Exception { .name(MockData.POLYGONS.getLocalPart()) .srs("EPSG:4326") .styles("scaleRangeNoMax") + .tile(true) .getAsMapML(); List inputsNoMax = getTypeFromInputOrDataListOrLink( @@ -742,6 +692,7 @@ public void testMapMLZoomLayer() throws Exception { .name(MockData.POLYGONS.getLocalPart()) .srs("EPSG:4326") .styles("scaleRangeExtremes") + .tile(true) .getAsMapML(); List inputsExtremes = getTypeFromInputOrDataListOrLink( @@ -761,14 +712,12 @@ public void testMapMLZoomLayer() throws Exception { li.setDefaultStyle(cat.getStyleByName("scaleRange")); cat.save(li); - GeoServer geoServer = getGeoServer(); - WMSInfo wms = geoServer.getService(WMSInfo.class); - wms.getMetadata().put(MapMLDocumentBuilder.MAPML_MULTILAYER_AS_MULTIEXTENT, Boolean.TRUE); - geoServer.save(wms); Mapml mapmlMultiExtentWithMultiStyles = new MapMLWMSRequest() .name(MockData.POLYGONS.getLocalPart() + "," + "layerGroup") .srs("EPSG:4326") .styles("scaleRange,") + .multiExtent(true) + .tile(true) .getAsMapML(); List inputsMultiExtent = getTypeFromInputOrDataListOrLink( @@ -785,12 +734,12 @@ public void testMapMLZoomLayer() throws Exception { "10", zoomInputsMultiExtent.get(0).getMax()); - wms.getMetadata().put(MapMLDocumentBuilder.MAPML_MULTILAYER_AS_MULTIEXTENT, Boolean.FALSE); - geoServer.save(wms); Mapml mapmlSingleExtentWithMultiStyles = new MapMLWMSRequest() .name(MockData.POLYGONS.getLocalPart() + "," + "layerGroup") .srs("EPSG:4326") .styles("scaleRange,") + .tile(true) + .multiExtent(false) .getAsMapML(); List inputsSingleExtent = getTypeFromInputOrDataListOrLink( @@ -985,15 +934,11 @@ public void testGWCTilesConfiguredMapMLLayer() throws Exception { // set up mapml layer to useTiles Catalog catalog = getCatalog(); - ResourceInfo layerMeta = - catalog.getLayerByName(MockData.ROAD_SEGMENTS.getLocalPart()).getResource(); - layerMeta.getMetadata().put(MAPML_USE_TILES, true); - catalog.save(layerMeta); enableTileCaching(MockData.ROAD_SEGMENTS, catalog); // request again MockRequestResponse requestResponseJapanese = - getMockRequestResponse(layerId, null, Locale.JAPANESE, "EPSG:4326", null); + getMockRequestResponse(layerId, null, Locale.JAPANESE, "EPSG:4326", null, false, true); doc = dom(new ByteArrayInputStream(requestResponseJapanese.response.getContentAsByteArray()), true); String wmtsLayerName = layerId; @@ -1049,7 +994,9 @@ public void testGWCTilesConfiguredMapMLLayer() throws Exception { null, Locale.JAPANESE, "EPSG:3857", - null); + null, + false, + true); org.w3c.dom.Document docOsmTile = dom( new ByteArrayInputStream( @@ -1072,7 +1019,8 @@ public void testGWCTilesConfiguredMapMLLayer() throws Exception { @Test public void testGWCTiledFeatureLinks() throws Exception { String layerId = getLayerId(MockData.ROAD_SEGMENTS); - MockRequestResponse requestResponse = getMockRequestResponse(layerId, null, null, "EPSG:4326", null); + MockRequestResponse requestResponse = + getMockRequestResponse(layerId, null, null, "EPSG:4326", null, false, false); org.w3c.dom.Document doc = dom(new ByteArrayInputStream(requestResponse.response.getContentAsByteArray()), true); @@ -1081,16 +1029,11 @@ public void testGWCTiledFeatureLinks() throws Exception { // set up mapml layer to useTiles and useFeatures as well Catalog catalog = getCatalog(); - ResourceInfo layerMeta = - catalog.getLayerByName(MockData.ROAD_SEGMENTS.getLocalPart()).getResource(); - layerMeta.getMetadata().put(MAPML_USE_TILES, true); - layerMeta.getMetadata().put(MAPML_USE_FEATURES, true); - catalog.save(layerMeta); enableTileCaching(MockData.ROAD_SEGMENTS, catalog); // request again MockRequestResponse requestResponseJapanese = - getMockRequestResponse(layerId, null, Locale.JAPANESE, "EPSG:4326", null); + getMockRequestResponse(layerId, null, Locale.JAPANESE, "EPSG:4326", null, true, true); doc = dom(new ByteArrayInputStream(requestResponseJapanese.response.getContentAsByteArray()), true); String wmtsLayerName = layerId; @@ -1524,7 +1467,9 @@ public void testLargeBounds() throws Exception { null, "MapML:WGS84", null, - new ReferencedEnvelope(-180, 180, -90, 90, DefaultGeographicCRS.WGS84)); + new ReferencedEnvelope(-180, 180, -90, 90, DefaultGeographicCRS.WGS84), + false, + false); Mapml mapml = parseMapML(requestResponse); @@ -1740,12 +1685,32 @@ public Mapml testLayersAndGroupsMapML(Object l, Locale locale) throws Exception private MockRequestResponse getMockRequestResponse(String name, Map kvp, Locale locale, String srs, String styles) throws Exception { + return getMockRequestResponse(name, kvp, locale, srs, styles, false, false); + } + + private MockRequestResponse getMockRequestResponse( + String name, Map kvp, Locale locale, String srs, String styles, Boolean useFeatures, Boolean useTiles) + throws Exception { return getMockRequestResponse( - name, kvp, locale, srs, styles, new ReferencedEnvelope(0, 1, 0, 1, DefaultGeographicCRS.WGS84)); + name, + kvp, + locale, + srs, + styles, + new ReferencedEnvelope(0, 1, 0, 1, DefaultGeographicCRS.WGS84), + useFeatures, + useTiles); } private MockRequestResponse getMockRequestResponse( - String name, Map kvp, Locale locale, String srs, String styles, ReferencedEnvelope bounds) + String name, + Map kvp, + Locale locale, + String srs, + String styles, + ReferencedEnvelope bounds, + Boolean useFeatures, + Boolean useTiles) throws Exception { String path = null; MockHttpServletRequest request = null; @@ -1769,7 +1734,9 @@ private MockRequestResponse getMockRequestResponse( + "&HEIGHT=150" + "&format_options=" + MapMLConstants.MAPML_WMS_MIME_TYPE_OPTION - + ":image/png"; + + ":image/png;" + + (useTiles ? (MapMLConstants.MAPML_USE_TILES_REP + ":true;") : "") + + (useFeatures ? MapMLConstants.MAPML_USE_FEATURES_REP + ":true" : ""); request = createRequest(path); } diff --git a/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLWMTSProxyTest.java b/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLWMTSProxyTest.java index b461b8d2313..30847ae2755 100644 --- a/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLWMTSProxyTest.java +++ b/src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLWMTSProxyTest.java @@ -42,7 +42,7 @@ public class MapMLWMTSProxyTest extends MapMLBaseProxyTest { + "&HEIGHT=414" + "&format_options=" + MapMLConstants.MAPML_WMS_MIME_TYPE_OPTION - + ":image/png"; + + ":image/png;" + MapMLConstants.MAPML_USE_TILES_REP + ":true"; @BeforeClass public static void beforeClass() { @@ -106,7 +106,6 @@ public void testRemoteVsNotRemote() throws Exception { ResourceInfo layerMeta = li.getResource(); layerMeta.getMetadata().put(MapMLConstants.MAPML_USE_REMOTE, false); - layerMeta.getMetadata().put(MapMLConstants.MAPML_USE_TILES, true); cat.save(layerMeta); // get the mapml doc for the layer diff --git a/src/extension/mapml/src/test/java/org/geoserver/mapml/web/MapMLAdminPanelTest.java b/src/extension/mapml/src/test/java/org/geoserver/mapml/web/MapMLAdminPanelTest.java deleted file mode 100644 index 453685394f5..00000000000 --- a/src/extension/mapml/src/test/java/org/geoserver/mapml/web/MapMLAdminPanelTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* (c) 2023 Open Source Geospatial Foundation - all rights reserved - * This code is licensed under the GPL 2.0 license, available at the root - * application directory. - */ -package org.geoserver.mapml.web; - -import static org.junit.Assert.assertTrue; - -import org.apache.wicket.model.Model; -import org.apache.wicket.util.tester.FormTester; -import org.geoserver.mapml.MapMLDocumentBuilder; -import org.geoserver.web.ComponentBuilder; -import org.geoserver.web.FormTestPage; -import org.geoserver.web.GeoServerWicketTestSupport; -import org.geoserver.wms.WMSInfo; -import org.junit.Before; -import org.junit.Test; - -/** Test for {@link MapMLAdminPanel}. */ -public class MapMLAdminPanelTest extends GeoServerWicketTestSupport { - private WMSInfo wms; - - @Before - public void setup() { - wms = getGeoServer().getService(WMSInfo.class); - getGeoServer().save(wms); - - tester.startPage(new FormTestPage((ComponentBuilder) id -> new MapMLAdminPanel(id, new Model<>(wms)))); - } - - @Test - public void testEditBasic() { - FormTester ft = tester.newFormTester("form"); - ft.setValue("panel:multiextent", true); - ft.submit(); - - tester.assertModelValue("form:panel:multiextent", true); - assertTrue(wms.getMetadata().get(MapMLDocumentBuilder.MAPML_MULTILAYER_AS_MULTIEXTENT, Boolean.class)); - } -} diff --git a/src/extension/mapml/src/test/java/org/geoserver/mapml/web/demo/MapMLMapPreviewTest.java b/src/extension/mapml/src/test/java/org/geoserver/mapml/web/demo/MapMLMapPreviewTest.java new file mode 100644 index 00000000000..3bdd6540fb6 --- /dev/null +++ b/src/extension/mapml/src/test/java/org/geoserver/mapml/web/demo/MapMLMapPreviewTest.java @@ -0,0 +1,103 @@ +/* (c) 2025 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.mapml.web.demo; + +import static org.geoserver.mapml.MapMLConstants.MAPML_MULTIEXTENT; +import static org.geoserver.mapml.MapMLConstants.MAPML_MULTILAYER_AS_MULTIEXTENT; +import static org.geoserver.mapml.MapMLConstants.MAPML_USE_FEATURES; +import static org.geoserver.mapml.MapMLConstants.MAPML_USE_FEATURES_REP; +import static org.geoserver.mapml.MapMLConstants.MAPML_USE_TILES_REP; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import org.apache.wicket.Component; +import org.apache.wicket.MarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.ExternalLink; +import org.apache.wicket.markup.repeater.data.DataView; +import org.geoserver.catalog.Catalog; +import org.geoserver.catalog.LayerInfo; +import org.geoserver.data.test.MockData; +import org.geoserver.data.test.SystemTestData; +import org.geoserver.web.GeoServerWicketTestSupport; +import org.geoserver.web.demo.MapPreviewPage; +import org.junit.After; +import org.junit.Test; + +public class MapMLMapPreviewTest extends GeoServerWicketTestSupport { + + @Override + protected void onSetUp(SystemTestData testData) throws Exception { + super.onSetUp(testData); + } + + @After + public void tearDown() throws IOException { + Catalog cat = getCatalog(); + LayerInfo li = cat.getLayerByName(MockData.LINES.getLocalPart()); + li.getResource().getMetadata().put(MAPML_USE_FEATURES, false); + li.getResource().getMetadata().put(MAPML_MULTIEXTENT, false); + cat.save(li); + } + + @Test + public void testUrlFormatDefault() throws Exception { + assertLink( + "http://localhost/context/cgf/wms?service=WMS&version=1.1.0&request=GetMap&layers=cgf%3ALines&" + + "bbox=-97.4903565027649%2C-8.117456282619509E-4%2C-97.4871312635105%2C8.117456282619509E-4&" + + "width=768&height=384&srs=MapML%3AWGS84&styles=&format=text%2Fhtml%3B%20subtype%3Dmapml&" + + "format_options=" + + MAPML_MULTILAYER_AS_MULTIEXTENT + "%3Afalse%3B" + MAPML_USE_FEATURES_REP + "%3Afalse%3B" + + MAPML_USE_TILES_REP + "%3Afalse"); + } + + @Test + public void testUrlFormatFeature() throws Exception { + Catalog cat = getCatalog(); + LayerInfo li = cat.getLayerByName(MockData.LINES.getLocalPart()); + li.getResource().getMetadata().put(MAPML_USE_FEATURES, true); + cat.save(li); + assertLink( + "http://localhost/context/cgf/wms?service=WMS&version=1.1.0&request=GetMap&layers=cgf%3ALines&" + + "bbox=-97.4903565027649%2C-8.117456282619509E-4%2C-97.4871312635105%2C8.117456282619509E-4&" + + "width=768&height=384&srs=MapML%3AWGS84&styles=&format=text%2Fhtml%3B%20subtype%3Dmapml&" + + "format_options=" + + MAPML_MULTILAYER_AS_MULTIEXTENT + "%3Afalse%3B" + MAPML_USE_FEATURES_REP + "%3Atrue%3B" + + MAPML_USE_TILES_REP + "%3Afalse"); + } + + @Test + public void testURLFormatMulti() throws Exception { + Catalog cat = getCatalog(); + LayerInfo li = cat.getLayerByName(MockData.LINES.getLocalPart()); + li.getResource().getMetadata().put(MAPML_MULTIEXTENT, true); + cat.save(li); + assertLink( + "http://localhost/context/cgf/wms?service=WMS&version=1.1.0&request=GetMap&layers=cgf%3ALines&" + + "bbox=-97.4903565027649%2C-8.117456282619509E-4%2C-97.4871312635105%2C8.117456282619509E-4&" + + "width=768&height=384&srs=MapML%3AWGS84&styles=&format=text%2Fhtml%3B%20subtype%3Dmapml&" + + "format_options=" + + MAPML_MULTILAYER_AS_MULTIEXTENT + "%3Atrue%3B" + MAPML_USE_FEATURES_REP + "%3Afalse%3B" + + MAPML_USE_TILES_REP + "%3Afalse"); + } + + private static void assertLink(String link) { + tester.startPage(MapPreviewPage.class); + tester.assertRenderedPage(MapPreviewPage.class); + + @SuppressWarnings("unchecked") + DataView data = (DataView) tester.getComponentFromLastRenderedPage("table:listContainer:items"); + for (Component datum : data) { + MarkupContainer c = (MarkupContainer) datum; + Label l = (Label) c.get("itemProperties:2:component"); + String model = l.getDefaultModelObjectAsString(); + if ("cgf:Lines".equals(model)) { + ExternalLink mapmlLink = (ExternalLink) + c.get("itemProperties:3:component:commonFormat:3").getDefaultModelObject(); + assertEquals(link, mapmlLink.getDefaultModelObjectAsString()); + } + } + } +} diff --git a/src/extension/mapml/src/test/java/org/geoserver/web/demo/MapMLFormatLinkTest.java b/src/extension/mapml/src/test/java/org/geoserver/web/demo/MapMLFormatLinkTest.java index 9efd0ccde28..a233831b458 100644 --- a/src/extension/mapml/src/test/java/org/geoserver/web/demo/MapMLFormatLinkTest.java +++ b/src/extension/mapml/src/test/java/org/geoserver/web/demo/MapMLFormatLinkTest.java @@ -10,6 +10,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import org.geoserver.data.test.MockData; +import org.geoserver.web.GeoServerWicketTestSupport; import org.geoserver.wfs.kvp.BBoxKvpParser; import org.geoserver.wms.GetMapRequest; import org.geotools.api.referencing.FactoryException; @@ -20,7 +22,7 @@ import org.junit.BeforeClass; import org.junit.Test; -public class MapMLFormatLinkTest { +public class MapMLFormatLinkTest extends GeoServerWicketTestSupport { @BeforeClass public static void setup() { @@ -57,6 +59,7 @@ public void testIncompatibleCode() throws Exception { String epsgCode = "EPSG:32632"; Map parameters = new HashMap<>(); parameters.put("srs", epsgCode); + parameters.put("layers", MockData.LINES.getLocalPart()); GetMapRequest request = new GetMapRequest(); CoordinateReferenceSystem crs = CRS.decode(epsgCode, true); request.setCrs(crs); @@ -78,6 +81,7 @@ private void transformLink(String epsgCode, String mapMLCode) throws FactoryExce MapMLFormatLink link = new MapMLFormatLink(); Map parameters = new HashMap<>(); parameters.put("srs", epsgCode); + parameters.put("layers", MockData.LINES.getLocalPart()); GetMapRequest request = new GetMapRequest(); request.setCrs(CRS.decode(epsgCode, true)); link.customizeRequest(request, parameters);