/*
===============================================================================

  FILE:  lasutility.cpp

  CONTENTS:

    see corresponding header file

  PROGRAMMERS:

    martin.isenburg@rapidlasso.com  -  http://rapidlasso.com

  COPYRIGHT:

    (c) 2007-2017, martin isenburg, rapidlasso - fast tools to catch reality

    This is free software; you can redistribute and/or modify it under the
    terms of the GNU Lesser General Licence as published by the Free Software
    Foundation. See the LICENSE.txt file for more information.

    This software is distributed WITHOUT ANY WARRANTY and without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  CHANGE HISTORY:

    see corresponding header file

===============================================================================
*/
#include "lasutility.hpp"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdexcept>

LASinventory::LASinventory()
{
  U32 i;
  extended_number_of_point_records = 0;
  for (i = 0; i < 16; i++) extended_number_of_points_by_return[i] = 0;
  max_X = min_X = 0;
  max_Y = min_Y = 0;
  max_Z = min_Z = 0;
  first = TRUE;
}

BOOL LASinventory::init(const LASheader* header)
{
  if (header)
  {
    U32 i;
    extended_number_of_point_records = (header->number_of_point_records ? header->number_of_point_records : header->extended_number_of_point_records);
    extended_number_of_points_by_return[0] = 0;
    for (i = 0; i < 5; i++) extended_number_of_points_by_return[i+1] = (header->number_of_points_by_return[i] ? header->number_of_points_by_return[i] : header->extended_number_of_points_by_return[i]);
    for (i = 5; i < 15; i++) extended_number_of_points_by_return[i+1] = header->extended_number_of_points_by_return[i];
    max_X = (I32)header->get_X(header->max_x);
    min_X = (I32)header->get_X(header->min_x);
    max_Y = (I32)header->get_Y(header->max_y);
    min_Y = (I32)header->get_Y(header->min_y);
    max_Z = (I32)header->get_Z(header->max_z);
    min_Z = (I32)header->get_Z(header->min_z);
    first = FALSE;
    return TRUE;
  }
  return FALSE;
}

BOOL LASinventory::add(const LASpoint* point)
{
  extended_number_of_point_records++;
  if (point->extended_point_type)
  {
    extended_number_of_points_by_return[point->extended_return_number]++;
  }
  else
  {
    extended_number_of_points_by_return[point->return_number]++;
  }
  if (first)
  {
    min_X = max_X = point->get_X();
    min_Y = max_Y = point->get_Y();
    min_Z = max_Z = point->get_Z();
    first = FALSE;
  }
  else
  {
    if (point->get_X() < min_X) min_X = point->get_X();
    else if (point->get_X() > max_X) max_X = point->get_X();
    if (point->get_Y() < min_Y) min_Y = point->get_Y();
    else if (point->get_Y() > max_Y) max_Y = point->get_Y();
    if (point->get_Z() < min_Z) min_Z = point->get_Z();
    else if (point->get_Z() > max_Z) max_Z = point->get_Z();
  }
  return TRUE;
}

BOOL LASinventory::update_header(LASheader* header) const
{
  if (header)
  {
    int i;
    if (extended_number_of_point_records > U32_MAX)
    {
      if (header->version_minor >= 4)
      {
        header->number_of_point_records = 0;
      }
      else
      {
        return FALSE;
      }
    }
    else
    {
      header->number_of_point_records = (U32)extended_number_of_point_records;
    }
    for (i = 0; i < 5; i++)
    {
      if (extended_number_of_points_by_return[i+1] > U32_MAX)
      {
        if (header->version_minor >= 4)
        {
          header->number_of_points_by_return[i] = 0;
        }
        else
        {
          return FALSE;
        }
      }
      else
      {
        header->number_of_points_by_return[i] = (U32)extended_number_of_points_by_return[i+1];
      }
    }
    header->max_x = header->get_x(max_X);
    header->min_x = header->get_x(min_X);
    header->max_y = header->get_y(max_Y);
    header->min_y = header->get_y(min_Y);
    header->max_z = header->get_z(max_Z);
    header->min_z = header->get_z(min_Z);
    header->extended_number_of_point_records = extended_number_of_point_records;
    for (i = 0; i < 15; i++)
    {
      header->extended_number_of_points_by_return[i] = extended_number_of_points_by_return[i+1];
    }
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

LASsummary::LASsummary()
{
  U32 i;
  number_of_point_records = 0;
  for (i = 0; i < 16; i++) number_of_points_by_return[i] = 0;
  for (i = 0; i < 16; i++) number_of_returns[i] = 0;
  for (i = 0; i < 32; i++) classification[i] = 0;
  for (i = 0; i < 256; i++)
  {
    extended_classification[i] = 0;
    flagged_synthetic_classification[i] = 0;
    flagged_keypoint_classification[i] = 0;
    flagged_withheld_classification[i] = 0;
    flagged_extended_overlap_classification[i] = 0;
  }
  for (i = 0; i < 3; i++)
  {
    xyz_fluff_10[i] = 0;
    xyz_fluff_100[i] = 0;
    xyz_fluff_1000[i] = 0;
    xyz_fluff_10000[i] = 0;
  }
  flagged_synthetic = 0;
  flagged_keypoint = 0;
  flagged_withheld = 0;
  flagged_extended_overlap = 0;
  first = TRUE;
}

BOOL LASsummary::add(const LASpoint* point)
{
  number_of_point_records++;
  if (point->extended_point_type)
  {
    number_of_points_by_return[point->get_extended_return_number()]++;
    number_of_returns[point->get_extended_number_of_returns()]++;
    if (point->get_extended_classification() > 31)
    {
      extended_classification[point->get_extended_classification()]++;
    }
    else
    {
      classification[point->get_classification()]++;
    }
    if (point->get_extended_overlap_flag()) { flagged_extended_overlap++; flagged_extended_overlap_classification[(point->get_classification() ? point->get_classification() : point->get_extended_classification())]++; }
  }
  else
  {
    number_of_points_by_return[point->get_return_number()]++;
    classification[point->get_classification()]++;
    number_of_returns[point->get_number_of_returns()]++;
  }
  if (point->get_synthetic_flag()) { flagged_synthetic++; flagged_synthetic_classification[(point->get_classification() ? point->get_classification() : point->get_extended_classification())]++; }
  if (point->get_keypoint_flag()) { flagged_keypoint++;  flagged_keypoint_classification[(point->get_classification() ? point->get_classification() : point->get_extended_classification())]++; }
  if (point->get_withheld_flag()) { flagged_withheld++;  flagged_withheld_classification[(point->get_classification() ? point->get_classification() : point->get_extended_classification())]++; }
  if (first)
  {
    // does the point have extra bytes
    if (point->extra_bytes_number)
    {
      min.extra_bytes = new U8[point->extra_bytes_number];
      min.extra_bytes_number = point->extra_bytes_number;
      max.extra_bytes = new U8[point->extra_bytes_number];
      max.extra_bytes_number = point->extra_bytes_number;
    }
    // initialize min and max
    min = *point;
    max = *point;
    // initialize fluff detection
    xyz_low_digits_10[0] = (U16)(point->get_X()%10);
    xyz_low_digits_10[1] = (U16)(point->get_Y()%10);
    xyz_low_digits_10[2] = (U16)(point->get_Z()%10);
    xyz_low_digits_100[0] = (U16)(point->get_X()%100);
    xyz_low_digits_100[1] = (U16)(point->get_Y()%100);
    xyz_low_digits_100[2] = (U16)(point->get_Z()%100);
    xyz_low_digits_1000[0] = (U16)(point->get_X()%1000);
    xyz_low_digits_1000[1] = (U16)(point->get_Y()%1000);
    xyz_low_digits_1000[2] = (U16)(point->get_Z()%1000);
    xyz_low_digits_10000[0] = (U16)(point->get_X()%10000);
    xyz_low_digits_10000[1] = (U16)(point->get_Y()%10000);
    xyz_low_digits_10000[2] = (U16)(point->get_Z()%10000);
    first = FALSE;
  }
  else
  {
    if (point->get_X() < min.get_X()) min.set_X(point->get_X());
    else if (point->get_X() > max.get_X()) max.set_X(point->get_X());
    if (point->get_Y() < min.get_Y()) min.set_Y(point->get_Y());
    else if (point->get_Y() > max.get_Y()) max.set_Y(point->get_Y());
    if (point->get_Z() < min.get_Z()) min.set_Z(point->get_Z());
    else if (point->get_Z() > max.get_Z()) max.set_Z(point->get_Z());
    if (point->intensity < min.intensity) min.intensity = point->intensity;
    else if (point->intensity > max.intensity) max.intensity = point->intensity;
    if (point->edge_of_flight_line < min.edge_of_flight_line) min.edge_of_flight_line = point->edge_of_flight_line;
    else if (point->edge_of_flight_line > max.edge_of_flight_line) max.edge_of_flight_line = point->edge_of_flight_line;
    if (point->scan_direction_flag < min.scan_direction_flag) min.scan_direction_flag = point->scan_direction_flag;
    else if (point->scan_direction_flag > max.scan_direction_flag) max.scan_direction_flag = point->scan_direction_flag;
    if (point->number_of_returns < min.number_of_returns) min.number_of_returns = point->number_of_returns;
    else if (point->number_of_returns > max.number_of_returns) max.number_of_returns = point->number_of_returns;
    if (point->return_number < min.return_number) min.return_number = point->return_number;
    else if (point->return_number > max.return_number) max.return_number = point->return_number;
    if (point->classification < min.classification) min.classification = point->classification;
    else if (point->classification > max.classification) max.classification = point->classification;
    if (point->scan_angle_rank < min.scan_angle_rank) min.scan_angle_rank = point->scan_angle_rank;
    else if (point->scan_angle_rank > max.scan_angle_rank) max.scan_angle_rank = point->scan_angle_rank;
    if (point->user_data < min.user_data) min.user_data = point->user_data;
    else if (point->user_data > max.user_data) max.user_data = point->user_data;
    if (point->point_source_ID < min.point_source_ID) min.point_source_ID = point->point_source_ID;
    else if (point->point_source_ID > max.point_source_ID) max.point_source_ID = point->point_source_ID;
    if (point->have_gps_time)
    {
      if (point->gps_time < min.gps_time) min.gps_time = point->gps_time;
      else if (point->gps_time > max.gps_time) max.gps_time = point->gps_time;
    }
    if (point->have_rgb)
    {
      if (point->rgb[0] < min.rgb[0]) min.rgb[0] = point->rgb[0];
      else if (point->rgb[0] > max.rgb[0]) max.rgb[0] = point->rgb[0];
      if (point->rgb[1] < min.rgb[1]) min.rgb[1] = point->rgb[1];
      else if (point->rgb[1] > max.rgb[1]) max.rgb[1] = point->rgb[1];
      if (point->rgb[2] < min.rgb[2]) min.rgb[2] = point->rgb[2];
      else if (point->rgb[2] > max.rgb[2]) max.rgb[2] = point->rgb[2];
    }
    if (point->extended_point_type)
    {
      if (point->extended_classification < min.extended_classification) min.extended_classification = point->extended_classification;
      else if (point->extended_classification > max.extended_classification) max.extended_classification = point->extended_classification;
      if (point->extended_return_number < min.extended_return_number) min.extended_return_number = point->extended_return_number;
      else if (point->extended_return_number > max.extended_return_number) max.extended_return_number = point->extended_return_number;
      if (point->extended_number_of_returns < min.extended_number_of_returns) min.extended_number_of_returns = point->extended_number_of_returns;
      else if (point->extended_number_of_returns > max.extended_number_of_returns) max.extended_number_of_returns = point->extended_number_of_returns;
      if (point->extended_scan_angle < min.extended_scan_angle) min.extended_scan_angle = point->extended_scan_angle;
      else if (point->extended_scan_angle > max.extended_scan_angle) max.extended_scan_angle = point->extended_scan_angle;
      if (point->extended_scanner_channel < min.extended_scanner_channel) min.extended_scanner_channel = point->extended_scanner_channel;
      else if (point->extended_scanner_channel > max.extended_scanner_channel) max.extended_scanner_channel = point->extended_scanner_channel;
      if (point->have_nir)
      {
        if (point->rgb[3] < min.rgb[3]) min.rgb[3] = point->rgb[3];
        else if (point->rgb[3] > max.rgb[3]) max.rgb[3] = point->rgb[3];
      }
    }
    if (point->have_wavepacket)
    {
      if (point->wavepacket.getIndex() < min.wavepacket.getIndex()) min.wavepacket.setIndex(point->wavepacket.getIndex());
      else if (point->wavepacket.getIndex() > max.wavepacket.getIndex()) max.wavepacket.setIndex(point->wavepacket.getIndex());
      if (point->wavepacket.getOffset() < min.wavepacket.getOffset()) min.wavepacket.setOffset(point->wavepacket.getOffset());
      else if (point->wavepacket.getOffset() > max.wavepacket.getOffset()) max.wavepacket.setOffset(point->wavepacket.getOffset());
      if (point->wavepacket.getSize() < min.wavepacket.getSize()) min.wavepacket.setSize(point->wavepacket.getSize());
      else if (point->wavepacket.getSize() > max.wavepacket.getSize()) max.wavepacket.setSize(point->wavepacket.getSize());
      if (point->wavepacket.getLocation() < min.wavepacket.getLocation()) min.wavepacket.setLocation(point->wavepacket.getLocation());
      else if (point->wavepacket.getLocation() > max.wavepacket.getLocation()) max.wavepacket.setLocation(point->wavepacket.getLocation());
      if (point->wavepacket.getXt() < min.wavepacket.getXt()) min.wavepacket.setXt(point->wavepacket.getXt());
      else if (point->wavepacket.getXt() > max.wavepacket.getXt()) max.wavepacket.setXt(point->wavepacket.getXt());
      if (point->wavepacket.getYt() < min.wavepacket.getYt()) min.wavepacket.setYt(point->wavepacket.getYt());
      else if (point->wavepacket.getYt() > max.wavepacket.getYt()) max.wavepacket.setYt(point->wavepacket.getYt());
      if (point->wavepacket.getZt() < min.wavepacket.getZt()) min.wavepacket.setZt(point->wavepacket.getZt());
      else if (point->wavepacket.getZt() > max.wavepacket.getZt()) max.wavepacket.setZt(point->wavepacket.getZt());
    }
    if (point->extra_bytes_number)
    {
      if (point->attributer)
      {
        min.attributer = point->attributer;
        max.attributer = point->attributer;
        I32 a;
        for (a = 0; a < point->attributer->number_attributes; a++)
        {
          F64 value = point->get_attribute_as_float(a);
          if (value < min.get_attribute_as_float(a))
          {
            min.set_attribute_as_float(a, value);
          }
          if (value > max.get_attribute_as_float(a))
          {
            max.set_attribute_as_float(a, value);
          }
        }
        min.attributer = 0;
        max.attributer = 0;
      }
    }
  }
  if (((U16)(point->get_X()%10)) == xyz_low_digits_10[0])
  {
    xyz_fluff_10[0]++;
    if (((U16)(point->get_X()%100)) == xyz_low_digits_100[0])
    {
      xyz_fluff_100[0]++;
      if (((U16)(point->get_X()%1000)) == xyz_low_digits_1000[0])
      {
        xyz_fluff_1000[0]++;
        if (((U16)(point->get_X()%10000)) == xyz_low_digits_10000[0])
        {
          xyz_fluff_10000[0]++;
        }
      }
    }
  }
  if (((U16)(point->get_Y()%10)) == xyz_low_digits_10[1])
  {
    xyz_fluff_10[1]++;
    if (((U16)(point->get_Y()%100)) == xyz_low_digits_100[1])
    {
      xyz_fluff_100[1]++;
      if (((U16)(point->get_Y()%1000)) == xyz_low_digits_1000[1])
      {
        xyz_fluff_1000[1]++;
        if (((U16)(point->get_Y()%10000)) == xyz_low_digits_10000[1])
        {
          xyz_fluff_10000[1]++;
        }
      }
    }
  }
  if (((U16)(point->get_Z()%10)) == xyz_low_digits_10[2])
  {
    xyz_fluff_10[2]++;
    if (((U16)(point->get_Z()%100)) == xyz_low_digits_100[2])
    {
      xyz_fluff_100[2]++;
      if (((U16)(point->get_Z()%1000)) == xyz_low_digits_1000[2])
      {
        xyz_fluff_1000[2]++;
        if (((U16)(point->get_Z()%10000)) == xyz_low_digits_10000[2])
        {
          xyz_fluff_10000[2]++;
        }
      }
    }
  }
  return TRUE;
}

F64 LASbin::get_step() const
{
  return step;
}

LASbin::LASbin(F64 step, F64 clamp_min, F64 clamp_max)
{
  total = 0;
  count = 0;
  this->step = step;
  this->one_over_step = 1.0/step;
  this->clamp_min = clamp_min;
  this->clamp_max = clamp_max;
  first = TRUE;
  size_pos = 0;
  size_neg = 0;
  bins_pos = 0;
  bins_neg = 0;
  values_pos = 0;
  values_neg = 0;
}

LASbin::~LASbin()
{
  if (bins_pos) free(bins_pos);
  if (bins_neg) free(bins_neg);
  if (values_pos) free(values_pos);
  if (values_neg) free(values_neg);
}

void LASbin::add(I32 item)
{
  if (item > clamp_max)
  {
    item = (I32)clamp_max;
  }
  else if (item < clamp_min)
  {
    item = (I32)clamp_min;
  }
  total += item;
  count++;
  I32 bin = I32_FLOOR(one_over_step*item);
  add_to_bin(bin);
}

void LASbin::add(F64 item)
{
  if (item > clamp_max)
  {
    item = clamp_max;
  }
  else if (item < clamp_min)
  {
    item = clamp_min;
  }
  total += item;
  count++;
  I32 bin = I32_FLOOR(one_over_step*item);
  add_to_bin(bin);
}

void LASbin::add(I64 item)
{
  if (item > clamp_max)
  {
    item = (I64)clamp_max;
  }
  else if (item < clamp_min)
  {
    item = (I64)clamp_min;
  }
  total += item;
  count++;
  I32 bin = I32_FLOOR(one_over_step*item);
  add_to_bin(bin);
}

void LASbin::add_to_bin(I32 bin)
{
  if (first)
  {
    anker = bin;
    first = FALSE;
  }
  bin = bin - anker;
  if (bin >= 0)
  {
    if (bin >= size_pos)
    {
      I32 i;
      if (size_pos == 0)
      {
        size_pos = bin + 1024;
        bins_pos = (U32*)malloc(sizeof(U32)*size_pos);
        if (bins_pos == 0)
        {
          REprintf( "ERROR: allocating %u pos bins\012", size_pos);
          throw std::runtime_error("Internal error");
        }
        for (i = 0; i < size_pos; i++) bins_pos[i] = 0;
      }
      else
      {
        I32 new_size = bin + 1024;
        bins_pos = (U32*)realloc(bins_pos, sizeof(U32)*new_size);
        if (bins_pos == 0)
        {
          REprintf( "ERROR: reallocating %u pos bins\012", new_size);
          throw std::runtime_error("Internal error");
        }
        for (i = size_pos; i < new_size; i++) bins_pos[i] = 0;
        size_pos = new_size;
      }
    }
    bins_pos[bin]++;
  }
  else
  {
    bin = -(bin+1);
    if (bin >= size_neg)
    {
      I32 i;
      if (size_neg == 0)
      {
        size_neg = bin + 1024;
        bins_neg = (U32*)malloc(sizeof(U32)*size_neg);
        if (bins_neg == 0)
        {
          REprintf( "ERROR: allocating %u neg bins\012", size_neg);
          throw std::runtime_error("Internal error");
        }
        for (i = 0; i < size_neg; i++) bins_neg[i] = 0;
      }
      else
      {
        I32 new_size = bin + 1024;
        bins_neg = (U32*)realloc(bins_neg, sizeof(U32)*new_size);
        if (bins_neg == 0)
        {
          REprintf( "ERROR: reallocating %u neg bins\012", new_size);
          throw std::runtime_error("Internal error");
        }
        for (i = size_neg; i < new_size; i++) bins_neg[i] = 0;
        size_neg = new_size;
      }
    }
    bins_neg[bin]++;
  }
}

void LASbin::add(I32 item, I32 value)
{
  total += item;
  count++;
  I32 bin = I32_FLOOR(one_over_step*item);
  if (first)
  {
    anker = bin;
    first = FALSE;
  }
  bin = bin - anker;
  if (bin >= 0)
  {
    if (bin >= size_pos)
    {
      I32 i;
      if (size_pos == 0)
      {
        size_pos = 1024;
        bins_pos = (U32*)malloc(sizeof(U32)*size_pos);
        values_pos = (F64*)malloc(sizeof(F64)*size_pos);
        if (bins_pos == 0)
        {
          REprintf( "ERROR: allocating %u pos bins\012", size_pos);
          throw std::runtime_error("Internal error");
        }
        if (values_pos == 0)
        {
          REprintf( "ERROR: allocating %u pos values\012", size_pos);
          throw std::runtime_error("Internal error");
        }
        for (i = 0; i < size_pos; i++) { bins_pos[i] = 0; values_pos[i] = 0; }
      }
      else
      {
        I32 new_size = bin + 1024;
        bins_pos = (U32*)realloc(bins_pos, sizeof(U32)*new_size);
        values_pos = (F64*)realloc(values_pos, sizeof(F64)*new_size);
        if (bins_pos == 0)
        {
          REprintf( "ERROR: reallocating %u pos bins\012", new_size);
          throw std::runtime_error("Internal error");
        }
        if (values_pos == 0)
        {
          REprintf( "ERROR: reallocating %u pos values\012", new_size);
          throw std::runtime_error("Internal error");
        }
        for (i = size_pos; i < new_size; i++) { bins_pos[i] = 0; values_pos[i] = 0; }
        size_pos = new_size;
      }
    }
    bins_pos[bin]++;
    values_pos[bin] += value;
  }
  else
  {
    bin = -(bin+1);
    if (bin >= size_neg)
    {
      I32 i;
      if (size_neg == 0)
      {
        size_neg = 1024;
        bins_neg = (U32*)malloc(sizeof(U32)*size_neg);
        values_neg = (F64*)malloc(sizeof(F64)*size_neg);
        if (bins_neg == 0)
        {
          REprintf( "ERROR: allocating %u neg bins\012", size_neg);
          throw std::runtime_error("Internal error");
        }
        if (values_neg == 0)
        {
          REprintf( "ERROR: allocating %u neg values\012", size_neg);
          throw std::runtime_error("Internal error");
        }
        for (i = 0; i < size_neg; i++) { bins_neg[i] = 0; values_neg[i] = 0; }
      }
      else
      {
        I32 new_size = bin + 1024;
        bins_neg = (U32*)realloc(bins_neg, sizeof(U32)*new_size);
        values_neg = (F64*)realloc(values_neg, sizeof(F64)*new_size);
        if (bins_neg == 0)
        {
          REprintf( "ERROR: reallocating %u neg bins\012", new_size);
          throw std::runtime_error("Internal error");
        }
        if (values_neg == 0)
        {
          REprintf( "ERROR: reallocating %u neg values\012", new_size);
          throw std::runtime_error("Internal error");
        }
        for (i = size_neg; i < new_size; i++) { bins_neg[i] = 0; values_neg[i] = 0; }
        size_neg = new_size;
      }
    }
    bins_neg[bin]++;
    values_neg[bin] += value;
  }
}

void LASbin::add(F64 item, F64 value)
{
  total += item;
  count++;
  I32 bin = I32_FLOOR(one_over_step*item);
  if (first)
  {
    anker = bin;
    first = FALSE;
  }
  bin = bin - anker;
  if (bin >= 0)
  {
    if (bin >= size_pos)
    {
      I32 i;
      if (size_pos == 0)
      {
        size_pos = 1024;
        bins_pos = (U32*)malloc(sizeof(U32)*size_pos);
        values_pos = (F64*)malloc(sizeof(F64)*size_pos);
        if (bins_pos == 0)
        {
          REprintf( "ERROR: allocating %u pos bins\012", size_pos);
          throw std::runtime_error("Internal error");
        }
        if (values_pos == 0)
        {
          REprintf( "ERROR: allocating %u pos values\012", size_pos);
          throw std::runtime_error("Internal error");
        }
        for (i = 0; i < size_pos; i++) { bins_pos[i] = 0; values_pos[i] = 0; }
      }
      else
      {
        I32 new_size = bin + 1024;
        bins_pos = (U32*)realloc(bins_pos, sizeof(U32)*new_size);
        values_pos = (F64*)realloc(values_pos, sizeof(F64)*new_size);
        if (bins_pos == 0)
        {
          REprintf( "ERROR: reallocating %u pos bins\012", new_size);
          throw std::runtime_error("Internal error");
        }
        if (values_pos == 0)
        {
          REprintf( "ERROR: reallocating %u pos values\012", new_size);
          throw std::runtime_error("Internal error");
        }
        for (i = size_pos; i < new_size; i++) { bins_pos[i] = 0; values_pos[i] = 0; }
        size_pos = new_size;
      }
    }
    bins_pos[bin]++;
    values_pos[bin] += value;
  }
  else
  {
    bin = -(bin+1);
    if (bin >= size_neg)
    {
      I32 i;
      if (size_neg == 0)
      {
        size_neg = 1024;
        bins_neg = (U32*)malloc(sizeof(U32)*size_neg);
        values_neg = (F64*)malloc(sizeof(F64)*size_neg);
        if (bins_neg == 0)
        {
          REprintf( "ERROR: allocating %u neg bins\012", size_neg);
          throw std::runtime_error("Internal error");
        }
        if (values_neg == 0)
        {
          REprintf( "ERROR: allocating %u neg values\012", size_neg);
          throw std::runtime_error("Internal error");
        }
        for (i = 0; i < size_neg; i++) { bins_neg[i] = 0; values_neg[i] = 0; }
      }
      else
      {
        I32 new_size = bin + 1024;
        bins_neg = (U32*)realloc(bins_neg, sizeof(U32)*new_size);
        values_neg = (F64*)realloc(values_neg, sizeof(F64)*new_size);
        if (bins_neg == 0)
        {
          REprintf( "ERROR: reallocating %u neg bins\012", new_size);
          throw std::runtime_error("Internal error");
        }
        if (values_neg == 0)
        {
          REprintf( "ERROR: reallocating %u neg values\012", new_size);
          throw std::runtime_error("Internal error");
        }
        for (i = size_neg; i < new_size; i++) { bins_neg[i] = 0; values_neg[i] = 0; }
        size_neg = new_size;
      }
    }
    bins_neg[bin]++;
    values_neg[bin] += value;
  }
}

static void lidardouble2string(CHAR* string, F64 value)
{
  int len;
  len = snprintf(string, 512, "%.15f", value) - 1;
  while (string[len] == '0') len--;
  if (string[len] != '.') len++;
  string[len] = '\0';
}

static void lidardouble2string(CHAR* string, F64 value, F64 precision)
{
  if (precision == 0.1 || precision == 0.2 || precision == 0.3 || precision == 0.4 || precision == 0.5)
    snprintf(string, 512, "%.1f", value);
  else if (precision == 0.01 || precision == 0.02 || precision == 0.03 || precision == 0.04 || precision == 0.05 || precision == 0.25)
    snprintf(string, 512, "%.2f", value);
  else if (precision == 0.001 || precision == 0.002 || precision == 0.003 || precision == 0.004 || precision == 0.005 || precision == 0.025 || precision == 0.125)
    snprintf(string, 512, "%.3f", value);
  else if (precision == 0.0001 || precision == 0.0002 || precision == 0.0005 || precision == 0.0025 || precision == 0.0125)
    snprintf(string, 512, "%.4f", value);
  else if (precision == 0.00001 || precision == 0.00002 || precision == 0.00005 || precision == 0.00025 || precision == 0.00125)
    snprintf(string, 512, "%.5f", value);
  else if (precision == 0.000001 || precision == 0.000002 || precision == 0.000005 || precision == 0.000025 || precision == 0.000125)
    snprintf(string, 512, "%.6f", value);
  else if (precision == 0.0000001)
    snprintf(string, 512, "%.7f", value);
  else if (precision == 0.00000001)
    snprintf(string, 512, "%.8f", value);
  else if (precision == 0.000000001)
    snprintf(string, 512, "%.9f", value);
  else
    lidardouble2string(string, value);
}

void LASbin::report(FILE* file, const CHAR* name, const CHAR* name_avg) const
{
  I32 i, bin;
  if (name)
  {
    if (values_pos)
    {
      if (name_avg)
        fprintf(file, "%s histogram of %s averages with bin size %lf\012", name, name_avg, step);
      else
        fprintf(file, "%s histogram of averages with bin size %lf\012", name, step);
    }
    else
      fprintf(file, "%s histogram with bin size %lf\012", name, step);
  }
  CHAR temp1[64];
  CHAR temp2[64];
  if (size_neg)
  {
    for (i = size_neg-1; i >= 0; i--)
    {
      if (bins_neg[i])
      {
        bin = -(i+1) + anker;
        if (step == 1)
        {
          if (values_neg)
            fprintf(file, "  bin %d has average %g (of %d)\012", bin, values_neg[i]/bins_neg[i], bins_neg[i]);
          else
            fprintf(file, "  bin %d has %d\012", bin, bins_neg[i]);
        }
        else
        {
          lidardouble2string(temp1, ((F64)bin)*step, step);
          lidardouble2string(temp2, ((F64)bin+1)*step, step);
          if (values_neg)
            fprintf(file, "  bin [%s,%s) has average %g (of %d)\012", temp1, temp2, values_neg[i]/bins_neg[i], bins_neg[i]);
          else
            fprintf(file, "  bin [%s,%s) has %d\012", temp1, temp2, bins_neg[i]);
        }
      }
    }
  }
  if (size_pos)
  {
    for (i = 0; i < size_pos; i++)
    {
      if (bins_pos[i])
      {
        bin = i + anker;
        if (step == 1)
        {
          if (values_pos)
            fprintf(file, "  bin %d has average %g (of %d)\012", bin, values_pos[i]/bins_pos[i], bins_pos[i]);
          else
            fprintf(file, "  bin %d has %d\012", bin, bins_pos[i]);
        }
        else
        {
          lidardouble2string(temp1, ((F64)bin)*step, step);
          lidardouble2string(temp2, ((F64)bin+1)*step, step);
          if (values_pos)
            fprintf(file, "  bin [%s,%s) average has %g (of %d)\012", temp1, temp2, values_pos[i]/bins_pos[i], bins_pos[i]);
          else
            fprintf(file, "  bin [%s,%s) has %d\012", temp1, temp2, bins_pos[i]);
        }
      }
    }
  }
  if (count)
  {
    lidardouble2string(temp1, total/count, step);

// temporarily comment this portion that is not useful in rlas. See #62
/*#ifdef _WIN32
    if (name)
      fprintf(file, "  average %s %s for %" PRId64 " element(s)\012", name, temp1, count);
    else
      fprintf(file, "  average %s for %" PRId64 " element(s)\012", temp1, count);
#else
    if (name)
      fprintf(file, "  average %s %s for %" PRId64 " element(s)\012", name, temp1, count);
    else
      fprintf(file, "  average %s for %" PRId64 " element(s)\012", temp1, count);
#endif*/
  }
}

void LASbin::reset()
{
  first = TRUE;
  count = 0;
  total = 0.0;
  if (size_pos)
  {
    memset(bins_pos, 0, sizeof(U32)*size_pos);
    if (values_pos)
    {
      memset(values_pos, 0, sizeof(F64)*size_pos);
    }
  }
  if (size_neg)
  {
    memset(bins_neg, 0, sizeof(U32)*size_neg);
    if (values_neg)
    {
      memset(values_neg, 0, sizeof(F64)*size_neg);
    }
  }
}

LAShistogram::LAShistogram()
{
  is_active = FALSE;
  // counter bins
  x_bin = 0;
  y_bin = 0;
  z_bin = 0;
  X_bin = 0;
  Y_bin = 0;
  Z_bin = 0;
  intensity_bin = 0;
  classification_bin = 0;
  scan_angle_bin = 0;
  extended_scan_angle_bin = 0;
  return_number_bin = 0;
  number_of_returns_bin = 0;
  user_data_bin = 0;
  point_source_id_bin = 0;
  gps_time_bin = 0;
  scanner_channel_bin = 0;
  R_bin = 0;
  G_bin = 0;
  B_bin = 0;
  I_bin = 0;
  attribute0_bin = 0;
  attribute1_bin = 0;
  attribute2_bin = 0;
  attribute3_bin = 0;
  attribute4_bin = 0;
  attribute5_bin = 0;
  attribute6_bin = 0;
  attribute7_bin = 0;
  attribute8_bin = 0;
  attribute9_bin = 0;
  wavepacket_index_bin = 0;
  wavepacket_offset_bin = 0;
  wavepacket_size_bin = 0;
  wavepacket_location_bin = 0;
  // averages bins
  classification_bin_intensity = 0;
  classification_bin_scan_angle = 0;
  scan_angle_bin_z = 0;
  scan_angle_bin_intensity = 0;
  scan_angle_bin_number_of_returns = 0;
  return_map_bin_intensity = 0;
}

LAShistogram::~LAShistogram()
{
  // counter bins
  if (x_bin) delete x_bin;
  if (y_bin) delete y_bin;
  if (z_bin) delete z_bin;
  if (X_bin) delete X_bin;
  if (Y_bin) delete Y_bin;
  if (Z_bin) delete Z_bin;
  if (intensity_bin) delete intensity_bin;
  if (classification_bin) delete classification_bin;
  if (scan_angle_bin) delete scan_angle_bin;
  if (extended_scan_angle_bin) delete extended_scan_angle_bin;
  if (return_number_bin) delete return_number_bin;
  if (number_of_returns_bin) delete number_of_returns_bin;
  if (user_data_bin) delete user_data_bin;
  if (point_source_id_bin) delete point_source_id_bin;
  if (gps_time_bin) delete gps_time_bin;
  if (scanner_channel_bin) delete scanner_channel_bin;
  if (R_bin) delete R_bin;
  if (G_bin) delete G_bin;
  if (B_bin) delete B_bin;
  if (I_bin) delete I_bin;
  if (attribute0_bin) delete attribute0_bin;
  if (attribute1_bin) delete attribute1_bin;
  if (attribute2_bin) delete attribute2_bin;
  if (attribute3_bin) delete attribute3_bin;
  if (attribute4_bin) delete attribute4_bin;
  if (attribute5_bin) delete attribute5_bin;
  if (attribute6_bin) delete attribute6_bin;
  if (attribute7_bin) delete attribute7_bin;
  if (attribute8_bin) delete attribute8_bin;
  if (attribute9_bin) delete attribute9_bin;
  if (wavepacket_index_bin) delete wavepacket_index_bin;
  if (wavepacket_offset_bin) delete wavepacket_offset_bin;
  if (wavepacket_size_bin) delete wavepacket_size_bin;
  if (wavepacket_location_bin) delete wavepacket_location_bin;
  // averages bins
  if (classification_bin_intensity) delete classification_bin_intensity;
  if (classification_bin_scan_angle) delete classification_bin_scan_angle;
  if (scan_angle_bin_z) delete scan_angle_bin_z;
  if (scan_angle_bin_intensity) delete scan_angle_bin_intensity;
  if (scan_angle_bin_number_of_returns) delete scan_angle_bin_number_of_returns;
  if (return_map_bin_intensity) delete return_map_bin_intensity;
}

BOOL LAShistogram::parse(int argc, char* argv[])
{
  int i;
  for (i = 1; i < argc; i++)
  {
    if (argv[i][0] == '\0')
    {
      continue;
    }
    else if (strcmp(argv[i],"-h") == 0 || strcmp(argv[i],"-help") == 0)
    {
      return TRUE;
    }
    else if (strcmp(argv[i],"-histo") == 0)
    {
      if ((i+2) >= argc)
      {
        REprintf("ERROR: '%s' needs 2 arguments: name step\n", argv[i]);
        return FALSE;
      }
      F64 step = 0.0;
      if (sscanf(argv[i+2], "%lf", &step) != 1)
      {
        REprintf("ERROR: '%s' needs 2 arguments: name step but '%s' is no valid step\n", argv[i], argv[i+2]);
        return FALSE;
      }
      if (!histo(argv[i+1], step)) return FALSE;
      *argv[i]='\0'; *argv[i+1]='\0'; *argv[i+2]='\0'; i+=2;
    }
    else if (strcmp(argv[i],"-histo_avg") == 0)
    {
      if ((i+3) >= argc)
      {
        REprintf("ERROR: '%s' needs 3 arguments: name step name_avg\n", argv[i]);
        return FALSE;
      }
      F64 step = 0.0;
      if (sscanf(argv[i+2], "%lf", &step) != 1)
      {
        REprintf("ERROR: '%s' needs 3 arguments: name step name_avg but '%s' is no valid step\n", argv[i], argv[i+2]);
        return FALSE;
      }
      if (!histo_avg(argv[i+1], step, argv[i+3])) return FALSE;
      *argv[i]='\0'; *argv[i+1]='\0'; *argv[i+2]='\0'; *argv[i+3]='\0'; i+=3;
    }
  }
  return TRUE;
}

I32 LAShistogram::unparse(CHAR* string) const
{
  I32 n = 0;
  if (x_bin) n += snprintf(&string[n], 0, "-histo x %lf ", x_bin->get_step());
  if (y_bin) n += snprintf(&string[n], 0, "-histo y %lf ", y_bin->get_step());
  if (z_bin) n += snprintf(&string[n], 0, "-histo z %lf ", z_bin->get_step());
  if (X_bin) n += snprintf(&string[n], 0, "-histo X %lf ", X_bin->get_step());
  if (Y_bin) n += snprintf(&string[n], 0, "-histo Y %lf ", Y_bin->get_step());
  if (Z_bin) n += snprintf(&string[n], 0, "-histo Z %lf ", Z_bin->get_step());
  if (intensity_bin) n += snprintf(&string[n], 0, "-histo intensity %lf ", intensity_bin->get_step());
  if (classification_bin) n += snprintf(&string[n], 0, "-histo classification %lf ", classification_bin->get_step());
  if (scan_angle_bin) n += snprintf(&string[n], 0, "-histo scan_angle %lf ", scan_angle_bin->get_step());
  if (extended_scan_angle_bin) n += snprintf(&string[n], 0, "-histo extended_scan_angle %lf ", extended_scan_angle_bin->get_step());
  if (return_number_bin) n += snprintf(&string[n], 0, "-histo return_number %lf ", return_number_bin->get_step());
  if (number_of_returns_bin) n += snprintf(&string[n], 0, "-histo number_of_returns %lf ", number_of_returns_bin->get_step());
  if (user_data_bin) n += snprintf(&string[n], 0, "-histo user_data %lf ", user_data_bin->get_step());
  if (point_source_id_bin) n += snprintf(&string[n], 0, "-histo point_source %lf ", point_source_id_bin->get_step());
  if (gps_time_bin) n += snprintf(&string[n], 0, "-histo gps_time %lf ", gps_time_bin->get_step());
  if (scanner_channel_bin) n += snprintf(&string[n], 0, "-histo scanner_channel %lf ", scanner_channel_bin->get_step());
  if (R_bin) n += snprintf(&string[n], 0, "-histo R %lf ", R_bin->get_step());
  if (G_bin) n += snprintf(&string[n], 0, "-histo G %lf ", G_bin->get_step());
  if (B_bin) n += snprintf(&string[n], 0, "-histo B %lf ", B_bin->get_step());
  if (I_bin) n += snprintf(&string[n], 0, "-histo I %lf ", I_bin->get_step());
  if (attribute0_bin) n += snprintf(&string[n], 0, "-histo attribute0 %lf ", attribute0_bin->get_step());
  if (attribute1_bin) n += snprintf(&string[n], 0, "-histo attribute1 %lf ", attribute1_bin->get_step());
  if (attribute2_bin) n += snprintf(&string[n], 0, "-histo attribute2 %lf ", attribute2_bin->get_step());
  if (attribute3_bin) n += snprintf(&string[n], 0, "-histo attribute3 %lf ", attribute3_bin->get_step());
  if (attribute4_bin) n += snprintf(&string[n], 0, "-histo attribute4 %lf ", attribute4_bin->get_step());
  if (attribute5_bin) n += snprintf(&string[n], 0, "-histo attribute5 %lf ", attribute5_bin->get_step());
  if (attribute6_bin) n += snprintf(&string[n], 0, "-histo attribute6 %lf ", attribute6_bin->get_step());
  if (attribute7_bin) n += snprintf(&string[n], 0, "-histo attribute7 %lf ", attribute7_bin->get_step());
  if (attribute8_bin) n += snprintf(&string[n], 0, "-histo attribute8 %lf ", attribute8_bin->get_step());
  if (attribute9_bin) n += snprintf(&string[n], 0, "-histo attribute9 %lf ", attribute9_bin->get_step());
  if (wavepacket_index_bin) n += snprintf(&string[n], 0, "-histo wavepacket_index %lf ", wavepacket_index_bin->get_step());
  if (wavepacket_offset_bin) n += snprintf(&string[n], 0, "-histo wavepacket_offset %lf ", wavepacket_offset_bin->get_step());
  if (wavepacket_size_bin) n += snprintf(&string[n], 0, "-histo wavepacket_size %lf ", wavepacket_size_bin->get_step());
  if (wavepacket_location_bin) n += snprintf(&string[n], 0, "-histo wavepacket_location %lf ", wavepacket_location_bin->get_step());
  return n;
}

BOOL LAShistogram::histo(const CHAR* name, F64 step)
{
  if (strcmp(name, "x") == 0)
    x_bin = new LASbin(step);
  else if (strcmp(name, "y") == 0)
    y_bin = new LASbin(step);
  else if (strcmp(name, "z") == 0)
    z_bin = new LASbin(step);
  else if (strcmp(name, "X") == 0)
    X_bin = new LASbin(step);
  else if (strcmp(name, "Y") == 0)
    Y_bin = new LASbin(step);
  else if (strcmp(name, "Z") == 0)
    Z_bin = new LASbin(step);
  else if (strcmp(name, "intensity") == 0)
    intensity_bin = new LASbin(step);
  else if (strcmp(name, "classification") == 0)
    classification_bin = new LASbin(step);
  else if (strstr(name, "extended_scan_angle") != 0)
    extended_scan_angle_bin = new LASbin(step);
  else if (strstr(name, "scan_angle") != 0)
    scan_angle_bin = new LASbin(step);
  else if (strstr(name, "return_number") != 0)
    return_number_bin = new LASbin(step);
  else if (strstr(name, "number_of_returns") != 0)
    number_of_returns_bin = new LASbin(step);
  else if (strstr(name, "user_data") != 0)
    user_data_bin = new LASbin(step);
  else if (strstr(name, "point_source") != 0)
    point_source_id_bin = new LASbin(step);
  else if (strstr(name, "gps_time") != 0)
    gps_time_bin = new LASbin(step);
  else if (strstr(name, "scanner_channel") != 0)
    scanner_channel_bin = new LASbin(step);
  else if (strcmp(name, "R") == 0)
    R_bin = new LASbin(step);
  else if (strcmp(name, "G") == 0)
    G_bin = new LASbin(step);
  else if (strcmp(name, "B") == 0)
    B_bin = new LASbin(step);
  else if (strcmp(name, "I") == 0)
    I_bin = new LASbin(step);
  else if (strcmp(name, "0") == 0 || strcmp(name, "attribute0") == 0)
    attribute0_bin = new LASbin(step);
  else if (strcmp(name, "1") == 0 || strcmp(name, "attribute1") == 0)
    attribute1_bin = new LASbin(step);
  else if (strcmp(name, "2") == 0 || strcmp(name, "attribute2") == 0)
    attribute2_bin = new LASbin(step);
  else if (strcmp(name, "3") == 0 || strcmp(name, "attribute3") == 0)
    attribute3_bin = new LASbin(step);
  else if (strcmp(name, "4") == 0 || strcmp(name, "attribute4") == 0)
    attribute4_bin = new LASbin(step);
  else if (strcmp(name, "5") == 0 || strcmp(name, "attribute5") == 0)
    attribute5_bin = new LASbin(step);
  else if (strcmp(name, "6") == 0 || strcmp(name, "attribute6") == 0)
    attribute6_bin = new LASbin(step);
  else if (strcmp(name, "7") == 0 || strcmp(name, "attribute7") == 0)
    attribute7_bin = new LASbin(step);
  else if (strcmp(name, "8") == 0 || strcmp(name, "attribute8") == 0)
    attribute8_bin = new LASbin(step);
  else if (strcmp(name, "9") == 0 || strcmp(name, "attribute9") == 0)
    attribute9_bin = new LASbin(step);
  else if (strstr(name, "wavepacket_index") != 0)
    wavepacket_index_bin = new LASbin(step);
  else if (strstr(name, "wavepacket_offset") != 0)
    wavepacket_offset_bin = new LASbin(step);
  else if (strstr(name, "wavepacket_size") != 0)
    wavepacket_size_bin = new LASbin(step);
  else if (strstr(name, "wavepacket_location") != 0)
    wavepacket_location_bin = new LASbin(step);
  else
  {
    REprintf("ERROR: histogram of '%s' not implemented\n", name);
    return FALSE;
  }
  is_active = TRUE;
  return TRUE;
}

BOOL LAShistogram::histo_avg(const CHAR* name, F64 step, const CHAR* name_avg)
{
  if (strcmp(name, "classification") == 0)
  {
    if (strcmp(name_avg, "intensity") == 0)
      classification_bin_intensity = new LASbin(step);
    else if (strstr(name_avg, "scan_angle") != 0)
      classification_bin_scan_angle = new LASbin(step);
    else
    {
      REprintf("ERROR: histogram of '%s' with '%s' averages not implemented\n", name, name_avg);
      return FALSE;
    }
  }
  else if (strcmp(name, "scan_angle") == 0)
  {
    if (strcmp(name_avg, "z") == 0)
      scan_angle_bin_z = new LASbin(step);
    else if (strcmp(name_avg, "number_of_returns") == 0)
      scan_angle_bin_number_of_returns = new LASbin(step);
    else if (strcmp(name_avg, "intensity") == 0)
      scan_angle_bin_intensity = new LASbin(step);
    else
    {
      REprintf("ERROR: histogram of '%s' with '%s' averages not implemented\n", name, name_avg);
      return FALSE;
    }
  }
  else if (strcmp(name, "return_map") == 0)
  {
    if (strcmp(name_avg, "intensity") == 0)
      return_map_bin_intensity = new LASbin(1);
    else
    {
      REprintf("ERROR: histogram of '%s' with '%s' averages not implemented\n", name, name_avg);
      return FALSE;
    }
  }
  else
  {
    REprintf("ERROR: histogram of '%s' not implemented\n", name);
    return FALSE;
  }
  is_active = TRUE;
  return TRUE;
}

void LAShistogram::add(const LASpoint* point)
{
  // counter bins
  if (x_bin) x_bin->add(point->get_x());
  if (y_bin) y_bin->add(point->get_y());
  if (z_bin) z_bin->add(point->get_z());
  if (X_bin) X_bin->add(point->get_X());
  if (Y_bin) Y_bin->add(point->get_Y());
  if (Z_bin) Z_bin->add(point->get_Z());
  if (intensity_bin) intensity_bin->add(point->get_intensity());
  if (classification_bin) classification_bin->add(point->get_classification());
  if (scan_angle_bin)
  {
    scan_angle_bin->add(point->get_scan_angle());
  }
  if (extended_scan_angle_bin)
  {
    extended_scan_angle_bin->add(point->extended_scan_angle);
  }
  if (return_number_bin) return_number_bin->add(point->get_return_number());
  if (number_of_returns_bin) number_of_returns_bin->add(point->get_number_of_returns());
  if (user_data_bin) user_data_bin->add(point->get_user_data());
  if (point_source_id_bin) point_source_id_bin->add(point->get_point_source_ID());
  if (gps_time_bin) gps_time_bin->add(point->get_gps_time());
  if (scanner_channel_bin) scanner_channel_bin->add(point->get_extended_scanner_channel());
  if (R_bin) R_bin->add(point->rgb[0]);
  if (G_bin) G_bin->add(point->rgb[1]);
  if (B_bin) B_bin->add(point->rgb[2]);
  if (I_bin) I_bin->add(point->rgb[3]);
  if (attribute0_bin) attribute0_bin->add(point->get_attribute_as_float(0));
  if (attribute1_bin) attribute1_bin->add(point->get_attribute_as_float(1));
  if (attribute2_bin) attribute2_bin->add(point->get_attribute_as_float(2));
  if (attribute3_bin) attribute3_bin->add(point->get_attribute_as_float(3));
  if (attribute4_bin) attribute4_bin->add(point->get_attribute_as_float(4));
  if (attribute5_bin) attribute5_bin->add(point->get_attribute_as_float(5));
  if (attribute6_bin) attribute6_bin->add(point->get_attribute_as_float(6));
  if (attribute7_bin) attribute7_bin->add(point->get_attribute_as_float(7));
  if (attribute8_bin) attribute8_bin->add(point->get_attribute_as_float(8));
  if (attribute9_bin) attribute9_bin->add(point->get_attribute_as_float(9));
  if (wavepacket_index_bin) wavepacket_index_bin->add(point->wavepacket.getIndex());
  if (wavepacket_offset_bin) wavepacket_offset_bin->add((I64)point->wavepacket.getOffset());
  if (wavepacket_size_bin) wavepacket_size_bin->add((I32)point->wavepacket.getSize());
  if (wavepacket_location_bin) wavepacket_location_bin->add(point->wavepacket.getLocation());
  // averages bins
  if (classification_bin_intensity) classification_bin_intensity->add(point->get_classification(), point->get_intensity());
  if (classification_bin_scan_angle)
  {
    classification_bin_scan_angle->add((F64)point->get_classification(), (F64)point->get_scan_angle());
  }
  if (scan_angle_bin_z)
  {
    scan_angle_bin_z->add((F64)point->get_scan_angle(), (F64)point->get_Z());
  }
  if (scan_angle_bin_number_of_returns)
  {
    scan_angle_bin_number_of_returns->add((F64)point->get_scan_angle(), (F64)point->get_extended_number_of_returns());
  }
  if (scan_angle_bin_intensity)
  {
    scan_angle_bin_intensity->add((F64)point->get_scan_angle(), (F64)point->get_intensity());
  }
  if (return_map_bin_intensity)
  {
    int n = point->number_of_returns;
    int r = point->return_number;
    return_map_bin_intensity->add((n == 1 ? 0 : (n == 2 ? r : (n == 3 ? r+2 : (n == 4 ? r+5 : (n == 5 ? r+9 : 15))))), point->intensity);
  }
}

void LAShistogram::report(FILE* file) const
{
  // counter bins
  if (x_bin) x_bin->report(file, "x coordinate");
  if (y_bin) y_bin->report(file, "y coordinate");
  if (z_bin) z_bin->report(file, "z coordinate");
  if (X_bin) X_bin->report(file, "raw integer X coordinate");
  if (Y_bin) Y_bin->report(file, "raw integer Y coordinate");
  if (Z_bin) Z_bin->report(file, "raw integer Z coordinate");
  if (intensity_bin) intensity_bin->report(file, "intensity");
  if (classification_bin) classification_bin->report(file, "classification");
  if (scan_angle_bin) scan_angle_bin->report(file, "scan angle");
  if (extended_scan_angle_bin) extended_scan_angle_bin->report(file, "extended scan angle");
  if (return_number_bin) return_number_bin->report(file, "return_number");
  if (number_of_returns_bin) number_of_returns_bin->report(file, "number_of_returns");
  if (user_data_bin) user_data_bin->report(file, "user data");
  if (point_source_id_bin) point_source_id_bin->report(file, "point source id");
  if (gps_time_bin) gps_time_bin->report(file, "gps_time");
  if (scanner_channel_bin) scanner_channel_bin->report(file, "scanner channel");
  if (R_bin) R_bin->report(file, "color R channel");
  if (G_bin) G_bin->report(file, "color G channel");
  if (B_bin) B_bin->report(file, "color B channel");
  if (I_bin) I_bin->report(file, "color I channel");
  if (attribute0_bin) attribute0_bin->report(file, "attribute 0");
  if (attribute1_bin) attribute1_bin->report(file, "attribute 1");
  if (attribute2_bin) attribute2_bin->report(file, "attribute 2");
  if (attribute3_bin) attribute3_bin->report(file, "attribute 3");
  if (attribute4_bin) attribute4_bin->report(file, "attribute 4");
  if (attribute5_bin) attribute5_bin->report(file, "attribute 5");
  if (attribute6_bin) attribute6_bin->report(file, "attribute 6");
  if (attribute7_bin) attribute7_bin->report(file, "attribute 7");
  if (attribute8_bin) attribute8_bin->report(file, "attribute 8");
  if (attribute9_bin) attribute9_bin->report(file, "attribute 9");
  if (wavepacket_index_bin) wavepacket_index_bin->report(file, "wavepacket_index");
  if (wavepacket_offset_bin) wavepacket_offset_bin->report(file, "wavepacket_offset");
  if (wavepacket_size_bin) wavepacket_size_bin->report(file, "wavepacket_size");
  if (wavepacket_location_bin) wavepacket_location_bin->report(file, "wavepacket_location");
  // averages bins
  if (classification_bin_intensity) classification_bin_intensity->report(file, "classification", "intensity");
  if (classification_bin_scan_angle) classification_bin_scan_angle->report(file, "classification", "scan_angle");
  if (scan_angle_bin_z) scan_angle_bin_z->report(file, "scan angle", "z coordinate");
  if (scan_angle_bin_number_of_returns) scan_angle_bin_number_of_returns->report(file, "scan_angle", "number_of_returns");
  if (scan_angle_bin_intensity) scan_angle_bin_intensity->report(file, "scan angle", "intensity");
  if (return_map_bin_intensity) return_map_bin_intensity->report(file, "return map", "intensity");
}

void LAShistogram::reset()
{
  // reset counter bins
  if (x_bin) x_bin->reset();
  if (y_bin) y_bin->reset();
  if (z_bin) z_bin->reset();
  if (X_bin) X_bin->reset();
  if (Y_bin) Y_bin->reset();
  if (Z_bin) Z_bin->reset();
  if (intensity_bin) intensity_bin->reset();
  if (classification_bin) classification_bin->reset();
  if (scan_angle_bin) scan_angle_bin->reset();
  if (extended_scan_angle_bin) extended_scan_angle_bin->reset();
  if (return_number_bin) return_number_bin->reset();
  if (number_of_returns_bin) number_of_returns_bin->reset();
  if (user_data_bin) user_data_bin->reset();
  if (point_source_id_bin) point_source_id_bin->reset();
  if (gps_time_bin) gps_time_bin->reset();
  if (scanner_channel_bin) scanner_channel_bin->reset();
  if (R_bin) R_bin->reset();
  if (G_bin) G_bin->reset();
  if (B_bin) B_bin->reset();
  if (I_bin) I_bin->reset();
  if (attribute0_bin) attribute0_bin->reset();
  if (attribute1_bin) attribute1_bin->reset();
  if (attribute2_bin) attribute2_bin->reset();
  if (attribute3_bin) attribute3_bin->reset();
  if (attribute4_bin) attribute4_bin->reset();
  if (attribute5_bin) attribute5_bin->reset();
  if (attribute6_bin) attribute6_bin->reset();
  if (attribute7_bin) attribute7_bin->reset();
  if (attribute8_bin) attribute8_bin->reset();
  if (attribute9_bin) attribute9_bin->reset();
  if (wavepacket_index_bin) wavepacket_index_bin->reset();
  if (wavepacket_offset_bin) wavepacket_offset_bin->reset();
  if (wavepacket_size_bin) wavepacket_size_bin->reset();
  if (wavepacket_location_bin) wavepacket_location_bin->reset();
  // averages bins
  if (classification_bin_intensity) classification_bin_intensity->reset();
  if (classification_bin_scan_angle) classification_bin_scan_angle->reset();
  if (scan_angle_bin_z) scan_angle_bin_z->reset();
  if (scan_angle_bin_intensity) scan_angle_bin_intensity->reset();
  if (scan_angle_bin_number_of_returns) scan_angle_bin_number_of_returns->reset();
  if (return_map_bin_intensity) return_map_bin_intensity->reset();
}

BOOL LASoccupancyGrid::add(const LASpoint* point)
{
  I32 pos_x, pos_y;
  if (grid_spacing < 0)
  {
    grid_spacing = -grid_spacing;
    pos_x = I32_FLOOR(point->get_x() / grid_spacing);
    pos_y = I32_FLOOR(point->get_y() / grid_spacing);
    anker = pos_y;
    min_x = max_x = pos_x;
    min_y = max_y = pos_y;
  }
  else
  {
    pos_x = I32_FLOOR(point->get_x() / grid_spacing);
    pos_y = I32_FLOOR(point->get_y() / grid_spacing);
    if (pos_x < min_x) min_x = pos_x; else if (pos_x > max_x) max_x = pos_x;
    if (pos_y < min_y) min_y = pos_y; else if (pos_y > max_y) max_y = pos_y;
  }
  return add_internal(pos_x, pos_y);
}

BOOL LASoccupancyGrid::add(I32 pos_x, I32 pos_y)
{
  if (grid_spacing < 0)
  {
    grid_spacing = -grid_spacing;
    anker = pos_y;
    min_x = max_x = pos_x;
    min_y = max_y = pos_y;
  }
  else
  {
    if (pos_x < min_x) min_x = pos_x; else if (pos_x > max_x) max_x = pos_x;
    if (pos_y < min_y) min_y = pos_y; else if (pos_y > max_y) max_y = pos_y;
  }
  return add_internal(pos_x, pos_y);
}

BOOL LASoccupancyGrid::add_internal(I32 pos_x, I32 pos_y)
{
  pos_y = pos_y - anker;
  BOOL no_x_anker = FALSE;
  U32* array_size;
  I32** ankers;
  U32*** array;
  U16** array_sizes;
  if (pos_y < 0)
  {
    pos_y = -pos_y - 1;
    ankers = &minus_ankers;
    if ((U32)pos_y < minus_plus_size && minus_plus_sizes[pos_y])
    {
      pos_x -= minus_ankers[pos_y];
      if (pos_x < 0)
      {
        pos_x = -pos_x - 1;
        array_size = &minus_minus_size;
        array = &minus_minus;
        array_sizes = &minus_minus_sizes;
      }
      else
      {
        array_size = &minus_plus_size;
        array = &minus_plus;
        array_sizes = &minus_plus_sizes;
      }
    }
    else
    {
      no_x_anker = TRUE;
      array_size = &minus_plus_size;
      array = &minus_plus;
      array_sizes = &minus_plus_sizes;
    }
  }
  else
  {
    ankers = &plus_ankers;
    if ((U32)pos_y < plus_plus_size && plus_plus_sizes[pos_y])
    {
      pos_x -= plus_ankers[pos_y];
      if (pos_x < 0)
      {
        pos_x = -pos_x - 1;
        array_size = &plus_minus_size;
        array = &plus_minus;
        array_sizes = &plus_minus_sizes;
      }
      else
      {
        array_size = &plus_plus_size;
        array = &plus_plus;
        array_sizes = &plus_plus_sizes;
      }
    }
    else
    {
      no_x_anker = TRUE;
      array_size = &plus_plus_size;
      array = &plus_plus;
      array_sizes = &plus_plus_sizes;
    }
  }
  // maybe grow banded grid in y direction
  if ((U32)pos_y >= *array_size)
  {
    U32 array_size_new = ((pos_y/1024)+1)*1024;
    if (*array_size)
    {
      if (array == &minus_plus || array == &plus_plus) *ankers = (I32*)realloc(*ankers, array_size_new*sizeof(I32));
      *array = (U32**)realloc(*array, array_size_new*sizeof(U32*));
      *array_sizes = (U16*)realloc(*array_sizes, array_size_new*sizeof(U16));
    }
    else
    {
      if (array == &minus_plus || array == &plus_plus) *ankers = (I32*)malloc(array_size_new*sizeof(I32));
      *array = (U32**)malloc(array_size_new*sizeof(U32*));
      *array_sizes = (U16*)malloc(array_size_new*sizeof(U16));
    }
    for (U32 i = *array_size; i < array_size_new; i++)
    {
      (*array)[i] = 0;
      (*array_sizes)[i] = 0;
    }
    *array_size = array_size_new;
  }
  // is this the first x anker for this y pos?
  if (no_x_anker)
  {
    (*ankers)[pos_y] = pos_x;
    pos_x = 0;
  }
  // maybe grow banded grid in x direction
  U32 pos_x_pos = pos_x/32;
  if (pos_x_pos >= (*array_sizes)[pos_y])
  {
    U32 array_sizes_new = ((pos_x_pos/256)+1)*256;
    if ((*array_sizes)[pos_y])
    {
      (*array)[pos_y] = (U32*)realloc((*array)[pos_y], array_sizes_new*sizeof(U32));
    }
    else
    {
      (*array)[pos_y] = (U32*)malloc(array_sizes_new*sizeof(U32));
    }
    for (U16 i = (*array_sizes)[pos_y]; i < array_sizes_new; i++)
    {
      (*array)[pos_y][i] = 0;
    }
    (*array_sizes)[pos_y] = array_sizes_new;
  }
  U32 pos_x_bit = 1 << (pos_x%32);
  if ((*array)[pos_y][pos_x_pos] & pos_x_bit) return FALSE;
  (*array)[pos_y][pos_x_pos] |= pos_x_bit;
  num_occupied++;
  return TRUE;
}

BOOL LASoccupancyGrid::occupied(const LASpoint* point) const
{
  I32 pos_x = I32_FLOOR(point->get_x() / grid_spacing);
  I32 pos_y = I32_FLOOR(point->get_y() / grid_spacing);
  return occupied(pos_x, pos_y);
}

BOOL LASoccupancyGrid::occupied(I32 pos_x, I32 pos_y) const
{
  if (grid_spacing < 0)
  {
    return FALSE;
  }
  pos_y = pos_y - anker;
  U32 array_size;
  const U32* const * array;
  const U16* array_sizes;
  if (pos_y < 0)
  {
    pos_y = -pos_y - 1;
    if ((U32)pos_y < minus_plus_size && minus_plus_sizes[pos_y])
    {
      pos_x -= minus_ankers[pos_y];
      if (pos_x < 0)
      {
        pos_x = -pos_x - 1;
        array_size = minus_minus_size;
        array = minus_minus;
        array_sizes = minus_minus_sizes;
      }
      else
      {
        array_size = minus_plus_size;
        array = minus_plus;
        array_sizes = minus_plus_sizes;
      }
    }
    else
    {
      return FALSE;
    }
  }
  else
  {
    if ((U32)pos_y < plus_plus_size && plus_plus_sizes[pos_y])
    {
      pos_x -= plus_ankers[pos_y];
      if (pos_x < 0)
      {
        pos_x = -pos_x - 1;
        array_size = plus_minus_size;
        array = plus_minus;
        array_sizes = plus_minus_sizes;
      }
      else
      {
        array_size = plus_plus_size;
        array = plus_plus;
        array_sizes = plus_plus_sizes;
      }
    }
    else
    {
      return FALSE;
    }
  }
  // maybe out of bounds in y direction
  if ((U32)pos_y >= array_size)
  {
    return FALSE;
  }
  // maybe out of bounds in x direction
  U32 pos_x_pos = pos_x/32;
  if (pos_x_pos >= array_sizes[pos_y])
  {
    return FALSE;
  }
  U32 pos_x_bit = 1 << (pos_x%32);
  if (array[pos_y][pos_x_pos] & pos_x_bit) return TRUE;
  return FALSE;
}

BOOL LASoccupancyGrid::active() const
{
  if (grid_spacing < 0) return FALSE;
  return TRUE;
}

void LASoccupancyGrid::reset()
{
  min_x = min_y = max_x = max_y = 0;
  if (grid_spacing > 0) grid_spacing = -grid_spacing;
  if (minus_minus_size)
  {
    for (U32 i = 0; i < minus_minus_size; i++) if (minus_minus[i]) free(minus_minus[i]);
    free(minus_minus);
    minus_minus = 0;
    free(minus_minus_sizes);
    minus_minus_sizes = 0;
    minus_minus_size = 0;
  }
  if (minus_plus_size)
  {
    free(minus_ankers);
    minus_ankers = 0;
    for (U32 i = 0; i < minus_plus_size; i++) if (minus_plus[i]) free(minus_plus[i]);
    free(minus_plus);
    minus_plus = 0;
    free(minus_plus_sizes);
    minus_plus_sizes = 0;
    minus_plus_size = 0;
  }
  if (plus_minus_size)
  {
    for (U32 i = 0; i < plus_minus_size; i++) if (plus_minus[i]) free(plus_minus[i]);
    free(plus_minus);
    plus_minus = 0;
    free(plus_minus_sizes);
    plus_minus_sizes = 0;
    plus_minus_size = 0;
  }
  if (plus_plus_size)
  {
    free(plus_ankers);
    plus_ankers = 0;
    for (U32 i = 0; i < plus_plus_size; i++) if (plus_plus[i]) free(plus_plus[i]);
    free(plus_plus);
    plus_plus = 0;
    free(plus_plus_sizes);
    plus_plus_sizes = 0;
    plus_plus_size = 0;
  }
  num_occupied = 0;
}

BOOL LASoccupancyGrid::write_asc_grid(const CHAR* file_name) const
{
  FILE* file = fopen(file_name, "w");
  if (file == 0) return FALSE;
  fprintf(file, "ncols %d\012", max_x-min_x+1);
  fprintf(file, "nrows %d\012", max_y-min_y+1);
  fprintf(file, "xllcorner %f\012", grid_spacing*min_x);
  fprintf(file, "yllcorner %f\012", grid_spacing*min_y);
  fprintf(file, "cellsize %lf\012", grid_spacing);
  fprintf(file, "NODATA_value %d\012", 0);
  fprintf(file, "\012");
  I32 pos_x, pos_y;
  for (pos_y = min_y; pos_y <= max_y; pos_y++)
  {
    for (pos_x = min_x; pos_x <= max_x; pos_x++)
    {
      if (occupied(pos_x, pos_y))
      {
        fprintf(file, "1 ");
      }
      else
      {
        fprintf(file, "0 ");
      }
    }
    fprintf(file, "\012");
  }
  fclose(file);
  return TRUE;
}

LASoccupancyGrid::LASoccupancyGrid(F32 grid_spacing)
{
  min_x = min_y = max_x = max_y = 0;
  this->grid_spacing = -grid_spacing;
  minus_ankers = 0;
  minus_minus_size = 0;
  minus_minus = 0;
  minus_minus_sizes = 0;
  minus_plus_size = 0;
  minus_plus = 0;
  minus_plus_sizes = 0;
  plus_ankers = 0;
  plus_minus_size = 0;
  plus_minus = 0;
  plus_minus_sizes = 0;
  plus_plus_size = 0;
  plus_plus = 0;
  plus_plus_sizes = 0;
  num_occupied = 0;
}

LASoccupancyGrid::~LASoccupancyGrid()
{
  reset();
}

