temp commit
[SARndbox.git] / BathymetrySaverTool.cpp
blobbded4fd43d45208ffea753f3c27b761de8e774cc
1 /***********************************************************************
2 BathymetrySaverTool - Tool to save the current bathymetry grid of an
3 augmented reality sandbox to a file or network socket.
4 Copyright (c) 2016-2018 Oliver Kreylos
6 This file is part of the Augmented Reality Sandbox (SARndbox).
8 The Augmented Reality Sandbox is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
13 The Augmented Reality Sandbox is distributed in the hope that it will be
14 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License along
19 with the Augmented Reality Sandbox; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 ***********************************************************************/
23 #include "BathymetrySaverTool.h"
25 #include <stdexcept>
26 #include <iomanip>
27 #include <Misc/PrintInteger.h>
28 #include <Misc/ThrowStdErr.h>
29 #include <Misc/MessageLogger.h>
30 #include <Misc/StandardValueCoders.h>
31 #include <Misc/ConfigurationFile.h>
32 #include <IO/ValueSource.h>
33 #include <IO/OStream.h>
34 #include <Comm/TCPPipe.h>
35 #include <Math/Math.h>
36 #include <Vrui/OpenFile.h>
38 #include "WaterTable2.h"
39 #include "Sandbox.h"
41 /**********************************************************
42 Methods of class BathymetrySaverToolFactory::Configuration:
43 **********************************************************/
45 BathymetrySaverToolFactory::Configuration::Configuration(void)
46 : saveFileName("BathymetrySaverTool.dem"),
47 postUpdate(false), postUpdatePort(80), postUpdatePage(""),
48 postUpdateMessage("app.GenerateTileCache();"),
49 gridScale(1.0) {
52 void BathymetrySaverToolFactory::Configuration::read(const Misc::ConfigurationFileSection& cfs) {
53 saveFileName = cfs.retrieveString("./saveFileName", saveFileName);
54 postUpdate = cfs.retrieveValue<bool>("./postUpdate", postUpdate);
55 postUpdateHostName = cfs.retrieveString("./postUpdateHostName", postUpdateHostName);
56 postUpdatePort = cfs.retrieveValue<int>("./postUpdatePort", postUpdatePort);
57 postUpdatePage = cfs.retrieveString("./postUpdatePage", postUpdatePage);
58 postUpdateMessage = cfs.retrieveString("./postUpdateMessage", postUpdateMessage);
59 gridScale = cfs.retrieveValue<double>("./gridScale", gridScale);
62 void BathymetrySaverToolFactory::Configuration::write(Misc::ConfigurationFileSection& cfs) const {
63 cfs.storeString("./saveFileName", saveFileName);
64 cfs.storeValue<bool>("./postUpdate", postUpdate);
65 cfs.storeString("./postUpdateHostName", postUpdateHostName);
66 cfs.storeValue<int>("./postUpdatePort", postUpdatePort);
67 cfs.storeString("./postUpdatePage", postUpdatePage);
68 cfs.storeString("./postUpdateMessage", postUpdateMessage);
69 cfs.storeValue<double>("./gridScale", gridScale);
72 /*******************************************
73 Methods of class BathymetrySaverToolFactory:
74 *******************************************/
76 BathymetrySaverToolFactory::BathymetrySaverToolFactory(WaterTable2* sWaterTable,
77 Vrui::ToolManager& toolManager)
78 : ToolFactory("BathymetrySaverTool", toolManager),
79 waterTable(sWaterTable) {
80 /* Retrieve bathymetry grid and cell sizes: */
81 for(int i = 0; i < 2; ++i) {
82 gridSize[i] = waterTable->getBathymetrySize(i);
83 cellSize[i] = waterTable->getCellSize()[i];
86 /* Initialize tool layout: */
87 layout.setNumButtons(1);
89 #if 0
90 /* Insert class into class hierarchy: */
91 ToolFactory* toolFactory = toolManager.loadClass("Tool");
92 toolFactory->addChildClass(this);
93 addParentClass(toolFactory);
94 #endif
96 /* Load class settings: */
97 Misc::ConfigurationFileSection cfs = toolManager.getToolClassSection(getClassName());
98 configuration.read(cfs);
100 /* Set tool class' factory pointer: */
101 BathymetrySaverTool::factory = this;
104 BathymetrySaverToolFactory::~BathymetrySaverToolFactory(void) {
105 /* Reset tool class' factory pointer: */
106 BathymetrySaverTool::factory = 0;
109 const char* BathymetrySaverToolFactory::getName(void) const {
110 return "Save Bathymetry";
113 const char* BathymetrySaverToolFactory::getButtonFunction(int) const {
114 return "Save Bathymetry";
117 Vrui::Tool* BathymetrySaverToolFactory::createTool(const Vrui::ToolInputAssignment&
118 inputAssignment) const {
119 return new BathymetrySaverTool(this, inputAssignment);
122 void BathymetrySaverToolFactory::destroyTool(Vrui::Tool* tool) const {
123 delete tool;
126 /********************************************
127 Static elements of class BathymetrySaverTool:
128 ********************************************/
130 BathymetrySaverToolFactory* BathymetrySaverTool::factory = 0;
132 /************************************
133 Methods of class BathymetrySaverTool:
134 ************************************/
136 namespace {
138 /****************
139 Helper functions:
140 ****************/
142 std::ostream& printInt2(std::ostream& os, int value) {
143 os << std::setw(6) << value;
144 return os;
147 std::ostream& printFloat4(std::ostream& os, double value) {
148 if(value != 0.0) {
149 /* Split the value into mantissa and exponent: */
150 int exponent = int(Math::floor(Math::log10(Math::abs(value))));
151 double mantissa = value / Math::pow(10.0, double(exponent));
153 /* Write the number: */
154 std::ios::fmtflags oldFlags = os.flags(std::ios::showpoint | std::ios::dec | std::ios::fixed |
155 std::ios::right);
156 char oldFill = os.fill(' ');
157 std::streamsize oldPrecision = os.precision(5);
158 os << std::setw(7) << mantissa << 'e';
159 os.setf(std::ios::showpos);
160 os.fill('0');
161 os << std::internal << std::setw(4) << exponent;
162 os.flags(oldFlags);
163 os.fill(oldFill);
164 os.precision(oldPrecision);
165 } else
166 os << "0.00000e+000";
168 return os;
171 std::ostream& printFloat8(std::ostream& os, double value) {
172 if(value != 0.0) {
173 /* Split the value into mantissa and exponent: */
174 int exponent = int(Math::floor(Math::log10(Math::abs(value))));
175 double mantissa = value / Math::pow(10.0, double(exponent));
177 /* Write the number: */
178 std::ios::fmtflags oldFlags = os.flags(std::ios::showpoint | std::ios::dec | std::ios::fixed |
179 std::ios::right);
180 char oldFill = os.fill(' ');
181 std::streamsize oldPrecision = os.precision(15);
182 os << std::setw(19) << mantissa << 'D';
183 os.setf(std::ios::showpos);
184 os.fill('0');
185 os << std::internal << std::setw(4) << exponent;
186 os.flags(oldFlags);
187 os.fill(oldFill);
188 os.precision(oldPrecision);
189 } else
190 os << " 0.000000000000000D+000";
192 return os;
197 void BathymetrySaverTool::writeDEMFile(void) const {
198 /* Open the output file as a std::ostream: */
199 IO::OStream demFile(Vrui::openFile(configuration.saveFileName.c_str(), IO::File::WriteOnly));
201 /* Write the bathymetry name: */
202 static const char* fileHeader = "Augmented Reality Sandbox bathymetry grid";
203 demFile << fileHeader;
204 for(size_t i = strlen(fileHeader); i < 144; ++i)
205 demFile << ' ';
207 /* Write first part of header: */
208 printInt2(demFile, 1); // DEM level code (DEM-1)
209 printInt2(demFile, 1); // Elevation pattern (regular)
210 printInt2(demFile, 1); // Planimetric reference system code (UTM)
211 printInt2(demFile, 10); // Planimetric reference system zone (Northern California)
213 /* Write dummy map projection parameters, because UTM: */
214 for(int i = 0; i < 15; ++i)
215 printFloat8(demFile, 0.0);
217 /* Write units of measurement: */
218 printInt2(demFile, 2); // Horizontal unit is meters
219 printInt2(demFile, 2); // Vertical unit is meters
221 /* Retrieve the grid scale factor: */
222 double gs = configuration.gridScale;
224 /* Write the DEM coverage polygon: */
225 printInt2(demFile, 4); // Polygon is quadrangle
227 /* Easter egg: all exported DEMs are centered around Davis, CA: */
228 static const double gridCenter[2] = {609959.0, 4268028.0};
229 double west = gridCenter[0] - double(factory->gridSize[0] - 1) * double(
230 factory->cellSize[0]) * gs * 0.5;
231 double east = gridCenter[0] + double(factory->gridSize[0] - 1) * double(
232 factory->cellSize[0]) * gs * 0.5;
233 double north = gridCenter[1] + double(factory->gridSize[1] - 1) * double(
234 factory->cellSize[1]) * gs * 0.5;
235 double south = gridCenter[1] - double(factory->gridSize[1] - 1) * double(
236 factory->cellSize[1]) * gs * 0.5;
238 /* Go around the polygon in clockwise order, starting in south-west corner: */
239 printFloat8(demFile, west);
240 printFloat8(demFile, south);
241 printFloat8(demFile, west);
242 printFloat8(demFile, north);
243 printFloat8(demFile, east);
244 printFloat8(demFile, north);
245 printFloat8(demFile, east);
246 printFloat8(demFile, south);
248 /* Calculate and write the grid's elevation range: */
249 GLfloat elevMin, elevMax;
250 elevMin = elevMax = bathymetryBuffer[0];
251 const GLfloat* bbPtr = bathymetryBuffer + 1;
252 for(GLsizei count = factory->gridSize[1] * factory->gridSize[0] - 1; count > 0; --count, ++bbPtr) {
253 if(elevMin > *bbPtr)
254 elevMin = *bbPtr;
255 if(elevMax < *bbPtr)
256 elevMax = *bbPtr;
259 // DEBUGGING
260 std::cout << elevMin << ", " << elevMax << std::endl;
262 elevMin *= gs;
263 elevMax *= gs;
264 printFloat8(demFile, elevMin);
265 printFloat8(demFile, elevMax);
267 /* Calculate the elevation quantization offset and scale: */
268 double elevationBase = 0.0; // double(elevMin+elevMax)*0.5;
269 double zScale = 1000.0; // Quantize to millimeters by default
270 double elevRange = Math::max(Math::abs(elevMax - elevationBase),
271 Math::abs(elevMin - elevationBase));
272 if(elevRange != 0.0) {
273 /* Calculate a power-of-ten scale factor to scale the actual terrain range to -9999 to 9999: */
274 zScale = Math::pow(10.0, Math::floor(Math::log10(9999.0 / elevRange)));
277 // DEBUGGING
278 // std::cout<<elevationBase<<", "<<zScale<<std::endl;
279 // std::cout<<(elevMax-elevationBase)*zScale<<", "<<(elevMin-elevationBase)*zScale<<std::endl;
281 /* Write the grid rotation angle: */
282 printFloat8(demFile, 0.0);
284 /* Write the accuracy code: */
285 printInt2(demFile, 0); // Unknown accuracy
287 /* Write the grid scales with full accuracy. Per spec, only integer values are supported: */
288 printFloat4(demFile, factory->cellSize[0]*gs);
289 printFloat4(demFile, factory->cellSize[1]*gs);
290 printFloat4(demFile, 1.0 / zScale);
292 /* Write the number of rows and columns in the grid: */
293 printInt2(demFile, 1); // Number of rows specified in each grid profile
294 printInt2(demFile, factory->gridSize[0]); // Number of columns
296 /* Calculate the total size of the file written so far: */
297 size_t fileSize = 864U;
299 /* Write all grid columns: */
300 for(GLsizei column = 0; column < factory->gridSize[0]; ++column) {
301 /* Pad the current file size to a multiple of 1024: */
302 size_t paddedSize = (fileSize + 1023U) & ~size_t(1023U);
303 for(; fileSize < paddedSize; ++fileSize)
304 demFile << ' ';
306 /* Write the profile header: */
307 printInt2(demFile, 1); // 1-based starting row index of this profile
308 printInt2(demFile, column + 1); // 1-based column index of this profile
309 printInt2(demFile, factory->gridSize[1]); // Number of rows in profile
310 printInt2(demFile, 1); // Number of columns in profile
311 printFloat8(demFile, west + double(column)*double(factory->cellSize[0])
312 *gs); // Easting of first elevation posting in column
313 printFloat8(demFile, south); // Northing of first elevation posting in column
314 printFloat8(demFile, elevationBase); // Local datum elevation
316 /* Calculate and write the profile's elevation range: */
317 const GLfloat* pPtr = bathymetryBuffer + column;
318 GLfloat elevMin, elevMax;
319 elevMin = elevMax = *pPtr;
320 pPtr += factory->gridSize[0];
321 for(GLsizei count = factory->gridSize[1] - 1; count > 0; --count, pPtr += factory->gridSize[0]) {
322 if(elevMin > *pPtr)
323 elevMin = *pPtr;
324 if(elevMax < *pPtr)
325 elevMax = *pPtr;
327 printFloat8(demFile, elevMin * gs);
328 printFloat8(demFile, elevMax * gs);
330 /* Update the file size: */
331 fileSize += 6 * 4 + 24 * 5;
333 /* Quantize and write the profile's elevation postings: */
334 pPtr = bathymetryBuffer + column;
335 for(GLsizei count = factory->gridSize[1]; count > 0; --count, pPtr += factory->gridSize[0]) {
336 /* Check if there is enough space left in the current 1024-character record: */
337 size_t paddedSize = (fileSize + 1023U) & ~size_t(1023U);
338 if(paddedSize - fileSize < 10U) { // Last four characters of each record need to be blank
339 /* Pad the record: */
340 for(; fileSize < paddedSize; ++fileSize)
341 demFile << ' ';
344 /* Quantize and write the posting: */
345 double scaled = (double(*pPtr) * gs - elevationBase) * zScale;
346 printInt2(demFile, int(Math::floor(scaled + 0.5)));
347 fileSize += 6;
351 /* Pad the current file size to a multiple of 1024: */
352 size_t paddedSize = (fileSize + 1023U) & ~size_t(1023U);
353 for(; fileSize < paddedSize; ++fileSize)
354 demFile << ' ';
356 /* Write a dummy "C" record: */
357 for(int i = 0; i < 10; ++i)
358 printInt2(demFile, 0);
359 fileSize += 6 * 10;
361 /* Pad the total file size to a multiple of 1024: */
362 paddedSize = (fileSize + 1023U) & ~size_t(1023U);
363 for(; fileSize < paddedSize; ++fileSize)
364 demFile << ' ';
367 void BathymetrySaverTool::postUpdate(void) const {
368 /* Connect to the HTTP server: */
369 Comm::NetPipePtr pipe = new Comm::TCPPipe(configuration.postUpdateHostName.c_str(),
370 configuration.postUpdatePort);
372 /* Assemble the PUT request: */
373 std::string request;
374 request.append("PUT");
375 request.push_back(' ');
376 request.push_back('/');
377 request.append(configuration.postUpdatePage.c_str());
378 request.push_back(' ');
379 request.append("HTTP/1.1\r\n");
381 request.append("Host: ");
382 request.append(configuration.postUpdateHostName.c_str());
383 request.push_back(':');
384 char portString[6];
385 request.append(Misc::print(configuration.postUpdatePort, portString + 5));
386 request.append("\r\n");
388 request.append("Accept: */*\r\n");
390 request.append("Content-Length: ");
391 char contentLengthString[6];
392 request.append(Misc::print(configuration.postUpdateMessage.size(), contentLengthString + 5));
393 request.append("\r\n");
395 request.append("Content-Type: application/x-www-form-urlencoded\r\n");
397 /* Finish the request header: */
398 request.append("\r\n");
400 /* Assemble the PUT request content: */
401 request.append(configuration.postUpdateMessage);
403 /* Send the PUT request: */
404 pipe->writeRaw(request.data(), request.size());
405 pipe->flush();
407 /* Parse the reply header: */
408 bool replyChunked = false;
409 bool replySized = false;
410 size_t replySize = 0;
412 /* Attach a value source to the pipe to parse the server's reply: */
413 IO::ValueSource reply(pipe);
414 reply.setPunctuation("()<>@,;:\\/[]?={}\r");
415 reply.setQuotes("\"");
416 reply.skipWs();
418 /* Read the status line: */
419 if(!reply.isLiteral("HTTP") || !reply.isLiteral('/'))
420 Misc::throwStdErr("Not an HTTP reply!");
421 reply.skipString();
422 unsigned int statusCode = reply.readUnsignedInteger();
423 if(statusCode != 200)
424 Misc::throwStdErr("HTTP error %d: %s", statusCode, reply.readLine().c_str());
425 reply.readLine();
426 reply.skipWs();
428 /* Parse reply options until the first empty line: */
429 while(!reply.eof() && reply.peekc() != '\r') {
430 /* Read the option tag: */
431 std::string option = reply.readString();
432 if(reply.isLiteral(':')) {
433 /* Handle the option value: */
434 if(option == "Transfer-Encoding") {
435 /* Parse the comma-separated list of transfer encodings: */
436 while(true) {
437 std::string coding = reply.readString();
438 if(coding == "chunked")
439 replyChunked = true;
440 else {
441 /* Skip the transfer extension: */
442 while(reply.isLiteral(';')) {
443 reply.skipString();
444 if(!reply.isLiteral('='))
445 Misc::throwStdErr("Malformed HTTP reply header");
446 reply.skipString();
449 if(reply.eof() || reply.peekc() != ',')
450 break;
451 while(!reply.eof() && reply.peekc() == ',')
452 reply.readChar();
454 } else if(option == "Content-Length") {
455 replySized = true;
456 replySize = reply.readUnsignedInteger();
460 /* Skip the rest of the line: */
461 reply.skipLine();
462 reply.skipWs();
465 /* Read the CR/LF pair: */
466 if(reply.getChar() != '\r' || reply.getChar() != '\n')
467 Misc::throwStdErr("Malformed HTTP reply header");
470 /* Print the reply entity: */
471 if(replyChunked) {
472 // std::cout<<"Chunked reply body!"<<std::endl<<std::endl;
474 /* Read all chunks until the end chunk: */
475 while(true) {
476 /* Read the next chunk size: */
477 size_t chunkSize = 0;
478 int digit;
479 while(true) {
480 digit = pipe->getChar();
481 if(digit >= '0' && digit <= '9')
482 chunkSize = (chunkSize << 4) + (digit - '0');
483 else if(digit >= 'a' && digit <= 'f')
484 chunkSize = (chunkSize << 4) + (digit - 'a' + 10);
485 else if(digit >= 'A' && digit <= 'F')
486 chunkSize = (chunkSize << 4) + (digit - 'A' + 10);
487 else
488 break;
490 while(digit != '\r')
491 digit = pipe->getChar();
492 if(pipe->getChar() != '\n')
493 Misc::throwStdErr("Malformed HTTP chunk header");
495 if(chunkSize == 0)
496 break;
498 /* Read the chunk: */
499 char buffer[256];
500 while(chunkSize > 0) {
501 size_t readSize = chunkSize;
502 if(readSize > sizeof(buffer))
503 readSize = sizeof(buffer);
504 pipe->read(buffer, readSize);
505 // buffer[readSize]='\0';
506 // std::cout<<buffer;
507 chunkSize -= readSize;
510 /* Read the chunk footer: */
511 if(pipe->getChar() != '\r' || pipe->getChar() != '\n')
512 Misc::throwStdErr("Malformed HTTP chunk footer");
515 /* Skip the body trailer: */
516 while(pipe->getChar() != '\r') {
517 /* Skip the line: */
518 while(pipe->getChar() != '\r')
520 if(pipe->getChar() != '\n')
521 Misc::throwStdErr("Malformed HTTP body trailer");
523 if(pipe->getChar() != '\n')
524 Misc::throwStdErr("Malformed HTTP body trailer");
525 } else if(replySized) {
526 /* Read the fixed reply size: */
527 char buffer[256];
528 while(replySize > 0) {
529 size_t readSize = replySize;
530 if(readSize > sizeof(buffer))
531 readSize = sizeof(buffer);
532 pipe->read(buffer, readSize);
533 // buffer[readSize]='\0';
534 // std::cout<<buffer;
535 replySize -= readSize;
537 } else {
538 /* Read until end-of-file: */
539 char buffer[256];
540 while(!pipe->eof()) {
541 pipe->readUpTo(buffer, sizeof(buffer));
542 // buffer[bufSize]='\0';
543 // std::cout<<buffer;
546 // std::cout<<std::endl;
549 BathymetrySaverToolFactory* BathymetrySaverTool::initClass(WaterTable2* sWaterTable,
550 Vrui::ToolManager& toolManager) {
551 /* Create the tool factory: */
552 factory = new BathymetrySaverToolFactory(sWaterTable, toolManager);
554 /* Register and return the class: */
555 toolManager.addClass(factory, Vrui::ToolManager::defaultToolFactoryDestructor);
556 return factory;
559 BathymetrySaverTool::BathymetrySaverTool(const Vrui::ToolFactory* factory,
560 const Vrui::ToolInputAssignment& inputAssignment)
561 : Vrui::Tool(factory, inputAssignment),
562 configuration(BathymetrySaverTool::factory->configuration),
563 bathymetryBuffer(new GLfloat[BathymetrySaverTool::factory->gridSize[1] *
564 BathymetrySaverTool::factory->gridSize[0]]),
565 requestPending(false) {
568 BathymetrySaverTool::~BathymetrySaverTool(void) {
569 delete[] bathymetryBuffer;
572 void BathymetrySaverTool::configure(const Misc::ConfigurationFileSection& configFileSection) {
573 /* Override private configuration data from given configuration file section: */
574 configuration.read(configFileSection);
577 void BathymetrySaverTool::storeState(Misc::ConfigurationFileSection& configFileSection) const {
578 /* Write private configuration data to given configuration file section: */
579 configuration.write(configFileSection);
582 const Vrui::ToolFactory* BathymetrySaverTool::getFactory(void) const {
583 return factory;
586 void BathymetrySaverTool::buttonCallback(int buttonSlotIndex,
587 Vrui::InputDevice::ButtonCallbackData* cbData) {
588 if(cbData->newButtonState) {
589 /* Request a bathymetry grid from the water table: */
590 requestPending = factory->waterTable->requestBathymetry(bathymetryBuffer);
594 void BathymetrySaverTool::frame(void) {
595 if(requestPending && factory->waterTable->haveBathymetry()) {
596 try {
597 /* Export the bathymetry grid: */
598 writeDEMFile();
600 if(configuration.postUpdate) {
601 /* Send an update message to the configured web server: */
602 postUpdate();
604 } catch(const std::runtime_error& err) {
605 Misc::formattedUserError("Save Bathymetry: Unable to save bathymetry due to exception \"%s\"",
606 err.what());
609 requestPending = false;