1 /******************************************************************************
2 * Copyright (c) 2014, Hobu Inc.
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following
8 * conditions are met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided
15 * with the distribution.
16 * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
17 * names of its contributors may be used to endorse or promote
18 * products derived from this software without specific prior
19 * written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
28 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
32 * OF SUCH DAMAGE.
33 ****************************************************************************/
34
35 #include <pdal/PointLayout.hpp>
36 #include <pdal/util/Algorithm.hpp>
37
38 namespace pdal
39 {
40
PointLayout()41 PointLayout::PointLayout()
42 : m_detail(Dimension::COUNT)
43 , m_used()
44 , m_propIds()
45 , m_nextFree(Dimension::PROPRIETARY)
46 , m_pointSize(0)
47 , m_finalized(false)
48 {
49 int id = 0;
50 for (auto& d : m_detail)
51 {
52 d.setId((Dimension::Id)id);
53 id++;
54 }
55 }
56
57
finalize()58 void PointLayout::finalize()
59 {
60 m_finalized = true;
61 }
62
63
registerDims(std::vector<Dimension::Id> ids)64 void PointLayout::registerDims(std::vector<Dimension::Id> ids)
65 {
66 for (auto ii = ids.begin(); ii != ids.end(); ++ii)
67 registerDim(*ii);
68 }
69
70
registerDims(Dimension::Id * id)71 void PointLayout::registerDims(Dimension::Id *id)
72 {
73 while (*id != Dimension::Id::Unknown)
74 registerDim(*id++);
75 }
76
77
registerDim(Dimension::Id id)78 void PointLayout::registerDim(Dimension::Id id)
79 {
80 registerDim(id, Dimension::defaultType(id));
81 }
82
83
registerDim(Dimension::Id id,Dimension::Type type)84 void PointLayout::registerDim(Dimension::Id id, Dimension::Type type)
85 {
86 using namespace Dimension;
87
88 Detail dd = m_detail[Utils::toNative(id)];
89
90 // Force X, Y and Z to always be doubles.
91 if (id == Id::X || id == Id::Y || id == Id::Z)
92 type = Type::Double;
93 else
94 type = resolveType(type, dd.type());
95 dd.setType(type);
96 update(dd, Dimension::name(id));
97 }
98
99
assignDim(const std::string & name,Dimension::Type type)100 Dimension::Id PointLayout::assignDim(const std::string& name,
101 Dimension::Type type)
102 {
103 if (!Dimension::nameValid(name))
104 throw pdal_error("Can't create dimension with invalid name '" + name + "'.");
105 if (m_nextFree == Dimension::COUNT)
106 throw pdal_error("No dimension IDs remaining for assignment.");
107 Dimension::Id id = (Dimension::Id)m_nextFree;
108
109 auto di = m_propIds.find(name);
110 if (di != m_propIds.end())
111 id = di->second;
112 Dimension::Detail dd = m_detail[Utils::toNative(id)];
113 dd.setType(resolveType(type, dd.type()));
114 if (update(dd, name))
115 {
116 if (di == m_propIds.end())
117 {
118 m_nextFree++;
119 m_propIds[name] = id;
120 }
121 return id;
122 }
123 return Dimension::Id::Unknown;
124 }
125
126
registerOrAssignDim(const std::string name,Dimension::Type type)127 Dimension::Id PointLayout::registerOrAssignDim(const std::string name,
128 Dimension::Type type)
129 {
130 Dimension::Id id = Dimension::id(name);
131 if (id != Dimension::Id::Unknown)
132 {
133 registerDim(id, type);
134 return id;
135 }
136 return assignDim(name, type);
137 }
138
139
dimTypes() const140 DimTypeList PointLayout::dimTypes() const
141 {
142 DimTypeList dimTypes;
143
144 const Dimension::IdList& ids = dims();
145 for (auto ii = ids.begin(); ii != ids.end(); ++ii)
146 dimTypes.push_back(DimType(*ii, dimType(*ii)));
147 return dimTypes;
148 }
149
150
findDimType(const std::string & name) const151 DimType PointLayout::findDimType(const std::string& name) const
152 {
153 Dimension::Id id = findDim(name);
154 return DimType(id, dimType(id));
155 }
156
157
findDim(const std::string & name) const158 Dimension::Id PointLayout::findDim(const std::string& name) const
159 {
160 Dimension::Id id = Dimension::id(name);
161 if (dimType(id) != Dimension::Type::None)
162 return id;
163 return findProprietaryDim(name);
164 }
165
166
167 Dimension::Id
findProprietaryDim(const std::string & name) const168 PointLayout::findProprietaryDim(const std::string& name) const
169 {
170 auto di = m_propIds.find(name);
171 return (di != m_propIds.end() ? di->second :
172 Dimension::Id::Unknown);
173 }
174
175
dimName(Dimension::Id id) const176 std::string PointLayout::dimName(Dimension::Id id) const
177 {
178 std::string name = Dimension::name(id);
179 if (!name.empty())
180 return name;
181 for (auto pi = m_propIds.begin();
182 pi != m_propIds.end(); ++pi)
183 if (pi->second == id)
184 return pi->first;
185 return "";
186 }
187
188
hasDim(Dimension::Id id) const189 bool PointLayout::hasDim(Dimension::Id id) const
190 {
191 return m_detail[Utils::toNative(id)].type() != Dimension::Type::None;
192 }
193
194
dims() const195 const Dimension::IdList& PointLayout::dims() const
196 {
197 return m_used;
198 }
199
200
dimType(Dimension::Id id) const201 Dimension::Type PointLayout::dimType(Dimension::Id id) const
202 {
203 return dimDetail(id)->type();
204 }
205
206
dimSize(Dimension::Id id) const207 size_t PointLayout::dimSize(Dimension::Id id) const
208 {
209 return dimDetail(id)->size();
210 }
211
212
dimOffset(Dimension::Id id) const213 size_t PointLayout::dimOffset(Dimension::Id id) const
214 {
215 return dimDetail(id)->offset();
216 }
217
218
pointSize() const219 size_t PointLayout::pointSize() const
220 {
221 return m_pointSize;
222 }
223
224
225 // Update the point layout given dimension detail and the dimension's name.
update(Dimension::Detail dd,const std::string & name)226 bool PointLayout::update(Dimension::Detail dd, const std::string& name)
227 {
228 if (m_finalized)
229 throw pdal_error("Can't update layout after points have been added.");
230
231 // If we have a list of allowed dimensions and this dimension isn't listed, return false.
232 if (m_allowedDimNames.size() && !Utils::contains(m_allowedDimNames, Utils::toupper(name)))
233 return false;
234
235 Dimension::DetailList detail;
236
237 // Update the list of used IDs.
238 bool used = Utils::contains(m_used, dd.id());
239 for (auto id : m_used)
240 {
241 if (id == dd.id())
242 detail.push_back(dd);
243 else
244 detail.push_back(m_detail[Utils::toNative(id)]);
245 }
246 if (!used)
247 detail.push_back(dd);
248
249 // Find the dimension in the list that we're referring to with
250 // this update.
251 auto di = std::find_if(detail.begin(), detail.end(),
252 [dd](const Dimension::Detail& td){ return td.id() == dd.id(); });
253 Dimension::Detail *cur = &(*di);
254
255 {
256 auto sorter = [](const Dimension::Detail& d1,
257 const Dimension::Detail& d2) -> bool
258 {
259 if (d1.size() > d2.size())
260 return true;
261 if (d1.size() < d2.size())
262 return false;
263 return d1.id() < d2.id();
264 };
265
266 // Sort dimensions based on size and then on ID.
267 int offset = 0;
268 std::sort(detail.begin(), detail.end(), sorter);
269 for (auto& d : detail)
270 {
271 d.setOffset(offset);
272 offset += (int)d.size();
273 }
274 //NOTE - I tried forcing all points to be aligned on 8-byte boundaries
275 // in case this would matter to the optimized memcpy, but it made
276 // no difference. No sense wasting space for no difference.
277 m_pointSize = (size_t)offset;
278 }
279
280 if (!used)
281 m_used.push_back(dd.id());
282
283 // Update the detail for the ID.
284 for (auto& dtemp : detail)
285 m_detail[Utils::toNative(dtemp.id())] = dtemp;
286
287 return true;
288 }
289
290
291 // Given two types, find a type that can represent the values of both.
resolveType(Dimension::Type t1,Dimension::Type t2)292 Dimension::Type PointLayout::resolveType(Dimension::Type t1,
293 Dimension::Type t2)
294 {
295 using namespace Dimension;
296 if (t1 == Type::None && t2 != Type::None)
297 return t2;
298 if (t2 == Type::None && t1 != Type::None)
299 return t1;
300 if (t1 == t2)
301 return t1;
302 if (base(t1) == base(t2))
303 return (std::max)(t1, t2);
304 //Prefer floating to non-floating.
305 if (base(t1) == BaseType::Floating && base(t2) != BaseType::Floating)
306 return t1;
307 if (base(t2) == BaseType::Floating && base(t1) != BaseType::Floating)
308 return t2;
309 // Now we're left with cases of a signed/unsigned mix.
310 // If the unsigned type is smaller, take the signed type.
311 if (base(t1) == BaseType::Unsigned && size(t1) < size(t2))
312 return t2;
313 if (base(t2) == BaseType::Unsigned && size(t2) < size(t1))
314 return t1;
315 // Signed type is smaller or the the sizes are equal.
316 switch ((std::max)(size(t1), size(t2)))
317 {
318 case 1:
319 return Type::Signed16;
320 case 2:
321 return Type::Signed32;
322 case 4:
323 return Type::Signed64;
324 default:
325 return Type::Double;
326 }
327 }
328
toMetadata() const329 MetadataNode PointLayout::toMetadata() const
330 {
331
332 MetadataNode root("schema");
333
334 for (const auto& id : dims())
335 {
336 MetadataNode dim("dimensions");
337 dim.add("name", dimName(id));
338 Dimension::Type t = dimType(id);
339 dim.add("type", Dimension::toName(Dimension::base(t)));
340 dim.add("size", dimSize(id));
341 root.addList(dim);
342 }
343
344 return root;
345 }
346
setAllowedDims(StringList dimNames)347 void PointLayout::setAllowedDims(StringList dimNames)
348 {
349 if (dimNames.empty())
350 return;
351
352 for (std::string& s : dimNames)
353 s = Utils::toupper(s);
354 for (const std::string& xyz : StringList { "X", "Y", "Z" })
355 if (!Utils::contains(dimNames, xyz))
356 dimNames.push_back(xyz);
357 m_allowedDimNames = dimNames;
358 }
359
360 } // namespace pdal
361
362
363