Subversion Repositories public iLand

Rev

Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
671 werner 1
/********************************************************************************************
2
**    iLand - an individual based forest landscape and disturbance model
3
**    http://iland.boku.ac.at
4
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
5
**
6
**    This program is free software: you can redistribute it and/or modify
7
**    it under the terms of the GNU General Public License as published by
8
**    the Free Software Foundation, either version 3 of the License, or
9
**    (at your option) any later version.
10
**
11
**    This program is distributed in the hope that it will be useful,
12
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
**    GNU General Public License for more details.
15
**
16
**    You should have received a copy of the GNU General Public License
17
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
********************************************************************************************/
767 werner 19
// for redirecting the script output
780 werner 20
#ifdef ILAND_GUI
767 werner 21
#include <QTextEdit>
780 werner 22
#endif
793 werner 23
#include <QJSValue>
247 werner 24
#include "global.h"
25
#include "scriptglobal.h"
26
#include "model.h"
1091 werner 27
#include "resourceunit.h"
247 werner 28
#include "globalsettings.h"
29
#include "helper.h"
808 werner 30
#include "debugtimer.h"
294 werner 31
#include "standloader.h"
552 werner 32
#include "mapgrid.h"
589 werner 33
#include "outputmanager.h"
590 werner 34
#include "modelcontroller.h"
599 werner 35
#include "grid.h"
675 werner 36
#include "snapshot.h"
1064 werner 37
#include "speciesset.h"
38
#include "species.h"
39
#include "seeddispersal.h"
1081 werner 40
#include "scriptgrid.h"
1091 werner 41
#include "expressionwrapper.h"
767 werner 42
 
794 werner 43
 
767 werner 44
// for accessing script publishing functions
45
#include "climateconverter.h"
46
#include "csvfile.h"
47
#include "spatialanalysis.h"
1041 werner 48
 
49
#ifdef ILAND_GUI
50
#include "mainwindow.h"
51
#include "ui_mainwindow.h"
52
#include "colors.h"
53
#endif
54
 
294 werner 55
class ResourceUnit;
56
 
248 werner 57
/** @class ScriptGlobal
697 werner 58
  @ingroup scripts
248 werner 59
   This is a global interface providing useful functionality for javascripts.
247 werner 60
  Within javascript-code an instance of this class can be accessed as "Globals" in the global scope
250 werner 61
 (no instantiation necessary).*/
247 werner 62
 
250 werner 63
/** \page globals Globals documentation
64
  Here are objects visible in the global space of javascript.
65
  \section sec An example section
66
  This page contains the subsections \ref subsection1 and \ref subsection2.
67
  For more info see page \ref page2.
68
  \subsection subsection1 The first subsection
69
  Text.
70
  \subsection subsection2 The second subsection
71
 - year integer. Current simulation year
72
 - currentDir current working directory. default value is the "script" directory defined in the project file.
73
  More text.
247 werner 74
*/
75
 
767 werner 76
QObject *ScriptGlobal::scriptOutput = 0;
250 werner 77
 
767 werner 78
ScriptGlobal::ScriptGlobal(QObject *)
247 werner 79
{
80
    mModel = GlobalSettings::instance()->model();
81
    // current directory
802 werner 82
    if (mModel)
83
        mCurrentDir = GlobalSettings::instance()->path(QString(), "script") + QDir::separator();
247 werner 84
}
85
 
767 werner 86
 
294 werner 87
QVariant ScriptGlobal::setting(QString key)
88
{
89
    const XmlHelper &xml = GlobalSettings::instance()->settings();
90
    if (!xml.hasNode(key)) {
91
        qDebug() << "scriptglobal: setting key " << key << "not valid.";
92
        return QVariant(); // undefined???
93
    }
94
    return QVariant(xml.value(key));
95
}
96
void ScriptGlobal::set(QString key, QString value)
97
{
98
    XmlHelper &xml = const_cast<XmlHelper&>(GlobalSettings::instance()->settings());
99
    if (!xml.hasNode(key)) {
100
        qDebug() << "scriptglobal: setting key " << key << "not valid.";
101
        return;
102
    }
103
    xml.setNodeValue(key, value);
104
}
105
 
794 werner 106
 
107
 
793 werner 108
void ScriptGlobal::print(QString message)
109
{
110
    qDebug() << message;
794 werner 111
#ifdef ILAND_GUI
112
    if (ScriptGlobal::scriptOutput) {
113
        QTextEdit *e = qobject_cast<QTextEdit*>(ScriptGlobal::scriptOutput);
114
        if (e)
115
            e->append(message);
116
    }
117
 
118
#endif
119
 
793 werner 120
}
121
 
794 werner 122
void ScriptGlobal::alert(QString message)
123
{
124
    Helper::msg(message); // nothing happens when not in GUI mode
125
 
126
}
127
 
1061 werner 128
 
794 werner 129
void ScriptGlobal::include(QString filename)
130
{
873 werner 131
    QString path = GlobalSettings::instance()->path(filename);
132
    if (!QFile::exists(path))
133
        throw IException(QString("include(): The javascript source file '%1' could not be found.").arg(path));
134
 
794 werner 135
    QString includeFile=Helper::loadTextFile(path);
136
 
873 werner 137
    QJSValue ret = GlobalSettings::instance()->scriptEngine()->evaluate(includeFile, path);
138
    if (ret.isError()) {
139
        QString error_message = formattedErrorMessage(ret, includeFile);
140
        qDebug() << error_message;
141
        throw IException("Error in javascript-include():" + error_message);
142
    }
794 werner 143
 
144
}
145
 
389 werner 146
QString ScriptGlobal::defaultDirectory(QString dir)
147
{
148
    QString result = GlobalSettings::instance()->path(QString(), dir) + QDir::separator();
149
    return result;
150
}
151
 
1077 werner 152
QString ScriptGlobal::path(QString filename)
153
{
154
    return GlobalSettings::instance()->path(filename);
155
}
156
 
247 werner 157
int ScriptGlobal::year() const
158
{
159
    return GlobalSettings::instance()->currentYear();
160
}
294 werner 161
int ScriptGlobal::resourceUnitCount() const
162
{
163
    Q_ASSERT(mModel!=0);
164
    return mModel->ruList().count();
165
}
850 werner 166
 
167
double ScriptGlobal::worldX()
168
{
169
    return GlobalSettings::instance()->model()->extent().width();
170
}
171
 
172
double ScriptGlobal::worldY()
173
{
174
    return GlobalSettings::instance()->model()->extent().height();
175
}
247 werner 176
// wrapped helper functions
177
QString ScriptGlobal::loadTextFile(QString fileName)
178
{
294 werner 179
    return Helper::loadTextFile(GlobalSettings::instance()->path(fileName));
247 werner 180
}
181
void ScriptGlobal::saveTextFile(QString fileName, QString content)
182
{
183
    Helper::saveToTextFile(fileName, content);
184
}
185
bool ScriptGlobal::fileExists(QString fileName)
186
{
1180 werner 187
    return QFile::exists(fileName);
247 werner 188
}
294 werner 189
 
1180 werner 190
void ScriptGlobal::systemCmd(QString command)
191
{
192
    qDebug() << "running system command:" << command;
193
    QProcess process;
194
    process.start(command);
195
    process.waitForFinished(); // will wait forever until finished
196
 
197
    QByteArray res_stdout = process.readAllStandardOutput();
198
    QByteArray res_stderr = process.readAllStandardError();
199
    qDebug() << "result (stdout):" << res_stdout;
200
    qDebug() << "result (stderr):" << res_stderr;
201
}
202
 
393 werner 203
/// add trees on given resource unit
204
/// @param content init file in a string (containing headers)
205
/// @return number of trees added
206
int ScriptGlobal::addSingleTrees(const int resourceIndex, QString content)
294 werner 207
{
208
    StandLoader loader(mModel);
209
    ResourceUnit *ru = mModel->ru(resourceIndex);
210
    if (!ru)
211
        throw IException(QString("addSingleTrees: invalid resource unit (index: %1").arg(resourceIndex));
389 werner 212
    int cnt = loader.loadSingleTreeList(content, ru, "called_from_script");
213
    qDebug() << "script: addSingleTrees:" << cnt <<"trees loaded.";
393 werner 214
    return cnt;
294 werner 215
}
216
 
393 werner 217
int ScriptGlobal::addTrees(const int resourceIndex, QString content)
294 werner 218
{
219
    StandLoader loader(mModel);
220
    ResourceUnit *ru = mModel->ru(resourceIndex);
221
    if (!ru)
222
        throw IException(QString("addTrees: invalid resource unit (index: %1").arg(resourceIndex));
549 werner 223
    return loader.loadDistributionList(content, ru, 0, "called_from_script");
294 werner 224
}
552 werner 225
 
603 werner 226
int ScriptGlobal::addTreesOnMap(const int standID, QString content)
559 werner 227
{
228
    StandLoader loader(mModel);
229
    return loader.loadDistributionList(content, NULL, standID, "called_from_script");
230
}
552 werner 231
 
232
/*
233
********** MapGrid wrapper
234
*/
235
 
793 werner 236
//Q_SCRIPT_DECLARE_QMETAOBJECT(MapGridWrapper, QObject*)
603 werner 237
 
793 werner 238
void MapGridWrapper::addToScriptEngine(QJSEngine &engine)
552 werner 239
{
240
    // about this kind of scripting magic see: http://qt.nokia.com/developer/faqs/faq.2007-06-25.9557303148
793 werner 241
    //QJSValue cc_class = engine.scriptValueFromQMetaObject<MapGridWrapper>();
552 werner 242
    // the script name for the object is "Map".
793 werner 243
    // TODO: solution for creating objects!!!
244
    QObject *mgw = new MapGridWrapper();
245
    QJSValue mgw_cls = engine.newQObject(mgw);
246
    engine.globalObject().setProperty("Map", mgw_cls);
552 werner 247
}
248
 
767 werner 249
MapGridWrapper::MapGridWrapper(QObject *)
552 werner 250
{
817 werner 251
    mCreated = false;
813 werner 252
    if (!GlobalSettings::instance()->model())
253
        return;
552 werner 254
    mMap = const_cast<MapGrid*>(GlobalSettings::instance()->model()->standGrid());
817 werner 255
 
552 werner 256
}
257
 
258
MapGridWrapper::~MapGridWrapper()
259
{
260
    if (mCreated)
261
        delete mMap;
262
}
263
 
264
 
265
void MapGridWrapper::load(QString file_name)
266
{
267
    if (mCreated)
268
        delete mMap;
269
    mMap = new MapGrid(file_name);
270
    mCreated = true;
271
}
272
 
273
bool MapGridWrapper::isValid() const
274
{
275
    return mMap->isValid();
276
}
277
 
767 werner 278
void MapGridWrapper::saveAsImage(QString)
552 werner 279
{
280
    qDebug() << "not implemented";
281
}
556 werner 282
 
596 werner 283
void MapGridWrapper::paint(double min_value, double max_value)
556 werner 284
{
285
    //gridToImage(mMap->grid(), false, min_value, max_value).save(file_name);
596 werner 286
    if (mMap) {
287
        if (GlobalSettings::instance()->controller())
288
            GlobalSettings::instance()->controller()->paintMap(mMap, min_value, max_value);
556 werner 289
 
596 werner 290
    }
291
 
556 werner 292
}
575 werner 293
 
848 werner 294
void MapGridWrapper::clear()
295
{
296
    if (!mCreated) {
297
        // create a empty map
298
        mMap = new MapGrid();
299
        mMap->createEmptyGrid();
300
        mCreated = true;
301
    }
302
    const_cast<Grid<int>& >(mMap->grid()).initialize(0); // clear all data and set to 0
303
}
304
 
980 werner 305
void MapGridWrapper::clearProjectArea()
306
{
307
    if (!mCreated) {
308
        // create a empty map
309
        mMap = new MapGrid();
310
        mMap->createEmptyGrid();
311
        mCreated = true;
312
    }
313
    const MapGrid *stand_grid = GlobalSettings::instance()->model()->standGrid();
314
    if (!stand_grid) {
315
        qDebug() << "MapGridWrapper::clearProjectArea: no valid stand grid to copy from!";
316
        return;
317
    }
318
    for(int *src=stand_grid->grid().begin(), *dest=mMap->grid().begin(); src!=stand_grid->grid().end(); ++src, ++dest)
319
        *dest = *src<0? *src : 0;
320
}
321
 
848 werner 322
void MapGridWrapper::createStand(int stand_id, QString paint_function, bool wrap_around)
323
{
324
    if (!mMap)
325
        throw IException("no valid map to paint on");
326
    Expression expr(paint_function);
327
    expr.setCatchExceptions(true);
328
    double *x_var = expr.addVar("x");
329
    double *y_var = expr.addVar("y");
330
    if (!wrap_around) {
331
        // now loop over all cells ...
332
        for (int *p = mMap->grid().begin(); p!=mMap->grid().end(); ++p) {
333
            QPoint pt = mMap->grid().indexOf(p);
334
            QPointF ptf = mMap->grid().cellCenterPoint(pt);
335
            // set the variable values and evaluate the expression
336
            *x_var = ptf.x();
337
            *y_var = ptf.y();
338
            if (expr.execute()) {
339
                *p = stand_id;
340
            }
341
        }
342
    } else {
343
        // WRAP AROUND MODE
344
        // now loop over all cells ...
345
        double delta_x = GlobalSettings::instance()->model()->extent().width();
346
        double delta_y = GlobalSettings::instance()->model()->extent().height();
347
 
348
        for (int *p = mMap->grid().begin(); p!=mMap->grid().end(); ++p) {
349
            QPoint pt = mMap->grid().indexOf(p);
350
            QPointF ptf = mMap->grid().cellCenterPoint(pt);
351
            if (ptf.x()<0. || ptf.x()>delta_x || ptf.y()<0. || ptf.y()>delta_y)
352
                continue;
353
            // set the variable values and evaluate the expression
354
            // we have to look at *9* positions to cover all wrap around cases....
355
            for (int dx=-1;dx<2;++dx) {
356
                for (int dy=-1;dy<2;++dy) {
357
                    *x_var = ptf.x() + dx*delta_x;
358
                    *y_var = ptf.y() + dy*delta_y;
359
                    if (expr.execute())
360
                        *p = stand_id;
361
                }
362
            }
363
        }
364
    }
365
    // after changing the map, recreate the index
366
    mMap->createIndex();
367
}
368
 
979 werner 369
double MapGridWrapper::copyPolygonFromRect(MapGridWrapper *source, int id_in, int id, double destx, double desty, double x1, double y1, double x2, double y2)
370
{
371
    const Grid<int> &src = source->map()->grid();
372
    Grid<int> &dest = const_cast<Grid<int> &>( mMap->grid() );
373
    QRect r = dest.rectangle().intersected(QRect(dest.indexAt(QPointF(destx, desty)),dest.indexAt(QPointF(destx+(x2-x1),desty+(y2-y1)))) );
374
    QPoint dest_coord = dest.indexAt(QPointF(destx, desty));
980 werner 375
    QPoint offset = dest.indexAt(QPointF(x1,y1)) - dest_coord;
979 werner 376
    qDebug() << "Rectangle" << r << "offset" << offset << "from" << QPointF(x1,y1) << "to" << QPointF(destx, desty);
377
    if (r.isNull())
378
        return 0.;
980 werner 379
 
380
    GridRunner<int> gr(dest, r);
979 werner 381
    int i=0, j=0;
382
    while (gr.next()) {
980 werner 383
        //if (gr.current()>=0) {
979 werner 384
            QPoint dp=gr.currentIndex()+offset;
385
            i++;
980 werner 386
            if (src.isIndexValid(dp) && src.constValueAtIndex(dp)==id_in && *gr.current()>=0) {
979 werner 387
                *gr.current() = id;
388
                //if (j<100) qDebug() << dp << gr.currentIndex() << src.constValueAtIndex(dp) << *gr.current();
389
                ++j;
390
            }
980 werner 391
        //}
979 werner 392
    }
1022 werner 393
    //qDebug() << "copyPolygonFromRect: copied" << j << "from" << i;
980 werner 394
 
395
    // after changing the map, recreate the index
396
    // mMap->createIndex();
397
 
979 werner 398
    return double(j)/100.; // in ha
399
 
400
}
401
 
980 werner 402
void MapGridWrapper::createMapIndex()
403
{
404
    if (mMap)
405
        mMap->createIndex();
406
}
407
 
575 werner 408
QString MapGridWrapper::name() const
409
{
410
    if (mMap)
411
        return mMap->name();
412
    else
413
        return "invalid";
414
}
578 werner 415
double MapGridWrapper::area(int id) {
416
    if (mMap && mMap->isValid())
417
        return mMap->area(id);
418
    else
419
        return -1;
420
}
421
 
589 werner 422
bool ScriptGlobal::startOutput(QString table_name)
423
{
802 werner 424
    if (table_name == "debug_dynamic") {
425
        GlobalSettings::instance()->controller()->setDynamicOutputEnabled(true);
426
        qDebug() << "started dynamic debug output";
427
        return true;
428
    }
599 werner 429
    if (table_name.startsWith("debug_")) {
430
        GlobalSettings::DebugOutputs dbg = GlobalSettings::instance()->debugOutputId(table_name.mid(6));
431
        if (dbg==0)
432
            qDebug() << "cannot start debug output" << table_name << "because this is not a valid name.";
433
        GlobalSettings::instance()->setDebugOutput(dbg, true);
434
        return true;
435
    }
589 werner 436
    OutputManager *om = GlobalSettings::instance()->outputManager();
437
    if (!om) return false;
438
    Output *out = om->find(table_name);
439
    if (!out) {
981 werner 440
        QString err=QString("startOutput: Output '%1' is not a valid output.").arg(table_name);
793 werner 441
        // TODO: ERROR function in script
442
//        if (context())
443
//           context()->throwError(err);
981 werner 444
        qWarning() << err;
589 werner 445
        return false;
446
    }
447
    out->setEnabled(true);
448
    qDebug() << "started output" << table_name;
449
    return true;
450
}
451
 
452
bool ScriptGlobal::stopOutput(QString table_name)
453
{
802 werner 454
    if (table_name == "debug_dynamic") {
455
        GlobalSettings::instance()->controller()->setDynamicOutputEnabled(false);
456
        qDebug() << "stopped dynamic debug output.";
457
        return true;
458
    }
599 werner 459
    if (table_name.startsWith("debug_")) {
460
        GlobalSettings::DebugOutputs dbg = GlobalSettings::instance()->debugOutputId(table_name.mid(6));
461
        if (dbg==0)
462
            qDebug() << "cannot stop debug output" << table_name << "because this is not a valid name.";
463
        GlobalSettings::instance()->setDebugOutput(dbg, false);
464
        return true;
465
    }
589 werner 466
    OutputManager *om = GlobalSettings::instance()->outputManager();
467
    if (!om) return false;
468
    Output *out = om->find(table_name);
469
    if (!out) {
981 werner 470
        QString err=QString("stopOutput: Output '%1' is not a valid output.").arg(table_name);
471
        qWarning() << err;
793 werner 472
        // TODO: ERROR function in script
473
//        if (context())
474
//           context()->throwError(err);
589 werner 475
        return false;
476
    }
477
    out->setEnabled(false);
478
    qDebug() << "stopped output" << table_name;
479
    return true;
480
}
481
 
482
bool ScriptGlobal::screenshot(QString file_name)
483
{
590 werner 484
    if (GlobalSettings::instance()->controller())
485
        GlobalSettings::instance()->controller()->saveScreenshot(file_name);
486
    return true;
589 werner 487
}
488
 
716 werner 489
void ScriptGlobal::repaint()
490
{
491
    if (GlobalSettings::instance()->controller())
492
        GlobalSettings::instance()->controller()->repaint();
493
}
494
 
634 werner 495
void ScriptGlobal::setViewport(double x, double y, double scale_px_per_m)
496
{
497
    if (GlobalSettings::instance()->controller())
498
        GlobalSettings::instance()->controller()->setViewport(QPointF(x,y), scale_px_per_m);
499
}
500
 
599 werner 501
// helper function...
502
QString heightGrid_height(const HeightGridValue &hgv) {
503
    return QString::number(hgv.height);
504
}
505
 
506
/// write grid to a file...
507
bool ScriptGlobal::gridToFile(QString grid_type, QString file_name)
508
{
509
    if (!GlobalSettings::instance()->model())
510
        return false;
511
    QString result;
512
    if (grid_type == "height")
513
        result = gridToESRIRaster(*GlobalSettings::instance()->model()->heightGrid(), *heightGrid_height);
514
    if (grid_type == "lif")
515
        result = gridToESRIRaster(*GlobalSettings::instance()->model()->grid());
680 werner 516
 
599 werner 517
    if (!result.isEmpty()) {
680 werner 518
        file_name = GlobalSettings::instance()->path(file_name);
599 werner 519
        Helper::saveToTextFile(file_name, result);
520
        qDebug() << "saved grid to " << file_name;
521
        return true;
522
    }
523
    qDebug() << "could not save gridToFile because" << grid_type << "is not a valid grid.";
524
    return false;
525
 
526
}
527
 
1081 werner 528
QJSValue ScriptGlobal::grid(QString type)
529
{
530
    int index = -1;
531
    if (type=="height") index = 0;
532
    if (type=="valid") index = 1;
533
    if (type=="count") index = 2;
534
    if (type=="forestoutside") index=3;
535
    if (index<0) {
1091 werner 536
        qDebug()<< "ScriptGlobal::grid(): error: invalid grid specified:" << type << ". valid options: 'height', 'valid', 'count', 'forestoutside'.";
1081 werner 537
    }
538
 
539
    HeightGrid *h = GlobalSettings::instance()->model()->heightGrid();
540
    Grid<double> *dgrid = new Grid<double>(h->cellsize(), h->sizeX(), h->sizeY());
541
    // fetch data from height grid
542
    double *p=dgrid->begin();
543
    for (HeightGridValue *hgv=h->begin(); hgv!=h->end(); ++hgv, ++p) {
544
        switch (index) {
545
        case 0: *p = hgv->height; break;
546
        case 1: *p = hgv->isValid()?1. : 0.; break;
547
        case 2: *p = hgv->count(); break;
548
        case 3: *p = hgv->isForestOutside()?1. : 0.; break;
549
        }
550
    }
551
 
552
    QJSValue g = ScriptGrid::createGrid(dgrid, type);
553
    return g;
554
 
555
}
556
 
1091 werner 557
QJSValue ScriptGlobal::speciesShareGrid(QString species)
558
{
559
    Species *s = GlobalSettings::instance()->model()->speciesSet()->species(species);
560
    if (!s) {
561
        qDebug() << "speciesShareGrid: invalid species" << species;
562
        return QJSValue();
563
    }
564
    const Grid<ResourceUnit*> &rug = GlobalSettings::instance()->model()->RUgrid();
565
    Grid<double> *grid = new Grid<double>(rug.cellsize(), rug.sizeX(), rug.sizeY());
566
    double *p=grid->begin();
567
    for (ResourceUnit **ru=rug.begin(); ru!=rug.end(); ++ru, ++p) {
568
        if (*ru && (*ru)->constResourceUnitSpecies(s))
569
            *p = (*ru)->resourceUnitSpecies(s).statistics().basalArea();
570
        else
571
            *p=0.;
572
    }
573
    QJSValue g = ScriptGrid::createGrid(grid, species);
574
    return g;
575
}
576
 
577
QJSValue ScriptGlobal::resourceUnitGrid(QString expression)
578
{
579
    const Grid<ResourceUnit*> &rug = GlobalSettings::instance()->model()->RUgrid();
580
    Grid<double> *grid = new Grid<double>(rug.cellsize(), rug.sizeX(), rug.sizeY());
581
    double *p=grid->begin();
582
    RUWrapper ru_wrap;
583
    Expression ru_value(expression, &ru_wrap);
584
 
585
    for (ResourceUnit **ru=rug.begin(); ru!=rug.end(); ++ru, ++p) {
586
        if (*ru) {
587
            ru_wrap.setResourceUnit(*ru);
588
            double value = ru_value.execute();
589
            *p = value;
590
        } else {
591
            *p=0.;
592
        }
593
    }
594
    QJSValue g = ScriptGrid::createGrid(grid, "ru");
595
    return g;
596
 
597
}
598
 
599
 
1064 werner 600
bool ScriptGlobal::seedMapToFile(QString species, QString file_name)
601
{
602
    // does not fully work:
603
    // Problem: after a full year cycle the seed maps are already cleared and prepared for the next round
604
    // --> this is now more an "occurence" map
605
 
606
    if (!GlobalSettings::instance()->model())
607
        return false;
608
    // find species
609
    Species *s = GlobalSettings::instance()->model()->speciesSet()->species(species);
610
    if (!s) {
611
        qDebug() << "invalid species" << species << ". No seed map saved.";
612
        return false;
613
    }
614
    s->seedDispersal()->dumpMapNextYear(file_name);
615
    qDebug() << "creating raster in the next year cycle for species" << s->id();
616
    return true;
617
 
618
    //gridToImage( s->seedDispersal()->seedMap(), true, 0., 1.).save(GlobalSettings::instance()->path(file_name));
619
//    QString result = gridToESRIRaster(s->seedDispersal()->seedMap());
620
//    if (!result.isEmpty()) {
621
//        file_name = GlobalSettings::instance()->path(file_name);
622
//        Helper::saveToTextFile(file_name, result);
623
//        qDebug() << "saved grid to " << file_name;
624
//        return true;
625
//    }
626
//    qDebug() << "failed creating seed map";
627
//    return false;
628
}
629
 
716 werner 630
void ScriptGlobal::wait(int milliseconds)
631
{
632
    // http://stackoverflow.com/questions/1950160/what-can-i-use-to-replace-sleep-and-usleep-in-my-qt-app
633
    QMutex dummy;
634
    dummy.lock();
635
    QWaitCondition waitCondition;
636
    waitCondition.wait(&dummy, milliseconds);
962 werner 637
    dummy.unlock();
716 werner 638
}
639
 
951 werner 640
int ScriptGlobal::addSaplingsOnMap(MapGridWrapper *map, const int mapID, QString species, int px_per_hectare, double height, int age)
600 werner 641
{
951 werner 642
    QString csv_file = QString("species;count;height;age\n%1;%2;%3;%4").arg(species).arg(px_per_hectare).arg(height).arg(age);
600 werner 643
    StandLoader loader(mModel);
644
    try {
603 werner 645
        loader.setMap(map->map());
646
        return loader.loadSaplings(csv_file, mapID, "called from script");
600 werner 647
    } catch (const IException &e) {
793 werner 648
        throwError(e.message());
600 werner 649
    }
650
    return 0;
651
}
652
 
675 werner 653
/// saves a snapshot of the current model state (trees, soil, etc.)
654
/// to a dedicated SQLite database.
655
bool ScriptGlobal::saveModelSnapshot(QString file_name)
656
{
657
    try {
658
        Snapshot shot;
659
        QString output_db = GlobalSettings::instance()->path(file_name);
660
        return shot.createSnapshot(output_db);
661
    } catch (const IException &e) {
793 werner 662
        throwError(e.message());
675 werner 663
    }
664
    return false;
665
}
634 werner 666
 
675 werner 667
/// loads a snapshot of the current model state (trees, soil, etc.)
668
/// from a dedicated SQLite database.
669
bool ScriptGlobal::loadModelSnapshot(QString file_name)
670
{
671
    try {
672
        Snapshot shot;
673
        QString input_db = GlobalSettings::instance()->path(file_name);
674
        return shot.loadSnapshot(input_db);
675
    } catch (const IException &e) {
793 werner 676
        throwError(e.message());
675 werner 677
    }
678
    return false;
679
}
634 werner 680
 
1213 werner 681
bool ScriptGlobal::saveStandSnapshot(int stand_id, QString file_name)
682
{
683
    try {
684
    Snapshot shot;
685
    const MapGrid *map_grid = GlobalSettings::instance()->model()->standGrid();
686
    if (!map_grid)
687
        return false;
688
    return shot.saveStandSnapshot(stand_id, map_grid, GlobalSettings::instance()->path(file_name));
689
    } catch (const IException &e) {
690
        throwError(e.message());
691
    }
692
    return false;
693
}
694
 
695
bool ScriptGlobal::loadStandSnapshot(int stand_id, QString file_name)
696
{
697
    try {
698
    Snapshot shot;
699
    const MapGrid *map_grid = GlobalSettings::instance()->model()->standGrid();
700
    if (!map_grid)
701
        return false;
702
    return shot.loadStandSnapshot(stand_id, map_grid, GlobalSettings::instance()->path(file_name));
703
    } catch (const IException &e) {
704
        throwError(e.message());
705
    }
706
    return false;
707
}
708
 
1058 werner 709
void ScriptGlobal::reloadABE()
710
{
711
    qDebug() << "attempting to reload ABE";
712
    GlobalSettings::instance()->model()->reloadABE();
713
}
714
 
1061 werner 715
void ScriptGlobal::setUIshortcuts(QJSValue shortcuts)
716
{
717
    if (!shortcuts.isObject()) {
718
        qDebug() << "setUIShortcuts: expected a JS-object (name: javascript-call, value: description). Got: " << shortcuts.toString();
719
    }
720
    QVariantMap vm = shortcuts.toVariant().toMap();
721
    GlobalSettings::instance()->controller()->setUIShortcuts(vm);
722
}
723
 
1071 werner 724
void ScriptGlobal::test_tree_mortality(double thresh, int years, double p_death)
725
{
726
#ifdef ALT_TREE_MORTALITY
727
    Tree::mortalityParams(thresh, years, p_death );
1077 werner 728
#else
729
    qDebug() << "test_tree_mortality() not enabled!!";
730
    Q_UNUSED(thresh); Q_UNUSED(years); Q_UNUSED(p_death);
1071 werner 731
#endif
732
 
733
}
734
 
1172 werner 735
 
793 werner 736
void ScriptGlobal::throwError(const QString &errormessage)
737
{
738
    GlobalSettings::instance()->scriptEngine()->evaluate(QString("throw '%1'").arg(errormessage));
794 werner 739
    qWarning() << "Scripterror:" << errormessage;
793 werner 740
    // TODO: check if this works....
741
}
742
 
767 werner 743
void ScriptGlobal::loadScript(const QString &fileName)
744
{
793 werner 745
    QJSEngine *engine = GlobalSettings::instance()->scriptEngine();
675 werner 746
 
767 werner 747
    QString program = Helper::loadTextFile(fileName);
1065 werner 748
    if (program.isEmpty()) {
749
        qDebug() << "loading of Javascript file" << fileName << "failed because file is either missing or empty.";
767 werner 750
        return;
1065 werner 751
    }
767 werner 752
 
793 werner 753
    QJSValue result = engine->evaluate(program);
837 werner 754
    qDebug() << "javascript file loaded" << fileName;
980 werner 755
    if (result.isError()) {
756
        int lineno = result.property("lineNumber").toInt();
757
        QStringList code_lines = program.replace('\r', "").split('\n'); // remove CR, split by LF
758
        QString code_part;
759
        for (int i=std::max(0, lineno - 5); i<std::min(lineno+5, code_lines.count()); ++i)
760
            code_part.append(QString("%1: %2 %3\n").arg(i).arg(code_lines[i]).arg(i==lineno?"  <---- [ERROR]":""));
761
        qDebug() << "Javascript Error in file" << fileName << ":" << result.property("lineNumber").toInt() << ":" << result.toString() << ":\n" << code_part;
767 werner 762
 
980 werner 763
    }
764
 
767 werner 765
}
766
 
767
QString ScriptGlobal::executeScript(QString cmd)
768
{
769
    DebugTimer t("execute javascript");
793 werner 770
    QJSEngine *engine = GlobalSettings::instance()->scriptEngine();
771
    QJSValue result;
767 werner 772
    if (engine)
793 werner 773
        result = engine->evaluate(cmd);
774
    if (result.isError()) {
767 werner 775
        //int line = mEngine->uncaughtExceptionLineNumber();
793 werner 776
        QString msg = QString( "Script Error occured: %1\n").arg( result.toString() );
1157 werner 777
        qDebug() << msg;
793 werner 778
        //msg+=engine->uncaughtExceptionBacktrace().join("\n");
767 werner 779
        return msg;
780
    } else {
781
        return QString();
782
    }
783
}
784
 
1157 werner 785
QString ScriptGlobal::executeJSFunction(QString function)
786
{
787
    DebugTimer t("execute javascript");
788
    QJSEngine *engine = GlobalSettings::instance()->scriptEngine();
789
    QJSValue result;
790
    if (!engine)
791
        return QStringLiteral("No valid javascript engine!");
792
 
793
    if (engine->globalObject().property(function).isCallable()) {
794
        result = engine->globalObject().property(function).call();
795
        if (result.isError()) {
796
            QString msg = QString( "Script Error occured: %1\n").arg( result.toString() );
797
            qDebug() << msg;
798
            return msg;
799
        }
800
    }
801
    return QString();
802
 
803
}
804
 
873 werner 805
QString ScriptGlobal::formattedErrorMessage(const QJSValue &error_value, const QString &sourcecode)
806
{
807
    if (error_value.isError()) {
808
        int lineno = error_value.property("lineNumber").toInt();
809
        QString code = sourcecode;
810
        QStringList code_lines = code.replace('\r', "").split('\n'); // remove CR, split by LF
811
        QString code_part;
812
        for (int i=std::max(0, lineno - 5); i<std::min(lineno+5, code_lines.count()); ++i)
813
            code_part.append(QString("%1: %2 %3\n").arg(i).arg(code_lines[i]).arg(i==lineno?"  <---- [ERROR]":""));
814
        QString error_string = QString("Javascript Error in file '%1:%2':%3\n%4")
815
                .arg(error_value.property("fileName").toString())
816
                .arg(error_value.property("lineNumber").toInt())
817
                .arg(error_value.toString())
818
                .arg(code_part);
819
        return error_string;
820
    }
821
    return QString();
822
}
767 werner 823
 
1041 werner 824
QJSValue ScriptGlobal::viewOptions()
825
{
826
    QJSValue res;
827
#ifdef ILAND_GUI
828
    MainWindow *mw = GlobalSettings::instance()->controller()->mainWindow();
829
    Ui::MainWindowClass *ui = mw->uiclass();
1053 werner 830
    // TODO: fix??
873 werner 831
 
1041 werner 832
#endif
833
    return res;
834
}
835
 
836
void ScriptGlobal::setViewOptions(QJSValue opts)
837
{
838
#ifdef ILAND_GUI
839
    MainWindow *mw = GlobalSettings::instance()->controller()->mainWindow();
840
    Ui::MainWindowClass *ui = mw->uiclass();
841
    // ruler options
842
    if (opts.hasProperty("maxValue") || opts.hasProperty("minValue")) {
843
        mw->ruler()->setMaxValue(opts.property("maxValue").toNumber());
844
        mw->ruler()->setMinValue(opts.property("minValue").toNumber());
845
        mw->ruler()->setAutoScale(false);
846
    } else {
847
        mw->ruler()->setAutoScale(true);
848
    }
849
 
850
    // main visualization options
851
    QString type = opts.property("type").toString();
852
    if (type=="lif")
853
        ui->visFon->setChecked(true);
854
    if (type=="dom")
855
        ui->visDomGrid->setChecked(true);
856
    if (type=="regeneration")
857
        ui->visRegeneration->setChecked(true);
858
    if (type=="trees") {
859
        ui->visImpact->setChecked(true);
860
        ui->visSpeciesColor->setChecked(false);
861
    }
862
    if (type=="ru") {
863
        ui->visResourceUnits->setChecked(true);
864
        ui->visRUSpeciesColor->setChecked(false);
865
    }
1181 werner 866
    if (type=="seed") {
867
        ui->visSeeds->setChecked(true);
868
    }
1041 werner 869
 
870
    // further options
871
    if (opts.hasProperty("clip"))
872
        ui->visClipStandGrid->setChecked(opts.property("clip").toBool());
873
 
874
    if (opts.hasProperty("transparent"))
875
        ui->drawTransparent->setChecked(opts.property("transparent").toBool());
876
 
1181 werner 877
 
1041 werner 878
    // color by a species ID
879
    if (opts.hasProperty("species") && opts.property("species").isBool() && type=="trees") {
880
        ui->visSpeciesColor->setChecked(opts.property("species").toBool());
881
        ui->speciesFilterBox->setCurrentIndex(0); // all species
882
    }
883
 
884
    if (opts.hasProperty("species") && opts.property("species").isString()) {
885
        QString species=opts.property("species").toString();
886
        if (type=="ru")
887
            ui->visRUSpeciesColor->setChecked(true);
1181 werner 888
        else if (type=="trees")
1041 werner 889
            ui->visSpeciesColor->setChecked(true);
890
 
891
        int idx = ui->speciesFilterBox->findData(species);
892
        ui->speciesFilterBox->setCurrentIndex(idx);
893
    }
894
    if (opts.hasProperty("autoscale"))
895
        ui->visAutoScale->setChecked(opts.property("autoscale").toBool());
896
 
897
    if (opts.hasProperty("shade"))
898
        ui->visAutoScale->setChecked(opts.property("shade").toBool());
899
 
900
    // draw a specific grid
901
    if (opts.property("grid").isString()) {
902
        QString grid=opts.property("grid").toString();
903
        ui->visOtherGrid->setChecked(true);
904
        int idx = ui->paintGridBox->findData(grid);
905
        ui->paintGridBox->setCurrentIndex(idx);
906
    }
907
 
1181 werner 908
    if (opts.hasProperty("expression"))
909
        ui->lTreeExpr->setText(opts.property("expression").toString());
1041 werner 910
 
911
    if (opts.hasProperty("filter")) {
912
        ui->expressionFilter->setText(opts.property("filter").toString());
913
        ui->cbDrawFiltered->setChecked(!ui->expressionFilter->text().isEmpty());
914
    }
915
 
916
 
917
 
918
 
919
#endif
920
 
921
}
922
 
923
 
767 werner 924
void ScriptGlobal::setupGlobalScripting()
925
{
793 werner 926
    QJSEngine *engine = GlobalSettings::instance()->scriptEngine();
927
//    QJSValue dbgprint = engine->newFunction(script_debug);
928
//    QJSValue sinclude = engine->newFunction(script_include);
929
//    QJSValue alert = engine->newFunction(script_alert);
930
//    engine->globalObject().setProperty("print",dbgprint);
931
//    engine->globalObject().setProperty("include",sinclude);
932
//    engine->globalObject().setProperty("alert", alert);
767 werner 933
 
813 werner 934
    // check if update necessary
935
    if (engine->globalObject().property("print").isCallable())
936
        return;
794 werner 937
 
938
    // wrapper functions for (former) stand-alone javascript functions
939
    // Qt5 - modification
876 werner 940
    engine->evaluate("function print(x) { Globals.print(x); } \n" \
941
                     "function include(x) { Globals.include(x); } \n" \
942
                     "function alert(x) { Globals.alert(x); } \n");
940 werner 943
    // add a (fake) console.log / console.print
944
    engine->evaluate("var console = { log: function(x) {Globals.print(x); }, " \
945
                     "                print: function(x) { for(var propertyName in x)  " \
946
                     "                                       console.log(propertyName + ': ' + x[propertyName]); " \
947
                     "                                   } " \
948
                     "              }");
794 werner 949
 
940 werner 950
 
794 werner 951
    ScriptObjectFactory *factory = new ScriptObjectFactory;
952
    QJSValue obj = GlobalSettings::instance()->scriptEngine()->newQObject(factory);
953
    engine->globalObject().setProperty("Factory", obj);
954
 
767 werner 955
    // other object types
956
    ClimateConverter::addToScriptEngine(*engine);
957
    CSVFile::addToScriptEngine(*engine);
958
    MapGridWrapper::addToScriptEngine(*engine);
959
    SpatialAnalysis::addToScriptEngine();
960
 
961
}
962
 
979 werner 963
int ScriptGlobal::msec() const
964
{
965
    return QTime::currentTime().msecsSinceStartOfDay();
966
}
967
 
794 werner 968
// Factory functions
969
 
970
 
971
ScriptObjectFactory::ScriptObjectFactory(QObject *parent):
972
    QObject(parent)
973
{
974
    mObjCreated = 0;
975
}
976
 
977
QJSValue ScriptObjectFactory::newCSVFile(QString filename)
978
{
979
    CSVFile *csv_file = new CSVFile;
980
    if (!filename.isEmpty()) {
981
        qDebug() << "CSVFile: loading file" << filename;
982
        csv_file->loadFile(filename);
983
    }
984
 
985
    QJSValue obj = GlobalSettings::instance()->scriptEngine()->newQObject(csv_file);
986
    mObjCreated++;
987
    return obj;
988
}
989
 
990
QJSValue ScriptObjectFactory::newClimateConverter()
991
{
992
    ClimateConverter *cc = new ClimateConverter(0);
993
    QJSValue obj = GlobalSettings::instance()->scriptEngine()->newQObject(cc);
994
    mObjCreated++;
995
    return obj;
996
 
997
}
998
 
853 werner 999
 
803 werner 1000
QJSValue ScriptObjectFactory::newMap()
1001
{
1002
    MapGridWrapper *map = new MapGridWrapper(0);
1003
    QJSValue obj = GlobalSettings::instance()->scriptEngine()->newQObject(map);
1004
    mObjCreated++;
1005
    return obj;
794 werner 1006
 
803 werner 1007
}
1008
 
1009