Fixed elevation optimiser to obtain shadow classifications using portion instead...
[tecorrec.git] / geo / tcElevationOptimization.cpp
blob75ae5ef3a71189d01fe782ec526499580089b3e9
1 /***************************************************************************
2 * This file is part of Tecorrec. *
3 * Copyright 2008 James Hogan <james@albanarts.com> *
4 * *
5 * Tecorrec is free software: you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation, either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * Tecorrec is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with Tecorrec. If not, write to the Free Software Foundation, *
17 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ***************************************************************************/
20 /**
21 * @file tcElevationOptimization.cpp
22 * @brief Optimized elevation.
25 #include "tcElevationOptimization.h"
26 #include "tcChannelConfigWidget.h"
27 #include "tcSrtmModel.h"
28 #include "tcShadowClassifyingData.h"
29 #include <SplineDefinitions.h>
31 #include <QObject>
32 #include <QWidget>
33 #include <QVBoxLayout>
34 #include <QHBoxLayout>
35 #include <QGridLayout>
36 #include <QPushButton>
37 #include <QSpinBox>
38 #include <QLabel>
39 #include <QSlider>
40 #include <QMessageBox>
43 * Constructors + destructor
46 /// Primary constructor.
47 tcElevationOptimization::tcElevationOptimization(const QList<tcShadowClassifyingData*>& datasets, tcSrtmModel* dem, tcGeoImageData* imagery)
48 : tcChannelDem(dem, imagery,
49 tr("Elevation Optimization"),
50 tr("Optimizes elevation using image data."),
51 false)
52 , m_numDatasets(datasets.size())
53 , m_datasets(new tcGeoImageData*[m_numDatasets])
54 , m_texToDatasetTex(new tcAffineTransform2<double>[m_numDatasets])
55 , m_shadowChannels(new tcChannel*[m_numDatasets])
56 , m_shadingChannels(new tcChannel*[m_numDatasets])
57 , m_elevationData(0)
58 , m_datum(new tcTypedPixelData<PerDatasetPixelCache>*[m_numDatasets])
59 , m_data(0)
60 , m_shadowData(new Reference<tcPixelData<float> >[m_numDatasets])
61 , m_shadingData(new Reference<tcPixelData<float> >[m_numDatasets])
62 , m_loadingFromDem(false)
63 , m_configWidget(0)
64 , m_spinIterations(0)
66 for (int i = 0; i < m_numDatasets; ++i)
68 tcShadowClassifyingData* landsat = datasets[i];
69 m_datasets[i] = landsat;
70 m_texToDatasetTex[i] = imagery->texToGeo() * landsat->geoToTex();
71 m_shadowChannels[i] = landsat->shadowClassification();
72 if (0 != m_shadowChannels[i])
74 m_shadowChannels[i]->addDerivitive(this);
76 m_shadingChannels[i] = landsat->shading();
77 if (0 != m_shadingChannels[i])
79 m_shadingChannels[i]->addDerivitive(this);
81 m_datum[i] = 0;
85 /// Destructor.
86 tcElevationOptimization::~tcElevationOptimization()
88 delete [] m_datasets;
89 delete [] m_texToDatasetTex;
90 for (int i = 0; i < m_numDatasets; ++i)
92 if (0 != m_shadowChannels[i])
94 m_shadowChannels[i]->removeDerivitive(this);
96 if (0 != m_shadingChannels[i])
98 m_shadingChannels[i]->removeDerivitive(this);
100 delete m_datum[i];
102 delete [] m_shadowChannels;
103 delete [] m_shadingChannels;
104 delete m_elevationData;
105 delete [] m_datum;
106 delete m_data;
107 delete [] m_shadowData;
108 delete [] m_shadingData;
109 delete m_configWidget;
113 * Main image interface
116 tcChannelConfigWidget* tcElevationOptimization::configWidget()
118 if (0 == m_configWidget)
120 m_configWidget = new tcChannelConfigWidget();
121 m_configWidget->setWindowTitle(tr("%1 Configuration").arg(name()));
123 QVBoxLayout* layout = new QVBoxLayout(m_configWidget);
125 // Row of optimisation controls
126 QWidget* buttons1 = new QWidget(m_configWidget);
127 layout->addWidget(buttons1);
128 QHBoxLayout* buttons1Layout = new QHBoxLayout(buttons1);
130 // reset DEM (sets all corrections to the original (interpolated) values
131 QPushButton* btnResetDem = new QPushButton(tr("Reset DEM"), m_configWidget);
132 buttons1Layout->addWidget(btnResetDem);
133 btnResetDem->setToolTip(tr("Reset DEM corrected values to interpolated values"));
134 connect(btnResetDem, SIGNAL(clicked(bool)), this, SLOT(resetDem()));
136 // load base from DEM (loads DEM elevations into elevation buffer)
137 QPushButton* btnLoadDem = new QPushButton(tr("Load DEM"), m_configWidget);
138 buttons1Layout->addWidget(btnLoadDem);
139 btnLoadDem->setToolTip(tr("Load corrected values from DEM into elevation field"));
140 connect(btnLoadDem, SIGNAL(clicked(bool)), this, SLOT(loadFromDem()));
142 // number of iterations (select number of iterations to perform)
143 m_spinIterations = new QSpinBox(m_configWidget);
144 buttons1Layout->addWidget(m_spinIterations);
145 m_spinIterations->setToolTip(tr("Number of iterations of optimization to perform"));
146 m_spinIterations->setRange(1, 1000);
147 m_spinIterations->setValue(10);
149 // optimize (iterates on elevation buffer and write back to DEM at intervals and end)
150 QPushButton* btnOptimize = new QPushButton(tr("Optimize Iteratively"), m_configWidget);
151 buttons1Layout->addWidget(btnOptimize);
152 btnOptimize->setToolTip(tr("Iteratively optimize elevation field and write results back to DEM"));
153 connect(btnOptimize, SIGNAL(clicked(bool)), this, SLOT(optimize()));
155 // Another row of optimisation controls
156 QWidget* buttons2 = new QWidget(m_configWidget);
157 layout->addWidget(buttons2);
158 QHBoxLayout* buttons2Layout = new QHBoxLayout(buttons2);
160 // apply hard constraints, adjusting reliabilities
161 QPushButton* btnHardConstrain = new QPushButton(tr("Hard Constrain"), m_configWidget);
162 buttons2Layout->addWidget(btnHardConstrain);
163 btnLoadDem->setToolTip(tr("Apply hard shadow constraints"));
164 connect(btnHardConstrain, SIGNAL(clicked(bool)), this, SLOT(applyHardConstraints()));
166 // bilinear interpolation of voids
167 QPushButton* btnBilinear = new QPushButton(tr("Bilinear"), m_configWidget);
168 buttons2Layout->addWidget(btnBilinear);
169 btnLoadDem->setToolTip(tr("Void-fill using bilinear interpolation"));
170 connect(btnBilinear, SIGNAL(clicked(bool)), this, SLOT(voidFillBilinear()));
172 // bicubic interpolation of voids
173 QPushButton* btnBicubic = new QPushButton(tr("Bicubic"), m_configWidget);
174 buttons2Layout->addWidget(btnBicubic);
175 btnLoadDem->setToolTip(tr("Void-fill using bicubic interpolation"));
176 connect(btnBicubic, SIGNAL(clicked(bool)), this, SLOT(voidFillBicubic()));
178 // Rows of weight sliders
179 #define NUM_WEIGHTS 9
180 QString weightNames[NUM_WEIGHTS] = {
181 tr("Variance"),
182 tr("Roughness"),
183 tr("Steepness"),
184 tr("Lit facing sun"),
185 tr("Shape from shading"),
186 tr("Below shadow line"),
187 tr("Transition elevation"),
188 tr("Transition slope"),
189 tr("Transition curving down")
191 QWidget* sliders = new QWidget(m_configWidget);
192 layout->addWidget(sliders);
193 QGridLayout* slidersLayout = new QGridLayout(sliders);
195 for (int i = 0; i < NUM_WEIGHTS; ++i)
197 QLabel* label = new QLabel(weightNames[i], sliders);
198 slidersLayout->addWidget(label, i, 0);
200 QSlider* slider = new QSlider(Qt::Horizontal, sliders);
201 slidersLayout->addWidget(slider, i, 1);
204 return m_configWidget;
208 * Slots
211 /// Reset DEM.
212 void tcElevationOptimization::resetDem()
216 /// Load elevation data from DEM.
217 void tcElevationOptimization::loadFromDem()
219 delete m_elevationData;
220 m_elevationData = 0;
222 delete m_data;
223 m_data = 0;
225 for (int i = 0; i < m_numDatasets; ++i)
227 delete m_datum[i];
228 m_datum[i] = 0;
231 m_loadingFromDem = true;
232 invalidate();
233 m_configWidget->requestRedraw();
234 m_loadingFromDem = false;
237 /// Apply hard constraints to elevation data.
238 void tcElevationOptimization::applyHardConstraints()
240 if (0 != m_elevationData)
242 const int width = m_elevationData->width();
243 const int height = m_elevationData->height();
244 // Now start optimising
245 startProcessing("Applying hard constraints");
246 for (int j = 0; j <= height; ++j)
248 progress((float)j/(height-1));
249 for (int i = 0; i <= width; ++i)
251 int index = j*width + i;
253 // Only apply constraints to unreliable pixels
254 PerPixelCache* cache = &m_data->buffer()[index];
255 if (cache->reliability != 0)
257 continue;
260 // Per dataset
261 for (int ds = 0; ds < m_numDatasets; ++ds)
263 PerDatasetPixelCache* dsCache = &m_datum[ds]->buffer()[index];
265 bool isShadowed = (m_shadowData[ds]->sampleFloat((float)i/(width-1), (float)j/(height-1)) < 0.5f);
266 bool isShadowEntrance = isShadowed &&
267 dsCache->shadowTransition[TransitionEntrance][0] == i &&
268 dsCache->shadowTransition[TransitionEntrance][1] == j;
269 bool isShadowExit = isShadowed &&
270 dsCache->shadowTransition[TransitionExit][0] == i &&
271 dsCache->shadowTransition[TransitionExit][1] == j;
273 if (isShadowEntrance)
275 if (dsCache->shadowTransition[TransitionExit][0] >= 0 &&
276 dsCache->shadowTransition[TransitionExit][0] < width &&
277 dsCache->shadowTransition[TransitionExit][1] >= 0 &&
278 dsCache->shadowTransition[TransitionExit][1] < height)
280 int exitIndex = width*dsCache->shadowTransition[TransitionExit][1] + dsCache->shadowTransition[TransitionExit][0];
281 PerPixelCache* exitCache = &m_data->buffer()[exitIndex];
282 // Great, we can say for definite the elevation of this pixel because the corresponding pixel is reliable
283 if (exitCache->reliability != 0)
285 float exitElevation = m_elevationData->buffer()[exitIndex];
286 m_elevationData->buffer()[index] = exitElevation + dsCache->shadowTransitionDistance[TransitionExit]*dsCache->sinLightElevation;
287 cache->reliability = 1;
288 break;
292 else if (isShadowExit)
294 if (dsCache->shadowTransition[TransitionEntrance][0] >= 0 &&
295 dsCache->shadowTransition[TransitionEntrance][0] < width &&
296 dsCache->shadowTransition[TransitionEntrance][1] >= 0 &&
297 dsCache->shadowTransition[TransitionEntrance][1] < height)
299 int entranceIndex = width*dsCache->shadowTransition[TransitionEntrance][1] + dsCache->shadowTransition[TransitionEntrance][0];
300 PerPixelCache* entranceCache = &m_data->buffer()[entranceIndex];
301 // Great, we can say for definite the elevation of this pixel because the corresponding pixel is reliable
302 if (entranceCache->reliability != 0)
304 float entranceElevation = m_elevationData->buffer()[entranceIndex];
305 m_elevationData->buffer()[index] = entranceElevation - dsCache->shadowTransitionDistance[TransitionEntrance]*dsCache->sinLightElevation;
306 cache->reliability = 1;
307 break;
314 endProcessing();
316 invalidate();
317 m_configWidget->requestRedraw();
319 else
321 QMessageBox::warning(m_configWidget,
322 tr("Elevation field not initialised."),
323 tr("Please ensure the optimisation channel is displayed."));
327 /// Void-fill bilinear.
328 void tcElevationOptimization::voidFillBilinear()
330 if (0 != m_elevationData)
332 const int width = m_elevationData->width();
333 const int height = m_elevationData->height();
334 // Now start optimising
335 startProcessing("Bilinear void filling");
336 for (int j = 0; j < height; ++j)
338 progress(0.5f*j/(height-1));
339 int lastNonVoid = -1;
340 float lastNonVoidValue;
341 for (int i = 0; i < width; ++i)
343 int index = j*width + i;
344 if (m_data->buffer()[index].reliability != 0)
346 float value = m_elevationData->buffer()[index];
347 if (lastNonVoid != -1)
349 int gap = i - lastNonVoid;
350 float startAlt = lastNonVoidValue;
351 float dAlt = (value - lastNonVoidValue) / gap;
352 // Lovely, between lastNonVoid and i is a void
353 for (int ii = lastNonVoid+1; ii < i; ++ii)
355 int iIndex = j*width + ii;
356 float alt = startAlt + dAlt * (ii - lastNonVoid);
357 m_elevationData->buffer()[iIndex] = alt;
358 // Keep track of the gap
359 m_data->buffer()[iIndex].temporary.i = gap;
362 else
364 m_data->buffer()[index].temporary.i = -1;
366 lastNonVoid = i;
367 lastNonVoidValue = value;
369 else
371 m_data->buffer()[index].temporary.i = -1;
375 for (int i = 0; i < width; ++i)
377 progress(0.5f + 0.5f*i/(width-1));
378 int lastNonVoid = -1;
379 float lastNonVoidValue;
380 for (int j = 0; j < height; ++j)
382 int index = j*width + i;
383 float value = m_elevationData->buffer()[index];
384 if (m_data->buffer()[index].reliability != 0)
386 if (lastNonVoid != -1)
388 int gap = j - lastNonVoid;
389 float startAlt = lastNonVoidValue;
390 float dAlt = (value - lastNonVoidValue) / gap;
391 // Lovely, between lastNonVoid and j is a void
392 for (int jj = lastNonVoid+1; jj < j; ++jj)
394 // Average with previous value if non zero, otherwise overwrite
395 int iIndex = jj*width + i;
396 float alt = startAlt + dAlt * (jj - lastNonVoid);
397 int otherGap = m_data->buffer()[iIndex].temporary.i;
398 if (otherGap != -1)
400 float prevAlt = m_elevationData->buffer()[iIndex];
401 // Prefer the value of the smaller gap
402 alt = (alt * otherGap + prevAlt * gap) / (gap+otherGap);
404 m_elevationData->buffer()[iIndex] = alt;
407 lastNonVoid = j;
408 lastNonVoidValue = value;
412 endProcessing();
414 invalidate();
415 m_configWidget->requestRedraw();
417 else
419 QMessageBox::warning(m_configWidget,
420 tr("Elevation field not initialised."),
421 tr("Please ensure the optimisation channel is displayed."));
425 /// Void-fill bicubic.
426 void tcElevationOptimization::voidFillBicubic()
428 if (0 != m_elevationData)
430 const int width = m_elevationData->width();
431 const int height = m_elevationData->height();
432 // Now start optimising
433 startProcessing("Bicubic void filling");
434 for (int j = 0; j < height; ++j)
436 progress(0.5f*j/(height-1));
437 int lastNonVoid = -1;
438 float lastNonVoidValue;
439 for (int i = 0; i < width; ++i)
441 int index = j*width + i;
442 if (m_data->buffer()[index].reliability != 0)
444 float value = m_elevationData->buffer()[index];
445 if (lastNonVoid != -1)
447 int gap = i - lastNonVoid;
448 // Obtain some control points for the splines
449 // use the two samples either side of the gap as control points 1 and 2
450 // use the ones before and after these samples, extended to the size of
451 // the gap as control points 0 and 3
452 float startAlt = lastNonVoidValue;
453 float beforeStartAlt = startAlt;
454 if (lastNonVoid > 0)
456 if (0 != m_data->buffer()[j*width + lastNonVoid - 1].reliability)
458 beforeStartAlt = startAlt + (m_elevationData->buffer()[j*width + lastNonVoid - 1] - startAlt) * gap;
461 float endAlt = value;
462 float afterEndAlt = endAlt;
463 if (i < width-1)
465 if (0 != m_data->buffer()[j*width + i + 1].reliability)
467 afterEndAlt = endAlt + (m_elevationData->buffer()[j*width + i + 1] - endAlt) * gap;
470 // Lovely, between lastNonVoid and i is a void
471 for (int ii = lastNonVoid+1; ii < i; ++ii)
473 int iIndex = j*width + ii;
474 float u = (float)(ii-lastNonVoid) / gap;
475 float u2 = u*u;
476 float u3 = u2*u;
477 float alt = maths::catmullRomSpline.Spline(u, u2, u3, beforeStartAlt, startAlt, endAlt, afterEndAlt);
478 m_elevationData->buffer()[iIndex] = alt;
479 // Keep track of the gap
480 m_data->buffer()[iIndex].temporary.i = gap;
483 else
485 m_data->buffer()[index].temporary.i = -1;
487 lastNonVoid = i;
488 lastNonVoidValue = value;
490 else
492 m_data->buffer()[index].temporary.i = -1;
496 for (int i = 0; i < width; ++i)
498 progress(0.5f + 0.5f*i/(width-1));
499 int lastNonVoid = -1;
500 float lastNonVoidValue;
501 for (int j = 0; j < height; ++j)
503 int index = j*width + i;
504 float value = m_elevationData->buffer()[index];
505 if (m_data->buffer()[index].reliability != 0)
507 if (lastNonVoid != -1)
509 int gap = j - lastNonVoid;
510 // Obtain some control points for the splines
511 // use the two samples either side of the gap as control points 1 and 2
512 // use the ones before and after these samples, extended to the size of
513 // the gap as control points 0 and 3
514 float startAlt = lastNonVoidValue;
515 float beforeStartAlt = startAlt;
516 if (lastNonVoid > 0)
518 if (0 != m_data->buffer()[(lastNonVoid-1)*width + i].reliability)
520 beforeStartAlt = startAlt + (m_elevationData->buffer()[(lastNonVoid-1)*width + i] - startAlt) * gap;
523 float endAlt = value;
524 float afterEndAlt = endAlt;
525 if (j < height-1)
527 if (0 != m_data->buffer()[(j+1)*width + i].reliability)
529 afterEndAlt = endAlt + (m_elevationData->buffer()[(j+1)*width + i] - endAlt) * gap;
532 // Lovely, between lastNonVoid and j is a void
533 for (int jj = lastNonVoid+1; jj < j; ++jj)
535 // Average with previous value if non zero, otherwise overwrite
536 int iIndex = jj*width + i;
537 float u = (float)(jj-lastNonVoid) / gap;
538 float u2 = u*u;
539 float u3 = u2*u;
540 float alt = maths::catmullRomSpline.Spline(u, u2, u3, beforeStartAlt, startAlt, endAlt, afterEndAlt);
541 int otherGap = m_data->buffer()[iIndex].temporary.i;
542 if (otherGap != -1)
544 float prevAlt = m_elevationData->buffer()[iIndex];
545 // Prefer the value of the smaller gap
546 alt = (alt * otherGap + prevAlt * gap) / (gap+otherGap);
548 m_elevationData->buffer()[iIndex] = alt;
551 lastNonVoid = j;
552 lastNonVoidValue = value;
556 endProcessing();
558 invalidate();
559 m_configWidget->requestRedraw();
561 else
563 QMessageBox::warning(m_configWidget,
564 tr("Elevation field not initialised."),
565 tr("Please ensure the optimisation channel is displayed."));
569 /// Optimize a number of times.
570 void tcElevationOptimization::optimize()
572 if (0 != m_elevationData)
574 const int width = m_elevationData->width();
575 const int height = m_elevationData->height();
576 // Now start optimising
577 startProcessing("Optimizing elevation data");
578 int passes = m_spinIterations->value();
579 for (int i = 0; i < passes; ++i)
581 progress((float)i/passes);
582 m_weights.variance = 1.0f;
583 m_weights.roughness = 50.0f *(1.0f - (float)i/passes);
584 m_weights.steepness = 0;//5000;//200.0f;
585 m_weights.litFacing = 1000.0f;
586 m_weights.shading = 1.0f * i/passes;
587 m_weights.belowShadowLine = 2000.0f*i/passes;
588 m_weights.transitionElev = 1000.0f;
589 m_weights.transitionTangential = 1000.0f*i/passes;
590 m_weights.transitionCurvingDown = 10.0f*i/passes;
591 optimizeElevations(0, 0, width-1, height-1, 8.0f - 7.8f*i/passes, (int)(4.5f - 4.0f*i/passes));
593 if ((i+1) % 2 == 0 || i == passes-1)
595 invalidate();
596 m_configWidget->requestRedraw();
598 startProcessing("Optimizing elevation data");
599 progress((float)i/passes);
602 endProcessing();
604 else
606 QMessageBox::warning(m_configWidget,
607 tr("Elevation field not initialised."),
608 tr("Please ensure the optimisation channel is displayed."));
613 * Interface for derived class to implement
616 void tcElevationOptimization::roundPortion(double* x1, double* y1, double* x2, double* y2)
618 //m_shadowChannel->roundPortion(x1,y1,x2,y2);
621 tcAbstractPixelData* tcElevationOptimization::loadPortion(double x1, double y1, double x2, double y2)
623 int width = -1;
624 int height = -1;
626 for (int i = 0; i < m_numDatasets; ++i)
628 maths::Vector<2,double> xy1 = m_texToDatasetTex[i] * maths::Vector<2,double>(x1,y1);
629 maths::Vector<2,double> xy2 = m_texToDatasetTex[i] * maths::Vector<2,double>(x2,y2);
630 Reference<tcAbstractPixelData> channelData = m_shadowChannels[i]->portion(xy1[0], xy1[1], xy2[0], xy2[1]);
631 if (i == 0)
633 width = channelData->width();
634 height = channelData->height();
636 m_shadowData[i] = dynamicCast<tcPixelData<float>*>(channelData);
637 if (0 == m_shadowData[i])
639 return 0;
641 if (0 != m_shadingChannels[i])
643 Reference<tcAbstractPixelData> shadingData = m_shadingChannels[i]->portion(xy1[0], xy1[1], xy2[0], xy2[1]);
644 m_shadingData[i] = dynamicCast<tcPixelData<float>*>(shadingData);
646 else
648 m_shadingData[i] = 0;
652 /// @todo What if its a different size because the portion has changed?
653 if (0 == m_elevationData || 0 == m_data)
655 delete m_elevationData;
656 for (int i = 0; i < m_numDatasets; ++i)
658 delete m_datum[i];
660 delete m_data;
662 // Create a new pixel buffer, initialised to altitude
664 m_elevationData = new tcElevationData(width, height);
665 // Find transform of elevation data
666 tcGeo geoBase = geoAt(maths::Vector<2,float>(x1, y1));
667 tcGeo geoDiagonal = geoAt(maths::Vector<2,float>(x1 + (x2-x1)/(width-1), y1 + (y2-y1)/(height-1))) - geoBase;
668 m_elevationData->setGeoToPixels(tcAffineTransform2<double>(geoBase, geoDiagonal).inverse());
670 m_data = new tcTypedPixelData<PerPixelCache>(width, height);
671 for (int i = 0; i < m_numDatasets; ++i)
673 m_datum[i] = new tcTypedPixelData<PerDatasetPixelCache>(width, height);
676 int maxWidthHeight = qMax(width, height);
677 double minXY12 = qMin(x2-x1, y2-y1);
679 startProcessing("Caching elevation characteristics");
680 for (int j = 0; j < height; ++j)
682 progress((float)j/(height-1));
683 for (int i = 0; i < width; ++i)
685 int index = j*width + i;
686 // Transform coordinates
687 maths::Vector<2,float> coord (x1 + (x2-x1)*i / (width - 1),
688 y1 + (y2-y1)*j / (height - 1));
689 tcGeo geoCoord = geoAt(coord);
691 PerPixelCache* cache = &m_data->buffer()[index];
693 // Find resolution
694 maths::Vector<2,float> coordx (x1 + (x2-x1)*(i+1) / (width - 1),
695 y1 + (y2-y1)*j / (height - 1));
696 maths::Vector<2,float> coordy (x1 + (x2-x1)*i / (width - 1),
697 y1 + (y2-y1)*(j+1) / (height - 1));
698 tcGeo geoCoordx = geoAt(coordx) - geoCoord;
699 tcGeo geoCoordy = geoAt(coordy) - geoCoord;
700 // Find approx
701 float res = geoCoordx.angle();
702 cache->resolution[0] = res;
703 cache->resolution[1] = geoCoordy.angle();
704 cache->resolution *= 6378.137e3;
706 // Get some elevation data
707 bool accurate;
708 float altitude = altitudeAt(geoCoord, true, &accurate);
709 m_elevationData->buffer()[index] = altitude;
710 cache->originalElevation = altitude;
711 cache->reliability = accurate ? 1 : 0;
713 // Per dataset cache
714 for (int ds = 0; ds < m_numDatasets; ++ds)
716 tcGeoImageData* imagery = m_datasets[ds];
717 maths::Vector<3,float> light = lightDirectionAt(geoCoord, imagery);
719 PerDatasetPixelCache* dsCache = &m_datum[ds]->buffer()[index];
720 dsCache->light = light;
721 dsCache->sinLightElevation = light[2]/light.slice<0,2>().mag();
723 tcGeo lightScaled(light[0]*res/sin(geoCoord.lat()),
724 light[1]*res);
725 tcGeo offset(geoCoord + lightScaled);
726 for (int depthDirection = 0; depthDirection < 2; ++depthDirection)
728 tcGeo pos = geoCoord;
729 tcGeo delta;
730 if (depthDirection == TransitionEntrance)
732 delta = offset - geoCoord;
734 else
736 delta = geoCoord - offset;
738 pos = pos + delta;
739 maths::Vector<2,float> tex = coord;
740 dsCache->shadowTransition[depthDirection][0] = i;
741 dsCache->shadowTransition[depthDirection][1] = j;
742 while (tex[0] >= x1 && tex[0] <= x2 && tex[1] >= y2 && tex[1] <= y1)
744 if (m_shadowData[ds]->sampleFloat((tex[0]-x1)/(x2-x1), (tex[1]-y1)/(y2-y1)) > 0.5f)
746 break;
748 dsCache->shadowTransition[depthDirection][0] = 0.5f + (tex[0]-x1)/(x2-x1)*(width -1);
749 dsCache->shadowTransition[depthDirection][1] = 0.5f + (tex[1]-y1)/(y2-y1)*(height-1);
750 pos = pos + delta;
751 tex = textureAt(pos);
753 dsCache->shadowTransitionDistance[depthDirection] = (pos-geoCoord).angle()*6378.137e3;
760 if (!m_loadingFromDem)
762 // Update elevation model
763 startProcessing("Updating elevation model");
764 dem()->updateFromElevationData(m_elevationData);
767 // Downscale the elevation for viewing
768 startProcessing("Downscaling for viewing");
769 tcPixelData<GLubyte>* result = new tcPixelData<GLubyte>(width, height);
770 int index = 0;
771 for (int j = 0; j < height; ++j)
773 progress((float)j/(height-1));
774 for (int i = 0; i < width; ++i)
776 result->buffer()[index] = 255.0f * m_elevationData->buffer()[index] / 4000.0f;
777 ++index;
781 endProcessing();
783 return result;
786 template <typename T>
787 T MySqr(T t)
789 return t*t;
793 * Private functions
796 /// Calculate the cost for a set of pixels.
797 float tcElevationOptimization::cost(int x1, int y1, int x2, int y2) const
799 int width = m_elevationData->width();
800 int height = m_elevationData->height();
801 if (x1 < 0)
803 x1 = 0;
805 if (x2 >= width)
807 x2 = width-1;
809 if (y1 < 0)
811 y1 = 0;
813 if (y2 >= height)
815 y2 = height-1;
818 int maxWidthHeight = qMax(width, height);
819 //double minXY12 = qMin(m_window.x2-m_window.x1, m_window.y2-m_window.y1);
821 float costVariance = 0.0f;
822 int countVariance = 0;
823 float costRoughness = 0.0f;
824 int countRoughness = 0;
825 float costSteepness = 0.0f;
826 int countSteepness = 0;
827 float costLitFacing = 0.0f;
828 int countLitFacing = 0;
829 float costShading = 0.0f;
830 int countShading = 0;
831 float costBelowShadowLine = 0.0f;
832 int countBelowShadowLine = 0;
833 float costTransitionElev = 0.0f;
834 int countTransitionElev = 0;
835 float costTransitionTangential = 0.0f;
836 int countTransitionTangential = 0;
837 float costTransitionCurvingDown = 0.0f;
838 int countTransitionCurvingDown = 0;
839 for (int j = y1; j <= y2; ++j)
841 for (int i = x1; i <= x2; ++i)
843 float* elevationPtr = pixelElevation(i, j);
844 if (0 == elevationPtr)
846 continue;
849 int index = j*width + i;
851 // Basic data retrieval
852 float elevation = *elevationPtr;
853 PerPixelCache* cache = &m_data->buffer()[index];
855 // Gradient
856 // Derivitive of gradient
857 float dh_dx = 0.0f;
858 float dh_dy = 0.0f;
859 float d2h_dx2 = 0.0f;
860 float d2h_dy2 = 0.0f;
861 if (i > 0 && i < width-1)
863 float hp1 = m_elevationData->buffer()[index+1];
864 float hm1 = m_elevationData->buffer()[index-1];
865 d2h_dx2 = (hp1+hm1)/2 - elevation;
866 dh_dx = (hp1-hm1)/2 / cache->resolution[0];
868 if (j > 0 && j < height-1)
870 float hp1 = m_elevationData->buffer()[index+width];
871 float hm1 = m_elevationData->buffer()[index-width];
872 d2h_dy2 = (hp1+hm1)/2 - elevation;
873 dh_dy = (hp1-hm1)/2 / cache->resolution[1];
876 // Minimise variance from original value
877 costVariance += cache->reliability * (elevation-cache->originalElevation)*(elevation-cache->originalElevation);
878 ++countVariance;
880 // Minimise roughness
881 costRoughness += (d2h_dx2*d2h_dx2 + d2h_dy2*d2h_dy2);
882 ++countRoughness;
884 // Also minimise slope
885 // This works really well at getting a nice slope
886 costSteepness += (dh_dx*dh_dx + dh_dy*dh_dy);
887 ++countSteepness;
889 // Per dataset
890 for (int ds = 0; ds < m_numDatasets; ++ds)
892 PerDatasetPixelCache* dsCache = &m_datum[ds]->buffer()[index];
894 bool isShadowed = (m_shadowData[ds]->sampleFloat((float)i/(width-1), (float)j/(height-1)) < 0.5f);
895 bool isShadowEntrance = isShadowed &&
896 dsCache->shadowTransition[TransitionEntrance][0] == i &&
897 dsCache->shadowTransition[TransitionEntrance][1] == j;
898 bool isShadowExit = isShadowed &&
899 dsCache->shadowTransition[TransitionExit][0] == i &&
900 dsCache->shadowTransition[TransitionExit][1] == j;
902 // Calculate measures relating to the light direction
903 // Gradient
904 float hwm1 = m_elevationData->sampleFloat(((float)i - dsCache->light[0])/(width-1),
905 ((float)j - dsCache->light[1])/(height-1));
906 float hwp1 = m_elevationData->sampleFloat(((float)i + dsCache->light[0])/(width-1),
907 ((float)j + dsCache->light[1])/(height-1));
908 /// @todo Ensure units are right (this is in terms of h metres, w pixels)
909 float dh_dw = (hwm1 - hwp1)/2 / cache->resolution[0]; // 30 metre resolution?
910 float d2h_dw2 = (hwm1+hwp1)/2 - elevation;
912 if (isShadowed)
914 // maximum elevation is between elevation at entrance and elevation at exit
915 if (dsCache->shadowTransition[TransitionExit][0] >= 0 &&
916 dsCache->shadowTransition[TransitionExit][0] < width &&
917 dsCache->shadowTransition[TransitionExit][1] >= 0 &&
918 dsCache->shadowTransition[TransitionExit][1] < height &&
919 dsCache->shadowTransition[TransitionEntrance][0] >= 0 &&
920 dsCache->shadowTransition[TransitionEntrance][0] < width &&
921 dsCache->shadowTransition[TransitionEntrance][1] >= 0 &&
922 dsCache->shadowTransition[TransitionEntrance][1] < height)
924 int exitIndex = width*dsCache->shadowTransition[TransitionExit][1] + dsCache->shadowTransition[TransitionExit][0];
925 int entranceIndex = width*dsCache->shadowTransition[TransitionEntrance][1] + dsCache->shadowTransition[TransitionEntrance][0];
926 float exitElevation = m_elevationData->buffer()[exitIndex];
927 float entranceElevation = m_elevationData->buffer()[entranceIndex];
929 costBelowShadowLine += MySqr(qMax(0.0f, elevation - (exitElevation * dsCache->shadowTransitionDistance[TransitionEntrance]
930 +entranceElevation * dsCache->shadowTransitionDistance[TransitionExit])
931 / (dsCache->shadowTransitionDistance[TransitionEntrance]
932 +dsCache->shadowTransitionDistance[TransitionExit])));
933 ++countBelowShadowLine;
936 else
938 float facingness = -dh_dw-dsCache->light[0];
939 if (facingness < 0.0f)
941 // Lit pixels must face light
942 costLitFacing += facingness*facingness;
943 ++countLitFacing;
945 // Simple shape from shading
946 if (0 != m_shadingData[ds])
948 float intensity = m_shadingData[ds]->sampleFloat((float)i/(width-1), (float)j/(height-1));
949 // intensity = cos(theta) = L.N
950 maths::Vector<3,float> xTan(1.0f, 0.0f, dh_dx);
951 maths::Vector<3,float> yTan(0.0f, 1.0f, dh_dy);
952 costShading += MySqr(acos(qMin(1.0f, intensity)) - acos(qMin(1.0f, dsCache->light*maths::cross(xTan, yTan).normalized())));
953 ++countShading;
956 if (isShadowEntrance)
958 if (dsCache->shadowTransition[TransitionExit][0] >= 0 &&
959 dsCache->shadowTransition[TransitionExit][0] < width &&
960 dsCache->shadowTransition[TransitionExit][1] >= 0 &&
961 dsCache->shadowTransition[TransitionExit][1] < height)
963 int exitIndex = width*dsCache->shadowTransition[TransitionExit][1] + dsCache->shadowTransition[TransitionExit][0];
964 float exitElevation = m_elevationData->buffer()[exitIndex];
965 costTransitionElev += (1.0f - cache->reliability) * MySqr(exitElevation + dsCache->shadowTransitionDistance[TransitionExit]*dsCache->sinLightElevation - elevation);
966 ++countTransitionElev;
968 // gradient tangential to sun direction
969 costTransitionTangential += MySqr(dh_dw - dsCache->sinLightElevation);
970 ++countTransitionTangential;
971 // second derivitive should be negative at entrance
972 costTransitionCurvingDown += MySqr(qMax(0.0f, d2h_dw2));
973 ++countTransitionCurvingDown;
975 if (0 && isShadowExit)
977 if (dsCache->shadowTransition[TransitionEntrance][0] >= 0 &&
978 dsCache->shadowTransition[TransitionEntrance][0] < width &&
979 dsCache->shadowTransition[TransitionEntrance][1] >= 0 &&
980 dsCache->shadowTransition[TransitionEntrance][1] < height)
982 int entranceIndex = width*dsCache->shadowTransition[TransitionEntrance][1] + dsCache->shadowTransition[TransitionEntrance][0];
983 float entranceElevation = m_elevationData->buffer()[entranceIndex];
984 // this has less effect, its usually "more" right
985 /// @todo Smooth high reliability into low reliability
986 costTransitionElev += 0.1f * (1.0f - cache->reliability) * MySqr(entranceElevation - dsCache->shadowTransitionDistance[TransitionEntrance]*dsCache->sinLightElevation - elevation);
987 ++countTransitionElev;
994 return m_weights.variance*costVariance//countVariance
995 + m_weights.roughness*costRoughness//countRoughness
996 + m_weights.steepness*costSteepness//countSteepness
997 + m_weights.litFacing*costLitFacing//countLitFacing
998 + m_weights.shading*costShading//countShading
999 + m_weights.belowShadowLine*costBelowShadowLine//countBelowShadowLine
1000 + m_weights.transitionElev*costTransitionElev//countTransitionElev
1001 + m_weights.transitionTangential*costTransitionTangential//countTransitionTangential
1002 + m_weights.transitionCurvingDown*costTransitionCurvingDown//countTransitionCurvingDown
1007 * f(0) = 1
1008 * f(range) = small
1009 * f(x) = exp[ - (2*x/range)^2 ]
1011 inline float elevationFalloffFunction(float xsq)
1013 return qMax(0.0f, 1.0f - xsq);
1014 //return exp(-4*xsq);
1019 /// Calculate the relative cost of changing a pixel's elevation.
1020 float tcElevationOptimization::costChange(int x, int y, float dh, int range)
1022 // Override range for now
1023 #define ELEV_RANGE 4
1024 range = qMin(range, ELEV_RANGE);
1025 static const int effectiveRange = 1 + range;
1026 float oldElevations[1+2*ELEV_RANGE][1+2*ELEV_RANGE];
1028 // Assume a single change in elevation only affects cost of pixels to effectiveRange from it.
1029 // Now technically this doesn't hold for shadow edges as all pixels across the shadow can be affected.
1030 // If the pixel is a shadow edge, take into account the cost of extra pixels.
1031 float currentCost = cost(x-effectiveRange, y-effectiveRange, x+effectiveRange, y+effectiveRange);
1032 for (int i = 0; i < 1+range*2; ++i)
1034 int di = i - range;
1035 for (int j = 0; j < 1+range*2; ++j)
1037 int dj = j - range;
1038 float* elev = pixelElevation(x+di, y+dj);
1039 if (0 != elev)
1041 oldElevations[i][j] = *elev;
1042 *elev += dh*elevationFalloffFunction(float(di*di + dj*dj) / ((range-1)*(range-1)));
1046 float newCost = cost(x-effectiveRange, y-effectiveRange, x+effectiveRange, y+effectiveRange);
1047 for (int i = 0; i < 1+range*2; ++i)
1049 int di = i - range;
1050 for (int j = 0; j < 1+range*2; ++j)
1052 int dj = j - range;
1053 float* elev = pixelElevation(x+di, y+dj);
1054 if (0 != elev)
1056 *elev = oldElevations[i][j];
1060 return newCost - currentCost;
1063 /// Optimize a whole range of elevations.
1064 void tcElevationOptimization::optimizeElevations(int x1, int y1, int x2, int y2, float dh, int range)
1066 for (int j = y1; j <= y2; ++j)
1068 for (int i = x1; i <= x2; ++i)
1070 int index = j*m_data->width() + i;
1071 //while (true)
1073 // Find the cost of adjusting the elevation by dh in each direction
1074 float costInc = costChange(i,j, dh, range);
1075 float costDec = costChange(i,j,-dh, range);
1076 if (costInc < 0.0f && costInc < costDec)
1078 adjustElevation(i,j,dh, range);
1080 else if (costDec < 0.0f)
1082 adjustElevation(i,j,-dh, range);
1084 else
1086 //break;
1093 /// Get pointer to elevation at a pixel.
1094 float* tcElevationOptimization::pixelElevation(int x, int y) const
1096 if (x < 0 || x >= m_elevationData->width() ||
1097 y < 0 || y >= m_elevationData->height())
1099 return 0;
1101 int index = y*m_elevationData->width() + x;
1102 return &(m_elevationData->buffer()[index]);
1105 /// Adjust elevation at a pixel.
1106 void tcElevationOptimization::adjustElevation(int x, int y, float dh, int range)
1108 range = qMin(range, ELEV_RANGE);
1109 for (int i = 0; i < 1+range*2; ++i)
1111 int di = i - range;
1112 for (int j = 0; j < 1+range*2; ++j)
1114 int dj = j - range;
1115 float* elev = pixelElevation(x+di, y+dj);
1116 if (0 != elev)
1118 *elev += dh*elevationFalloffFunction(float(di*di + dj*dj) / ((range+1)*(range+1)));