Example #1
0
int _createmapniklayer(const std::string& sLayerName,  const std::string& sLayerPath, const std::vector<double>& vecBoundary, boost::shared_ptr<Logger> qLogger)
{
    if (!FileSystem::makedir(sLayerPath))
   {
      qLogger->Error("Can't create directory " + sLayerPath);
      return ERROR_LAYERDIR;
   }

   boost::shared_ptr<PointLayerSettings> qPointLayerSettings = boost::shared_ptr<PointLayerSettings>(new PointLayerSettings());
   if (!qPointLayerSettings) {return ERROR_OUTOFMEMORY;}

   qPointLayerSettings->SetLayerName(sLayerName);
   qPointLayerSettings->SetBoundary(vecBoundary[0], vecBoundary[1], vecBoundary[2], vecBoundary[3], vecBoundary[4], vecBoundary[5]);

   if (!qPointLayerSettings->Save(sLayerPath))
   {
      qLogger->Error("Can't write into layer path: " + sLayerPath);
      return ERROR_WRITE_PERMISSION;
   }

   return 0;
}
Example #2
0
int _createimagelayer(const std::string& sLayerName, const std::string& sLayerPath, int nLod, const std::vector<int64>& vecExtent, boost::shared_ptr<Logger> qLogger, bool temp)
{
   if (!FileSystem::makedir(sLayerPath))
   {
      qLogger->Error("Can't create directory " + sLayerPath);
      return ERROR_LAYERDIR;
   }

   boost::shared_ptr<ImageLayerSettings> qImageLayerSettings = boost::shared_ptr<ImageLayerSettings>(new ImageLayerSettings());
   if (!qImageLayerSettings) {return ERROR_OUTOFMEMORY;}

   qImageLayerSettings->SetLayerName(sLayerName);
   qImageLayerSettings->SetMaxLod(nLod);
   qImageLayerSettings->SetTileExtent(vecExtent[0], vecExtent[1], vecExtent[2], vecExtent[3]);

   if (!qImageLayerSettings->Save(sLayerPath))
   {
      qLogger->Error("Can't write into layer path: " + sLayerPath);
      return ERROR_WRITE_PERMISSION;
   }

   return _createDirectoriesXY(sLayerPath, qLogger, vecExtent, nLod, temp);
}
   int process( boost::shared_ptr<Logger> qLogger, boost::shared_ptr<ProcessingSettings> qSettings, std::string sLayer, bool bVerbose, bool bLock, int epsg, std::string sImagefile, bool bFill, int& out_lod, int64& out_x0, int64& out_y0, int64& out_x1, int64& out_y1)
   {
      DataSetInfo oInfo;

      if (!ProcessingUtils::init_gdal())
      {
         qLogger->Error("gdal-data directory not found!");
         return ERROR_GDAL;
      }      

      //---------------------------------------------------------------------------
      // Retrieve ImageLayerSettings:
      std::ostringstream oss;

      std::string sImageLayerDir = FilenameUtils::DelimitPath(qSettings->GetPath()) + sLayer;
      std::string sTileDir = FilenameUtils::DelimitPath(FilenameUtils::DelimitPath(sImageLayerDir) + "tiles");

      boost::shared_ptr<ImageLayerSettings> qImageLayerSettings = ImageLayerSettings::Load(sImageLayerDir);
      if (!qImageLayerSettings)
      {
         qLogger->Error("Failed retrieving image layer settings! Make sure to create it using 'createlayer'.");
         ProcessingUtils::exit_gdal();
         return ERROR_IMAGELAYERSETTINGS;
      }

      int lod = qImageLayerSettings->GetMaxLod();
      out_lod = lod;
      int64 layerTileX0, layerTileY0, layerTileX1, layerTileY1;
      qImageLayerSettings->GetTileExtent(layerTileX0, layerTileY0, layerTileX1, layerTileY1);

      if (bVerbose)
      {
         oss << "\nImage Layer:\n";
         oss << "     name = " << qImageLayerSettings->GetLayerName() << "\n";
         oss << "   maxlod = " << lod << "\n";
         oss << "   extent = " << layerTileX0 << ", " << layerTileY0 << ", " << layerTileX1 << ", " << layerTileY1 << "\n";
      }


      //---------------------------------------------------------------------------
      boost::shared_ptr<CoordinateTransformation> qCT;
      qCT = boost::shared_ptr<CoordinateTransformation>(new CoordinateTransformation(epsg, 3785));
   
      clock_t t0,t1;
      t0 = clock();

      ProcessingUtils::RetrieveDatasetInfo(sImagefile, qCT.get(), &oInfo, bVerbose);

      if (!oInfo.bGood)
      {
         qLogger->Error("Failed retrieving info!");
      }

      if (bVerbose)
      {
         oss << "Loaded image info:\n   Image Size: w= " << oInfo.nSizeX << ", h= " << oInfo.nSizeY << "\n";
         oss << "   dest: " << oInfo.dest_lrx << ", " << oInfo.dest_lry << ", " << oInfo.dest_ulx << ", " << oInfo.dest_uly << "\n";
         qLogger->Info(oss.str());
         oss.str("");
      }

      boost::shared_ptr<MercatorQuadtree> qQuadtree = boost::shared_ptr<MercatorQuadtree>(new MercatorQuadtree());

      int64 px0, py0, px1, py1;
      qQuadtree->MercatorToPixel(oInfo.dest_ulx, oInfo.dest_uly, lod, px0, py0);
      qQuadtree->MercatorToPixel(oInfo.dest_lrx, oInfo.dest_lry, lod, px1, py1);

      int64 imageTileX0, imageTileY0, imageTileX1, imageTileY1;
      qQuadtree->PixelToTileCoord(px0, py0, imageTileX0, imageTileY0);
      qQuadtree->PixelToTileCoord(px1, py1, imageTileX1, imageTileY1);

      if (bVerbose)
      {
         oss << "\nTile Coords (image):";
         oss << "   (" << imageTileX0 << ", " << imageTileY0 << ")-(" << imageTileX1 << ", " << imageTileY1 << ")\n";
         qLogger->Info(oss.str());
         oss.str("");
      }

      // check if image is outside layer
      if (imageTileX0 > layerTileX1 || 
         imageTileY0 > layerTileY1 ||
         imageTileX1 < layerTileX0 ||
         imageTileY1 < layerTileY0)
      {
         qLogger->Info("The dataset is outside of the layer and not being added!");
         ProcessingUtils::exit_gdal();
         return 0;
      }

      // clip tiles to layer extent
      imageTileX0 = math::Max<int64>(imageTileX0, layerTileX0);
      imageTileY0 = math::Max<int64>(imageTileY0, layerTileY0);
      imageTileX1 = math::Min<int64>(imageTileX1, layerTileX1);
      imageTileY1 = math::Min<int64>(imageTileY1, layerTileY1);

      out_x0 = imageTileX0;
      out_y0 = imageTileY0;
      out_x1 = imageTileX1;
      out_y1 = imageTileY1;

      // Load image 
      boost::shared_array<unsigned char> vImage = ProcessingUtils::ImageToMemoryRGB(oInfo);
      unsigned char* pImage = vImage.get();

      if (!vImage)
      {
         qLogger->Error("Can't load image into memory!\n");
         return ERROR_NOMEMORY;
      }

      //########################################################################
      // Beacuse proj4 is not thread safe at this time, 
      // the target extents are precalculate.
      // unfortunately this can't be fixed by using OpenMP locks / critical sections

      int64 numTiles = (imageTileX1-imageTileX0+1)*(imageTileY1-imageTileY0+1);
      boost::shared_array<Anchor> vAnchor = boost::shared_array<Anchor>(new Anchor[numTiles]);
      Anchor* pAnchor = vAnchor.get();

      if (!vAnchor)
      {
         qLogger->Error("Not enough memory for target tile structure! (This is a known issue and will be fixed soon!)\n");
         return ERROR_NOMEMORY;
      }


      if (bVerbose)
      {
         oss << "\nCalculating Destination Coordinates (transformation)...";
         qLogger->Info(oss.str());
         oss.str("");
      }

      
      for (int64 xx = imageTileX0; xx <= imageTileX1; ++xx)
      {
         for (int64 yy = imageTileY0; yy <= imageTileY1; ++yy)
         {
            int64 cnt = (xx-imageTileX0)*(imageTileY1-imageTileY0+1)+yy-imageTileY0;

            std::string sQuadcode = qQuadtree->TileCoordToQuadkey(xx,yy,lod);
             double px0m, py0m, px1m, py1m;
            qQuadtree->QuadKeyToMercatorCoord(sQuadcode, px0m, py0m, px1m, py1m);

            double ulx = px0m;
            double uly = py1m;
            double lrx = px1m;
            double lry = py0m;

            double anchor_Ax = ulx; 
            double anchor_Ay = lry;
            double anchor_Bx = lrx; 
            double anchor_By = lry;
            double anchor_Cx = lrx; 
            double anchor_Cy = uly;
            double anchor_Dx = ulx; 
            double anchor_Dy = uly;

            qCT->TransformBackwards(&anchor_Ax, &anchor_Ay);
            qCT->TransformBackwards(&anchor_Bx, &anchor_By);
            qCT->TransformBackwards(&anchor_Cx, &anchor_Cy);
            qCT->TransformBackwards(&anchor_Dx, &anchor_Dy);

            pAnchor[cnt].anchor_Ax = anchor_Ax;
            pAnchor[cnt].anchor_Ay = anchor_Ay;
            pAnchor[cnt].anchor_Bx = anchor_Bx;
            pAnchor[cnt].anchor_By = anchor_By;
            pAnchor[cnt].anchor_Cx = anchor_Cx;
            pAnchor[cnt].anchor_Cy = anchor_Cy;
            pAnchor[cnt].anchor_Dx = anchor_Dx;
            pAnchor[cnt].anchor_Dy = anchor_Dy;

            cnt++;
         }
      }
      //########################################################################

      if (bVerbose)
      {
         oss << "\nCalculating Tiles";
         qLogger->Info(oss.str());
         oss.str("");
      }

      // iterate through all tiles and create them
      #pragma omp parallel for
      for (int64 xx = imageTileX0; xx <= imageTileX1; ++xx)
      {
         for (int64 yy = imageTileY0; yy <= imageTileY1; ++yy)
         {
            int64 cnt = (xx-imageTileX0)*(imageTileY1-imageTileY0+1)+yy-imageTileY0;

            boost::shared_array<unsigned char> vTile;

            std::string sQuadcode = qQuadtree->TileCoordToQuadkey(xx,yy,lod);
            std::string sTilefile = ProcessingUtils::GetTilePath(sTileDir, ".png" , lod, xx, yy);

            if (bVerbose)
            {
               std::stringstream sst;
               sst << "processing " << sQuadcode << " (" << xx << ", " << yy << ")";
               qLogger->Info(sst.str());
            }

            //---------------------------------------------------------------------
            // LOCK this tile. If this tile is currently locked 
            //     -> wait until lock is removed.

            int lockhandle = -1;
            if (bLock)
            {
               lockhandle = FileSystem::Lock(sTilefile);
            }
            else
            {
               std::cout << "WARNING: locking disabled\n";
            }

            //---------------------------------------------------------------------
            // if mode is --fill: (bFill)
            //      * load possibly existing tile into vTile
            // ...  * if there is none, clear vTile (memset 0)
            // if mode is --overwrite (bOverwrite)
            //      * load possibly existing tile into vTile
            //      * if there is none, clear vTile (memset 0)
            //      * overwrite
            //_--------------------------------------------------------------------

            // load tile:

            // tile already exists ?
            bool bCreateNew = true;

            if (FileSystem::FileExists(sTilefile))
            {
               qLogger->Info(sTilefile + " already exists, updating");
               ImageObject outputimage;
               if (ImageLoader::LoadFromDisk(Img::Format_PNG, sTilefile, Img::PixelFormat_RGBA, outputimage))
               {
                  if (outputimage.GetHeight() == tilesize && outputimage.GetWidth() == tilesize)
                  {
                     vTile = outputimage.GetRawData();
                     bCreateNew = false;
                  }
               }
            }

            if (bCreateNew)
            {
               // create new tile memory and clear to fully transparent
               vTile = boost::shared_array<unsigned char>(new unsigned char[tilesize*tilesize*4]);
               memset(vTile.get(),0,tilesize*tilesize*4);
            }

            unsigned char* pTile = vTile.get();

            // Copy image to tile:
            /*double px0m, py0m, px1m, py1m;
            qQuadtree->QuadKeyToMercatorCoord(sQuadcode, px0m, py0m, px1m, py1m);

            double ulx = px0m;
            double uly = py1m;
            double lrx = px1m;
            double lry = py0m;

            double anchor_Ax = ulx; 
            double anchor_Ay = lry;
            double anchor_Bx = lrx; 
            double anchor_By = lry;
            double anchor_Cx = lrx; 
            double anchor_Cy = uly;
            double anchor_Dx = ulx; 
            double anchor_Dy = uly;

            // avoid calculating transformation per pixel. This is done using anchor point method
           
            qCT->TransformBackwards(&anchor_Ax, &anchor_Ay);
            qCT->TransformBackwards(&anchor_Bx, &anchor_By);
            qCT->TransformBackwards(&anchor_Cx, &anchor_Cy);
            qCT->TransformBackwards(&anchor_Dx, &anchor_Dy);
            */


            double anchor_Ax = pAnchor[cnt].anchor_Ax;
            double anchor_Ay = pAnchor[cnt].anchor_Ay;
            double anchor_Bx = pAnchor[cnt].anchor_Bx;
            double anchor_By = pAnchor[cnt].anchor_By;
            double anchor_Cx = pAnchor[cnt].anchor_Cx;
            double anchor_Cy = pAnchor[cnt].anchor_Cy;
            double anchor_Dx = pAnchor[cnt].anchor_Dx;
            double anchor_Dy = pAnchor[cnt].anchor_Dy;
   
            // write current tile
            for (int ty=0;ty<tilesize;++ty)
            {
               for (int tx=0;tx<tilesize;++tx)
               {
                  double dx = (double)tx*dWanc;
                  double dy = (double)ty*dHanc;
                  double xd = (anchor_Ax*(1.0-dx)*(1.0-dy)+anchor_Bx*dx*(1.0-dy)+anchor_Dx*(1.0-dx)*dy+anchor_Cx*dx*dy);
                  double yd = (anchor_Ay*(1.0-dx)*(1.0-dy)+anchor_By*dx*(1.0-dy)+anchor_Dy*(1.0-dx)*dy+anchor_Cy*dx*dy);

                  // pixel coordinate in original image
                  double dPixelX = (oInfo.affineTransformation_inverse[0] + xd * oInfo.affineTransformation_inverse[1] + yd * oInfo.affineTransformation_inverse[2]);
                  double dPixelY = (oInfo.affineTransformation_inverse[3] + xd * oInfo.affineTransformation_inverse[4] + yd * oInfo.affineTransformation_inverse[5]);
                  unsigned char r,g,b,a;

                  // out of image -> set transparent
                  if (dPixelX<0 || dPixelX>oInfo.nSizeX ||
                     dPixelY<0 || dPixelY>oInfo.nSizeY)
                  {
                     r = g = b = a = 0;
                  }
                  else
                  {
                     // read pixel in image pImage[dPixelX, dPixelY] (biliear, bicubic or nearest neighbour)
                     // and store as r,g,b
                     _ReadImageValueBilinear(pImage, oInfo.nSizeX, oInfo.nSizeY, dPixelX, dPixelY, &r, &g, &b, &a);
                  }

                  size_t adr=4*ty*tilesize+4*tx;

                  if (a>0)
                  {
                     if (bFill)
                     {
                        if (pTile[adr+3] == 0)
                        {
                           pTile[adr+0] = r;  
                           pTile[adr+1] = g;  
                           pTile[adr+2] = b; 
                           pTile[adr+3] = a;
                        }
                     }
                     else // if (bOverwrite)
                     {
                        // currently RGB for testing purposes!
                        pTile[adr+0] = r;  
                        pTile[adr+1] = g;  
                        pTile[adr+2] = b; 
                        pTile[adr+3] = a;
                     }
                  }
               }
            }

            // save tile (pTile)
            if (bVerbose)
            {
               qLogger->Info("Storing tile: " + sTilefile);
            }

            ImageWriter::WritePNG(sTilefile, pTile, tilesize, tilesize);

            // unlock file. Other computers/processes/threads can access it again.
            FileSystem::Unlock(sTilefile, lockhandle);
         }
      }


      //---------------------------------------------------------------------------


      //---------------------------------------------------------------------------
      t1=clock();

      std::ostringstream out;
      out << "calculated in: " << double(t1-t0)/double(CLOCKS_PER_SEC) << " s \n";
      qLogger->Info(out.str());

      ProcessingUtils::exit_gdal();

      return 0;
   }
Example #4
0
int _start(int argc, char *argv[], boost::shared_ptr<Logger> qLogger, const std::string& processpath)
{
   bool bError = false;

   po::options_description desc("Program-Options");
   desc.add_options()
       ("name", po::value<std::string>(), "layer name (string)")
       ("lod", po::value<int>(), "desired level of detail (integer)")
       ("extent", po::value< std::vector<int64> >()->multitoken(), "tile boundary (tx0 ty0 tx1 ty1) for elevation/image data")
       ("boundary", po::value<std::vector<double> >()->multitoken(), "WGS84 boundary for point data or mapnik rendering")
       ("force", "[optional] force creation. (Warning: if this layer already exists it will be deleted)")
       ("numthreads", po::value<int>(), "[optional] force number of threads")
       ("type",  po::value<std::string>(), "[optional] layer type. This can be image, elevation, poi, point, geometry. image is default value.")
       ;

   po::variables_map vm;

   try
   {
      po::store(po::parse_command_line(argc, argv, desc), vm);
      po::notify(vm);
   }
   catch (std::exception&)
   {
      bError = true;
   }

   std::string sLayerName;
   int nLod = 0;
   std::vector<int64> vecExtent;
   std::vector<double> vecBoundary;
   bool bForce = false;
   ELayerType eLayer = IMAGE_LAYER;

   
   if (!vm.count("name"))
   {
      qLogger->Error("layer name is not specified!");
      bError = true;
   }
   else
   {
      sLayerName = vm["name"].as<std::string>();
      if (sLayerName.length() == 0)
      {
         qLogger->Error("layer name is empty!");
         bError = true;
      }
   }

   if (!vm.count("lod"))
   {
	   if(vm["type"].as< std::string >() != "mapnik")
	   {
		qLogger->Error("lod not specified!");
		bError = true;
	   }
   }
   else
   {
      nLod = vm["lod"].as<int>();
   }

   if (vm.count("force"))
   {
      bForce = true;
   }

   if (vm.count("extent"))
   {
      vecExtent = vm["extent"].as< std::vector<int64> >();
   }

   if (vm.count("boundary"))
   {
      vecBoundary = vm["boundary"].as< std::vector<double> >();
   }

   if (vm.count("numthreads"))
   {
      int n = vm["numthreads"].as<int>();
      if (n>0 && n<65)
      {
         std::ostringstream oss; 
         oss << "Forcing number of threads to " << n;
         qLogger->Info(oss.str());
         omp_set_num_threads(n);
      }
   }

   if (vm.count("type"))
   {
      std::string sLayerType = vm["type"].as< std::string >();
      if (sLayerType == "image")
      {
         eLayer = IMAGE_LAYER;
      }
      else if (sLayerType == "imagepostprocessing")
      {
         eLayer = IMAGE_POSTPROCESSING_LAYER;
      }
	  else if (sLayerType == "mapnik")
      {
         eLayer = MAPNIK_LAYER;
      }
      else if (sLayerType == "elevation")
      {
         eLayer = ELEVATION_LAYER;
      }
      else if (sLayerType == "poi")
      {
         eLayer = POI_LAYER;
      }
      else if (sLayerType == "point")
      {
         eLayer = POINT_LAYER;
      }
      else if (sLayerType == "geometry")
      {
         eLayer = GEOMETRY_LAYER;
      }
      else
      {
         bError = true;
      }
   }
   else
   {
       qLogger->Warn("It is highly recommended to use --type! Using default --type image");
   }

   if (eLayer == POINT_LAYER)
   {
      if (vecBoundary.size() != 6 )
      {
         qLogger->Error("boundary must be specified with 6 values (WGS84): lng0 lat0 elv0 lng1 lat1 elv1");
         bError = true;
      }
   }
   else
   {
      if (vecExtent.size() != 4 )
      {
         qLogger->Error("extent must be defined with 4 values (Tile Coords): x0 y0 x1 y1");
         bError = true;
      }
   }

   if (bError)
   {
      qLogger->Error("Wrong parameters!");
      std::ostringstream sstr;
      sstr << desc;
      qLogger->Info("\n" + sstr.str());

      return ERROR_PARAMS;
   }

   std::string sLayerPath = FilenameUtils::DelimitPath(processpath) + sLayerName;
   qLogger->Info("Target directory: " + sLayerPath);
   
   if (FileSystem::DirExists(sLayerPath))
   {
      if (!bForce)
      {
         qLogger->Error("Layer already exists!!");
         qLogger->Error("the directory " + sLayerPath + " already exists. Please delete manually or choose another layer name or use the --force option");
         return ERROR_LAYEREXISTS;
      }
      else
      {
         qLogger->Info("Force option detected. Deleting already existing layer... this may take a while");
         if (!FileSystem::rm_all(sLayerPath))
         {
            qLogger->Error("Can't delete old layer (file permission).");
            return ERROR_DELETE_PERMISSION;
         }
         else
         {
            qLogger->Info("ok.. layer deleted.");
         }
      }
   }

   if (eLayer == IMAGE_LAYER)
   {
      return _createimagelayer(sLayerName, sLayerPath, nLod, vecExtent, qLogger, false);
   }
   if (eLayer == IMAGE_POSTPROCESSING_LAYER)
   {
      return _createimagelayer(sLayerName, sLayerPath, nLod, vecExtent, qLogger, true);
   }
   if (eLayer == MAPNIK_LAYER)
   {
      return _createmapniklayer(sLayerName, sLayerPath, vecBoundary, qLogger);
   }
   else if (eLayer == ELEVATION_LAYER)
   {
      return _createelevationlayer(sLayerName, sLayerPath, nLod, vecExtent, qLogger);
   }
   else if (eLayer == POINT_LAYER)
   {
      return _createpointlayer(sLayerName, sLayerPath, nLod, vecBoundary, qLogger);
   }
   else
   {
      return ERROR_UNSUPPORTED;
   }
   
}
   int process( boost::shared_ptr<Logger> qLogger, boost::shared_ptr<ProcessingSettings> qSettings, std::string sLayer, bool bVerbose, bool bLock, int epsg, std::string sPointFile, bool bFill, int& out_lod, int64& out_x0, int64& out_y0, int64& out_z0, int64& out_x1, int64& out_y1, int64& out_z1)
   {
      clock_t t0,t1;
      t0 = clock();

      if (!ProcessingUtils::init_gdal())
      {
         qLogger->Error("gdal-data directory not found!");
         return ERROR_GDAL;
      }

      boost::shared_ptr<CoordinateTransformation> qCT;
      qCT = boost::shared_ptr<CoordinateTransformation>(new CoordinateTransformation(epsg, 4326));

      //---------------------------------------------------------------------------
      // Retrieve PointLayerSettings:
      std::ostringstream oss;

      std::string sPointLayerDir = FilenameUtils::DelimitPath(qSettings->GetPath()) + sLayer;
      std::string sTileDir = FilenameUtils::DelimitPath(FilenameUtils::DelimitPath(sPointLayerDir) + "tiles");
      std::string sTempDir = FilenameUtils::DelimitPath(FilenameUtils::DelimitPath(sPointLayerDir) + "temp/tiles");
      std::string sIndexFile = FilenameUtils::DelimitPath(sPointLayerDir) + "temp/" + FilenameUtils::ExtractBaseFileName(sPointFile) + ".idx";

      boost::shared_ptr<PointLayerSettings> qPointLayerSettings = PointLayerSettings::Load(sPointLayerDir);
      if (!qPointLayerSettings)
      {
         qLogger->Error("Failed retrieving point layer settings! Make sure to create it using 'createlayer'.");
         ProcessingUtils::exit_gdal();
         return ERROR_ELVLAYERSETTINGS;
      }

      int lod = qPointLayerSettings->GetMaxLod();
      double x0, y0, z0, x1, y1, z1;
      qPointLayerSettings->GetBoundary(x0, y0, z0, x1, y1, z1);
      
      if (bVerbose)
      {
         oss << "Point Layer:\n";
         oss << "     name = " << qPointLayerSettings->GetLayerName() << "\n";
         oss << "   maxlod = " << lod << "\n";
         oss << " boundary = (" << x0 << ", " << y0 << ", " << z0 << ")-(" << x1 << ", " << y1 << ", " << z1 << ")\n";
         qLogger->Info(oss.str());
         oss.str("");
      }

      double lodlen = pow(2.0,lod);

      //------------------------------------------------------------------------
      // Calculate matrix for octree voxel data transformation
      
      double xcenter, ycenter, zcenter;
      xcenter = x0 + fabs(x1-x0)*0.5;
      ycenter = y0 + fabs(y1-y0)*0.5;
      zcenter = z0 + fabs(z1-z0)*0.5; // elevation is currently ignored and set to 0

      double len = OCTREE_CUBE_SIZE; // octree cube size
   
      mat4<double> L, Linv;
   
      // center to radiant
      double lng = DEG2RAD(xcenter);
      double lat = DEG2RAD(ycenter);

      // center to cartesian coord
      vec3<double> vCenter;
      GeoCoord geoCenter(xcenter, ycenter, zcenter);
      geoCenter.GetCartesian(vCenter);

      // create orthonormal basis
      // (and create 4x4 matrix with translation to vCenter)
      // scale to normalized geozentric cartesian coordinates (scaled meters)
      double scalelen = CARTESIAN_SCALE_INV * len;

      // translate to center (lng,lat)
      mat4<double> matTrans;
      matTrans.SetTranslation(vCenter);

      mat4<double> matNavigation;
      //matNavigation.CalcNavigationFrame(lng, lat);

      // Navigation frame with z-axis up!
      matNavigation.Set(
         -sin(lng),  -sin(lat)*cos(lng),  cos(lat)*cos(lng), 0,
         cos(lng),   -sin(lat)*sin(lng),  cos(lat)*sin(lng), 0,
         0,          cos(lat),            sin(lat),          0,
         0,          0,                   0,                 1);

      // scale to range [-0.5,0.5] (local coordinates)
      mat4<double> matScale;
      matScale.SetScale(scalelen); 

      // translate to range [0,1]
      mat4<double> matTrans2;
      matTrans2.SetTranslation(-0.5, -0.5, -0.5);

      L = matTrans; // translate to vCenter
      L *= matNavigation; // rotate to align ellipsoid normal
      L *= matScale; // scale to [-0.5, 0.5]
      L *= matTrans2; // translate [0.5, 0.5, 0.5] to have range [0,1]

      Linv = L.Inverse(); // create inverse of this transformation

      //------------------------------------------------------------------------

      size_t numpts = 0;
      size_t totalpoints = 0;

      CloudPoint pt;
      CloudPoint pt_octree; // point in octree coords
      PointCloudReader pr;
      PointMap pointmap(lod);

      if (pr.Open(sPointFile))
      {
         GeoCoord in_geopt;          
         vec3<double> in_pt_cart;    // point in geocentric cartesian coordinates (WGS84)
         vec3<double> out_pt_octree; // point in local octree coordinates

         while (pr.ReadPoint(pt))
         {
            qCT->Transform(&pt.x, &pt.y);

            in_geopt.SetLongitude(pt.x);
            in_geopt.SetLatitude(pt.y);
            in_geopt.SetEllipsoidHeight(pt.elevation);
            
            in_geopt.ToCartesian(&in_pt_cart.x, &in_pt_cart.y, &in_pt_cart.z);
            out_pt_octree = Linv.vec3mul(in_pt_cart);
            pt_octree.r = pt.r;
            pt_octree.g = pt.g;
            pt_octree.b = pt.b;
            pt_octree.a = pt.a;
            pt_octree.intensity = pt.intensity;
            pt_octree.x = out_pt_octree.x;
            pt_octree.y = out_pt_octree.y;
            pt_octree.elevation = out_pt_octree.z;

            int64 octreeX = int64(out_pt_octree.x * lodlen); 
            int64 octreeY = int64(out_pt_octree.y * lodlen); 
            int64 octreeZ = int64(out_pt_octree.z * lodlen); 

            // now we have the octree coordinate (octreeX,Y,Z) of the point
            // -> add the point to pointmap (which is actually a hash map)
            // -> note: don't calculate the octocode for each point, it would be way too slow.
            pointmap.AddPoint(octreeX, octreeY, octreeZ, pt_octree);

            if (pointmap.GetNumPoints()>membuffer)
            {
               totalpoints+=pointmap.GetNumPoints();

               pointmap.ExportData(sTempDir);

               pointmap.Clear();
            }

            numpts++;
         }
      }
      else
      {
         return -1;
      }

      if (pointmap.GetNumPoints()>0)
         pointmap.ExportData(sTempDir);

      totalpoints+=pointmap.GetNumPoints();
    
      // export list of all written tiles (for future processing)
       if (bVerbose)
      {
         oss << "Exporting index...\n";
         qLogger->Info(oss.str());
         oss.str("");
      }
      pointmap.ExportIndex(sIndexFile);


      //------------------------------------------------------------------------

      ProcessingUtils::exit_gdal();


      std::cout << "Pointmap Stats:\n";
      std::cout << " numpoints: " << totalpoints << "\n";

      t1 = clock();
      std::cout << "calculated in: " << double(t1-t0)/double(CLOCKS_PER_SEC) << " s \n";

      return 0;
   }