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

IWXXM 2023-1 serialization support for AIRMETs and SIGMETs #121

Merged
Merged
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
13 changes: 9 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,27 @@
<dependency>
<groupId>fi.fmi.avi.converter</groupId>
<artifactId>fmi-avi-messageconverter</artifactId>
<version>7.0.0-beta1</version>
<version>7.0.0-beta2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>fi.fmi.xmlmodel</groupId>
<artifactId>fmi-avi-xmlmodel-iwxxm211</artifactId>
<version>1.5.0</version>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>fi.fmi.xmlmodel</groupId>
<artifactId>fmi-avi-xmlmodel-iwxxm30</artifactId>
<version>1.5.0</version>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>fi.fmi.xmlmodel</groupId>
<artifactId>fmi-avi-xmlmodel-iwxxm2023-1</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>fi.fmi.xmlmodel</groupId>
<artifactId>fmi-avi-xmlmodel-wmo-collect2014</artifactId>
<version>1.5.0</version>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>net.sf.saxon</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package fi.fmi.avi.converter.iwxxm;

import aero.aixm511full.SurfacePropertyType;
import aero.aixm511full.SurfaceType;
import aero.aixm511full.ValDistanceVerticalType;
import fi.fmi.avi.converter.ConversionException;
import fi.fmi.avi.converter.ConversionHints;
import fi.fmi.avi.model.*;
import fi.fmi.avi.model.immutable.NumericMeasureImpl;
import net.opengis.gml32.*;
import org.w3c.dom.Document;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.io.StringWriter;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;

/**
* Common functionality for serializing aviation messages into IWXXM. Uses the full AIXM 5.1.1 schema.
*/
public abstract class AbstractIWXXMAixm511FullSerializer<T extends AviationWeatherMessageOrCollection, S> extends AbstractIWXXMSerializer<T, S> {

private static JAXBContext aixm511FullJaxbContext = null;

/**
* Singleton for accessing the shared JAXBContext for IWXXM JAXB handling. Uses the full AIXM 5.1.1 schema.
* <p>
* NOTE: this can take several seconds when done for the first time after JVM start,
* needs to scan all the jars in classpath.
*
* @return the context
* @throws JAXBException if the context cannot be created
*/
public static synchronized JAXBContext getAixm511FullJAXBContext() throws JAXBException {
if (aixm511FullJaxbContext == null) {
aixm511FullJaxbContext = JAXBContext.newInstance("icao.iwxxm2023_1:aero.aixm511full:net.opengis.gml32:org.iso19139.ogc2007.gmd:org.iso19139.ogc2007.gco:org"
+ ".iso19139.ogc2007.gss:org.iso19139.ogc2007.gts:org.iso19139.ogc2007.gsr:net.opengis.om20:net.opengis.sampling:net.opengis.sampling"
+ ".spatial:wmo.metce2013:wmo.opm2013:wmo.collect2014:org.w3c.xlink11");
}
return aixm511FullJaxbContext;
}

protected Document renderXMLDocument(final Object input, final ConversionHints hints) throws ConversionException {
final StringWriter sw = new StringWriter();
try {
final Marshaller marshaller = getAixm511FullJAXBContext().createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, getSchemaInfo().getCombinedSchemaLocations());
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", getNamespaceContext());
marshaller.marshal(wrap(input, (Class<Object>) input.getClass()), sw);
return asCleanedUpXML(sw.toString(), hints);
} catch (final JAXBException e) {
throw new ConversionException("Exception in rendering to DOM", e);
}
}

protected static SurfacePropertyType createAixm511fullSurface(final Geometry geom, final String id) throws IllegalArgumentException {
SurfacePropertyType retval = null;
if (geom != null) {
retval = create(SurfacePropertyType.class, spt -> spt.setSurface(createAndWrap(SurfaceType.class, sft -> {
geom.getCrs().ifPresent(crs -> setCrsToType(sft, crs));
sft.setId(id);
final JAXBElement<SurfacePatchArrayPropertyType> spapt;
if (CircleByCenterPoint.class.isAssignableFrom(geom.getClass()) || PointGeometry.class.isAssignableFrom(geom.getClass())) {
final List<Double> centerPointCoords;
final LengthType radius;
if (CircleByCenterPoint.class.isAssignableFrom(geom.getClass())) {
final CircleByCenterPoint cbcp = (CircleByCenterPoint) geom;
centerPointCoords = cbcp.getCenterPointCoordinates();
radius = asMeasure(cbcp.getRadius(), LengthType.class);
} else {
//Create a zero-radius circle if a point geometry is given
final PointGeometry point = (PointGeometry) geom;
centerPointCoords = point.getCoordinates();
radius = asMeasure(NumericMeasureImpl.of(0.0, "[nmi_i]"), LengthType.class);
}

final JAXBElement<PolygonPatchType> ppt = createAndWrap(PolygonPatchType.class, poly -> poly.setExterior(
create(AbstractRingPropertyType.class, arpt -> arpt.setAbstractRing(createAndWrap(RingType.class, rt -> rt.getCurveMember()
.add(create(CurvePropertyType.class, curvept -> curvept.setAbstractCurve(createAndWrap(CurveType.class, curvet -> {
curvet.setId(UUID_PREFIX + UUID.randomUUID());
curvet.setSegments(create(CurveSegmentArrayPropertyType.class,
curvesat -> curvesat.getAbstractCurveSegment().add(createAndWrap(CircleByCenterPointType.class, cbcpt -> {
cbcpt.setPos(create(DirectPositionType.class, dpt -> dpt.getValue().addAll(centerPointCoords)));
cbcpt.setNumArc(BigInteger.valueOf(1));
cbcpt.setRadius(radius);
}))));
})))))))));
spapt = createAndWrap(SurfacePatchArrayPropertyType.class, "createPatches", _spapt -> _spapt.getAbstractSurfacePatch().add(ppt));
} else if (PolygonGeometry.class.isAssignableFrom(geom.getClass())) { //Polygon
final PolygonGeometry polygon = (PolygonGeometry) geom;
final JAXBElement<PolygonPatchType> ppt = createAndWrap(PolygonPatchType.class, poly -> poly.setExterior(
create(AbstractRingPropertyType.class, arpt -> arpt.setAbstractRing(createAndWrap(LinearRingType.class, lrt -> {
final DirectPositionListType dplt = create(DirectPositionListType.class,
dpl -> dpl.getValue().addAll(polygon.getExteriorRingPositions(Winding.COUNTERCLOCKWISE)));
lrt.setPosList(dplt);
})))));
spapt = createAndWrap(SurfacePatchArrayPropertyType.class, "createPatches", _spapt -> _spapt.getAbstractSurfacePatch().add(ppt));
} else {
throw new IllegalArgumentException("Unable to create a Surface from geometry of type " + geom.getClass().getCanonicalName());
}
if (spapt != null) {
sft.setPatches(spapt);
}
})));
}
return retval;
}

protected static Optional<ValDistanceVerticalType> toValDistanceVertical(final NumericMeasure numericMeasure) {
return numericMeasure == null ? Optional.empty() : valDistanceVertical(numericMeasure.getValue(), numericMeasure.getUom());
}

protected static Optional<ValDistanceVerticalType> valDistanceVertical(final Double value, final String uom) {
return valDistanceVertical(value == null ? Double.NaN : value, uom);
}

protected static Optional<ValDistanceVerticalType> valDistanceVertical(final double value, final String uom) {
if (Double.isNaN(value)) {
return Optional.empty();
}
final ValDistanceVerticalType type = create(ValDistanceVerticalType.class);
final DecimalFormat format = new DecimalFormat("", DecimalFormatSymbols.getInstance(Locale.US));
format.setMinimumIntegerDigits(1);
format.setMinimumFractionDigits(0);
format.setMaximumFractionDigits(4);
format.setGroupingUsed(false);
type.setValue(format.format(value));
if (uom != null && !uom.isEmpty()) {
type.setUom(uom.toUpperCase(Locale.US));
}
return Optional.of(type);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package fi.fmi.avi.converter.iwxxm;

import fi.fmi.avi.converter.ConversionException;
import fi.fmi.avi.converter.ConversionHints;
import fi.fmi.avi.converter.ConversionIssue;
import fi.fmi.avi.converter.ConversionResult;
import fi.fmi.avi.model.AviationWeatherMessageOrCollection;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import javax.xml.bind.Binder;
import javax.xml.bind.JAXBException;
import javax.xml.bind.ValidationEvent;
import java.util.List;

/**
* Created by rinne on 25/07/2018.
*/
public abstract class AbstractIWXXMAixm511WxParser<T, S extends AviationWeatherMessageOrCollection> extends AbstractIWXXMParser<T, S> {

/**
* Converts a message into a POJO.
* <p>
* The IWXXM TAF message parsing is done in two phases:
* &lt;ul&gt;
* &lt;li&gt;In the first phase the IWXXM DOM document is validated against its
* XML Schema and Schematron rules and (if validation passed), the JAXB objects created from the
* DOM scanned for all the necessary property values for creating MessageConverter Java model objects. Additional validation
* for the document structure and content is also done in this phase.&lt;/li&gt;
* &lt;li&gt;In the second phase the model objects are created and populated from the property data
* collected in the first phase.&lt;/li&gt;
* &lt;/ul&gt;
*
* @param input input message
* @param hints parsing hints
* @return the conversion result
*/
@Override
public ConversionResult<S> convertMessage(final T input, final ConversionHints hints) {
final ConversionResult<S> result = new ConversionResult<>();
final Object source;
final ReferredObjectRetrievalContext refCtx;

try {
final Document dom = parseAsDom(input);
final XMLSchemaInfo schemaInfo = getSchemaInfo();
final Binder<Node> binder = AbstractIWXXMAixm511WxSerializer.getAixm511WxJAXBContext().createBinder();

//XML Schema validation upon JAXB unmarshal:
binder.setSchema(schemaInfo.getSchema());
final IWXXMValidationEventHandler collector = new IWXXMValidationEventHandler();
binder.setEventHandler(collector);
source = binder.unmarshal(dom);

final List<ValidationEvent> events = collector.getEvents();
if (events.isEmpty()) {
//Reset binder event handler after validation:
binder.setEventHandler(null);

refCtx = new ReferredObjectRetrievalContext(dom, binder);

//Schematron validation:
result.addIssue(validateAgainstIWXXMSchematron(dom, schemaInfo, hints));
try {
result.setConvertedMessage(createPOJO(source, refCtx, result, hints));
} catch (final IllegalStateException ise) {
result.addIssue(new ConversionIssue(ConversionIssue.Severity.ERROR, ConversionIssue.Type.MISSING_DATA,
"All mandatory information for " + "constructing a message object was not available", ise));
}
} else {
for (final ValidationEvent evt : collector.getEvents()) {
result.addIssue(
new ConversionIssue(ConversionIssue.Type.SYNTAX, "XML Schema validation issue: " + evt.getMessage(), evt.getLinkedException()));
}
}

} catch (final ConversionException ce) {
result.addIssue(new ConversionIssue(ConversionIssue.Type.SYNTAX, "Unable to parse input as an XML document", ce));
return result;
} catch (JAXBException | SAXException e) {
throw new RuntimeException("Unexpected exception in parsing IWXXM content", e);
}

return result;
}

}
Loading