1 Introduction

Intrepid Geophysics provides the users of GeoModeller the means to build software interaction using our API.

All of Intrepid Geophysics’ software solutions operate a file instruction API based on the Google Protocol Buffers (GPB) which functions as a scripting language/parameter file interface between plain users and/or other codes.

Because the API uses plain text files as intermediates, it is possible to write GPB compliant message files by hand and pass them to the API via command line instructions, see section 6 for more details on how to do so.

This tutorial presents the main steps required to setup, install and integrate our API into your own source code.

2 Requirements

To use GeoModeller’s API, you will need:

  • A licensed install of GeoModeller
  • Valid environment variables
  • GPB install

3 Installing and licensing GeoModeller

Download GeoModeller and install. It is recommended not to change the default installation directory.

If you do not own a license, you can request a one month fully enable trial license through the license manager.



4 Environment variables

For GeoModeller 4.1.0 and onward:

Set or add $GEOMOD_INSTALL\jre\bin and $GEOMOD_INSTALL\jre\bin\server to your environment variable Path.

For older versions:

Set or add $GEOMOD_INSTALL\bin\server to your environment variable Path.

5 The Google Protocol Buffers

The Google Protocol Buffers is a data structure serialization library published by Google as an alternative to JSON or XML files with a particular aim on speed, clarity and lightness.

The API of GeoModeller uses GPB compliant message files called .task files. These .task files are then parsed/written using GPB.

5.1 Overview

The GPB website offers free, high quality tutorials and comprehensive reference that we strongly advise prospective API users to read through.

Overall, the GPB can be described as a four staged system:

  • the data structures (not the data itself) to be serialized are written in .proto files with a message syntax defined by the GPB.
  • the .proto files are processed using the GPB’s own compiler to produce I/O classes in a given target language such as C++, C#, Python or Java.
  • the codes can then be used and compiled in another project provided that the GPB library is installed and adequately linked.
  • data structures are serialized into messages.



5.2 GPB Installation

Although installation of the GPB lib is not technically required to run GeoModeller’s API, it is strongly recommended for any reasonably complex project. You may download the GPB lib from the GitHub release page of the GPB project.

Steps for installation are fairly simple and may be found in the README.md file.

If you are developing on Windows, it is recommended to install either msys or Cygwin to emulate a Linux environment and then follow the Ubuntu specific instructions in the README.md.

5.3 GPB integration

To integrate GPB into your project, you will have to link the library’s dll into your build system.

  • $\local\lib\libprotoc.dll.a
  • $\local\lib\libprotoc.a
  • $\local\lib\libprotobuf-lite.dll.a
  • $\local\lib\libprotobuf-lite.a
  • $\local\lib\libprotobuf.dll.a
  • $\local\lib\libprotobuf.a

This will allow you to include the necessary GPB headers into your source code to parse/write the message files, for example:

#include <google/protobuf/text_format.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>


5.4 GPB compilation

In GeoModeller’s install directory, under $GEOMOD_INSTALL\examples\API, you will find a number of .proto files. These proto files define a message syntax which corresponds to the data structures the API employs.

Once GPB is installed, it is possible to invoke the protoc compiler onto our proto files.

The first proto file to be compiled is the commontaskmodel.proto which contains all the low level data structures (such as 3D point) to generate code C++ code for example.

The output is a header and source file with GPB C++ I/O codes that you may then include into your project.

  • commontaskmodel.pb.h
  • commontaskmodel.pb.cc

Note that all Intrepid software use proto2 syntax with specific package names. See the first lines of the commontaskmodel.proto file.


syntax = "proto2";
 
// Here is the specification of the common low level or useful GOOGLE/Protobuf schema
// note, no XML, designed for fast message passing between processes.
// (C) 2017 Intrepid Geophysics
package ctm; // commontaskmodel
 
message PairInt32 {
    required int32 first = 1;
    required int32 second = 2;
}


Additionally, proto files may include definitions of other proto files. For example the gmtaskmodel.proto includes two.


import "commontaskmodel.proto"; // include
import "vtktasksup:proto"; // include
 
package gmtaskmodel;
 
enum GeomodellerDataSource {
    Project = 0;
    File = 1;
}


5.5 Parsing tasks

Once GPB is fully deployed, GeoModeller API .proto files can be translated into source code which is used to parse/write existing .task files (C++ example):


void ImportProtoTaskGeoModProject(const std::string &taskPath)
{
    std::ifstream inputRawTask(taskPath.c_str());
    if (!inputRawTask.good())
    {
        std::cerr << "Error while opening a .task file!" << taskPath << std::endl;
        std::exit(0);
    }
    std::string input;
    inputRawTask.seekg(0, std::ios::end);
    input.reserve(inputRawTask.tellg());
    inputRawTask.seekg(0, std::ios::beg);
    input.assign((std::istreambuf_iterator<char>(inputRawTask)), std::istreambuf_iterator<char>());
 
 
    // Declare a custom wrapper message for all GeomodellerTasks
    wrapper::WrapperForGeoTasks tasksList;
 
    // Parsing.
    google::protobuf::TextFormat::ParseFromString(input, &tasksList);
 
    // Declare container.
    gmtaskmodel::CreateProject_GMT createProjectList;
     
    // Populate container.
    for (int i = 0; i < tasksList.geomodellertask_size(); ++i)
    {
        if (tasksList.geomodellertask(i).has_createproject())
        {
            createProjectList.push_back(tasksList.geomodellertask(i).createproject());
        }
        // Convert into local class.
        Extents2D extents;
        extents.maxi.x = createProjectList.extents().xmax();
        extents.maxi.y = createProjectList.extents().ymax();
        extents.mini.x = createProjectList.extents().xmin();
        extents.mini.y = createProjectList.extents().ymin();
        return;
    }
}


5.6 Writing tasks

The same C++ I/O classes are used to write .task files. GPB specific details can be found at https://developers.google.com/protocol-buffers/docs/reference/cpp-generated

Below is an example of C++ codes which uses I/O classes derived from the commontaskmodel.proto and gmtaskmodel.proto files found at $GEOMOD_INSTALL\examples\API.


/*
Basic protobuf io module for task files.
Authors: Evren Pakyuz-Charrier.
[email protected]
Intrepid Geophysics 2018.
*/
#ifndef TASK_IO_H
#define TASK_IO_H
 
#include <vector>
 
// Include GPB lib headers.
#include <google/protobuf/text_format.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
 
// Include automatically generated code.
#include "commontaskmodel.pb.h"
#include "gmtaskmodel.pb.h"
 
class TaskIO
{
public:
  std::string WriteSillyTask(const std::string &taskPath)
  {
    std::ofstream outputTaskFile(taskPath.c_str());
 
    if (!outputTaskFile.good())
    {
      std::cerr << "Error while opening a .task file!" << taskPath << std::endl;
      std::exit(0);
    }
 
    // Task container.
    std::vector<gmtaskmodel::GeomodellerTask> tasks;
 
    // Declare a CreateProject message.
    gmtaskmodel::CreateProject_GMT createProject;
    {
      // Locally define essential attributes for the CreateProject message, leave others default.
      gmtaskmodel::HorizontalDTM_GMT dtm;
      dtm.set_name("Topography");
      dtm.set_elevation(0.);
 
      ctm::BoundingBox extents;
      extents.set_xmin(0.);
      extents.set_ymin(0.);
      extents.set_zmin(-1000.);
      extents.set_xmax(1000.);
      extents.set_ymax(1000.);
      extents.set_zmax(0.);
 
      // Copy into the CreateProject message.
      createProject.mutable_HorizontalDTM()->CopyFrom(dtm);
      createProject.mutable_Extents()->CopyFrom(extents);
    }
 
    // Container for formations.
    std::vector<gmtaskmodel::CreateFormation_GMT> createFormationList;
 
    for (size_t i = 0; i < 2; ++i)
    {
      gmtaskmodel::CreateFormation_GMT formation;
      std::string name = "Formation_" + i;
      formation.set_name(name);
      createFormationList.push_back(formation);
    }
    gmtaskmodel::CreateFormation_GMT basement;
    basement.set_name("basement");
    createFormationList.push_back(basement);
 
    // Container for series.
    std::vector<gmtaskmodel::SetSeries_GMT> setSeriesList;
    size_t pos = 1;
 
    gmtaskmodel::SetSeries_GMT basementSeries;
    basementSeries.set_name("basement_Series");
    basementSeries.set_relation("onlap");
    setSeriesList.push_back(basementSeries);
    ++pos;
 
    for (std::vector<gmtaskmodel::CreateFormation_GMT> const_iterator formationIterator = createFormationList.begin();
         formationIterator != createFormationList.end() - 1;
         ++formationIterator)
    {
      gmtaskmodel::SetSeries_GMT series;
      series.set_name(formationIterator->name() + "_Series");
      series.set_position(pos);
      series.set_relation("erode");
      setSeriesList.push_back(series);
    }
 
    // Container for formation allocation.
    gmtaskmodel::AddFormationToSeries_GMT addToSeriesList;
 
    // Silly formation allocation.
    gmtaskmodel::AddFormationToSeries_GMT addToSeries;
    addToSeries.set_series("basement_Series");
    addToSeries.set_formation("basement");
    addToSeriesList.push_back(addToSeries);
    addToSeries.set_series("Formation_0_Series");
    addToSeries.set_formation("Formation_0");
    addToSeriesList.push_back(addToSeries);
    addToSeries.set_series("Formation_1_Series");
    addToSeries.set_formation("Formation_1");
    addToSeriesList.push_back(addToSeries);
 
    // Container for interfaces.
    std::vector<gmtaskmodel::Add3DInterfacesToFormation_GMT> add3DInterfacesList;
 
    gmtaskmodel::Add3DInterfacesToFormation_GMT interfaceAdd;
    ctm.Point3d interfaceLocation;
 
    // Silly interfaces attribution.
    interfaceAdd.set_formation("Formation_0");
    interfaceLocation.set_x(500.);
    interfaceLocation.set_y(500.);
    interfaceLocation.set_z(-200.);
    interfaceAdd.mutable_point()->CopyFrom(interfaceLocation);
    add3DInterfacesList.push_back(interfaceAdd);
    interfaceAdd.set_formation("Formation_1");
    interfaceLocation.set_x(500.);
    interfaceLocation.set_y(500.);
    interfaceLocation.set_z(-100.);
    interfaceAdd.mutable_point()->CopyFrom(interfaceLocation);
    add3DInterfacesList.push_back(interfaceAdd);
 
    // Container for foliations.
    std::vector<gmtaskmodel::Add3DFoliationToFormation_GMT> add3DFoliationList;
 
    gmtaskmodel::Add3DFoliationToFormation_GMT foliationAdd;
    gmtaskmodel::Foliation_GMT foliation;
    ctm.Point3d foliationLocation;
 
    // Silly foliations attribution.
    // Foliations can be anywhere.
    foliationLocation.set_x(1.);
    foliationLocation.set_y(1.);
    foliationLocation.set_z(-200.);
    foliation.mutable_Point3D()->CopyFrom(foliationLocation);
    // Horizontal foliations are OK.
    foliation.set_dip(0.);
    foliation.set_direction(0.);
    foliationAdd.mutable_foliation()->CopyFrom(foliation);
    foliationAdd.set_formation("Formation_0");
    add3DFoliationList.push_back(foliationAdd);
 
    foliationLocation.set_x(1.);
    foliationLocation.set_y(1.);
    foliationLocation.set_z(-50.);
    foliation.mutable_Point3D()->CopyFrom(foliationLocation);
 
    foliation.set_dip(30.);
    foliation.set_direction(270.);
    foliationAdd.mutable_foliation()->CopyFrom(foliation);
    foliationAdd.set_formation("Formation_1");
    add3DFoliationList.push_back(foliationAdd);
 
    gmtaskmodel::ComputeModel_GMT modelCreation;
 
    gmtaskmodel::CloseProjectNoGUI_GMT closeProject;
 
    for (std::vector<gmtaskmodel>::const_iterator taskIterator = tasks.begin();
         taskIterator != tasks.end();
         ++taskIterator)
    {
      std::string stringOutput;
      google::protobuf::TextFormat::PrintToString(*taskIterator, &stringOutput);
      outputTaskFile << stringOutput << std::endl;
    }
 
    return;
  }
}



We recommend using a new GeoModellerTask message for each high level message when writing your own own task files.

Consequently, it may be useful to make a wrapper for the gmtaskmodel::GeomodellerTask class and use GPB vector containers instead of STL or custom ones:


// Wrapper for GeoModellerTasks text based tasks.
syntax = "proto2";
 
import "gmtaskmodel.proto";
 
package wrapper;
 
message WrapperForGeoTasks (
    optional string name = 1;
    optional string description = 2;
    repeated gmtaskmodel.GeomodellerTask GeomodellerTask = 3;
}


This allows the use of the .add_foo() method to list GeomodellerTask messages, more at https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.repeated_field

6 Running API tasks

6.1 In command line

There are two common ways to interact with GeoModeller’s API for the moment, via command line and via GUI.

The command line syntax for GeoModeller 4.1.0 is:


$GEOMOD_INSTALL\bin\batch.bat myTask.task



or for older versions:


$GEOMOD_INSTALL\bin\geomodellerbatch.exe myTask.task


6.2 In GUI

The GUI procedure is: Navigator -> double click task file -> Left click into text editor -> Run As -> Execute



7 Example task

Example task files for GeoModeller can be found at $GEOMOD_INSTALL\examples. Below is an example task file that builds a simple 3D geological model and saves it to disk:


GeomodellerTask {
CreateProject {
name: "MyProject"
author: "Evren Pakyuz-Charrier"
Extents {
xmin: 0.
ymin: 0.
zmin: -1000.
xmax: 1000.
ymax: 1000.
zmax: 0.
}
referenceTop: true
HorizontalDTM {
name: "Topography"
elevation: 0
}
}
}
GeomodellerTask {
CreateFormation {
name: "Formation1"
}
}
GeomodellerTask {
CreateFormation {
name: "Formation2"
}
}
GeomodellerTask {
SetSeries {
name: "Series1"
position: 1
relation: "erode"
}
}
GeomodellerTask {
SetSeries {
name: "Series2"
position: 2
relation: "erode"
}
}
GeomodellerTask {
AddFormationToSeries {
series: "Series1"
formation: "Formation1"
}
}
GeomodellerTask {
AddFormationToSeries {
series: "Series2"
formation: "Formation2"
}
}
GeomodellerTask {
Add3DFoliationToFormation {
formation: "Formation2"
foliation {
Point3D {
x: 400
y: 800
z: -500
}
dip: 45
direction: 0
polarity: Normal_Polarity
}
}
}
GeomodellerTask {
Add3DFoliationToFormation {
formation: "Formation1"
foliation {
Point3D {
x: 400
y: 800
z: -500
}
dip: 45
direction: 0
polarity: Normal_Polarity
}
}
}
GeomodellerTask {
Add3DInterfacesToFormation {
formation: "Formation1"
Point3D {
x: 350
y: 700
z: -400
}
}
}
GeomodellerTask {
Add3DInterfacesToFormation {
formation: "Formation1"
Point3D {
x: 350
y: 700
z: -100
}
}
}
GeomodellerTask {
ComputeModel {
SeriesList {
node: "All"
}
}
}
GeomodellerTask {
SaveProjectAs {
filename: "./out/layer_cake.xml"
}
}