/** * 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 org.apache.muse.ws.notification.NotificationMessage; import org.apache.xmlbeans.XmlObject; import org.joda.time.DateTime; import org.n52.oxf.xmlbeans.parser.XMLBeansParser; import org.n52.oxf.xmlbeans.tools.XmlUtil; 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 com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.io.ParseException; /** * Parser for notifications encoded in AIXM. * */ public class AIXMParser extends AbstractParser { /** * namespace used for parsing dNOTAMs */ public static final String DNOTAM_NAMESPACE = "http://www.aixm.aero/schema/5.1/dnotam"; public static final String AIXM_NAMESPACE = "http://www.aixm.aero/schema/5.1"; public static final String AIXM_MESSAGE_NAMESPACE = "http://www.aixm.aero/schema/5.1/message"; private static final String GML_NAMESPACE = "http://www.opengis.net/gml/3.2"; // private static final String XLINK_NAMESPACE = "http://www.w3.org/1999/xlink"; /* * Event Qnames */ private static final QName EVENT = new QName(DNOTAM_NAMESPACE, "Event"); private static final QName EVENT_NAME = new QName(DNOTAM_NAMESPACE, "name"); // private static final QName EVENT_DESCRIPTION = new QName(EVENT_NAMESPACE, "description"); private static final QName EVENT_TYPE = new QName(DNOTAM_NAMESPACE, "type"); private static final QName EVENT_HAS_MEMBER = new QName(DNOTAM_NAMESPACE, "hasMember"); /* * AIXM Qnames */ private static final QName AIXM_NAVAID = new QName(AIXM_NAMESPACE, "Navaid"); private static final QName AIXM_TIME_SLICE = new QName(AIXM_NAMESPACE, "timeSlice"); private static final QName AIXM_NAVAID_TIME_SLICE = new QName(AIXM_NAMESPACE, "NavaidTimeSlice"); private static final QName AIXM_AIRPORT_HELIPORT_TIME_SLICE = new QName(AIXM_NAMESPACE, "AirportHeliportTimeSlice"); private static final QName AIXM_INTERPRETATION = new QName(AIXM_NAMESPACE, "interpretation"); // private static final QName AIXM_SEQUENCE_NUMBER = new QName(AIXM_NAMESPACE, "sequenceNumber"); // private static final QName AIXM_CORRECTION_NUMBER = new QName(AIXM_NAMESPACE, "correctionNumber"); // private static final QName AIXM_TYPE = new QName(AIXM_NAMESPACE, "type"); private static final QName AIXM_DESIGNATOR = new QName(AIXM_NAMESPACE, "designator"); // private static final QName AIXM_OPERATIONAL_STATUS = new QName(AIXM_NAMESPACE, "operationalStatus"); private static final QName AIXM_AIRPORT_HELIPORT = new QName(AIXM_NAMESPACE, "AirportHeliport"); // private static final QName AIXM_AFFECTED_RUNWAY_DIRECTION = new QName(AIXM_NAMESPACE, "affectedRunwayDirection"); // private static final QName AIXM_LIMITATION = new QName(AIXM_NAMESPACE, "limitation"); // private static final QName AIXM_AIRPORT_HELIPORT_USAGE_LIMITATION = new QName(AIXM_NAMESPACE, "AirportHeliportUsageLimitation"); // private static final QName AIXM_CONDITION = new QName(AIXM_NAMESPACE, "condition"); // private static final QName AIXM_AIRPORT_HELIPORT_USAGE_CONDITION = new QName(AIXM_NAMESPACE, "AirportHeliportUsageCondition"); // private static final QName AIXM_FLIGHT = new QName(AIXM_NAMESPACE, "flight"); // private static final QName AIXM_FLIGHT_CHARACTERISTICS = new QName(AIXM_NAMESPACE, "FlightCharacteristic"); // private static final QName AIXM_RULE = new QName(AIXM_NAMESPACE, "rule"); // private static final QName AIXM_OPERATION = new QName(AIXM_NAMESPACE, "operation"); // private static final QName AIXM_AIRPORT_HELIPORT_OPERATION = new QName(AIXM_NAMESPACE, "AirportHeliportOperation"); /* * XPath expressions */ private static final String SELECT_OP_STATUS_XPATH = "declare namespace aixm='" + AIXM_NAMESPACE + "'; .//aixm:availability//aixm:operationalStatus"; /* * GML Qnames */ private static final QName GML_BOUNDED_BY = new QName(GML_NAMESPACE, "boundedBy"); // private static final QName GML_ENVELOPE = new QName(GML_NAMESPACE, "Envelope"); // private static final QName GML_LOWER_CORNER = new QName(GML_NAMESPACE, "lowerCorner"); // private static final QName GML_UPPER_CORNER = new QName(GML_NAMESPACE, "upperCorner"); private static final QName GML_IDENTIFIER = new QName(GML_NAMESPACE, "identifier"); private static final QName GML_VALID_TIME = new QName(GML_NAMESPACE, "validTime"); private static final QName GML_TIME_PERIOD = new QName(GML_NAMESPACE, "TimePeriod"); private static final QName GML_TIME_INSTANT = new QName(GML_NAMESPACE, "TimeInstant"); private static final QName GML_TIME_POSITION = new QName(GML_NAMESPACE, "timePosition"); private static final QName GML_BEGIN_POSITION = new QName(GML_NAMESPACE, "beginPosition"); private static final QName GML_END_POSITION = new QName(GML_NAMESPACE, "endPosition"); /** * globally used key for the aeronautical feature of a MapEvent */ public static final String AERO_FEATURE_KEY = "aeronauticalFeature"; private static final String DNOTAM_TYPE_KEY = "aixmType"; // private static final QName GML_POS = new QName(GML_NAMESPACE, "pos"); // /* // * XLink Qnames // */ // private static final QName XLINK_HREF = new QName(XLINK_NAMESPACE, "href"); // private static final QName XLINK_TITLE = new QName(XLINK_NAMESPACE, "title"); private static final Logger logger = LoggerFactory .getLogger(AIXMParser.class); @Override public boolean accept(NotificationMessage message) { Element content = message.getMessageContent(EVENT); if (content != null) { return true; } return false; } @Override public List<MapEvent> parse(NotificationMessage message) throws Exception { Element content = message.getMessageContent(EVENT); if (content != null) { XmlObject obj = XMLBeansParser.parse(content, false); return parseAIXM(obj); } return null; } /** * Parses the AIXM documents and creates events from it. * * @param doc the document to parse * @return a List of MapEvent containing the resulting events. */ public List<MapEvent> parseAIXM(XmlObject doc) { List<MapEvent> resultingEvents = new ArrayList<MapEvent>(); /* * get all Events */ XmlObject[] events = doc.selectChildren(EVENT); if (events != null && events.length > 0) { for (XmlObject event : events) { String eventName = XmlUtil.stripText(event.selectChildren(EVENT_NAME)); // String eventDesc = XmlUtil.stripText(event.selectChildren(EVENT_DESCRIPTION)); String eventType = XmlUtil.stripText(event.selectChildren(EVENT_TYPE)); XmlObject[] hasMember = event.selectChildren(EVENT_HAS_MEMBER); /* * get the hasMember */ if (hasMember != null && hasMember.length > 0){ try { resultingEvents = parseHasMember(hasMember); } catch (ParseException e) { logger.warn(e.getMessage(), e); } } /* * add MapEvent here */ if (resultingEvents != null) { for (MapEvent newEvent : resultingEvents) { newEvent.put("event_name", eventName); newEvent.put("event_type", eventType); } } } } return resultingEvents; } /** * Parses the hasMember section of the AIXM document */ private List<MapEvent> parseHasMember(XmlObject[] hasMember) throws ParseException { for (XmlObject members : hasMember) { /* * get the Navaid */ XmlObject[] navaIds = members.selectChildren(AIXM_NAVAID); if (navaIds != null && navaIds.length > 0) { /* * only one Navaid allowed */ XmlObject navaId = navaIds[0]; return parseNavaid(navaId); } //else: no Navaid -> AirportHeliport? XmlObject[] airportHeliports = members.selectChildren(AIXM_AIRPORT_HELIPORT); if (airportHeliports != null && airportHeliports.length > 0) { /* * only one AirportHeliport allowed */ return parseAirportHeliports(airportHeliports[0]); } } return null; } /** * Parsed the AirportHeliportsUsage element. * @throws ParseException */ private List<MapEvent> parseAirportHeliports(XmlObject airportUsage) throws ParseException { List<MapEvent> newEvents = new ArrayList<MapEvent>(); /* * get the gml */ XmlObject[] boundedBys = airportUsage.selectChildren(GML_BOUNDED_BY); Geometry geom = null; if (boundedBys != null && boundedBys.length > 0) { try { geom = GML32Parser.parseGeometry(boundedBys[0], true); } catch (GMLParseException e) { throw new ParseException(e); } } XmlObject[] gmlident = airportUsage.selectChildren(GML_IDENTIFIER); String identifier = null; String identifierCodeSpace = null; if (gmlident != null && gmlident.length > 0) { identifier = XmlUtil.stripText(gmlident[0]); identifierCodeSpace = XmlUtil.stripText(gmlident[0].selectAttribute(new QName("", "codeSpace"))); } /* * get the timeSlice */ XmlObject[] timeSlices = airportUsage.selectChildren(AIXM_TIME_SLICE); if (timeSlices != null && timeSlices.length > 0) { MapEvent newEvent = null; for (XmlObject slice : timeSlices) { XmlObject heliportTimeSlice = slice.selectChildren(AIXM_AIRPORT_HELIPORT_TIME_SLICE)[0]; /* * parse the validTime */ XmlObject[] validTime = heliportTimeSlice.selectChildren(GML_VALID_TIME); newEvent = parseTime(validTime); /* * parse other elements */ String interpretation = XmlUtil.stripText(heliportTimeSlice.selectChildren(AIXM_INTERPRETATION)); // String sequenceNumber = XmlUtil.stripText(heliportTimeSlice.selectChildren(AIXM_SEQUENCE_NUMBER)); // String correctionNumber = XmlUtil.stripText(heliportTimeSlice.selectChildren(AIXM_CORRECTION_NUMBER)); String designator = XmlUtil.stripText(heliportTimeSlice.selectChildren(AIXM_DESIGNATOR)); /* * parse operational status */ XmlObject[] opStats = XmlUtil.selectPath(SELECT_OP_STATUS_XPATH, heliportTimeSlice); String opStatus = null; if (opStats.length > 0) { opStatus = XmlUtil.stripText(opStats); } /* * (Metadata parsing left out) */ /* * do not parse the featureLifetime. time already set */ // /* // * parse the limitation // */ // XmlObject[] limitations = heliportTimeSlice.selectChildren(AIXM_LIMITATION); // if (limitations != null && limitations.length > 0) { // parseLimitations(limitations, newEvent); // } /* * parse the runways */ // XmlObject[] runwayObjects = heliportTimeSlice.selectChildren(AIXM_AFFECTED_RUNWAY_DIRECTION); if (newEvent != null) { newEvent.put(MapEvent.GEOMETRY_KEY, geom); //only PERMDELTA or TEMPDELTA. if not, do not add it if (interpretation.equals("TEMPDELTA") || interpretation.equals("PERMDELTA")) { newEvent.put("aixm_interpretation", interpretation); newEvent.put("identifierCodeSpace", identifierCodeSpace); newEvent.put("identifier", identifier); newEvent.put("operationalStatus", opStatus); newEvent.put(DNOTAM_TYPE_KEY, "dnotam:Event"); if (designator != null) { newEvent.put(AERO_FEATURE_KEY, designator); } else { newEvent.put(AERO_FEATURE_KEY, identifier); } newEvents.add(newEvent); } } } } return newEvents; } // /** // * Parses the aixm:limitation element. // * @param newEvent // */ // private void parseLimitations(XmlObject[] limitations, MapEvent newEvent) { // for (XmlObject limit : limitations) { // /* // * parse AirportHeliportUsageLimitation (only one allowed) // */ // XmlObject ahul = limit.selectChildren(AIXM_AIRPORT_HELIPORT_USAGE_LIMITATION)[0]; // String type = XmlUtil.stripText(ahul.selectChildren(AIXM_TYPE)); // /* // * parse conditions // */ // XmlObject[] conditions = ahul.selectChildren(AIXM_CONDITION); // if (conditions != null && conditions.length > 0) { // for (XmlObject cond : conditions) { // /* // * only one allowed // */ //// XmlObject ahuc = cond.selectChildren(AIXM_AIRPORT_HELIPORT_USAGE_CONDITION)[0]; // //// String flightRule = XmlUtil.stripText(ahuc.selectChildren(AIXM_FLIGHT)[0]. //// selectChildren(AIXM_FLIGHT_CHARACTERISTICS)[0]. //// selectChildren(AIXM_RULE)[0]); // //// String operationType = XmlUtil.stripText(ahuc.selectChildren(AIXM_OPERATION)[0]. //// selectChildren(AIXM_AIRPORT_HELIPORT_OPERATION)[0]. //// selectChildren(AIXM_TYPE)[0]); // // } // } // } // } /** * Parses the Navaid element. * @param newEvent */ private List<MapEvent> parseNavaid(XmlObject navaId) throws ParseException { List<MapEvent> newEvents = new ArrayList<MapEvent>(); /* * get the gml */ XmlObject[] boundedBys = navaId.selectChildren(GML_BOUNDED_BY); Geometry geom = null; if (boundedBys != null && boundedBys.length > 0) { try { geom = GML32Parser.parseGeometry(boundedBys[0]); } catch (GMLParseException e) { throw new ParseException(e); } } XmlObject[] gmlident = navaId.selectChildren(GML_IDENTIFIER); String identifier = null; String identifierCodeSpace = null; if (gmlident != null && gmlident.length > 0) { identifier = XmlUtil.stripText(gmlident[0]); identifierCodeSpace = XmlUtil.stripText(gmlident[0].selectAttribute(new QName("", "codeSpace"))); } /* * get the timeSlice */ XmlObject[] timeSlices = navaId.selectChildren(AIXM_TIME_SLICE); if (timeSlices != null && timeSlices.length > 0) { MapEvent newEvent = null; for (XmlObject slice : timeSlices) { XmlObject navaidTimeSlice = slice.selectChildren(AIXM_NAVAID_TIME_SLICE)[0]; /* * parse the validTime */ XmlObject[] validTime = navaidTimeSlice.selectChildren(GML_VALID_TIME); newEvent = parseTime(validTime); /* * parse other elements */ // String interpretation = XmlUtil.stripText(navaidTimeSlice.selectChildren(AIXM_INTERPRETATION)); // String sequenceNumber = XmlUtil.stripText(navaidTimeSlice.selectChildren(AIXM_SEQUENCE_NUMBER)); // String correctionNumber = XmlUtil.stripText(navaidTimeSlice.selectChildren(AIXM_CORRECTION_NUMBER)); // String type = XmlUtil.stripText(navaidTimeSlice.selectChildren(AIXM_TYPE)); String designator = XmlUtil.stripText(navaidTimeSlice.selectChildren(AIXM_DESIGNATOR)); // String operationalStatus = XmlUtil.stripText(navaidTimeSlice.selectChildren(AIXM_OPERATIONAL_STATUS)); if (newEvent != null) { newEvent.put(MapEvent.GEOMETRY_KEY, geom); newEvent.put("identifierCodeSpace", identifierCodeSpace); newEvent.put("identifier", identifier); if (designator != null) { newEvent.put(AERO_FEATURE_KEY, designator); } else { newEvent.put(AERO_FEATURE_KEY, identifier); } newEvents.add(newEvent); } } } return newEvents; } /** * parses the time of a gml:validTime */ private MapEvent parseTime(XmlObject[] time) { if (time != null && time.length > 0) { XmlObject[] period = time[0].selectChildren(GML_TIME_PERIOD); if (period != null && period.length > 0) { String beginPos = ""; XmlObject hasIndeterminate = period[0].selectChildren(GML_BEGIN_POSITION)[0].selectAttribute(new QName("", "indeterminatePosition")); if (hasIndeterminate == null) { beginPos = XmlUtil.stripText(period[0].selectChildren(GML_BEGIN_POSITION)); } else { beginPos = XmlUtil.stripText(hasIndeterminate); } String endPos = ""; hasIndeterminate = period[0].selectChildren(GML_END_POSITION)[0].selectAttribute(new QName("", "indeterminatePosition")); if (hasIndeterminate == null) { endPos = XmlUtil.stripText(period[0].selectChildren(GML_END_POSITION)); } else { endPos = XmlUtil.stripText(hasIndeterminate); } /* * construct time in millis. for "unknown" set the MAX_VALUE */ long beginPosMs, endPosMs = 0L; if (beginPos.equals("unknown")) { beginPosMs = Long.MAX_VALUE; } else { beginPosMs = new DateTime(beginPos).getMillis(); } if (endPos.equals("unknown")) { endPosMs = Long.MAX_VALUE; } else { endPosMs = new DateTime(endPos).getMillis(); } return new MapEvent(beginPosMs, endPosMs); } XmlObject[] instant = time[0].selectChildren(GML_TIME_INSTANT); if (instant != null && instant.length > 0) { String timepos = XmlUtil.stripText(instant[0].selectChildren(GML_TIME_POSITION)); long beginPosMs = new DateTime(timepos).getMillis(); return new MapEvent(beginPosMs, beginPosMs); } } return null; } @Override protected String getName() { return "AIXMParser [dNOTAM Event]"; } // /** // * Constructs a Geometry from the given gml:boundedBy // */ // private Geometry parseBoundedBy(XmlObject[] boundedBys) throws ParseException { // if (boundedBys != null && boundedBys.length > 0) { // /* // * get the first - only one allowed // */ // XmlObject boundedBy = boundedBys[0]; // // XmlObject[] env = boundedBy.selectChildren(GML_ENVELOPE); // if (env != null && env.length > 0) { // XmlObject[] lower = env[0].selectChildren(GML_LOWER_CORNER); // XmlObject[] upper = env[0].selectChildren(GML_UPPER_CORNER); // // /* // * get text of lowerCorner and split at " " // */ // String cString = XmlUtil.stripText(lower); // String[] lc = cString.split(" "); // // int i = 0; // for (String string : lc) { // string = string.trim(); // lc[i] = string; // i++; // } // // /* // * get text of upperCorner and split at " " // */ // cString = XmlUtil.stripText(upper); // String[] uc = cString.split(" "); // // i = 0; // for (String string : uc) { // string = string.trim(); // uc[i] = string; // i++; // } // // /* // * construct Geometry using JTS // */ // String wktString = "POLYGON(("+ lc[0] +" "+ lc[1] +", "+ lc[0] +" "+ uc[1] +", "+ uc[0] +" "+ uc[1] + // ", "+ uc[0] +" "+ lc[1] +", "+ lc[0] +" "+ lc[1]+ "))"; // // WKTReader wktReader = new WKTReader(); // return wktReader.read(wktString); // } // // XmlObject[] pos = boundedBy.selectChildren(GML_POS); // // if (pos != null && pos.length == 2) { // /* // * get text of first pos and split at " " // */ // String cString = XmlUtil.stripText(pos[0]); // String[] lc = cString.split(" "); // // int i = 0; // for (String string : lc) { // string = string.trim(); // lc[i] = string; // i++; // } // // /* // * get text of second pos and split at " " // */ // cString = XmlUtil.stripText(pos[1]); // String[] uc = cString.split(" "); // // i = 0; // for (String string : uc) { // string = string.trim(); // uc[i] = string; // i++; // } // // /* // * construct Geometry using JTS // */ // String wktString = "POLYGON(("+ lc[0] +" "+ lc[1] +", "+ lc[0] +" "+ uc[1] +", "+ uc[0] +" "+ uc[1] + // ", "+ uc[0] +" "+ lc[1] +", "+ lc[0] +" "+ lc[1]+ "))"; // // WKTReader wktReader = new WKTReader(); // return wktReader.read(wktString); // } // } // return null; // } }