Skip to content

Commit

Permalink
Merge pull request geoserver#4847 from Maps4HTML/MapML-GetFeature
Browse files Browse the repository at this point in the history
[GEOS-9965] Add support for MapML CRS authority in GetFeature responses
  • Loading branch information
prushforth authored Mar 10, 2021
2 parents c273a14 + abec769 commit ea8f44a
Show file tree
Hide file tree
Showing 8 changed files with 478 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
import net.opengis.wfs.impl.GetFeatureTypeImpl;
import net.opengis.wfs.impl.QueryTypeImpl;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.MetadataMap;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.mapml.tcrs.TiledCRSConstants;
import org.geoserver.mapml.tcrs.TiledCRSParams;
import org.geoserver.mapml.xml.BodyContent;
import org.geoserver.mapml.xml.Extent;
import org.geoserver.mapml.xml.Feature;
import org.geoserver.mapml.xml.HeadContent;
import org.geoserver.mapml.xml.Input;
import org.geoserver.mapml.xml.InputType;
import org.geoserver.mapml.xml.Link;
import org.geoserver.mapml.xml.Mapml;
import org.geoserver.mapml.xml.Meta;
Expand All @@ -32,7 +36,12 @@
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.feature.FeatureCollection;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.referencing.CRS;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeodeticCRS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

Expand Down Expand Up @@ -90,6 +99,7 @@ protected void write(
meta.setHttpEquiv("Content-Type");
meta.setContent(MapMLConstants.MAPML_MIME_TYPE); // ;projection=" + projType.value());
metas.add(meta);
metas.addAll(deduceProjectionAndExtent(getFeature, layerInfo));
List<Link> links = head.getLinks();

String licenseLink = layerMeta.get("mapml.licenseLink", String.class);
Expand All @@ -111,19 +121,6 @@ protected void write(
// build the body
BodyContent body = new BodyContent();
mapml.setBody(body);
Extent extent = new Extent();
body.setExtent(extent);
// extent.setUnits(projType);
List<Object> extentList = extent.getInputOrDatalistOrLink();

// zoom
Input input = new Input();
input.setName("z");
input.setType(InputType.ZOOM);
input.setValue("0");
input.setMin("0");
input.setMax("0");
extentList.add(input);

List<Feature> features = body.getFeatures();
try (SimpleFeatureIterator iterator = fc.features()) {
Expand All @@ -141,4 +138,149 @@ protected void write(
mapmlMarshaller.marshal(mapml, result);
osw.flush();
}

/**
* @param getFeature the GetFeature operation itself, from which we obtain srsName
* @param layerInfo metadata for the feature class
* @return
*/
private Set<Meta> deduceProjectionAndExtent(Operation getFeature, LayerInfo layerInfo) {
Set<Meta> metas = new HashSet<>();
TiledCRSParams tcrs = null;
CoordinateReferenceSystem sourceCRS = layerInfo.getResource().getCRS();
CoordinateReferenceSystem responseCRS = sourceCRS;
String sourceCRSCode = CRS.toSRS(sourceCRS);
String responseCRSCode = sourceCRSCode;
Meta projection = new Meta();
Meta extent = new Meta();
Meta coordinateSystem = new Meta();
coordinateSystem.setName("cs");
extent.setName("extent");
projection.setName("projection");
String cite = Citations.fromName("MapML").getTitle().toString();
String crs;
try {
URI srsName = getSrsNameFromOperation(getFeature);
if (srsName != null) {
String code = srsName.toString();
responseCRS = CRS.decode(code);
responseCRSCode = CRS.toSRS(CRS.decode(code));
tcrs = lookupTCRS(responseCRSCode);
if (tcrs != null) {
projection.setContent(tcrs.getName());
crs = (responseCRS instanceof GeodeticCRS) ? "gcrs" : "pcrs";
coordinateSystem.setContent(crs);
}
}
} catch (Exception e) {
}
// if tcrs is not set, either exception encountered deciphering the
// response CRS or the requested projection is not known to MapML.
if (tcrs == null) {
// this crs has no TCRS match, so if it's a gcrs, tag it with the
// "MapML" CRS 'authority'
// so that nobody can be surprised by x,y axis order in WGS84 data
crs = (responseCRS instanceof GeodeticCRS) ? "gcrs" : "pcrs";
projection.setContent(
crs.equalsIgnoreCase("grcrs") ? cite + ":" : "" + responseCRSCode);
coordinateSystem.setContent(crs);
}
extent.setContent(getExtent(layerInfo, responseCRSCode, responseCRS));
metas.add(projection);
metas.add(coordinateSystem);
metas.add(extent);
return metas;
}
/**
* @param crsCode - an official CRS code / srsName to look up
* @return the TCRS corresponding to the crsCode, long or short, or null if not found
*/
private TiledCRSParams lookupTCRS(String crsCode) {
return TiledCRSConstants.tiledCRSDefinitions.getOrDefault(
crsCode, TiledCRSConstants.tiledCRSBySrsName.get(crsCode));
}

/**
* @param getFeature the Operation from which we get the srsName of the request
* @return the EPSG / whatever code that was requested, or null if not available
*/
private URI getSrsNameFromOperation(Operation getFeature) {
URI srsName = null;
try {
boolean wfs1 = getFeature.getService().getVersion().toString().startsWith("1");
srsName =
wfs1
? ((QueryTypeImpl)
((GetFeatureTypeImpl) getFeature.getParameters()[0])
.getQuery()
.get(0))
.getSrsName()
: ((net.opengis.wfs20.impl.QueryTypeImpl)
((net.opengis.wfs20.impl.GetFeatureTypeImpl)
getFeature.getParameters()[0])
.getAbstractQueryExpressionGroup()
.get(0)
.getValue())
.getSrsName();
} catch (Exception e) {
}
return srsName;
}

/**
* @param layerInfo source of bbox info
* @param responseCRSCode output CRS code
* @param responseCRS used to transform source bbox to, if necessary
* @return
*/
private String getExtent(
LayerInfo layerInfo, String responseCRSCode, CoordinateReferenceSystem responseCRS) {
String extent = "";
ResourceInfo r = layerInfo.getResource();
ReferencedEnvelope re;
String gcrsFormat =
"top-left-longitude=%1$.6f,top-left-latitude=%2$.6f,bottom-right-longitude=%3$.6f,bottom-right-latitude=%4$.6f";
String pcrsFormat =
"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 = lookupTCRS(responseCRSCode);
try {
if (responseCRS instanceof GeodeticCRS) {
re = r.getLatLonBoundingBox();
minLong = re.getMinX();
minLat = re.getMinY();
maxLong = re.getMaxX();
maxLat = re.getMaxY();
extent = String.format(gcrsFormat, minLong, maxLat, maxLong, minLat);
} else {
re = r.boundingBox().transform(responseCRS, true);
minEasting = re.getMinX();
minNorthing = re.getMinY();
maxEasting = re.getMaxX();
maxNorthing = re.getMaxY();
extent =
String.format(pcrsFormat, minEasting, maxNorthing, maxEasting, minNorthing);
}
} catch (Exception e) {
if (tcrs != null) {
if (tcrs.getName().equalsIgnoreCase("WGS84")) {
minLong = tcrs.getBounds().getMin().x;
minLat = tcrs.getBounds().getMin().y;
maxLong = tcrs.getBounds().getMax().x;
maxLat = tcrs.getBounds().getMax().y;
extent = String.format(gcrsFormat, maxLong, maxLat, minLong, minLat);
} else {
minEasting = tcrs.getBounds().getMin().x;
minNorthing = tcrs.getBounds().getMin().y;
maxEasting = tcrs.getBounds().getMax().x;
maxNorthing = tcrs.getBounds().getMax().y;
extent =
String.format(
pcrsFormat, minEasting, maxNorthing, maxEasting, minNorthing);
}
}
}
return extent;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package org.geoserver.mapml.tcrs;

import java.util.HashMap;
import org.geotools.referencing.CRS;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;

/** @author prushforth */
Expand All @@ -14,9 +16,18 @@ public class TiledCRSConstants {
/** */
public static final HashMap<String, TiledCRSParams> tiledCRSDefinitions = new HashMap<>();

public static final HashMap<String, TiledCRSParams> tiledCRSBySrsName = new HashMap<>();

static {
final String WGS84_NAME = "WGS84";
final String WGS84_CODE = "urn:ogc:def:crs:OGC:1.3:CRS84";
final CoordinateReferenceSystem CRS_WGS84;
String WGS84_SRSNAME = null;
try {
CRS_WGS84 = CRS.decode(WGS84_CODE);
WGS84_SRSNAME = CRS.toSRS(CRS_WGS84);
} catch (Exception e) {
}
final Bounds WGS84_BOUNDS =
new Bounds(new Point(-180.0D, -90.0D), new Point(180.0D, 90.0D));
final int WGS84_TILE_SIZE = 256;
Expand Down Expand Up @@ -55,9 +66,17 @@ public class TiledCRSConstants {
WGS84_TILE_SIZE,
WGS84_TILE_ORIGIN,
WGS84_SCALES));
tiledCRSBySrsName.put(WGS84_SRSNAME, tiledCRSDefinitions.get(WGS84_NAME));

final String OSMTILE_NAME = "OSMTILE";
final String OSMTILE_CODE = "urn:x-ogc:def:crs:EPSG:3857";
final CoordinateReferenceSystem CRS_OSMTILE;
String OSMTILE_SRSNAME = null;
try {
CRS_OSMTILE = CRS.decode(OSMTILE_CODE);
OSMTILE_SRSNAME = CRS.toSRS(CRS_OSMTILE);
} catch (Exception e) {
}
Projection proj = new Projection(OSMTILE_CODE);
final Bounds OSMTILE_BOUNDS;
try {
Expand Down Expand Up @@ -101,9 +120,17 @@ public class TiledCRSConstants {
OSMTILE_TILE_SIZE,
OSMTILE_TILE_ORIGIN,
OSMTILE_SCALES));
tiledCRSBySrsName.put(OSMTILE_SRSNAME, tiledCRSDefinitions.get(OSMTILE_NAME));

final String CBMTILE_NAME = "CBMTILE";
final String CBMTILE_CODE = "urn:x-ogc:def:crs:EPSG:3978";
final CoordinateReferenceSystem CRS_CBMTILE;
String CBMTILE_SRSNAME = null;
try {
CRS_CBMTILE = CRS.decode(CBMTILE_CODE);
CBMTILE_SRSNAME = CRS.toSRS(CRS_CBMTILE);
} catch (Exception e) {
}
final Bounds CBMTILE_BOUNDS =
new Bounds(new Point(-3.46558E7D, -3.9E7D), new Point(1.0E7D, 3.931E7D));
// new Bounds(new Point(-4282638.06150141,-5153821.09213678),new
Expand Down Expand Up @@ -147,10 +174,18 @@ public class TiledCRSConstants {
CBMTILE_TILE_SIZE,
CBMTILE_TILE_ORIGIN,
CBMTILE_SCALES));
tiledCRSBySrsName.put(CBMTILE_SRSNAME, tiledCRSDefinitions.get(CBMTILE_NAME));

/* Arctic Polar Stereographic, origin and scales defined by map service at http://maps8.arcgisonline.com/arcgis/rest/services/Arctic_Polar_Ocean_Base/MapServer */
final String APSTILE_NAME = "APSTILE";
final String APSTILE_CODE = "urn:x-ogc:def:crs:EPSG:5936";
final CoordinateReferenceSystem CRS_APSTILE;
String APSTILE_SRSNAME = null;
try {
CRS_APSTILE = CRS.decode(APSTILE_CODE);
APSTILE_SRSNAME = CRS.toSRS(CRS_APSTILE);
} catch (Exception e) {
}
final Bounds APSTILE_BOUNDS =
new Bounds(
new Point(-28567784.109254867D, -28567784.109254755D),
Expand Down Expand Up @@ -189,5 +224,6 @@ public class TiledCRSConstants {
APSTILE_TILE_SIZE,
APSTILE_TILE_ORIGIN,
APSTILE_SCALES));
tiledCRSBySrsName.put(APSTILE_SRSNAME, tiledCRSDefinitions.get(APSTILE_NAME));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ export class MapViewer extends HTMLElement {
}
}

if (!this.controlslist.toLowerCase().includes("nolayer") && !this._layerControl){
if (!this.controlslist.toLowerCase().includes("nolayer") && !this._layerControl && this.layers.length > 0){
this._layerControl = M.mapMlLayerControl(null,{"collapsed": true, mapEl: this}).addTo(this._map);
//if this is the initial setup the layers dont need to be readded, causes issues if they are
if(!setup){
Expand Down
21 changes: 21 additions & 0 deletions src/extension/mapml/src/main/resources/viewer/widget/mapml.css
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,24 @@ summary {
pointer-events: none;
position: relative;
}

.mapml-control-summary-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}

.mapml-layer-remove-button {
margin-left: 15px;
text-decoration: none;
}

/* Force printers to include background-images of controls for printing.
(https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/294) */
@media print {
.leaflet-control {
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
}
Loading

0 comments on commit ea8f44a

Please sign in to comment.