Skip to content

Commit

Permalink
[GEOS-11232] Add Zoom scaled layer templates to MapML
Browse files Browse the repository at this point in the history
switched to binary search

working test for zoom input

unit tests and fix for single extent detection

GetMap tests

wms output format documentation

added some edge tests

unused var

ground resolution to scale

updated tests and fixed infinite check

Reproject bounds

- For gwc-backed GetMap request update extentZoomInput.value with
the gss.getZoomStop() to match the zoom value used by the location
inputs' min/max row and column axes.  Non-GWC backed GetMap zoom
input will not carry a value attribute, and it's not necessary

- tbd:  a test to cover it
  • Loading branch information
turingtestfail committed Feb 5, 2024
1 parent 026413c commit d864c79
Show file tree
Hide file tree
Showing 7 changed files with 497 additions and 24 deletions.
6 changes: 6 additions & 0 deletions doc/en/user/source/services/wms/outputformats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ where ``<format>`` is any of the options below.
* - KMZ
- ``format=kmz``
-
* - MapML
- ``format=text/mapml``
-
* - MapML HTML Viewer
- ``text/html; subtype=mapml``
-
* - OpenLayers
- ``format=application/openlayers``
- Generates an OpenLayers HTML application.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,16 @@
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSInfo;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.capabilities.CapabilityUtil;
import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.api.referencing.operation.TransformException;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.renderer.crs.ProjectionHandler;
import org.geotools.renderer.crs.ProjectionHandlerFinder;
import org.geotools.util.NumberRange;
import org.geotools.util.logging.Logging;
import org.geowebcache.grid.GridSubset;
import org.locationtech.jts.geom.Envelope;
Expand Down Expand Up @@ -757,23 +762,62 @@ private HeadContent prepareHead() {
for (ProjType pt : ProjType.values()) {
// skip the current proj
if (pt.equals(projType)) continue;
Link projectionLink = new Link();
projectionLink.setRel(RelType.ALTERNATE);
projectionLink.setProjection(pt);
// Copy the base params to create one for self style
Map<String, String> projParams = new HashMap<>(wmsParams);
projParams.put("crs", pt.getEpsgCode());
projParams.put("width", Integer.toString(width));
projParams.put("height", Integer.toString(height));
projParams.put("bbox", bbox);
String projURL =
ResponseUtils.buildURL(baseUrl, "wms", projParams, URLMangler.URLType.SERVICE);
projectionLink.setHref(projURL);
links.add(projectionLink);
try {
Link projectionLink = new Link();
projectionLink.setRel(RelType.ALTERNATE);
projectionLink.setProjection(pt);
// reproject the bounds
ReferencedEnvelope reprojectedBounds = reproject(projectedBox, pt);
// Copy the base params to create one for self style
Map<String, String> projParams = new HashMap<>(wmsParams);
projParams.put("crs", pt.getEpsgCode());
projParams.put("width", Integer.toString(width));
projParams.put("height", Integer.toString(height));
projParams.put("bbox", toCommaDelimitedBbox(reprojectedBounds));
String projURL =
ResponseUtils.buildURL(
baseUrl, "wms", projParams, URLMangler.URLType.SERVICE);
projectionLink.setHref(projURL);
links.add(projectionLink);
} catch (Exception e) {
// we gave it our best try but reprojection failed anyways, log and skip this link
LOGGER.log(Level.INFO, "Unable to reproject bounds for " + pt.value(), e);
}
}
return head;
}

/**
* Reproject the bounds to the target CRS
*
* @param bounds ReferencedEnvelope object
* @param pt ProjType object
* @return ReferencedEnvelope object
* @throws FactoryException In the event of a factory error.
* @throws TransformException In the event of a transform error.
*/
private ReferencedEnvelope reproject(ReferencedEnvelope bounds, ProjType pt)
throws FactoryException, TransformException {
CoordinateReferenceSystem targetCRS = 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);
ReferencedEnvelope targetBounds = null;
if (ph != null) {
List<ReferencedEnvelope> queryEnvelopes = ph.getQueryEnvelopes();
for (ReferencedEnvelope envelope : queryEnvelopes) {
if (targetBounds == null) {
targetBounds = envelope;
} else {
targetBounds.expandToInclude(envelope);
}
}
} else {
targetBounds = bounds.transform(targetCRS, true);
}
return targetBounds;
}

/**
* Create and return MapML BodyContent JAXB object
*
Expand Down Expand Up @@ -807,14 +851,39 @@ private List<Extent> prepareExtents() throws IOException {
extentList = extent.getInputOrDatalistOrLink();

// zoom
zoomInput = new Input();
zoomInput.setName("z");
zoomInput.setType(InputType.ZOOM);
zoomInput.setMin("0");
NumberRange<Double> scaleDenominators = null;
// layerInfo is null when layer is a layer group or multi layer request for multi-extent
if (!mapMLLayerMetadata.isLayerGroup() && mapMLLayerMetadata.getLayerInfo() != null) {
scaleDenominators =
CapabilityUtil.searchMinMaxScaleDenominator(
mapMLLayerMetadata.getLayerInfo());
} else if (mapMLLayerMetadata.getLayerGroupInfo() != null) {
scaleDenominators =
CapabilityUtil.searchMinMaxScaleDenominator(
mapMLLayerMetadata.getLayerGroupInfo());
}

Input extentZoomInput = new Input();
TiledCRS tiledCRS = PREVIEW_TCRS_MAP.get(projType.value());
extentZoomInput.setName("z");
extentZoomInput.setType(InputType.ZOOM);
// passing in max sld denominator to get min zoom
extentZoomInput.setMin(
scaleDenominators != null
? String.valueOf(
tiledCRS.getMinZoomForDenominator(
scaleDenominators.getMaxValue().intValue()))
: "0");
int mxz = PREVIEW_TCRS_MAP.get(projType.value()).getScales().length - 1;
zoomInput.setMax(Integer.toString(mxz));
zoomInput.setValue(Integer.toString(mxz));
extentList.add(zoomInput);
// passing in min sld denominator to get max zoom
String maxZoom =
scaleDenominators != null
? String.valueOf(
tiledCRS.getMaxZoomForDenominator(
scaleDenominators.getMinValue().intValue()))
: String.valueOf(mxz);
extentZoomInput.setMax(maxZoom);
extentList.add(extentZoomInput);

Input input;
// shard list
Expand Down Expand Up @@ -945,18 +1014,20 @@ private void generateWMTSClientLinks(MapMLLayerMetadata mapMLLayerMetadata) {
? mapMLLayerMetadata.getLayerGroupInfo()
: layerInfo.getResource());
GridSubset gss = gstl.getGridSubset(projType.value());

long[][] minMax = gss.getWMTSCoverages();
// zoom start/stop are the min/max published zoom levels
zoomInput = (Input) extentList.get(0);
// zoom value must be the same as that used to establish the axes min/max
// on location inputs, below
zoomInput.setValue(Integer.toString(gss.getZoomStop()));
zoomInput.setMin(Integer.toString(gss.getZoomStart()));
zoomInput.setMax(Integer.toString(gss.getZoomStop()));

// tilematrix inputs
Input input = new Input();
input.setName("x");
input.setType(InputType.LOCATION);
input.setUnits(UnitType.TILEMATRIX);
input.setAxis(AxisType.COLUMN);
long[][] minMax = gss.getWMTSCoverages();
input.setMin(Long.toString(minMax[minMax.length - 1][0]));
input.setMax(Long.toString(minMax[minMax.length - 1][2]));
// there's no way to specify min/max here because
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
/** @author prushforth */
public class TiledCRS {
private static final Logger LOGGER = Logging.getLogger("org.geoserver.mapml.tcrs");
public static final int SCREEN_RESOLUTION = 96;
public static final double METERS_PER_INCH = 0.0254;

private final Transformation transformation;
private final Projection projection;
Expand Down Expand Up @@ -118,6 +120,7 @@ public Bounds getTileBoundsForProjectedBounds(int zoom, Bounds projectedBounds)
new Bounds(pb.min.divideBy(TILESIZE).floor(), pb.max.divideBy(TILESIZE).floor());
return tb;
}

/**
* Transforms the point (in projected units) into a bounds centred on that point, in projected
* units, matching the size of the display bounds
Expand All @@ -143,6 +146,7 @@ public Bounds getProjectedBoundsForDisplayBounds(
public int getMaxZoom() {
return Collections.max(tileBounds.keySet());
}

/**
* Returns a zoom at which bounds when rendered at the specified display width and height will
* fit, when centered on bounds' centre point.
Expand Down Expand Up @@ -192,6 +196,7 @@ public int fitProjectedBoundsToDisplay(Bounds prjb, Bounds dsplyb) {
}
return zoom;
}

/**
* For testing purposes need to be able to set the pagesize.
*
Expand Down Expand Up @@ -241,6 +246,7 @@ public LatLng pointToLatLng(Point p, int zoom)
Point untransformedPoint = this.transformation.untransform(p, this.scales[zoom]);
return this.projection.unproject(untransformedPoint);
}

/**
* @param p * @return Point transformed from PCRS units to pixel units at zoom
* @param zoom
Expand All @@ -257,6 +263,7 @@ public Point transform(Point p, int zoom) {
public Point untransform(Point p, int zoom) {
return this.transformation.untransform(p, this.scales[zoom]);
}

/**
* @param bounds the LatLngBounds that should be transformed to projected, scaled bounds
* @param zoom the zoom scale at which to transform the bounds
Expand All @@ -270,6 +277,7 @@ public Bounds getPixelBounds(LatLngBounds bounds, int zoom)
LatLng ne = bounds.northEast;
return new Bounds(latLngToPoint(sw, zoom), latLngToPoint(ne, zoom));
}

/**
* @param bounds - projected, but not scaled bounds
* @param zoom - the scale at which to transform the bounds
Expand All @@ -280,6 +288,7 @@ public Bounds getPixelBounds(Bounds bounds, int zoom) {
Point max = this.transformation.transform(bounds.max, this.scales[zoom]).round();
return new Bounds(min, max);
}

// convenience methods

/**
Expand All @@ -301,6 +310,7 @@ public Point project(LatLng latLng) throws MismatchedDimensionException, Transfo
public LatLng unproject(Point point) throws MismatchedDimensionException, TransformException {
return this.projection.unproject(point);
}

/**
* Count the width of the *tile* bounds at the given zoom level in integral tile units.
*
Expand Down Expand Up @@ -386,8 +396,52 @@ public List<TileCoordinates> getTilesForExtent(LatLngBounds extent, int zoom, lo
}
return getTilesForExtent(pb, zoom, start);
}

// extent is in projected but not scaled units (e.g. meters)

/**
* Get the Min zoom level that matches the given denominator
*
* @param denominator the denominator to match
* @return the zoom level that matches the given denominator
*/
public Integer getMinZoomForDenominator(double denominator) {
// handles the case where denominator is larger than the largest scale or is infinity
if (denominator == Double.POSITIVE_INFINITY
|| denominator >= convertGroundResolutionToScale(scales[0])) return 0;
for (int i = 0; i < scales.length; i++) {
if (convertGroundResolutionToScale(scales[i]) <= denominator) return i;
}
return 0;
}

/**
* Get the Max zoom level that matches the given denominator
*
* @param denominator the denominator to match
* @return the zoom level that matches the given denominator
*/
public Integer getMaxZoomForDenominator(double denominator) {
// handles the case where denominator is larger than the largest scale or is infinity
if (denominator == Double.POSITIVE_INFINITY
|| convertGroundResolutionToScale(scales[scales.length - 1]) >= denominator)
return scales.length - 1;
for (int i = scales.length - 1; i >= 0; i--) {
if (convertGroundResolutionToScale(scales[i]) >= denominator) return i;
}
return scales.length - 1;
}

/**
* Convert a ground resolution to a scale
*
* @param groundResolution the ground resolution to convert
* @return the scale
*/
private Double convertGroundResolutionToScale(double groundResolution) {
return ((1 / groundResolution) * SCREEN_RESOLUTION) / METERS_PER_INCH;
}

/**
* @param extent
* @param zoom
Expand All @@ -413,6 +467,7 @@ public Bounds getBounds() {
public Point getOrigin() {
return TILE_ORIGIN;
}

/**
* Compares two tile coordinates and ranks them by distance from the constructed center point.
*/
Expand Down
Loading

0 comments on commit d864c79

Please sign in to comment.