Skip to content

Commit

Permalink
[GEOS-11605]: MapML Supporting custom TCRS/gridsets
Browse files Browse the repository at this point in the history
  • Loading branch information
dromagnoli committed Nov 15, 2024
1 parent d28efe3 commit 3758e34
Show file tree
Hide file tree
Showing 31 changed files with 1,482 additions and 219 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 28 additions & 1 deletion doc/en/user/source/extensions/mapml/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,33 @@ If the ``Represent multi-layer requests as multiple elements`` is checked (and t

.. figure:: images/mapml_wms_multi_extent.png

TiledCRS
--------
MapML supports 4 built-in TiledCRS:

- MapML:WGS84 (or EPSG:4326)
- MapML:OSMTILE (or EPSG:3857)
- MapML:CBMTILE (or EPSG:3978)
- MapML:APSTILE (or EPSG:5936)

In addition, is it possible to configure custom TiledCRS based on the available WMTS GridSets.
A new MapML TCRS Settings menu is available in the GeoServer UI on the Settings section:

.. figure:: images/mapml_tcrs_menu.png


It provides a selector containing available GridSets. Administrator can select GridSets from the left list that will be converted to TiledCRSs.


.. figure:: images/mapml_tcrs_selector.png


Notes:

- Gridsets containing ":" character in the name won't be listed
- Gridsets with not-numeric levels or without a common prefix won't be listed


Styles
------

Expand Down Expand Up @@ -262,7 +289,7 @@ MapML resources will be available for any published WMS layers by making a GetMa

**SRS/CRS**

Note that the WMS SRS or CRS must be one of the projections supported by MapML:
Note that the WMS SRS or CRS must be one of the built-in projections supported by MapML or one of the TCRS configured through the dedicated section. Built-in MapML CRS are:

- MapML:WGS84 (or EPSG:4326)
- MapML:OSMTILE (or EPSG:3857)
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,13 @@
import org.geoserver.mapml.xml.Link;
import org.geoserver.mapml.xml.Mapml;
import org.geoserver.mapml.xml.Meta;
import org.geoserver.mapml.xml.ProjType;
import org.geoserver.mapml.xml.RelType;
import org.geoserver.ows.Request;
import org.geoserver.ows.URLMangler;
import org.geoserver.ows.util.ResponseUtils;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.featureinfo.FeatureTemplate;
import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.api.referencing.crs.GeodeticCRS;
import org.geotools.api.style.Style;
Expand Down Expand Up @@ -409,7 +407,7 @@ private static Set<Meta> deduceProjectionAndExtent(
if (requestCRS != null) {
responseCRS = requestCRS;
responseCRSCode = CRS.toSRS(requestCRS);
tcrs = TiledCRSConstants.lookupTCRS(responseCRSCode);
tcrs = TiledCRSConstants.lookupTCRSParams(responseCRSCode);
if (tcrs != null) {
projection.setContent(tcrs.getName());
crs = (responseCRS instanceof GeodeticCRS) ? "gcrs" : "pcrs";
Expand Down Expand Up @@ -453,7 +451,7 @@ private static String getExtent(
"top-left-easting=%1$.2f,top-left-northing=%2$.2f,bottom-right-easting=%3$.2f,bottom-right-northing=%4$.2f";
double minLong, minLat, maxLong, maxLat;
double minEasting, minNorthing, maxEasting, maxNorthing;
TiledCRSParams tcrs = TiledCRSConstants.lookupTCRS(responseCRSCode);
TiledCRSParams tcrs = TiledCRSConstants.lookupTCRSParams(responseCRSCode);
try {
if (responseCRS instanceof GeodeticCRS) {
re = r.getLatLonBoundingBox();
Expand Down Expand Up @@ -507,14 +505,10 @@ public static List<Link> alternateProjections(
ArrayList<Link> links = new ArrayList<>();
Set<String> projections = TiledCRSConstants.tiledCRSBySrsName.keySet();
projections.forEach(
(String p) -> {
(String proj) -> {
Link l = new Link();
TiledCRSParams projection = TiledCRSConstants.lookupTCRS(p);
try {
l.setProjection(ProjType.fromValue(projection.getName()));
} catch (FactoryException e) {
throw new ServiceException("Invalid TCRS name");
}
TiledCRSParams projection = TiledCRSConstants.lookupTCRSParams(proj);
l.setProjection(projection.getName());
l.setRel(RelType.ALTERNATE);
query.put("srsName", "MapML:" + projection.getName());
HashMap<String, String> kvp = new HashMap<>(query.size());
Expand All @@ -529,7 +523,6 @@ public static List<Link> alternateProjections(
base, path, kvp, URLMangler.URLType.SERVICE)));
links.add(l);
});

return links;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import javax.servlet.http.HttpServletRequest;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.mapml.xml.ProjType;
import org.geoserver.mapml.tcrs.WrappingProjType;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.Request;
import org.geoserver.ows.URLMangler;
Expand Down Expand Up @@ -83,7 +83,7 @@ protected void write(
ReferencedEnvelope projectedBbox = extractBbox(fc);
LayerInfo layerInfo = gs.getCatalog().getLayerByName(fc.getSchema().getTypeName());
CoordinateReferenceSystem crs = projectedBbox.getCoordinateReferenceSystem();
ProjType projType = parseProjType(request);
WrappingProjType projType = parseProjType(request);
double longitude;
double latitude;
ReferencedEnvelope geographicBox;
Expand Down Expand Up @@ -116,7 +116,7 @@ protected void write(
osw.flush();
}

private ProjType parseProjType(Request request) throws ServiceException {
private WrappingProjType parseProjType(Request request) throws ServiceException {
try {
Map<String, Object> rawKvp = request.getRawKvp();
String srs;
Expand All @@ -127,7 +127,7 @@ private ProjType parseProjType(Request request) throws ServiceException {
} else {
srs = "EPSG:4326";
}
return ProjType.fromValue(srs.toUpperCase());
return new WrappingProjType(srs.toUpperCase());
} catch (IllegalArgumentException | FactoryException iae) {
// figure out the parameter name (version dependent) and the actual original
// string value for the srs/crs parameter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,27 @@

import static org.apache.commons.text.StringEscapeUtils.escapeHtml4;

import java.util.HashMap;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.geoserver.mapml.tcrs.Bounds;
import org.geoserver.mapml.tcrs.Point;
import org.geoserver.mapml.tcrs.TiledCRS;
import org.geoserver.mapml.xml.ProjType;
import org.geoserver.mapml.tcrs.TiledCRSParams;
import org.geoserver.mapml.tcrs.WrappingProjType;
import org.geoserver.ows.URLMangler;
import org.geoserver.ows.util.ResponseUtils;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.proj.PROJFormattable;
import org.geotools.referencing.proj.PROJFormatter;

/** Class delegated to build an HTML Document embedding a MapML Viewer. */
public class MapMLHTMLOutput {

public static final HashMap<String, TiledCRS> PREVIEW_TCRS_MAP = new HashMap<>();

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"));
}

private String layerLabel;
private HttpServletRequest request;
private ProjType projType;
private WrappingProjType projType;
private String sourceUrL;
private int zoom = 0;
private Double latitude = 0.0;
Expand All @@ -54,7 +49,7 @@ private MapMLHTMLOutput(HTMLOutputBuilder builder) {
public static class HTMLOutputBuilder {
private String layerLabel;
private HttpServletRequest request;
private ProjType projType;
private WrappingProjType projType;
private String sourceUrL;
private ReferencedEnvelope projectedBbox;
private int zoom = 0;
Expand All @@ -72,7 +67,7 @@ public HTMLOutputBuilder setRequest(HttpServletRequest request) {
return this;
}

public HTMLOutputBuilder setProjType(ProjType projType) {
public HTMLOutputBuilder setProjType(WrappingProjType projType) {
this.projType = projType;
return this;
}
Expand Down Expand Up @@ -127,6 +122,7 @@ public String toHTML() {
.append("mapml-viewer:not(:defined) > * { display: none; } n")
.append("map-layer { display: none; }\n")
.append("</style>\n")
.append(buildProjectionScript(projType))
.append("<noscript>\n")
.append("<style>\n")
.append("mapml-viewer:not(:defined) > :not(map-layer) { display: initial; }\n")
Expand All @@ -136,7 +132,7 @@ public String toHTML() {
.append("</head>\n")
.append("<body>\n")
.append("<mapml-viewer projection=\"")
.append(projType.value())
.append(projType.getTiledCRS().getParams().getName())
.append("\" ")
.append("zoom=\"")
.append(computeZoom(projType, projectedBbox))
Expand All @@ -158,14 +154,91 @@ public String toHTML() {
return sb.toString();
}

private String buildProjectionScript(WrappingProjType projType) {
String projectionScript = "";
if (!projType.isBuiltIn()) {
StringBuilder sb = new StringBuilder();
sb.append("<script type=\"module\">\n")
.append("let customProjectionDefinition = `\n")
.append(buildDefinition(projType.getTiledCRS(), 10))
.append("let map = document.querySelector(\"mapml-viewer\");\n")
.append(
"let cProjection = map.defineCustomProjection(customProjectionDefinition);\n")
.append("map.projection = cProjection;\n")
.append("</script>");
projectionScript = sb.toString();
}
return projectionScript;
}

private String buildDefinition(TiledCRS tiledCRS, int indentChars) {
TiledCRSParams params = tiledCRS.getParams();
int tileSize = params.getTILE_SIZE();
String name = params.getName();
Point origin = params.getOrigin();
String indent = " ".repeat(indentChars);
String originString = String.format("[%.8f, %.8f]", origin.getX(), origin.getY());

double[] resolutions = params.getResolutions();
StringBuilder resolutionsString = new StringBuilder("[");
for (int i = 0; i < resolutions.length; i++) {
resolutionsString.append(resolutions[i]);
if (i != resolutions.length - 1) {
resolutionsString.append(", ");
}
}
resolutionsString.append("]");

Bounds bounds = params.getBounds();
String boundsString =
String.format(
Locale.ENGLISH,
"[[%.8f, %.8f], [%.8f, %.8f]]",
bounds.getMin().getX(),
bounds.getMin().getY(),
bounds.getMax().getX(),
bounds.getMax().getY());

CoordinateReferenceSystem crs = tiledCRS.getCRS();
PROJFormatter formatter = new PROJFormatter();
String projString = formatter.toPROJ((PROJFormattable) crs);
StringBuilder sb =
new StringBuilder("{\n")
.append("\"projection\": \"")
.append(name)
.append("\",\n")
.append(indent)
.append("\"origin\": ")
.append(originString)
.append(",\n")
.append(indent)
.append("\"resolutions\": ")
.append(resolutionsString)
.append(",\n")
.append(indent)
.append("\"bounds\": ")
.append(boundsString)
.append(",\n")
.append(indent)
.append("\"tilesize\": ")
.append(tileSize)
.append(",\n")
.append(indent)
.append("\"proj4string\" : \"")
.append(projString)
.append("\"\n")
.append("}`;\n");
return sb.toString();
}

private String buildViewerPath(HttpServletRequest request) {
String base = ResponseUtils.baseURL(request);
return ResponseUtils.buildURL(
base, "/mapml/viewer/widget/mapml.js", null, URLMangler.URLType.RESOURCE);
}

private int computeZoom(ProjType projType, ReferencedEnvelope projectedBbox) {
TiledCRS tcrs = PREVIEW_TCRS_MAP.get(projType.value());
private int computeZoom(WrappingProjType projType, ReferencedEnvelope projectedBbox) {
TiledCRS tcrs = projType.getTiledCRS();
boolean flipAxis =
CRS.getAxisOrder(projectedBbox.getCoordinateReferenceSystem())
.equals(CRS.AxisOrder.NORTH_EAST);
Expand Down
Loading

0 comments on commit 3758e34

Please sign in to comment.