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"
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"
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();"),
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);
90 /* Insert class into class hierarchy: */
91 ToolFactory
* toolFactory
= toolManager
.loadClass("Tool");
92 toolFactory
->addChildClass(this);
93 addParentClass(toolFactory
);
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 {
126 /********************************************
127 Static elements of class BathymetrySaverTool:
128 ********************************************/
130 BathymetrySaverToolFactory
* BathymetrySaverTool::factory
= 0;
132 /************************************
133 Methods of class BathymetrySaverTool:
134 ************************************/
142 std::ostream
& printInt2(std::ostream
& os
, int value
) {
143 os
<< std::setw(6) << value
;
147 std::ostream
& printFloat4(std::ostream
& os
, double value
) {
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
|
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
);
161 os
<< std::internal
<< std::setw(4) << exponent
;
164 os
.precision(oldPrecision
);
166 os
<< "0.00000e+000";
171 std::ostream
& printFloat8(std::ostream
& os
, double value
) {
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
|
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
);
185 os
<< std::internal
<< std::setw(4) << exponent
;
188 os
.precision(oldPrecision
);
190 os
<< " 0.000000000000000D+000";
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
)
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
) {
260 std::cout
<< elevMin
<< ", " << elevMax
<< std::endl
;
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
)));
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
)
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]) {
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
)
344 /* Quantize and write the posting: */
345 double scaled
= (double(*pPtr
) * gs
- elevationBase
) * zScale
;
346 printInt2(demFile
, int(Math::floor(scaled
+ 0.5)));
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
)
356 /* Write a dummy "C" record: */
357 for(int i
= 0; i
< 10; ++i
)
358 printInt2(demFile
, 0);
361 /* Pad the total file size to a multiple of 1024: */
362 paddedSize
= (fileSize
+ 1023U) & ~size_t(1023U);
363 for(; fileSize
< paddedSize
; ++fileSize
)
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: */
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(':');
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());
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("\"");
418 /* Read the status line: */
419 if(!reply
.isLiteral("HTTP") || !reply
.isLiteral('/'))
420 Misc::throwStdErr("Not an HTTP reply!");
422 unsigned int statusCode
= reply
.readUnsignedInteger();
423 if(statusCode
!= 200)
424 Misc::throwStdErr("HTTP error %d: %s", statusCode
, reply
.readLine().c_str());
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: */
437 std::string coding
= reply
.readString();
438 if(coding
== "chunked")
441 /* Skip the transfer extension: */
442 while(reply
.isLiteral(';')) {
444 if(!reply
.isLiteral('='))
445 Misc::throwStdErr("Malformed HTTP reply header");
449 if(reply
.eof() || reply
.peekc() != ',')
451 while(!reply
.eof() && reply
.peekc() == ',')
454 } else if(option
== "Content-Length") {
456 replySize
= reply
.readUnsignedInteger();
460 /* Skip the rest of the line: */
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: */
472 // std::cout<<"Chunked reply body!"<<std::endl<<std::endl;
474 /* Read all chunks until the end chunk: */
476 /* Read the next chunk size: */
477 size_t chunkSize
= 0;
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);
491 digit
= pipe
->getChar();
492 if(pipe
->getChar() != '\n')
493 Misc::throwStdErr("Malformed HTTP chunk header");
498 /* Read the chunk: */
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') {
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: */
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
;
538 /* Read until end-of-file: */
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
);
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 {
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()) {
597 /* Export the bathymetry grid: */
600 if(configuration
.postUpdate
) {
601 /* Send an update message to the configured web server: */
604 } catch(const std::runtime_error
& err
) {
605 Misc::formattedUserError("Save Bathymetry: Unable to save bathymetry due to exception \"%s\"",
609 requestPending
= false;