/****************************************************************************** * * Project: Interlis 2 Reader * Purpose: Implementation of ILI2Reader class. * Author: Markus Schnider, Sourcepole AG * ****************************************************************************** * Copyright (c) 2004, Pirmin Kalberer, Sourcepole AG * Copyright (c) 2008-2012, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "ili2readerp.h" #include "ogr_ili2.h" #include "cpl_conv.h" #include "cpl_string.h" #include "ili2reader.h" using namespace std; CPL_CVSID("$Id: ili2reader.cpp b1c9c12ad373e40b955162b45d704070d4ebf7b0 2019-06-19 16:50:15 +0200 Even Rouault $") // // constants // static const char * const ILI2_TID = "TID"; static const XMLCh xmlch_ILI2_TID[] = {'T', 'I', 'D', '\0' }; static const XMLCh ILI2_REF[] = {'R', 'E', 'F', '\0' }; constexpr int ILI2_STRING_TYPE = 0; constexpr int ILI2_COORD_TYPE = 1; constexpr int ILI2_ARC_TYPE = 2; constexpr int ILI2_POLYLINE_TYPE = 4; constexpr int ILI2_BOUNDARY_TYPE = 8; constexpr int ILI2_AREA_TYPE = 16; // also SURFACE constexpr int ILI2_GEOMCOLL_TYPE = 32; static const char * const ILI2_COORD = "COORD"; static const char * const ILI2_ARC = "ARC"; static const char * const ILI2_POLYLINE = "POLYLINE"; static const char * const ILI2_BOUNDARY = "BOUNDARY"; static const char * const ILI2_AREA = "AREA"; static const char * const ILI2_SURFACE = "SURFACE"; // // helper functions // int cmpStr(string s1, string s2) { string::const_iterator p1 = s1.begin(); string::const_iterator p2 = s2.begin(); while (p1 != s1.end() && p2 != s2.end()) { if (toupper(*p1) != toupper(*p2)) return (toupper(*p1) < toupper(*p2)) ? -1 : 1; ++p1; ++p2; } return (s2.size() == s1.size()) ? 0 : (s1.size() < s2.size()) ? -1 : 1; } string ltrim(string tmpstr) { unsigned int i = 0; while (i < tmpstr.length() && (tmpstr[i] == ' ' || tmpstr[i] == '\t' || tmpstr[i] == '\r' || tmpstr[i] == '\n')) ++i; return i > 0 ? tmpstr.substr(i, tmpstr.length()-i) : tmpstr; } string rtrim(string tmpstr) { if (tmpstr.length() == 0) return tmpstr; unsigned int i = static_cast(tmpstr.length()) - 1; while (tmpstr[i] == ' ' || tmpstr[i] == '\t' || tmpstr[i] == '\r' || tmpstr[i] == '\n') --i; return i < tmpstr.length() - 1 ? tmpstr.substr(0, i+1) : tmpstr; } string trim(string tmpstr) { tmpstr = ltrim(tmpstr); tmpstr = rtrim(tmpstr); return tmpstr; } static int getGeometryTypeOfElem(DOMElement* elem) { int type = ILI2_STRING_TYPE; if( elem == nullptr ) return type; char* pszTagName = XMLString::transcode(elem->getTagName()); if (elem->getNodeType() == DOMNode::ELEMENT_NODE) { if (cmpStr(ILI2_COORD, pszTagName) == 0) { type = ILI2_COORD_TYPE; } else if (cmpStr(ILI2_ARC, pszTagName) == 0) { type = ILI2_ARC_TYPE; } else if (cmpStr(ILI2_POLYLINE, pszTagName) == 0) { type = ILI2_POLYLINE_TYPE; } else if (cmpStr(ILI2_BOUNDARY, pszTagName) == 0) { type = ILI2_BOUNDARY_TYPE; } else if (cmpStr(ILI2_AREA, pszTagName) == 0) { type = ILI2_AREA_TYPE; } else if (cmpStr(ILI2_SURFACE, pszTagName) == 0) { type = ILI2_AREA_TYPE; } } XMLString::release(&pszTagName); return type; } static char *getObjValue(DOMElement *elem) { DOMNode* child = elem->getFirstChild(); if ((child != nullptr) && (child->getNodeType() == DOMNode::TEXT_NODE)) { return CPLStrdup(transcode(child->getNodeValue())); } return nullptr; } static char *getREFValue(DOMElement *elem) { CPLString osREFValue(transcode(elem->getAttribute(ILI2_REF))); return CPLStrdup(osREFValue); } static OGRPoint *getPoint(DOMElement *elem) { // elem -> COORD (or ARC) DOMElement *coordElem = dynamic_cast(elem->getFirstChild()); if( coordElem == nullptr ) return nullptr; OGRPoint *pt = new OGRPoint(); while (coordElem != nullptr) { char* pszTagName = XMLString::transcode(coordElem->getTagName()); char* pszObjValue = getObjValue(coordElem); if( pszObjValue ) { if (cmpStr("C1", pszTagName) == 0) pt->setX(CPLAtof(pszObjValue)); else if (cmpStr("C2", pszTagName) == 0) pt->setY(CPLAtof(pszObjValue)); else if (cmpStr("C3", pszTagName) == 0) pt->setZ(CPLAtof(pszObjValue)); } CPLFree(pszObjValue); XMLString::release(&pszTagName); coordElem = dynamic_cast(coordElem->getNextSibling()); } pt->flattenTo2D(); return pt; } OGRCircularString *ILI2Reader::getArc(DOMElement *elem) { // previous point -> start point auto elemPrev = dynamic_cast(elem->getPreviousSibling()); if( elemPrev == nullptr ) return nullptr; OGRPoint *ptStart = getPoint(elemPrev); // COORD or ARC if( ptStart == nullptr ) return nullptr; // elem -> ARC OGRCircularString *arc = new OGRCircularString(); // end point OGRPoint *ptEnd = new OGRPoint(); // point on the arc OGRPoint *ptOnArc = new OGRPoint(); // double radius = 0; // radius DOMElement *arcElem = dynamic_cast(elem->getFirstChild()); while (arcElem != nullptr) { char* pszTagName = XMLString::transcode(arcElem->getTagName()); char* pszObjValue = getObjValue(arcElem); if( pszObjValue ) { if (cmpStr("C1", pszTagName) == 0) ptEnd->setX(CPLAtof(pszObjValue)); else if (cmpStr("C2", pszTagName) == 0) ptEnd->setY(CPLAtof(pszObjValue)); else if (cmpStr("C3", pszTagName) == 0) ptEnd->setZ(CPLAtof(pszObjValue)); else if (cmpStr("A1", pszTagName) == 0) ptOnArc->setX(CPLAtof(pszObjValue)); else if (cmpStr("A2", pszTagName) == 0) ptOnArc->setY(CPLAtof(pszObjValue)); else if (cmpStr("A3", pszTagName) == 0) ptOnArc->setZ(CPLAtof(pszObjValue)); else if (cmpStr("R", pszTagName) == 0) { // radius = CPLAtof(pszObjValue); } } CPLFree(pszObjValue); XMLString::release(&pszTagName); arcElem = dynamic_cast(arcElem->getNextSibling()); } arc->addPoint(ptStart); arc->addPoint(ptOnArc); arc->addPoint(ptEnd); delete ptStart; delete ptOnArc; delete ptEnd; return arc; } static OGRCompoundCurve *getPolyline(DOMElement *elem) { // elem -> POLYLINE OGRCompoundCurve *ogrCurve = new OGRCompoundCurve(); OGRLineString *ls = new OGRLineString(); DOMElement *lineElem = dynamic_cast(elem->getFirstChild()); while (lineElem != nullptr) { char* pszTagName = XMLString::transcode(lineElem->getTagName()); if (cmpStr(ILI2_COORD, pszTagName) == 0) { OGRPoint* poPoint = getPoint(lineElem); if( poPoint ) { ls->addPoint(poPoint); delete poPoint; } } else if (cmpStr(ILI2_ARC, pszTagName) == 0) { //Finish line and start arc if (ls->getNumPoints() > 1) { ogrCurve->addCurveDirectly(ls); ls = new OGRLineString(); } else { ls->empty(); } OGRCircularString *arc = new OGRCircularString(); // end point OGRPoint *ptEnd = new OGRPoint(); // point on the arc OGRPoint *ptOnArc = new OGRPoint(); // radius // double radius = 0; DOMElement *arcElem = dynamic_cast(lineElem->getFirstChild()); while (arcElem != nullptr) { char* pszTagName2 = XMLString::transcode(arcElem->getTagName()); char* pszObjValue = getObjValue(arcElem); if( pszObjValue ) { if (cmpStr("C1", pszTagName2) == 0) ptEnd->setX(CPLAtof(pszObjValue)); else if (cmpStr("C2", pszTagName2) == 0) ptEnd->setY(CPLAtof(pszObjValue)); else if (cmpStr("C3", pszTagName2) == 0) ptEnd->setZ(CPLAtof(pszObjValue)); else if (cmpStr("A1", pszTagName2) == 0) ptOnArc->setX(CPLAtof(pszObjValue)); else if (cmpStr("A2", pszTagName2) == 0) ptOnArc->setY(CPLAtof(pszObjValue)); else if (cmpStr("A3", pszTagName2) == 0) ptOnArc->setZ(CPLAtof(pszObjValue)); else if (cmpStr("R", pszTagName2) == 0) { // radius = CPLAtof(pszObjValue); } } CPLFree(pszObjValue); XMLString::release(&pszTagName2); arcElem = dynamic_cast(arcElem->getNextSibling()); } auto elemPrev = dynamic_cast(lineElem->getPreviousSibling()); if( elemPrev ) { OGRPoint *ptStart = getPoint(elemPrev); // COORD or ARC if( ptStart ) arc->addPoint(ptStart); delete ptStart; } arc->addPoint(ptOnArc); arc->addPoint(ptEnd); ogrCurve->addCurveDirectly(arc); delete ptEnd; delete ptOnArc; } /* else { // TODO: StructureValue in Polyline not yet supported } */ XMLString::release(&pszTagName); lineElem = dynamic_cast(lineElem->getNextSibling()); } if (ls->getNumPoints() > 1) { ogrCurve->addCurveDirectly(ls); } else { delete ls; } return ogrCurve; } static OGRCompoundCurve *getBoundary(DOMElement *elem) { DOMElement *lineElem = dynamic_cast(elem->getFirstChild()); if (lineElem != nullptr) { char* pszTagName = XMLString::transcode(lineElem->getTagName()); if (cmpStr(ILI2_POLYLINE, pszTagName) == 0) { XMLString::release(&pszTagName); return getPolyline(lineElem); } XMLString::release(&pszTagName); } return new OGRCompoundCurve(); } static OGRCurvePolygon *getPolygon(DOMElement *elem) { OGRCurvePolygon *pg = new OGRCurvePolygon(); DOMElement *boundaryElem = dynamic_cast(elem->getFirstChild()); // outer boundary while (boundaryElem != nullptr) { char* pszTagName = XMLString::transcode(boundaryElem->getTagName()); if (cmpStr(ILI2_BOUNDARY, pszTagName) == 0) { OGRCompoundCurve* poCC = getBoundary(boundaryElem); if( pg->addRingDirectly(poCC) != OGRERR_NONE ) { delete poCC; } } XMLString::release(&pszTagName); boundaryElem = dynamic_cast(boundaryElem->getNextSibling()); // inner boundaries } return pg; } OGRGeometry *ILI2Reader::getGeometry(DOMElement *elem, int type) { OGRGeometryCollection *gm = new OGRGeometryCollection(); DOMElement *childElem = elem; while (childElem != nullptr) { char* pszTagName = XMLString::transcode(childElem->getTagName()); switch (type) { case ILI2_COORD_TYPE : if (cmpStr(ILI2_COORD, pszTagName) == 0) { delete gm; XMLString::release(&pszTagName); return getPoint(childElem); } break; case ILI2_ARC_TYPE : // is it possible here? It have to be a ARC or COORD before (getPreviousSibling) if (cmpStr(ILI2_ARC, pszTagName) == 0) { delete gm; XMLString::release(&pszTagName); return getArc(childElem); } break; case ILI2_POLYLINE_TYPE : if (cmpStr(ILI2_POLYLINE, pszTagName) == 0) { delete gm; XMLString::release(&pszTagName); return getPolyline(childElem); } break; case ILI2_BOUNDARY_TYPE : if (cmpStr(ILI2_BOUNDARY, pszTagName) == 0) { delete gm; XMLString::release(&pszTagName); return getPolyline(childElem); } break; case ILI2_AREA_TYPE : if ((cmpStr(ILI2_AREA, pszTagName) == 0) || (cmpStr(ILI2_SURFACE, pszTagName) == 0)) { delete gm; XMLString::release(&pszTagName); return getPolygon(childElem); } break; default : if (type >= ILI2_GEOMCOLL_TYPE) { int subType = getGeometryTypeOfElem(childElem); //???? OGRGeometry* poSubGeom = getGeometry(childElem, subType); if( poSubGeom ) gm->addGeometryDirectly(poSubGeom); } break; } XMLString::release(&pszTagName); // GEOMCOLL childElem = dynamic_cast(childElem->getNextSibling()); } return gm; } int ILI2Reader::ReadModel(ImdReader *poImdReader, const char *modelFilename) { poImdReader->ReadModel(modelFilename); for (FeatureDefnInfos::const_iterator it = poImdReader->featureDefnInfos.begin(); it != poImdReader->featureDefnInfos.end(); ++it) { OGRLayer* layer = new OGRILI2Layer(it->GetTableDefnRef(), it->poGeomFieldInfos, nullptr); m_listLayer.push_back(layer); } return 0; } //Detect field name of value element static char* fieldName(DOMElement* elem) { DOMNode *node = elem; if (getGeometryTypeOfElem(elem)) { int depth = 0; // Depth of value elem node for (node = elem; node; node = node->getParentNode()) ++depth; //Field name is on level 4 node = elem; for (int d = 0; dgetParentNode(); } if( node == nullptr ) { CPLError(CE_Failure, CPLE_AssertionFailed, "node == NULL"); return CPLStrdup("***bug***"); } return CPLStrdup(transcode(node->getNodeName())); } void ILI2Reader::setFieldDefn(OGRFeatureDefn *featureDef, DOMElement* elem) { int type = 0; //recursively search children for (DOMNode *childNode = elem->getFirstChild(); type == 0 && childNode && childNode->getNodeType() == DOMNode::ELEMENT_NODE; childNode = childNode->getNextSibling()) { DOMElement* childElem = dynamic_cast(childNode); CPLAssert(childElem); type = getGeometryTypeOfElem(childElem); if (type == 0) { if (childElem->getFirstChild() && childElem->getFirstChild()->getNodeType() == DOMNode::ELEMENT_NODE) { setFieldDefn(featureDef, childElem); } else { char *fName = fieldName(childElem); if (featureDef->GetFieldIndex(fName) == -1) { CPLDebug( "OGR_ILI", "AddFieldDefn: %s",fName ); OGRFieldDefn oFieldDefn(fName, OFTString); featureDef->AddFieldDefn(&oFieldDefn); } CPLFree(fName); } } } } void ILI2Reader::SetFieldValues(OGRFeature *feature, DOMElement* elem) { int type = 0; //recursively search children for (DOMNode *childNode = elem->getFirstChild(); type == 0 && childNode && childNode->getNodeType() == DOMNode::ELEMENT_NODE; childNode = childNode->getNextSibling()) { DOMElement* childElem = dynamic_cast(childNode); CPLAssert(childElem); type = getGeometryTypeOfElem(childElem); if (type == 0) { if (childElem->getFirstChild() && childElem->getFirstChild()->getNodeType() == DOMNode::ELEMENT_NODE) { SetFieldValues(feature, childElem); } else { char *fName = fieldName(childElem); int fIndex = feature->GetFieldIndex(fName); if (fIndex != -1) { char * objVal = getObjValue(childElem); if (objVal == nullptr) objVal = getREFValue(childElem); // only to try feature->SetField(fIndex, objVal); CPLFree(objVal); } else { CPLDebug( "OGR_ILI","Attribute '%s' not found", fName); m_missAttrs.push_back(fName); } CPLFree(fName); } } else { char *fName = fieldName(childElem); int fIndex = feature->GetGeomFieldIndex(fName); OGRGeometry *geom = getGeometry(childElem, type); if( geom ) { if (fIndex == -1) { // Unknown model feature->SetGeometryDirectly(geom); } else { OGRwkbGeometryType geomType = feature->GetGeomFieldDefnRef(fIndex)->GetType(); if (geomType == wkbMultiLineString || geomType == wkbPolygon) { feature->SetGeomFieldDirectly(fIndex, geom->getLinearGeometry()); delete geom; } else { feature->SetGeomFieldDirectly(fIndex, geom); } } } CPLFree(fName); } } } // // ILI2Reader // IILI2Reader::~IILI2Reader() { } ILI2Reader::ILI2Reader() : m_pszFilename(nullptr), m_poILI2Handler(nullptr), m_poSAXReader(nullptr), m_bReadStarted(FALSE), m_bXercesInitialized(false) { SetupParser(); } ILI2Reader::~ILI2Reader() { CPLFree( m_pszFilename ); CleanupParser(); if( m_bXercesInitialized ) OGRDeinitializeXerces(); list::const_iterator layerIt = m_listLayer.begin(); while (layerIt != m_listLayer.end()) { OGRILI2Layer *tmpLayer = (OGRILI2Layer *)*layerIt; delete tmpLayer; ++layerIt; } } void ILI2Reader::SetSourceFile( const char *pszFilename ) { CPLFree( m_pszFilename ); m_pszFilename = CPLStrdup( pszFilename ); } int ILI2Reader::SetupParser() { if( !m_bXercesInitialized ) { if( !OGRInitializeXerces() ) return FALSE; m_bXercesInitialized = true; } // Cleanup any old parser. if( m_poSAXReader != nullptr ) CleanupParser(); // Create and initialize parser. m_poSAXReader = XMLReaderFactory::createXMLReader(); m_poILI2Handler = new ILI2Handler( this ); m_poSAXReader->setContentHandler( m_poILI2Handler ); m_poSAXReader->setErrorHandler( m_poILI2Handler ); m_poSAXReader->setLexicalHandler( m_poILI2Handler ); m_poSAXReader->setEntityResolver( m_poILI2Handler ); m_poSAXReader->setDTDHandler( m_poILI2Handler ); /* No Validation #if (OGR_ILI2_VALIDATION) m_poSAXReader->setFeature( XMLString::transcode("http://xml.org/sax/features/validation"), true); m_poSAXReader->setFeature( XMLString::transcode("http://xml.org/sax/features/namespaces"), true); m_poSAXReader->setFeature( XMLUni::fgSAX2CoreNameSpaces, true ); m_poSAXReader->setFeature( XMLUni::fgXercesSchema, true ); // m_poSAXReader->setDoSchema(true); // m_poSAXReader->setValidationSchemaFullChecking(true); #else */ XMLCh *tmpCh = XMLString::transcode("http://xml.org/sax/features/validation"); m_poSAXReader->setFeature(tmpCh, false); XMLString::release(&tmpCh); tmpCh = XMLString::transcode("http://xml.org/sax/features/namespaces"); m_poSAXReader->setFeature(tmpCh, false); XMLString::release(&tmpCh); //#endif m_bReadStarted = FALSE; return TRUE; } void ILI2Reader::CleanupParser() { if( m_poSAXReader == nullptr ) return; delete m_poSAXReader; m_poSAXReader = nullptr; delete m_poILI2Handler; m_poILI2Handler = nullptr; m_bReadStarted = FALSE; } int ILI2Reader::SaveClasses( const char *pszFile = nullptr ) { // Add logic later to determine reasonable default schema file. if( pszFile == nullptr ) return FALSE; VSILFILE* fp = VSIFOpenL(pszFile, "rb"); if( fp == nullptr ) return FALSE; InputSource* is = OGRCreateXercesInputSource(fp); // parse and create layers and features try { CPLDebug( "OGR_ILI", "Parsing %s", pszFile); m_poSAXReader->parse(*is); VSIFCloseL(fp); OGRDestroyXercesInputSource(is); } catch (const DOMException& toCatch) { // Can happen with createElement() in ILI2Handler::startElement() CPLError( CE_Failure, CPLE_AppDefined, "DOMException: %s\n", transcode(toCatch.getMessage()).c_str()); VSIFCloseL(fp); OGRDestroyXercesInputSource(is); return FALSE; } catch (const SAXException& toCatch) { CPLError( CE_Failure, CPLE_AppDefined, "Parsing failed: %s\n", transcode(toCatch.getMessage()).c_str()); VSIFCloseL(fp); OGRDestroyXercesInputSource(is); return FALSE; } if (!m_missAttrs.empty()) { m_missAttrs.sort(); m_missAttrs.unique(); string attrs = ""; list::const_iterator it; for (it = m_missAttrs.begin(); it != m_missAttrs.end(); ++it) attrs += *it + ", "; CPLError( CE_Warning, CPLE_NotSupported, "Failed to add new definition to existing layers, attributes not saved: %s", attrs.c_str() ); } return TRUE; } list ILI2Reader::GetLayers() { return m_listLayer; } int ILI2Reader::GetLayerCount() { return static_cast(m_listLayer.size()); } OGRLayer* ILI2Reader::GetLayer(const char* pszName) { for (list::reverse_iterator layerIt = m_listLayer.rbegin(); layerIt != m_listLayer.rend(); ++layerIt) { OGRFeatureDefn *fDef = (*layerIt)->GetLayerDefn(); if (cmpStr(fDef->GetName(), pszName) == 0) { return *layerIt; } } return nullptr; } int ILI2Reader::AddFeature(DOMElement *elem) { CPLString osName(transcode(elem->getTagName())); //CPLDebug( "OGR_ILI", "Reading layer: %s", osName.c_str() ); // test if this layer exist OGRILI2Layer* curLayer = dynamic_cast(GetLayer(osName)); bool newLayer = (curLayer == nullptr); // add a layer if (newLayer) { CPLDebug( "OGR_ILI", "Adding layer: %s", osName.c_str() ); OGRFeatureDefn* poFeatureDefn = new OGRFeatureDefn(osName); poFeatureDefn->SetGeomType( wkbUnknown ); GeomFieldInfos oGeomFieldInfos; curLayer = new OGRILI2Layer(poFeatureDefn, oGeomFieldInfos, nullptr); m_listLayer.push_back(curLayer); } // the feature and field definition OGRFeatureDefn *featureDef = curLayer->GetLayerDefn(); if (newLayer) { // add TID field OGRFieldDefn ofieldDefn (ILI2_TID, OFTString); featureDef->AddFieldDefn(&ofieldDefn); setFieldDefn(featureDef, elem); } // add the features OGRFeature *feature = new OGRFeature(featureDef); // assign TID int fIndex = feature->GetFieldIndex(ILI2_TID); if (fIndex != -1) { feature->SetField(fIndex, transcode(elem->getAttribute(xmlch_ILI2_TID)).c_str()); } else { CPLDebug( "OGR_ILI","'%s' not found", ILI2_TID); } SetFieldValues(feature, elem); curLayer->AddFeature(feature); return 0; } IILI2Reader *CreateILI2Reader() { return new ILI2Reader(); } void DestroyILI2Reader(IILI2Reader* reader) { if (reader) delete reader; }