1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "ConfigManager.h"
17 
18 #include "json/json.h"
19 
20 #include <fstream>
21 #include <math.h>
22 #include <assert.h>
23 
24 namespace android {
25 namespace automotive {
26 namespace evs {
27 namespace support {
28 
29 static const float kDegreesToRadians = M_PI / 180.0f;
30 
31 
normalizeToPlusMinus180degrees(float theta)32 static float normalizeToPlusMinus180degrees(float theta) {
33     const float wraps = floor((theta+180.0f) / 360.0f);
34     return theta - wraps*360.0f;
35 }
36 
37 
readChildNodeAsFloat(const char * groupName,const Json::Value & parentNode,const char * childName,float * value)38 static bool readChildNodeAsFloat(const char* groupName,
39                                  const Json::Value& parentNode,
40                                  const char* childName,
41                                  float* value) {
42     // Must have a place to put the value!
43     assert(value);
44 
45     Json::Value childNode = parentNode[childName];
46     if (!childNode.isNumeric()) {
47         printf("Missing or invalid field %s in record %s", childName, groupName);
48         return false;
49     }
50 
51     *value = childNode.asFloat();
52     return true;
53 }
54 
55 
initialize(const char * configFileName)56 bool ConfigManager::initialize(const char* configFileName)
57 {
58     bool complete = true;
59 
60     // Set up a stream to read in the input file
61     std::ifstream configStream(configFileName);
62 
63     // Parse the stream into JSON objects
64     Json::CharReaderBuilder builder;
65     builder["collectComments"] = false;
66     std::string errorMessage;
67     Json::Value rootNode;
68     bool parseOk = Json::parseFromStream(builder, configStream, &rootNode, &errorMessage);
69     if (!parseOk) {
70         printf("Failed to read configuration file %s\n", configFileName);
71         printf("%s\n", errorMessage.c_str());
72         return false;
73     }
74 
75 
76     //
77     // Read car information
78     //
79     {
80         Json::Value car = rootNode["car"];
81         if (!car.isObject()) {
82             printf("Invalid configuration format -- we expect a car description\n");
83             return false;
84         }
85         complete &= readChildNodeAsFloat("car", car, "width",       &mCarWidth);
86         complete &= readChildNodeAsFloat("car", car, "wheelBase",   &mWheelBase);
87         complete &= readChildNodeAsFloat("car", car, "frontExtent", &mFrontExtent);
88         complete &= readChildNodeAsFloat("car", car, "rearExtent",  &mRearExtent);
89     }
90 
91 
92     //
93     // Read display layout information
94     //
95     {
96         Json::Value displayNode = rootNode["display"];
97         if (!displayNode.isObject()) {
98             printf("Invalid configuration format -- we expect a display description\n");
99             return false;
100         }
101         complete &= readChildNodeAsFloat("display", displayNode, "frontRange", &mFrontRangeInCarSpace);
102         complete &= readChildNodeAsFloat("display", displayNode, "rearRange",  &mRearRangeInCarSpace);
103     }
104 
105 
106     //
107     // Car top view texture properties for top down view
108     //
109     {
110         Json::Value graphicNode = rootNode["graphic"];
111         if (!graphicNode.isObject()) {
112             printf("Invalid configuration format -- we expect a graphic description\n");
113             return false;
114         }
115         complete &= readChildNodeAsFloat("graphic", graphicNode, "frontPixel", &mCarGraphicFrontPixel);
116         complete &= readChildNodeAsFloat("display", graphicNode, "rearPixel",  &mCarGraphicRearPixel);
117     }
118 
119 
120     //
121     // Read camera information
122     // NOTE:  Missing positions and angles are not reported, but instead default to zero
123     //
124     {
125         Json::Value cameraArray = rootNode["cameras"];
126         if (!cameraArray.isArray()) {
127             printf("Invalid configuration format -- we expect an array of cameras\n");
128             return false;
129         }
130 
131         mCameras.reserve(cameraArray.size());
132         for (auto&& node: cameraArray) {
133             // Get data from the configuration file
134             Json::Value nameNode = node.get("cameraId", "MISSING");
135             const char *cameraId = nameNode.asCString();
136 
137             Json::Value usageNode = node.get("function", "");
138             const char *function = usageNode.asCString();
139 
140             float yaw   = node.get("yaw", 0).asFloat();
141             float pitch = node.get("pitch", 0).asFloat();
142             float hfov  = node.get("hfov", 0).asFloat();
143             float vfov  = node.get("vfov", 0).asFloat();
144 
145             // Wrap the direction angles to be in the 180deg to -180deg range
146             // Rotate 180 in yaw if necessary to flip the pitch into the +/-90degree range
147             pitch = normalizeToPlusMinus180degrees(pitch);
148             if (pitch > 90.0f) {
149                 yaw += 180.0f;
150                 pitch = 180.0f - pitch;
151             }
152             if (pitch < -90.0f) {
153                 yaw += 180.0f;
154                 pitch = -180.0f + pitch;
155             }
156             yaw = normalizeToPlusMinus180degrees(yaw);
157 
158             // Range check the FOV values to ensure they are positive and less than 180degrees
159             if (hfov > 179.0f) {
160                 printf("Pathological horizontal field of view %f clamped to 179 degrees\n", hfov);
161                 hfov = 179.0f;
162             }
163             if (hfov < 1.0f) {
164                 printf("Pathological horizontal field of view %f clamped to 1 degree\n", hfov);
165                 hfov = 1.0f;
166             }
167             if (vfov > 179.0f) {
168                 printf("Pathological horizontal field of view %f clamped to 179 degrees\n", vfov);
169                 vfov = 179.0f;
170             }
171             if (vfov < 1.0f) {
172                 printf("Pathological horizontal field of view %f clamped to 1 degree\n", vfov);
173                 vfov = 1.0f;
174             }
175 
176             // Store the camera info (converting degrees to radians in the process)
177             CameraInfo info;
178             info.position[0] = node.get("x", 0).asFloat();
179             info.position[1] = node.get("y", 0).asFloat();
180             info.position[2] = node.get("z", 0).asFloat();
181             info.yaw         = yaw   * kDegreesToRadians;
182             info.pitch       = pitch * kDegreesToRadians;
183             info.hfov        = hfov  * kDegreesToRadians;
184             info.vfov        = vfov  * kDegreesToRadians;
185             info.cameraId    = cameraId;
186             info.function    = function;
187 
188             mCameras.push_back(info);
189         }
190     }
191 
192     // If we got this far, we were successful as long as we found all our child fields
193     return complete;
194 }
195 
196 }  // namespace support
197 }  // namespace evs
198 }  // namespace automotive
199 }  // namespace android
200