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