/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #include "OdaCommon.h" #include "DbSystemServices.h" #include "DbHostAppServices.h" #include "OdGeoDataPEImpl.h" #include "OdDbGeoCoordinateSystem.h" #include "Ge/GeMatrix3d.h" #include "Ge/GeLineSeg2d.h" #include "Ge/avgpc.h" #include "SpatialReference/OdCsdFormatter.h" ODRX_CONS_DEFINE_MEMBERS(OdDbGeoDataPEImpl, OdDbGeoDataPE, RXIMPL_CONSTR); ODRX_CONS_DEFINE_MEMBERS(OdDbGeoDataExportPEImpl, OdDbBaseGeoDataExportPE, RXIMPL_CONSTR); OdResult OdDbGeoDataPEImpl::transformFromLonLatAlt(const OdDbGeoData* pGeoData, const double & dLongitude, const double & dLatitude, const double & dAltitude, double & dDwgX, double & dDwgY, double & dDwgZ) { if (pGeoData == NULL) { return eNullPtr; } OdGePoint3d res; if (!pGeoData->coordinateSystem().isEmpty()) { OdDbGeoCoordinateSystemTransformerPtr pTransformer; if (eOk == pGeoData->getSimpleTransformerFromLLA(pTransformer)) { OdGePoint3d lla(dLongitude, dLatitude, dAltitude); if (eOk == pTransformer->transformPoint(lla, res)) { if (pGeoData->coordinateType() == OdDbGeoData::kCoordTypLocal) { res.transformBy(pGeoData->getMatrixLocalFromLLA()); } dDwgX = res.x; dDwgY = res.y; dDwgZ = res.z; return eOk; } } } if (pGeoData->numMeshPoints() == 0 || pGeoData->numMeshFaces() == 0) { OdGePoint3d lla(dLongitude, dLatitude, dAltitude); lla.transformBy(pGeoData->getMatrixFromLLA()); res = lla + pGeoData->designPoint().asVector(); dDwgX = res.x; dDwgY = res.y; dDwgZ = res.z; return eOk; } OdGePoint2d ptXY; OdResult errCode = pGeoData->meshTransformFromLLA(OdGePoint2d(dLongitude, dLatitude), ptXY); dDwgX = ptXY.x; dDwgY = ptXY.y; dDwgZ = dAltitude; return errCode; } OdResult OdDbGeoDataPEImpl::transformToLonLatAlt(const OdDbGeoData* pGeoData, const double & dDwgX, const double & dDwgY, const double & dDwgZ, double & dLongitude, double & dLatitude, double & dAltitude) { if(pGeoData == NULL) { return eNullPtr; } OdGePoint3d res; if (!pGeoData->coordinateSystem().isEmpty()) { OdGePoint3d ptOld(dDwgX, dDwgY, dDwgZ); if (pGeoData->coordinateType() == OdDbGeoData::kCoordTypLocal) { ptOld.transformBy(pGeoData->getMatrixLocalToLLA()); } OdDbGeoCoordinateSystemTransformerPtr pTransformer; if (eOk == pGeoData->getSimpleTransformerToLLA(pTransformer) && eOk == pTransformer->transformPoint(ptOld, res)) { dLongitude = res.x; dLatitude = res.y; dAltitude = res.z; return eOk; } } if (pGeoData->numMeshPoints() == 0 || pGeoData->numMeshFaces() == 0) { OdGePoint3d ptOld(dDwgX, dDwgY, dDwgZ); res = ptOld - pGeoData->designPoint().asVector(); res.transformBy(pGeoData->getMatrixToLLA()); //X corrections if (fabs(res.x) > 180.) { double dX = fmod(res.x, 360.); if (res.x <= 0.) { if (dX < -180.) { dX += 360.; } } else if (dX > 180.) { dX -= 360.; } res.x = dX; } //Y corrections if (fabs(res.y) > 90.) { double dY = fmod(res.y, 360.); if (res.y <= 0.) { if (dY >= -270.) { if (dY < -90.) { dY = -180. - dY; } } else { dY += 360.; } } else if (dY >= 90.) { if (dY >= 270.) { dY -= 360.; } else { dY = 180. - dY; } } res.y = dY; } dLongitude = res.x; dLatitude = res.y; dAltitude = res.z; return eOk; } OdGePoint2d ptGeo; OdResult errCode = pGeoData->meshTransformToLLA(OdGePoint2d(dDwgX, dDwgY), ptGeo); dLongitude = ptGeo.x; dLatitude = ptGeo.y; dAltitude = dDwgZ; return errCode; } bool OdDbGeoDataPEImpl::validateCs(const OdString& /*sCoordinateSystem*/) { //validation presented in OdDbGeoCoordinateSystem.h classes //this function is for user validation stuff return true; } OdResult OdDbGeoDataPEImpl::geoCoordinateSystemWillChange(OdDbGeoData* pGeoData, const OdString& /*sNewCsId*/) { if (pGeoData == NULL) { return eNullPtr; } return eOk; } OdResult OdDbGeoDataPEImpl::geoCoordinateSystemChanged(OdDbGeoData* pGeoData, const OdString& /*sOldCsId*/) { if (pGeoData == NULL) { return eNullPtr; } return eOk; } //OdDbGeoDataExportPEImpl OdResult OdDbGeoDataExportPEImpl::getGeoDataParams(const OdDbBaseDatabase* pDb, OdString& sWktDef, int& type, int& epsgCode) const { if(!pDb) return eNullPtr; const OdDbDatabase* pDatabase = dynamic_cast(pDb); if(!pDatabase) return eNullPtr; OdDbObjectId idGeoData; OdDbGeoDataPtr pGeoData; if(eOk != oddbGetGeoDataObjId(pDatabase, idGeoData) || idGeoData.isNull() || (pGeoData = idGeoData.openObject()).isNull()) return eNullPtr; OdDbGeoCoordinateSystemPtr pCs; if(OdDbGeoCoordinateSystem::create(pGeoData->coordinateSystem(), pCs) != eOk) return eInvalidInput; OdDbGeoCoordinateSystem::Type geoType; if(pCs->getEpsgCode(epsgCode) != eOk || pCs->getWktRepresentation(sWktDef) != eOk || pCs->getType(geoType) != eOk) return eInvalidInput; if(geoType == OdDbGeoCoordinateSystem::kTypeGeographic) type = 0; else if(geoType == OdDbGeoCoordinateSystem::kTypeProjected) type = 1; else return eNotApplicable; return eOk; } bool intersectContours(const OdGePoint2dArray& geoPoints, const OdGePoint2dArray& clipPoints, OdGePoint2dArray& outPoints) { gpc_polygon gpMap, gpViewport, gpResult; gpMap.num_contours = 1; gpMap.hole = 0; gpMap.contour = ::gpc_alloc(1); gpMap.contour->num_vertices = geoPoints.size(); gpMap.contour->vertex = ::gpc_alloc(geoPoints.size());; for(OdUInt32 i = 0; i < geoPoints.size(); i++) { gpMap.contour[0].vertex[i].x = geoPoints[i].x; gpMap.contour[0].vertex[i].y = geoPoints[i].y; } gpViewport.num_contours = 1; gpViewport.hole = 0; gpViewport.contour = ::gpc_alloc(1); gpViewport.contour->num_vertices = clipPoints.size(); gpViewport.contour->vertex = ::gpc_alloc(clipPoints.size());; for(OdUInt32 i = 0; i < clipPoints.size(); i++) { gpViewport.contour[0].vertex[i].x = clipPoints[i].x; gpViewport.contour[0].vertex[i].y = clipPoints[i].y; } gpResult.hole = NULL; gpResult.contour = NULL; gpResult.num_contours = 0; ::gpc_polygon_clip(GPC_INT, &gpMap, &gpViewport, &gpResult); for(int j = 0; j < gpResult.num_contours; j++) { for(int k = 0; k < gpResult.contour[j].num_vertices; k++) outPoints.push_back(OdGePoint2d(gpResult.contour[j].vertex[k].x, gpResult.contour[j].vertex[k].y)); } gpc_free_polygon(&gpMap); gpc_free_polygon(&gpViewport); gpc_free_polygon(&gpResult); return true; } void expandExtents(OdGePoint2dArray& arrPoints) { OdGePoint2dArray arrTmp; const OdUInt32 nSize = arrPoints.size(); const OdUInt32 nStepsCount = 9; arrTmp.resize(nSize * nStepsCount); OdUInt32 nIndex = 0; for(OdUInt32 i = 0; i < nSize; ++i) { OdGeVector2d vShift = (arrPoints[(i + 1) % nSize] - arrPoints[i]) / nStepsCount; for(OdUInt32 j = 0; j < nStepsCount; ++j) { arrTmp[nIndex++] = arrPoints[i] + vShift * j; } } arrPoints = arrTmp; } OdResult getGeoMapExtents(const OdString& coordSys, OdGePoint2dArray& geoPoints) { if(!coordSys.isEmpty()) { OdDbGeoCoordinateSystemPtr pCs; if(OdDbGeoCoordinateSystem::create(coordSys, pCs) != eOk) return eInvalidInput; OdGeExtents2d extGeo; if(pCs->getGeodeticExtents(extGeo) != eOk) return eInvalidInput; OdGePoint2d ptMin; OdGePoint2d ptMax; ptMin.x = odmin(odmax(extGeo.minPoint().x, -180/*OdBingMaps::MinLongitude*/), 180/*OdBingMaps::MaxLongitude*/); ptMin.y = odmin(odmax(extGeo.minPoint().y, -85.05112878/*OdBingMaps::MinLatitude*/), 85.05112878/*OdBingMaps::MaxLatitude*/); ptMax.x = odmin(odmax(extGeo.maxPoint().x, -180/*OdBingMaps::MinLongitude*/), 180/*OdBingMaps::MaxLongitude*/); ptMax.y = odmin(odmax(extGeo.maxPoint().y, -85.05112878/*OdBingMaps::MinLatitude*/), 85.05112878/*OdBingMaps::MaxLatitude*/); OdGeVector2d vShift(0.01, 0.01); ptMin += vShift; ptMax -= vShift; geoPoints.resize(4); geoPoints[0].set(ptMin.x, ptMin.y); geoPoints[1].set(ptMax.x, ptMin.y); geoPoints[2].set(ptMax.x, ptMax.y); geoPoints[3].set(ptMin.x, ptMax.y); return eOk; } return eInvalidInput; } void simplifyMapExtents(OdGePoint2dArray& extents) { OdGePoint2dArray arrRes; OdGeLineSeg2d seg1, seg2; for(OdUInt32 i = 0; i < extents.size(); ++i) { if(arrRes.size() > 1) { seg1.set(arrRes[arrRes.size() - 2], extents[i]); seg2.set(arrRes[arrRes.size() - 1], extents[i]); //in case of the point on the same line, we can get rid of intermediate point without quality loss if(seg1.isParallelTo(seg2)) arrRes[arrRes.size() - 1] = extents[i]; else arrRes.push_back(extents[i]); } else arrRes.push_back(extents[i]); } //check the last point if(arrRes.size() > 2) //if not, means smth went wrong, so just keep the source array { seg1.set(arrRes[arrRes.size() - 2], arrRes[0]); seg2.set(arrRes[arrRes.size() - 1], arrRes[0]); if(seg1.isParallelTo(seg2)) arrRes.removeLast(); if(arrRes.size() > 2) extents = arrRes; } } //convert drawing coordinates to geo coordinates or opposite depending on bToLLA flag OdResult convertLLA(const OdDbGeoData* pGeoData, OdGePoint2dArray& from, OdGePoint2dArray& to, bool bToLLA) { if(!pGeoData) return eNullPtr; if(from.isEmpty()) return eInvalidInput; OdGePoint3d ptBuf; OdResult res = eOk; to.clear(); to.resize(from.size()); for(OdUInt32 i = 0; i < from.size(); ++i) { if(bToLLA) res = pGeoData->transformToLonLatAlt(OdGePoint3d(from[i].x, from[i].y, 0.), ptBuf); else res = pGeoData->transformFromLonLatAlt(OdGePoint3d(from[i].x, from[i].y, 0.), ptBuf); if(res != eOk) break; to[i] = ptBuf.convert2d(); } return res; } OdResult OdDbGeoDataExportPEImpl::getGeoExtents(const OdDbBaseDatabase* pDb, OdGePoint2dArray& geoPoints, OdGePoint2dArray& clipPoints) const { if(!pDb) return eNullPtr; const OdDbDatabase* pDatabase = dynamic_cast(pDb); if(!pDatabase) return eNullPtr; OdDbObjectId idGeoData; OdDbGeoDataPtr pGeoData; if(eOk != oddbGetGeoDataObjId(pDatabase, idGeoData) || idGeoData.isNull() || (pGeoData = idGeoData.openObject()).isNull()) return eNullPtr; if(clipPoints.size() && clipPoints.size() != 4) //extents of viewport are expected here return eInvalidInput; OdResult res = getGeoMapExtents(pGeoData->coordinateSystem(), geoPoints); if(res != eOk) return res; //the real for of the map, may be not the same as its extents, so we need to expand the geo coordinates to get the correct result expandExtents(geoPoints); if(clipPoints.isEmpty()) //just return the map extents { res = convertLLA(pGeoData, geoPoints, clipPoints, false); if(res != eOk) { geoPoints.clear(); clipPoints.clear(); return res; } simplifyMapExtents(clipPoints); res = convertLLA(pGeoData, clipPoints, geoPoints, true); if(res != eOk) { geoPoints.clear(); clipPoints.clear(); return res; } } else //find intersection of map extents and viewport { OdGePoint2dArray tmpPoints, intersectedPoints; res = convertLLA(pGeoData, geoPoints, tmpPoints, false); if(res != eOk) { geoPoints.clear(); clipPoints.clear(); return res; } simplifyMapExtents(tmpPoints); try { if(!intersectContours(tmpPoints, clipPoints, intersectedPoints) || intersectedPoints.size() < 3) { geoPoints.clear(); clipPoints.clear(); return eInvalidInput; } } catch (...) { geoPoints.clear(); clipPoints.clear(); return eInvalidInput; } clipPoints = intersectedPoints; res = convertLLA(pGeoData, clipPoints, geoPoints, true); if(res != eOk) { geoPoints.clear(); clipPoints.clear(); return res; } } return eOk; }