Skip to content

Commit

Permalink
GEOS-11461: Support MapML embedded viewer on HTML for WFS GetFeature
Browse files Browse the repository at this point in the history
  • Loading branch information
dromagnoli committed Jul 10, 2024
1 parent 2a202ea commit 0d4acd5
Show file tree
Hide file tree
Showing 12 changed files with 626 additions and 111 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions doc/en/user/source/extensions/mapml/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,8 @@ MapML visualization is supported by the Web-Map-Custom-Element project. The MapM
</html>

In the above example, the place-holders ``topp:states``, ``localhost:8080``, ``osmtile``, and ``population`` would need to be replaced with the appropriate values, and/or the ``style`` parameter could be removed entirely from the URL if not needed. You may also like to "View Source" on the preview page to see what the markup looks like for any layer. This code can be copied and pasted without harm, and you should try it and see what works and what the limitations are. For further information about MapML, and the Maps for HTML Community Group, please visit http://maps4html.org.

In addition the MapML viewer is also available as output of a WFS GetFeature request. Select the "text/html; subtype=mapml" from the dropdown as shown below:

.. figure:: images/mapml_wfs_format_dropdown.png

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import org.geoserver.mapml.tcrs.Bounds;
import org.geoserver.mapml.tcrs.Point;
import org.springframework.http.MediaType;

/**
Expand All @@ -31,6 +33,9 @@ public final class MapMLConstants {
/** format name */
public static final String FORMAT_NAME = "MAPML";

/** format name needed to have a parseable format in WFS 1.0.0 capabilities */
public static final String HTML_FORMAT_NAME = "MAPML-HTML";

/** MapML format option enabling features */
public static final String MAPML_FEATURE_FO = "mapmlfeatures";

Expand Down Expand Up @@ -113,6 +118,8 @@ public final class MapMLConstants {
public static final String REL_LICENSE = "license";

public static final List<String> ZOOM_RELS = Arrays.asList(REL_ZOOMIN, REL_ZOOMOUT);
public static final Bounds DISPLAY_BOUNDS_DESKTOP_LANDSCAPE =
new Bounds(new Point(0, 0), new Point(768, 1024));

public static int PAGESIZE = 100;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
import org.geoserver.gwc.GWC;
import org.geoserver.gwc.layer.GeoServerTileLayer;
import org.geoserver.mapml.tcrs.Bounds;
import org.geoserver.mapml.tcrs.Point;
import org.geoserver.mapml.tcrs.TiledCRS;
import org.geoserver.mapml.xml.AxisType;
import org.geoserver.mapml.xml.Base;
Expand Down Expand Up @@ -92,11 +91,8 @@
/** Builds a MapML document from a WMSMapContent object */
public class MapMLDocumentBuilder {
private static final Logger LOGGER = Logging.getLogger(MapMLDocumentBuilder.class);
private static final Bounds DISPLAY_BOUNDS_DESKTOP_LANDSCAPE =
new Bounds(new Point(0, 0), new Point(768, 1024));

private static final Pattern ALL_COMMAS = Pattern.compile("^,+$");
public static final HashMap<String, TiledCRS> PREVIEW_TCRS_MAP = new HashMap<>();

/**
* The key for the metadata entry that controls whether a multi-layer request is rendered as a
Expand Down Expand Up @@ -153,13 +149,6 @@ public class MapMLDocumentBuilder {

private Boolean isMultiExtent = MAPML_MULTILAYER_AS_MULTIEXTENT_DEFAULT;

static {
PREVIEW_TCRS_MAP.put("OSMTILE", new TiledCRS("OSMTILE"));
PREVIEW_TCRS_MAP.put("CBMTILE", new TiledCRS("CBMTILE"));
PREVIEW_TCRS_MAP.put("APSTILE", new TiledCRS("APSTILE"));
PREVIEW_TCRS_MAP.put("WGS84", new TiledCRS("WGS84"));
}

/**
* Constructor
*
Expand Down Expand Up @@ -482,7 +471,7 @@ private String layersToLabel(List<RawLayer> layers) {
private ReferencedEnvelope layersToBBBox(List<RawLayer> layers, ProjType projType) {

ReferencedEnvelope bbbox;
bbbox = new ReferencedEnvelope(PREVIEW_TCRS_MAP.get(projType.value()).getCRS());
bbbox = new ReferencedEnvelope(MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getCRS());
for (int i = 0; i < layers.size(); i++) {
RawLayer layer = layers.get(i);
try {
Expand All @@ -495,15 +484,18 @@ private ReferencedEnvelope layersToBBBox(List<RawLayer> layers, ProjType projTyp
if (i == 0) {
bbbox =
layerBbbox.transform(
PREVIEW_TCRS_MAP.get(projType.value()).getCRS(), true);
MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getCRS(),
true);
} else {
bbbox.expandToInclude(
layerBbbox.transform(
PREVIEW_TCRS_MAP.get(projType.value()).getCRS(), true));
MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getCRS(),
true));
}
} catch (Exception e) {
// get the default max/min of the pcrs from the TCRS
Bounds defaultBounds = PREVIEW_TCRS_MAP.get(projType.value()).getBounds();
Bounds defaultBounds =
MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getBounds();
double x1, x2, y1, y2;
x1 = defaultBounds.getMin().x;
x2 = defaultBounds.getMax().x;
Expand All @@ -512,7 +504,11 @@ private ReferencedEnvelope layersToBBBox(List<RawLayer> layers, ProjType projTyp
// use the bounds of the TCRS as the default bounds for this layer
bbbox =
new ReferencedEnvelope(
x1, x2, y1, y2, PREVIEW_TCRS_MAP.get(projType.value()).getCRS());
x1,
x2,
y1,
y2,
MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getCRS());
}
}

Expand Down Expand Up @@ -911,7 +907,7 @@ private String buildStyles() throws IOException {
*/
private ReferencedEnvelope reproject(ReferencedEnvelope bounds, ProjType pt)
throws FactoryException, TransformException {
CoordinateReferenceSystem targetCRS = PREVIEW_TCRS_MAP.get(pt.value()).getCRS();
CoordinateReferenceSystem targetCRS = MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(pt.value()).getCRS();
// leverage the rendering ProjectionHandlers to build a set of envelopes
// inside the valid area of the target CRS, and fuse them
ProjectionHandler ph = ProjectionHandlerFinder.getHandler(bounds, targetCRS, true);
Expand Down Expand Up @@ -977,7 +973,7 @@ private List<Extent> prepareExtents() throws IOException {
}

Input extentZoomInput = new Input();
TiledCRS tiledCRS = PREVIEW_TCRS_MAP.get(projType.value());
TiledCRS tiledCRS = MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value());
extentZoomInput.setName("z");
extentZoomInput.setType(InputType.ZOOM);
// passing in max sld denominator to get min zoom
Expand All @@ -987,7 +983,7 @@ private List<Extent> prepareExtents() throws IOException {
tiledCRS.getMinZoomForDenominator(
scaleDenominators.getMaxValue().intValue()))
: "0");
int mxz = PREVIEW_TCRS_MAP.get(projType.value()).getScales().length - 1;
int mxz = MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getScales().length - 1;
// passing in min sld denominator to get max zoom
String maxZoom =
scaleDenominators != null
Expand Down Expand Up @@ -1229,20 +1225,24 @@ private void generateTiledWMSClientLinks(MapMLLayerMetadata mapMLLayerMetadata)
// of WGS84 is a cartesian cs per the table on this page:
// https://docs.geotools.org/stable/javadocs/org/opengis/referencing/cs/package-summary.html#AxisNames
// input.setAxis(previewTcrsMap.get(projType.value()).getCRS(UnitType.PCRS).getAxisByDirection(AxisDirection.DISPLAY_RIGHT));
bbbox = new ReferencedEnvelope(PREVIEW_TCRS_MAP.get(projType.value()).getCRS());
bbbox =
new ReferencedEnvelope(
MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getCRS());
LayerInfo layerInfo = mapMLLayerMetadata.getLayerInfo();

try {
bbbox =
mapMLLayerMetadata.isLayerGroup()
? mapMLLayerMetadata.getLayerGroupInfo().getBounds()
: layerInfo.getResource().boundingBox();
bbbox = bbbox.transform(PREVIEW_TCRS_MAP.get(projType.value()).getCRS(), true);
bbbox =
bbbox.transform(
MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getCRS(), true);
} catch (Exception e) {
// sometimes, when the geographicBox is right to 90N or 90S, in epsg:3857,
// the transform method will throw. In that case, use the
// bounds of the TCRS to define the geographicBox for the layer
TiledCRS t = PREVIEW_TCRS_MAP.get(projType.value());
TiledCRS t = MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value());
double x1 = t.getBounds().getMax().x;
double y1 = t.getBounds().getMax().y;
double x2 = t.getBounds().getMin().x;
Expand Down Expand Up @@ -1365,7 +1365,9 @@ public void generateWMSClientLinks(MapMLLayerMetadata mapMLLayerMetadata) {
try {
// initialization is necessary so as to set the PCRS to which
// the resource's geographicBox will be transformed, below.
bbbox = new ReferencedEnvelope(PREVIEW_TCRS_MAP.get(projType.value()).getCRS());
bbbox =
new ReferencedEnvelope(
MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getCRS());
bbbox =
mapMLLayerMetadata.isLayerGroup
? mapMLLayerMetadata.getLayerGroupInfo().getBounds()
Expand All @@ -1378,10 +1380,13 @@ public void generateWMSClientLinks(MapMLLayerMetadata mapMLLayerMetadata) {
// the projectedBox.transform will leave the CRS set to that of whatever
// was returned by layerInfo.getResource().boundingBox() or
// layerGroupInfo.getBounds(), above.
bbbox = bbbox.transform(PREVIEW_TCRS_MAP.get(projType.value()).getCRS(), true);
bbbox =
bbbox.transform(
MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getCRS(), true);
} catch (Exception e) {
// get the default max/min of the pcrs from the TCRS
Bounds defaultBounds = PREVIEW_TCRS_MAP.get(projType.value()).getBounds();
Bounds defaultBounds =
MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getBounds();
double x1, x2, y1, y2;
x1 = defaultBounds.getMin().x;
x2 = defaultBounds.getMax().x;
Expand All @@ -1390,7 +1395,11 @@ public void generateWMSClientLinks(MapMLLayerMetadata mapMLLayerMetadata) {
// use the bounds of the TCRS as the default bounds for this layer
bbbox =
new ReferencedEnvelope(
x1, x2, y1, y2, PREVIEW_TCRS_MAP.get(projType.value()).getCRS());
x1,
x2,
y1,
y2,
MapMLHTMLOutput.PREVIEW_TCRS_MAP.get(projType.value()).getCRS());
}
}

Expand Down Expand Up @@ -1662,7 +1671,6 @@ public String getMapMLHTMLDocument() {
Double longitude = 0.0;
ReferencedEnvelope projectedBbox = this.projectedBox;
ReferencedEnvelope geographicBox = new ReferencedEnvelope(DefaultGeographicCRS.WGS84);
TiledCRS tcrs = PREVIEW_TCRS_MAP.get(projType.value());
for (MapMLLayerMetadata mapMLLayerMetadata : mapMLLayerMetadataList) {
layer += mapMLLayerMetadata.getLayerName() + ",";
styleName += mapMLLayerMetadata.getStyleName() + ",";
Expand Down Expand Up @@ -1708,77 +1716,26 @@ public String getMapMLHTMLDocument() {
if (ALL_COMMAS.matcher(cqlFilter).matches()) {
cqlFilter = "";
}
final Bounds pb =
new Bounds(
new Point(projectedBbox.getMinX(), projectedBbox.getMinY()),
new Point(projectedBbox.getMaxX(), projectedBbox.getMaxY()));
// allowing for the data to be displayed at 1024x768 pixels, figure out
// the zoom level at which the projected bounds fits into 1024x768
// in both dimensions
zoom = tcrs.fitProjectedBoundsToDisplay(pb, DISPLAY_BOUNDS_DESKTOP_LANDSCAPE);
String base = ResponseUtils.baseURL(request);
String viewerPath =
ResponseUtils.buildURL(
base,
"/mapml/viewer/widget/mapml-viewer.js",
null,
URLMangler.URLType.RESOURCE);
StringBuilder sb = new StringBuilder();
sb.append("<!DOCTYPE html>\n")
.append("<html>\n")
.append("<head>\n")
.append("<title>")
.append(escapeHtml4(layerLabel))
.append("</title>\n")
.append("<meta charset='utf-8'>\n")
.append("<script type=\"module\" src=\"")
.append(viewerPath)
.append("\"></script>\n")
.append("<style>\n")
.append("html, body { height: 100%; }\n")
.append("* { margin: 0; padding: 0; }\n")
.append(
"mapml-viewer:defined { max-width: 100%; width: 100%; height: 100%; border: none; vertical-align: middle }\n")
.append("mapml-viewer:not(:defined) > * { display: none; } n")
.append("layer- { display: none; }\n")
.append("</style>\n")
.append("<noscript>\n")
.append("<style>\n")
.append("mapml-viewer:not(:defined) > :not(layer-) { display: initial; }\n")
.append("</style>\n")
.append("</noscript>\n")
.append("</head>\n")
.append("<body>\n")
.append("<mapml-viewer projection=\"")
.append(projType.value())
.append("\" ")
.append("zoom=\"")
.append(zoom)
.append("\" lat=\"")
.append(latitude)
.append("\" ")
.append("lon=\"")
.append(longitude)
.append("\" controls controlslist=\"geolocation\">\n")
.append("<layer- label=\"")
.append(escapeHtml4(layerLabel))
.append("\" ")
.append("src=\"")
.append(
buildGetMap(
layer,
projectedBbox,
width,
height,
escapeHtml4(proj),
styleName,
format,
cqlFilter))
.append("\" checked></layer->\n")
.append("</mapml-viewer>\n")
.append("</body>\n")
.append("</html>");
return sb.toString();
MapMLHTMLOutput htmlOutput =
new MapMLHTMLOutput.HTMLOutputBuilder()
.setSourceUrL(
buildGetMap(
layer,
projectedBbox,
width,
height,
escapeHtml4(proj),
styleName,
format,
cqlFilter))
.setProjType(projType)
.setLatitude(latitude)
.setLongitude(longitude)
.setRequest(request)
.setProjectedBbox(projectedBbox)
.setLayerLabel(layerLabel)
.build();
return htmlOutput.toHTML();
}

/** Builds the GetMap backlink to get MapML */
Expand Down
Loading

0 comments on commit 0d4acd5

Please sign in to comment.