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