1 
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2017-2018 Artem Bishev, The appleseedhq Organization
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28 
29 #pragma once
30 
31 // Boost headers.
32 #include "boost/algorithm/string.hpp"
33 #include "boost/lexical_cast.hpp"
34 #include "boost/unordered_map.hpp"
35 
36 // Standard headers.
37 #include <string>
38 #include <vector>
39 
40 // Foundation headers.
41 #include "foundation/core/exceptions/exception.h"
42 #include "foundation/utility/foreach.h"
43 #include "foundation/utility/string.h"
44 #include "foundation/utility/test.h"
45 
46 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ParseFormat);
47 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ReadLine_IgnoreEmptyLines);
48 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ReadLine_DoNotIgnoreEmptyLines);
49 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, CheckEmpty);
50 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ParseKeywords);
51 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Empty);
52 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Good);
53 DECLARE_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Bad);
54 
55 namespace foundation
56 {
57 
58 //
59 // Class for parsing IESNA LM-63 Photometric Data File
60 // and storing its contents.
61 //
62 
63 class IESParser
64 {
65   public:
66     // Constructor.
67     IESParser();
68 
69     // Exception that can store erroneous line number.
70     class Exception
71       : public foundation::Exception
72     {
73       public:
74         Exception(const char* message, const int line);
75 
line()76         virtual int line() const
77         {
78             return m_line_number;
79         }
80 
81       protected:
82         int m_line_number;
83     };
84 
85     // Exception thrown when file format violates the IES specifications.
86     class ParsingException : public Exception
87     {
88       public:
ParsingException(const char * message,int line)89         ParsingException(const char* message, int line)
90           : Exception(message, line)
91         {
92         }
93     };
94 
95     // Exception thrown when the feature of IES file is not supported by this parser.
96     class NotSupportedException : public Exception
97     {
98       public:
NotSupportedException(const char * message,int line)99         NotSupportedException(const char* message, int line)
100           : Exception(message, line)
101         {
102         }
103     };
104 
105     // Dictionary containing IES file keywords and corresponding values.
106     typedef boost::unordered_map<std::string, std::string> KeywordsDictionary;
107 
108     // Type of IES file format.
109     enum Format
110     {
111         UnknownFormat,
112         Format1986,
113         Format1991,
114         Format1995,
115         Format2002,
116     };
117 
118     // How TILT information is specified in the file.
119     enum TiltSpecification
120     {
121         IncludeTilt,                    // TILT is included in this IES file
122         TiltFromFile,                   // TILT is specified in the separate file
123         NoTilt                          // the lamp output does not vary as a function
124                                         // of the luminaire tilt angle (and TILT is not specified)
125     };
126 
127     // Lamp-to-luminaire geometry types.
128     // For more information, see the IESNA LM-63 specifications
129     enum LampToLuminaireGeometry
130     {
131         GeometryNotSpecified,
132         VerticalGeometry,
133         HorizontalInvariantGeometry,
134         HorizontalNonInvariantGeometry
135     };
136 
137     // Photometric types.
138     enum PhotometricType
139     {
140         PhotometricTypeNotSpecified,
141         PhotometricTypeC,
142         PhotometricTypeB,
143         PhotometricTypeA
144     };
145 
146     // Types of symmetry that candela values have with respect to horziontal angle
147     enum SymmetryType
148     {
149         NoSymmetry,
150         SymmetricHalvesX,               // bilaterally symmetric about the 0-180 degree axis
151         SymmetricHalvesY,               // bilaterally symmetric about the 90-270 degree axis
152         SymmetricQuadrants,             // symmetric in each quadrant
153         FullySymmetric                  // full rotational symmetry
154     };
155 
156     // Shape of luminous opening.
157     struct LuminousOpeningShape
158     {
159         enum UnitsType
160         {
161             Feet,
162             Meters
163         };
164 
165         UnitsType   m_units_type;
166         double      m_width;
167         double      m_length;
168         double      m_height;
169     };
170 
171     typedef std::vector<std::vector<double>> PhotometricGrid;
172 
173     //
174     // Options.
175     //
176 
177     bool m_ignore_allowed_keywords;     // if true, all keywors are allowed not depending on format version
178     bool m_ignore_required_keywords;    // if true, some required keywors can be missing
179     bool m_ignore_empty_lines;          // if true, than the file can contain whitespace lines
180     bool m_ignore_tilt;                 // if true, TILT section is not parsed
181 
182     //
183     // Main methods.
184     //
185 
186     // Parse input stream containing IESNA LM-63 Photometric Data.
187     void parse(std::istream& input_stream);
188 
189     // Check if the keyword is allowed by IESNA LM-63-2002 standard.
190     static bool is_keyword_allowed_by_iesna02(const std::string& keyword);
191 
192     // Check if the keyword is allowed by IESNA LM-63-95 standard.
193     static bool is_keyword_allowed_by_iesna95(const std::string& keyword);
194 
195     // Check if the keyword is allowed by IESNA LM-63-91 standard.
196     static bool is_keyword_allowed_by_iesna91(const std::string& keyword);
197 
198     //
199     // Getters.
200     //
201 
get_format()202     Format get_format() const
203     {
204         return m_format;
205     }
206 
get_lamp_to_luminaire_geometry()207     LampToLuminaireGeometry get_lamp_to_luminaire_geometry() const
208     {
209         return m_lamp_to_luminaire_geometry;
210     }
211 
get_tilt_angles()212     const std::vector<double>& get_tilt_angles() const
213     {
214         return m_tilt_angles;
215     }
216 
get_tilt_multiplying_factors()217     const std::vector<double>& get_tilt_multiplying_factors() const
218     {
219         return m_tilt_multiplying_factors;
220     }
221 
get_number_of_lamps()222     int get_number_of_lamps() const
223     {
224         return m_number_of_lamps;
225     }
226 
get_lumens_per_lamp()227     double get_lumens_per_lamp() const
228     {
229         return m_lumens_per_lamp;
230     }
231 
is_absolute_photometry()232     bool is_absolute_photometry() const
233     {
234         return m_absolute_photometry;
235     }
236 
get_candela_multiplier()237     double get_candela_multiplier() const
238     {
239         return m_candela_multiplier;
240     }
241 
get_number_of_vertical_angles()242     int get_number_of_vertical_angles() const
243     {
244         return m_number_of_vertical_angles;
245     }
246 
get_number_of_horizontal_angles()247     int get_number_of_horizontal_angles() const
248     {
249         return m_number_of_horizontal_angles;
250     }
251 
get_photometric_type()252     PhotometricType get_photometric_type() const
253     {
254         return m_photometric_type;
255     }
256 
get_luminous_opening()257     const LuminousOpeningShape& get_luminous_opening() const
258     {
259         return m_luminous_opening;
260     }
261 
get_ballast_factor()262     double get_ballast_factor() const
263     {
264         return m_ballast_factor;
265     }
266 
get_ballast_lamp_photometric_factor()267     double get_ballast_lamp_photometric_factor() const
268     {
269         return m_ballast_lamp_photometric_factor;
270     }
271 
get_input_watts()272     double get_input_watts() const
273     {
274         return m_input_watts;
275     }
276 
get_symmetry()277     SymmetryType get_symmetry() const
278     {
279         return m_symmetry;
280     }
281 
get_vertical_angles()282     const std::vector<double>& get_vertical_angles() const
283     {
284         return m_vertical_angles;
285     }
286 
get_horizontal_angles()287     const std::vector<double>& get_horizontal_angles() const
288     {
289         return m_horizontal_angles;
290     }
291 
get_candela_values()292     const PhotometricGrid& get_candela_values() const
293     {
294         return m_candela_values;
295     }
296 
get_keywords_dictionary()297     const KeywordsDictionary& get_keywords_dictionary() const
298     {
299         return m_keywords_dictionary;
300     }
301 
get_keyword_value(const std::string & keyword)302     const std::string& get_keyword_value(const std::string& keyword) const
303     {
304         return m_keywords_dictionary.at(keyword);
305     }
306 
307   private:
308     GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ParseFormat);
309     GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ReadLine_IgnoreEmptyLines);
310     GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ReadLine_DoNotIgnoreEmptyLines);
311     GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, CheckEmpty);
312     GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ParseKeywords);
313     GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Empty);
314     GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Good);
315     GRANT_ACCESS_TO_TEST_CASE(Foundation_Utility_Iesparser, ParseToVector_Bad);
316 
317     static const char* const KeywordLineRegex;
318     static const char* const TiltLineRegex;
319 
320     // Reset parser.
321     void reset(std::istream& input_stream);
322 
323     // Read line and increase the counter.
324     void read_line(std::istream& input_stream);
325 
326     // Read line, trim it and increase the counter.
327     // When ignore_empty_lines set to true this method
328     // ignores all lines consisting of whitespace characters.
329     void read_trimmed_line(std::istream& input_stream);
330 
331     // Retrieve format version.
332     // LM_63_1986 is set if this line is not one of supported version strings.
333     void parse_format_version(std::istream& input_stream);
334 
335     // Parse all data before TILT=<...> line
336     // and store keywords and corresponding values.
337     void parse_keywords_and_tilt(std::istream& input_stream);
338 
339     // Parse TILT section.
340     void parse_tilt_data(std::istream& input_stream);
341 
342     // Parse all photometric data after the TILT section
343     void parse_photometric_data(std::istream& input_stream);
344 
345     void parse_angles(std::istream& input_stream);
346 
347     void parse_candela_values(std::istream& input_stream);
348 
349     // Check if the line defines a valid keyword-value pair.
350     static bool is_keyword_line(const std::string& line);
351 
352     // Check if the line is a valid TILT=<...> line.
353     static bool is_tilt_line(const std::string& line);
354 
355     // Parse the line containing keyword-value pair
356     // and store this pair in the dictionary.
357     void parse_keyword_line(const std::string& line);
358 
359     // Parse TILT=<...> line.
360     void parse_tilt_line(const std::string& line);
361 
362     // Process BLOCK and ENDBLOCK keywords.
363     void process_block_keyword(const std::string& keyword);
364 
365     // Check if the specified standard allows this keyword.
366     void accept_keyword(const std::string& keyword);
367 
368     // Check if all required keywords were found.
369     void check_required_keywords() const;
370 
371     void check_iesna02_required_keywords() const;
372 
373     void check_iesna91_required_keywords() const;
374 
375     // Check if line is not empty and EOF is not reached.
376     void check_empty(std::istream& input_stream) const;
377 
378     // Read lines, split each line and write the values to the vector,
379     // until the total number of values does not exceed the specified size.
380     // If number of values in the last parsed line it too large, raise a ParsingException.
381     // Can also raise a boost::bad_cast exception, when a token cannot be converted
382     // to the value of specified type.
383     template <typename ValueType>
parse_to_vector(std::istream & input_stream,size_t count)384     std::vector<ValueType> parse_to_vector(std::istream& input_stream, size_t count)
385     {
386         std::vector<ValueType> output;
387         output.reserve(count);
388 
389         while (output.size() < count)
390         {
391             check_empty(input_stream);
392 
393             std::vector<std::string> tokens;
394             boost::split(tokens, m_line, isspace, boost::token_compress_on);
395             if (output.size() + tokens.size() > count)
396             {
397                 std::string expected_number_of_values =
398                     boost::lexical_cast<std::string>(count - output.size());
399                 static const char* ErrorMsg = "Too many values in the line, expected {0}";
400                 throw ParsingException(
401                     format(ErrorMsg, expected_number_of_values).c_str(), m_line_counter);
402             }
403 
404             for (each<std::vector<std::string>> token = tokens; token; ++token)
405             {
406                 output.push_back(boost::lexical_cast<ValueType>(*token));
407             }
408 
409             read_trimmed_line(input_stream);
410         }
411 
412         return output;
413     }
414 
415     //
416     // All information retrieved from file:
417     //
418 
419     // File format.
420     Format                          m_format;
421 
422     // TILT specification.
423     TiltSpecification               m_tilt_specification;
424     std::string                     m_tilt_specification_filename;
425 
426     // TILT section.
427     LampToLuminaireGeometry         m_lamp_to_luminaire_geometry;
428     std::vector<double>             m_tilt_angles;
429     std::vector<double>             m_tilt_multiplying_factors;
430 
431     // Photometric information.
432     int                             m_number_of_lamps;
433     double                          m_lumens_per_lamp;
434     bool                            m_absolute_photometry;
435     double                          m_candela_multiplier;
436     int                             m_number_of_vertical_angles;
437     int                             m_number_of_horizontal_angles;
438     PhotometricType                 m_photometric_type;
439     LuminousOpeningShape            m_luminous_opening;
440     double                          m_ballast_factor;
441     double                          m_ballast_lamp_photometric_factor;
442     double                          m_input_watts;
443     SymmetryType                    m_symmetry;
444     std::vector<double>             m_vertical_angles;
445     std::vector<double>             m_horizontal_angles;
446     PhotometricGrid                 m_candela_values;
447 
448     // Keywords.
449     KeywordsDictionary              m_keywords_dictionary;
450 
451     //
452     // Variables that describe the current parsing state:
453     //
454 
455     KeywordsDictionary::iterator    m_last_added_keyword;
456     int                             m_line_counter;
457     std::string                     m_line;
458 };
459 
460 }   // namespace foundation
461