Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: S-101 driver
4 : * Purpose: Implements OGRS101Reader
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "ogr_s101.h"
14 : #include "ogrs101readerconstants.h"
15 :
16 : #include <memory>
17 :
18 : /************************************************************************/
19 : /* GetMultiPointLayerName() */
20 : /************************************************************************/
21 :
22 : /* static */ std::string
23 285 : OGRS101Reader::GetMultiPointLayerName(const OGRSpatialReference &oSRS)
24 : {
25 285 : return oSRS.GetAxesCount() == 2
26 : ? "MultiPoint2D"
27 855 : : CPLSPrintf("MultiPoint3D_%s", LaunderCRSName(oSRS).c_str());
28 : }
29 :
30 : /************************************************************************/
31 : /* CreateMultiPointFeatureDefns() */
32 : /************************************************************************/
33 :
34 : /** Create the feature definition(s) for the MultiPoint layer(s)
35 : *
36 : * There is a layer per CRS used by multipoints.
37 : */
38 251 : bool OGRS101Reader::CreateMultiPointFeatureDefns()
39 : {
40 251 : bool bError = false;
41 :
42 : m_oMapCRSIdToMultiPointRecordIdx =
43 251 : CreateMapCRSIdToRecordIdxForMultiPoints(bError);
44 251 : if (bError)
45 2 : return false;
46 309 : for (const auto &[nCRSId, anRecordIdx] : m_oMapCRSIdToMultiPointRecordIdx)
47 : {
48 60 : const auto &oSRS = m_oMapSRS[nCRSId];
49 60 : const bool bIs2D = nCRSId == HORIZONTAL_CRS_ID;
50 : auto poFDefn = OGRFeatureDefnRefCountedPtr::makeInstance(
51 60 : GetMultiPointLayerName(oSRS).c_str());
52 60 : poFDefn->SetGeomType(bIs2D ? wkbMultiPoint : wkbMultiPoint25D);
53 120 : poFDefn->GetGeomFieldDefn(0)->SetSpatialRef(
54 120 : OGRSpatialReferenceRefCountedPtr::makeClone(&oSRS).get());
55 : {
56 120 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_ID, OFTInteger);
57 60 : poFDefn->AddFieldDefn(&oFieldDefn);
58 : }
59 : {
60 120 : OGRFieldDefn oFieldDefn(OGR_FIELD_NAME_RECORD_VERSION, OFTInteger);
61 60 : poFDefn->AddFieldDefn(&oFieldDefn);
62 : }
63 60 : if (!InferFeatureDefn(m_oMultiPointRecordIndex, MRID_FIELD, INAS_FIELD,
64 60 : anRecordIdx, *poFDefn, m_oMapFieldDomains))
65 : {
66 0 : return false;
67 : }
68 60 : m_oMapMultiPointFeatureDefn[nCRSId] = std::move(poFDefn);
69 : }
70 :
71 249 : return true;
72 : }
73 :
74 : /************************************************************************/
75 : /* GetCRSIdForMultiPointRecord() */
76 : /************************************************************************/
77 :
78 : /** Return the CRS id for a given MultiPoint record, or INVALID_CRS_ID on error */
79 : OGRS101Reader::CRSId
80 141 : OGRS101Reader::GetCRSIdForMultiPointRecord(const DDFRecord *poRecord,
81 : int iRecord, int nRecordID) const
82 : {
83 141 : if (nRecordID < 0)
84 75 : nRecordID = poRecord->GetIntSubfield(MRID_FIELD, 0, RCID_SUBFIELD, 0);
85 :
86 10 : const auto GetErrorContext = [iRecord, nRecordID]()
87 : {
88 5 : if (iRecord >= 0)
89 5 : return CPLSPrintf("Record index=%d of MRID", iRecord);
90 : else
91 0 : return CPLSPrintf("Record ID=%d of MRID", nRecordID);
92 141 : };
93 :
94 141 : if (poRecord->FindField(C3IL_FIELD))
95 : {
96 : const CRSId nVCID =
97 20 : poRecord->GetIntSubfield(C3IL_FIELD, 0, VCID_SUBFIELD, 0);
98 20 : if (nVCID == HORIZONTAL_CRS_ID)
99 : {
100 2 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
101 : CPLSPrintf("%s: VCID subfield = %d of C3IL "
102 : "field points to a non-3D CRS.",
103 : GetErrorContext(), static_cast<int>(nVCID))));
104 2 : return INVALID_CRS_ID;
105 : }
106 18 : else if (!cpl::contains(m_oMapSRS, nVCID))
107 : {
108 2 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
109 : CPLSPrintf("%s: Unknown value %d for VCID subfield of C3IL "
110 : "field.",
111 : GetErrorContext(), static_cast<int>(nVCID))));
112 2 : return INVALID_CRS_ID;
113 : }
114 : else
115 : {
116 16 : return nVCID;
117 : }
118 : }
119 121 : else if (poRecord->FindField(C2IL_FIELD))
120 : {
121 120 : return HORIZONTAL_CRS_ID;
122 : }
123 : else
124 : {
125 1 : CPL_IGNORE_RET_VAL(EMIT_ERROR_OR_WARNING(
126 : CPLSPrintf("%s: No C2IL or C3IL field found.", GetErrorContext())));
127 1 : return INVALID_CRS_ID;
128 : }
129 : }
130 :
131 : /************************************************************************/
132 : /* CreateMapCRSIdToRecordIdxForMultiPoints() */
133 : /************************************************************************/
134 :
135 : /** Browse through m_oMultiPointRecordIndex to identify which record belongs to
136 : * each CRS and create a map from each CRS id to the record indices that use
137 : * it.
138 : */
139 : std::map<OGRS101Reader::CRSId, std::vector<int>>
140 251 : OGRS101Reader::CreateMapCRSIdToRecordIdxForMultiPoints(bool &bError) const
141 : {
142 502 : std::map<OGRS101Reader::CRSId, std::vector<int>> map;
143 :
144 251 : const int nRecords = m_oMultiPointRecordIndex.GetCount();
145 324 : for (int iRecord = 0; iRecord < nRecords; ++iRecord)
146 : {
147 75 : const auto poRecord = m_oMultiPointRecordIndex.GetByIndex(iRecord);
148 : const CRSId nCRSId = GetCRSIdForMultiPointRecord(poRecord, iRecord,
149 75 : /* nRecordID = */ -1);
150 75 : if (nCRSId == INVALID_CRS_ID)
151 : {
152 5 : if (m_bStrict)
153 : {
154 2 : bError = true;
155 2 : return {};
156 : }
157 : }
158 : else
159 : {
160 70 : map[nCRSId].push_back(iRecord);
161 : }
162 : }
163 :
164 249 : return map;
165 : }
166 :
167 : /************************************************************************/
168 : /* ReadMultiPointGeometry() */
169 : /************************************************************************/
170 :
171 : std::unique_ptr<OGRMultiPoint>
172 387 : OGRS101Reader::ReadMultiPointGeometry(const DDFRecord *poRecord, int iRecord,
173 : int nRecordID,
174 : const OGRSpatialReference *poSRS) const
175 : {
176 387 : const bool bIs3D = poRecord->FindField(C3IL_FIELD) != nullptr;
177 387 : const char *pszCoordFieldName = bIs3D ? C3IL_FIELD : C2IL_FIELD;
178 774 : const auto apoCoordFields = poRecord->GetFields(pszCoordFieldName);
179 387 : if (apoCoordFields.empty())
180 0 : return nullptr;
181 :
182 774 : auto poMP = std::make_unique<OGRMultiPoint>();
183 387 : poMP->assignSpatialReference(poSRS);
184 772 : for (const auto *poCoordField : apoCoordFields)
185 : {
186 : const int nCoordCount =
187 86 : bIs3D && poCoordField->GetParts().size() == 2
188 473 : ? poCoordField->GetParts()[1]->GetRepeatCount()
189 301 : : poCoordField->GetRepeatCount();
190 :
191 1084 : for (int iPnt = 0; iPnt < nCoordCount; ++iPnt)
192 : {
193 : auto poPoint = ReadPointGeometryInternal(
194 : poRecord, iRecord, nRecordID, iPnt, poSRS, bIs3D, poCoordField,
195 699 : MRID_FIELD);
196 699 : if (!poPoint)
197 2 : return nullptr;
198 697 : poMP->addGeometry(std::move(poPoint));
199 : }
200 : }
201 385 : return poMP;
202 : }
203 :
204 : /************************************************************************/
205 : /* FillFeatureMultiPoint() */
206 : /************************************************************************/
207 :
208 : /** Fill the content of the provided feature from the identified record
209 : * (of m_oMultiPointRecordIndex).
210 : */
211 223 : bool OGRS101Reader::FillFeatureMultiPoint(const DDFRecordIndex &oIndex,
212 : int iRecord,
213 : OGRFeature &oFeature) const
214 : {
215 223 : const auto poRecord = oIndex.GetByIndex(iRecord);
216 223 : CPLAssert(poRecord);
217 :
218 : const OGRSpatialReference *poSRS =
219 223 : oFeature.GetDefnRef()->GetGeomFieldDefn(0)->GetSpatialRef();
220 : auto poMP =
221 446 : ReadMultiPointGeometry(poRecord, iRecord, /* nRecordID = */ -1, poSRS);
222 223 : if (poMP)
223 : {
224 221 : oFeature.SetGeometry(std::move(poMP));
225 : }
226 2 : else if (m_bStrict)
227 2 : return false;
228 :
229 442 : return FillFeatureAttributes(oIndex, iRecord, INAS_FIELD, oFeature) &&
230 221 : FillFeatureWithNonAttrAssocSubfields(poRecord, iRecord, INAS_FIELD,
231 221 : oFeature);
232 : }
|