Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supporting TCRS configuration through gridset selector #375

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -506,30 +504,27 @@ public static List<Link> alternateProjections(
String base, String path, Map<String, Object> query) {
ArrayList<Link> links = new ArrayList<>();
Set<String> projections = TiledCRSConstants.tiledCRSBySrsName.keySet();
projections.forEach(
(String p) -> {
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");
}
l.setRel(RelType.ALTERNATE);
query.put("srsName", "MapML:" + projection.getName());
HashMap<String, String> kvp = new HashMap<>(query.size());
query.keySet()
.forEach(
key -> {
kvp.put(key, query.getOrDefault(key, "").toString());
});
l.setHref(
ResponseUtils.urlDecode(
ResponseUtils.buildURL(
base, path, kvp, URLMangler.URLType.SERVICE)));
links.add(l);
});

for (String proj : projections) {
Link l = new Link();
TiledCRSParams projection = TiledCRSConstants.lookupTCRSParams(proj);
dromagnoli marked this conversation as resolved.
Show resolved Hide resolved
boolean addMe = false;
l.setProjection(projection.getName());
addMe = true;

if (!addMe) continue;
dromagnoli marked this conversation as resolved.
Show resolved Hide resolved
l.setRel(RelType.ALTERNATE);
query.put("srsName", "MapML:" + projection.getName());
HashMap<String, String> kvp = new HashMap<>(query.size());
query.keySet()
.forEach(
key -> {
kvp.put(key, query.getOrDefault(key, "").toString());
});
l.setHref(
ResponseUtils.urlDecode(
ResponseUtils.buildURL(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,26 @@

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.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 +48,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 +66,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 +121,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 +131,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 +153,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(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
Loading