/** * Copyright (C) 2008 - 2014 52°North Initiative for Geospatial Open Source * Software GmbH * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * * If the program is linked with libraries which are licensed under one of * the following licenses, the combination of the program with the linked * library is not considered a "derivative work" of the program: * * - Apache License, version 2.0 * - Apache Software License, version 1.0 * - GNU Lesser General Public License, version 3 * - Mozilla Public License, versions 1.0, 1.1 and 2.0 * - Common Development and Distribution License (CDDL), version 1.0 * * Therefore the distribution of the program linked with libraries licensed * under the aforementioned licenses, is permitted by the copyright holders * if the distribution is compliant with both the GNU General Public * icense version 2 and the aforementioned licenses. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. */ package org.n52.ses.io.parser; import java.util.ArrayList; import java.util.List; import javax.xml.namespace.QName; import net.opengis.gml.x32.AbstractTimePrimitiveType; import net.opengis.gml.x32.BoundingShapeType; import net.opengis.gml.x32.CodeWithAuthorityType; import net.opengis.gml.x32.EnvelopeType; import net.opengis.gml.x32.TimePeriodType; import net.opengis.gml.x32.TimePositionType; import net.opengis.gml.x32.TimePrimitivePropertyType; import org.apache.muse.ws.notification.NotificationMessage; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatterBuilder; import org.n52.ses.api.AbstractParser; import org.n52.ses.api.event.MapEvent; import org.n52.ses.api.exception.GMLParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import aero.aixm.schema.x51.AbstractAIXMFeatureType; import aero.aixm.schema.x51.AirspaceActivationPropertyType; import aero.aixm.schema.x51.AirspaceActivationType; import aero.aixm.schema.x51.AirspaceLayerType; import aero.aixm.schema.x51.AirspaceTimeSliceType; import aero.aixm.schema.x51.AirspaceType; import aero.aixm.schema.x51.CodeAirspaceDesignatorType; import aero.aixm.schema.x51.event.EventType; import aero.aixm.schema.x51.message.AIXMBasicMessageDocument; import aero.aixm.schema.x51.message.BasicMessageMemberAIXMPropertyType; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.io.ParseException; /** * A parser for AIXM updates as provided in the * OGC FAA SAA Dissemination Pilot * * @author Thomas Everding * */ public class FaaSaaPilotParser extends AbstractParser { private static final Logger logger = LoggerFactory .getLogger(FaaSaaPilotParser.class); //AIXM namespace private static final String AIXM_MESSAGE_NAMESPACE = "http://www.aixm.aero/schema/5.1/message"; //AIXM Basic Message QName private static final QName MESSAGE_ROOT_QNAME = new QName(AIXM_MESSAGE_NAMESPACE, "AIXMBasicMessage"); //UCUM code for foot private static final String UCUM_FEET = "[ft_i]"; private DateTimeFormatter fmt = this.buildDTFormatter(); @Override public boolean accept(NotificationMessage message) { Element elem = message.getMessageContent(MESSAGE_ROOT_QNAME); if (elem != null) { return true; } return false; } @Override public List<MapEvent> parse(NotificationMessage message) throws Exception { List<MapEvent> result = new ArrayList<MapEvent>(); //get the notification content and create XMLBeans representation Element elem = message.getMessageContent(MESSAGE_ROOT_QNAME); AIXMBasicMessageDocument aixmMessage = AIXMBasicMessageDocument.Factory.parse(elem, null); result.add(this.parseAIXMBasicMessage(aixmMessage)); return result; } /** * parses an AIXM basic message * * @param aixmMessage the message * @return the message parse in a {@link MapEvent} */ private MapEvent parseAIXMBasicMessage(AIXMBasicMessageDocument aixmMessage) { MapEvent mapEvent = new MapEvent(0, 0); //get hasMember elements BasicMessageMemberAIXMPropertyType[] members = aixmMessage.getAIXMBasicMessage().getHasMemberArray(); for (BasicMessageMemberAIXMPropertyType member : members) { this.parseMember(member, mapEvent); } return mapEvent; } /** * Parses a member of the AIXM Basic Message * * @param member the member * @param mapEvent the event to populate */ private void parseMember(BasicMessageMemberAIXMPropertyType member, MapEvent mapEvent) { AbstractAIXMFeatureType abtractAIXMFeature = member.getAbstractAIXMFeature(); if (abtractAIXMFeature instanceof EventType) { //parse the Event part EventType event = (EventType) abtractAIXMFeature; this.parseEvent(event, mapEvent); } else if (abtractAIXMFeature instanceof AirspaceType) { //parse the Airspace part AirspaceType airspace = (AirspaceType) abtractAIXMFeature; this.parseAirspace(airspace, mapEvent); } } /** * Parses an airspace * * @param airspace the airspace type * @param mapEvent the event to be populated */ private void parseAirspace(AirspaceType airspace, MapEvent mapEvent) { //parse the identifier if (airspace.isSetIdentifier()) { CodeWithAuthorityType identifier = airspace.getIdentifier(); this.parseGMLIdentifier(identifier, mapEvent); } //parse the time slice if (airspace.getTimeSliceArray().length > 0) { AirspaceTimeSliceType airspaceTimeSlice = airspace.getTimeSliceArray(0).getAirspaceTimeSlice(); this.parseTimeSlice(airspaceTimeSlice, mapEvent); } if (airspace.isSetBoundedBy()) { //parse bounding box this.parseBoundingBox(airspace.getBoundedBy(), mapEvent); } } /** * parses an airspace time slice * * @param airspaceTimeSlice the time slice node of an airspace * @param mapEvent the map event to populate */ private void parseTimeSlice(AirspaceTimeSliceType airspaceTimeSlice, MapEvent mapEvent) { //parse valid time this.parseValidTime(airspaceTimeSlice.getValidTime(), mapEvent); //parse activation if (airspaceTimeSlice.getActivationArray().length > 0) { AirspaceActivationPropertyType activation = airspaceTimeSlice.getActivationArray(0); this.parseActivation(activation.getAirspaceActivation(), mapEvent); } //parse designator if (airspaceTimeSlice.isSetDesignator()){ this.parseDesignator(airspaceTimeSlice.getDesignator(), mapEvent); } } /** * Parses the designator * * @param designator the designator of an airspacetiemslice * @param mapEvent the map event to populate */ private void parseDesignator(CodeAirspaceDesignatorType designator, MapEvent mapEvent) { // parse the designator value String designatorValue = designator.getStringValue(); mapEvent.put(MapEvent.AIXM_DESIGNATOR_KEY, designatorValue); } /** * Parses the activation of the time slice * * @param activation the aixm:activation element * @param mapEvent the map event to populate */ private void parseActivation(AirspaceActivationType activation, MapEvent mapEvent) { //parse status if (activation.isSetStatus()) { String status = activation.getStatus().getStringValue(); mapEvent.put(MapEvent.STAUS_KEY, status); } //parse levels if (activation.getLevelsArray().length > 0) { AirspaceLayerType levels = activation.getLevelsArray(0).getAirspaceLayer(); this.parseLevels(levels, mapEvent); } // //parse extension // if (activation.getExtensionArray().length > 0) { // Extension extension = activation.getExtensionArray(0); // this.parseActivationExtension(extension, mapEvent); // } } // /** // * parses an aixm:extension element of the activation // * // * @param extension the extension element // * @param mapEvent the event to populate // */ // private void parseActivationExtension(Extension extension, MapEvent mapEvent) { // //get the reservation phase element // AbstractExtensionType ext = extension.getAbstractAirspaceActivationExtension(); // if (ext instanceof AirspaceActivationExtensionType) { // AirspaceActivationExtensionType aaEx = (AirspaceActivationExtensionType) ext; // // if (aaEx.isSetReservationPhase()) { // String phase = aaEx.getReservationPhase().toString(); // mapEvent.put(MapEvent.RESERVATION_PHASE_KEY, phase); // } // } // } /** * Parses the levels of an activation * * @param layer the aixm:levels element * @param mapEvent the event to populate */ private void parseLevels(AirspaceLayerType layer, MapEvent mapEvent) { if (!layer.isSetLowerLimit() || !layer.isSetUpperLimit()) { return; } //get values and convert double u = Double.parseDouble(layer.getUpperLimit().getStringValue()); double l = Double.parseDouble(layer.getLowerLimit().getStringValue()); //convert lower to SI unit String uom = ""; if (layer.getLowerLimit().isSetUom()) { uom = layer.getLowerLimit().getUom(); //adjust non-UCUM codes if (uom.equals("FT") || uom.equals("ft")) { uom = UCUM_FEET; } } l = this.unitConverter.convert(uom, l).getValue(); //convert upper to SI unit uom = ""; if (layer.getUpperLimit().isSetUom()) { uom = layer.getUpperLimit().getUom(); //adjust non-UCUM codes if (uom.equals("FT") || uom.equals("ft")) { uom = UCUM_FEET; } } u = this.unitConverter.convert(uom, u).getValue(); //store level information in event mapEvent.put(MapEvent.LOWER_LIMIT_KEY, l); mapEvent.put(MapEvent.UPPER_LIMIT_KEY, u); } /** * Parses the valid time of the time slice * * @param timePrimitivePropertyType the gml:validTime element * @param mapEvent the map event to populate */ private void parseValidTime(TimePrimitivePropertyType timePrimitivePropertyType, MapEvent mapEvent) { long begin = 0; long end = 0; //parse the valid time element AbstractTimePrimitiveType abstractTime = timePrimitivePropertyType.getAbstractTimePrimitive(); if (abstractTime instanceof TimePeriodType) { //parse gml:TimePeriod type TimePeriodType timePeriod = (TimePeriodType) abstractTime; if (timePeriod.isSetBeginPosition()) { begin = this.parseTimePosition(timePeriod.getBeginPosition()); } if (timePeriod.isSetEndPosition()) { end = this.parseTimePosition(timePeriod.getEndPosition()); } } //parse other time types here mapEvent.put(MapEvent.START_KEY, begin); mapEvent.put(MapEvent.END_KEY, end); } /** * Parses a gml:identifier element * * @param identifier the gml:identifier * @param mapEvent the map event to populate */ private void parseGMLIdentifier(CodeWithAuthorityType identifier, MapEvent mapEvent) { //get value String value = identifier.getStringValue(); //get code space String codeSpace = identifier.getCodeSpace(); //set properties mapEvent.put(MapEvent.IDENTIFIER_CODESPACE_KEY, codeSpace); mapEvent.put(MapEvent.IDENTIFIER_VALUE_KEY, value); } /** * Parses an event:Event into a {@link MapEvent} * * @param event the AIXM Event to parse * * @param mapEvent the map event for the results */ private void parseEvent(EventType event, MapEvent mapEvent) { if (event.isSetBoundedBy()) { //parse the bounding box of the event this.parseBoundingBox(event.getBoundedBy(), mapEvent); } } /** * parses a bounding box * * @param boundingBox the gml Bounding box * @param mapEvent the event to populate */ private void parseBoundingBox(BoundingShapeType boundingBox, MapEvent mapEvent) { //check if a bounding box was already parsed (multiple instances possible) if (mapEvent.containsKey(MapEvent.GEOMETRY_KEY)) { //geometry already parsed and set return; } //geometry not yet parsed, do so EnvelopeType envelope = boundingBox.getEnvelope(); try { Geometry geom = GML32Parser.parseGeometry(envelope); mapEvent.put(MapEvent.GEOMETRY_KEY, geom); } catch (ParseException e) { logger.warn(e.getMessage(), e); } catch (GMLParseException e) { logger.warn(e.getMessage(), e); } } /** * Parses a time position * * @param timePositionType * @return */ private long parseTimePosition(TimePositionType timePositionType) { String timeString = timePositionType.getStringValue(); //replace Z by +00:00 if (timeString.endsWith("Z")) { timeString = timeString.substring(0, timeString.length() - 1) + "+00:00"; } DateTime position = this.fmt.parseDateTime(timeString); return position.getMillis(); } /** * * @return a formatter for common ISO strings */ private DateTimeFormatter buildDTFormatter() { //build a parser for time stamps return new DateTimeFormatterBuilder() .appendYear(4, 4) //4 digit year (YYYY) .appendLiteral("-") .appendMonthOfYear(2) //2 digit month (MM) .appendLiteral("-") .appendDayOfMonth(2) //2 digit day (DD) .appendLiteral("T") .appendHourOfDay(2) //2 digit hour (hh) .appendLiteral(":") .appendMinuteOfHour(2) //2 digit minute (mm) .appendLiteral(":") .appendSecondOfMinute(2)//2 digit second (ss) //optional 3 digit milliseconds of second .appendOptional(new DateTimeFormatterBuilder() .appendLiteral(".") .appendMillisOfSecond(3) .toParser()) //optional time zone offset as (+|-)hh:mm .appendOptional(new DateTimeFormatterBuilder() .appendTimeZoneOffset("", true, 2, 2) .toParser()) .toFormatter(); } @Override protected String getName() { return "FAA SAA Pilot parser"; } /** * test main * @param args s */ public static void main(String[] args) { // System.out.println("initialize()"); // // Logger logger = Logger.getLogger(FaaSaaPilotParser.class.getName()); // // System.out.println("logger built"); // // IUnitConverter uConv = new SESUnitConverter(logger); // // System.out.println("unit conv built"); // // String testFilePath = "./ses-main/src/main\\resources\\wsdl\\test_files\\EVENT_2328231_PENDING.xml"; // File testFile = new File(testFilePath); // // System.out.println("file found: " + testFile.exists()); // // try { // ConfigurationRegistry.init(new FileInputStream(testFile), logger, null, uConv); // } // catch (FileNotFoundException e) { // e.printStackTrace(); // } // // FaaSaaPilotParser parser = new FaaSaaPilotParser(uConv); // // System.out.println("parser built"); // // AIXMBasicMessageDocument message = null; // try { // message = AIXMBasicMessageDocument.Factory.parse(testFile); // } // catch (XmlException e) { // e.printStackTrace(); // } // catch (IOException e) { // e.printStackTrace(); // } // // if (message != null) { // System.out.println("test input parsed to XML bean"); // System.out.println(message.toString()); // // MapEvent event = parser.parseAIXMBasicMessage(message); // // System.out.println(event.toString()); // } } }