[pdal] 01/10: Imported Upstream version 1.4.0

Bas Couwenberg sebastic at debian.org
Thu Dec 15 21:12:11 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository pdal.

commit 071c75a8fa621a85fa054e77aa29358ace924ed3
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Thu Dec 15 20:41:06 2016 +0100

    Imported Upstream version 1.4.0
---
 .gitignore                                         |    3 +-
 .travis.yml                                        |    4 +-
 CMakeLists.txt                                     |  199 ++-
 CONTRIBUTING.md                                    |    1 +
 ChangeLog                                          |    4 +-
 HOWTORELEASE.txt                                   |   17 +
 PDAL--src.tar.bz2.md5                              |    0
 PDAL--src.tar.gz.md5                               |    0
 apps/CMakeLists.txt                                |  140 +-
 apps/pdal-config                                   |   16 +-
 apps/pdal-config-bat.in                            |   31 +
 apps/pdal.cpp                                      |  180 ++-
 cmake/compiler_options.cmake                       |    6 -
 cmake/directories.cmake                            |   10 +-
 cmake/gdal.cmake                                   |    1 -
 cmake/geotiff.cmake                                |   28 +-
 cmake/gtest.cmake                                  |    2 +-
 cmake/json.cmake                                   |    2 +-
 cmake/laszip.cmake                                 |    3 +
 cmake/libxml2.cmake                                |    6 +-
 cmake/macros.cmake                                 |  102 +-
 cmake/modules/FindJSONCPP.cmake                    |    2 +-
 cmake/modules/FindSQLite3.cmake                    |   14 +-
 cmake/options.cmake                                |    5 -
 cmake/pdaljni.cmake                                |    7 +
 cmake/rply.cmake                                   |    1 +
 cmake/sqlite.cmake                                 |    3 +
 cmake/test.cmake                                   |    4 +-
 cmake/win32_compiler_options.cmake                 |    1 +
 dimbuilder/CMakeLists.txt                          |   29 +-
 doc/.gitignore                                     |    1 +
 doc/api/cpp/algorithm.rst                          |   15 -
 doc/api/cpp/index.rst                              |    1 -
 doc/apps/density.rst                               |    4 +
 doc/apps/ground.rst                                |   38 +-
 doc/apps/hausdorff.rst                             |   54 +
 doc/apps/pipeline.rst                              |   28 +-
 doc/apps/sort.rst                                  |   16 +
 doc/apps/translate.rst                             |   50 +-
 doc/apps/view.rst                                  |   58 -
 doc/conf.py                                        |   21 +
 doc/development/compilation/dependencies.rst       |    9 +-
 doc/development/contributors.rst                   |    7 +-
 doc/development/docs.rst                           |    2 +-
 doc/dimensions.rst                                 |   15 +
 doc/download.rst                                   |   17 +-
 doc/images/awesome-green.png                       |  Bin 0 -> 990 bytes
 doc/images/black-orange.png                        |  Bin 0 -> 1031 bytes
 doc/images/blue-hue.png                            |  Bin 0 -> 1047 bytes
 doc/images/blue-orange.png                         |  Bin 0 -> 1053 bytes
 doc/images/blue-red.png                            |  Bin 0 -> 977 bytes
 doc/images/heat-map.png                            |  Bin 0 -> 1028 bytes
 doc/images/pestel-shades.png                       |  Bin 0 -> 1078 bytes
 doc/images/pestel_scaled_helheim.png               |  Bin 0 -> 654740 bytes
 doc/images/pestel_scaled_plasio.png                |  Bin 0 -> 379816 bytes
 doc/index.rst                                      |   53 +-
 doc/pipeline.rst                                   |   15 +-
 doc/quickstart.rst                                 |    8 +-
 doc/references.rst                                 |   20 +
 doc/stages/filters.colorinterp.rst                 |  148 ++
 doc/stages/filters.computerange.rst                |   33 +
 doc/stages/filters.crop.rst                        |   12 +-
 doc/stages/filters.dartsample.rst                  |   30 -
 doc/stages/filters.decimation.rst                  |   32 +-
 doc/stages/filters.ground.rst                      |   60 -
 doc/stages/filters.height.rst                      |   36 -
 doc/stages/filters.iqr.rst                         |   51 +
 doc/stages/filters.kdistance.rst                   |   36 +
 doc/stages/filters.lof.rst                         |   61 +
 doc/stages/filters.mad.rst                         |   47 +
 doc/stages/filters.merge.rst                       |   80 +-
 doc/stages/filters.mongus.rst                      |   63 +
 doc/stages/filters.outlier.rst                     |   25 +-
 doc/stages/filters.pclblock.rst                    |   75 +-
 doc/stages/filters.pmf.rst                         |    4 +-
 doc/stages/filters.programmable.rst                |    2 +-
 doc/stages/filters.radialdensity.rst               |   47 +
 doc/stages/filters.radiusoutlier.rst               |   51 -
 doc/stages/filters.sample.rst                      |    5 -
 doc/stages/filters.smrf.rst                        |   66 +
 doc/stages/filters.statisticaloutlier.rst          |   72 -
 doc/stages/readers.bpf.rst                         |    2 +-
 doc/stages/readers.greyhound.rst                   |   65 +-
 doc/stages/readers.pcd.rst                         |    6 +
 doc/stages/readers.qfit.rst                        |    3 +-
 doc/stages/readers.rst                             |    6 +-
 doc/stages/readers.rxp.rst                         |   56 +-
 doc/stages/writers.derivative.rst                  |   34 +-
 doc/stages/writers.gdal.rst                        |  126 ++
 doc/stages/writers.las.rst                         |    4 +-
 doc/stages/writers.matlab.rst                      |   12 +-
 doc/stages/writers.p2g.rst                         |    7 +
 doc/stages/writers.pcd.rst                         |    5 +
 doc/stages/writers.pclvisualizer.rst               |   21 -
 doc/stages/writers.rst                             |    5 +-
 doc/tutorial/calculating-normalized-heights.rst    |   73 +-
 doc/tutorial/dart-throwing.rst                     |    9 +-
 doc/tutorial/overview.rst                          |  154 +-
 doc/tutorial/pcl_block_tutorial.rst                |  107 +-
 doc/tutorial/pcl_ground.rst                        |    9 +
 doc/tutorial/pcl_spec.rst                          |  368 ++---
 doc/tutorial/using.rst                             |    4 +-
 doc/tutorial/writing-filter.rst                    |  100 +-
 doc/tutorial/writing-kernel.rst                    |    2 +-
 doc/tutorial/writing-reader.rst                    |   95 +-
 doc/tutorial/writing-writer.rst                    |   40 +-
 .../exercises/analysis/denoising/denoise.json      |    8 +-
 .../exercises/analysis/denoising/denoising.rst     |   35 +-
 doc/workshop/exercises/analysis/ground/filter.json |   14 -
 .../analysis/ground/ground-run-ground-only.txt     |    5 +-
 .../analysis/ground/ground-run-no-filter.txt       |    2 +-
 .../analysis/ground/ground-run-pcl-filter.txt      |    5 -
 doc/workshop/exercises/analysis/ground/ground.rst  |   47 +-
 .../analysis/ground/translate-run-ground-only.txt  |   11 +
 .../analysis/thinning/thinning-run-dartsample.txt  |    4 +-
 .../exercises/analysis/thinning/thinning.rst       |    5 +-
 doc/workshop/pdal-introduction.rst                 |    6 +-
 doc/workshop/slides/source/denoising.rst           |    2 +-
 doc/workshop/slides/source/ground.rst              |   11 +-
 examples/writing-filter/CMakeLists.txt             |    8 +-
 examples/writing-kernel/CMakeLists.txt             |    8 +-
 examples/writing-reader/CMakeLists.txt             |    8 +-
 examples/writing-reader/MyReader.cpp               |    7 +-
 examples/writing-writer/CMakeLists.txt             |    5 +-
 examples/writing-writer/MyWriter.cpp               |    1 +
 examples/writing/CMakeLists.txt                    |    6 +-
 examples/writing/tutorial.cpp                      |    3 +-
 filters/ApproximateCoplanarFilter.cpp              |  104 ++
 .../ApproximateCoplanarFilter.hpp                  |    0
 filters/AttributeFilter.cpp                        |  238 +++
 filters/{attribute => }/AttributeFilter.hpp        |    0
 filters/CMakeLists.txt                             |   29 -
 filters/ChipperFilter.cpp                          |  338 ++++
 filters/{chipper => }/ChipperFilter.hpp            |    0
 filters/ColorInterpRamps.hpp                       |  407 +++++
 filters/ColorinterpFilter.cpp                      |  243 +++
 filters/ColorinterpFilter.hpp                      |  102 ++
 filters/{colorization => }/ColorizationFilter.cpp  |    0
 filters/{colorization => }/ColorizationFilter.hpp  |    0
 filters/ComputeRangeFilter.cpp                     |  115 ++
 filters/ComputeRangeFilter.hpp                     |   72 +
 filters/CropFilter.cpp                             |  243 +++
 filters/CropFilter.hpp                             |   97 ++
 filters/{decimation => }/DecimationFilter.cpp      |    0
 filters/{decimation => }/DecimationFilter.hpp      |    0
 filters/{divider => }/DividerFilter.cpp            |    0
 filters/{divider => }/DividerFilter.hpp            |    0
 filters/EigenvaluesFilter.cpp                      |  102 ++
 filters/{eigenvalues => }/EigenvaluesFilter.hpp    |    0
 filters/EstimateRankFilter.cpp                     |   86 +
 filters/{estimaterank => }/EstimateRankFilter.hpp  |    0
 filters/FerryFilter.cpp                            |  141 ++
 filters/{ferry => }/FerryFilter.hpp                |    0
 filters/HAGFilter.cpp                              |  116 ++
 filters/{hag => }/HAGFilter.hpp                    |    0
 filters/IQRFilter.cpp                              |  123 ++
 filters/IQRFilter.hpp                              |   75 +
 filters/KDistanceFilter.cpp                        |   93 ++
 filters/KDistanceFilter.hpp                        |   74 +
 filters/LOFFilter.cpp                              |  135 ++
 filters/LOFFilter.hpp                              |   74 +
 filters/MADFilter.cpp                              |  121 ++
 filters/MADFilter.hpp                              |   76 +
 filters/{merge => }/MergeFilter.cpp                |    0
 filters/{merge => }/MergeFilter.hpp                |    0
 filters/MongusFilter.cpp                           |  555 +++++++
 filters/MongusFilter.hpp                           |   93 ++
 filters/{mortonorder => }/MortonOrderFilter.cpp    |    0
 filters/{mortonorder => }/MortonOrderFilter.hpp    |    0
 filters/NormalFilter.cpp                           |  110 ++
 filters/{normal => }/NormalFilter.hpp              |    0
 filters/OutlierFilter.cpp                          |  229 +++
 filters/{outlier => }/OutlierFilter.hpp            |    0
 filters/PMFFilter.cpp                              |  347 ++++
 filters/PMFFilter.hpp                              |   84 +
 filters/RadialDensityFilter.cpp                    |   89 +
 filters/RadialDensityFilter.hpp                    |   74 +
 filters/{randomize => }/RandomizeFilter.cpp        |    0
 filters/{randomize => }/RandomizeFilter.hpp        |    0
 filters/{range => }/RangeFilter.cpp                |    0
 filters/{range => }/RangeFilter.hpp                |    0
 filters/ReprojectionFilter.cpp                     |  199 +++
 filters/{reprojection => }/ReprojectionFilter.hpp  |    0
 filters/SMRFilter.cpp                              |  950 +++++++++++
 filters/SMRFilter.hpp                              |  103 ++
 filters/SampleFilter.cpp                           |  126 ++
 filters/{sample => }/SampleFilter.hpp              |    0
 filters/{sort => }/SortFilter.cpp                  |    0
 filters/SortFilter.hpp                             |   94 ++
 filters/SplitterFilter.cpp                         |  112 ++
 filters/SplitterFilter.hpp                         |   83 +
 filters/StatsFilter.cpp                            |  314 ++++
 filters/StatsFilter.hpp                            |  188 +++
 .../{streamcallback => }/StreamCallbackFilter.cpp  |    0
 .../{streamcallback => }/StreamCallbackFilter.hpp  |    0
 filters/TransformationFilter.cpp                   |  128 ++
 .../{transformation => }/TransformationFilter.hpp  |    0
 .../ApproximateCoplanarFilter.cpp                  |  107 --
 filters/approximatecoplanar/CMakeLists.txt         |    2 -
 filters/attribute/AttributeFilter.cpp              |  239 ---
 filters/attribute/CMakeLists.txt                   |    9 -
 filters/chipper/CMakeLists.txt                     |   17 -
 filters/chipper/ChipperFilter.cpp                  |  322 ----
 filters/colorization/CMakeLists.txt                |   17 -
 filters/crop/CMakeLists.txt                        |   17 -
 filters/crop/CropFilter.cpp                        |  204 ---
 filters/crop/CropFilter.hpp                        |   93 --
 filters/decimation/CMakeLists.txt                  |   17 -
 filters/divider/CMakeLists.txt                     |   14 -
 filters/eigenvalues/CMakeLists.txt                 |    2 -
 filters/eigenvalues/EigenvaluesFilter.cpp          |  105 --
 filters/estimaterank/CMakeLists.txt                |    2 -
 filters/estimaterank/EstimateRankFilter.cpp        |   89 -
 filters/ferry/CMakeLists.txt                       |   17 -
 filters/ferry/FerryFilter.cpp                      |  141 --
 filters/hag/CMakeLists.txt                         |    2 -
 filters/hag/HAGFilter.cpp                          |  117 --
 filters/merge/CMakeLists.txt                       |    5 -
 filters/mongus/CMakeLists.txt                      |    2 -
 filters/mongus/MongusFilter.cpp                    |  894 ----------
 filters/mongus/MongusFilter.hpp                    |  104 --
 filters/mortonorder/CMakeLists.txt                 |   17 -
 filters/normal/CMakeLists.txt                      |    2 -
 filters/normal/NormalFilter.cpp                    |  113 --
 filters/outlier/CMakeLists.txt                     |    2 -
 filters/outlier/OutlierFilter.cpp                  |  237 ---
 filters/pmf/CMakeLists.txt                         |    2 -
 filters/pmf/PMFFilter.cpp                          |  249 ---
 filters/pmf/PMFFilter.hpp                          |   83 -
 filters/private/crop/Point.cpp                     |  124 ++
 filters/private/crop/Point.hpp                     |   67 +
 filters/randomize/CMakeLists.txt                   |    5 -
 filters/range/CMakeLists.txt                       |    5 -
 filters/reprojection/CMakeLists.txt                |   17 -
 filters/reprojection/ReprojectionFilter.cpp        |  201 ---
 filters/sample/CMakeLists.txt                      |    2 -
 filters/sample/SampleFilter.cpp                    |  129 --
 filters/smrf/CMakeLists.txt                        |    2 -
 filters/smrf/SMRFilter.cpp                         | 1097 -------------
 filters/smrf/SMRFilter.hpp                         |  122 --
 filters/sort/CMakeLists.txt                        |    5 -
 filters/sort/SortFilter.hpp                        |   88 -
 filters/splitter/CMakeLists.txt                    |   17 -
 filters/splitter/SplitterFilter.cpp                |  122 --
 filters/splitter/SplitterFilter.hpp                |   68 -
 filters/stats/CMakeLists.txt                       |   17 -
 filters/stats/StatsFilter.cpp                      |  257 ---
 filters/stats/StatsFilter.hpp                      |  169 --
 filters/streamcallback/CMakeLists.txt              |   10 -
 filters/transformation/CMakeLists.txt              |   18 -
 filters/transformation/TransformationFilter.cpp    |  128 --
 include/pdal/DimUtil.hpp                           |  192 ---
 include/pdal/Eigen.hpp                             |  140 --
 include/pdal/FlexWriter.hpp                        |  141 --
 include/pdal/GDALUtils.hpp                         |  307 ----
 include/pdal/GEOSUtils.hpp                         |  107 --
 include/pdal/KDIndex.hpp                           |  348 ----
 include/pdal/Kernel.hpp                            |  143 --
 include/pdal/Log.hpp                               |  135 --
 include/pdal/PDALUtils.hpp                         |  270 ----
 include/pdal/PipelineManager.hpp                   |  145 --
 include/pdal/PointLayout.hpp                       |  246 ---
 include/pdal/PointView.hpp                         |  573 -------
 include/pdal/PointViewIter.hpp                     |  171 --
 include/pdal/Polygon.hpp                           |  129 --
 include/pdal/SpatialReference.hpp                  |  188 ---
 include/pdal/Stage.hpp                             |  425 -----
 include/pdal/Writer.hpp                            |   98 --
 include/pdal/pdal_macros.hpp                       |  122 --
 include/pdal/pdal_types.hpp                        |  178 --
 include/pdal/plang/Redirector.hpp                  |   51 -
 include/pdal/util/Bounds.hpp                       |  639 --------
 include/pdal/util/FileUtils.hpp                    |  248 ---
 include/pdal/util/IStream.hpp                      |  545 -------
 include/pdal/util/ProgramArgs.hpp                  | 1417 ----------------
 include/pdal/util/Utils.hpp                        |  905 -----------
 io/{bpf => }/BpfCompressor.cpp                     |    0
 io/{bpf => }/BpfCompressor.hpp                     |    0
 io/{bpf => }/BpfHeader.cpp                         |    0
 io/BpfHeader.hpp                                   |  261 +++
 io/BpfReader.cpp                                   |  648 ++++++++
 io/BpfReader.hpp                                   |  116 ++
 io/BpfWriter.cpp                                   |  359 ++++
 io/BpfWriter.hpp                                   |   91 ++
 io/{buffer => }/BufferReader.hpp                   |    0
 io/CMakeLists.txt                                  |   18 -
 io/DerivativeWriter.cpp                            |  191 +++
 io/DerivativeWriter.hpp                            |  103 ++
 io/{faux => }/FauxReader.cpp                       |    0
 io/{faux => }/FauxReader.hpp                       |    0
 io/GDALGrid.cpp                                    |  407 +++++
 io/GDALGrid.hpp                                    |  162 ++
 io/GDALReader.cpp                                  |  172 ++
 io/GDALReader.hpp                                  |   84 +
 io/GDALWriter.cpp                                  |  204 +++
 io/GDALWriter.hpp                                  |   83 +
 io/GeotiffSupport.cpp                              |  280 ++++
 io/GeotiffSupport.hpp                              |   80 +
 io/{las => }/HeaderVal.hpp                         |    0
 io/Ilvis2MetadataReader.cpp                        |  688 ++++++++
 io/Ilvis2MetadataReader.hpp                        |  107 ++
 io/Ilvis2Reader.cpp                                |  304 ++++
 io/{ilvis2 => }/Ilvis2Reader.hpp                   |    0
 io/{las => }/LasError.hpp                          |    0
 io/LasHeader.cpp                                   |  486 ++++++
 io/LasHeader.hpp                                   |  425 +++++
 io/LasReader.cpp                                   |  808 +++++++++
 io/LasReader.hpp                                   |  152 ++
 io/LasSummaryData.cpp                              |  109 ++
 io/LasSummaryData.hpp                              |   76 +
 io/{las => }/LasUtils.cpp                          |    0
 io/{las => }/LasUtils.hpp                          |    0
 io/LasVLR.cpp                                      |  107 ++
 io/LasVLR.hpp                                      |  126 ++
 io/LasWriter.cpp                                   |  926 +++++++++++
 io/LasWriter.hpp                                   |  173 ++
 io/LasZipPoint.cpp                                 |  124 ++
 io/LasZipPoint.hpp                                 |   83 +
 io/{null => }/NullWriter.cpp                       |    0
 io/{null => }/NullWriter.hpp                       |    0
 io/{optech => }/OptechCommon.hpp                   |    0
 io/OptechReader.cpp                                |  283 ++++
 io/{optech => }/OptechReader.hpp                   |    0
 io/{optech => }/OptechRotationMatrix.hpp           |    0
 io/{ply => }/PlyReader.cpp                         |    0
 io/PlyReader.hpp                                   |   78 +
 io/PlyWriter.cpp                                   |  205 +++
 io/PlyWriter.hpp                                   |   71 +
 io/{pts => }/PtsReader.cpp                         |    0
 io/{pts => }/PtsReader.hpp                         |    0
 io/{qfit => }/QfitReader.cpp                       |    0
 io/{qfit => }/QfitReader.hpp                       |    0
 io/{sbet => }/SbetCommon.cpp                       |    0
 io/{sbet => }/SbetCommon.hpp                       |    0
 io/SbetReader.cpp                                  |  120 ++
 io/{sbet => }/SbetReader.hpp                       |    0
 io/SbetWriter.cpp                                  |   86 +
 io/SbetWriter.hpp                                  |   69 +
 io/{tindex => }/TIndexReader.cpp                   |    0
 io/TIndexReader.hpp                                |  110 ++
 io/{terrasolid => }/TerrasolidReader.cpp           |    0
 io/{terrasolid => }/TerrasolidReader.hpp           |    0
 io/TextReader.cpp                                  |  185 +++
 io/{text => }/TextReader.hpp                       |    0
 io/TextWriter.cpp                                  |  251 +++
 io/{text => }/TextWriter.hpp                       |    0
 io/bpf/BpfHeader.hpp                               |  261 ---
 io/bpf/BpfReader.cpp                               |  602 -------
 io/bpf/BpfReader.hpp                               |  112 --
 io/bpf/BpfWriter.cpp                               |  356 ----
 io/bpf/BpfWriter.hpp                               |   90 --
 io/bpf/CMakeLists.txt                              |   23 -
 io/buffer/CMakeLists.txt                           |   15 -
 io/derivative/CMakeLists.txt                       |   17 -
 io/derivative/DerivativeWriter.cpp                 | 1708 --------------------
 io/derivative/DerivativeWriter.hpp                 |  163 --
 io/faux/CMakeLists.txt                             |   17 -
 io/gdal/CMakeLists.txt                             |   10 -
 io/gdal/GDALReader.cpp                             |  162 --
 io/gdal/GDALReader.hpp                             |   83 -
 io/ilvis2/CMakeLists.txt                           |   26 -
 io/ilvis2/Ilvis2MetadataReader.cpp                 |  688 --------
 io/ilvis2/Ilvis2MetadataReader.hpp                 |  107 --
 io/ilvis2/Ilvis2Reader.cpp                         |  302 ----
 io/las/CMakeLists.txt                              |   67 -
 io/las/GeotiffSupport.cpp                          |  338 ----
 io/las/GeotiffSupport.hpp                          |   81 -
 io/las/LasHeader.cpp                               |  485 ------
 io/las/LasHeader.hpp                               |  425 -----
 io/las/LasReader.cpp                               |  794 ---------
 io/las/LasReader.hpp                               |  152 --
 io/las/LasWriter.cpp                               |  935 -----------
 io/las/LasWriter.hpp                               |  173 --
 io/las/SummaryData.cpp                             |  109 --
 io/las/SummaryData.hpp                             |   76 -
 io/las/VariableLengthRecord.cpp                    |  103 --
 io/las/VariableLengthRecord.hpp                    |  124 --
 io/las/ZipPoint.cpp                                |  124 --
 io/las/ZipPoint.hpp                                |   83 -
 io/null/CMakeLists.txt                             |   14 -
 io/optech/CMakeLists.txt                           |   10 -
 io/optech/OptechReader.cpp                         |  285 ----
 io/ply/CMakeLists.txt                              |   14 -
 io/ply/PlyReader.hpp                               |   79 -
 io/ply/PlyWriter.cpp                               |  206 ---
 io/ply/PlyWriter.hpp                               |   73 -
 io/pts/CMakeLists.txt                              |   14 -
 io/qfit/CMakeLists.txt                             |   17 -
 io/sbet/CMakeLists.txt                             |   39 -
 io/sbet/SbetReader.cpp                             |  119 --
 io/sbet/SbetWriter.cpp                             |   80 -
 io/sbet/SbetWriter.hpp                             |   68 -
 io/terrasolid/CMakeLists.txt                       |   17 -
 io/text/CMakeLists.txt                             |   16 -
 io/text/TextReader.cpp                             |  181 ---
 io/text/TextWriter.cpp                             |  250 ---
 io/tindex/CMakeLists.txt                           |   10 -
 io/tindex/TIndexReader.hpp                         |  110 --
 java/.gitignore                                    |   43 +
 java/README.md                                     |   34 +
 java/build.sbt                                     |   64 +
 java/core/src/main/scala/io/pdal/DimType.scala     |   49 +
 java/core/src/main/scala/io/pdal/Native.scala      |   40 +
 java/core/src/main/scala/io/pdal/Pipeline.scala    |   56 +
 java/core/src/main/scala/io/pdal/PointCloud.scala  |  134 ++
 java/core/src/main/scala/io/pdal/PointLayout.scala |   67 +
 java/core/src/main/scala/io/pdal/PointView.scala   |  156 ++
 .../src/main/scala/io/pdal/PointViewIterator.scala |   42 +
 .../core/src/main/scala/io/pdal/SizedDimType.scala |   36 +
 java/core/src/test/resources/las.json              |    8 +
 .../core/src/test/scala/io/pdal/PipelineSpec.scala |  177 ++
 .../src/test/scala/io/pdal/PointCloudSpec.scala    |  183 +++
 .../test/scala/io/pdal/TestEnvironmentSpec.scala   |   68 +
 java/native/src/Accessors.cpp                      |   41 +
 java/native/src/CMakeLists.txt                     |   71 +
 java/native/src/JavaPipeline.cpp                   |   83 +
 java/native/src/PointViewRawPtr.cpp                |   46 +
 java/native/src/include/Accessors.hpp              |   55 +
 java/native/src/include/JavaIterator.hpp           |   77 +
 java/native/src/include/JavaPipeline.hpp           |   91 ++
 java/native/src/include/PointViewRawPtr.hpp        |   55 +
 java/native/src/include/io_pdal_Pipeline.h         |   93 ++
 java/native/src/include/io_pdal_PointLayout.h      |   61 +
 java/native/src/include/io_pdal_PointView.h        |   77 +
 .../native/src/include/io_pdal_PointViewIterator.h |   37 +
 java/native/src/io_pdal_Pipeline.cpp               |  144 ++
 java/native/src/io_pdal_PointLayout.cpp            |  133 ++
 java/native/src/io_pdal_PointView.cpp              |  213 +++
 java/native/src/io_pdal_PointViewIterator.cpp      |   75 +
 java/project/Environment.scala                     |   45 +
 java/project/build.properties                      |    2 +
 java/project/plugins.sbt                           |    5 +
 java/sbt                                           |  568 +++++++
 java/scripts/publish-212.sh                        |    3 +
 java/scripts/publish-all.sh                        |    5 +
 java/scripts/publish-javastyle.sh                  |    3 +
 java/scripts/publish-local.sh                      |    3 +
 java/scripts/publish.sh                            |    3 +
 kernels/CMakeLists.txt                             |   13 -
 kernels/DeltaKernel.cpp                            |  212 +++
 kernels/{delta => }/DeltaKernel.hpp                |    0
 kernels/DiffKernel.cpp                             |  158 ++
 kernels/{diff => }/DiffKernel.hpp                  |    0
 kernels/GroundKernel.cpp                           |  123 ++
 kernels/GroundKernel.hpp                           |   79 +
 kernels/HausdorffKernel.cpp                        |   99 ++
 kernels/HausdorffKernel.hpp                        |   67 +
 kernels/InfoKernel.cpp                             |  462 ++++++
 kernels/{info => }/InfoKernel.hpp                  |    0
 kernels/MergeKernel.cpp                            |   88 +
 kernels/{merge => }/MergeKernel.hpp                |    0
 kernels/PipelineKernel.cpp                         |  127 ++
 kernels/{pipeline => }/PipelineKernel.hpp          |    0
 kernels/RandomKernel.cpp                           |  110 ++
 kernels/{random => }/RandomKernel.hpp              |    0
 kernels/SortKernel.cpp                             |  109 ++
 kernels/{sort => }/SortKernel.hpp                  |    0
 kernels/SplitKernel.cpp                            |  136 ++
 kernels/{split => }/SplitKernel.hpp                |    0
 kernels/TIndexKernel.cpp                           |  678 ++++++++
 kernels/TIndexKernel.hpp                           |  119 ++
 kernels/TranslateKernel.cpp                        |  200 +++
 kernels/TranslateKernel.hpp                        |   77 +
 kernels/delta/CMakeLists.txt                       |   17 -
 kernels/delta/DeltaKernel.cpp                      |  219 ---
 kernels/diff/CMakeLists.txt                        |   17 -
 kernels/diff/DiffKernel.cpp                        |  161 --
 kernels/info/CMakeLists.txt                        |   17 -
 kernels/info/InfoKernel.cpp                        |  460 ------
 kernels/merge/CMakeLists.txt                       |   17 -
 kernels/merge/MergeKernel.cpp                      |   91 --
 kernels/pipeline/CMakeLists.txt                    |   17 -
 kernels/pipeline/PipelineKernel.cpp                |  119 --
 kernels/random/CMakeLists.txt                      |   17 -
 kernels/random/RandomKernel.cpp                    |  113 --
 kernels/sort/CMakeLists.txt                        |   17 -
 kernels/sort/SortKernel.cpp                        |  112 --
 kernels/split/CMakeLists.txt                       |   17 -
 kernels/split/SplitKernel.cpp                      |  139 --
 kernels/tindex/CMakeLists.txt                      |   18 -
 kernels/tindex/TIndexKernel.cpp                    |  709 --------
 kernels/tindex/TIndexKernel.hpp                    |  120 --
 kernels/translate/CMakeLists.txt                   |    3 -
 kernels/translate/TranslateKernel.cpp              |  113 --
 kernels/translate/TranslateKernel.hpp              |   73 -
 src/libpdalcpp => libpdalcpp                       |    0
 {include/pdal => pdal}/Compression.hpp             |    0
 {src => pdal}/DbReader.cpp                         |    0
 {include/pdal => pdal}/DbReader.hpp                |    0
 pdal/DbWriter.cpp                                  |  271 ++++
 {include/pdal => pdal}/DbWriter.hpp                |    0
 {include/pdal => pdal}/DimDetail.hpp               |    0
 {include/pdal => pdal}/DimType.hpp                 |    0
 pdal/DimUtil.hpp                                   |  192 +++
 pdal/Dimension.json                                |  341 ++++
 pdal/DynamicLibrary.cpp                            |  128 ++
 pdal/EigenUtils.cpp                                |  604 +++++++
 pdal/EigenUtils.hpp                                |  780 +++++++++
 {include/pdal => pdal}/Filter.hpp                  |    0
 pdal/FlexWriter.hpp                                |  145 ++
 pdal/GDALUtils.cpp                                 |  818 ++++++++++
 pdal/GDALUtils.hpp                                 |  443 +++++
 pdal/GEOSUtils.cpp                                 |  166 ++
 pdal/GEOSUtils.hpp                                 |  107 ++
 pdal/Geometry.cpp                                  |  344 ++++
 pdal/Geometry.hpp                                  |  148 ++
 pdal/KDIndex.hpp                                   |  462 ++++++
 pdal/Kernel.cpp                                    |  442 +++++
 pdal/Kernel.hpp                                    |  141 ++
 pdal/KernelFactory.cpp                             |   73 +
 {include/pdal => pdal}/KernelFactory.hpp           |    0
 pdal/Log.cpp                                       |  153 ++
 pdal/Log.hpp                                       |  152 ++
 {src => pdal}/Metadata.cpp                         |    0
 {include/pdal => pdal}/Metadata.hpp                |    0
 {src => pdal}/Options.cpp                          |    0
 {include/pdal => pdal}/Options.hpp                 |    0
 pdal/PDALUtils.cpp                                 |  404 +++++
 pdal/PDALUtils.hpp                                 |  280 ++++
 pdal/PipelineExecutor.cpp                          |  138 ++
 pdal/PipelineExecutor.hpp                          |  143 ++
 pdal/PipelineManager.cpp                           |  358 ++++
 pdal/PipelineManager.hpp                           |  146 ++
 pdal/PipelineReaderJSON.cpp                        |  341 ++++
 pdal/PipelineReaderJSON.hpp                        |   82 +
 pdal/PipelineReaderXML.cpp                         |  484 ++++++
 {src => pdal}/PipelineWriter.cpp                   |    0
 {include/pdal => pdal}/PipelineWriter.hpp          |    0
 pdal/PluginManager.cpp                             |  499 ++++++
 {include/pdal => pdal}/PluginManager.hpp           |    0
 {include/pdal => pdal}/PointContainer.hpp          |    0
 {src => pdal}/PointLayout.cpp                      |    0
 pdal/PointLayout.hpp                               |  246 +++
 {include/pdal => pdal}/PointRef.hpp                |    0
 {src => pdal}/PointTable.cpp                       |    0
 {include/pdal => pdal}/PointTable.hpp              |    0
 pdal/PointView.cpp                                 |  245 +++
 pdal/PointView.hpp                                 |  587 +++++++
 pdal/PointViewIter.hpp                             |  173 ++
 pdal/Polygon.cpp                                   |  326 ++++
 pdal/Polygon.hpp                                   |   92 ++
 {src => pdal}/QuadIndex.cpp                        |    0
 {include/pdal => pdal}/QuadIndex.hpp               |    0
 {include/pdal => pdal}/QuickInfo.hpp               |    0
 {src => pdal}/Reader.cpp                           |    0
 {include/pdal => pdal}/Reader.hpp                  |    0
 {src => pdal}/Scaling.cpp                          |    0
 {include/pdal => pdal}/Scaling.hpp                 |    0
 pdal/SpatialReference.cpp                          |  496 ++++++
 pdal/SpatialReference.hpp                          |  161 ++
 pdal/Stage.cpp                                     |  422 +++++
 pdal/Stage.hpp                                     |  439 +++++
 pdal/StageFactory.cpp                              |  321 ++++
 {include/pdal => pdal}/StageFactory.hpp            |    0
 {include/pdal => pdal}/StageWrapper.hpp            |    0
 pdal/Writer.cpp                                    |   72 +
 pdal/Writer.hpp                                    |   95 ++
 pdal/XMLSchema.cpp                                 |  648 ++++++++
 {include/pdal => pdal}/XMLSchema.hpp               |    0
 pdal/gitsha.cpp                                    |    3 +
 {include/pdal => pdal}/gitsha.h                    |    0
 {include/pdal => pdal}/pdal.hpp                    |    0
 pdal/pdal_config.cpp                               |  186 +++
 {include/pdal => pdal}/pdal_config.hpp             |    0
 {include/pdal => pdal}/pdal_export.hpp             |    0
 {include/pdal => pdal}/pdal_internal.hpp           |    0
 pdal/pdal_macros.hpp                               |  109 ++
 {include/pdal => pdal}/pdal_test_main.hpp          |    0
 pdal/pdal_types.hpp                                |  225 +++
 {src => pdal}/plang/Array.cpp                      |    0
 {include/pdal => pdal}/plang/Array.hpp             |    0
 {src => pdal}/plang/BufferedInvocation.cpp         |    0
 .../pdal => pdal}/plang/BufferedInvocation.hpp     |    0
 pdal/plang/CMakeLists.txt                          |   30 +
 pdal/plang/Environment.cpp                         |  348 ++++
 {include/pdal => pdal}/plang/Environment.hpp       |    0
 pdal/plang/Invocation.cpp                          |  291 ++++
 {include/pdal => pdal}/plang/Invocation.hpp        |    0
 pdal/plang/Redirector.cpp                          |  221 +++
 pdal/plang/Redirector.hpp                          |   57 +
 {src => pdal}/plang/Script.cpp                     |    0
 {include/pdal => pdal}/plang/Script.hpp            |    0
 {include/pdal => pdal}/plugin.hpp                  |    0
 {src => pdal/private}/DynamicLibrary.hpp           |    0
 {src => pdal/private}/PipelineReaderXML.hpp        |    0
 {src => pdal/private}/StageRunner.hpp              |    0
 {src => pdal}/prototype.vcxproj                    |    0
 {include/pdal => pdal}/util/Algorithm.hpp          |    0
 pdal/util/Bounds.cpp                               |  325 ++++
 pdal/util/Bounds.hpp                               |  643 ++++++++
 pdal/util/CMakeLists.txt                           |   37 +
 {src => pdal}/util/Charbuf.cpp                     |    0
 {include/pdal => pdal}/util/Charbuf.hpp            |    0
 {include/pdal => pdal}/util/Extractor.hpp          |    0
 pdal/util/FileUtils.cpp                            |  401 +++++
 pdal/util/FileUtils.hpp                            |  254 +++
 {src => pdal}/util/Georeference.cpp                |    0
 {include/pdal => pdal}/util/Georeference.hpp       |    0
 pdal/util/IStream.hpp                              |  550 +++++++
 {include/pdal => pdal}/util/Inserter.hpp           |    0
 {include/pdal => pdal}/util/OStream.hpp            |    0
 pdal/util/ProgramArgs.hpp                          | 1591 ++++++++++++++++++
 pdal/util/Utils.cpp                                |  698 ++++++++
 pdal/util/Utils.hpp                                |  957 +++++++++++
 {include/pdal => pdal}/util/Uuid.hpp               |    0
 {include/pdal => pdal}/util/pdal_util_export.hpp   |    0
 {include/pdal => pdal}/util/portable_endian.hpp    |    0
 pdal_defines.h.in                                  |    1 -
 plugins/CMakeLists.txt                             |   14 +-
 plugins/cpd/CMakeLists.txt                         |   12 +-
 plugins/cpd/kernel/Cpd.cpp                         |    6 +-
 plugins/cpd/test/CpdKernelTest.cpp                 |    3 +-
 plugins/greyhound/CMakeLists.txt                   |   41 +-
 plugins/greyhound/io/GreyhoundReader.cpp           |  890 +++++-----
 plugins/greyhound/io/GreyhoundReader.hpp           |  132 +-
 plugins/greyhound/io/bbox.cpp                      |  318 ----
 plugins/greyhound/io/bbox.hpp                      |  217 ---
 plugins/greyhound/io/bounds.cpp                    |  163 ++
 plugins/greyhound/io/bounds.hpp                    |  309 ++++
 plugins/greyhound/io/dir.hpp                       |   47 +-
 plugins/greyhound/io/point.hpp                     |  311 +++-
 plugins/greyhound/io/pool.cpp                      |  167 ++
 plugins/greyhound/io/pool.hpp                      |   93 ++
 plugins/greyhound/io/range.hpp                     |   37 -
 plugins/greyhound/test/GreyhoundReaderTest.cpp     |  225 ++-
 plugins/hexbin/CMakeLists.txt                      |   32 +-
 plugins/hexbin/filters/HexBin.cpp                  |   28 +-
 plugins/hexbin/filters/HexBin.hpp                  |    1 +
 plugins/hexbin/kernel/DensityKernel.cpp            |   10 +-
 plugins/hexbin/kernel/DensityKernel.hpp            |    1 +
 plugins/hexbin/kernel/OGR.cpp                      |   25 +-
 plugins/hexbin/kernel/OGR.hpp                      |    3 +-
 plugins/hexbin/test/HexbinFilterTest.cpp           |    1 +
 plugins/icebridge/CMakeLists.txt                   |   19 +-
 plugins/icebridge/io/IcebridgeReader.cpp           |   13 +-
 plugins/icebridge/io/IcebridgeReader.hpp           |    9 +-
 plugins/icebridge/test/IcebridgeReaderTest.cpp     |    3 +-
 plugins/matlab/CMakeLists.txt                      |   25 +-
 plugins/matlab/io/MatlabWriter.cpp                 |    5 +-
 plugins/matlab/io/MatlabWriter.hpp                 |    1 +
 plugins/mrsid/CMakeLists.txt                       |   24 +-
 plugins/mrsid/io/MrsidReader.cpp                   |    5 +
 plugins/mrsid/io/MrsidReader.hpp                   |    2 +
 plugins/mrsid/test/MrsidTest.cpp                   |    2 +-
 plugins/nitf/CMakeLists.txt                        |   71 +-
 plugins/nitf/io/NitfFileWriter.cpp                 |   29 +-
 plugins/nitf/io/NitfReader.hpp                     |    3 +-
 plugins/nitf/io/NitfWriter.cpp                     |    6 +-
 plugins/nitf/io/NitfWriter.hpp                     |    2 +-
 plugins/nitf/io/tre_plugins.cpp                    |    7 +-
 plugins/nitf/io/tre_plugins.hpp                    |    2 +
 plugins/nitf/test/NitfReaderTest.cpp               |    2 +-
 plugins/nitf/test/NitfWriterTest.cpp               |   28 +-
 plugins/oci/CMakeLists.txt                         |   58 +-
 plugins/oci/io/OciCommon.hpp                       |    2 +-
 plugins/oci/io/OciReader.cpp                       |    1 +
 plugins/oci/io/OciWriter.cpp                       |   15 +-
 plugins/oci/test/OCITest.cpp                       |    4 +-
 plugins/p2g/CMakeLists.txt                         |   16 +-
 plugins/p2g/io/P2gWriter.cpp                       |  100 +-
 plugins/p2g/io/P2gWriter.hpp                       |   24 +-
 plugins/pcl/CMakeLists.txt                         |  187 +--
 plugins/pcl/dartsample/dart_sample.cpp             |   49 -
 plugins/pcl/dartsample/dart_sample.h               |  112 --
 plugins/pcl/dartsample/dart_sample.hpp             |  116 --
 plugins/pcl/filters/DartSampleFilter.cpp           |  111 --
 plugins/pcl/filters/DartSampleFilter.hpp           |   63 -
 plugins/pcl/filters/GreedyProjectionFilter.cpp     |    5 +-
 plugins/pcl/filters/GridProjectionFilter.cpp       |    5 +-
 plugins/pcl/filters/GroundFilter.cpp               |  178 --
 plugins/pcl/filters/GroundFilter.hpp               |   78 -
 plugins/pcl/filters/HeightFilter.cpp               |  181 ---
 plugins/pcl/filters/HeightFilter.hpp               |   73 -
 plugins/pcl/filters/MovingLeastSquaresFilter.cpp   |    5 +-
 plugins/pcl/filters/PCLBlock.cpp                   |   45 +-
 plugins/pcl/filters/PCLBlock.hpp                   |   11 +-
 plugins/pcl/filters/PoissonFilter.cpp              |    5 +-
 plugins/pcl/filters/RadiusOutlierFilter.cpp        |  177 --
 plugins/pcl/filters/RadiusOutlierFilter.hpp        |   73 -
 plugins/pcl/filters/StatisticalOutlierFilter.cpp   |  180 ---
 plugins/pcl/filters/StatisticalOutlierFilter.hpp   |   74 -
 plugins/pcl/filters/VoxelGridFilter.cpp            |    5 +-
 plugins/pcl/io/PCLVisualizer.cpp                   |  197 ---
 plugins/pcl/io/PCLVisualizer.hpp                   |   61 -
 plugins/pcl/io/PcdReader.cpp                       |    2 +-
 plugins/pcl/io/PcdWriter.cpp                       |   44 +-
 plugins/pcl/io/PcdWriter.hpp                       |   19 +-
 plugins/pcl/kernel/GroundKernel.cpp                |  125 --
 plugins/pcl/kernel/GroundKernel.hpp                |   76 -
 plugins/pcl/kernel/HeightAboveGroundKernel.cpp     |  224 ---
 plugins/pcl/kernel/HeightAboveGroundKernel.hpp     |   63 -
 plugins/pcl/kernel/PCLKernel.cpp                   |   15 +-
 plugins/pcl/kernel/SmoothKernel.cpp                |    6 +-
 plugins/pcl/kernel/ViewKernel.cpp                  |  163 --
 plugins/pcl/kernel/ViewKernel.hpp                  |   60 -
 plugins/pcl/pipeline/PCLPipeline.h                 |   89 +-
 plugins/pcl/pipeline/PCLPipeline.hpp               |  564 ++-----
 plugins/pcl/test/PCLBlockFilterTest.cpp            |  121 +-
 plugins/pgpointcloud/CMakeLists.txt                |   50 +-
 plugins/pgpointcloud/io/PgCommon.hpp               |   13 +-
 plugins/pgpointcloud/io/PgReader.cpp               |   18 +-
 plugins/pgpointcloud/io/PgWriter.cpp               |   41 +-
 .../pgpointcloud/test/PgpointcloudWriterTest.cpp   |    4 +-
 plugins/python/CMakeLists.txt                      |   11 +-
 plugins/python/filters/CMakeLists.txt              |   25 +-
 plugins/python/filters/PredicateFilter.cpp         |    1 +
 plugins/python/filters/ProgrammableFilter.cpp      |    3 +
 plugins/python/test/PLangTest.cpp                  |    5 +-
 plugins/python/test/PredicateFilterTest.cpp        |   24 +-
 plugins/python/test/ProgrammableFilterTest.cpp     |   22 +-
 plugins/rxp/CMakeLists.txt                         |   29 +-
 plugins/rxp/io/RxpPointcloud.cpp                   |   18 +
 plugins/rxp/io/RxpPointcloud.hpp                   |    6 +
 plugins/rxp/io/RxpReader.cpp                       |   12 +-
 plugins/rxp/io/RxpReader.hpp                       |   10 +-
 plugins/rxp/test/RxpReaderTest.cpp                 |   17 +
 plugins/sqlite/CMakeLists.txt                      |   25 +-
 plugins/sqlite/io/SQLiteReader.cpp                 |    2 +-
 plugins/sqlite/io/SQLiteWriter.cpp                 |   10 +-
 plugins/sqlite/test/SQLiteTest.cpp                 |    4 +-
 python/README.rst                                  |   41 +-
 python/VERSION.txt                                 |    2 +-
 python/pdal/Pipeline.cpp                           |   59 -
 python/pdal/Pipeline.hpp                           |   39 -
 python/pdal/PyPipeline.cpp                         |  101 ++
 python/pdal/PyPipeline.hpp                         |  102 ++
 python/pdal/__init__.py                            |    4 +-
 python/pdal/libpdalpython.pyx                      |   74 +-
 python/pdal/pipeline.py                            |   46 +
 python/pdal/pipeline_xml.py                        |   62 -
 python/setup.py                                    |   20 +-
 python/test/__init__.py                            |    2 -
 python/test/test_libpdal.py                        |   74 -
 python/test/test_pipeline.py                       |  152 +-
 scripts/appveyor/config.cmd                        |    3 +-
 scripts/ci/common.sh                               |    9 +-
 scripts/ci/script.sh                               |   10 +-
 scripts/docker/Dockerfile                          |   11 +-
 scripts/docker/Dockerfile.xenial                   |    2 +-
 scripts/docker/dependencies/Dockerfile             |   22 +-
 scripts/docker/dependencies/Dockerfile.xenial      |  273 ----
 scripts/docker/docbuild/Dockerfile                 |    6 +-
 scripts/docker/rivlib/Dockerfile                   |   10 +-
 scripts/linux-install-scripts/pdal.sh              |    1 -
 src/CMakeLists.txt                                 |  230 ---
 src/DbWriter.cpp                                   |  271 ----
 src/Dimension.json                                 |  331 ----
 src/DynamicLibrary.cpp                             |  127 --
 src/Eigen.cpp                                      |  143 --
 src/Filter.cpp                                     |   47 -
 src/GDALUtils.cpp                                  |  593 -------
 src/GEOSUtils.cpp                                  |  163 --
 src/Kernel.cpp                                     |  515 ------
 src/KernelFactory.cpp                              |   69 -
 src/Log.cpp                                        |  153 --
 src/PDALUtils.cpp                                  |  338 ----
 src/PipelineManager.cpp                            |  355 ----
 src/PipelineReader.hpp                             |   92 --
 src/PipelineReaderJSON.cpp                         |  316 ----
 src/PipelineReaderJSON.hpp                         |   78 -
 src/PipelineReaderXML.cpp                          |  484 ------
 src/PluginManager.cpp                              |  499 ------
 src/PointView.cpp                                  |  244 ---
 src/Polygon.cpp                                    |  515 ------
 src/SpatialReference.cpp                           |  511 ------
 src/Stage.cpp                                      |  398 -----
 src/StageFactory.cpp                               |  305 ----
 src/Writer.cpp                                     |   71 -
 src/XMLSchema.cpp                                  |  649 --------
 src/gitsha.cpp                                     |    3 -
 src/pdal_config.cpp                                |  194 ---
 src/plang/CMakeLists.txt                           |   38 -
 src/plang/Environment.cpp                          |  339 ----
 src/plang/Invocation.cpp                           |  290 ----
 src/plang/Redirector.cpp                           |  220 ---
 src/util/Bounds.cpp                                |  316 ----
 src/util/CMakeLists.txt                            |   46 -
 src/util/FileUtils.cpp                             |  353 ----
 src/util/Utils.cpp                                 |  635 --------
 test/data/autzen/autzen-interpolate.xml            |   30 -
 test/data/autzen/autzen.jpg.aux.xml                |   78 -
 test/data/autzen/hag.py                            |   53 -
 test/data/autzen/hag.xml.in                        |   34 -
 test/data/autzen/thin-1.las                        |  Bin 0 -> 181870 bytes
 test/data/autzen/thin-2.las                        |  Bin 0 -> 181836 bytes
 test/data/bpf/bpf.xml.in                           |   19 -
 test/data/bpf/bpf2nitf.xml.in                      |   25 -
 test/data/filters/attribute.xml.in                 |   58 -
 test/data/filters/chip.xml.in                      |   18 -
 test/data/filters/chipper.xml.in                   |   22 -
 test/data/filters/colorize-multi.xml.in            |   92 --
 test/data/filters/colorize.xml.in                  |   21 -
 test/data/filters/crop_reproject.xml.in            |   35 -
 test/data/filters/crop_wkt.xml.in                  |   46 -
 test/data/filters/crop_wkt_2d.xml.in               |   27 -
 .../data/filters/crop_wkt_2d_classification.xml.in |   49 -
 test/data/filters/decimate.xml.in                  |   20 -
 test/data/filters/ferry.xml.in                     |   26 -
 test/data/filters/hexbin-info.xml.in               |   28 -
 test/data/filters/hexbin.xml.in                    |   27 -
 test/data/filters/merge.xml.in                     |   15 -
 test/data/filters/merge2.xml.in                    |   20 -
 test/data/filters/merge3.xml.in                    |   17 -
 test/data/filters/pcl/example_PMF_1.json           |   13 -
 test/data/filters/pcl/example_PMF_2.json           |   18 -
 test/data/filters/pcl/example_PassThrough_1.json   |   25 +-
 test/data/filters/pcl/example_PassThrough_2.json   |   42 +-
 test/data/filters/pcl/filter_APMF_1.json           |   19 -
 .../filters/pcl/filter_ConditionalRemoval_1.json   |   32 +-
 .../filters/pcl/filter_ConditionalRemoval_2.json   |   32 +-
 test/data/filters/pcl/filter_GridMinimum.json      |   14 +-
 .../filters/pcl/filter_NormalEstimation_1.json     |   32 +-
 .../filters/pcl/filter_NormalEstimation_2.json     |   32 +-
 test/data/filters/pcl/filter_PMF_1.json            |   19 -
 test/data/filters/pcl/filter_PMF_2.json            |   19 -
 test/data/filters/pcl/filter_PMF_3.json            |   19 -
 test/data/filters/pcl/filter_PMF_4.json            |   19 -
 test/data/filters/pcl/filter_PMF_5.json            |   19 -
 test/data/filters/pcl/filter_PMF_6.json            |   19 -
 test/data/filters/pcl/filter_PMF_7.json            |   19 -
 test/data/filters/pcl/filter_PMF_8.json            |   19 -
 test/data/filters/pcl/filter_PMF_9.json            |   19 -
 test/data/filters/pcl/filter_PassThrough_1.json    |   24 +-
 test/data/filters/pcl/filter_PassThrough_2.json    |   24 +-
 .../filters/pcl/filter_RadiusOutlierRemoval_1.json |   13 -
 .../filters/pcl/filter_RadiusOutlierRemoval_2.json |   13 -
 .../pcl/filter_StatisticalOutlierRemoval_1.json    |   13 -
 .../pcl/filter_StatisticalOutlierRemoval_2.json    |   13 -
 test/data/filters/pcl/filter_VoxelGrid.json        |   24 +-
 test/data/filters/pcl/passthrough.xml.in           |   18 -
 test/data/filters/range_classification.xml.in      |   19 -
 test/data/filters/range_z.xml.in                   |   19 -
 test/data/filters/range_z_classification.xml.in    |   19 -
 test/data/filters/reproject.xml.in                 |   40 -
 test/data/filters/sort.xml.in                      |   13 -
 test/data/filters/splitter.xml.in                  |   18 -
 test/data/filters/stats.xml.in                     |   15 -
 test/data/gdal/grid.txt                            |   34 +
 test/data/hole/crop.xml.in                         |   24 -
 test/data/icebridge/pipeline.json.in               |   13 +
 test/data/icebridge/pipeline.xml.in                |    9 -
 test/data/ilvis2/ilvis.xml                         |   13 -
 test/data/io/p2g-writer.xml.in                     |   19 -
 test/data/io/sqlite-reader.xml.in                  |   22 -
 test/data/io/sqlite-writer.xml.in                  |   61 -
 test/data/io/text-writer-csv.xml.in                |   19 -
 test/data/io/text-writer-geojson.xml.in            |   25 -
 test/data/las/1.2-empty-geotiff-vlrs.las           |  Bin 0 -> 9860 bytes
 test/data/nitf/conversion.xml.in                   |   13 -
 test/data/nitf/las2nitf.xml.in                     |   32 -
 test/data/nitf/reader.xml                          |    8 -
 test/data/nitf/write_laz.xml.in                    |   31 -
 test/data/nitf/write_options.xml.in                |   47 -
 test/data/nitf/write_test1.ntf                     |  Bin 36474 -> 0 bytes
 test/data/oracle/big-read.xml                      |   30 -
 test/data/oracle/big-write.xml                     |   69 -
 test/data/oracle/qfit-read.xml                     |   14 -
 test/data/oracle/qfit-write.xml                    |   69 -
 test/data/oracle/read-colorize.xml                 |   60 -
 test/data/oracle/read.xml                          |   32 -
 test/data/oracle/view-read.xml                     |   39 -
 test/data/oracle/write.xml                         |   76 -
 test/data/pipeline/bad/pipeline_bad01.xml          |   20 -
 test/data/pipeline/bad/pipeline_bad02.xml          |   19 -
 test/data/pipeline/bad/pipeline_bad03.xml          |   21 -
 test/data/pipeline/bad/pipeline_bad04.xml          |   19 -
 test/data/pipeline/bad/pipeline_bad05.xml          |   24 -
 test/data/pipeline/bad/pipeline_bad06.xml          |   24 -
 test/data/pipeline/bad/pipeline_bad07.xml          |   24 -
 test/data/pipeline/bad/pipeline_bad08.xml          |   19 -
 test/data/pipeline/bad/pipeline_bad09.xml          |   19 -
 test/data/pipeline/bad/pipeline_bad10.xml          |   14 -
 test/data/pipeline/drop_color.xml.in               |   21 -
 test/data/pipeline/glob.json.in                    |    7 +
 test/data/pipeline/issue1417.json.in               |    9 +
 test/data/pipeline/pipeline_interpolate.xml.in     |   49 -
 test/data/pipeline/pipeline_metadata_reader.xml.in |    8 -
 test/data/pipeline/pipeline_metadata_writer.xml.in |   15 -
 test/data/pipeline/pipeline_mississippi.xml.in     |   14 -
 .../pipeline/pipeline_mississippi_reverse.xml.in   |   14 -
 test/data/pipeline/pipeline_multioptions.xml.in    |   21 -
 test/data/pipeline/pipeline_read.xml.in            |   13 -
 test/data/pipeline/pipeline_read_notype.xml.in     |   13 -
 test/data/pipeline/pipeline_readcomments.xml.in    |   14 -
 test/data/pipeline/pipeline_write.xml.in           |   27 -
 test/data/pipeline/pipeline_write2.xml.in          |   13 -
 test/data/pipeline/pipeline_writecomments.xml.in   |   19 -
 test/data/pipeline/tags.json.in                    |   22 +
 test/data/pipeline/tindex.xml                      |   31 -
 test/data/pipeline/tindex.xml.in                   |   31 -
 test/data/plang/from-module.xml.in                 |   13 -
 test/data/plang/predicate-embed.xml.in             |   23 -
 .../plang/predicate-keep-ground-and-unclass.xml.in |   37 -
 test/data/plang/predicate-keep-last-return.xml.in  |   32 -
 .../plang/predicate-keep-specified-returns.xml.in  |   37 -
 .../programmable-update-classifications.xml.in     |   33 -
 test/data/plang/programmable-update-y-dims.xml.in  |   20 -
 test/data/qfit/conversion.xml.in                   |   28 -
 test/data/qfit/little-endian-conversion.xml.in     |   31 -
 test/data/qfit/pipeline.xml.in                     |   12 -
 test/data/qfit/reader.xml.in                       |   14 -
 test/data/sbet/pipeline.xml.in                     |    9 -
 test/data/soci/read-cloud.xml                      |   33 -
 test/data/soci/read.xml                            |   38 -
 test/data/soci/sthelens-write.xml                  |   51 -
 test/data/soci/write-utm.xml                       |   48 -
 test/data/soci/write.xml                           |   51 -
 test/data/text/badheader.txt                       |   10 +
 test/data/text/utm17_2.txt                         |    2 +-
 test/temp/SbetWriterTest.sbet                      |  Bin 0 -> 272 bytes
 test/temp/colorized.las                            |  Bin 0 -> 36687 bytes
 test/temp/crop-wkt-2d-classification.las           |  Bin 0 -> 1825 bytes
 test/temp/foo.las                                  |  Bin 0 -> 27257 bytes
 test/temp/issue895.sqlite                          |  Bin 0 -> 3072 bytes
 test/temp/meta.json                                |   91 ++
 test/temp/mylog_three.txt                          |    1 +
 test/temp/out.las                                  |  Bin 0 -> 3740744 bytes
 test/temp/out.ply                                  |  Bin 0 -> 21176 bytes
 test/temp/out2.las                                 |  Bin 0 -> 27353 bytes
 test/temp/outfile.txt                              |    3 +
 test/temp/simple.las                               |  Bin 0 -> 68425 bytes
 test/temp/spat.sqlite                              |  Bin 0 -> 5808128 bytes
 test/temp/spver.sqlite                             |    0
 .../temp-SqliteWriterTest_test_simple_las.sqlite   |  Bin 0 -> 5824512 bytes
 test/temp/temp_nitf.ntf                            |  Bin 0 -> 37941 bytes
 test/temp/test.bpf                                 |  Bin 0 -> 21756 bytes
 test/temp/test_1.bpf                               |  Bin 0 -> 16412 bytes
 test/temp/test_1.las                               |  Bin 0 -> 12297 bytes
 test/temp/test_1.ntf                               |  Bin 0 -> 2955 bytes
 test/temp/test_2.bpf                               |  Bin 0 -> 16412 bytes
 test/temp/test_2.las                               |  Bin 0 -> 12297 bytes
 test/temp/test_2.ntf                               |  Bin 0 -> 2955 bytes
 test/temp/test_3.bpf                               |  Bin 0 -> 16412 bytes
 test/temp/test_3.las                               |  Bin 0 -> 12297 bytes
 test/temp/test_3.ntf                               |  Bin 0 -> 2955 bytes
 test/temp/test_flex.bpf                            |  Bin 0 -> 47652 bytes
 test/temp/test_flex.las                            |  Bin 0 -> 36437 bytes
 test/temp/test_flex.ntf                            |  Bin 0 -> 5335 bytes
 test/temp/tmp.bpf                                  |  Bin 0 -> 47768 bytes
 test/temp/tmp.las                                  |  Bin 0 -> 82155 bytes
 test/temp/tmp.tif                                  |  Bin 0 -> 808 bytes
 test/temp/trimtest.las                             |  Bin 0 -> 3740744 bytes
 test/temp/triple.las                               |  Bin 0 -> 1497 bytes
 test/temp/utm17.txt                                |   11 +
 test/unit/BoundsTest.cpp                           |   54 +-
 test/unit/CMakeLists.txt                           |  145 +-
 test/unit/CompressionTest.cpp                      |    2 +-
 test/unit/ConfigTest.cpp                           |    4 -
 test/unit/EigenTest.cpp                            |  217 +++
 test/unit/FileUtilsTest.cpp                        |   45 +
 test/unit/KDIndexTest.cpp                          |   68 +
 test/unit/LogTest.cpp                              |    2 +-
 test/unit/OldPCLBlockTest.cpp                      |  296 ++++
 test/unit/OptionsTest.cpp                          |   43 +-
 test/unit/PipelineManagerTest.cpp                  |   33 +-
 test/unit/PointTableTest.cpp                       |    2 +-
 test/unit/ProgramArgsTest.cpp                      |  121 +-
 test/unit/SpatialReferenceTest.cpp                 |   46 +-
 test/unit/StreamingTest.cpp                        |    6 +-
 test/unit/XMLSchemaTest.cpp                        |   24 +-
 test/unit/apps/AppTest.cpp                         |   67 +
 test/unit/apps/HausdorffTest.cpp                   |   94 ++
 test/unit/apps/MergeTest.cpp                       |    2 +-
 test/unit/apps/RandomTest.cpp                      |    2 +-
 test/unit/apps/TranslateTest.cpp                   |  121 ++
 test/unit/apps/pc2pcTest.cpp                       |   19 +-
 test/unit/apps/pcpipelineTestJSON.cpp              |   75 +-
 test/unit/filters/AdditionalMergeTest.cpp          |    8 +-
 test/unit/filters/ChipperTest.cpp                  |    6 +-
 test/unit/filters/ColorizationFilterTest.cpp       |    6 +-
 test/unit/filters/ComputeRangeFilterTest.cpp       |   99 ++
 test/unit/filters/CropFilterTest.cpp               |   53 +-
 test/unit/filters/DecimationFilterTest.cpp         |    6 +-
 test/unit/filters/DividerFilterTest.cpp            |    4 +-
 test/unit/filters/FerryFilterTest.cpp              |   36 +-
 test/unit/filters/MergeTest.cpp                    |   55 -
 test/unit/filters/RandomizeFilterTest.cpp          |    4 +-
 test/unit/filters/RangeFilterTest.cpp              |    6 +-
 test/unit/filters/ReprojectionFilterTest.cpp       |   12 +-
 test/unit/filters/SortFilterTest.cpp               |   44 +-
 test/unit/filters/SplitterTest.cpp                 |   34 +-
 test/unit/filters/StatsFilterTest.cpp              |   37 +-
 test/unit/filters/TransformationFilterTest.cpp     |    5 +-
 test/unit/io/BPFTest.cpp                           |  710 ++++++++
 test/unit/io/BufferTest.cpp                        |   97 ++
 test/unit/io/FauxReaderTest.cpp                    |  239 +++
 test/unit/io/GDALReaderTest.cpp                    |  201 +++
 test/unit/io/GDALWriterTest.cpp                    |  374 +++++
 test/unit/io/Ilvis2MetadataReaderTest.cpp          |   97 ++
 test/unit/io/Ilvis2ReaderTest.cpp                  |  126 ++
 test/unit/io/Ilvis2ReaderWithMDReaderTest.cpp      |  126 ++
 test/unit/io/LasReaderTest.cpp                     |  509 ++++++
 test/unit/io/LasWriterTest.cpp                     |  756 +++++++++
 test/unit/io/OptechReaderTest.cpp                  |  141 ++
 test/unit/io/PlyReaderTest.cpp                     |  143 ++
 test/unit/io/PlyWriterTest.cpp                     |   77 +
 test/unit/io/PtsReaderTest.cpp                     |   79 +
 test/unit/io/QFITReaderTest.cpp                    |  109 ++
 test/unit/io/SbetReaderTest.cpp                    |  140 ++
 test/unit/io/SbetWriterTest.cpp                    |   96 ++
 test/unit/io/TerrasolidReaderTest.cpp              |  126 ++
 test/unit/io/TextReaderTest.cpp                    |  110 ++
 test/unit/io/TextWriterTest.cpp                    |  105 ++
 test/unit/io/bpf/BPFTest.cpp                       |  711 --------
 test/unit/io/buffer/BufferTest.cpp                 |   97 --
 test/unit/io/faux/FauxReaderTest.cpp               |  239 ---
 test/unit/io/gdal/GDALReaderTest.cpp               |  187 ---
 test/unit/io/ilvis2/Ilvis2MetadataReaderTest.cpp   |   97 --
 test/unit/io/ilvis2/Ilvis2ReaderTest.cpp           |  126 --
 .../io/ilvis2/Ilvis2ReaderWithMDReaderTest.cpp     |  127 --
 test/unit/io/las/LasReaderTest.cpp                 |  495 ------
 test/unit/io/las/LasWriterTest.cpp                 |  761 ---------
 test/unit/io/oci/oracle_array.cpp                  |  324 ----
 test/unit/io/optech/OptechReaderTest.cpp           |  146 --
 test/unit/io/ply/PlyReaderTest.cpp                 |  144 --
 test/unit/io/ply/PlyWriterTest.cpp                 |   77 -
 test/unit/io/pts/PtsReaderTest.cpp                 |   81 -
 test/unit/io/qfit/QFITReaderTest.cpp               |  109 --
 test/unit/io/sbet/SbetReaderTest.cpp               |  150 --
 test/unit/io/sbet/SbetWriterTest.cpp               |   95 --
 test/unit/io/terrasolid/TerrasolidReaderTest.cpp   |  128 --
 test/unit/io/text/TextReaderTest.cpp               |   99 --
 tools/lasdump/CMakeLists.txt                       |   19 +-
 tools/nitfwrap/CMakeLists.txt                      |   30 +-
 tools/nitfwrap/NitfWrap.cpp                        |    6 +-
 tools/nitfwrap/NitfWrap.hpp                        |    2 +-
 vendor/arbiter/CMakeLists.txt                      |   25 +-
 vendor/arbiter/arbiter.cpp                         |  400 +++--
 vendor/arbiter/arbiter.hpp                         |   65 +-
 vendor/{eigen-3.2.8 => eigen}/Eigen/Array          |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/CMakeLists.txt |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/Cholesky       |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/CholmodSupport |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/Core           |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/Dense          |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/Eigen          |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/Eigen2Support  |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/Eigenvalues    |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/Geometry       |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/Householder    |    0
 .../Eigen/IterativeLinearSolvers                   |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/Jacobi         |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/LU             |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/LeastSquares   |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/MetisSupport   |    0
 .../{eigen-3.2.8 => eigen}/Eigen/OrderingMethods   |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/PaStiXSupport  |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/PardisoSupport |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/QR             |    0
 .../{eigen-3.2.8 => eigen}/Eigen/QtAlignedMalloc   |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/SPQRSupport    |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/SVD            |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/Sparse         |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/SparseCholesky |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/SparseCore     |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/SparseLU       |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/SparseQR       |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/StdDeque       |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/StdList        |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/StdVector      |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/SuperLUSupport |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/UmfPackSupport |    0
 .../Eigen/src/CMakeLists.txt                       |    0
 .../Eigen/src/Cholesky/CMakeLists.txt              |    0
 .../Eigen/src/Cholesky/LDLT.h                      |    0
 .../Eigen/src/Cholesky/LLT.h                       |    0
 .../Eigen/src/Cholesky/LLT_MKL.h                   |    0
 .../Eigen/src/CholmodSupport/CMakeLists.txt        |    0
 .../Eigen/src/CholmodSupport/CholmodSupport.h      |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/Core/Array.h  |    0
 .../Eigen/src/Core/ArrayBase.h                     |    0
 .../Eigen/src/Core/ArrayWrapper.h                  |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/Core/Assign.h |    0
 .../Eigen/src/Core/Assign_MKL.h                    |    0
 .../Eigen/src/Core/BandMatrix.h                    |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/Core/Block.h  |    0
 .../Eigen/src/Core/BooleanRedux.h                  |    0
 .../Eigen/src/Core/CMakeLists.txt                  |    0
 .../Eigen/src/Core/CommaInitializer.h              |    0
 .../Eigen/src/Core/CoreIterators.h                 |    0
 .../Eigen/src/Core/CwiseBinaryOp.h                 |    0
 .../Eigen/src/Core/CwiseNullaryOp.h                |    0
 .../Eigen/src/Core/CwiseUnaryOp.h                  |    0
 .../Eigen/src/Core/CwiseUnaryView.h                |    0
 .../Eigen/src/Core/DenseBase.h                     |    0
 .../Eigen/src/Core/DenseCoeffsBase.h               |    0
 .../Eigen/src/Core/DenseStorage.h                  |    0
 .../Eigen/src/Core/Diagonal.h                      |    0
 .../Eigen/src/Core/DiagonalMatrix.h                |    0
 .../Eigen/src/Core/DiagonalProduct.h               |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/src/Core/Dot.h |    0
 .../Eigen/src/Core/EigenBase.h                     |    0
 .../Eigen/src/Core/Flagged.h                       |    0
 .../Eigen/src/Core/ForceAlignedAccess.h            |    0
 .../Eigen/src/Core/Functors.h                      |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/Core/Fuzzy.h  |    0
 .../Eigen/src/Core/GeneralProduct.h                |    0
 .../Eigen/src/Core/GenericPacketMath.h             |    0
 .../Eigen/src/Core/GlobalFunctions.h               |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/src/Core/IO.h  |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/src/Core/Map.h |    0
 .../Eigen/src/Core/MapBase.h                       |    0
 .../Eigen/src/Core/MathFunctions.h                 |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/Core/Matrix.h |    0
 .../Eigen/src/Core/MatrixBase.h                    |    0
 .../Eigen/src/Core/NestByValue.h                   |    0
 .../Eigen/src/Core/NoAlias.h                       |    0
 .../Eigen/src/Core/NumTraits.h                     |    0
 .../Eigen/src/Core/PermutationMatrix.h             |    0
 .../Eigen/src/Core/PlainObjectBase.h               |    0
 .../Eigen/src/Core/ProductBase.h                   |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/Core/Random.h |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/Core/Redux.h  |    0
 vendor/{eigen-3.2.8 => eigen}/Eigen/src/Core/Ref.h |    0
 .../Eigen/src/Core/Replicate.h                     |    0
 .../Eigen/src/Core/ReturnByValue.h                 |    0
 .../Eigen/src/Core/Reverse.h                       |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/Core/Select.h |    0
 .../Eigen/src/Core/SelfAdjointView.h               |    0
 .../Eigen/src/Core/SelfCwiseBinaryOp.h             |    0
 .../Eigen/src/Core/SolveTriangular.h               |    0
 .../Eigen/src/Core/StableNorm.h                    |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/Core/Stride.h |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/Core/Swap.h   |    0
 .../Eigen/src/Core/Transpose.h                     |    0
 .../Eigen/src/Core/Transpositions.h                |    0
 .../Eigen/src/Core/TriangularMatrix.h              |    0
 .../Eigen/src/Core/VectorBlock.h                   |    0
 .../Eigen/src/Core/VectorwiseOp.h                  |    0
 .../Eigen/src/Core/Visitor.h                       |    0
 .../Eigen/src/Core/arch/AltiVec/CMakeLists.txt     |    0
 .../Eigen/src/Core/arch/AltiVec/Complex.h          |    0
 .../Eigen/src/Core/arch/AltiVec/PacketMath.h       |    0
 .../Eigen/src/Core/arch/CMakeLists.txt             |    0
 .../Eigen/src/Core/arch/Default/CMakeLists.txt     |    0
 .../Eigen/src/Core/arch/Default/Settings.h         |    0
 .../Eigen/src/Core/arch/NEON/CMakeLists.txt        |    0
 .../Eigen/src/Core/arch/NEON/Complex.h             |    0
 .../Eigen/src/Core/arch/NEON/PacketMath.h          |    0
 .../Eigen/src/Core/arch/SSE/CMakeLists.txt         |    0
 .../Eigen/src/Core/arch/SSE/Complex.h              |    0
 .../Eigen/src/Core/arch/SSE/MathFunctions.h        |    0
 .../Eigen/src/Core/arch/SSE/PacketMath.h           |    0
 .../Eigen/src/Core/products/CMakeLists.txt         |    0
 .../Eigen/src/Core/products/CoeffBasedProduct.h    |    0
 .../src/Core/products/GeneralBlockPanelKernel.h    |    0
 .../Eigen/src/Core/products/GeneralMatrixMatrix.h  |    0
 .../Core/products/GeneralMatrixMatrixTriangular.h  |    0
 .../products/GeneralMatrixMatrixTriangular_MKL.h   |    0
 .../src/Core/products/GeneralMatrixMatrix_MKL.h    |    0
 .../Eigen/src/Core/products/GeneralMatrixVector.h  |    0
 .../src/Core/products/GeneralMatrixVector_MKL.h    |    0
 .../Eigen/src/Core/products/Parallelizer.h         |    0
 .../src/Core/products/SelfadjointMatrixMatrix.h    |    0
 .../Core/products/SelfadjointMatrixMatrix_MKL.h    |    0
 .../src/Core/products/SelfadjointMatrixVector.h    |    0
 .../Core/products/SelfadjointMatrixVector_MKL.h    |    0
 .../Eigen/src/Core/products/SelfadjointProduct.h   |    0
 .../src/Core/products/SelfadjointRank2Update.h     |    0
 .../src/Core/products/TriangularMatrixMatrix.h     |    0
 .../src/Core/products/TriangularMatrixMatrix_MKL.h |    0
 .../src/Core/products/TriangularMatrixVector.h     |    0
 .../src/Core/products/TriangularMatrixVector_MKL.h |    0
 .../src/Core/products/TriangularSolverMatrix.h     |    0
 .../src/Core/products/TriangularSolverMatrix_MKL.h |    0
 .../src/Core/products/TriangularSolverVector.h     |    0
 .../Eigen/src/Core/util/BlasUtil.h                 |    0
 .../Eigen/src/Core/util/CMakeLists.txt             |    0
 .../Eigen/src/Core/util/Constants.h                |    0
 .../Eigen/src/Core/util/DisableStupidWarnings.h    |    0
 .../Eigen/src/Core/util/ForwardDeclarations.h      |    0
 .../Eigen/src/Core/util/MKL_support.h              |    0
 .../Eigen/src/Core/util/Macros.h                   |    0
 .../Eigen/src/Core/util/Memory.h                   |    0
 .../Eigen/src/Core/util/Meta.h                     |    0
 .../Eigen/src/Core/util/NonMPL2.h                  |    0
 .../Eigen/src/Core/util/ReenableStupidWarnings.h   |    0
 .../Eigen/src/Core/util/StaticAssert.h             |    0
 .../Eigen/src/Core/util/XprHelper.h                |    0
 .../Eigen/src/Eigen2Support/Block.h                |    0
 .../Eigen/src/Eigen2Support/CMakeLists.txt         |    0
 .../Eigen/src/Eigen2Support/Cwise.h                |    0
 .../Eigen/src/Eigen2Support/CwiseOperators.h       |    0
 .../Eigen/src/Eigen2Support/Geometry/AlignedBox.h  |    0
 .../Eigen/src/Eigen2Support/Geometry/All.h         |    0
 .../Eigen/src/Eigen2Support/Geometry/AngleAxis.h   |    0
 .../src/Eigen2Support/Geometry/CMakeLists.txt      |    0
 .../Eigen/src/Eigen2Support/Geometry/Hyperplane.h  |    0
 .../src/Eigen2Support/Geometry/ParametrizedLine.h  |    0
 .../Eigen/src/Eigen2Support/Geometry/Quaternion.h  |    0
 .../Eigen/src/Eigen2Support/Geometry/Rotation2D.h  |    0
 .../src/Eigen2Support/Geometry/RotationBase.h      |    0
 .../Eigen/src/Eigen2Support/Geometry/Scaling.h     |    0
 .../Eigen/src/Eigen2Support/Geometry/Transform.h   |    0
 .../Eigen/src/Eigen2Support/Geometry/Translation.h |    0
 .../Eigen/src/Eigen2Support/LU.h                   |    0
 .../Eigen/src/Eigen2Support/Lazy.h                 |    0
 .../Eigen/src/Eigen2Support/LeastSquares.h         |    0
 .../Eigen/src/Eigen2Support/Macros.h               |    0
 .../Eigen/src/Eigen2Support/MathFunctions.h        |    0
 .../Eigen/src/Eigen2Support/Memory.h               |    0
 .../Eigen/src/Eigen2Support/Meta.h                 |    0
 .../Eigen/src/Eigen2Support/Minor.h                |    0
 .../Eigen/src/Eigen2Support/QR.h                   |    0
 .../Eigen/src/Eigen2Support/SVD.h                  |    0
 .../Eigen/src/Eigen2Support/TriangularSolver.h     |    0
 .../Eigen/src/Eigen2Support/VectorBlock.h          |    0
 .../Eigen/src/Eigenvalues/CMakeLists.txt           |    0
 .../Eigen/src/Eigenvalues/ComplexEigenSolver.h     |    0
 .../Eigen/src/Eigenvalues/ComplexSchur.h           |    0
 .../Eigen/src/Eigenvalues/ComplexSchur_MKL.h       |    0
 .../Eigen/src/Eigenvalues/EigenSolver.h            |    0
 .../Eigen/src/Eigenvalues/GeneralizedEigenSolver.h |    0
 .../GeneralizedSelfAdjointEigenSolver.h            |    0
 .../src/Eigenvalues/HessenbergDecomposition.h      |    0
 .../Eigen/src/Eigenvalues/MatrixBaseEigenvalues.h  |    0
 .../Eigen/src/Eigenvalues/RealQZ.h                 |    0
 .../Eigen/src/Eigenvalues/RealSchur.h              |    0
 .../Eigen/src/Eigenvalues/RealSchur_MKL.h          |    0
 .../Eigen/src/Eigenvalues/SelfAdjointEigenSolver.h |    0
 .../src/Eigenvalues/SelfAdjointEigenSolver_MKL.h   |    0
 .../Eigen/src/Eigenvalues/Tridiagonalization.h     |    0
 .../Eigen/src/Geometry/AlignedBox.h                |    0
 .../Eigen/src/Geometry/AngleAxis.h                 |    0
 .../Eigen/src/Geometry/CMakeLists.txt              |    0
 .../Eigen/src/Geometry/EulerAngles.h               |    0
 .../Eigen/src/Geometry/Homogeneous.h               |    0
 .../Eigen/src/Geometry/Hyperplane.h                |    0
 .../Eigen/src/Geometry/OrthoMethods.h              |    0
 .../Eigen/src/Geometry/ParametrizedLine.h          |    0
 .../Eigen/src/Geometry/Quaternion.h                |    0
 .../Eigen/src/Geometry/Rotation2D.h                |    0
 .../Eigen/src/Geometry/RotationBase.h              |    0
 .../Eigen/src/Geometry/Scaling.h                   |    0
 .../Eigen/src/Geometry/Transform.h                 |    0
 .../Eigen/src/Geometry/Translation.h               |    0
 .../Eigen/src/Geometry/Umeyama.h                   |    0
 .../Eigen/src/Geometry/arch/CMakeLists.txt         |    0
 .../Eigen/src/Geometry/arch/Geometry_SSE.h         |    0
 .../Eigen/src/Householder/BlockHouseholder.h       |    0
 .../Eigen/src/Householder/CMakeLists.txt           |    0
 .../Eigen/src/Householder/Householder.h            |    0
 .../Eigen/src/Householder/HouseholderSequence.h    |    0
 .../IterativeLinearSolvers/BasicPreconditioners.h  |    0
 .../Eigen/src/IterativeLinearSolvers/BiCGSTAB.h    |    0
 .../src/IterativeLinearSolvers/CMakeLists.txt      |    0
 .../src/IterativeLinearSolvers/ConjugateGradient.h |    0
 .../src/IterativeLinearSolvers/IncompleteLUT.h     |    0
 .../IterativeLinearSolvers/IterativeSolverBase.h   |    0
 .../Eigen/src/Jacobi/CMakeLists.txt                |    0
 .../Eigen/src/Jacobi/Jacobi.h                      |    0
 .../Eigen/src/LU/CMakeLists.txt                    |    0
 .../Eigen/src/LU/Determinant.h                     |    0
 .../Eigen/src/LU/FullPivLU.h                       |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/LU/Inverse.h  |    0
 .../Eigen/src/LU/PartialPivLU.h                    |    0
 .../Eigen/src/LU/PartialPivLU_MKL.h                |    0
 .../Eigen/src/LU/arch/CMakeLists.txt               |    0
 .../Eigen/src/LU/arch/Inverse_SSE.h                |    0
 .../Eigen/src/MetisSupport/CMakeLists.txt          |    0
 .../Eigen/src/MetisSupport/MetisSupport.h          |    0
 .../Eigen/src/OrderingMethods/Amd.h                |    0
 .../Eigen/src/OrderingMethods/CMakeLists.txt       |    0
 .../Eigen/src/OrderingMethods/Eigen_Colamd.h       |    0
 .../Eigen/src/OrderingMethods/Ordering.h           |    0
 .../Eigen/src/PaStiXSupport/CMakeLists.txt         |    0
 .../Eigen/src/PaStiXSupport/PaStiXSupport.h        |    0
 .../Eigen/src/PardisoSupport/CMakeLists.txt        |    0
 .../Eigen/src/PardisoSupport/PardisoSupport.h      |    0
 .../Eigen/src/QR/CMakeLists.txt                    |    0
 .../Eigen/src/QR/ColPivHouseholderQR.h             |    0
 .../Eigen/src/QR/ColPivHouseholderQR_MKL.h         |    0
 .../Eigen/src/QR/FullPivHouseholderQR.h            |    0
 .../Eigen/src/QR/HouseholderQR.h                   |    0
 .../Eigen/src/QR/HouseholderQR_MKL.h               |    0
 .../Eigen/src/SPQRSupport/CMakeLists.txt           |    0
 .../Eigen/src/SPQRSupport/SuiteSparseQRSupport.h   |    0
 .../Eigen/src/SVD/CMakeLists.txt                   |    0
 .../Eigen/src/SVD/JacobiSVD.h                      |    0
 .../Eigen/src/SVD/JacobiSVD_MKL.h                  |    0
 .../Eigen/src/SVD/UpperBidiagonalization.h         |    0
 .../Eigen/src/SparseCholesky/CMakeLists.txt        |    0
 .../Eigen/src/SparseCholesky/SimplicialCholesky.h  |    0
 .../src/SparseCholesky/SimplicialCholesky_impl.h   |    0
 .../Eigen/src/SparseCore/AmbiVector.h              |    0
 .../Eigen/src/SparseCore/CMakeLists.txt            |    0
 .../Eigen/src/SparseCore/CompressedStorage.h       |    0
 .../SparseCore/ConservativeSparseSparseProduct.h   |    0
 .../Eigen/src/SparseCore/MappedSparseMatrix.h      |    0
 .../Eigen/src/SparseCore/SparseBlock.h             |    0
 .../Eigen/src/SparseCore/SparseColEtree.h          |    0
 .../Eigen/src/SparseCore/SparseCwiseBinaryOp.h     |    0
 .../Eigen/src/SparseCore/SparseCwiseUnaryOp.h      |    0
 .../Eigen/src/SparseCore/SparseDenseProduct.h      |    0
 .../Eigen/src/SparseCore/SparseDiagonalProduct.h   |    0
 .../Eigen/src/SparseCore/SparseDot.h               |    0
 .../Eigen/src/SparseCore/SparseFuzzy.h             |    0
 .../Eigen/src/SparseCore/SparseMatrix.h            |    0
 .../Eigen/src/SparseCore/SparseMatrixBase.h        |    0
 .../Eigen/src/SparseCore/SparsePermutation.h       |    0
 .../Eigen/src/SparseCore/SparseProduct.h           |    0
 .../Eigen/src/SparseCore/SparseRedux.h             |    0
 .../Eigen/src/SparseCore/SparseSelfAdjointView.h   |    0
 .../SparseCore/SparseSparseProductWithPruning.h    |    0
 .../Eigen/src/SparseCore/SparseTranspose.h         |    0
 .../Eigen/src/SparseCore/SparseTriangularView.h    |    0
 .../Eigen/src/SparseCore/SparseUtil.h              |    0
 .../Eigen/src/SparseCore/SparseVector.h            |    0
 .../Eigen/src/SparseCore/SparseView.h              |    0
 .../Eigen/src/SparseCore/TriangularSolver.h        |    0
 .../Eigen/src/SparseLU/CMakeLists.txt              |    0
 .../Eigen/src/SparseLU/SparseLU.h                  |    0
 .../Eigen/src/SparseLU/SparseLUImpl.h              |    0
 .../Eigen/src/SparseLU/SparseLU_Memory.h           |    0
 .../Eigen/src/SparseLU/SparseLU_Structs.h          |    0
 .../Eigen/src/SparseLU/SparseLU_SupernodalMatrix.h |    0
 .../Eigen/src/SparseLU/SparseLU_Utils.h            |    0
 .../Eigen/src/SparseLU/SparseLU_column_bmod.h      |    0
 .../Eigen/src/SparseLU/SparseLU_column_dfs.h       |    0
 .../Eigen/src/SparseLU/SparseLU_copy_to_ucol.h     |    0
 .../Eigen/src/SparseLU/SparseLU_gemm_kernel.h      |    0
 .../Eigen/src/SparseLU/SparseLU_heap_relax_snode.h |    0
 .../Eigen/src/SparseLU/SparseLU_kernel_bmod.h      |    0
 .../Eigen/src/SparseLU/SparseLU_panel_bmod.h       |    0
 .../Eigen/src/SparseLU/SparseLU_panel_dfs.h        |    0
 .../Eigen/src/SparseLU/SparseLU_pivotL.h           |    0
 .../Eigen/src/SparseLU/SparseLU_pruneL.h           |    0
 .../Eigen/src/SparseLU/SparseLU_relax_snode.h      |    0
 .../Eigen/src/SparseQR/CMakeLists.txt              |    0
 .../Eigen/src/SparseQR/SparseQR.h                  |    0
 .../Eigen/src/StlSupport/CMakeLists.txt            |    0
 .../Eigen/src/StlSupport/StdDeque.h                |    0
 .../Eigen/src/StlSupport/StdList.h                 |    0
 .../Eigen/src/StlSupport/StdVector.h               |    0
 .../Eigen/src/StlSupport/details.h                 |    0
 .../Eigen/src/SuperLUSupport/CMakeLists.txt        |    0
 .../Eigen/src/SuperLUSupport/SuperLUSupport.h      |    0
 .../Eigen/src/UmfPackSupport/CMakeLists.txt        |    0
 .../Eigen/src/UmfPackSupport/UmfPackSupport.h      |    0
 .../Eigen/src/misc/CMakeLists.txt                  |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/misc/Image.h  |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/misc/Kernel.h |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/misc/Solve.h  |    0
 .../Eigen/src/misc/SparseSolve.h                   |    0
 .../{eigen-3.2.8 => eigen}/Eigen/src/misc/blas.h   |    0
 .../Eigen/src/plugins/ArrayCwiseBinaryOps.h        |    0
 .../Eigen/src/plugins/ArrayCwiseUnaryOps.h         |    0
 .../Eigen/src/plugins/BlockMethods.h               |    0
 .../Eigen/src/plugins/CMakeLists.txt               |    0
 .../Eigen/src/plugins/CommonCwiseBinaryOps.h       |    0
 .../Eigen/src/plugins/CommonCwiseUnaryOps.h        |    0
 .../Eigen/src/plugins/MatrixCwiseBinaryOps.h       |    0
 .../Eigen/src/plugins/MatrixCwiseUnaryOps.h        |    0
 vendor/{gtest-1.7.0 => gtest}/CHANGES              |    0
 vendor/{gtest-1.7.0 => gtest}/CMakeLists.txt       |    0
 vendor/{gtest-1.7.0 => gtest}/CONTRIBUTORS         |    0
 vendor/{gtest-1.7.0 => gtest}/LICENSE              |    0
 vendor/{gtest-1.7.0 => gtest}/README               |    0
 vendor/{gtest-1.7.0 => gtest}/aclocal.m4           |    0
 .../{gtest-1.7.0 => gtest}/build-aux/config.guess  |    0
 .../{gtest-1.7.0 => gtest}/build-aux/config.h.in   |    0
 vendor/{gtest-1.7.0 => gtest}/build-aux/config.sub |    0
 vendor/{gtest-1.7.0 => gtest}/build-aux/depcomp    |    0
 vendor/{gtest-1.7.0 => gtest}/build-aux/install-sh |    0
 vendor/{gtest-1.7.0 => gtest}/build-aux/ltmain.sh  |    0
 vendor/{gtest-1.7.0 => gtest}/build-aux/missing    |    0
 .../cmake/internal_utils.cmake                     |    0
 .../{gtest-1.7.0 => gtest}/codegear/gtest.cbproj   |    0
 .../codegear/gtest.groupproj                       |    0
 .../{gtest-1.7.0 => gtest}/codegear/gtest_all.cc   |    0
 .../{gtest-1.7.0 => gtest}/codegear/gtest_link.cc  |    0
 .../codegear/gtest_main.cbproj                     |    0
 .../codegear/gtest_unittest.cbproj                 |    0
 vendor/{gtest-1.7.0 => gtest}/configure            |    0
 vendor/{gtest-1.7.0 => gtest}/configure.ac         |    0
 .../fused-src/gtest/gtest-all.cc                   |    0
 .../{gtest-1.7.0 => gtest}/fused-src/gtest/gtest.h |    0
 .../fused-src/gtest/gtest_main.cc                  |    0
 .../include/gtest/gtest-death-test.h               |    0
 .../include/gtest/gtest-message.h                  |    0
 .../include/gtest/gtest-param-test.h               |    0
 .../include/gtest/gtest-param-test.h.pump          |    0
 .../include/gtest/gtest-printers.h                 |    0
 .../include/gtest/gtest-spi.h                      |    0
 .../include/gtest/gtest-test-part.h                |    0
 .../include/gtest/gtest-typed-test.h               |    0
 .../{gtest-1.7.0 => gtest}/include/gtest/gtest.h   |    0
 .../include/gtest/gtest_pred_impl.h                |    0
 .../include/gtest/gtest_prod.h                     |    0
 .../gtest/internal/gtest-death-test-internal.h     |    0
 .../include/gtest/internal/gtest-filepath.h        |    0
 .../include/gtest/internal/gtest-internal.h        |    0
 .../include/gtest/internal/gtest-linked_ptr.h      |    0
 .../gtest/internal/gtest-param-util-generated.h    |    0
 .../internal/gtest-param-util-generated.h.pump     |    0
 .../include/gtest/internal/gtest-param-util.h      |    0
 .../include/gtest/internal/gtest-port.h            |    0
 .../include/gtest/internal/gtest-string.h          |    0
 .../include/gtest/internal/gtest-tuple.h           |    0
 .../include/gtest/internal/gtest-tuple.h.pump      |    0
 .../include/gtest/internal/gtest-type-util.h       |    0
 .../include/gtest/internal/gtest-type-util.h.pump  |    0
 vendor/{gtest-1.7.0 => gtest}/m4/acx_pthread.m4    |    0
 vendor/{gtest-1.7.0 => gtest}/m4/gtest.m4          |    0
 vendor/{gtest-1.7.0 => gtest}/m4/libtool.m4        |    0
 vendor/{gtest-1.7.0 => gtest}/m4/ltoptions.m4      |    0
 vendor/{gtest-1.7.0 => gtest}/m4/ltsugar.m4        |    0
 vendor/{gtest-1.7.0 => gtest}/m4/ltversion.m4      |    0
 vendor/{gtest-1.7.0 => gtest}/m4/lt~obsolete.m4    |    0
 vendor/{gtest-1.7.0 => gtest}/msvc/gtest-md.vcproj |    0
 vendor/{gtest-1.7.0 => gtest}/msvc/gtest.vcproj    |    0
 .../msvc/gtest_main-md.vcproj                      |    0
 .../{gtest-1.7.0 => gtest}/msvc/gtest_main.vcproj  |    0
 .../msvc/gtest_prod_test-md.vcproj                 |    0
 .../msvc/gtest_prod_test.vcproj                    |    0
 .../msvc/gtest_unittest-md.vcproj                  |    0
 .../msvc/gtest_unittest.vcproj                     |    0
 .../{gtest-1.7.0 => gtest}/samples/prime_tables.h  |    0
 vendor/{gtest-1.7.0 => gtest}/samples/sample1.cc   |    0
 vendor/{gtest-1.7.0 => gtest}/samples/sample1.h    |    0
 .../samples/sample10_unittest.cc                   |    0
 .../samples/sample1_unittest.cc                    |    0
 vendor/{gtest-1.7.0 => gtest}/samples/sample2.cc   |    0
 vendor/{gtest-1.7.0 => gtest}/samples/sample2.h    |    0
 .../samples/sample2_unittest.cc                    |    0
 .../{gtest-1.7.0 => gtest}/samples/sample3-inl.h   |    0
 .../samples/sample3_unittest.cc                    |    0
 vendor/{gtest-1.7.0 => gtest}/samples/sample4.cc   |    0
 vendor/{gtest-1.7.0 => gtest}/samples/sample4.h    |    0
 .../samples/sample4_unittest.cc                    |    0
 .../samples/sample5_unittest.cc                    |    0
 .../samples/sample6_unittest.cc                    |    0
 .../samples/sample7_unittest.cc                    |    0
 .../samples/sample8_unittest.cc                    |    0
 .../samples/sample9_unittest.cc                    |    0
 .../scripts/fuse_gtest_files.py                    |    0
 .../scripts/gen_gtest_pred_impl.py                 |    0
 .../{gtest-1.7.0 => gtest}/scripts/gtest-config.in |    0
 vendor/{gtest-1.7.0 => gtest}/scripts/pump.py      |    0
 vendor/{gtest-1.7.0 => gtest}/src/gtest-all.cc     |    0
 .../{gtest-1.7.0 => gtest}/src/gtest-death-test.cc |    0
 .../{gtest-1.7.0 => gtest}/src/gtest-filepath.cc   |    0
 .../src/gtest-internal-inl.h                       |    0
 vendor/{gtest-1.7.0 => gtest}/src/gtest-port.cc    |    0
 .../{gtest-1.7.0 => gtest}/src/gtest-printers.cc   |    0
 .../{gtest-1.7.0 => gtest}/src/gtest-test-part.cc  |    0
 .../{gtest-1.7.0 => gtest}/src/gtest-typed-test.cc |    0
 vendor/{gtest-1.7.0 => gtest}/src/gtest.cc         |    0
 vendor/{gtest-1.7.0 => gtest}/src/gtest_main.cc    |    0
 .../test/gtest-death-test_ex_test.cc               |    0
 .../test/gtest-death-test_test.cc                  |    0
 .../test/gtest-filepath_test.cc                    |    0
 .../test/gtest-linked_ptr_test.cc                  |    0
 .../test/gtest-listener_test.cc                    |    0
 .../test/gtest-message_test.cc                     |    0
 .../test/gtest-options_test.cc                     |    0
 .../test/gtest-param-test2_test.cc                 |    0
 .../test/gtest-param-test_test.cc                  |    0
 .../test/gtest-param-test_test.h                   |    0
 .../{gtest-1.7.0 => gtest}/test/gtest-port_test.cc |    0
 .../test/gtest-printers_test.cc                    |    0
 .../test/gtest-test-part_test.cc                   |    0
 .../test/gtest-tuple_test.cc                       |    0
 .../test/gtest-typed-test2_test.cc                 |    0
 .../test/gtest-typed-test_test.cc                  |    0
 .../test/gtest-typed-test_test.h                   |    0
 .../test/gtest-unittest-api_test.cc                |    0
 .../{gtest-1.7.0 => gtest}/test/gtest_all_test.cc  |    0
 .../test/gtest_break_on_failure_unittest.py        |    0
 .../test/gtest_break_on_failure_unittest_.cc       |    0
 .../test/gtest_catch_exceptions_test.py            |    0
 .../test/gtest_catch_exceptions_test_.cc           |    0
 .../test/gtest_color_test.py                       |    0
 .../test/gtest_color_test_.cc                      |    0
 .../test/gtest_env_var_test.py                     |    0
 .../test/gtest_env_var_test_.cc                    |    0
 .../test/gtest_environment_test.cc                 |    0
 .../test/gtest_filter_unittest.py                  |    0
 .../test/gtest_filter_unittest_.cc                 |    0
 .../{gtest-1.7.0 => gtest}/test/gtest_help_test.py |    0
 .../test/gtest_help_test_.cc                       |    0
 .../test/gtest_list_tests_unittest.py              |    0
 .../test/gtest_list_tests_unittest_.cc             |    0
 .../test/gtest_main_unittest.cc                    |    0
 .../test/gtest_no_test_unittest.cc                 |    0
 .../test/gtest_output_test.py                      |    0
 .../test/gtest_output_test_.cc                     |    0
 .../test/gtest_output_test_golden_lin.txt          |    0
 .../test/gtest_pred_impl_unittest.cc               |    0
 .../test/gtest_premature_exit_test.cc              |    0
 .../{gtest-1.7.0 => gtest}/test/gtest_prod_test.cc |    0
 .../test/gtest_repeat_test.cc                      |    0
 .../test/gtest_shuffle_test.py                     |    0
 .../test/gtest_shuffle_test_.cc                    |    0
 .../test/gtest_sole_header_test.cc                 |    0
 .../test/gtest_stress_test.cc                      |    0
 .../test/gtest_test_utils.py                       |    0
 .../test/gtest_throw_on_failure_ex_test.cc         |    0
 .../test/gtest_throw_on_failure_test.py            |    0
 .../test/gtest_throw_on_failure_test_.cc           |    0
 .../test/gtest_uninitialized_test.py               |    0
 .../test/gtest_uninitialized_test_.cc              |    0
 .../{gtest-1.7.0 => gtest}/test/gtest_unittest.cc  |    0
 .../test/gtest_xml_outfile1_test_.cc               |    0
 .../test/gtest_xml_outfile2_test_.cc               |    0
 .../test/gtest_xml_outfiles_test.py                |    0
 .../test/gtest_xml_output_unittest.py              |    0
 .../test/gtest_xml_output_unittest_.cc             |    0
 .../test/gtest_xml_test_utils.py                   |    0
 vendor/{gtest-1.7.0 => gtest}/test/production.cc   |    0
 vendor/{gtest-1.7.0 => gtest}/test/production.h    |    0
 .../xcode/Config/DebugProject.xcconfig             |    0
 .../xcode/Config/FrameworkTarget.xcconfig          |    0
 .../xcode/Config/General.xcconfig                  |    0
 .../xcode/Config/ReleaseProject.xcconfig           |    0
 .../xcode/Config/StaticLibraryTarget.xcconfig      |    0
 .../xcode/Config/TestTarget.xcconfig               |    0
 .../xcode/Resources/Info.plist                     |    0
 .../xcode/Samples/FrameworkSample/Info.plist       |    0
 .../xcode/Samples/FrameworkSample/runtests.sh      |    0
 .../xcode/Samples/FrameworkSample/widget.cc        |    0
 .../xcode/Samples/FrameworkSample/widget.h         |    0
 .../xcode/Samples/FrameworkSample/widget_test.cc   |    0
 .../xcode/Scripts/runtests.sh                      |    0
 .../xcode/Scripts/versiongenerate.py               |    0
 vendor/jsoncpp-1.6.2/dist/CMakeLists.txt           |   26 -
 vendor/jsoncpp/dist/CMakeLists.txt                 |   34 +
 .../dist/json/forwards.h                           |    0
 vendor/{jsoncpp-1.6.2 => jsoncpp}/dist/json/json.h |    0
 vendor/{jsoncpp-1.6.2 => jsoncpp}/dist/jsoncpp.cpp |    0
 .../{nanoflann-1.1.8 => nanoflann}/nanoflann.hpp   |    0
 vendor/pdalboost/CMakeLists.txt                    |    5 +-
 .../boost/format/detail/compat_workarounds.hpp     |    2 +-
 .../boost/format/detail/config_macros.hpp          |    2 +-
 .../boost/format/detail/workarounds_gcc-2_95.hpp   |    2 +-
 vendor/pdalboost/boost/mpl/for_each.hpp            |    2 +-
 vendor/{rply-1.1.4 => rply}/LICENSE                |    0
 vendor/{rply-1.1.4 => rply}/etc/convert.c          |    0
 vendor/{rply-1.1.4 => rply}/etc/dump.c             |    0
 vendor/{rply-1.1.4 => rply}/etc/input.ply          |    0
 vendor/{rply-1.1.4 => rply}/etc/sconvert.c         |    0
 vendor/{rply-1.1.4 => rply}/manual/manual.html     |    0
 vendor/{rply-1.1.4 => rply}/manual/reference.css   |    0
 vendor/{rply-1.1.4 => rply}/manual/rply.png        |  Bin
 vendor/{rply-1.1.4 => rply}/rply.c                 |    0
 vendor/{rply-1.1.4 => rply}/rply.h                 |    0
 vendor/{rply-1.1.4 => rply}/rplyfile.h             |    0
 1547 files changed, 54869 insertions(+), 51255 deletions(-)

diff --git a/.gitignore b/.gitignore
index d057d6d..2b1cc7c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,7 @@ CTestTestfile.cmake
 Testing/
 test/temp/
 apps/pdal-config
+apps/pdal-config.bat
 apps/pdal.pc
 src/CMakeScripts/
 apps/CMakeScripts/
@@ -75,7 +76,7 @@ doc/build
 bin/
 lib/
 test/unit/CMakeScripts/
-src/gitsha.cpp
+pdal/gitsha.cpp
 .vagrant/
 test/unit/TestConfig.hpp
 *.pyc
diff --git a/.travis.yml b/.travis.yml
index d116c0b..c62cf12 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,7 +10,7 @@ services:
     docker
 
 compiler:
-  - clang
+  - gcc
 
 env:
   - PDAL_OPTIONAL_COMPONENTS=all
@@ -24,7 +24,7 @@ script:
 
 after_success:
   - echo "secure travis:" "$TRAVIS_SECURE_ENV_VARS"
-  - sh -c 'if test "$TRAVIS_SECURE_ENV_VARS" = "true" -a "$TRAVIS_BRANCH" = "1.3-maintenance" -a "$PDAL_OPTIONAL_COMPONENTS" = "all"; then echo "publish website"; ./scripts/ci/build_docs.sh; ./scripts/ci/add_deploy_key.sh; ./scripts/ci/deploy_website.sh $TRAVIS_BUILD_DIR/doc/build /tmp; fi'
+  - sh -c 'if test "$TRAVIS_SECURE_ENV_VARS" = "true" -a "$TRAVIS_BRANCH" = "1.4-maintenance" -a "$PDAL_OPTIONAL_COMPONENTS" = "all"; then echo "publish website"; ./scripts/ci/build_docs.sh; ./scripts/ci/add_deploy_key.sh; ./scripts/ci/deploy_website.sh $TRAVIS_BUILD_DIR/doc/build /tmp; fi'
 
 notifications:
   on_success: always
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6cd11e4..63bc7ef 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@
 #
 # (based originally on the libLAS files copyright Mateusz Loskot)
 
-cmake_minimum_required(VERSION 2.8.11)
+cmake_minimum_required(VERSION 2.8.12)
 
 project(PDAL CXX C)
 string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWER)
@@ -28,14 +28,14 @@ mark_as_advanced(CMAKE_VERBOSE_MAKEFILE)
 
 # the next line is the ONLY place in the entire pdal system where
 # the version info is hard-coded
-set(PDAL_VERSION_STRING "1.3.0" CACHE STRING "PDAL version" FORCE)
+set(PDAL_VERSION_STRING "1.4.0" CACHE STRING "PDAL version" FORCE)
 
 DISSECT_VERSION()
 GET_OS_INFO()
 SET_INSTALL_DIRS()
 
-set(PDAL_API_VERSION "3")
-set(PDAL_BUILD_VERSION "4.0.0")
+set(PDAL_API_VERSION "4")
+set(PDAL_BUILD_VERSION "5.0.0")
 
 # Name of C++ library
 
@@ -46,7 +46,7 @@ set(PDAL_BUILD_VERSION "4.0.0")
 # in turn links libpdal_base.so and libpdal_util.so
 #
 # On OSX we reexport the symbols from libpdal_util.dylib into libpdalcpp.dylib
-# See src/CMakeLists.txt for the rest of the magic.
+# See below for the rest of the magic.
 #
 if (APPLE OR WIN32)
     set(PDAL_LIB_NAME pdalcpp)
@@ -58,7 +58,7 @@ endif()
 set(PDAL_UTIL_LIB_NAME pdal_util)
 set(PDAL_PLANG_LIB_NAME pdal_plang)
 set(PDAL_BOOST_LIB_NAME pdal_boost)
-set(PDAL_ARBITER_LIB_NAME pdal_arbiter)
+#set(PDAL_ARBITER_LIB_NAME pdal_arbiter)
 
 set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON)
 
@@ -91,8 +91,6 @@ else(WIN32)
   set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PDAL_OUTPUT_LIB_DIR}")
 endif(WIN32)
 
-set(PDAL_HEADERS_DIR "${PROJECT_SOURCE_DIR}/include/pdal")
-
 # Choose package components
 
 include(${PDAL_CMAKE_DIR}/options.cmake)
@@ -112,15 +110,18 @@ include(${PDAL_CMAKE_DIR}/geos.cmake)
 include(${PDAL_CMAKE_DIR}/geotiff.cmake)  # Optional (not really)
 include(${PDAL_CMAKE_DIR}/lazperf.cmake)  # Optional
 include(${PDAL_CMAKE_DIR}/laszip.cmake)  # Optional
+include(${PDAL_CMAKE_DIR}/pdaljni.cmake)  # Optional
 include(${PDAL_CMAKE_DIR}/threads.cmake)
 include(${PDAL_CMAKE_DIR}/zlib.cmake)
 include(${PDAL_CMAKE_DIR}/test.cmake)
 include(${PDAL_CMAKE_DIR}/ctest.cmake)
 include(${PDAL_CMAKE_DIR}/curl.cmake)
-include(${PDAL_CMAKE_DIR}/json.cmake)
-if (BUILD_PLUGIN_PGPOINTCLOUD OR BUILD_PLUGIN_OCI OR BUILD_PLUGIN_SQLITE)
-    include(${PDAL_CMAKE_DIR}/libxml2.cmake)
+if (CURL_FOUND)
+    set(PDAL_ARBITER_LIB_NAME pdal_arbiter)
 endif()
+include(${PDAL_CMAKE_DIR}/rply.cmake)
+include(${PDAL_CMAKE_DIR}/json.cmake)
+include(${PDAL_CMAKE_DIR}/libxml2.cmake)
 include(${PDAL_CMAKE_DIR}/dimension.cmake)
 
 #------------------------------------------------------------------------------
@@ -133,7 +134,7 @@ get_git_head_revision(GIT_REFSPEC GIT_SHA1)
 
 configure_file(
   "${PROJECT_SOURCE_DIR}/gitsha.cpp.in"
-  "${PROJECT_SOURCE_DIR}/src/gitsha.cpp")
+  "${PROJECT_SOURCE_DIR}/pdal/gitsha.cpp")
 
 # needs to come before configuration of pdal_defines
 if(APPLE)
@@ -146,15 +147,6 @@ endif()
 set(pdal_defines_h_in "${CMAKE_CURRENT_SOURCE_DIR}/pdal_defines.h.in")
 set(pdal_defines_h "${CMAKE_CURRENT_BINARY_DIR}/include/pdal/pdal_defines.h")
 configure_file(${pdal_defines_h_in} ${pdal_defines_h})
-PDAL_ADD_INCLUDES("" "" ${pdal_defines_h})
-include_directories("${CMAKE_CURRENT_BINARY_DIR}/include")
-
-include_directories(${PDAL_VENDOR_DIR}/arbiter)
-include_directories(${PDAL_VENDOR_DIR}/eigen-3.2.8)
-include_directories(${PDAL_JSONCPP_INCLUDE_DIR})
-
-include_directories(${PDAL_VENDOR_DIR}/nanoflann-1.1.8)
-include_directories(${PDAL_VENDOR_DIR}/rply-1.1.4)
 
 #------------------------------------------------------------------------------
 # subdirectory controls
@@ -169,7 +161,7 @@ endif()
 
 add_subdirectory(plugins)
 
-include_directories(vendor/pdalboost)
+#include_directories(vendor/pdalboost)
 if (WITH_TESTS)
     include (${PDAL_CMAKE_DIR}/gtest.cmake)
     add_subdirectory(test)
@@ -177,41 +169,172 @@ endif()
 add_subdirectory(dimbuilder)
 add_subdirectory(vendor/pdalboost)
 add_subdirectory(vendor/arbiter)
+
 if (NOT PDAL_HAVE_JSONCPP)
-    add_subdirectory(vendor/jsoncpp-1.6.2/dist)
+    add_subdirectory(vendor/jsoncpp/dist)
 endif()
-add_subdirectory(src/util)
-add_subdirectory(io)
-add_subdirectory(filters)
-add_subdirectory(kernels)
-add_subdirectory(src)
+add_subdirectory(pdal/util)
 add_subdirectory(tools)
 if (BUILD_PLUGIN_PYTHON)
     set(PYTHON_VERSION_STRING "something" CACHE STRING "Python version" FORCE)
-    add_subdirectory(src/plang)
+    add_subdirectory(pdal/plang)
 endif()
-if (WITH_APPS)
-    add_subdirectory(apps)
+add_subdirectory(apps)
+
+#
+# On OSX we reexport the symbols in libpdal_util.dylib into libpdalcpp.dylib
+# so that users only need link libpdalcpp.
+#
+if (APPLE)
+    set(PDAL_REEXPORT "-Wl,-reexport_library,$<TARGET_FILE:${PDAL_UTIL_LIB_NAME}>")
+    #
+    # This allows the rpath reference for the reexported library (above) to
+    # be found.
+    #
+    set(PDAL_LIBDIR "-L$<TARGET_FILE_DIR:${PDAL_UTIL_LIB_NAME}>")
 endif()
 
+file(GLOB BASE_SRCS
+    ${PDAL_FILTERS_DIR}/*.cpp
+    ${PDAL_IO_DIR}/*.cpp
+    ${PDAL_KERNELS_DIR}/*.cpp
+    ${PDAL_SRC_DIR}/*.cpp)
+file(GLOB_RECURSE PRIVATE_SRCS
+    ${PDAL_FILTERS_DIR}/private/*.cpp
+    ${PDAL_IO_DIR}/private/*.cpp
+    ${PDAL_KERNELS_DIR}/private/*.cpp
+    ${PDAL_SRC_DIR}/private/*.cpp)
+list(APPEND SRCS ${BASE_SRCS} ${PRIVATE_SRCS})
+if (NOT PDAL_HAVE_LIBXML2)
+    file(GLOB XML_SRCS
+        io/Ilvis2MetadataReader.cpp
+        io/Ilvis2Metadata.cpp
+        pdal/DbWriter.cpp
+        pdal/DbReader.cpp
+        pdal/XMLSchema.cpp)
+    list(REMOVE_ITEM SRCS ${XML_SRCS})
+endif()
+PDAL_ADD_LIBRARY(${PDAL_BASE_LIB_NAME} ${SRCS} ${RPLY_SRCS})
+
+#
+# Interface include directories allow downstream project to get the directory
+# without specification.
+#
+target_include_directories(${PDAL_BASE_LIB_NAME}
+    PRIVATE
+        ${ROOT_DIR}
+        ${PROJECT_BINARY_DIR}/include
+        ${PDAL_VENDOR_DIR}
+        ${PDAL_VENDOR_DIR}/eigen
+        ${PDAL_VENDOR_DIR}/pdalboost
+        ${PDAL_JSONCPP_INCLUDE_DIR}
+        ${LIBXML2_INCLUDE_DIR}
+    INTERFACE
+        ${GDAL_INCLUDE_DIR}
+)
+target_link_libraries(${PDAL_BASE_LIB_NAME}
+    PUBLIC
+        ${CMAKE_THREAD_LIBS_INIT}
+        ${GDAL_LIBRARY}
+        ${GEOS_LIBRARY}
+        ${GEOTIFF_LIBRARY}
+        ${LASZIP_LIBRARY}
+        ${LIBXML2_LIBRARIES}
+        ${ZLIB_LIBRARIES}
+        ${CURL_LIBRARIES}
+        ${WINSOCK_LIBRARY}
+    PRIVATE
+        ${PDAL_REEXPORT}
+        ${PDAL_UTIL_LIB_NAME}
+        ${PDAL_ARBITER_LIB_NAME}
+    ${JSON_CPP_LINK_TYPE}
+        ${PDAL_JSONCPP_LIB_NAME}
+    INTERFACE
+        ${PDAL_LIBDIR}
+)
+target_compile_definitions(${PDAL_BASE_LIB_NAME}
+    PRIVATE
+        ${LASZIP_DEFINES}
+)
+set_target_properties(${PDAL_BASE_LIB_NAME} PROPERTIES
+    VERSION ${PDAL_BUILD_VERSION}
+    SOVERSION ${PDAL_API_VERSION}
+    CLEAN_DIRECT_OUTPUT 1)
+#
+# On Linux, we install a linker script as libpdalcpp.so.  That file
+# specifies linking in libpdal_base.so and libpdal_util.so.  This allows
+# users to link a single library, libpdalcpp
+#
+if (UNIX AND NOT APPLE)
+    set(LIBNAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PDAL_LIB_NAME})
+    install(FILES ${LIBNAME} DESTINATION ${PDAL_LIB_INSTALL_DIR}
+        RENAME ${LIBNAME}${CMAKE_SHARED_LIBRARY_SUFFIX})
+endif()
+
+#
+# Installation
+#
+install(DIRECTORY ${PDAL_INCLUDE_DIR}/pdal
+    DESTINATION include
+    FILES_MATCHING PATTERN "*.hpp"
+    PATTERN "gitsha.h"
+    PATTERN "pdal/private" EXCLUDE
+)
+install(DIRECTORY ${PDAL_KERNELS_DIR}
+    DESTINATION include/pdal
+    FILES_MATCHING PATTERN "*.hpp"
+    PATTERN "private" EXCLUDE
+)
+install(DIRECTORY ${PDAL_IO_DIR}
+    DESTINATION include/pdal
+    FILES_MATCHING PATTERN "*.hpp"
+    PATTERN "private" EXCLUDE
+)
+install(DIRECTORY ${PDAL_FILTERS_DIR}
+    DESTINATION include/pdal
+    FILES_MATCHING PATTERN "*.hpp"
+    PATTERN "private" EXCLUDE
+)
+
+install(FILES ${DIMENSION_OUTFILE} ${pdal_defines_h}
+  DESTINATION include/pdal
+)
+
+#
+# CPACK
+#
 include (${PDAL_CMAKE_DIR}/cpack.cmake)
 
 add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source)
 
-export(TARGETS ${PDAL_BASE_LIB_NAME}
-    ${PDAL_UTIL_LIB_NAME}
-    FILE "${PDAL_BINARY_DIR}/PDALTargets.cmake")
-install(EXPORT PDALTargets DESTINATION "${PDAL_LIB_INSTALL_DIR}/pdal/cmake")
+export(
+    TARGETS
+        ${PDAL_BASE_LIB_NAME} ${PDAL_UTIL_LIB_NAME}
+    FILE
+        "${PDAL_BINARY_DIR}/PDALTargets.cmake")
+
+install(
+    EXPORT
+        PDALTargets
+    DESTINATION
+        "${PDAL_LIB_INSTALL_DIR}/pdal/cmake")
 include(${PDAL_CMAKE_DIR}/config.cmake)
-
 feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES)
 export(PACKAGE PDAL)
 
 # TODO: move under scripts/bash-completion ?
 if (WITH_COMPLETION)
     if (IS_DIRECTORY ${CMAKE_INSTALL_PREFIX}/share/bash-completion/completions)
-        install(FILES "${PROJECT_SOURCE_DIR}/scripts/bash-completion/pdal" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/bash-completion/completions")
+        install(
+            FILES
+                "${PROJECT_SOURCE_DIR}/scripts/bash-completion/pdal"
+            DESTINATION
+                "${CMAKE_INSTALL_PREFIX}/share/bash-completion/completions")
     elseif (IS_DIRECTORY /etc/bash_completion.d)
-        install(FILES "${PROJECT_SOURCE_DIR}/scripts/bash-completion/pdal" DESTINATION "${CMAKE_INSTALL_PREFIX}/etc/bash_completion.d")
+        install(
+            FILES
+                "${PROJECT_SOURCE_DIR}/scripts/bash-completion/pdal"
+            DESTINATION
+                "${CMAKE_INSTALL_PREFIX}/etc/bash_completion.d")
     endif()
 endif()
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a4ee38f..7f12380 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -39,6 +39,7 @@ Fixes #123.
 * [General GitHub documentation](http://help.github.com/)
 * [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
 * #pdal IRC channel on freenode.org
+* [PDAL Gitter channel](https://gitter.im/PDAL/PDAL)
 
 ## Acknowledgements
 
diff --git a/ChangeLog b/ChangeLog
index b42ea75..30e3d56 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7078,8 +7078,8 @@
 	* Howard Butler <hobu.inc at gmail.com> decruft (14:40:34)
 	* Howard Butler <hobu.inc at gmail.com> decruft (14:40:25)
 	* Howard Butler <hobu.inc at gmail.com> used cached byte positions to avoid per-point map lookup of positions for getField queries (14:40:00)
-	* Howard Butler <hobu.inc at gmail.com> use inplacereprojfilter because the scalingfilter combo doesnt work right now (14:39:03)
-	* Howard Butler <hobu.inc at gmail.com> use inplacereprojfilter because the scalingfilter combo doesnt work right now (14:38:18)
+	* Howard Butler <hobu.inc at gmail.com> use inplacereprojfilter because the scalingfilter combo doesn't work right now (14:39:03)
+	* Howard Butler <hobu.inc at gmail.com> use inplacereprojfilter because the scalingfilter combo doesn't work right now (14:38:18)
 	* Howard Butler <hobu.inc at gmail.com> add getField specialization for fetching based on a pointIndex and Schema::getByteOffset() output (14:36:06)
 	* Howard Butler <hobu.inc at gmail.com> no longer include tuple, not used (14:34:57)
 
diff --git a/HOWTORELEASE.txt b/HOWTORELEASE.txt
index 93be97c..e237866 100644
--- a/HOWTORELEASE.txt
+++ b/HOWTORELEASE.txt
@@ -29,10 +29,13 @@ Release Process
     set(PDAL_BUILD_VERSION "1.0.0")
     * https://github.com/libspatialindex/libspatialindex/pull/44#issuecomment-57088783
 
+  - doc/quickstart.rst has a number of current-release references
+
   - Increment the doc build branch of .travis.yml:
 
     "$TRAVIS_BRANCH" = "1.2-maintenance"
 
+
 2) Update README to include any relevant info about the release that
    might have changed.
 
@@ -88,3 +91,17 @@ Release Process
 10) Write the release notes. Email PDAL mailing list with notice about release
 
 11) Upload Python extension to PyPI
+
+12) Publish JNI Bindings
+    What you need: 
+        - an account on sonatype (https://issues.sonatype.org/secure/Signup!default.jspa)
+        - ~/.sbt/0.13/sonatype.sbt file with the following content: 
+            credentials += Credentials("Sonatype Nexus Repository Manager",
+                           "oss.sonatype.org",
+                           "<your username>",
+                           "<your password>")
+
+    Sonatype publish:
+    ::        
+          export PDAL_VERSION_SUFFIX="" # -SNAPSHOT by default
+          cd ./java; ./scripts/publish-all.sh
diff --git a/PDAL--src.tar.bz2.md5 b/PDAL--src.tar.bz2.md5
new file mode 100644
index 0000000..e69de29
diff --git a/PDAL--src.tar.gz.md5 b/PDAL--src.tar.gz.md5
new file mode 100644
index 0000000..e69de29
diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
index 469c9fe..b02aa4a 100644
--- a/apps/CMakeLists.txt
+++ b/apps/CMakeLists.txt
@@ -5,102 +5,100 @@
 
 cmake_minimum_required(VERSION 2.8)
 
-set(PDAL_SOURCE_DIR ../src)
-
-#------------------------------------------------------------------------------
-# includes
-#------------------------------------------------------------------------------
-
-include_directories(
-    .
-    ../include
-    ../util
-    ${PROJECT_SOURCE_DIR}/io/faux
-    ${PROJECT_SOURCE_DIR}/io/las
-    ${PROJECT_SOURCE_DIR}/kernels
-    ${PROJECT_SOURCE_DIR}/kernels/delta
-    ${PROJECT_SOURCE_DIR}/kernels/diff
-    ${PROJECT_SOURCE_DIR}/kernels/info
-    ${PROJECT_SOURCE_DIR}/kernels/pipeline
-    ${PROJECT_SOURCE_DIR}/kernels/random
-    ${PROJECT_SOURCE_DIR}/kernels/sort
-    ${PROJECT_SOURCE_DIR}/kernels/translate
-)
-
-#------------------------------------------------------------------------------
-# Collect programs to build
-#------------------------------------------------------------------------------
-
-set(PDAL_UTILITY pdal)
-
 #------------------------------------------------------------------------------
 # Configure build targets
 #------------------------------------------------------------------------------
 
-if(PDAL_UTILITY)
-    set(srcs pdal.cpp)
-
-    if(WIN32)
-        list(APPEND srcs ${PDAL_TARGET_OBJECTS})
-    endif()
-
-    list(APPEND PDAL_UTILITIES ${PDAL_UTILITY})
-    if (APPLE AND PDAL_BUNDLE)
-        add_executable(${PDAL_UTILITY} MACOSX_BUNDLE ${srcs})
-    else (APPLE AND PDAL_BUNDLE)
-        add_executable(${PDAL_UTILITY} ${srcs})
-    endif(APPLE AND PDAL_BUNDLE)
-    target_link_libraries(${PDAL_UTILITY} ${PDAL_BASE_LIB_NAME}
-        ${PDAL_UTIL_LIB_NAME})
-endif()
+set(PDAL_APP pdal)
+
+if (APPLE AND PDAL_BUNDLE)
+    add_executable(${PDAL_APP} MACOSX_BUNDLE pdal.cpp)
+else (APPLE AND PDAL_BUNDLE)
+    add_executable(${PDAL_APP} pdal.cpp)
+endif(APPLE AND PDAL_BUNDLE)
+target_link_libraries(${PDAL_APP} PRIVATE
+    ${PDAL_BASE_LIB_NAME} ${PDAL_UTIL_LIB_NAME})
+target_include_directories(${PDAL_APP} PRIVATE
+    ${PDAL_JSONCPP_INCLUDE_DIR}
+    ${PDAL_INCLUDE_DIR}
+    ${PROJECT_BINARY_DIR}/include)
 
 #------------------------------------------------------------------------------
 # Targets installation
 #------------------------------------------------------------------------------
 
 if (APPLE AND PDAL_BUNDLE)
-    install(TARGETS ${PDAL_UTILITIES}
+    install(TARGETS ${PDAL_APP}
         BUNDLE DESTINATION ${PDAL_BIN_INSTALL_DIR})
 else(APPLE AND PDAL_BUNDLE)
-    install(TARGETS ${PDAL_UTILITIES}
+    install(TARGETS ${PDAL_APP}
         RUNTIME DESTINATION ${PDAL_BIN_INSTALL_DIR})
 endif(APPLE AND PDAL_BUNDLE)
 
-if(UNIX OR APPLE)
+set(PKGCONFIG_LIBRARY_DEFINITIONS "")
+set(PDAL_INCLUDE_DEFINITIONS
+    "-I${CMAKE_INSTALL_PREFIX}/include -I${GDAL_INCLUDE_DIR}")
+set(PKGCONFIG_LIBRARY_DEFINITIONS "${PKGCONFIG_LIBRARY_DEFINITIONS} gdal")
 
-    set(PKGCONFIG_LIBRARY_DEFINITIONS "")
-    set(PDAL_INCLUDE_DEFINITIONS "-I${CMAKE_INSTALL_PREFIX}/include -I${GDAL_INCLUDE_DIR}")
-    set(PKGCONFIG_LIBRARY_DEFINITIONS "${PKGCONFIG_LIBRARY_DEFINITIONS} gdal")
-
-    if(LIBXML2_FOUND)
-        set(PKGCONFIG_LIBRARY_DEFINITIONS "${PKGCONFIG_LIBRARY_DEFINITIONS} libxml-2.0")
-        set(PDAL_INCLUDE_DEFINITIONS "${PDAL_INCLUDE_DEFINITIONS} -I${LIBXML2_INCLUDE_DIR}")
-    endif()
+if(LIBXML2_FOUND)
+    set(PKGCONFIG_LIBRARY_DEFINITIONS
+        "${PKGCONFIG_LIBRARY_DEFINITIONS} libxml-2.0")
+    set(PDAL_INCLUDE_DEFINITIONS
+        "${PDAL_INCLUDE_DEFINITIONS} -I${LIBXML2_INCLUDE_DIR}")
+endif()
 
-    set(PKGCONFIG_LIBRARY_DEFINITIONS "${PKGCONFIG_LIBRARY_DEFINITIONS} geos")
-    set(PDAL_INCLUDE_DEFINITIONS "${PDAL_INCLUDE_DEFINITIONS} -I${GEOS_INCLUDE_DIR}")
+set(PKGCONFIG_LIBRARY_DEFINITIONS "${PKGCONFIG_LIBRARY_DEFINITIONS} geos")
+set(PDAL_INCLUDE_DEFINITIONS
+    "${PDAL_INCLUDE_DEFINITIONS} -I${GEOS_INCLUDE_DIR}")
 
-    if (LASZIP_FOUND)
-        set(PDAL_INCLUDE_DEFINITIONS "${PDAL_INCLUDE_DEFINITIONS} -I${LASZIP_INCLUDE_DIR}")
-    endif()
+if (LASZIP_FOUND)
+    set(PDAL_INCLUDE_DEFINITIONS
+        "${PDAL_INCLUDE_DEFINITIONS} -I${LASZIP_INCLUDE_DIR}")
+endif()
 
-    file(MAKE_DIRECTORY "${PDAL_OUTPUT_LIB_DIR}/pkgconfig/")
-    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pdal.pc.in
-                   ${CMAKE_CURRENT_BINARY_DIR}/pdal.pc @ONLY)
-    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pdal.pc
-        DESTINATION "${PDAL_LIB_INSTALL_DIR}/pkgconfig/"
-        PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
+file(MAKE_DIRECTORY "${PDAL_OUTPUT_LIB_DIR}/pkgconfig/")
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/pdal.pc.in
+               ${CMAKE_CURRENT_BINARY_DIR}/pdal.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pdal.pc
+    DESTINATION "${PDAL_LIB_INSTALL_DIR}/pkgconfig/"
+    PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
 
+if(UNIX OR APPLE)
     # Autoconf compatibility variables to use the same script source.
     configure_file("${CMAKE_CURRENT_SOURCE_DIR}/pdal-config.in"
                    "${CMAKE_CURRENT_SOURCE_DIR}/pdal-config" @ONLY)
 
     file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/pdal-config"
-         DESTINATION "${PDAL_OUTPUT_BIN_DIR}/"
-         FILE_PERMISSIONS  OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
+        DESTINATION
+            "${PDAL_OUTPUT_BIN_DIR}/"
+        FILE_PERMISSIONS
+            OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE
+            WORLD_READ WORLD_EXECUTE)
 
     install(PROGRAMS "${PDAL_OUTPUT_BIN_DIR}/pdal-config"
-            DESTINATION "${CMAKE_INSTALL_PREFIX}/bin"
-            PERMISSIONS
-            OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
+        DESTINATION
+            "${CMAKE_INSTALL_PREFIX}/bin"
+        PERMISSIONS
+            OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE
+            WORLD_READ WORLD_EXECUTE)
+
+elseif(WIN32)
+    # Autoconf compatibility variables to use the same script source.
+    configure_file("${CMAKE_CURRENT_SOURCE_DIR}/pdal-config-bat.in"
+                   "${CMAKE_CURRENT_SOURCE_DIR}/pdal-config.bat" @ONLY)
+
+    file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/pdal-config.bat"
+         DESTINATION
+            "${PDAL_OUTPUT_BIN_DIR}/"
+         FILE_PERMISSIONS
+            OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE
+            WORLD_READ WORLD_EXECUTE)
+
+    install(PROGRAMS "${PDAL_OUTPUT_BIN_DIR}/pdal-config.bat"
+        DESTINATION
+            "${CMAKE_INSTALL_PREFIX}/bin"
+        PERMISSIONS
+            OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE
+            WORLD_READ WORLD_EXECUTE)
+
 endif()
diff --git a/apps/pdal-config b/apps/pdal-config
index e1cae46..39649c1 100644
--- a/apps/pdal-config
+++ b/apps/pdal-config
@@ -1,7 +1,7 @@
 #!/bin/sh
-prefix=/usr/local
-exec_prefix=/usr/local/bin
-libdir=/usr/local/lib
+prefix=/usr
+exec_prefix=/usr/bin
+libdir=/usr/lib
 
 usage()
 {
@@ -26,11 +26,11 @@ fi
 
 case $1 in
   --libs)
-    echo -L/usr/local/lib -lpdalcpp
+    echo -L/usr/lib -lpdalcpp
     ;;
 
   --plugin-dir)
-    echo /usr/local/lib
+    echo /usr/lib
     ;;
 
   --prefix)
@@ -46,7 +46,7 @@ case $1 in
     ;;
 
   --includes)
-    echo -I/usr/local/include -I/usr/include/gdal -I/usr/include/libxml2 -I/usr/include -I/usr/include
+    echo -I/usr/include -I/usr/include/gdal -I/usr/include/libxml2 -I/usr/include -I/usr/include
     ;;
 
   --cflags)
@@ -58,11 +58,11 @@ case $1 in
     ;;
 
   --version)
-    echo 1.3.0
+    echo 1.4.0
     ;;
 
   --python-version)
-    echo 
+    echo 2.7.12
     ;;
 
   *)
diff --git a/apps/pdal-config-bat.in b/apps/pdal-config-bat.in
new file mode 100644
index 0000000..ade9020
--- /dev/null
+++ b/apps/pdal-config-bat.in
@@ -0,0 +1,31 @@
+ at echo off
+
+SET prefix=@CMAKE_INSTALL_PREFIX@
+SET exec_prefix=@CMAKE_INSTALL_PREFIX@/bin
+SET libdir=@CMAKE_INSTALL_PREFIX@/lib
+
+
+IF "%1" == "--libs" echo -L at CMAKE_INSTALL_PREFIX@/lib -lpdalcpp & goto exit
+IF "%1" == "--plugin-dir" echo @PDAL_PLUGIN_INSTALL_PATH@ & goto exit
+IF "%1" == "--prefix" echo %prefix% & goto exit
+IF "%1" == "--ldflags" echo -L%libdir% & goto exit
+IF "%1" == "--defines" echo @PDAL_CONFIG_DEFINITIONS@ & goto exit
+IF "%1" == "--includes" echo @PDAL_INCLUDE_DEFINITIONS@ & goto exit
+IF "%1" == "--cflags" echo @CMAKE_C_FLAGS@ & goto exit
+IF "%1" == "--cxxflags" echo @CMAKE_CXX_FLAGS@ -std=c++11 & goto exit
+IF "%1" == "--version" echo @PDAL_VERSION_STRING@ & goto exit
+IF "%1" == "--python-version" echo @PYTHON_VERSION_STRING@ & goto exit
+
+
+echo Usage: pdal-config [OPTIONS]
+echo Options:
+echo    [--cflags]
+echo    [--cxxflags]
+echo    [--defines]
+echo    [--includes]
+echo    [--libs]
+echo    [--plugin-dir]
+echo    [--version]
+echo    [--python-version]
+
+:exit
diff --git a/apps/pdal.cpp b/apps/pdal.cpp
index 88d48b7..ad6e78e 100644
--- a/apps/pdal.cpp
+++ b/apps/pdal.cpp
@@ -46,6 +46,13 @@
 #include <string>
 #include <vector>
 
+#include <json/json.h>
+
+#ifndef _WIN32
+#include <csignal>
+#include <unistd.h>
+#endif
+
 using namespace pdal;
 
 std::string headline(Utils::screenWidth(), '-');
@@ -64,7 +71,7 @@ private:
     void outputDrivers();
     void outputCommands();
     void outputOptions();
-    void outputOptions(const std::string& stageName);
+    void outputOptions(const std::string& stageName,std::ostream& strm);
     void addArgs(ProgramArgs& args);
     std::string findKernel();
 
@@ -72,12 +79,13 @@ private:
 
     std::string m_command;
     bool m_debug;
-    int m_logLevel;
+    LogLevel m_logLevel;
     bool m_showDrivers;
     bool m_help;
     bool m_showCommands;
     bool m_showVersion;
     std::string m_showOptions;
+    bool m_showJSON;
 };
 
 
@@ -106,7 +114,7 @@ void App::outputHelp(const ProgramArgs& args)
 
     for (auto name : loaded_kernels)
         m_out << "   - " << name << std::endl;
-    m_out << "See http://pdal.io/apps.html for more detail" << std::endl;
+    m_out << "See http://pdal.io/apps/ for more detail" << std::endl;
 }
 
 
@@ -115,35 +123,58 @@ void App::outputDrivers()
     // Force plugin loading.
     StageFactory f(false);
 
-    int nameColLen(25);
-    int descripColLen(Utils::screenWidth() - nameColLen - 1);
+    StringList stages = PluginManager::names(PF_PluginType_Filter |
+        PF_PluginType_Reader | PF_PluginType_Writer);
 
-    std::string tablehead(std::string(nameColLen, '=') + ' ' +
-        std::string(descripColLen, '='));
+    if (!m_showJSON)
+    {
+        int nameColLen(28);
+        int descripColLen(Utils::screenWidth() - nameColLen - 1);
 
-    m_out << std::endl;
-    m_out << tablehead << std::endl;
-    m_out << std::left << std::setw(nameColLen) << "Name" <<
-        " Description" << std::endl;
-    m_out << tablehead << std::endl;
+        std::string tablehead(std::string(nameColLen, '=') + ' ' +
+            std::string(descripColLen, '='));
 
-    m_out << std::left;
+        m_out << std::endl;
+        m_out << tablehead << std::endl;
+        m_out << std::left << std::setw(nameColLen) << "Name" <<
+            " Description" << std::endl;
+        m_out << tablehead << std::endl;
 
-    StringList stages = PluginManager::names(PF_PluginType_Filter |
-        PF_PluginType_Reader | PF_PluginType_Writer);
-    for (auto name : stages)
-    {
-        std::string descrip = PluginManager::description(name);
-        StringList lines = Utils::wordWrap(descrip, descripColLen - 1);
-        for (size_t i = 0; i < lines.size(); ++i)
+        m_out << std::left;
+
+
+        for (auto name : stages)
         {
-            m_out << std::setw(nameColLen) << name << " " <<
-                lines[i] << std::endl;
-            name.clear();
+            std::string descrip = PluginManager::description(name);
+            StringList lines = Utils::wordWrap(descrip, descripColLen - 1);
+            for (size_t i = 0; i < lines.size(); ++i)
+            {
+                m_out << std::setw(nameColLen) << name << " " <<
+                    lines[i] << std::endl;
+                name.clear();
+            }
         }
+
+        m_out << tablehead << std::endl << std::endl;
     }
+    else
+    {
 
-    m_out << tablehead << std::endl << std::endl;
+        Json::Value array(Json::arrayValue);
+        for (auto name : stages)
+        {
+            std::string description = PluginManager::description(name);
+            std::string link = PluginManager::link(name);
+            Json::Value node(Json::objectValue);
+            node["name"] = name;
+            node["description"] = description;
+            node["link"] = link;
+            array.append(node);
+        }
+
+        m_out << array;
+
+    }
 }
 
 
@@ -157,7 +188,7 @@ void App::outputCommands()
 }
 
 
-void App::outputOptions(std::string const& stageName)
+void App::outputOptions(std::string const& stageName, std::ostream& strm)
 {
     // Force plugin loading.
     StageFactory f(false);
@@ -169,13 +200,33 @@ void App::outputOptions(std::string const& stageName)
         return;
     }
 
-    m_out << stageName << " -- " << PluginManager::link(stageName) << std::endl;
-    m_out << headline << std::endl;
 
     ProgramArgs args;
-
     s->addAllArgs(args);
-    args.dump2(m_out, 2, 6, headline.size());
+
+    if (!m_showJSON)
+    {
+        strm  << stageName << " -- " << PluginManager::link(stageName) << std::endl;
+        strm  << headline << std::endl;
+
+        args.dump2(strm , 2, 6, headline.size());
+    }
+    else
+    {
+        std::ostringstream ostr;
+        args.dump3(ostr);
+        std::string json = ostr.str();
+
+        Json::Reader jsonReader;
+        Json::Value array;
+        Json::Value object(Json::objectValue);
+        jsonReader.parse(json, array);
+
+        object[stageName] = array;
+
+        strm  << object;
+
+    }
 }
 
 
@@ -186,10 +237,31 @@ void App::outputOptions()
 
     StringList nv = PluginManager::names(PF_PluginType_Filter |
         PF_PluginType_Reader | PF_PluginType_Writer);
-    for (auto const& n : nv)
+
+    if (!m_showJSON)
     {
-        outputOptions(n);
-        m_out << std::endl;
+        for (auto const& n : nv)
+        {
+            outputOptions(n, m_out);
+            m_out << std::endl;
+        }
+    } else
+    {
+        std::ostringstream strm;
+        Json::Value options (Json::arrayValue);
+        for (auto const& n : nv)
+        {
+            outputOptions(n, strm);
+            std::string json(strm.str());
+            Json::Reader jsonReader;
+            Json::Value array;
+            jsonReader.parse(json, array);
+            options.append(array);
+
+            strm.str("");
+        }
+
+        m_out << options;
     }
 }
 
@@ -199,26 +271,32 @@ void App::addArgs(ProgramArgs& args)
     args.add("command", "The PDAL command", m_command).setPositional();
     args.add("debug", "Sets the output level to 3 (option deprecated)",
         m_debug);
-    args.add("verbose,v", "Sets the output level (0-8)", m_logLevel, -1);
+    args.add("verbose,v", "Sets the output level (0-8)", m_logLevel,
+        LogLevel::None);
     args.add("drivers", "List available drivers", m_showDrivers);
     args.add("help,h", "Display help text", m_help);
     args.add("list-commands", "List available commands", m_showCommands);
     args.add("version", "Show program version", m_showVersion);
     args.add("options", "Show options for specified driver (or 'all')",
         m_showOptions);
+    Arg& json = args.add("showjson", "List options or drivers as JSON output",
+        m_showJSON);
+    json.setHidden();
 }
 
+namespace
+{
+    LogPtr logPtr(new Log("PDAL", "stderr"));
+}
 
 int main(int argc, char* argv[])
 {
-    LogPtr log(new Log("PDAL", "stderr"));
-
     App pdal;
 
     StringList cmdArgs;
     for (int i = 1; i < argc; ++i)
         cmdArgs.push_back(argv[i]);
-    return pdal.execute(cmdArgs, log);
+    return pdal.execute(cmdArgs, logPtr);
 }
 
 
@@ -256,13 +334,35 @@ int App::execute(StringList& cmdArgs, LogPtr& log)
     ProgramArgs args;
 
     addArgs(args);
-    args.parseSimple(cmdArgs);
+    try
+    {
+        args.parseSimple(cmdArgs);
+    }
+    catch (arg_val_error const& e)
+    {
+        Utils::printError(e.what());
+        return -1;
+    }
 
-    if (m_logLevel >= (int)LogLevel::Error)
-        log->setLevel((LogLevel)m_logLevel);
+    if (m_logLevel != LogLevel::None)
+        log->setLevel(m_logLevel);
     else if (m_debug)
         log->setLevel(LogLevel::Debug);
     PluginManager::setLog(log);
+#ifndef _WIN32
+    if (m_debug)
+    {
+        signal(SIGSEGV, [](int sig)
+        {
+            logPtr->get(LogLevel::Debug) << "Segmentation fault (signal 11)\n";
+            StringList lines = Utils::backtrace();
+
+            for (const auto& l : lines)
+                logPtr->get(LogLevel::Debug) << l << std::endl;
+            exit(1);
+        });
+    }
+#endif
 
     m_command = Utils::tolower(m_command);
     if (!m_command.empty())
@@ -300,7 +400,7 @@ int App::execute(StringList& cmdArgs, LogPtr& log)
         if (m_showOptions == "all")
             outputOptions();
         else
-            outputOptions(m_showOptions);
+            outputOptions(m_showOptions, m_out);
     }
     else
         outputHelp(args);
diff --git a/cmake/compiler_options.cmake b/cmake/compiler_options.cmake
index 6032cff..45019f4 100644
--- a/cmake/compiler_options.cmake
+++ b/cmake/compiler_options.cmake
@@ -1,9 +1,3 @@
-include_directories(${PDAL_INCLUDE_DIR})
-include_directories(${PDAL_UTIL_DIR})
-include_directories(${PDAL_SRC_DIR})
-include_directories(${PDAL_IO_DIR})
-include_directories(${PDAL_KERNEL_DIR})
-include_directories(${PDAL_FILTER_DIR})
 if (WIN32)
     include (${CMAKE_CURRENT_LIST_DIR}/win32_compiler_options.cmake)
 else()
diff --git a/cmake/directories.cmake b/cmake/directories.cmake
index ee2b3c3..f079ec7 100644
--- a/cmake/directories.cmake
+++ b/cmake/directories.cmake
@@ -1,14 +1,14 @@
 if(NOT ROOT_DIR)
     message(FATAL_ERROR "ROOT_DIR must be set in top-level CMakeLists.txt")
 endif()
-set(PDAL_SRC_DIR ${ROOT_DIR}/src)
+set(PDAL_SRC_DIR ${ROOT_DIR}/pdal)
+set(PDAL_INCLUDE_DIR ${ROOT_DIR})
 set(PDAL_CMAKE_DIR ${ROOT_DIR}/cmake)
-set(PDAL_INCLUDE_DIR ${ROOT_DIR}/include)
 set(PDAL_MODULE_DIR ${PDAL_CMAKE_DIR}/modules)
-set(PDAL_UTIL_DIR ${ROOT_DIR}/src/util)
+set(PDAL_UTIL_DIR ${PDAL_SRC_DIR}/util)
 set(PDAL_TOOLS_DIR ${ROOT_DIR}/tools)
-set(PDAL_KERNEL_DIR ${ROOT_DIR}/kernels)
-set(PDAL_FILTER_DIR ${ROOT_DIR}/filters)
+set(PDAL_KERNELS_DIR ${ROOT_DIR}/kernels)
+set(PDAL_FILTERS_DIR ${ROOT_DIR}/filters)
 set(PDAL_IO_DIR ${ROOT_DIR}/io)
 set(PDAL_VENDOR_DIR ${ROOT_DIR}/vendor)
 
diff --git a/cmake/gdal.cmake b/cmake/gdal.cmake
index 613aa35..b7be79b 100644
--- a/cmake/gdal.cmake
+++ b/cmake/gdal.cmake
@@ -5,7 +5,6 @@ find_package(GDAL 1.9.0)
 set_package_properties(GDAL PROPERTIES TYPE REQUIRED
     PURPOSE "Provides general purpose raster, vector, and reference system support")
 if (GDAL_FOUND)
-
     include_directories("${GDAL_INCLUDE_DIR}")
     mark_as_advanced(CLEAR GDAL_INCLUDE_DIR)
     mark_as_advanced(CLEAR GDAL_LIBRARY)
diff --git a/cmake/geotiff.cmake b/cmake/geotiff.cmake
index 4450f05..1d1e8af 100644
--- a/cmake/geotiff.cmake
+++ b/cmake/geotiff.cmake
@@ -1,19 +1,17 @@
 #
 # GeoTIFF support
 #
-option(WITH_GEOTIFF "Choose if GeoTIFF support should be built" TRUE)
-if (WITH_GEOTIFF)
-    find_package(GeoTIFF QUIET 1.3.0)
-    set_package_properties(GeoTIFF PROPERTIES TYPE OPTIONAL)
-    if (GEOTIFF_FOUND)
-        include_directories("${GEOTIFF_INCLUDE_DIR}")
-        set(PDAL_HAVE_LIBGEOTIFF 1)
-        mark_as_advanced(CLEAR TIFF_INCLUDE_DIR)
-        mark_as_advanced(CLEAR TIFF_LIBRARY)
-        if (WIN32)
-            set(TIFF_NAMES libtiff_i)
-        endif(WIN32)
-    else()
-        set(WITH_GEOTIFF FALSE)
-    endif()
+
+find_package(GeoTIFF REQUIRED 1.3.0)
+set_package_properties(GeoTIFF PROPERTIES TYPE REQUIRED)
+if (GEOTIFF_FOUND)
+    include_directories("${GEOTIFF_INCLUDE_DIR}")
+    set(PDAL_HAVE_LIBGEOTIFF 1)
+    mark_as_advanced(CLEAR TIFF_INCLUDE_DIR)
+    mark_as_advanced(CLEAR TIFF_LIBRARY)
+    if (WIN32)
+        set(TIFF_NAMES libtiff_i)
+    endif(WIN32)
+else()
+    message(FATAL_ERROR "GeoTIFF support is required")
 endif()
diff --git a/cmake/gtest.cmake b/cmake/gtest.cmake
index 31f1160..cf5b6f4 100644
--- a/cmake/gtest.cmake
+++ b/cmake/gtest.cmake
@@ -3,7 +3,7 @@ if (MSVC)
     #link dynamically too (default is /MT[d])
     option(gtest_force_shared_crt "Always use shared Visual C++ run-time DLL" ON)
 endif()
-add_subdirectory(vendor/gtest-1.7.0)
+add_subdirectory(vendor/gtest)
 
 # gtest 1.7.0 has some CMake warnings, so we silence these by setting the
 # following properties.
diff --git a/cmake/json.cmake b/cmake/json.cmake
index b1aa280..6b3bd7d 100644
--- a/cmake/json.cmake
+++ b/cmake/json.cmake
@@ -18,7 +18,7 @@ if (JSONCPP_FOUND)
     set(PDAL_HAVE_JSONCPP 1)
 else()
     set(PDAL_JSONCPP_LIB_NAME pdal_jsoncpp)
-    set(PDAL_JSONCPP_INCLUDE_DIR ${PDAL_VENDOR_DIR}/jsoncpp-1.6.2/dist)
+    set(PDAL_JSONCPP_INCLUDE_DIR ${PDAL_VENDOR_DIR}/jsoncpp/dist)
     set(PDAL_JSONCPP_SRC ${PDAL_JSONCPP_INCLUDE_DIR}/jsoncpp.cpp)
     set(JSON_CPP_LINK_TYPE PRIVATE)
 endif()
diff --git a/cmake/laszip.cmake b/cmake/laszip.cmake
index 5271142..403aeb5 100644
--- a/cmake/laszip.cmake
+++ b/cmake/laszip.cmake
@@ -15,6 +15,9 @@ if(WITH_LASZIP)
         mark_as_advanced(CLEAR LASZIP_LIBRARY)
         mark_as_advanced(CLEAR LASZIP_VERSION)
         set(PDAL_HAVE_LASZIP 1)
+        if(NOT WITH_STATIC_LASZIP AND WIN32)
+            set(LASZIP_DEFINES "-DLASZIP_DLL_IMPORT=1")
+        endif()
     else()
         set(LASZIP_LIBRARY "")
         set(WITH_LASZIP FALSE)
diff --git a/cmake/libxml2.cmake b/cmake/libxml2.cmake
index 27f88fd..c2d8edf 100644
--- a/cmake/libxml2.cmake
+++ b/cmake/libxml2.cmake
@@ -6,12 +6,12 @@ endif()
 # libxml2
 #
 
-
 find_package(LibXml2)
 set_package_properties(LibXml2 PROPERTIES TYPE OPTIONAL)
 mark_as_advanced(CLEAR LIBXML2_INCLUDE_DIR)
 mark_as_advanced(CLEAR LIBXML2_LIBRARIES)
-include_directories(${LIBXML2_INCLUDE_DIR})
-set(PDAL_HAVE_LIBXML2 1)
+if (LIBXML2_FOUND)
+    set(PDAL_HAVE_LIBXML2 1)
+endif()
 
 set_property(GLOBAL PROPERTY _LIBXML2_INCLUDED TRUE)
diff --git a/cmake/macros.cmake b/cmake/macros.cmake
index 703117a..ef3ed9d 100644
--- a/cmake/macros.cmake
+++ b/cmake/macros.cmake
@@ -41,19 +41,8 @@
 
 
 ###############################################################################
-# Add a set of include files to install.
-# _component The part of PDAL that the install files belong to.
-# _subdir The sub-directory for these include files.
-# ARGN The include files.
-macro(PDAL_ADD_INCLUDES _subdir)
-    install(FILES ${ARGN} DESTINATION ${PDAL_INCLUDE_INSTALL_DIR}/${_subdir})
-endmacro(PDAL_ADD_INCLUDES)
-
-
-###############################################################################
 # Add a library target.
 # _name The library name.
-# _component The part of PDAL that this library belongs to.
 # ARGN The source files for the library.
 #
 # The "generate_dimension_hpp" ensures that Dimension.hpp is built before
@@ -63,6 +52,8 @@ macro(PDAL_ADD_LIBRARY _name)
     add_library(${_name} ${PDAL_LIB_TYPE} ${ARGN})
     add_dependencies(${_name} generate_dimension_hpp)
     set_property(TARGET ${_name} PROPERTY FOLDER "Libraries")
+    target_include_directories(${_name} PRIVATE
+        ${PDAL_INCLUDE_DIR})
 
     install(TARGETS ${_name}
         EXPORT PDALTargets
@@ -72,6 +63,25 @@ macro(PDAL_ADD_LIBRARY _name)
 endmacro(PDAL_ADD_LIBRARY)
 
 ###############################################################################
+# Add a free library target (one that doesn't depend on PDAL).
+# _name The library name.
+# _library_type Shared or static
+# ARGN The source files for the library.
+#
+macro(PDAL_ADD_FREE_LIBRARY _name _library_type)
+    add_library(${_name} ${_library_type} ${ARGN})
+    set_property(TARGET ${_name} PROPERTY FOLDER "Libraries")
+    target_include_directories(${_name} PRIVATE
+        ${PDAL_INCLUDE_DIR})
+
+    install(TARGETS ${_name}
+        EXPORT PDALTargets
+        RUNTIME DESTINATION ${PDAL_BIN_INSTALL_DIR}
+        LIBRARY DESTINATION ${PDAL_LIB_INSTALL_DIR}
+        ARCHIVE DESTINATION ${PDAL_LIB_INSTALL_DIR})
+endmacro(PDAL_ADD_FREE_LIBRARY)
+
+###############################################################################
 # Add an executable target.
 # _name The executable name.
 # _component The part of PDAL that this library belongs to.
@@ -96,11 +106,14 @@ endmacro(PDAL_ADD_EXECUTABLE)
 # The "generate_dimension_hpp" ensures that Dimension.hpp is built before
 #  attempting to build anything else in the "library".
 #
+# NOTE: _name is the name of a variable that will hold the plugin name
+#    when the macro completes
 macro(PDAL_ADD_PLUGIN _name _type _shortname)
     set(options)
     set(oneValueArgs)
     set(multiValueArgs FILES LINK_WITH)
-    cmake_parse_arguments(PDAL_ADD_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+    cmake_parse_arguments(PDAL_ADD_PLUGIN "${options}" "${oneValueArgs}"
+        "${multiValueArgs}" ${ARGN})
     if(WIN32)
         set(${_name} "libpdal_plugin_${_type}_${_shortname}")
     else()
@@ -112,7 +125,10 @@ macro(PDAL_ADD_PLUGIN _name _type _shortname)
     endif()
 
     add_library(${${_name}} SHARED ${PDAL_ADD_PLUGIN_FILES})
-    target_link_libraries(${${_name}}
+    target_include_directories(${${_name}} PRIVATE
+        ${PROJECT_BINARY_DIR}/include
+        ${PDAL_INCLUDE_DIR})
+    target_link_libraries(${${_name}} PUBLIC
         ${PDAL_BASE_LIB_NAME}
         ${PDAL_UTIL_LIB_NAME}
         ${PDAL_ADD_PLUGIN_LINK_WITH})
@@ -128,7 +144,8 @@ macro(PDAL_ADD_PLUGIN _name _type _shortname)
         LIBRARY DESTINATION ${PDAL_LIB_INSTALL_DIR}
         ARCHIVE DESTINATION ${PDAL_LIB_INSTALL_DIR})
     if (APPLE)
-        set_target_properties(${${_name}} PROPERTIES INSTALL_NAME_DIR "@loader_path/../lib")
+        set_target_properties(${${_name}} PROPERTIES
+            INSTALL_NAME_DIR "@loader_path/../lib")
     endif()
 endmacro(PDAL_ADD_PLUGIN)
 
@@ -143,8 +160,6 @@ macro(PDAL_ADD_TEST _name)
     set(oneValueArgs)
     set(multiValueArgs FILES LINK_WITH)
     cmake_parse_arguments(PDAL_ADD_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
-    include_directories(${PROJECT_SOURCE_DIR}/test/unit)
-    include_directories(${PROJECT_BINARY_DIR}/test/unit)
     set(common_srcs
         ${PROJECT_SOURCE_DIR}/test/unit/Support.cpp
     )
@@ -153,12 +168,24 @@ macro(PDAL_ADD_TEST _name)
         add_definitions("-DPDAL_DLL_EXPORT=1")
     endif()
     add_executable(${_name} ${PDAL_ADD_TEST_FILES} ${common_srcs})
-    set_target_properties(${_name} PROPERTIES COMPILE_DEFINITIONS PDAL_DLL_IMPORT)
+    target_include_directories(${_name} PRIVATE
+        ${ROOT_DIR}
+        ${PDAL_INCLUDE_DIR}
+        ${PROJECT_SOURCE_DIR}/test/unit
+        ${PROJECT_BINARY_DIR}/test/unit
+        ${PROJECT_BINARY_DIR}/include)
+    set_target_properties(${_name}
+        PROPERTIES
+            COMPILE_DEFINITIONS PDAL_DLL_IMPORT)
     set_property(TARGET ${_name} PROPERTY FOLDER "Tests")
-    target_link_libraries(${_name}
+    target_link_libraries(${_name} PRIVATE
         ${PDAL_BASE_LIB_NAME} ${PDAL_UTIL_LIB_NAME} gtest
         ${PDAL_ADD_TEST_LINK_WITH})
-    add_test(NAME ${_name} COMMAND "${PROJECT_BINARY_DIR}/bin/${_name}" WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/..")
+    add_test(NAME ${_name}
+        COMMAND
+            "${PROJECT_BINARY_DIR}/bin/${_name}"
+        WORKING_DIRECTORY
+            "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/..")
     # Ensure plugins are loaded from build dir
     # https://github.com/PDAL/PDAL/issues/840
     if (WIN32)
@@ -172,7 +199,7 @@ endmacro(PDAL_ADD_TEST)
 
 ###############################################################################
 # Add a driver. Creates object library and adds files to source_group for windows IDE.
-# _type The driver type (e.g., driver, filter, kernel).
+# _type The driver type (e.g., reader, writer, driver, filter, kernel).
 # _name The driver name.
 # _srcs The list of source files to add.
 # _incs The list of includes to add.
@@ -188,15 +215,37 @@ macro(PDAL_ADD_DRIVER _type _name _srcs _incs _objs)
 	endif()
     add_library(${libname} OBJECT ${_srcs} ${_incs})
     add_dependencies(${libname} generate_dimension_hpp)
+    target_include_directories(${libname} PRIVATE
+        ${PDAL_INCLUDE_DIR})
     set_property(TARGET ${libname} PROPERTY FOLDER "Drivers/${_type}")
-
-    install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/"
-        DESTINATION "${PDAL_INCLUDE_INSTALL_DIR}"
-        FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp"
-    )
 endmacro(PDAL_ADD_DRIVER)
 
 ###############################################################################
+# Add a kernel. Creates object library and adds files to source_group
+# for windows IDE.
+# _name The driver name.
+# _srcs The list of source files to add.
+# _incs The list of includes to add.
+# _objs The object library name that is created.
+macro(PDAL_ADD_KERNEL _name _srcs _incs _objs)
+    source_group("Header Files\\kernel\\${_name}" FILES ${_incs})
+    source_group("Source Files\\kernel\\${_name}" FILES ${_srcs})
+
+    set(libname kernel_${_name})
+    set(${_objs} $<TARGET_OBJECTS:${libname}>)
+	if (NOT WIN32)
+		add_definitions("-fPIC")
+	endif()
+    add_library(${libname} OBJECT ${_srcs} ${_incs})
+    add_dependencies(${libname} generate_dimension_hpp)
+    target_include_directories(${libname} PRIVATE
+        ${PDAL_INCLUDE_DIR}
+        ${PDAL_IO_DIR}
+        ${PDAL_FILTERS_DIR})
+    set_property(TARGET ${libname} PROPERTY FOLDER "Drivers/kernel")
+endmacro(PDAL_ADD_KERNEL)
+
+###############################################################################
 # Get the operating system information. Generally, CMake does a good job of
 # this. Sometimes, though, it doesn't give enough information. This macro will
 # distinguish between the UNIX variants. Otherwise, use the CMake variables
@@ -242,9 +291,6 @@ macro(SET_INSTALL_DIRS)
           set(PDAL_LIB_INSTALL_DIR "lib")
       endif()
   endif ()
-    set(PDAL_INCLUDE_INSTALL_ROOT "include/")
-    set(PDAL_INCLUDE_INSTALL_DIR
-        "${PDAL_INCLUDE_INSTALL_ROOT}/${PROJECT_NAME_LOWER}/")
     set(PDAL_DOC_INCLUDE_DIR
         "share/doc/${PROJECT_NAME_LOWER}-${PDAL_VERSION_MAJOR}.${PDAL_VERSION_MINOR}")
     set(PDAL_BIN_INSTALL_DIR "bin")
diff --git a/cmake/modules/FindJSONCPP.cmake b/cmake/modules/FindJSONCPP.cmake
index 39ab630..8dc0d7c 100644
--- a/cmake/modules/FindJSONCPP.cmake
+++ b/cmake/modules/FindJSONCPP.cmake
@@ -35,7 +35,7 @@ set(_pathsuffixes
 	msvc80
 	msvc90
 	linux-gcc)
-if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
 	execute_process(COMMAND
 		${CMAKE_CXX_COMPILER}
 		-dumpversion
diff --git a/cmake/modules/FindSQLite3.cmake b/cmake/modules/FindSQLite3.cmake
index 86c5e21..1456f73 100644
--- a/cmake/modules/FindSQLite3.cmake
+++ b/cmake/modules/FindSQLite3.cmake
@@ -5,7 +5,8 @@
 # SQLITE3_FOUND = if the library found
 # SQLITE3_LIBRARY = full path to the library
 # SQLITE3_LIBRARIES = full path to the library
-# SSQLITE3_INCLUDE_DIR = where to find the library headers
+# SQLITE3_HAS_LOAD_EXTENSION = was sqlite3 compiled with load extension?
+# SQLITE3_INCLUDE_DIR = where to find the library headers
 #
 # Copyright (c) 2009 Mateusz Loskot <mateusz at loskot.net>
 #
@@ -46,6 +47,17 @@ find_library(SQLITE3_LIBRARY
   ${SQLITE_ROOT_DIR}/lib
   $ENV{OSGEO4W_ROOT}/lib)
 
+# If sqlite3 was compiled with `OMIT_LOAD_EXTENSION`, PDAL compilation will fail due
+# to a missing symbol, `_sqlite3_enable_load_extension`.
+include(CheckCXXSourceRuns)
+unset(SQLITE3_HAS_LOAD_EXTENSION CACHE)
+set(CMAKE_REQUIRED_INCLUDES "${SQLITE3_INCLUDE_DIR}")
+set(CMAKE_REQUIRED_LIBRARIES "${SQLITE3_LIBRARY}")
+check_cxx_source_runs("#include <sqlite3.h>
+int main(int argc, char** argv) {
+    return sqlite3_compileoption_used(\"OMIT_LOAD_EXTENSION\");
+}" SQLITE3_HAS_LOAD_EXTENSION)
+
 set(SQLITE3_LIBRARIES ${SQLITE3_LIBRARY})
 
 #message(STATUS ${SQLITE3_LIBRARY})
diff --git a/cmake/options.cmake b/cmake/options.cmake
index ce1c6f9..e8fa854 100644
--- a/cmake/options.cmake
+++ b/cmake/options.cmake
@@ -2,11 +2,6 @@
 # Options for optional components.
 #
 
-option(WITH_APPS
-    "Choose if PDAL utilities should be built" TRUE)
-add_feature_info("PDAL application" WITH_APPS
-    "the PDAL command line application")
-
 option(WITH_COMPLETION
     "Install bash completions scripts for command line?" FALSE)
 add_feature_info("Bash completion" WITH_COMPLETION
diff --git a/cmake/pdaljni.cmake b/cmake/pdaljni.cmake
new file mode 100644
index 0000000..45dab88
--- /dev/null
+++ b/cmake/pdaljni.cmake
@@ -0,0 +1,7 @@
+option(WITH_PDAL_JNI
+    "Build PDAL JNI Bindings" FALSE)
+
+if (WITH_PDAL_JNI)
+    set(PDAL_BUILD TURE)
+    add_subdirectory(${ROOT_DIR}/java/native/src)
+endif()
diff --git a/cmake/rply.cmake b/cmake/rply.cmake
new file mode 100644
index 0000000..a242529
--- /dev/null
+++ b/cmake/rply.cmake
@@ -0,0 +1 @@
+set(RPLY_SRCS ${PDAL_VENDOR_DIR}/rply/rply.c)
diff --git a/cmake/sqlite.cmake b/cmake/sqlite.cmake
index 84199c2..2007547 100644
--- a/cmake/sqlite.cmake
+++ b/cmake/sqlite.cmake
@@ -3,6 +3,9 @@
 #
 
 find_package(SQLite3 QUIET REQUIRED)
+if(NOT "${SQLITE3_HAS_LOAD_EXTENSION}")
+    message(FATAL_ERROR "SQLite3 compiled without load extension, which is required. Please re-compile SQLite3 with load extension or ensure CMake is pointing at the correct SQLite3 installation.")
+endif()
 mark_as_advanced(CLEAR SQLITE3_INCLUDE_DIR)
 mark_as_advanced(CLEAR SQLITE3_LIBRARY)
 include_directories(${SQLITE3_INCLUDE_DIR})
diff --git a/cmake/test.cmake b/cmake/test.cmake
index e81c52d..8a398ac 100644
--- a/cmake/test.cmake
+++ b/cmake/test.cmake
@@ -12,6 +12,6 @@ if (WITH_TESTS)
     add_definitions(/D _VARIADIC_MAX=10)
   endif()
 
-  include_directories(${ROOT_DIR}/vendor/gtest-1.7.0/include
-      ${ROOT_DIR}/vendor/gtest-1.7.0)
+  include_directories(${ROOT_DIR}/vendor/gtest/include
+      ${ROOT_DIR}/vendor/gtest)
 endif()
diff --git a/cmake/win32_compiler_options.cmake b/cmake/win32_compiler_options.cmake
index d86271a..5ccf9e6 100644
--- a/cmake/win32_compiler_options.cmake
+++ b/cmake/win32_compiler_options.cmake
@@ -84,3 +84,4 @@ set(CMAKE_PREFIX_PATH "c:/OSGeo4W64/cmake;$ENV{CMAKE_LIBRARY_PATH}")
 
 #ABELL - WHY?
 set(PDAL_PLATFORM_WIN32 1)
+set(WINSOCK_LIBRARY ws2_32)
diff --git a/dimbuilder/CMakeLists.txt b/dimbuilder/CMakeLists.txt
index 08d4016..d9791cb 100644
--- a/dimbuilder/CMakeLists.txt
+++ b/dimbuilder/CMakeLists.txt
@@ -13,21 +13,22 @@ endif()
 
 include(${ROOT_DIR}/cmake/common.cmake NO_POLICY_SCOPE)
 
-include_directories(${PDAL_JSONCPP_INCLUDE_DIR})
-
-
-set (SOURCES
+#
+# We include Utils.cpp here rather than linking in the Utils library because
+# some people building linux packages disable rpath, which means that
+# the Utils library can't be located when running dimbuilder, even though
+# it has been built.
+#
+add_executable(dimbuilder
     DimBuilder.cpp
-    ${PDAL_JSONCPP_SRC}
     ${PDAL_UTIL_DIR}/Utils.cpp
-)
-
-set (HEADERS
-    DimBuilder.hpp
-    ${PDAL_INCLUDE_DIR}/pdal/util/Utils.hpp
-)
-
-add_executable(dimbuilder ${SOURCES} ${HEADERS})
+    ${PDAL_JSONCPP_SRC})
+target_include_directories(dimbuilder PRIVATE
+    ${PDAL_INCLUDE_DIR}
+    ${PDAL_JSONCPP_INCLUDE_DIR})
 if (PDAL_HAVE_JSONCPP)
-    target_link_libraries(dimbuilder ${PDAL_JSONCPP_LIB_NAME})
+    target_link_libraries(dimbuilder PRIVATE ${PDAL_JSONCPP_LIB_NAME})
+endif()
+if (UNIX AND NOT APPLE)
+    target_link_libraries(dimbuilder PRIVATE dl)
 endif()
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644
index 0000000..029b044
--- /dev/null
+++ b/doc/.gitignore
@@ -0,0 +1 @@
+dimension-table.csv
diff --git a/doc/api/cpp/algorithm.rst b/doc/api/cpp/algorithm.rst
deleted file mode 100644
index 64ee89e..0000000
--- a/doc/api/cpp/algorithm.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-.. _cpp-pdal-algorithm:
-
-******************************************************************************
-``pdal::Algorithm``
-******************************************************************************
-
-.. index:: Algorithm
-
-:cpp:namespace:`pdal::Algorithm` is a set of functions to present a more
-straightforward API for some common library algorithms.  .
-
-.. doxygennamespace:: pdal::Algorithm
-   :members:
-   :undoc-members:
-
diff --git a/doc/api/cpp/index.rst b/doc/api/cpp/index.rst
index 7880fe9..c8a5833 100644
--- a/doc/api/cpp/index.rst
+++ b/doc/api/cpp/index.rst
@@ -7,7 +7,6 @@ C++ API
 .. toctree::
    :maxdepth: 2
 
-   algorithm
    bounds
    charbuf
    dimension
diff --git a/doc/apps/density.rst b/doc/apps/density.rst
index 9f211d0..35b6156 100644
--- a/doc/apps/density.rst
+++ b/doc/apps/density.rst
@@ -7,6 +7,10 @@ density
 The density command produces a tessellated hexagonal OGR layer from the
 output of :ref:`filters.hexbin`.
 
+.. note::
+
+    The ``density`` command is only available when PDAL is linked with Hexer.
+
 ::
 
     --input, -i        input point cloud file name
diff --git a/doc/apps/ground.rst b/doc/apps/ground.rst
index 174210b..f1c8295 100644
--- a/doc/apps/ground.rst
+++ b/doc/apps/ground.rst
@@ -5,29 +5,27 @@ ground
 ********************************************************************************
 
 The ``ground`` command is used to segment the input point cloud into ground
-versus non-ground returns. The output is a point cloud containing only ground
-returns. The ``ground`` command invokes `Point Cloud Library
-<http://pointclouds.org/>`_'s `ProgressiveMorphologicalFilter`_.
-
-.. note::
-
-    The ``ground`` command is only available when PDAL is linked with PCL.
+versus non-ground returns.
 
 ::
 
-    $ pdal ground <input> <output>
+    $ pdal ground [options] <input> <output>
 
 ::
 
-    --input [-i] arg        Non-positional option for specifying input filename
-    --output [-o] arg       Non-positional option for specifying output filename
-    --max_window_size arg   max window size [33]
-    --slope arg             slope [1]
-    --max_distance arg      max distance [2.5]
-    --initial_distance arg  initial distance [0.15]
-    --cell_size arg         cell size [1]
-    --classify              apply classification labels? [true]
-    --extract               extract ground returns? [false]
-    --approximate [-a]      Use significantly faster approximate algorithm? [false]
-
-.. _`ProgressiveMorphologicalFilter`: http://pointclouds.org/documentation/tutorials/progressive_morphological_filtering.php#progressive-morphological-filtering.
+    --developer-debug   Enable developer debug (don't trap exceptions)
+    --label             A string to label the process with
+    --visualize         Visualize result
+    --driver            Override reader driver
+    --input, -i         Input filename
+    --output, -o        Output filename
+    --max_window_size   Max window size
+    --slope             Slope
+    --max_distance      Max distance
+    --initial_distance  Initial distance
+    --cell_size         Cell size
+    --classify          Apply classification labels?
+    --extract           extract ground returns?
+    --approximate, -a   use approximate algorithm? (much faster)
+
+  For more information, see the full documentation for PDAL at http://pdal.io/
diff --git a/doc/apps/hausdorff.rst b/doc/apps/hausdorff.rst
new file mode 100644
index 0000000..086dc40
--- /dev/null
+++ b/doc/apps/hausdorff.rst
@@ -0,0 +1,54 @@
+.. _hausdorff_command:
+
+********************************************************************************
+hausdorff
+********************************************************************************
+
+The ``hausdorff`` command is used to compute the Hausdorff distance between two
+point clouds. In this context, the Hausdorff distance is the greatest of all
+Euclidean distances from a point in one point cloud to the closest point in the
+other point cloud.
+
+More formally, for two non-empty subsets :math:`X` and :math:`Y`, the Hausdorff
+distance :math:`d_H(X,Y)` is
+
+.. math::
+
+  d_H(X,Y) = \operatorname*{max} \big\{ \operatorname*{sup}_{x \in X} \operatorname*{inf}_{y \in Y} d(x,y), \operatorname*{sup}_{y \in Y} \operatorname*{inf}_{x \in X} d(x,y)\big\}
+  
+where :math:`\operatorname*{sup}` and :math:`\operatorname*{inf}` are the
+supremum and infimum respectively.
+
+::
+
+    $ pdal hausdorff <source> <candidate>
+
+::
+
+    --source arg     Non-positional option for specifying filename of source file.
+    --candidate arg  Non-positional option for specifying filename to test against source.
+
+The algorithm makes no distinction between source and candidate files (i.e.,
+they can be transposed with no affect on the computed distance).
+
+The command returns 0 along with a JSON-formatted message summarizing the PDAL
+version, source and candidate filenames, and the Hausdorff distance. Identical
+point clouds will return a Hausdorff distance of 0.
+
+::
+
+    $ pdal hausdorff source.las candidate.las
+    {
+      "filenames":
+      [
+        "\/path\/to\/source.las",
+        "\/path\/to\/candidate.las"
+      ],
+      "hausdorff": 1.303648726,
+      "pdal_version": "1.3.0 (git-version: 191301)"
+    }
+
+.. note::
+  
+  The ``hausdorff`` is computed for XYZ coordinates only and as such says
+  nothing about differences in other dimensions or metadata.
diff --git a/doc/apps/pipeline.rst b/doc/apps/pipeline.rst
index cab6094..3635bf7 100644
--- a/doc/apps/pipeline.rst
+++ b/doc/apps/pipeline.rst
@@ -13,21 +13,25 @@ The ``pipeline`` command is used to execute :ref:`pipeline` JSON. See
 
 ::
 
-    --input [-i] arg  Non-positional argument to specify input file name.
-    --pipeline-serialization arg
-                      Write input pipeline along with all metadata and created by the
-                      pipeline to the specified file.
-    --validate        Validate the pipeline (including serialization), but do not execute
-                      writing of points
+    --developer-debug         Enable developer debug (don't trap exceptions)
+    --label                   A string to label the process with
+    --driver                  Override reader driver
+    --input, -i               input file name
+    --pipeline-serialization  Output file for pipeline serialization
+    --validate                Validate the pipeline (including serialization), but do not write
+                              points
+    --progress                Name of file or FIFO to which stages should write progress
+                              information. The file/FIFO must exist. PDAL will not create the progress file.
+    --stdin, -s               Read pipeline from standard input
 
 .. note::
 
-    The ``pipeline`` command can accept option substitutions, but they
-    do not replace existing options that are specified in the input JSON
-    pipeline.  For example, to set the output and input LAS files for a
-    pipeline that does a translation, construct JSON that does not contain
-    ``filename`` for reader and writer and issue the command with the
-    following arguments:
+    The ``pipeline`` command can accept option substitutions, and they replace
+    existing options that are specified in the input JSON pipeline.  If
+    multiple stages of the same name exist in the pipeline, `all` stages would
+    be overridden. For example, to set the output and input LAS files for a
+    pipeline that does a translation, the ``filename`` for the reader and the
+    writer can be overridden:
 
     ::
 
diff --git a/doc/apps/sort.rst b/doc/apps/sort.rst
new file mode 100644
index 0000000..897efea
--- /dev/null
+++ b/doc/apps/sort.rst
@@ -0,0 +1,16 @@
+.. _sort_command:
+
+********************************************************************************
+sort
+********************************************************************************
+
+The ``sort`` command uses :ref:`filters.mortonorder` to sort data by XY values.
+
+::
+
+    $ pdal sort <input> <output>
+
+::
+
+    --input [-i] arg   Non-positional argument to specify input file name.
+    --output [-o] arg  Non-positional argument to specify output file name.
diff --git a/doc/apps/translate.rst b/doc/apps/translate.rst
index de1fe05..629261c 100644
--- a/doc/apps/translate.rst
+++ b/doc/apps/translate.rst
@@ -10,16 +10,18 @@ from the command-line.
 
 ::
 
-    $ pdal translate <input> <output>
+    $ pdal translate [options] input output [filter]
 
 ::
 
-    -i [ --input ] arg    input file name
-    -o [ --output ] arg   output file name
-    -p [ --pipeline ] arg pipeline output
-    -r [ --reader ] arg   reader type
-    -f [ --filter ] arg   filter type
-    -w [ --writer ] arg   writer type
+    --input, -i        Input filename
+    --output, -o       Output filename
+    --filter, -f       Filter type
+    --json             JSON array of filters
+    --pipeline, -p     Pipeline output
+    --metadata, -m     Dump metadata output to the specified file
+    --reader, -r       Reader type
+    --writer, -w       Writer type
 
 The ``--input`` and ``--output`` file names are required options.
 
@@ -27,9 +29,22 @@ The ``--pipeline`` file name is optional. If given, the pipeline constructed
 from the command-line arguments will be written to disk for reuse in the
 :ref:`pipeline_command`.
 
-The ``--filter`` flag is optional. It is used to specify the driver used to
+The ``--json`` flag can use used to specify a JSON array of filters
+as if they were being specified in a :ref:`pipeline_command`.  If a filename
+follows the flag, the file is opened and it is assumed that the file
+contains a valid JSON array of filter specifications.  If the flag value
+is not a filename, the value is taken to be a literal JSON string that is
+the array of filters.  The flag
+can't be used if filters are listed on the command line or explicitly
+with the ``--filter`` option.
+
+The ``--filter`` flag is optional. It is used to specify drivers used to
 filter the data. ``--filter`` accepts multiple arguments if provided, thus
-constructing a multi-stage filtering operation.
+constructing a multi-stage filtering operation.  Filters can't be specified
+using this method and with the ``--json`` flag.
+
+The ``--metadata`` flag accepts a filename for the output of metadata
+associated with the execution of the translate operation.
 
 If no ``--reader`` or ``--writer`` type are given, PDAL will attempt to infer
 the correct drivers from the input and output file name extensions respectively.
@@ -62,15 +77,24 @@ Example 2:
 
 Given these tools, we can now construct a custom pipeline on-the-fly. The
 example below uses a simple LAS reader and writer, but stages a PCL-based
-voxel grid filter, followed by the PCL-based ground filter. We can even set
+voxel grid filter, followed by the PMF filter. We can even set
 stage-specific parameters as shown.
 
 ::
 
-    $ pdal translate input.las output.las \
-        --filter filters.pclblock filters.ground \
+    $ pdal translate input.las output.las pclblock pmf \
         --filters.pclblock.json="{\"pipeline\":{\"filters\":[{\"name\":\"VoxelGrid\"}]}}" \
-        --filters.ground.approximate=true --filters.ground.extract=true
+        --filters.pmf.approximate=true --filters.pmf.extract=true
 
+Example 3:
+--------------------------------------------------------------------------------
 
+This command reads the input text file "myfile" and writes an output LAS file
+"output.las", processing the data through the stats filter.  The metadata
+output (including the output from the stats filter) is written to the file
+"meta.json".
+
+::
 
+    $ pdal translate myfile output.las --metadata=meta.json -r readers.text \
+        --json="[ { \"type\":\"filters.stats\" } ]"
diff --git a/doc/apps/view.rst b/doc/apps/view.rst
deleted file mode 100644
index fb43b31..0000000
--- a/doc/apps/view.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-.. _view_command:
-
-********************************************************************************
-view
-********************************************************************************
-
-The ``view`` command can be used to visualize a point cloud using the
-PCLVisualizer. The command takes a single argument, the input file name.
-
-.. note::
-
-    The ``view`` command is only available when PDAL is linked with PCL.
-
-::
-
-    $ pdal view <input>
-
-Once the data has been loaded into the viewer, press h or H to display the
-help.
-
-::
-
-    | Help:
-    -------
-              p, P   : switch to a point-based representation
-              w, W   : switch to a wireframe-based representation (where available)
-              s, S   : switch to a surface-based representation (where available)
-
-              j, J   : take a .PNG snapshot of the current window view
-              c, C   : display current camera/window parameters
-              f, F   : fly to point mode
-
-              e, E   : exit the interactor
-              q, Q   : stop and call VTK's TerminateApp
-
-               +/-   : increment/decrement overall point size
-         +/- [+ ALT] : zoom in/out
-
-              g, G   : display scale grid (on/off)
-              u, U   : display lookup table (on/off)
-
-        o, O         : switch between perspective/parallel projection (default = perspective)
-        r, R [+ ALT] : reset camera [to viewpoint = {0, 0, 0} -> center_{x, y, z}]
-        CTRL + s, S  : save camera parameters
-        CTRL + r, R  : restore camera parameters
-
-        ALT + s, S   : turn stereo mode on/off
-        ALT + f, F   : switch between maximized window mode and original size
-
-              l, L           : list all available geometric and color handlers for the current actor map
-        ALT + 0..9 [+ CTRL]  : switch between different geometric handlers (where available)
-              0..9 [+ CTRL]  : switch between different color handlers (where available)
-
-        SHIFT + left click   : select a point (start with -use_point_picking)
-
-              x, X   : toggle rubber band selection mode for left mouse button
-
-
diff --git a/doc/conf.py b/doc/conf.py
index 5b8d6b2..e64b67e 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -21,6 +21,27 @@ else:
     year  = datetime.datetime.now().year
 
 
+
+def process_dimensions():
+    import json, csv
+
+    data = open('../pdal/Dimension.json','rb').read()
+
+    data = json.loads(data)['dimensions']
+
+    output = []
+    for dim in data:
+        output.append([dim['name'], dim['type'], dim['description']])
+
+    output = sorted(output,key=lambda x: x[0])
+
+    with open('dimension-table.csv','wb') as fp:
+        a = csv.writer(fp, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)
+        a.writerows(output)
+
+
+process_dimensions()
+
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
diff --git a/doc/development/compilation/dependencies.rst b/doc/development/compilation/dependencies.rst
index ff00dbb..bac0c95 100644
--- a/doc/development/compilation/dependencies.rst
+++ b/doc/development/compilation/dependencies.rst
@@ -159,15 +159,15 @@ LAS files. It is also used as a compression type for :ref:`writers.oci` and
 PCL
 ..............................................................................
 
-The `Point Cloud Library (PCL)`_ is used by the :ref:`ground_command`,
-:ref:`pcl_command`, :ref:`writers.pcd`, :ref:`readers.pcd`, and
-:ref:`filters.pclblock` to provide support for various PCL-related operations.
+The `Point Cloud Library (PCL)`_ is used by the :ref:`pcl_command`,
+:ref:`writers.pcd`, :ref:`readers.pcd`, and :ref:`filters.pclblock` to provide
+support for various PCL-related operations.
 
 PCL must be 1.7.2+. We do our best to keep this up-to-date with PCL master.
 
 .. note::
     `Homebrew`_-based OSX builds use PCL 1.7.2, but you may need to switch
-    of `VTK`_ support depending on the configuration.
+    off `VTK`_ support depending on the configuration.
 
 .. _`Homebrew`: http://brew.sh
 .. _`VTK`: http://vtk.org
@@ -195,4 +195,3 @@ PCL must be 1.7.2+. We do our best to keep this up-to-date with PCL master.
 
 .. _`Points2Grid`: https://github.com/CRREL/points2grid
 .. _`Point Cloud Library (PCL)`: http://pointclouds.org
-
diff --git a/doc/development/contributors.rst b/doc/development/contributors.rst
index 2769574..23a5a81 100644
--- a/doc/development/contributors.rst
+++ b/doc/development/contributors.rst
@@ -42,10 +42,8 @@ implementations.
 
 `Bradley Chambers`_ from `RadiantBlue`_ has contributed numerous features and
 capabilities to the PDAL project, including :ref:`Poisson sampling
-<dart-throwing-tutorial>`, :ref:`Progressive Morphological Filters <pcl_ground>`, and
-:ref:`PCL Visualizer <writers.pclvisualizer>`. He is also a prolific
-:ref:`tutorial` writer, and he maintains the :ref:`Windows builds <download>`
-for the project.
+<dart-throwing-tutorial>` and :ref:`Progressive Morphological Filters <pcl_ground>`. He is also a prolific
+:ref:`tutorial` writer.
 
 .. _`Bradley Chambers`: https://github.com/chambbj
 .. _`RadiantBlue`: http://radiantblue.com/
@@ -79,4 +77,3 @@ development, and `PCL <http://pointclouds.org>`_ integration.
 .. _`GRiD`: http://lidar.io/about.html
 .. _`LiDAR Magazine article`: http://www.lidarmag.com/content/view/11343/198/
 .. _`CRREL`: http://www.erdc.usace.army.mil/Locations/CRREL.aspx
-
diff --git a/doc/development/docs.rst b/doc/development/docs.rst
index 48a55d3..c6de3ba 100644
--- a/doc/development/docs.rst
+++ b/doc/development/docs.rst
@@ -37,7 +37,7 @@ Python dependencies should be installed from PyPI_ with ``pip`` or
 
 .. code-block:: bash
 
-    (sudo) pip install sphinx breathe
+    (sudo) pip install sphinx sphinxconfig-bibtex breathe
 
 .. note::
 
diff --git a/doc/dimensions.rst b/doc/dimensions.rst
new file mode 100644
index 0000000..e83827e
--- /dev/null
+++ b/doc/dimensions.rst
@@ -0,0 +1,15 @@
+.. _dimensions:
+
+===============================================================================
+Dimensions
+===============================================================================
+
+PDAL dimensions describe the combination of data's type, size, and meaning. The
+following table provides a list of known dimension names you can use in
+:ref:`filters`, :ref:`writers`, and :ref:`readers` descriptions.
+
+
+.. csv-table::
+    :file: ./dimension-table.csv
+    :header: "Name", "Type", "Description"
+    :widths: 10, 5, 40
diff --git a/doc/download.rst b/doc/download.rst
index 1322a56..30599a6 100644
--- a/doc/download.rst
+++ b/doc/download.rst
@@ -13,22 +13,24 @@ Download
 Current Release(s)
 ------------------------------------------------------------------------------
 
-* **2016-03-31** `PDAL-1.2.0-src.tar.gz`_ `Release Notes`_ (`md5`_)
+* **2016-12-15** `PDAL-1.4.0-src.tar.gz`_ `Release Notes`_ (`md5`_)
 
-.. _`Release Notes`: https://github.com/PDAL/PDAL/releases/tag/1.2.0
+.. _`Release Notes`: https://github.com/PDAL/PDAL/releases/tag/1.4.0
 
-.. _`PDAL-1.2.0-src.tar.gz`: http://download.osgeo.org/pdal/PDAL-1.2.0-src.tar.gz
-.. _`md5`: http://download.osgeo.org/pdal/PDAL-1.2.0-src.tar.gz.md5
+.. _`PDAL-1.4.0-src.tar.gz`: http://download.osgeo.org/pdal/PDAL-1.4.0-src.tar.gz
+.. _`md5`: http://download.osgeo.org/pdal/PDAL-1.4.0-src.tar.gz.md5
 .. _`DebianGIS`: http://wiki.debian.org/DebianGis
 
 
 Past Releases
 ------------------------------------------------------------------------------
 
-* **2015-11-25** `PDAL-1.1.0-src.tar.gz`_ `Release Notes`_ (`md5`_)
+* **2016-08-29** `PDAL-1.3.0-src.tar.gz`_ `Release Notes`_
+* **2016-03-31** `PDAL-1.2.0-src.tar.gz`_ `Release Notes`_
 
 
-.. _`PDAL-1.1.0-src.tar.gz`: http://download.osgeo.org/pdal/PDAL-1.1.0-src.tar.gz
+.. _`PDAL-1.3.0-src.tar.gz`: http://download.osgeo.org/pdal/PDAL-1.3.0-src.tar.gz
+.. _`PDAL-1.2.0-src.tar.gz`: http://download.osgeo.org/pdal/PDAL-1.2.0-src.tar.gz
 
 
 
@@ -63,7 +65,8 @@ Windows
 ................................................................................
 
 A 1.1.0 release of PDAL is available via `OSGeo4W`_. It is only 64-bit at this
-time.
+time. Use the :ref:`docker` builds if you want to use the PDAL :ref:`apps`, otherwise,
+a call for help with building current Windows PDAL builds is at https://lists.osgeo.org/pipermail/pdal/2016-November/001089.html
 
 RPMs
 ................................................................................
diff --git a/doc/images/awesome-green.png b/doc/images/awesome-green.png
new file mode 100644
index 0000000..023ce9e
Binary files /dev/null and b/doc/images/awesome-green.png differ
diff --git a/doc/images/black-orange.png b/doc/images/black-orange.png
new file mode 100644
index 0000000..00e9b87
Binary files /dev/null and b/doc/images/black-orange.png differ
diff --git a/doc/images/blue-hue.png b/doc/images/blue-hue.png
new file mode 100644
index 0000000..124d47a
Binary files /dev/null and b/doc/images/blue-hue.png differ
diff --git a/doc/images/blue-orange.png b/doc/images/blue-orange.png
new file mode 100644
index 0000000..378a057
Binary files /dev/null and b/doc/images/blue-orange.png differ
diff --git a/doc/images/blue-red.png b/doc/images/blue-red.png
new file mode 100644
index 0000000..1c77abd
Binary files /dev/null and b/doc/images/blue-red.png differ
diff --git a/doc/images/heat-map.png b/doc/images/heat-map.png
new file mode 100644
index 0000000..1d03cab
Binary files /dev/null and b/doc/images/heat-map.png differ
diff --git a/doc/images/pestel-shades.png b/doc/images/pestel-shades.png
new file mode 100644
index 0000000..dd3b871
Binary files /dev/null and b/doc/images/pestel-shades.png differ
diff --git a/doc/images/pestel_scaled_helheim.png b/doc/images/pestel_scaled_helheim.png
new file mode 100644
index 0000000..08134d5
Binary files /dev/null and b/doc/images/pestel_scaled_helheim.png differ
diff --git a/doc/images/pestel_scaled_plasio.png b/doc/images/pestel_scaled_plasio.png
new file mode 100644
index 0000000..6308c19
Binary files /dev/null and b/doc/images/pestel_scaled_plasio.png differ
diff --git a/doc/index.rst b/doc/index.rst
index 9578dff..52dbaeb 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -22,21 +22,14 @@ The entire website is available as a single PDF at http://pdal.io/PDAL.pdf
 News
 --------------------------------------------------------------------------------
 
-**04-24-2016**
+**08-29-2016**
 ................................................................................
 
-See `Howard Butler`_ and `Brad Chambers`_ give PDAL talks at
-`FOSS4GNA 2016`_ in Raleigh, NC May 2-5. Howard will be presenting `Point cloud web
-services with Greyhound, Entwine, and PDAL`_, and Brad will be presenting `Filtering
-point clouds with PDAL and PCL`_.
+PDAL 1.3.0 has been released. Visit :ref:`download` to obtain a copy of the
+source code, or follow the :ref:`quickstart` to get going in a hurry with
+`Docker`_.
 
-
-.. note::
-
-    Catch up with Brad and Howard to get a PDAL sticker.
-
-    .. image:: ./images/iheartpdal.png
-        :scale: 60%
+.. _`Docker`: https://www.docker.com/
 
 .. _`Howard Butler`: http://github.com/hobu
 .. _`Brad Chambers`: http://github.com/chambbj
@@ -53,6 +46,22 @@ Download
 
    download
 
+Quickstart
+--------------------------------------------------------------------------------
+
+.. toctree::
+   :maxdepth: 2
+
+   quickstart
+
+Applications
+--------------------------------------------------------------------------------
+
+.. toctree::
+   :maxdepth: 2
+
+   apps/index
+
 Community
 --------------------------------------------------------------------------------
 
@@ -61,19 +70,26 @@ Community
 
    community
 
-Usage
+Drivers
 --------------------------------------------------------------------------------
 
 .. toctree::
    :maxdepth: 2
    :glob:
 
-   apps/index
-   quickstart
+   pipeline
    stages/readers
    stages/writers
    stages/filters
-   pipeline
+   dimensions
+
+Tutorials
+--------------------------------------------------------------------------------
+
+.. toctree::
+   :maxdepth: 2
+   :glob:
+
    tutorial/index
 
 Workshop
@@ -95,6 +111,11 @@ Development
    faq
    copyright
 
+.. toctree::
+   :includehidden:
+
+   references
+
 
 
 
diff --git a/doc/pipeline.rst b/doc/pipeline.rst
index 3a9c420..27da6de 100644
--- a/doc/pipeline.rst
+++ b/doc/pipeline.rst
@@ -182,7 +182,8 @@ For more on PDAL stages and their options, check the PDAL documentation on
   a string. The ``type`` must specify a valid PDAL filter name.
 
 * A stage object may have additional members with names corresponding to
-  stage-specific option names and their respective values.
+  stage-specific option names and their respective values. Values provided as
+  JSON objects or arrays will be stringified and parsed within the stage.
 
 Filename Globbing
 ................................................................................
@@ -230,7 +231,7 @@ written back into the Z dimension for further analysis.
 
 .. seealso::
 
-    :ref:`filters.height` describes using a specific filter to do
+    :ref:`filters.hag` describes using a specific filter to do
     this job in more detail.
 
 .. code-block:: json
@@ -257,7 +258,7 @@ DTM
 
 A common task is to create a digital terrain model (DTM) from the input point
 cloud. This pipeline infers the reader type, applies an approximate ground
-segmentation filter using :ref:`filters.ground`, and then creates the DTM using
+segmentation filter using :ref:`filters.pmf`, and then creates the DTM using
 the :ref:`writers.p2g` with only the ground returns.
 
 .. code-block:: json
@@ -343,7 +344,7 @@ spatial reference system.
               "spatialreference":"EPSG:2027"
           },
           {
-              "type":"merge"
+              "type":"filters.merge"
           },
           {
               "type":"reprojection",
@@ -363,9 +364,6 @@ multiple input LAS files from a given directory.
   {
       "pipeline":[
           "/path/to/data/\*.las",
-          {
-              "type":"merge"
-          },
           "output.las"
       ]
   }
@@ -496,6 +494,3 @@ for the :ref:`writers.las` :cpp:class:`pdal::Stage`.
         }
       ]
     }
-
-
-
diff --git a/doc/quickstart.rst b/doc/quickstart.rst
index 64474c2..5df5745 100644
--- a/doc/quickstart.rst
+++ b/doc/quickstart.rst
@@ -76,7 +76,7 @@ whatever reason.
 
 ::
 
-    docker pull pdal/pdal:1.3
+    docker pull pdal/pdal:1.4
 
 
 .. image:: ./images/docker-quickstart-pull.png
@@ -84,7 +84,7 @@ whatever reason.
 .. note::
 
     Other PDAL versions are provided at the same `Docker Hub`_ location,
-    with an expected tag name (ie ``pdal/pdal:1.3``, or ``pdal/pdal:1.x``) for
+    with an expected tag name (ie ``pdal/pdal:1.4``, or ``pdal/pdal:1.x``) for
     major PDAL versions. The PDAL Docker hub location at
     https://hub.docker.com/u/pdal/ has images and more information
     on this topic.
@@ -114,7 +114,7 @@ Print the first point
 
 ::
 
-    docker run -v /c/Users/hobu:/data pdal/pdal:1.3 pdal info /data/autzen.laz -p 0
+    docker run -v /c/Users/hobu:/data pdal/pdal:1.4 pdal info /data/autzen.laz -p 0
 
 Here's a summary of what's going on with that command invocation
 
@@ -132,7 +132,7 @@ Here's a summary of what's going on with that command invocation
        The `Docker Volume <https://docs.docker.com/engine/userguide/dockervolumes/>`__
        document describes mounting volumes in more detail.
 
-4. ``pdal/pdal:1.3``: This is the Docker image we are going to run. We fetched it
+4. ``pdal/pdal:1.4``: This is the Docker image we are going to run. We fetched it
    with the command above. If it were not already fetched, Docker would attempt
    to fetch it when we run this command.
 
diff --git a/doc/references.rst b/doc/references.rst
new file mode 100644
index 0000000..d9adcbd
--- /dev/null
+++ b/doc/references.rst
@@ -0,0 +1,20 @@
+.. _references:
+
+******************************************************************************
+References
+******************************************************************************
+
+.. index:: References
+
+
+.. [Cook1986] Cook, Robert L. "Stochastic sampling in computer graphics." *ACM Transactions on Graphics (TOG)* 5.1 (1986): 51-72.
+
+.. [Dippe1985] Dippé, Mark AZ, and Erling Henry Wold. "Antialiasing through stochastic sampling." *ACM Siggraph Computer Graphics* 19.3 (1985): 69-78.
+
+.. [Mesh2009] ALoopingIcon. "Meshing Point Clouds." *MESHLAB STUFF*. n.p., 7 Sept. 2009. Web. 13 Nov. 2015.
+
+.. [Rusu2008] Rusu, Radu Bogdan, et al. "Towards 3D point cloud based object maps for household environments." Robotics and Autonomous Systems 56.11 (2008): 927-941.
+
+.. [Zhang2003] Zhang, Keqi, et al. "A progressive morphological filter for removing nonground measurements from airborne LIDAR data." Geoscience and Remote Sensing, IEEE Transactions on 41.4 (2003): 872-882.
+
+
diff --git a/doc/stages/filters.colorinterp.rst b/doc/stages/filters.colorinterp.rst
new file mode 100644
index 0000000..c5308a7
--- /dev/null
+++ b/doc/stages/filters.colorinterp.rst
@@ -0,0 +1,148 @@
+.. _filters.colorinterp:
+
+filters.colorinterp
+====================
+
+The color interpolation filter assigns scaled RGB values from an image based on
+a given dimension.  It provides three possible approaches:
+
+1. You provide a ``minimum`` and ``maximum``, and the data are scaled for the
+   given ``dimension`` accordingly.
+
+2. You provide a ``k`` and a ``mad`` setting, and the scaling is set based on
+   Median Absolute Deviation.
+
+3. You provide a ``k`` setting and the scaling is set based on the
+   ``k``-number of standard deviations from the median.
+
+You can provide your own `GDAL`_-readable image for the scale color factors,
+but a number of pre-defined ramps are embedded in PDAL.  The default ramps
+provided by PDAL are 256x1 RGB images, and might be a good starting point for
+creating your own scale factors. See `Default Ramps`_ for more information.
+
+.. note::
+
+    :ref:`filters.colorinterp` will use the entire band to scale the colors.
+
+.. code-block:: json
+
+  {
+    "pipeline":[
+      "uncolored.las",
+      {
+        "type":"filters.colorinterp",
+        "ramp":"pestel_shades",
+        "mad":true,
+        "k":1.8,
+        "dimension":"Z"
+      },
+      "colorized.las"
+    ]
+  }
+
+.. figure:: ../images/pestel_scaled_helheim.png
+    :scale: 80%
+
+    Image data with interpolated colors based on ``Z`` dimension and ``pestel_shades``
+    ramp.
+
+Default Ramps
+--------------------------------------------------------------------------------
+
+PDAL provides a number of default color ramps you can use in addition to
+providing your own. Give the ramp name as the ``ramp`` option to the filter
+and it will be used. Otherwise, provide a `GDAL`_-readable raster filename.
+
+``awesome_green``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. image:: ../images/awesome-green.png
+    :scale: 400%
+    :alt: awesome-green color ramp
+
+``black_orange``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. image:: ../images/black-orange.png
+    :scale: 400%
+    :alt: black-orange color ramp
+
+``blue_orange``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. image:: ../images/blue-orange.png
+    :scale: 400%
+    :alt: blue-orange color ramp
+
+``blue_hue``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. image:: ../images/blue-hue.png
+    :scale: 400%
+    :alt: blue-hue color ramp
+
+``blue_orange``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. image:: ../images/blue-orange.png
+    :scale: 400%
+    :alt: blue-orange color ramp
+
+``blue_red``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. image:: ../images/blue-red.png
+    :scale: 400%
+    :alt: blue-red color ramp
+
+``heat_map``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. image:: ../images/heat-map.png
+    :scale: 400%
+    :alt: heat-map color ramp
+
+``pestel_shades``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. image:: ../images/pestel-shades.png
+    :scale: 400%
+    :alt: pestel-shades color ramp
+
+Options
+-------
+
+ramp
+  The raster file to use for the color ramp. Any format supported by `GDAL`_ may be read.
+  Alternatively, one of the default color ramp names can be used. [Default: ``pestel_shades``]
+
+dimension
+  A dimension name to use for the values to interpolate colors. [Default: ``Z``]
+
+minimum
+  The minimum value to use to scale the data. If none is specified, one is
+  computed from the data. If one is specified but a ``k`` value is also
+  provided, the ``k`` value will be used.
+
+maximum
+  The maximum value to use to scale the data. If none is specified, one is
+  computed from the data. If one is specified but a ``k`` value is also
+  provided, the ``k`` value will be used.
+
+invert
+  Invert the direction of the ramp? [Default: false]
+
+k
+  Color based on the given number of standard deviations from the median. If
+  set, ``minimum`` and ``maximum`` will be computed from the median and setting
+  them will have no effect.
+
+mad
+  If true, ``minimum`` and ``maximum`` will be computed by the median absolute
+  deviation. See :ref:`filters.mad` for discussion. [Default: false]
+
+mad_multiplier
+  MAD threshold multiplier. Used in conjunction with ``k`` to threshold the
+  diferencing. [Default: 1.4862]
+
+.. _`GDAL`: http://www.gdal.org
diff --git a/doc/stages/filters.computerange.rst b/doc/stages/filters.computerange.rst
new file mode 100644
index 0000000..c854562
--- /dev/null
+++ b/doc/stages/filters.computerange.rst
@@ -0,0 +1,33 @@
+.. _filters.computerange:
+
+===============================================================================
+filters.computerange
+===============================================================================
+
+The Compute Range filter computes the range from the sensor to each of the
+detected returns.
+
+.. note::
+  The Compute Range filter is specific to raw data from a particular data
+  provider, where the sensor coordinates for each frame are encoded as regular
+  points, and are identified by the pixel number -5.
+
+Example
+-------------------------------------------------------------------------------
+
+.. code-block:: json
+
+    {
+      "pipeline":[
+        "input.bpf",
+        {
+          "type":"filters.computerange"
+        },
+        {
+          "type":"writers.bpf",
+          "filename":"output.bpf",
+          "output_dims":"X,Y,Z,Range"
+        }
+      ]
+    }
+
diff --git a/doc/stages/filters.crop.rst b/doc/stages/filters.crop.rst
index 50c82e8..add6e4e 100644
--- a/doc/stages/filters.crop.rst
+++ b/doc/stages/filters.crop.rst
@@ -4,10 +4,9 @@ filters.crop
 ============
 
 The crop filter removes points that fall outside or inside a cropping bounding
-box (2D)
-or polygon.  If more than one bounding region is specified, the filter will
-pass all input points through each bounding region, creating an output point
-set for each input crop region.
+box (2D), polygon, or point+radius.  If more than one bounding region is
+specified, the filter will pass all input points through each bounding region,
+creating an output point set for each input crop region.
 
 Example
 -------
@@ -42,3 +41,8 @@ polygon
 outside
   Invert the cropping logic and only take points **outside** the cropping bounds or polygon. [Default: **false**]
 
+point
+  An array of WKT or GeoJSON 2D or 3D points. Requires ``radius``.
+
+radius
+  Distance in units of common X, Y, and Z :ref:`dimensions` to crop circle or sphere in combination with ``point``.
diff --git a/doc/stages/filters.dartsample.rst b/doc/stages/filters.dartsample.rst
deleted file mode 100644
index e739880..0000000
--- a/doc/stages/filters.dartsample.rst
+++ /dev/null
@@ -1,30 +0,0 @@
-.. _filters.dartsample:
-
-===============================================================================
-filters.dartsample
-===============================================================================
-
-The practice of performing Poisson sampling via "Dart Throwing" was introduced
-in the mid-1980's by [Cook1986]_ and [Dippe1985]_, and has been applied to
-point clouds in other software [Mesh2009]_. Our implementation is a brute force
-approach that randomly selects points from the input ``PointView``, adding them
-to the output ``PointView`` subject to the minimum distance constraint (the
-``radius``). The full layout (i.e., the dimensions) of the input ``PointView``
-is kept in tact (the same cannot be said for :ref:`filters.voxelgrid`).
-
-.. seealso::
-
-    :ref:`filters.decimation` and :ref:`filters.voxelgrid` also perform
-    decimation.
-
-.. [Cook1986] Cook, Robert L. "Stochastic sampling in computer graphics." *ACM Transactions on Graphics (TOG)* 5.1 (1986): 51-72.
-
-.. [Dippe1985] Dippé, Mark AZ, and Erling Henry Wold. "Antialiasing through stochastic sampling." *ACM Siggraph Computer Graphics* 19.3 (1985): 69-78.
-
-.. [Mesh2009] ALoopingIcon. "Meshing Point Clouds." *MESHLAB STUFF*. n.p., 7 Sept. 2009. Web. 13 Nov. 2015.
-
-Options
--------------------------------------------------------------------------------
-
-radius
-  Minimum distance between samples. [Default: **1.0**]
diff --git a/doc/stages/filters.decimation.rst b/doc/stages/filters.decimation.rst
index 6706d2e..68d3fd8 100644
--- a/doc/stages/filters.decimation.rst
+++ b/doc/stages/filters.decimation.rst
@@ -8,20 +8,24 @@ The decimation filter retains every Nth point from an input point view.
 Example
 -------
 
-.. code-block:: xml
-
-  <?xml version="1.0" encoding="utf-8"?>
-  <Pipeline version="1.0">
-    <Writer type="writers.las">
-      <Option name="filename">smaller.las</Option>
-      <Filter type="filters.decimation">
-        <Option name="step">10</Option>
-        <Reader type="readers.las">
-            <Option name="filename">larger.las</Option>
-        </Reader>
-      </Filter>
-    </Writer>
-  </Pipeline>
+.. code-block:: json
+
+    {
+      "pipeline":[
+        {
+            "type": "readers.las",
+            "filename": "larger.las"
+        },
+        {
+            "type":"filters.decimation",
+            "step": 10
+        },
+        {
+          "type":"writers.las",
+          "filename":"smaller.las"
+        }
+      ]
+    }
 
 .. seealso::
 
diff --git a/doc/stages/filters.ground.rst b/doc/stages/filters.ground.rst
deleted file mode 100644
index d0c8711..0000000
--- a/doc/stages/filters.ground.rst
+++ /dev/null
@@ -1,60 +0,0 @@
-.. _filters.ground:
-
-filters.ground
-===============================================================================
-
-The Ground filter passes data through the Point Cloud Library (`PCL`_)
-ProgressiveMorphologicalFilter algorithm.
-
-ProgressiveMorphologicalFilter is an implementation of the method described in
-[Zhang2003]_.
-
-.. [Zhang2003] Zhang, Keqi, et al. "A progressive morphological filter for removing nonground measurements from airborne LIDAR data." Geoscience and Remote Sensing, IEEE Transactions on 41.4 (2003): 872-882.
-
-.. _`PCL`: http://www.pointclouds.org
-
-
-Example
--------
-
-.. code-block:: json
-
-    {
-      "pipeline":[
-        "input.las",
-        {
-          "type":"filters.ground"
-        },
-        {
-          "type":"writers.las",
-          "filename":"output.las"
-        }
-      ]
-    }
-
-Options
--------------------------------------------------------------------------------
-
-max_window_size
-  Maximum window size. [Default: **33**]
-
-slope
-  Slope. [Default: **1.0**]
-
-max_distance
-  Maximum distance. [Default: **2.5**]
-
-initial_distance
-  Initial distance. [Default: **0.15**]
-
-cell_size
-  Cell Size. [Default: **1**]
-
-classify
-  Apply classification labels? [Default: **true**]
-
-extract
-  Extract ground returns? [Default: **false**]
-
-approximate
-  Use approximate algorithm? [Default:: **false**]
diff --git a/doc/stages/filters.height.rst b/doc/stages/filters.height.rst
deleted file mode 100644
index c9a5a11..0000000
--- a/doc/stages/filters.height.rst
+++ /dev/null
@@ -1,36 +0,0 @@
-.. _filters.height:
-
-filters.height
-===============================================================================
-
-The Height filter takes as input a point cloud with a Classification dimension,
-with ground points assigned the classification label of 2 (per LAS
-specification). It returns a point cloud with a new dimension ``HeightAboveGround`` that
-contains the normalized height value.
-
-Example
--------
-
-.. code-block:: json
-
-    {
-      "pipeline":[
-        "input.las",
-        {
-          "type":"filters.height"
-        },
-        {
-          "type":"filters.ferry",
-          "dimensions":"HeightAboveGround = Z",
-        },
-        {
-          "type":"writers.las",
-          "filename":"output.las"
-        }
-      ]
-    }
-
-Options
--------------------------------------------------------------------------------
-
-None
diff --git a/doc/stages/filters.iqr.rst b/doc/stages/filters.iqr.rst
new file mode 100644
index 0000000..ba34fe7
--- /dev/null
+++ b/doc/stages/filters.iqr.rst
@@ -0,0 +1,51 @@
+.. _filters.iqr:
+
+filters.iqr
+===============================================================================
+
+The ``filters.iqr`` filter automatically crops the input point cloud based on
+the distribution of points in the specified dimension. Specifically, we choose
+the method of Interquartile Range (IQR). The IQR is defined as the range between
+the first and third quartile (25th and 75th percentile). Upper and lower bounds
+are determined by adding 1.5 times the IQR to the third quartile or subtracting
+1.5 times the IQR from the first quartile. The multiplier, which defaults to
+1.5, can be adjusted by the user.
+
+.. note::
+  
+  This method can remove real data, especially ridges and valleys in rugged
+  terrain, or tall features such as towers and rooftops in flat terrain. While
+  the number of deviations can be adjusted to account for such content-specific
+  considerations, it must be used with care.
+
+Example
+-------
+
+The sample pipeline below uses ``filters.iqr`` to automatically crop the Z
+dimension and remove possible outliers. The multiplier to determine high/low
+thresholds has been adjusted to be less agressive and to only crop those
+outliers that are greater than the third quartile plus 3 times the IQR or are
+less than the first quartile minus 3 times the IQR.
+
+.. code-block:: json
+
+    {
+      "pipeline":[
+        "input.las",
+        {
+          "type":"filters.iqr",
+          "dimension":"Z",
+          "k":3.0
+        },
+        "output.laz"
+      ]
+    }
+
+Options
+-------------------------------------------------------------------------------
+
+k
+  The IQR multiplier used to determine upper/lower bounds. [Default: **1.5**]
+  
+dimension
+  The name of the dimension to filter.
diff --git a/doc/stages/filters.kdistance.rst b/doc/stages/filters.kdistance.rst
new file mode 100644
index 0000000..939cfce
--- /dev/null
+++ b/doc/stages/filters.kdistance.rst
@@ -0,0 +1,36 @@
+.. _filters.kdistance:
+
+===============================================================================
+filters.kdistance
+===============================================================================
+
+The K-Distance filter creates a new attribute ``KDistance`` that contains the
+Euclidean distance to a point's k-th nearest neighbor.
+
+Example
+-------------------------------------------------------------------------------
+
+.. code-block:: json
+
+    {
+      "pipeline":[
+        "input.las",
+        {
+          "type":"filters.kdistance",
+          "k":8
+        },
+        {
+          "type":"writers.bpf",
+          "filename":"output.las",
+          "output_dims":"X,Y,Z,KDistance"
+        }
+      ]
+    }
+
+
+Options
+-------------------------------------------------------------------------------
+
+k
+  The number of k nearest neighbors. [Default: **10**]
+
diff --git a/doc/stages/filters.lof.rst b/doc/stages/filters.lof.rst
new file mode 100644
index 0000000..71c5285
--- /dev/null
+++ b/doc/stages/filters.lof.rst
@@ -0,0 +1,61 @@
+.. _filters.lof:
+
+filters.lof
+===============================================================================
+
+Local Outlier Factor (LOF) was introduced as a method of determining the degree
+to which an object is an outlier. This filter is an implementation of the method
+described in [Breunig2000]_.
+
+The filter creates three new dimensions, all of which are doubles. The
+``KDistance`` dimension records the Euclidean distance between a point and it's
+``k-th`` nearest neighbor (the number of ``k`` neighbors is set with the
+``minpts`` option). The ``LocalReachabilityDistance`` is the inverse of the mean
+of all reachability distances for a neighborhood of points. This reachability
+distance is defined as the max of the Euclidean distance to a neighboring point
+and that neighbor's own previously computed ``KDistance``. Finally, each point
+has a ``LocalOutlierFactor`` which is the mean of all
+``LocalReachabilityDistance`` values for the neighborhood. In each case, the
+neighborhood is the set of ``k`` nearest neighbors, where ``k`` is set with the
+``minpts`` option.
+
+In practice, setting the ``minpts`` parameter appropriately and subsequently
+filtering outliers based on the computed ``LocalOutlierFactor`` can be
+difficult. The authors present some work on establishing upper and lower bounds
+on LOF values, and provide some guidelines on selecting ``minpts`` values, which
+users of ``filters.lof`` should find instructive.
+
+.. note::
+  
+  To inspect the newly created, non-standard dimensions, be sure to write to an
+  output format that can support arbitrary dimensions, such as BPF.
+  
+.. [Breunig2000] Breunig, M.M., Kriegel, H.-P., Ng, R.T., Sander, J., 2000. LOF: Identifying Density-Based Local Outliers. Proc. 2000 Acm Sigmod Int. Conf. Manag. Data 1–12.
+
+Example
+-------
+
+The sample pipeline below uses ``filters.lof`` to compute the LOF with a neighborhood of 20 neighbors, followed by a range filter to crop out points whose ``LocalOutlierFactor`` exceeds 1.2, before writing the output.
+
+.. code-block:: json
+
+    {
+      "pipeline":[
+        "input.las",
+        {
+          "type":"filters.lof",
+          "minpts":20
+        },
+        {
+          "type":"filters.range",
+          "limits":"LocalOutlierFactor[:1.2]"
+        },
+        "output.laz"
+      ]
+    }
+
+Options
+-------------------------------------------------------------------------------
+
+minpts
+  The number of k nearest neighbors. [Default: **10**]
diff --git a/doc/stages/filters.mad.rst b/doc/stages/filters.mad.rst
new file mode 100644
index 0000000..e9bbcf7
--- /dev/null
+++ b/doc/stages/filters.mad.rst
@@ -0,0 +1,47 @@
+.. _filters.mad:
+
+filters.mad
+===============================================================================
+
+The ``filters.mad`` filter automatically crops the input point cloud based on
+the distribution of points in the specified dimension. Specifically, we choose
+the method of median absolute deviation from the median (commonly referred to as
+MAD), which is robust to outliers (as opposed to mean and standard deviation).
+
+.. note::
+  
+  This method can remove real data, especially ridges and valleys in rugged
+  terrain, or tall features such as towers and rooftops in flat terrain. While
+  the number of deviations can be adjusted to account for such content-specific
+  considerations, it must be used with care.
+
+Example
+-------
+
+The sample pipeline below uses ``filters.mad`` to automatically crop the Z
+dimension and remove possible outliers. The number of deviations from the median
+has been adjusted to be less agressive and to only crop those outliers that are
+greater than four deviations from the median.
+
+.. code-block:: json
+
+    {
+      "pipeline":[
+        "input.las",
+        {
+          "type":"filters.mad",
+          "dimension":"Z",
+          "k":4.0
+        },
+        "output.laz"
+      ]
+    }
+
+Options
+-------------------------------------------------------------------------------
+
+k
+  The number of deviations from the median. [Default: **2.0**]
+
+dimension
+  The name of the dimension to filter.
diff --git a/doc/stages/filters.merge.rst b/doc/stages/filters.merge.rst
index d6c51c2..d1a382e 100644
--- a/doc/stages/filters.merge.rst
+++ b/doc/stages/filters.merge.rst
@@ -4,34 +4,78 @@ filters.merge
 ===============================================================================
 
 The merge filter combines input from multiple sources into a single output.
-No checks are made to ensure that points from the various sources have similar
-dimensions or are generally compatible.  Notably, dimensions are not
-initialized when points merged from various sources do not have dimensions in
-common.
+In most cases, this happens automatically on output and use of the merge
+filter is unnecessary.  However, there may be special cases where
+merging points prior to a particular filter or writer is necessary
+or desirable.
 
-Example
--------
+The merge filter will log a warning if its input point sets are based on
+different spatial references.  No checks are made to ensure that points
+from various sources being merged have similar dimensions or are generally
+compatible.  Notably, dimensions are not initialized when points merged
+from various sources do not have dimensions in common.
+
+Example 1
+---------
+
+This pipeline will create an output file "output.las" that contcatenates
+the points from "file1", "file2" and "file3".  Note that the explicit
+use of the merge filter is unnecessary in this case (removing the merge
+filter will yield the same result).
 
 .. code-block:: json
 
     {
       "pipeline": [
+        "file1",
+        "file2",
+        "file3",
         {
-          "filename": "/Users/hobu/dev/git/pdal/test/data/las/1.2-with-color.las",
-          "tag": "A"
-        },
-        {
-          "filename": "/Users/hobu/dev/git/pdal/test/data/las/1.2-with-color.las",
-          "tag": "B"
+          "type": "filters.merge"
         },
+        "output.las"
+      ]
+    }
+
+Example 2
+---------
+
+Here are a pair of unlikely pipelines that show one way in which a merge filter
+might be used.  The first pipeline simply reads the input files "utm1.las",
+"utm2.las" and "utm3.las".  Since the points from each input set are
+carried separately through the pipeline, three files are created as output,
+"out1.las", "out2.las" and "out3.las".  "out1.las" contains the points
+in "utm1.las".  "out2.las" contains the points in "utm2.las" and "out3.las"
+contains the points in "utm3.las".
+
+.. code-block:: json
+
+    {
+      "pipeline": [
+        "utm1.las"
+        "utm2.las",
+        "utm3.las",
+        "out#.las"
+      ]
+    }
+
+Here is the same pipeline with a merge filter added.  The merge filter will
+combine the points in its input: "utm1.las" and "utm2.las".  Then the result
+of the merge filter is passed to the writer along with "utm3.las".  This
+results in two output files: "out1.las" contains the points from "utm1.las"
+and "utm2.las", while "out2.las" contains the points from "utm3.las".
+
+.. code-block:: json
+
+    {
+      "pipeline": [
+        "utm1.las"
+        "utm2.las",
         {
-          "type": "filters.merge",
-          "inputs": ["A", "B"]
+            "type" : "filters.merge"
         },
-        {
-          "type": "writers.las",
-          "filename": "output.las"
-        }
+        "utm3.las",
+        "out#.las"
       ]
     }
 
diff --git a/doc/stages/filters.mongus.rst b/doc/stages/filters.mongus.rst
new file mode 100644
index 0000000..723a0ac
--- /dev/null
+++ b/doc/stages/filters.mongus.rst
@@ -0,0 +1,63 @@
+.. _filters.mongus:
+
+filters.mongus
+===============================================================================
+
+Filter ground returns using the approach outlined in [Mongus2012]_.
+
+.. note::
+  
+  Our implmentation of Mongus is in an alpha state. We'd love to have you kick
+  the tires and provide feedback, but do not plan on using this in production.
+  
+The current implementation of ``filters.mongus`` differs slightly from the
+original paper. We weren't too happy with the criteria for how control points at
+the current level are compared against the TPS at the previous scale and were
+exploring some alternate metrics.
+
+Some warts about the current implementation:
+
+* It writes a bunch of intermediate/debugging outputs to the current directory
+  while processing. This should be made optional and then eventually go away.
+  
+* We require specification of a max level, whereas the original paper 
+  automatically determined an appropriate max level.
+  
+.. [Mongus2012] Mongus, D., Zalik, B., 2012. Parameter-free ground filtering of LiDAR data for automatic DTM generation. ISPRS J. Photogramm. Remote Sens. 67, 1–12.
+
+Example
+-------
+
+The sample pipeline below uses ``filters.mongus`` to segment ground and
+non-ground returns, writing only the ground returns to the output file.
+
+.. code-block:: json
+
+    {
+      "pipeline":[
+        "input.las",
+        {
+          "type":"filters.mongus",
+          "extract":true
+        },
+        "output.laz"
+      ]
+    }
+
+Options
+-------------------------------------------------------------------------------
+
+cell
+  Cell size. [Default: **1.0**]
+  
+classify
+  Apply classification labels (i.e., ground = 2)? [Default: **true**]
+  
+extract
+  Extract ground returns (non-ground returns are cropped)? [Default: **false**]
+  
+k
+  Standard deviation multiplier to be used when thresholding values. [Default: **3.0**]
+  
+l
+  Maximum level in the hierarchical decomposition. [Default: **8**]
diff --git a/doc/stages/filters.outlier.rst b/doc/stages/filters.outlier.rst
index 7f71a9e..3367ad0 100644
--- a/doc/stages/filters.outlier.rst
+++ b/doc/stages/filters.outlier.rst
@@ -15,30 +15,30 @@ The default method for identifying outlier points is the statistical outlier met
 In the first pass, for each point :math:`p_i` in the input ``PointView``, compute the mean distance :math:`\mu_i` to each of the :math:`k` nearest neighbors (where :math:`k` is configurable and specified by ``mean_k``). Then,
 
 .. math::
-  
+
   \overline{\mu} = \frac{1}{N} \sum_{i=1}^N \mu_i
-  
+
 .. math::
-  
+
   \sigma = \sqrt{\frac{1}{N-1} \sum_{i=1}^N (\mu_i - \overline{\mu})^2}
 
 A global mean :math:`\overline{\mu}` of these mean distances is then computed along with the standard deviation :math:`\sigma`. From this, the threshold is computed as
 
 .. math::
-  
+
   t = \mu + m\sigma
-  
+
 where :math:`m` is a user-defined multiplier specified by ``multiplier``.
 
 We now interate over the pre-computed mean distances :math:`\mu_i` and compare to computed threshold value. If :math:`\mu_i` is greater than the threshold, it is marked as an outlier.
 
 .. math::
-  
+
   outlier_i = \begin{cases}
       \text{true,} \phantom{false,} \text{if } \mu_i >= t \\
       \text{false,} \phantom{true,} \text{otherwise} \\
   \end{cases}
-  
+
 The ``classify`` and ``extract`` options are used to control whether outlier
 points are labeled as noise, or removed from the output ``PointView``
 completely.
@@ -59,7 +59,6 @@ After outlier removal, the noise points are removed.
 
 See [Rusu2008]_ for more information.
 
-.. [Rusu2008] Rusu, Radu Bogdan, et al. "Towards 3D point cloud based object maps for household environments." Robotics and Autonomous Systems 56.11 (2008): 927-941.
 
 Example
 ................................................................................
@@ -81,7 +80,7 @@ of the 12 nearest neighbors is below the computed threshold.
         "output.las"
       ]
     }
-        
+
 Radius Method
 -------------------------------------------------------------------------------
 
@@ -91,12 +90,12 @@ number of neighboring points :math:`k_i` within radius :math:`r` (specified by
 of neighbors specified by ``min_k``, it is marked as an outlier.
 
 .. math::
-  
+
   outlier_i = \begin{cases}
       \text{true,} \phantom{false,} \text{if } k_i < k_{min} \\
       \text{false,} \phantom{true,} \text{otherwise} \\
   \end{cases}
-  
+
 The ``classify`` and ``extract`` options are used to control whether outlier
 points are labeled as noise, or removed from the output ``PointView``
 completely.
@@ -121,7 +120,7 @@ four neighbors within a radius of 1.0.
         "output.las"
       ]
     }
-    
+
 Options
 -------------------------------------------------------------------------------
 
@@ -139,7 +138,7 @@ mean_k
 
 multiplier
   Standard deviation threshold (statistical method only). [Default: **2.0**]
-  
+
 classify
   Apply classification value of 18 (LAS high noise)? [Default: **true**]
 
diff --git a/doc/stages/filters.pclblock.rst b/doc/stages/filters.pclblock.rst
index ed641aa..cfb8d4c 100644
--- a/doc/stages/filters.pclblock.rst
+++ b/doc/stages/filters.pclblock.rst
@@ -10,22 +10,23 @@ between PDAL and PCL point cloud representations.
 
 This filter is under active development. The current implementation serves as a
 proof of concept for linking PCL into PDAL and converting data. The PCL Block
-filter creates a PCL ``Pipeline`` object and passes it a single argument, the JSON
-file containing the PCL block definition. After filtering, the resulting indices
-can be retrieved and used to create a new PDAL ``PointView`` containing only
-those points that passed the filtering stages.
+filter creates a PCL ``Pipeline`` object and passes it a single argument, the
+JSON file containing the PCL block definition. After filtering, the resulting
+indices can be retrieved and used to create a new PDAL ``PointView`` containing
+only those points that passed the filtering stages.
 
 At this stage in its development, the PCL ``Pipeline`` does not allow complex
-operations that may change the point type (e.g., ``PointXYZ`` to ``PointNormal``) or
-alter points.  We will continue to look into use cases that are of value and
-feasible, but for now are limited primarily to PCL functions that filter or
-segment the point cloud, returning a list of indices of the filtered points
-(e.g., ground or object, noise or signal). The main reason for this design
-decision is that we want to avoid converting all ``PointView`` dimensions to the
-PCL ``PointCloud``. In the case of an LAS reader, we may very well not want to
-operate on fields such as return number, but we do not want to lose this
-information post PCL filtering. The easy solution is to simply retain the index
-between the ``PointView`` and ``PointCloud`` objects and update as necessary.
+operations that may change the point type (e.g., ``PointXYZ`` to
+``PointNormal``) or alter points.  We will continue to look into use cases that
+are of value and feasible, but for now are limited primarily to PCL functions
+that filter or segment the point cloud, returning a list of indices of the
+filtered points (e.g., ground or object, noise or signal). The main reason for
+this design decision is that we want to avoid converting all ``PointView``
+dimensions to the PCL ``PointCloud``. In the case of an LAS reader, we may very
+well not want to operate on fields such as return number, but we do not want to
+lose this information post PCL filtering. The easy solution is to simply retain
+the index between the ``PointView`` and ``PointCloud`` objects and update as
+necessary.
 
 .. seealso::
 
@@ -43,7 +44,10 @@ Options
 -------------------------------------------------------------------------------
 
 filename
-  JSON file to read [Required]
+  Path to external PCL JSON file describing the pipeline
+  
+methods
+  Raw PCL JSON array describing the pipeline
 
 
 
@@ -55,30 +59,21 @@ PCL. Here is an example:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "name": "PCL-Block-Name",
-            "help": "This is an example pipeline with two filters.",
-            "author": "mpg",
-            "filters":
-            [
-                {
-                    "name": "FilterOne",
-                    "setFooParameter": "value"
-                },
-                {
-                    "name": "FilterTwo",
-                    "setBarParameter": false,
-                    "setBounds":
-                    {
-                        "upper": 42,
-                        "lower": 17
-                    }
-                }
-            ]
+            "name": "FilterOne",
+            "setFooParameter": "value"
+        },
+        {
+            "name": "FilterTwo",
+            "setBarParameter": false,
+            "setBounds":
+            {
+                "upper": 42,
+                "lower": 17
+            }
         }
-    }
+    ]
 
 
 
@@ -98,15 +93,9 @@ ApproximateProgressiveMorphologicalFilter
     faster (and potentially less accurate) version of the
     **ProgressiveMorphologicalFilter**
 
-ConditionalRemoval
-    filters the data to remove normals outside of a given Z range
-
 GridMinimum
     assembles a local 2D grid over a given PointCloud, then downsamples the data
 
-NormalEstimation
-    computes the surfaces normals of the points in the input
-
 PassThrough
     allows the user to set min/max bounds on one dimension of the data
 
diff --git a/doc/stages/filters.pmf.rst b/doc/stages/filters.pmf.rst
index 2191005..7b2f17e 100644
--- a/doc/stages/filters.pmf.rst
+++ b/doc/stages/filters.pmf.rst
@@ -7,8 +7,6 @@ The Progressive Morphological Filter (PMF) is a method of segmenting ground and
 non-ground returns. This filter is an implementation of the method described in
 [Zhang2003]_.
 
-.. [Zhang2003] Zhang, Keqi, et al. "A progressive morphological filter for removing nonground measurements from airborne LIDAR data." Geoscience and Remote Sensing, IEEE Transactions on 41.4 (2003): 872-882.
-
 
 Example
 -------
@@ -31,7 +29,7 @@ Example
 Options
 -------------------------------------------------------------------------------
 
-maxWindowSize
+max_window_size
   Maximum window size. [Default: **33**]
 
 slope
diff --git a/doc/stages/filters.programmable.rst b/doc/stages/filters.programmable.rst
index 0c47073..e58fb62 100644
--- a/doc/stages/filters.programmable.rst
+++ b/doc/stages/filters.programmable.rst
@@ -58,7 +58,7 @@ Example
       ]
     }
 
-The XML pipeline file referenced the external `multiply_z.py` `Python`_ script,
+The JSON pipeline file referenced the external `multiply_z.py` `Python`_ script,
 which scales up the Z coordinate by a factor of 10.
 
 .. code-block:: python
diff --git a/doc/stages/filters.radialdensity.rst b/doc/stages/filters.radialdensity.rst
new file mode 100644
index 0000000..3ed4bd2
--- /dev/null
+++ b/doc/stages/filters.radialdensity.rst
@@ -0,0 +1,47 @@
+.. _filters.radialdensity:
+
+===============================================================================
+filters.radialdensity
+===============================================================================
+
+The Radial Density filter creates a new attribute ``RadialDensity`` that
+contains the density of points in a sphere of given radius.
+
+The density at each point is computed by counting the number of points falling
+within a sphere of given radius (default is 1.0) and centered at the current
+point. The number of neighbors (including the query point) is then normalized
+by the volume of the sphere, defined as
+
+.. math::
+
+  V = \frac{4}{3} \pi r^3
+
+The radius :math:`r` can be adjusted by changing the ``radius`` option.
+
+Example
+-------------------------------------------------------------------------------
+
+.. code-block:: json
+
+    {
+      "pipeline":[
+        "input.las",
+        {
+          "type":"filters.radialdensity",
+          "radius":2.0
+        },
+        {
+          "type":"writers.bpf",
+          "filename":"output.bpf",
+          "output_dims":"X,Y,Z,RadialDensity"
+        }
+      ]
+    }
+
+
+Options
+-------------------------------------------------------------------------------
+
+radius
+  Radius. [Default: **1.0**]
+
diff --git a/doc/stages/filters.radiusoutlier.rst b/doc/stages/filters.radiusoutlier.rst
deleted file mode 100644
index 706f7b7..0000000
--- a/doc/stages/filters.radiusoutlier.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-.. _filters.radiusoutlier:
-
-===============================================================================
-filters.radiusoutlier
-===============================================================================
-
-The Radius Outlier filter passes data through the Point Cloud Library (`PCL`_)
-RadiusOutlierRemoval algorithm.
-
-RadiusOutlierRemoval filters points in a cloud based on the number of neighbors
-they have. Iterates through the entire input once, and for each point, retrieves
-the number of neighbors within a certain radius. The point will be considered an
-outlier if it has too few neighbors, as determined by ``min_neighbors``. The
-radius can be changed using ``radius``.
-
-.. _`PCL`: http://www.pointclouds.org
-
-Example
--------------------------------------------------------------------------------
-
-.. code-block:: json
-
-    {
-      "pipeline":[
-        "input.las",
-        {
-          "type":"filters.radiusoutlier",
-          "min_neighbors":"4"
-        },
-        {
-          "type":"writers.las",
-          "filename":"output.las"
-        }
-      ]
-    }
-
-
-Options
--------------------------------------------------------------------------------
-
-min_neighbors
-  Minimum number of neighbors in radius. [Default: **2**]
-
-radius
-  Radius. [Default: **1.0**]
-
-classify
-  Apply classification labels? [Default: **true**]
-
-extract
-  Extract ground returns? [Default: **false**]
diff --git a/doc/stages/filters.sample.rst b/doc/stages/filters.sample.rst
index 6113dc6..dc97df0 100644
--- a/doc/stages/filters.sample.rst
+++ b/doc/stages/filters.sample.rst
@@ -21,11 +21,6 @@ is kept in tact (the same cannot be said for :ref:`filters.voxelgrid`).
     :ref:`filters.decimation` and :ref:`filters.voxelgrid` also perform
     decimation.
 
-.. [Cook1986] Cook, Robert L. "Stochastic sampling in computer graphics." *ACM Transactions on Graphics (TOG)* 5.1 (1986): 51-72.
-
-.. [Dippe1985] Dippé, Mark AZ, and Erling Henry Wold. "Antialiasing through stochastic sampling." *ACM Siggraph Computer Graphics* 19.3 (1985): 69-78.
-
-.. [Mesh2009] ALoopingIcon. "Meshing Point Clouds." *MESHLAB STUFF*. n.p., 7 Sept. 2009. Web. 13 Nov. 2015.
 
 Options
 -------------------------------------------------------------------------------
diff --git a/doc/stages/filters.smrf.rst b/doc/stages/filters.smrf.rst
new file mode 100644
index 0000000..a9a59db
--- /dev/null
+++ b/doc/stages/filters.smrf.rst
@@ -0,0 +1,66 @@
+.. _filters.smrf:
+
+filters.smrf
+===============================================================================
+
+Filter ground returns using the Simple Morphological Filter (SMRF) approach
+outlined in [Pingel2013]_.
+
+.. note::
+  
+  Our implmentation of SMRF is in an alpha state. We'd love to have you kick
+  the tires and provide feedback, but do not plan on using this in production.
+  
+The current implementation of ``filters.smrf`` differs slightly from the
+original paper. We weren't too happy with the performance of (our implementation
+of) the inpainting routine, so we started exploring some other methods.
+
+Some warts about the current implementation:
+
+* It writes a bunch of intermediate/debugging outputs to the current directory
+  while processing. This should be made optional and then eventually go away.
+  
+.. [Pingel2013] Pingel, T.J., Clarke, K.C., McBride, W.A., 2013. An improved simple morphological filter for the terrain classification of airborne LIDAR data. ISPRS J. Photogramm. Remote Sens. 77, 21–30.
+
+Example
+-------
+
+The sample pipeline below uses ``filters.smrf`` to segment ground and non-ground
+returns, writing only the ground returns to the output file.
+
+.. code-block:: json
+
+    {
+      "pipeline":[
+        "input.las",
+        {
+          "type":"filters.smrf",
+          "extract":true
+        },
+        "output.laz"
+      ]
+    }
+
+Options
+-------------------------------------------------------------------------------
+
+cell
+  Cell size. [Default: **1.0**]
+
+classify
+  Apply classification labels (i.e., ground = 2)? [Default: **true**]
+
+cut
+  Cut net size (``cut=0`` skips the net cutting step). [Default: **0.0**]
+  
+extract
+  Extract ground returns (non-ground returns are cropped)? [Default: **false**]
+  
+slope
+  Slope (rise over run). [Default: **0.15**]
+  
+threshold
+  Elevation threshold. [Default: **0.15**]
+  
+window
+  Max window size. [Default: **21.0**]
diff --git a/doc/stages/filters.statisticaloutlier.rst b/doc/stages/filters.statisticaloutlier.rst
deleted file mode 100644
index adf5aa3..0000000
--- a/doc/stages/filters.statisticaloutlier.rst
+++ /dev/null
@@ -1,72 +0,0 @@
-.. _filters.statisticaloutlier:
-
-filters.statisticaloutlier
--------------------------------------------------------------------------------
-
-The Statistical Outlier filter passes data through the Point Cloud Library
-(`PCL`_) StatisticalOutlierRemoval algorithm.
-
-``filters.statisticaloutlier`` uses point neighborhood statistics to filter
-outlier data. The algorithm iterates through the entire input twice. The first
-iteration is used to calculate the average of the distances from each point :math:`i`
-to its :math:`k` nearest neighbors or  :math:`AD_{i}`.  The second iteration is used to
-identify outliers based on the distribution of :math:`AD_{i}` values.  Points with an
-average neighbor distance greater than the mean plus 2 standard deviations are
-classified as outliers.  The value of :math:`k` can be set using :math:`\tt mean\_k`. By
-default, the distance threshold is set to :math:`\overline{AD}_{i} + 2 \hat{\sigma}`,
-but a value other than 2 can be chosen using :math:`\tt  multiplier`.
-
-.. figure:: filters.statisticaloutlier.img1.png
-    :scale: 70 %
-    :alt: Points before outlier removal
-
-    Before outlier removal, noise points can be found both above and below the scene.
-
-
-.. figure:: filters.statisticaloutlier.img2.png
-    :scale: 60 %
-    :alt: Points after outlier removal
-
-    After outlier removal, the noise points are removed.
-
-See [Rusu2008]_ for more information.
-
-.. [Rusu2008] Rusu, Radu Bogdan, et al. "Towards 3D point cloud based object maps for household environments." Robotics and Autonomous Systems 56.11 (2008): 927-941.
-
-.. _`PCL`: http://www.pointclouds.org
-
-Example
-................................................................................
-
-.. code-block:: json
-
-    {
-      "pipeline":[
-        "input.las",
-        {
-          "type":"filters.statisticaloutlier",
-          "mean_k":"12",
-          "multiplier":"2.2"
-        },
-        {
-          "type":"writers.las",
-          "filename":"output.las"
-        }
-      ]
-    }
-
-
-Options
-................................................................................
-
-mean_k
-  Mean number of neighbors. [Default: **8**]
-
-multiplier
-  Standard deviation threshold. [Default: **2.0**]
-
-classify
-  Apply classification labels? [Default: **true**]
-
-extract
-  Extract ground returns? [Default: **false**]
diff --git a/doc/stages/readers.bpf.rst b/doc/stages/readers.bpf.rst
index fbf745b..8993204 100644
--- a/doc/stages/readers.bpf.rst
+++ b/doc/stages/readers.bpf.rst
@@ -5,7 +5,7 @@ readers.bpf
 ******************************************************************************
 
 BPF is an NGA specification for point cloud data. The specification can be
-found at https://nsgreg.nga.mil/doc/view?i=4202 The **BPF Reader** supports
+found at https://nsgreg.nga.mil/doc/view?i=4220&month=8&day=30&year=2016 The **BPF Reader** supports
 reading from BPF files that are encoded as version 1, 2 or 3.
 
 This BPF reader only supports Zlib compression.  It does NOT support the
diff --git a/doc/stages/readers.greyhound.rst b/doc/stages/readers.greyhound.rst
index 59c9497..7828235 100644
--- a/doc/stages/readers.greyhound.rst
+++ b/doc/stages/readers.greyhound.rst
@@ -3,7 +3,8 @@
 readers.greyhound
 =================
 
-The **Greyhound Reader** allows you to read point data from a `Greyhound`_ server.
+The **Greyhound Reader** allows you to query point data from a `Greyhound`_
+server.
 
 Example
 -------
@@ -14,7 +15,8 @@ Example
       "pipeline":[
         {
           "type":"readers.greyhound",
-          "url":"data.greyhound.io/resource/autzen"
+          "url":"data.greyhound.io",
+          "resource":"autzen"
         },
         {
           "type":"writers.text",
@@ -27,9 +29,66 @@ Example
 Options
 -------
 
-url
+_`url`
   Greyhound server URL string. [Required]
 
+_`resource`
+  Name of the Greyhound resource to access. [Required]
 
+_`bounds`
+  Spatial bounds to query, expressed as a string, e.g.
+  *([xmin, xmax], [ymin, ymax])* or
+  *([xmin, xmax], [ymin, ymax], [zmin, zmax])*.  By default, the entire resource
+  is queried.
+
+_`depth_begin`
+  Beginning octree depth to query, inclusive.  Lower depth values have coarser
+  resolution, so a depth range of *[0, 8)* could provide a low-resolution
+  overview of the entire resource, for example.  [Default: **0**]
+
+_`depth_end`
+  Ending octree depth to query, non-inclusive.  A value of **0** will search all
+  depths greater-than or equal-to *depth_begin*.  If non-zero, this value should
+  be greater than *depth_begin* or the result will always be empty.
+  [Default: **0**]
+
+_`tile_path`
+  A Greyhound resource may be an aggregation of multiple input files.  If a
+  *tile_path* option is present, then only points belonging to that file will
+  be queried.  This search is spatially optimized, so no `bounds`_ option needs
+  to be present to limit the query bounds.
+
+_`filter`
+  Server-side filtering may be requested which may further limit the data
+  selected by the query.  The filter is represented as JSON, and performs
+  filtering on dimensions present in the resource, or the pseudo-dimension
+  *Path*, corresponding to `tile_path`_ values.
+
+  Arbitrary logic combinations may be created using `comparison`_ and
+  `logical`_ query operators with syntax matching that of MongoDB.  Some sample
+  filters follow.
+
+.. code-block:: json
+
+    {
+        "Path":{"$in":["tile-845.laz", "tile-846.laz"]},
+        "Classification":{"$ne":18}
+    }
+
+.. code-block:: json
+
+    {"$or":[
+        {"Red":{"$gt":200}},
+        {"Blue":{"$gt":120,"$lt":130}},
+        {"Classification":{"$nin":[2,3]}}
+    ]}
+
+_`threads`
+  Because Greyhound resources are accessed by combination of multiple HTTP
+  requests, threaded operation can greatly increase throughput.  This option
+  sets the number of concurrent requests allowed.  [Default: **4**]
 
 .. _Greyhound: https://github.com/hobu/greyhound
+.. _comparison: https://docs.mongodb.com/manual/reference/operator/query-comparison/
+.. _logical: https://docs.mongodb.com/manual/reference/operator/query-logical/
+
diff --git a/doc/stages/readers.pcd.rst b/doc/stages/readers.pcd.rst
index 6030074..33b5007 100644
--- a/doc/stages/readers.pcd.rst
+++ b/doc/stages/readers.pcd.rst
@@ -8,6 +8,11 @@ readers.pcd
 The **PCD Reader** supports reading from `Point Cloud Data (PCD)`_ formatted
 files, which are used by the `Point Cloud Library (PCL)`_.
 
+.. note::
+
+    The `PCD Reader` requires linkage of the `PCL`_ library.
+
+
 Example
 -------
 
@@ -36,4 +41,5 @@ filename
 
 .. _Point Cloud Data (PCD): http://pointclouds.org/documentation/tutorials/pcd_file_format.php
 .. _Point Cloud Library (PCL): http://pointclouds.org
+.. _PCL: http://pointclouds.org
 
diff --git a/doc/stages/readers.qfit.rst b/doc/stages/readers.qfit.rst
index 9644cab..5b022da 100644
--- a/doc/stages/readers.qfit.rst
+++ b/doc/stages/readers.qfit.rst
@@ -1,7 +1,8 @@
 .. _readers.qfit:
 
+******************************************************************************
 readers.qfit
-============
+******************************************************************************
 
 The **QFIT reader** read from files in the `QFIT format`_ originated for the
 Airborne Topographic Mapper (ATM) project at NASA Goddard Space Flight Center.
diff --git a/doc/stages/readers.rst b/doc/stages/readers.rst
index b495d90..f77a453 100644
--- a/doc/stages/readers.rst
+++ b/doc/stages/readers.rst
@@ -3,13 +3,13 @@
 Readers
 =======
 
-Readers provide data to :ref:`pipeline` operations. Readers might be a simple
-file type, like :ref:`readers.las`, a complex database like :ref:`readers.oci`, or
+Readers are data providers to :ref:`pipeline` operations. A reader might provide a simple
+file type, like :ref:`readers.text`, a complex database like :ref:`readers.oci`, or
 a network service like :ref:`readers.greyhound`.
 
 .. note::
 
-    Readers provide :cpp:class:`pdal::Dimension` to :ref:`pipeline`. PDAL attempts
+    Readers provide :ref:`dimensions` to :ref:`pipeline`. PDAL attempts
     to normalize common dimension types, like X, Y, Z, or Intensity, which are
     often found in LiDAR point clouds. Not all dimension types need to be fixed, however.
     Database drivers typically return unstructured lists of dimensions.
diff --git a/doc/stages/readers.rxp.rst b/doc/stages/readers.rxp.rst
index 1f11ccc..f391f04 100644
--- a/doc/stages/readers.rxp.rst
+++ b/doc/stages/readers.rxp.rst
@@ -26,52 +26,30 @@ Example
 
 This example rescales the points, given in the scanner's own coordinate system, to values that can be written to a las file.
 Only points with a valid gps time, as determined by a pps pulse, are read from the rxp, since the ``sync_to_pps`` option is "true".
-
-.. code-block:: python
-
-    import numpy
-    def reflectance_to_intensity(ins, outs):
-        ref = ins["Reflectance"]
-        min = numpy.amin(ref)
-        max = numpy.amax(ref)
-        outs["Intensity"] = (65535 * (ref - min) / (max - min)).astype(numpy.uint16)
-        return True
+Reflectance values are mapped to intensity values using sensible defaults.
 
 .. code-block:: json
 
     {
       "pipeline":[
         {
-          "type":"readers.rxp",
-          "filename":"120304_204030.rxp",
-          "sync_to_pps":"true"
+          "type": "readers.rxp",
+          "filename": "120304_204030.rxp",
+          "sync_to_pps": "true",
+          "reflectance_as_intensity": "true"
         },
         {
-          "type":"filters.programmable",
-          "source":"filter_rxp.py",
-          "function":"reflectance_to_intensity",
-          "add_dimension":"Intensity",
-          "module":"filter_rxp",
-        },
-        {
-          "type":"writers.las",
-          "filename":"outputfile.las"
-          "discard_high_return_numbers":"true"
+          "type": "writers.las",
+          "filename": "outputfile.las",
+          "discard_high_return_numbers": "true"
         }
       ]
     }
 
 
-A few things to note:
-
-- We use a :ref:`filters.programmable` to remap Reflectance values to Intensity values, scaling them so the entire range of Reflectance values fit into the Intensity field.
-  This is analogous to the "Export reflectance as intensity" option in RiSCAN Pro.
-  You could also explicitly set the minimum and maximum Reflectance values as you would in RiSCAN Pro using the same programmable filter.
-  You could also use "Amplitude" instead of "Reflectance".
-  If you do not need Intensity values in your output file, you can delete the programmable filter.
-- We set the ``discard_high_return_numbers`` option to ``true`` on the :ref:`writers.las`.
-  RXP files can contain more returns per shot than is supported by las, and so we need to explicitly tell the las writer to ignore those high return number points.
-  You could also use :ref:`filters.predicate` to filter those points earlier in the pipeline, or modify the return values with a :ref:`filters.programmable`.
+We set the ``discard_high_return_numbers`` option to ``true`` on the :ref:`writers.las`.
+RXP files can contain more returns per shot than is supported by las, and so we need to explicitly tell the las writer to ignore those high return number points.
+You could also use :ref:`filters.predicate` to filter those points earlier in the pipeline, or modify the return values with a :ref:`filters.programmable`.
 
 
 Options
@@ -94,6 +72,18 @@ minimal
   Use this feature to reduce the memory footprint of a PDAL run, if you don't need any values but the points themselves.
   [default: false]
 
+reflectance_as_intensity
+  If "true", maps reflectance values onto intensity values using a range from -25dB to 5dB.
+  [default: true]
+
+min_reflectance
+  The low end of the reflectance-to-intensity map.
+  [default: -25.0]
+
+max_reflectance
+  The high end of the reflectance-to-intensity map.
+  [default: 5.0]
+
 .. _RIEGL Laser Measurement Systems: http://www.riegl.com
 .. _RIEGL download pages: http://www.riegl.com/members-area/software-downloads/libraries/
 .. _the optional library documentation: http://www.pdal.io/compilation/unix.html#configure-your-optional-libraries
diff --git a/doc/stages/writers.derivative.rst b/doc/stages/writers.derivative.rst
index 73b5afb..3625fb6 100644
--- a/doc/stages/writers.derivative.rst
+++ b/doc/stages/writers.derivative.rst
@@ -13,8 +13,10 @@ The **Derivative Writer** supports writing of primary topographic attributes.
 .. _`GDAL`: http://gdal.org
 .. _`GeoTiff`: http://www.gdal.org/frmt_gtiff.html
 
-Example
--------
+Example #1
+----------
+
+Create a single GeoTIFF with slope values calculated using the D8 method.
 
 .. code-block:: json
 
@@ -28,6 +30,25 @@ Example
         }
       ]
     }
+    
+Example #2
+----------
+
+Create multiple GeoTIFFs containing slope, hillshade, and contour curvature
+values.
+
+.. code-block:: json
+
+    {
+      "pipeline":[
+        "inputfile.las",
+        {
+          "type":"writers.derivative",
+          "filename":"outputfile_#.tiff",
+          "primitive_type":"slope_d8,hillshade,contour_curvature"
+        }
+      ]
+    }
 
 
 Options
@@ -48,8 +69,13 @@ primitive_type
   * tangential_curvature
   * total_curvature
   * hillshade
-  * catchment_area
 
-grid_dist_x, grid_dist_y
+edge_length
   Size of grid cell in X and Y dimensions using native units of the input point
   cloud.  [Default: 15.0]
+
+altitude
+  Illumination altitude in degrees (hillshade only). [Default: 45.0]
+
+azimuth
+  Illumination azimuth in degrees (hillshade only). [Default: 315.0]
diff --git a/doc/stages/writers.gdal.rst b/doc/stages/writers.gdal.rst
new file mode 100644
index 0000000..6afcb7c
--- /dev/null
+++ b/doc/stages/writers.gdal.rst
@@ -0,0 +1,126 @@
+.. _writers.gdal:
+
+writers.gdal
+================================================================================
+
+The `GDAL`_ writer creates a raster from a point cloud using an interpolation
+algorithm.  Output is produced using GDAL and can therefore use any `driver
+that supports creation of rasters`_.
+
+.. _`GDAL`: http://gdal.org
+.. _`driver that supports creation of rasters`: http://www.gdal.org/formats_list.html
+
+The technique used to create the raster is a simple interpolation where
+each point that falls within a given radius of a raster cell center
+potentially contributes to the raster's value.
+
+.. note::
+
+    If a circle with the provided radius doesn't encompass the entire cell,
+    it is possible that some points will not be considered at all, including
+    those that may be within the bounds of the raster cell.
+
+The GDAL writer creates rasters using the data specified in the ``dimension``
+option (defaults to `Z`).The writer will creates up to six rasters based on
+different statistics in the output dataset.  The order of the layers in the
+dataset is as follows:
+
+min
+    Give the cell the minimum value of all points within the given radius.
+
+max
+    Give the cell the maximum value of all points within the given radius.
+
+mean
+    Give the cell the mean value of all points within the given radius.
+
+idw
+    Cells are assigned a value based on `Shepard's inverse distance weighting`_
+    algorithm, considering all points within the given radius.
+
+count
+    Give the cell the number of points that lie within the given radius.
+
+stdev
+    Give the cell the population standard deviation of the points that lie
+    within the given radius.
+
+.. _`Shepard's inverse distance weighting`: https://en.wikipedia.org/wiki/Inverse_distance_weighting
+
+If no points fall within the circle about a raster cell, a secondary
+algorithm can be used to attempt to provide a value after the standard
+interpolation is complete.  If the window_size_ option is set to a non-zero
+value, a square of rasters surrounding an empty cell, and the value of each
+non-empty surrounding is averaged using inverse distance weighting to provide
+a value for the subject cell.  The value provided for window_size is the
+maximum horizontal or vertical distance that a donor cell may be in order to
+contribute to the subject cell (A window_size of 1 essentially creates a 3x3
+array around the subject cell.  A window_size of 2 creates a 5x5 array, and
+so on.)
+
+Cells that have no value after interpolation are given the empty value of -9999.
+
+Basic Example
+--------------------------------------------------------------------------------
+
+This  pipeline reads the file autzen_trim.las and creates a Geotiff dataset
+called outputfile.tif.  Since output_type isn't specified, it creates six
+raster bands ("min", "max", "mean", "idx", "count" and "stdev") in the output
+dataset.  The raster cells are 10x10 and the radius used to locate points
+whose values contribute to the cell value is 14.14.
+
+.. code-block:: json
+
+    {
+      "pipeline":[
+        "pdal/test/data/las/autzen_trim.las",
+        {
+          "resolution": 10,
+          "radius": 14.14,
+          "filename":"outputfile.tif"
+        }
+      ]
+    }
+
+
+Options
+--------------------------------------------------------------------------------
+
+filename
+    Name of output file. [Required]
+
+resolution
+    Length of raster cell edges in X/Y units.  [Required]
+
+radius
+    Radius about cell center bounding points to use to calculate a cell value.
+    [Required]
+
+gdaldriver
+    Name of the GDAL driver to use to write the output. [Default: "GTiff"]
+
+gdalopts
+    A list of key/value options to pass directly to the GDAL driver.  The
+    format is name=value,name=value,...  The option may be specified
+    any number of times.
+
+    .. note::
+        The INTERLEAVE GDAL driver option is not supported.  writers.gdal
+        always uses BAND interleaving.
+
+.. _output_type:
+
+output_type
+    A comma separated list of statistics for which to produce raster layers.
+    The supported values are "min", "max", "mean", "idw", "count", "stdev"
+    and "all".  The option may be specified more than once. [Default: "all"]
+
+.. _window_size:
+
+window_size
+    The maximum distance from a donor cell to a target cell when applying
+    the fallback interpolation method.  See the stage description for more
+    information. [Default: 0]
+
+dimension
+  A dimension name to use for the interpolation. [Default: ``Z``]
diff --git a/doc/stages/writers.las.rst b/doc/stages/writers.las.rst
index 44027f4..028dd66 100644
--- a/doc/stages/writers.las.rst
+++ b/doc/stages/writers.las.rst
@@ -107,11 +107,11 @@ system_id
   String identifying the system that created this LAS file. [Default: "PDAL"]
 
 a_srs
-  The spatial reference system of the file to be written. Can be an EPSG string (eg "EPSG:268910") or a WKT string. [Default: Not set]
+  The spatial reference system of the file to be written. Can be an EPSG string (e.g. "EPSG:268910") or a WKT string. [Default: Not set]
 
 global_encoding
   Various indicators to describe the data.  See the LAS documentation.  Note
-  that PDAL will always set bit four when creating LAS version output.
+  that PDAL will always set bit four when creating LAS version 1.4 output.
   [Default: 0]
 
 project_id
diff --git a/doc/stages/writers.matlab.rst b/doc/stages/writers.matlab.rst
index 453f8a7..5d51e73 100644
--- a/doc/stages/writers.matlab.rst
+++ b/doc/stages/writers.matlab.rst
@@ -5,9 +5,15 @@ writers.matlab
 
 The **Matlab Writer** supports writing Matlab `.mat` files.
 
-The produced files have two variables, `Dimensions` and `Points`.
-`Dimensions` is a comma-delimited list of dimension names, and `Points` is a double array of all dimensions of every points.
-Note that this output array can get very large very quickly.
+The produced files have two variables, `Dimensions` and `Points`.  `Dimensions`
+is a comma-delimited list of dimension names, and `Points` is a double array of
+all dimensions of every points.  This output array can get very large
+very quickly.
+
+
+.. note::
+
+    The Matlab writer requires the Mat-File API from MathWorks.
 
 Example
 -------
diff --git a/doc/stages/writers.p2g.rst b/doc/stages/writers.p2g.rst
index 3aa77fe..ee1e86a 100644
--- a/doc/stages/writers.p2g.rst
+++ b/doc/stages/writers.p2g.rst
@@ -11,6 +11,13 @@ grid writer supports creating multiple output grids simultaneously, so it is
 possible to generate all grid variants in one pass.
 
 
+.. warning::
+
+    :ref:`writers.gdal` is a replacement for `writers.p2g`. It doesn't require
+    an externally installed library, it supports
+    more GDAL output formats and options, and it supports the ability to write
+    all output to a single GeoTIFF.
+
 .. note::
 
     A project called `lidar2dems`_ by `Applied GeoSolutions`_ integrates the P2G
diff --git a/doc/stages/writers.pcd.rst b/doc/stages/writers.pcd.rst
index 289b62e..25e52df 100644
--- a/doc/stages/writers.pcd.rst
+++ b/doc/stages/writers.pcd.rst
@@ -10,6 +10,10 @@ By default, compression is not enabled, and the PCD writer will output ASCII
 formatted data. When compression is enabled, the output is PCD's
 binary-compressed format.
 
+.. note::
+
+    The `PCD Writer` requires linkage of the `PCL`_ library.
+
 Example
 -------
 
@@ -41,4 +45,5 @@ compression
 
 .. _Point Cloud Data (PCD): http://pointclouds.org/documentation/tutorials/pcd_file_format.php
 .. _Point Cloud Library (PCL): http://pointclouds.org
+.. _PCL: http://pointclouds.org
 
diff --git a/doc/stages/writers.pclvisualizer.rst b/doc/stages/writers.pclvisualizer.rst
deleted file mode 100644
index a19f9bb..0000000
--- a/doc/stages/writers.pclvisualizer.rst
+++ /dev/null
@@ -1,21 +0,0 @@
-.. _writers.pclvisualizer:
-
-writers.pclvisualizer
-=====================
-
-The **PCLVisualizer Writer** enables use of the `Point Cloud Library (PCL)`_
-PCLVisualizer. This is primarily used by :ref:`pdal view <view_command>`.
-
-Options
--------
-
-filename
-  PCD file to write [Required] 
-
-.. note::
-
-    A filename is currently required by PDAL, but no data is written to disk.
-
-
-.. _Point Cloud Library (PCL): http://pointclouds.org
- 
diff --git a/doc/stages/writers.rst b/doc/stages/writers.rst
index f614dea..4147c2c 100644
--- a/doc/stages/writers.rst
+++ b/doc/stages/writers.rst
@@ -8,9 +8,8 @@ dimension type, while others only understand fixed dimension names.
 
 .. note::
 
-    Some writers can only consume known dimension names, while PDAL doesn't
-    yet have a registery for the dimension types, you can see the
-    base dimension types at https://github.com/PDAL/PDAL/blob/master/include/pdal/Dimension.hpp
+    PDAL predefined dimension names can be found in the dimension registry:
+    https://github.com/PDAL/PDAL/blob/master/src/Dimension.json
 
 .. toctree::
    :maxdepth: 1
diff --git a/doc/tutorial/calculating-normalized-heights.rst b/doc/tutorial/calculating-normalized-heights.rst
index 2957625..5a29b8d 100644
--- a/doc/tutorial/calculating-normalized-heights.rst
+++ b/doc/tutorial/calculating-normalized-heights.rst
@@ -9,35 +9,63 @@ Calculating Normalized Heights
 :Date: 11/11/2015
 
 
-This tutorial will describe the creation of a new filter for calculating normalized heights, :ref:`filters.height`.
+This tutorial will describe the creation of a new filter for calculating
+normalized heights, ``filters.height``.
+
+.. note::
+
+  ``filters.height`` required PCL and has since been replaced by
+  :ref:`filters.hag`, which is a native PDAL filter. We leave this tutorial as
+  an example of how to create a filter, and of how to work with the PCL plugin,
+  but the filter in reference is no longer available.
 
 Introduction
 -------------------------------------------------------------------------------
 
-Normalized heights are a commonly used attribute of point cloud data. This can also be referred to as *height above ground* (HAG) or *above ground level* (AGL) heights. In the end, it is simply a measure of a point's relative height as opposed to its raw elevation value.
+Normalized heights are a commonly used attribute of point cloud data. This can
+also be referred to as *height above ground* (HAG) or *above ground level*
+(AGL) heights. In the end, it is simply a measure of a point's relative height
+as opposed to its raw elevation value.
 
-The process of computing normalized heights is straightforward. First, we must have an estimate of the underlying terrain model. With this we can compute the difference between each point's elevation and the elevation of the terrain model at the same XY coordinate. The quality of the normalized heights will be a function of the quality of the terrain model, which of course depends on the quality of the ground segmentation approach and any interpolation that is required to arrive at the t [...]
+The process of computing normalized heights is straightforward. First, we must
+have an estimate of the underlying terrain model. With this we can compute the
+difference between each point's elevation and the elevation of the terrain
+model at the same XY coordinate. The quality of the normalized heights will be
+a function of the quality of the terrain model, which of course depends on the
+quality of the ground segmentation approach and any interpolation that is
+required to arrive at the terrain elevation for a given XY coordinate.
 
-We will use a nearest neighbor interpolation scheme to estimate terrain elevations. While this may not be the most accurate approach, it is available in PDAL today, and we hope it will inspire you to implement your own methods!
+We will use a nearest neighbor interpolation scheme to estimate terrain
+elevations. While this may not be the most accurate approach, it is available
+in PDAL today, and we hope it will inspire you to implement your own methods!
 
 Approach
 -------------------------------------------------------------------------------
 
-For the height filter, we only assume that our input point cloud has an already existing ``Classification`` dimension with some subset of points marked as ground (``Classification=2``). This could, for example, be generated by :ref:`filters.ground` (see :ref:`pcl_ground`), but you can use whichever method you choose, as long as the ground returns are marked.
+For the height filter, we only assume that our input point cloud has an already
+existing ``Classification`` dimension with some subset of points marked as
+ground (``Classification=2``). This could, for example, be generated by
+:ref:`filters.pmf` (see :ref:`pcl_ground`), but you can use whichever method
+you choose, as long as the ground returns are marked.
 
 .. note::
 
    We expect ground returns to have the classification value of 2 in keeping with the ASPRS Standard LIDAR Point Classes (see http://www.asprs.org/a/society/committees/standards/LAS_1_4_r13.pdf).
 
-For the full source, please see ``HeightFilter.cpp`` in the ``plugins/pcl/filters`` folder. Below, we dissect the key aspects of the algorithm and its implementation.
+For the full source, please see ``HeightFilter.cpp`` in the
+``plugins/pcl/filters`` folder. Below, we dissect the key aspects of the
+algorithm and its implementation.
 
-The bulk of our processing is actually taking place within PCL. For convenience, we've defined:
+The bulk of our processing is actually taking place within PCL. For
+convenience, we've defined:
 
 .. code-block:: cpp
 
     typedef pcl::PointCloud<pcl::PointXYZ> Cloud;
 
-Our first step is to convert the filter's incoming PDAL ``PointView`` to a PCL ``PointCloud``, which requires that we first calculate our bounds so that we can subtract our XYZ offsets in the conversion step.
+Our first step is to convert the filter's incoming PDAL ``PointView`` to a PCL
+``PointCloud``, which requires that we first calculate our bounds so that we
+can subtract our XYZ offsets in the conversion step.
 
 .. code-block:: cpp
 
@@ -47,7 +75,9 @@ Our first step is to convert the filter's incoming PDAL ``PointView`` to a PCL `
     Cloud::Ptr cloud_in(new Cloud);
     pclsupport::PDALtoPCD(std::make_shared<PointView>(view), *cloud_in, bounds);
 
-Next, we will create two vectors of indices - one for ground returns, one for non-ground returns - and make our first pass through the point cloud to populate these.
+Next, we will create two vectors of indices - one for ground returns, one for
+non-ground returns - and make our first pass through the point cloud to
+populate these.
 
 .. code-block:: cpp
 
@@ -67,7 +97,8 @@ Next, we will create two vectors of indices - one for ground returns, one for no
             nonground.push_back(id);
     }
 
-With our ground indices identified, we can use PCL to extract the ground returns into a new ``PointCloud``.
+With our ground indices identified, we can use PCL to extract the ground
+returns into a new ``PointCloud``.
 
 .. code-block:: cpp
 
@@ -79,7 +110,8 @@ With our ground indices identified, we can use PCL to extract the ground returns
     extract.setNegative(false);
     extract.filter(*cloud_ground);
 
-We repeat the extraction now, flipping ``setNegative`` from false to true to extract the non-ground returns into a new ``PointCloud``.
+We repeat the extraction now, flipping ``setNegative`` from false to true to
+extract the non-ground returns into a new ``PointCloud``.
 
 .. code-block:: cpp
 
@@ -87,7 +119,12 @@ We repeat the extraction now, flipping ``setNegative`` from false to true to ext
     extract.setNegative(true);
     extract.filter(*cloud_nonground);
 
-To compute the normalized height, we wish to find the nearest ground point for each non-ground point. Here, we achieve this by using a nearest neighbor interpolation scheme. One may prefer to use a more sophisticated interpolation scheme, but that is beyond the scope of this tutorial. We begin by defining model coefficients that will allow us to project the ground and non-ground clouds into the XY plane.
+To compute the normalized height, we wish to find the nearest ground point for
+each non-ground point. Here, we achieve this by using a nearest neighbor
+interpolation scheme. One may prefer to use a more sophisticated interpolation
+scheme, but that is beyond the scope of this tutorial. We begin by defining
+model coefficients that will allow us to project the ground and non-ground
+clouds into the XY plane.
 
 .. code-block:: cpp
 
@@ -118,7 +155,9 @@ followed by the non-ground points
     proj.setModelCoefficients(coefficients);
     proj.filter(*cloud_nonground_projected);
 
-Next, we create a KdTree to accelerate our nearest neighbor search. The tree is composed of only ground returns, as our non-ground returns will serve as query points for the nearest neighbor search.
+Next, we create a KdTree to accelerate our nearest neighbor search. The tree is
+composed of only ground returns, as our non-ground returns will serve as query
+points for the nearest neighbor search.
 
 .. code-block:: cpp
 
@@ -126,7 +165,11 @@ Next, we create a KdTree to accelerate our nearest neighbor search. The tree is
     ground_tree.reset(new pcl::search::KdTree<pcl::PointXYZ> (false));
     ground_tree->setInputCloud(cloud_ground_projected);
 
-We iterate over each of our projected non-ground points, searching for our nearest neighbor in the ground points. Using the indices of each the query (non-ground) and nearest neighbor (ground), we can retrieve the Z dimension from the input cloud, compute the height, and set this field in our original ``PointView``.
+We iterate over each of our projected non-ground points, searching for our
+nearest neighbor in the ground points. Using the indices of each the query
+(non-ground) and nearest neighbor (ground), we can retrieve the Z dimension
+from the input cloud, compute the height, and set this field in our original
+``PointView``.
 
 .. code-block:: cpp
 
@@ -184,7 +227,7 @@ If you'd like to overwrite your Z values, follow the height filter with :ref:`fi
 Example #3
 -------------------------------------------------------------------------------
 
-If you don't yet have points classified as ground, start with :ref:`filters.ground`.
+If you don't yet have points classified as ground, start with :ref:`filters.pmf`.
 
 ::
 
diff --git a/doc/tutorial/dart-throwing.rst b/doc/tutorial/dart-throwing.rst
index 98cc23c..d64f574 100644
--- a/doc/tutorial/dart-throwing.rst
+++ b/doc/tutorial/dart-throwing.rst
@@ -10,7 +10,14 @@ Performing Poisson Sampling of Point Clouds Using Dart Throwing
 
 
 This tutorial will describe the creation of a new filter for sampling point
-cloud data, :ref:`filters.dartsample`.
+cloud data, :ref:`filters.sample`.
+
+.. note::
+
+  ``filters.dartsample`` required PCL and has since been replaced by
+  :ref:`filters.sample`, which is a native PDAL filter. We leave this tutorial
+  as an example of how to create a filter, and of how to work with the PCL
+  plugin, but the filter in reference is no longer available.
 
 Introduction
 -------------------------------------------------------------------------------
diff --git a/doc/tutorial/overview.rst b/doc/tutorial/overview.rst
index f92f2ff..7752e98 100644
--- a/doc/tutorial/overview.rst
+++ b/doc/tutorial/overview.rst
@@ -6,7 +6,7 @@ PDAL Architecture Overview
 
 :Author: Andrew Bell
 :Contact: andrew at hobu.co
-:Date: 9/3/2014
+:Date: 5/15/2016
 
 PDAL is a set of applications and library to facilitate translation of point
 cloud data between various formats.  In addition, it provides some facilities
@@ -56,14 +56,20 @@ formats provide this information in a header or preamble.  PDAL calls each of
 the elements that make up a point a dimension.  PDAL predefines the dimensions
 that are in common use by the formats that it currently supports.  Readers may
 register their use of a predefined dimension or may have PDAL create a
+<<<<<<< Updated upstream
 dimension with a name and type as requested.  Dimensions are described by the
 enumeration pdal::Dimension::Id and associated functions in Dimension.hpp.
+=======
+dimension with a name and type as requested.  Dimensions are described in a
+JSON file, Dimension.json.
+>>>>>>> Stashed changes
 
 PDAL has a default type (Double, Float, Signed32, etc.) for each of its
 predefined dimensions which is believed to be sufficient to accurately
 hold the necessary data.  Only when the default data type is deemed
 insufficient should a request be made to "upgrade" a storage datatype.  There
-is no facility to "downsize" a dimension type to save memory.  Dimension.hpp
+is no simple facility to "downsize" a dimension type to save memory, though
+it can be done by creating a custom PointLayout object.  Dimension.json
 can be examined to determine the default storage type of each predefined
 dimension.  In most cases knowledge of the storage data type for
 a dimension isn't required.  PDAL properly converts data to and from the
@@ -98,10 +104,16 @@ pipeline stages are executed.  Most functions receive a PointTableRef object,
 which refers to the active point table.  A PointTableRef can be stored
 or copied cheaply.
 
+A subclass of PointTable called StreamingPointTable exists to allow a pipeline
+to run without loading all points in memory.  A StreamingPointTable holds a
+fixed number of points.  Some filters can't operate in streaming mode and
+an attempt to run a pipeline with a stage that doesn't support streaming
+will raise an exception.
+
 Point View
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-A point view (PointView object) stores references to points.  All storage
+A point view (PointView object) stores references to points.  Storage
 and retrieval of points is done through a point view rather than directly
 through a point table.  Point data is accessed from a point view through a
 point ID (type PointId), which is an integer value.  The first point reference
@@ -118,10 +130,17 @@ existing reference to a destination point view. The point ID of the appended
 point in the destination view may be different than the point ID of the same
 point in the source view.  The point ID of an appended point reference is the
 same as the size of the point view after the operation.  Note that appending a
-point reference does not create a new point, rather, it creates another
+point reference does not create a new point.  Rather, it creates another
 reference to an existing point.  There are currently no built-in facilities for
 creating copies of points.
 
+Point Reference
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Some functions take a reference to a single point (PointRef object).
+In streaming mode, stages implement the processOne() function which operates
+on a point reference instead of a point view.
+
 Making a Stage (Reader, Filter or Writer):
 ................................................................................
 
@@ -129,35 +148,17 @@ All stages (Stage object) share a common interface, though readers, filters and
 writers each have a simplified interface if the generic stage interface is more
 complex than necessary.  One should create a new stage by creating a subclass of
 reader (Reader object), filter (Filter or MultiFilter object) or writer (Writer
-object).  When a pipeline is created, each stage is created using its default
+object).  When a pipeline is made, each stage is created using its default
 constructor.
 
-Each stage class should invoke two or three public macros to allow the stage to
-be hooked into the PDAL infrastructure:
-
-    SET_STAGE_NAME(<name>, <description>)
-
-    name:  A character array (string) constant that will be used to reference
-    the stage from the command line.  Typically, readers are named
-    “readers.<something>”, filters are “filters.<something>” and writers
-    are ”writers.<something>”.
-
-    description: A character array (string) constant that may be more meaningful
-    than the name.  It appears in some debug and informational output.
-
-    SET_STAGE_LINK(<http_link>)  [optional]:
-
-    http_link:  A character array (string) constant that references a web site
-    containing documentation about the stage.  It allows the information to be
-    integrated into PDAL’s web site and user information.
-
 When a pipeline is started, each of its stages is processed in two distinct
 steps.  First, all stages are prepared.
 
 Stage Preparation
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Preparation of a stage consists of executing the following private virtual
+Preparation of a stage is done by calling the prepare() function of the stage
+at the end of the pipeline.  prepare() executes the following private virtual
 functions calls, none of which need to be implemented in a stage unless desired.
 Each stage is guaranteed to be prepared after all stages that precede it in the
 pipeline.
@@ -195,19 +196,21 @@ pipeline.
     not be added to the layout of a pipeline’s point layout except in this
     method.
 
+4) void prepared(PointTableRef)
 
-
+    Called after dimensions are added.  It can be used to verify state and
+    raise exceptions before stage execution.
 
 
 Stage Execution
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 After all stages are prepared, processing continues with the execution of each
-stage.  Each stage will be executed only after all stages preceding it in a
-pipeline have been executed.  A stage is executed by invoking the following
-private virtual methods.  It is important to note that ready() and done() are
-called only once for each stage while run() is called once for each point view
-to be processed by the stage.
+stage by calling execute().  Each stage will be executed only after all stages
+preceding it in a pipeline have been executed.  A stage is executed by
+invoking the following private virtual methods.  It is important to note
+that ready() and done() are called only once for each stage while run()
+is called once for each point view to be processed by the stage.
 
 1) void ready(PointTablePtr table)
 
@@ -234,7 +237,28 @@ to be processed by the stage.
     a closing of databases, writing file footers, rewriting headers or
     closing or renaming files.
 
+Streaming Stage Execution
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+PDAL normally processes all points through each stage before passing the
+points to the next stage.  This means that all point data is held in memory
+during processing.  There are some situations that may make this undesirable.
+As an alternative, PDAL allows execution of data with a point table that
+contains a fixed number of points (StreamPointTable).  When a StreamPointTable
+is passed to the execute() function, the private run() function detailed above
+isn't called, and instead processOne() is called.  If a StreamPointTable is
+passed to execute() but a pipeline stage doesn't implement processOne(),
+an exception is thrown.
+
+bool processOne(PointRef& ref)
+
+    This method allows processing of a single point.  A reader will typically
+    read a point from an input source.  When a reader returns 'false' from
+    this function, it indicates that there are no more points to be read.
+    When a filter returns 'false' from this funciton, it indicates
+    that the point just processed should be filtered out and not passed
+    to subsequent stages for processing.
+    
 Implementing a Reader
 ................................................................................
 
@@ -282,6 +306,10 @@ precision floating point.
         }
     }
 
+If a reader implements initialize() and opens a source file during the function,
+the file should be closed again before exiting the function to ensure that
+filehandles aren't exhausted when processing a large number of files.
+
 Readers should use the ready() function to reset the input data to a state
 where the first point can be read from the source.  The done() function
 should be used to free resources or reset the state initialized in ready().
@@ -324,7 +352,7 @@ point_count_t read(PointViewPtr view, point_count_t count)
             {
                 double x, y, z;
 
-                // Read X, Y and from input source.
+                // Read X, Y and Z from input source.
                 x = m_file.read<double>(pos);
                 pos += sizeof(double);
                 y = m_file.read<double>(pos);
@@ -348,6 +376,53 @@ point_count_t read(PointViewPtr view, point_count_t count)
     input so that subsequent calls to read() will result in all points being
     read.
 
+    Here's the same function written so that streaming can be supported:
+
+    ::
+
+        point_count_t MyFormat::read(PointViewPtr view, point_count_t count)
+        {
+            // Determine the number of points remaining in the input.
+            point_count_t remainingInput = m_totalNumPts - m_index;
+
+            // Determine the number of points to read.
+            count = std::min(count, remainingInput);
+
+            // Determine the ID of the next point in the point view
+            PointId nextId = view->size();
+
+            // Determine the current input position.
+            auto pos = m_pointSize * m_index;
+
+            point_count_t remaining = count;
+            while (remaining--)
+            {
+                PointRef point(view->point(nextId));
+               
+                processOne(point);
+                nextId++;
+            }
+            m_index += count;
+            return count;
+        }
+
+        bool MyFormat::processOne(PointRef& point)
+        {
+            double x, y, z;
+
+            // Read X, Y and Z from input source.
+            x = m_file.read<double>(pos);
+            pos += sizeof(double);
+            y = m_file.read<double>(pos);
+            pos += sizeof(double);
+            z = m_file.read<double>(pos);
+            pos += sizeof(double);
+
+            point.setField(Dimension::Id::X, x);
+            point.setField(Dimension::Id::Y, y);
+            point.setField(Dimension::Id::Z, z);
+            return m_file.ok();
+        }
 
 .. _implementing-a-filter:
 
@@ -387,10 +462,10 @@ void filter(PointViewPtr view)
             }
         }
 
-    The filter simply loops through the points, retrieving the X, Y and Z values
-    of each point, transforms those value using a reprojection algorithm and
-    then stores the transformed values in the point table using the point
-    view’s setField() function.
+    The filter simply loops through the points, retrieving the X, Y and Z
+    values of each point, transforms those value using a reprojection
+    algorithm and then stores the transformed values in the point table
+    using the point view’s setField() function.
 
     A filter may need to use the run() function instead of filter(), typically
     because it needs to create multiple output point views from a single input
@@ -447,3 +522,10 @@ call to ready().
             out << setw(10) << view->getFieldAs<double>(Dimension::Id::Z, id);
         }
     }
+
+    bool processOne(PointRef& point)
+    {
+        out << setw(10) << point.getFieldAs<double>(Dimension::Id::X);
+        out << setw(10) << point.getFieldAs<double>(Dimension::Id::Y);
+        out << setw(10) << point.getFieldAs<double>(Dimension::Id::Z);
+    }
diff --git a/doc/tutorial/pcl_block_tutorial.rst b/doc/tutorial/pcl_block_tutorial.rst
index c73fbdc..0c69c79 100644
--- a/doc/tutorial/pcl_block_tutorial.rst
+++ b/doc/tutorial/pcl_block_tutorial.rst
@@ -180,24 +180,17 @@ returning only those points with z values in the range 100 to 200.
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "name": "PassThroughExample",
-            "filters":
-            [
-                {
-                    "name": "PassThrough",
-                    "setFilterFieldName": "z",
-                    "setFilterLimits":
-                    {
-                        "min": 410.0,
-                        "max": 440.0
-                    }
-                }
-            ]
+            "name": "PassThrough",
+            "setFilterFieldName": "z",
+            "setFilterLimits":
+            {
+                "min": 410.0,
+                "max": 440.0
+            }
         }
-    }
+    ]
 
 (This example is taken from the unit test
 `PCLBlockFilterTest_example_PassThrough_1`.)
@@ -216,34 +209,24 @@ their public member functions by name.
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
+        {
+            "name": "PassThrough",
+            "help": "filter z values to the range [410,440]",
+            "setFilterFieldName": "z",
+            "setFilterLimits":
+            {
+                "min": 410.0,
+                "max": 440.0
+            }
+        },
         {
-            "name": "CombinedExample",
-            "help": "Apply passthrough filter followed by statistical outlier removal",
-            "version": 1.0,
-            "author": "Bradley J Chambers",
-            "filters":
-            [
-                {
-                    "name": "PassThrough",
-                    "help": "filter z values to the range [410,440]",
-                    "setFilterFieldName": "z",
-                    "setFilterLimits":
-                    {
-                        "min": 410.0,
-                        "max": 440.0
-                    }
-                },
-                {
-                    "name": "StatisticalOutlierRemoval",
-                    "help": "apply outlier removal",
-                    "setMeanK": 8,
-                    "setStddevMulThresh": 0.2
-                }
-            ]
+            "name": "StatisticalOutlierRemoval",
+            "help": "apply outlier removal",
+            "setMeanK": 8,
+            "setStddevMulThresh": 0.2
         }
-    }
+    ]
 
 (This example is taken from the unit test
 `PCLBlockFilterTest_example_PassThrough_2`.)
@@ -265,42 +248,28 @@ To run the PMF with default settings, the PCL Block JSON is simply:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "name": "ProgressiveMorphologicalFilterExample",
-            "filters":
-            [
-                {
-                    "name": "ProgressiveMorphologicalFilter"
-                    "setMaxWindowSize": 200,
-                }
-            ]
+            "name": "ProgressiveMorphologicalFilter"
+            "setMaxWindowSize": 200,
         }
-    }
+    ]
 
 Additional parameters can be set by advanced users:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "name": "ProgressiveMorphologicalFilterAdvancedExample",
-            "filters":
-            [
-                {
-                    "name": "ProgressiveMorphologicalFilter",
-                    "setCellSize": 1.0,
-                    "setMaxWindowSize": 200,
-                    "setSlope": 1.0,
-                    "setInitialDistance": 0.5,
-                    "setMaxDistance": 3.0,
-                    "setExponential": true
-                }
-            ]
+            "name": "ProgressiveMorphologicalFilter",
+            "setCellSize": 1.0,
+            "setMaxWindowSize": 200,
+            "setSlope": 1.0,
+            "setInitialDistance": 0.5,
+            "setMaxDistance": 3.0,
+            "setExponential": true
         }
-    }
+    ]
 
 (These examples are taken from the unit tests
 `PCLBlockFilterTest_example_PMF_1` and `PCLBlockFilterTest_example_PMF_2`.)
diff --git a/doc/tutorial/pcl_ground.rst b/doc/tutorial/pcl_ground.rst
index 4f3bb4d..bdb2ca7 100644
--- a/doc/tutorial/pcl_ground.rst
+++ b/doc/tutorial/pcl_ground.rst
@@ -12,6 +12,15 @@ Identifying ground returns using ProgressiveMorphologicalFilter segmentation
 Implements the Progressive Morphological Filter for segmentation of ground
 points.
 
+.. note::
+  
+  ``filters.ground`` required PCL and has since been replaced by
+  :ref:`filters.pmf`, which is a native PDAL filter. :ref:`ground_command` has
+  been retained, but now calls :ref:`filters.pmf` under the hood as opposed to
+  ``filters.ground`` and is installed as a native PDAL kernel independent of the
+  PCL plugin. As such, the outputs shown in this tutorial may vary slightly, but
+  the underlying algorithm is identical.
+
 Background
 ------------------------------------------------------------------------------
 
diff --git a/doc/tutorial/pcl_spec.rst b/doc/tutorial/pcl_spec.rst
index f64560c..9eaa335 100644
--- a/doc/tutorial/pcl_spec.rst
+++ b/doc/tutorial/pcl_spec.rst
@@ -5,9 +5,9 @@ Draft PCL JSON Specification
 ============================
 
 :Author: Bradley J. Chambers (RadiantBlue Technologies, Inc.)
-:Revision: 0.1
-:Date: 28 February 2014
-:Copyright: Copyright (c) 2014, RadiantBlue Technologies, Inc. This work is licensed under a Creative Commons Attribution 3.0 United States License.
+:Revision: 0.2
+:Date: 13 December 2016
+:Copyright: Copyright (c) 2014-2016, RadiantBlue Technologies, Inc. This work is licensed under a Creative Commons Attribution 3.0 United States License.
 
 The PCL JSON specification is a point cloud processing pipeline interchange
 format based on JavaScript Object Notation (JSON), drawing inspiration from
@@ -24,14 +24,14 @@ both GeoJSON and TopoJSON.
 Introduction
 ============
 
-A PCL JSON object represents a processing pipeline.
+A PCL JSON array represents a processing pipeline.
 
-A complete PCL JSON data structure is always an object (in JSON terms). In PCL
-JSON, an object consists of a collection of name/value pairs -- also called
-members. For each member, the name is always a string. Member values are either
-a string, number, object, array or one of the literals: "true", "false", and
-"null". An array consists of elements where each element is a value as
-described above.
+A complete PCL JSON data structure is always an array of objects (in JSON
+terms). In PCL JSON, an object consists of a collection of name/value pairs --
+also called members. For each member, the name is always a string. Member values
+are either a string, number, object, array or one of the literals: "true",
+"false", and "null". An array consists of elements where each element is a value
+as described above.
 
 
 
@@ -42,56 +42,62 @@ A very simple PCL JSON pipeline:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "name": "My cool pipeline",
-            "filters":
-            [
-                {
-                    "name": "VoxelGrid",
-                    "setLeafSize":
-                    {
-                        "x": 1.0,
-                        "y": 1.0,
-                        "z": 1.0
-                    }
-                }
-            ]
+            "name": "VoxelGrid",
+            "setLeafSize":
+            {
+                "x": 1.0,
+                "y": 1.0,
+                "z": 1.0
+            }
         }
-    }
+    ]
 
 A more complex pipeline, containing two filters:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
+        {
+            "name": "PassThrough",
+            "setFilterFieldName": "z",
+            "setFilterLimits":
+            {
+                "min": 410.0,
+                "max": 440.0
+            }
+        },
         {
-            "name": "CombinedExample",
-            "help": "Apply passthrough filter followed by statistical outlier removal",
-            "version": 1.0,
-            "author": "Bradley J Chambers",
-            "filters":
-            [
-                {
-                    "name": "PassThrough",
-                    "help": "filter z values to the range [410,440]",
-                    "setFilterFieldName": "z",
-                    "setFilterLimits":
+            "name": "StatisticalOutlierRemoval",
+            "setMeanK": 8,
+            "setStddevMulThresh": 0.2
+        }
+    ]
+
+A PCL pipeline is embedded within a PDAL pipeline as the "methods" option to :ref:`filters.pclblock` as shown below:
+
+.. code-block:: json
+
+    {
+        "pipeline": [
+            "input.las",
+            {
+                "type": "filters.pclblock",
+                "methods": [
                     {
-                        "min": 410.0,
-                        "max": 440.0
+                        "name": "VoxelGrid",
+                        "setLeafSize":
+                        {
+                            "x": 1.0,
+                            "y": 1.0,
+                            "z": 1.0
+                        }
                     }
-                },
-                {
-                    "name": "StatisticalOutlierRemoval",
-                    "help": "apply outlier removal",
-                    "setMeanK": 8,
-                    "setStddevMulThresh": 0.2
-                }
-            ]
-        }
+                ]
+            },
+            "output.las"
+        ]
     }
 
 
@@ -114,35 +120,21 @@ Definitions
 PCL JSON Objects
 ================
 
-PCL JSON always consists of a single object. This object (referred to as the
-PCL JSON object below) represents a processing pipeline.
-
-* The PCL JSON object may have any number of members (name/value pairs).
-
-* The PCL JSON object must have a "pipeline" object.
-
+PCL JSON always consists of a single array of PCL JSON objects. This array
+(referred to as the PCL JSON array below) represents a processing pipeline.
 
+* The PCL JSON array may have any number of PCL JSON objects.
 
-Pipeline Objects
-----------------
+* A PCL JSON object shall have a "name" member that identifies a supported PCL
+  filter (as documented below).
 
-* A pipeline may have a member with the name "name" whose value is a string.
-
-* A pipeline may have a member with the name "help" whose value is a string.
-
-* A pipeline may have a member with the name "version" whose value is a number.
-
-* A pipeline must have a member with the name "filters" whose value is an array
-  of filters.
+* A PCL JSON object may have any number of members (name/value pairs).
 
 
 
 Filters
 .......
 
-A pipeline must have a "filters" member whose value is an array of zero or more
-filters.
-
 A filter is any of the PCL filters that has been exposed through the PCL
 pipeline class.
 
@@ -171,25 +163,19 @@ Example:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "filters":
-            [
-                {
-                    "name": "ApproximateProgressiveMorphologicalFilter",
-                    "setMaxWindowSize": 65,
-                    "setSlope": 0.7,
-                    "setMaxDistance": 10,
-                    "setInitialDistance": 0.3,
-                    "setCellSize": 1,
-                    "setBase": 2,
-                    "setExponential": false,
-                    "setNegative": false
-                }
-            ]
+            "name": "ApproximateProgressiveMorphologicalFilter",
+            "setMaxWindowSize": 65,
+            "setSlope": 0.7,
+            "setMaxDistance": 10,
+            "setInitialDistance": 0.3,
+            "setCellSize": 1,
+            "setBase": 2,
+            "setExponential": false,
+            "setNegative": false
         }
-    }
+    ]
 
 **Parameters**
 
@@ -225,47 +211,6 @@ setNegative: bool
 
 
 
-ConditionalRemoval
-``````````````````
-
-.. seealso::
-
-    :ref:`filters.range` implements support for this PCL operation as a
-    PDAL filter
-
-This filter removes normals outside of a given Z range.
-
-PCL details: http://docs.pointclouds.org/trunk/classpcl_1_1_conditional_removal.html
-
-Example:
-
-.. code-block:: json
-
-    {
-        "pipeline":
-        {
-            "filters":
-            [
-                {
-                    "name": "ConditionalRemoval",
-                    "normalZ":
-                    {
-                        "min": 0,
-                        "max": 0.95
-                    }
-                }
-            ]
-        }
-    }
-
-**Parameters**
-
-normalZ: object `{"min": float, "max": float}`
-  Set the numerical limits for filtering points based on the z component of
-  their normal. [default: `{"min": 0.0, "max": FLT_MAX}`]
-
-
-
 GridMinimum
 ```````````
 
@@ -278,18 +223,12 @@ Example:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "filters":
-            [
-                {
-                    "name": "GridMinimum",
-                    "setResolution": 2.0
-                }
-            ]
+            "name": "GridMinimum",
+            "setResolution": 2.0
         }
-    }
+    ]
 
 **Parameters**
 
@@ -298,45 +237,6 @@ setResolution: float
 
 
 
-NormalEstimation
-````````````````
-
-
-**Description**
-
-This filter computes the surfaces normals of the points in the input.
-
-PCL details: http://docs.pointclouds.org/1.7.1/classpcl_1_1_normal_estimation.html
-
-Example:
-
-.. code-block:: json
-
-    {
-        "pipeline":
-        {
-            "filters":
-            [
-                {
-                    "name": "NormalEstimation",
-                    "setRadiusSearch": 2
-                }
-            ]
-        }
-    }
-
-**Parameters**
-
-setKSearch: float
-    Set the number of k nearest neighbors to use for the feature estimation.
-    [default: 0.0]
-
-setRadiusSearch: float
-    Set the sphere radius that is to be used for determining the nearest
-    neighbors used for the feature estimation. [default: 1.0]
-
-
-
 PassThrough
 ```````````
 
@@ -350,23 +250,17 @@ Example:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "filters":
-            [
-                {
-                    "name": "PassThrough",
-                    "setFilterFieldName": "z",
-                    "setFilterLimits":
-                    {
-                        "min": 3850100,
-                        "max": 3850200
-                    }
-                }
-            ]
+            "name": "PassThrough",
+            "setFilterFieldName": "z",
+            "setFilterLimits":
+            {
+                "min": 3850100,
+                "max": 3850200
+            }
         }
-    }
+    ]
 
 **Parameters**
 
@@ -395,7 +289,7 @@ ProgressiveMorphologicalFilter (PMF)
 
 .. seealso::
 
-    :ref:`filters.ground` implements support for this operation as a
+    :ref:`filters.pmf` implements support for this operation as a
     PDAL filter
 
 **Description**
@@ -408,25 +302,19 @@ Example:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "filters":
-            [
-                {
-                    "name": "ProgressiveMorphologicalFilter",
-                    "setMaxWindowSize": 65,
-                    "setSlope": 0.7,
-                    "setMaxDistance": 10,
-                    "setInitialDistance": 0.3,
-                    "setCellSize": 1,
-                    "setBase": 2,
-                    "setExponential": false,
-                    "setNegative": true
-                }
-            ]
+            "name": "ProgressiveMorphologicalFilter",
+            "setMaxWindowSize": 65,
+            "setSlope": 0.7,
+            "setMaxDistance": 10,
+            "setInitialDistance": 0.3,
+            "setCellSize": 1,
+            "setBase": 2,
+            "setExponential": false,
+            "setNegative": true
         }
-    }
+    ]
 
 **Parameters**
 
@@ -467,7 +355,7 @@ RadiusOutlierRemoval
 
 .. seealso::
 
-    :ref:`filters.radiusoutlier` implements support for this operation
+    :ref:`filters.outlier` implements support for this operation
     as a PDAL filter
 
 
@@ -482,19 +370,13 @@ Example:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "filters":
-            [
-                {
-                    "name": "RadiusOutlierRemoval",
-                    "setMinNeighborsInRadius": 8,
-                    "setRadiusSearch": 1.0
-                }
-            ]
+            "name": "RadiusOutlierRemoval",
+            "setMinNeighborsInRadius": 8,
+            "setRadiusSearch": 1.0
         }
-    }
+    ]
 
 **Parameters**
 
@@ -513,7 +395,7 @@ StatisticalOutlierRemoval
 
 .. seealso::
 
-    :ref:`filters.statisticaloutlier` implements support for this
+    :ref:`filters.outlier` implements support for this
     operation as a PDAL filter
 
 **Description**
@@ -526,19 +408,13 @@ Example:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "filters":
-            [
-                {
-                    "name": "StatisticalOutlierRemoval",
-                    "setMeanK": 8,
-                    "setStddevMulThresh": 1.17
-                }
-            ]
+            "name": "StatisticalOutlierRemoval",
+            "setMeanK": 8,
+            "setStddevMulThresh": 1.17
         }
-    }
+    ]
 
 **Parameters**
 
@@ -570,23 +446,17 @@ Example:
 
 .. code-block:: json
 
-    {
-        "pipeline":
+    [
         {
-            "filters":
-            [
-                {
-                    "name": "VoxelGrid",
-                    "setLeafSize":
-                    {
-                        "x": 1.0,
-                        "y": 1.0,
-                        "z": 1.0
-                    }
-                }
-            ]
+            "name": "VoxelGrid",
+            "setLeafSize":
+            {
+                "x": 1.0,
+                "y": 1.0,
+                "z": 1.0
+            }
         }
-    }
+    ]
 
 **Parameters**
 
diff --git a/doc/tutorial/using.rst b/doc/tutorial/using.rst
index de6ece7..ac9d911 100644
--- a/doc/tutorial/using.rst
+++ b/doc/tutorial/using.rst
@@ -30,7 +30,7 @@ Begin by creating a file named CMakeLists.txt that contains:
   add_definitions(${PDAL_DEFINITIONS})
   set(CMAKE_CXX_FLAGS "-std=c++11")
   add_executable(tutorial tutorial.cpp)
-  target_link_libraries(tutorial ${PDAL_LIBRARIES})
+  target_link_libraries(tutorial PRIVATE ${PDAL_LIBRARIES})
 
 CMakeLists explained
 -------------------------------------------------------------------------------
@@ -88,7 +88,7 @@ We use the `add_executable` command to tell CMake to create an executable named
 
 .. code-block:: cmake
 
-  target_link_libraries(tutorial ${PDAL_LIBRARIES})
+  target_link_libraries(tutorial PRIVATE ${PDAL_LIBRARIES})
 
 We assume that the tutorial executable makes calls to PDAL functions. To make
 the linker aware of the PDAL libraries, we use `target_link_libraries` to link
diff --git a/doc/tutorial/writing-filter.rst b/doc/tutorial/writing-filter.rst
index 0e9d026..b89b6be 100644
--- a/doc/tutorial/writing-filter.rst
+++ b/doc/tutorial/writing-filter.rst
@@ -6,7 +6,7 @@ Writing a filter
 
 :Author: Bradley Chambers
 :Contact: brad.chambers at gmail.com
-:Date: 11/11/2015
+:Date: 10/26/2016
 
 
 PDAL can be extended through the development of filter functions.
@@ -16,9 +16,16 @@ PDAL can be extended through the development of filter functions.
     For more on filters and their role in PDAL, please refer to
     :ref:`overview`.
 
-Every filter stage in PDAL is implemented as a plugin (sometimes referred to as a "driver"). Filters native to PDAL, such as :ref:`filters.ferry`, are implemented as _static_ filters and are statically linked into the PDAL library. Filters that require extra/optional dependencies, or are external to the core PDAL codebase altogether, such as :ref:`filters.ground`, are implemented as _shared_ filters, and are built as individual shared libraries, discoverable by PDAL at runtime.
+Every filter stage in PDAL is implemented as a plugin (sometimes referred to as
+a "driver"). Filters native to PDAL, such as :ref:`filters.ferry`, are
+implemented as _static_ filters and are statically linked into the PDAL
+library. Filters that require extra/optional dependencies, or are external to
+the core PDAL codebase altogether, such as :ref:`filters.pmf`, are
+implemented as _shared_ filters, and are built as individual shared libraries,
+discoverable by PDAL at runtime.
 
-In this tutorial, we will give a brief example of a filter, with notes on how to make it static or shared.
+In this tutorial, we will give a brief example of a filter, with notes on how
+to make it static or shared.
 
 
 The header
@@ -28,14 +35,18 @@ First, we provide a full listing of the filter header.
 
 .. literalinclude:: ../../examples/writing-filter/MyFilter.hpp
    :language: cpp
+   :linenos:
 
-This header should be relatively straightforward, but we will point out three methods that must be declared for the plugin interface to be satisfied.
+This header should be relatively straightforward, but we will point out three
+methods that must be declared for the plugin interface to be satisfied.
 
 .. literalinclude:: ../../examples/writing-filter/MyFilter.hpp
    :language: cpp
    :lines: 23-25
 
-In many instances, you should be able to copy this header template verbatim, changing only the filter class name, includes, and member functions/variables as required by your implementation.
+In many instances, you should be able to copy this header template verbatim,
+changing only the filter class name, includes, and member functions/variables
+as required by your implementation.
 
 The source
 ...............................................................................
@@ -44,63 +55,96 @@ Again, we start with a full listing of the filter source.
 
 .. literalinclude:: ../../examples/writing-filter/MyFilter.cpp
    :language: cpp
+   :linenos:
 
-For your filter to be available to PDAL at runtime, it must adhere to the PDAL plugin interface. As a convenience, we provide the macros in ``pdal_macros.hpp`` to do just this.
+For your filter to be available to PDAL at runtime, it must adhere to the PDAL
+plugin interface. As a convenience, we provide the macros in
+``pdal_macros.hpp`` to do just this.
 
-We begin by creating a ``PluginInfo`` struct containing three identifying elements - the filter name, description, and a link to documentation.
+We begin by creating a ``PluginInfo`` struct containing three identifying
+elements - the filter name, description, and a link to documentation.
 
 .. literalinclude:: ../../examples/writing-filter/MyFilter.cpp
    :language: cpp
-   :lines: 14-16
+   :lines: 15-18
+   :linenos:
 
-PDAL requires that filter names always begin with ``filters.``, and end with a string that uniquely identifies the filter. The description will be displayed to users of the PDAL CLI (``pdal --drivers``).
+PDAL requires that filter names always begin with ``filters.``, and end with a
+string that uniquely identifies the filter. The description will be displayed
+to users of the PDAL CLI (``pdal --drivers``).
 
-Next, we pass the following to the ``CREATE_STATIC_PLUGIN`` macro, in order: PDAL plugin ABI major version, PDAL plugin ABI minor version, filter class name, stage type (``Filter``), and our ``PluginInfo`` struct.
+Next, we pass the following to the ``CREATE_STATIC_PLUGIN`` macro, in order:
+PDAL plugin ABI major version, PDAL plugin ABI minor version, filter class
+name, stage type (``Filter``), and our ``PluginInfo`` struct.
 
 .. literalinclude:: ../../examples/writing-filter/MyFilter.cpp
    :language: cpp
-   :lines: 18
+   :lines: 19
 
-To create a shared plugin, we simply change ``CREATE_STATIC_PLUGIN`` to ``CREATE_SHARED_PLUGIN``.
+To create a shared plugin, we simply change ``CREATE_STATIC_PLUGIN`` to
+``CREATE_SHARED_PLUGIN``.
 
-Finally, we implement a method to get the plugin name, which is primarily used by the PDAL CLI when using the ``--drivers`` or ``--options`` arguments.
+Finally, we implement a method to get the plugin name, which is primarily used
+by the PDAL CLI when using the ``--drivers`` or ``--options`` arguments.
 
 .. literalinclude:: ../../examples/writing-filter/MyFilter.cpp
    :language: cpp
-   :lines: 20-23
+   :lines: 21-24
+   :linenos:
 
-Now that the filter has implemented the proper plugin interface, we will begin to implement some methods that actually implement the filter. First, ``getDefaultOptions()`` is used to advertise those options that the filter provides. Within PDAL, this is primarily used as a means of displaying options via the PDAL CLI with the ``--options`` argument. It provides the user with the option names, descriptions, and default values.
+Now that the filter has implemented the proper plugin interface, we will begin
+to implement some methods that actually implement the filter. First,
+``getDefaultOptions()`` is used to advertise those options that the filter
+provides. Within PDAL, this is primarily used as a means of displaying options
+via the PDAL CLI with the ``--options`` argument. It provides the user with the
+option names, descriptions, and default values.
 
 .. literalinclude:: ../../examples/writing-filter/MyFilter.cpp
    :language: cpp
-   :lines: 25-30
+   :lines: 26-29
+   :linenos:
 
-The ``processOptions()`` method is used to parse any provided options. Here, we get the value of ``param``, if provided, else we populate ``m_value`` with the default value of ``1.0``.
+The ``addArgs()`` method is used to register and bind any provided options to
+the stage. Here, we get the value of ``param``, if provided, else we populate
+``m_value`` with the default value of ``1.0``.
 
 .. literalinclude:: ../../examples/writing-filter/MyFilter.cpp
    :language: cpp
-   :lines: 32-35
+   :lines: 31-36
+   :linenos:
 
-In ``addDimensions()`` we make sure that the known ``Intensity`` dimension is registered. We can also add a custom dimension, ``MyDimension``, which will be populated within ``run()``.
+In ``addDimensions()`` we make sure that the known ``Intensity`` dimension is
+registered. We can also add a custom dimension, ``MyDimension``, which will be
+populated within ``run()``.
 
 .. literalinclude:: ../../examples/writing-filter/MyFilter.cpp
    :language: cpp
-   :lines: 37-41
+   :lines: 38-43
+   :linenos:
 
-Finally, we define ``run()``, which takes as input a ``PointViewPtr`` and returns a ``PointViewSet``. It is here that we can transform existing dimensions, add data to new dimensions, or selectively add/remove individual points.
+Finally, we define ``run()``, which takes as input a ``PointViewPtr`` and
+returns a ``PointViewSet``. It is here that we can transform existing
+dimensions, add data to new dimensions, or selectively add/remove individual
+points.
 
-.. literalinclude:: ../../examples/writing-filter/MyFilter.cpp
-   :language: cpp
-   :lines: 43-48
-
-We suggest you take a closer look at our existing filters to get an idea of the power of the ``Filter`` stage and inspiration for your own filters!
+We suggest you take a closer look at our existing filters to get an idea of the
+power of the ``Filter`` stage and inspiration for your own filters!
 
 StageFactory
 ...............................................................................
 
-As of this writing, users must also make a couple of changes to ``StageFactory.cpp`` to properly register static plugins only (this is not required for shared plugins). It is our goal to eventually remove this requirement to further streamline development of add-on plugins.
+As of this writing, users must also make a couple of changes to
+``StageFactory.cpp`` to properly register static plugins only (this is not
+required for shared plugins). It is our goal to eventually remove this
+requirement to further streamline development of add-on plugins.
+
+.. note::
+
+    Modification of StageFactory is required for STATIC plugins only.
+    Dynamic plugins are registered at runtime.
 
-First, add the following line to the beginning of ``StageFactory.cpp`` (adjusting the path and filename as necessary).
+First, add the following line to the beginning of ``StageFactory.cpp``
+(adjusting the path and filename as necessary).
 
 .. code-block:: cpp
 
diff --git a/doc/tutorial/writing-kernel.rst b/doc/tutorial/writing-kernel.rst
index e004ef7..c7b78f9 100644
--- a/doc/tutorial/writing-kernel.rst
+++ b/doc/tutorial/writing-kernel.rst
@@ -6,7 +6,7 @@ Writing a kernel
 
 :Author: Bradley Chambers
 :Contact: brad.chambers at gmail.com
-:Date: 01/21/2015
+:Date: 10/16/2016
 
 
 PDAL's command-line application can be extended through the development of
diff --git a/doc/tutorial/writing-reader.rst b/doc/tutorial/writing-reader.rst
index ca1883a..af5a921 100644
--- a/doc/tutorial/writing-reader.rst
+++ b/doc/tutorial/writing-reader.rst
@@ -6,7 +6,7 @@ Writing a reader
 
 :Authors: Bradley Chambers, Scott Lewis
 :Contact: brad.chambers at gmail.com
-:Date: 11/13/2015
+:Date: 10/26/2016
 
 
 PDAL's command-line application can be extended through the development of
@@ -19,6 +19,7 @@ First, we provide a full listing of the reader header.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.hpp
    :language: cpp
+   :linenos:
 
 In your MyReader class, you will declare the necessary methods and variables
 needed to make the reader work and meet the plugin specifications.
@@ -26,18 +27,22 @@ needed to make the reader work and meet the plugin specifications.
 .. literalinclude:: ../../examples/writing-reader/MyReader.hpp
    :language: cpp
    :lines: 16-18
+   :linenos:
 
 These methods are required to fulfill the specs for defining a new plugin.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.hpp
    :language: cpp
-   :lines: 20-21
+   :lines: 20
+   :linenos:
 
-These methods are used for setting various defaults for the Reader.
+``getDefaultDimensions`` returns a list of :ref:`dimensions` that the
+reader provides.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.hpp
    :language: cpp
-   :lines: 24-26
+   :lines: 23-25
+   :linenos:
 
 ``m_stream`` is used to process the input, while ``m_index`` is used to track
 the index of the records.  ``m_scale_z`` is specific to MyReader, and will
@@ -45,11 +50,17 @@ be described later.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.hpp
    :language: cpp
-   :lines: 28-32
+   :lines: 27-31
+   :linenos:
 
 Various other override methods for the stage.  There are a few others that
 could be overridden, which will not be discussed in this tutorial.
 
+.. note::
+
+    See ``./include/pdal/Reader.hpp`` of the source tree for more methods
+    that a reader can override or implement.
+
 The source
 -------------------------------------------------------------------------------
 
@@ -57,16 +68,18 @@ Again, we start with a full listing of the reader source.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
+   :linenos:
 
 In your reader implementation, you will use a macro defined in pdal_macros.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 7-12
+   :lines: 9-12
+   :linenos:
 
 This macro registers the plugin with the PDAL code.  In this case, we are
 declaring this as a SHARED plugin, meaning that it will be located external
-to the main PDAL instalation.  The macro is supplied with a version number
+to the main PDAL installation.  The macro is supplied with a version number
 (major and minor), the class of the plugin, the parent class (in this case,
 to identify it as a reader), and an object with information.  This information
 includes the name of the plugin, a description, and a link to documentation.
@@ -76,35 +89,39 @@ in this tutorial.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 16-22
+   :lines: 18-21
+   :linenos:
 
-This method will process a set of default options for the reader.  In this
+This method will process a options for the reader.  In this
 example, we are setting the z_scale value to a default of 1.0, indicating
 that the Z values we read should remain as-is.  (In our reader, this could
 be changed if, for example, the Z values in the file represented mm values,
-and we want to represent them as m in the storage model).
-
-The options will then be processed elsewhere and will be supplied values from
-the pipeline configuration, etc.
+and we want to represent them as m in the storage model). ``addArgs`` will
+bind values given for the argument to the ``m_scale_z`` variable of the
+stage.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 24-27
+   :lines: 23-29
+   :linenos:
 
-This method takes an Options object and populates the reader's private
-variables with values found in the options object.
+This method registers the various dimensions the reader will use.  In our case,
+we are using the X, Y, and Z built-in dimensions, as well as a custom
+dimension MyData.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 29-35
+   :lines: 31-40
+   :linenos:
+
+This method returns the list of :ref:`dimensions` that the reader can
+provide.
 
-This method registers the various dimensions the reader will use.  In our case,
-we are using the X, Y, and Z built-in dimensions, as well as a custom
-dimension MyData.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 48-52
+   :lines: 42-46
+   :linenos:
 
 This method is called when the Reader is ready for use.  It will only be
 called once, regardless of the number of PointViews that are to be
@@ -112,15 +129,18 @@ processed.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 55-68
+   :lines: 49-62
+   :linenos:
 
 This is a helper function, which will convert a string value into the type
 specified when it's called.  In our example, it will be used to convert
 strings to doubles when reading from the input stream.
 
+
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 71-78
+   :lines: 65
+   :linenos:
 
 This method is the main processing method for the reader.  It takes a
 pointer to a Point View which we will build as we read from the file.  We
@@ -128,34 +148,40 @@ initialize some variables as well, and then reset the input stream with
 the filename used for the reader.  Note that in other readers, the contents
 of this method could be very different depending on the format of the file
 being read, but this should serve as a good start for how to build the
-PointView ojbect.
+PointView object.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 80-82
+   :lines: 74-76
+   :linenos:
 
-In prepration for reading the file, we prepare to skip some header lines.  In
+In preparation for reading the file, we prepare to skip some header lines.  In
 our case, the header is only a single line.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 83-87
+   :lines: 77-82
+   :linenos:
 
 Here we begin our main loop.  In our example file, the first line is a header,
 and each line thereafter is a single point.  If the file had a different format
 the method of looping and reading would have to change as appropriate.  We make
 sure we are skipping the header lines here before moving on.
 
+
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 91-99
+   :lines: 85-94
+   :linenos:
 
 Here we take the line we read in the for block header, split it, and make sure
 that we have the proper number of fields.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 101-112
+   :lines: 96-109
+   :linenos:
+
 
 Here we take the values we read and put them into the PointView object.  The
 X and Y fields are simply converted from the file and put into the respective
@@ -169,17 +195,15 @@ each iteration of the loop), and the dimension value.
 
 .. literalinclude:: ../../examples/writing-reader/MyReader.cpp
    :language: cpp
-   :lines: 111-122
+   :lines: 111-113
+   :linenos:
 
-Finally, we increment the nextId.  After the loop is done, we set the index
+Finally, we increment the nextId and make a call into the progress callback
+if we have one with our nextId.  After the loop is done, we set the index
 and number read, and return that value as the number of points read.
 This could differ in cases where we read multiple streams, but that won't
 be covered here.
 
-.. literalinclude:: ../../examples/writing-reader/MyReader.cpp
-   :language: cpp
-   :lines: 124-127
-
 When the read method is finished, the done method is called for any cleanup.
 In this case, we simply make sure the stream is reset.
 
@@ -190,6 +214,7 @@ The MyReader.cpp code can be compiled.  For this example, we'll use cmake.
 Here is the CMakeLists.txt file we will use:
 
 .. literalinclude:: ../../examples/writing-reader/CMakeLists.txt
+    :linenos:
 
 If this file is in the directory containing MyReader.hpp and MyReader.cpp,
 simply run ``cmake .``, followed by ``make``.  This will generate a file called
diff --git a/doc/tutorial/writing-writer.rst b/doc/tutorial/writing-writer.rst
index f16ec52..a4c99db 100644
--- a/doc/tutorial/writing-writer.rst
+++ b/doc/tutorial/writing-writer.rst
@@ -6,7 +6,7 @@ Writing a writer
 
 :Authors: Bradley Chambers, Scott Lewis
 :Contact: brad.chambers at gmail.com
-:Date: 11/18/2015
+:Date: 10/26/2016
 
 
 PDAL's command-line application can be extended through the development of
@@ -19,6 +19,7 @@ First, we provide a full listing of the writer header.
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.hpp
    :language: cpp
+   :linenos:
 
 In your MyWriter class, you will declare the necessary methods and variables
 needed to make the writer work and meet the plugin specifications.
@@ -26,6 +27,7 @@ needed to make the writer work and meet the plugin specifications.
 .. literalinclude:: ../../examples/writing-writer/MyWriter.hpp
    :language: cpp
    :lines: 11
+   :linenos:
 
 FileStreamPtr is defined to make the declaration of the stream easier to manage
 later on.
@@ -36,22 +38,17 @@ later on.
 
 These three methods are required to fulfill the specs for defining a new plugin.
 
-.. literalinclude:: ../../examples/writing-writer/MyWriter.hpp
-   :language: cpp
-   :lines: 23
-
-This method will be used to specify default options for the Writer.
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.hpp
    :language: cpp
-   :lines: 26-29
+   :lines: 24-28
 
 These methods are used during various phases of the pipeline.  There are also
 more methods, which will not be covered in this tutorial.
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.hpp
    :language: cpp
-   :lines: 31-37
+   :lines: 30-36
 
 These are variables our Writer will use, such as the file to write to, the
 newline character to use, the name of the data field to use to write the MyData
@@ -68,13 +65,14 @@ We will start with a full listing of the writer source.
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.cpp
    :language: cpp
+   :linenos:
 
 In the writer implementation, we will use a macro defined in pdal_macros,
 which is included in the include chain we are using.
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.cpp
    :language: cpp
-   :lines: 11-16
+   :lines: 10-15
 
 Here we define a struct with information regarding the writer, such as the
 name, a description, and a path to documentation.  We then use the macro
@@ -88,27 +86,30 @@ also possible, but requires some extra steps and will not be covered here.
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.cpp
    :language: cpp
-   :lines: 20-31
+   :lines: 19-30
+   :linenos:
 
 This struct is used for helping with the FileStreamPtr for cleanup.
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.cpp
    :language: cpp
-   :lines: 34-44
+   :lines: 33-40
+   :linenos:
 
-This method sets various default parameters.  They can be overridden via the
-pipeline, if desired.
+This method defines the arguments the writer provides and binds them to
+private variables.
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.cpp
    :language: cpp
    :lines: 47-63
 
-This method processes the options, including the default options given, and in
-this case also opens the output file stream for use.
+This method initializes our file stream in preparation for writing.
+
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.cpp
    :language: cpp
-   :lines: 66-82
+   :lines: 55-70
+   :linenos:
 
 The ready method is used to prepare the writer for any number of PointViews that
 may be passed in.  In this case, we are setting the precision for our double
@@ -117,7 +118,8 @@ and writing the header of the output file.
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.cpp
    :language: cpp
-   :lines: 85-101
+   :lines: 74-90
+   :linenos:
 
 This method is the main method for writing.  In our case, we are writing a very
 simple file, with data in the format of X:Y:Z:MyData.  We loop through each
@@ -136,7 +138,8 @@ to the output stream.
 
 .. literalinclude:: ../../examples/writing-writer/MyWriter.cpp
    :language: cpp
-   :lines: 104-107
+   :lines: 93-96
+   :linenos:
 
 This method is called when the writing is done.  In this case, it simply cleans
 up the output stream by resetting it.
@@ -148,6 +151,7 @@ To compile this reader, we will use cmake.  Here is the CMakeLists.txt file we
 will use for this process:
 
 .. literalinclude:: ../../examples/writing-writer/CMakeLists.txt
+    :linenos:
 
 If this file is in the directory with the MyWriter.hpp and MyWriter.cpp files,
 simply run ``cmake .`` followed by ``make``.  This will generate a file called
diff --git a/doc/workshop/exercises/analysis/denoising/denoise.json b/doc/workshop/exercises/analysis/denoising/denoise.json
index 7769662..d9a4d82 100644
--- a/doc/workshop/exercises/analysis/denoising/denoise.json
+++ b/doc/workshop/exercises/analysis/denoising/denoise.json
@@ -2,9 +2,10 @@
     "pipeline": [
         "/data/exercises/analysis/denoising/18TWK820985.laz",
         {
-            "type": "filters.statisticaloutlier",
-            "extract":"true",
-            "multiplier":3,
+            "type": "filters.outlier",
+            "method": "statistical",
+            "extract": "true",
+            "multiplier": 3,
             "mean_k": 8
         },
         {
@@ -20,4 +21,3 @@
         }
     ]
 }
-
diff --git a/doc/workshop/exercises/analysis/denoising/denoising.rst b/doc/workshop/exercises/analysis/denoising/denoising.rst
index d6162a9..8c12750 100644
--- a/doc/workshop/exercises/analysis/denoising/denoising.rst
+++ b/doc/workshop/exercises/analysis/denoising/denoising.rst
@@ -12,8 +12,8 @@ This exercise uses PDAL to remove unwanted noise in an ALS collection.
 .. warning::
 
     Our default :ref:`docker` machine instance is probably going to run out of
-    memory for this operation (it only has 1gb). We may need to recreate it with the following
-    commands to increase the available memory:
+    memory for this operation (it only has 1gb). We may need to recreate it with
+    the following commands to increase the available memory:
 
     1. Remove the existing machine instances
 
@@ -28,11 +28,11 @@ This exercise uses PDAL to remove unwanted noise in an ALS collection.
 Exercise
 --------------------------------------------------------------------------------
 
-PDAL provides a :ref:`filter <filters>` through |PCL| to apply a
-statistical filter to data.
+PDAL provides a :ref:`filter <filters>` through |PCL| to apply a statistical
+filter to data.
 
-Because this operation is somewhat complex, we are going to use a pipeline
-to define it.
+Because this operation is somewhat complex, we are going to use a pipeline to
+define it.
 
 .. include:: ./denoise.json
     :literal:
@@ -55,18 +55,18 @@ point cloud file we're going to read.
 
     "/data/exercises/analysis/denoising/18TWK820985.laz",
 
-2. :ref:`filters.statisticaloutlier`
+2. :ref:`filters.outlier`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The :ref:`filters.statisticaloutlier` PDAL filter does most of the work for this
-operation.
+The :ref:`filters.outlier` PDAL filter does most of the work for this operation.
 
 ::
 
     {
-        "type": "filters.statisticaloutlier",
-        "extract":"true",
-        "multiplier":3,
+        "type": "filters.outlier",
+        "method": "statistical",
+        "extract": "true",
+        "multiplier": 3,
         "mean_k": 8
     },
 
@@ -75,10 +75,10 @@ operation.
 3. :ref:`filters.range`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Even with the :ref:`filters.statisticaloutlier` operation, there is still a
-cluster of points with extremely negative ``Z`` values. These are some artifact
-or miscomputation of processing, and we don't want these points. We are going
-to use ::ref:`filters.range` to keep only points that are within the range
+Even with the :ref:`filters.outlier` operation, there is still a cluster of
+points with extremely negative ``Z`` values. These are some artifact or
+miscomputation of processing, and we don't want these points. We are going to
+use ::ref:`filters.range` to keep only points that are within the range
 ``-100 <= Z <= 3000``.
 
 ::
@@ -143,7 +143,6 @@ Notes
 
 1. Control the aggressiveness of the algorithm with the ``mean_k`` parameter.
 
-2. :ref:`filters.statisticaloutlier` requires the entire set in memory to
+2. :ref:`filters.outlier` requires the entire set in memory to
    process. If you have really large files, you are going to need to
    :ref:`split <filters.splitter>` them in some way.
-
diff --git a/doc/workshop/exercises/analysis/ground/filter.json b/doc/workshop/exercises/analysis/ground/filter.json
deleted file mode 100644
index cc9daec..0000000
--- a/doc/workshop/exercises/analysis/ground/filter.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "pipeline": {
-    "name": "Progressive Morphological Filter with Outlier Removal",
-    "version": 1.0,
-    "filters": [{
-        "name": "StatisticalOutlierRemoval",
-        "setMeanK": 8,
-        "setStddevMulThresh": 3.0
-      }, {
-        "name": "ProgressiveMorphologicalFilter",
-        "setCellSize": 1.5
-    }]
-  }
-}
diff --git a/doc/workshop/exercises/analysis/ground/ground-run-ground-only.txt b/doc/workshop/exercises/analysis/ground/ground-run-ground-only.txt
index 16f80c5..e7f6e1b 100644
--- a/doc/workshop/exercises/analysis/ground/ground-run-ground-only.txt
+++ b/doc/workshop/exercises/analysis/ground/ground-run-ground-only.txt
@@ -2,6 +2,5 @@ docker run -v /c/Users/Howard/PDAL:/data -t pdal/pdal \
        pdal ground \
        /data/exercises/analysis/ground/CSite1_orig-utm.laz \
        -o /data/exercises/analysis/ground/ground-only.laz \
-       --filters.ground.classify=true \
-       --filters.ground.extract=true \
-       --writers.las.compression=true -v 4
+       --classify=true --extract=true \
+       --writers.las.compression=true --verbose 4
diff --git a/doc/workshop/exercises/analysis/ground/ground-run-no-filter.txt b/doc/workshop/exercises/analysis/ground/ground-run-no-filter.txt
index afb1e64..1a5fce3 100644
--- a/doc/workshop/exercises/analysis/ground/ground-run-no-filter.txt
+++ b/doc/workshop/exercises/analysis/ground/ground-run-no-filter.txt
@@ -2,5 +2,5 @@ docker run -v /c/Users/Howard/PDAL:/data -t pdal/pdal \
        pdal ground \
        /data/exercises/analysis/ground/CSite1_orig-utm.laz \
        -o /data/exercises/analysis/ground/ground.laz \
-       --filters.ground.classify=true \
+       --classify=true \
        --writers.las.compression=true -v 4
diff --git a/doc/workshop/exercises/analysis/ground/ground-run-pcl-filter.txt b/doc/workshop/exercises/analysis/ground/ground-run-pcl-filter.txt
deleted file mode 100644
index b27fc2d..0000000
--- a/doc/workshop/exercises/analysis/ground/ground-run-pcl-filter.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-docker run -v /c/Users/Howard/PDAL:/data -t pdal/pdal \
-   pdal pcl \
-   /data/exercises/analysis/ground/CSite1_orig-utm.laz \
-   -o /data/exercises/analysis/ground/ground-filtered.laz \
-   -p /data/exercises/analysis/ground/filter.json
diff --git a/doc/workshop/exercises/analysis/ground/ground.rst b/doc/workshop/exercises/analysis/ground/ground.rst
index 700f946..e18954e 100644
--- a/doc/workshop/exercises/analysis/ground/ground.rst
+++ b/doc/workshop/exercises/analysis/ground/ground.rst
@@ -26,11 +26,10 @@ generate a ground surface.
 
 .. seealso::
 
-    PMF is implemented in PCL. PCL is then linked to PDAL. You can read more
-    about the specifics of the algorithm from the `paper
+    You can read more about the specifics of the PMF algorithm from the `paper
     <http://users.cis.fiu.edu/~chens/PDF/TGRS.pdf>`__, and you can read more
-    about the PCL implementation in the source code on `github
-    <https://github.com/PointCloudLibrary/pcl/blob/master/filters/include/pcl/filters/morphological_filter.h>`__.
+    about the PDAL implementation in the source code on `github
+    <https://github.com/PDAL/PDAL/blob/master/filters/PMFFilter.cpp>`__.
 
 .. _`Digital Terrain Model`: https://en.wikipedia.org/wiki/Digital_elevation_model
 
@@ -61,9 +60,8 @@ Filtering
 
 We do not yet have a satisfactory surface for generating a DTM.  When we
 visualize the output of this ground operation, we notice there's still some
-noise. PCL also has its own :ref:`pipeline` concept, and we can stack the
-call to PMF with a call to a the `filters.statisticaloutlier` technique we
-learned about in :ref:`denoising`.
+noise. We can stack the call to PMF with a call to a the `filters.outlier`
+technique we learned about in :ref:`denoising`.
 
 1. Let us start by removing the non-ground data:
 
@@ -73,40 +71,21 @@ learned about in :ref:`denoising`.
 
 .. note::
 
-    The ``filters.ground.extract=true`` item causes all data except
+    The ``filters.pmf.extract=true`` item causes all data except
     ground-classified points to be removed from the set.
 
 Buildings and other non-ground points are removed with the ``extract`` option
-of :ref:`filters.ground`
+of :ref:`filters.pmf`
 
 .. image:: ../../../images/ground-ground-only-view.png
 
 
-2. Now we will remove the noise. PDAL has the :ref:`pcl_command` to allow you
-   to pass |PCL| pipelines for processing. We will use this to combine
-   the PMF and StatisticalOutlierRemoval filters into a single operation.
+2. Now we will remove the noise, using the :ref:`translate_command` to stack the
+:ref:`filters.outlier` and :ref:`filters.pmf` stages:
 
-.. literalinclude:: ./filter.json
-    :linenos:
-
-.. note::
-
-    This pipeline is available in your workshop materials in the
-        ``./exercises/analysis/ground/filter.json`` file.
-
-
-.. literalinclude:: ./ground-run-pcl-filter.txt
-    :linenos:
-
-The :ref:`pcl_command` allows you to use :ref:`pcl_json_specification` operations in
-succession over data.
+.. literalinclude:: ./translate-run-ground-only.txt
+   :linenos:
+   
+The result is a more accurate representation of the ground returns.
 
 .. image:: ../../../images/ground-filtered.png
-
-.. note::
-
-    This pipeline is available in your workshop materials in the
-    ``./exercises/analysis/ground/filter.json`` file.
-
-
-
diff --git a/doc/workshop/exercises/analysis/ground/translate-run-ground-only.txt b/doc/workshop/exercises/analysis/ground/translate-run-ground-only.txt
new file mode 100644
index 0000000..2602e43
--- /dev/null
+++ b/doc/workshop/exercises/analysis/ground/translate-run-ground-only.txt
@@ -0,0 +1,11 @@
+docker run -v /c/Users/Howard/PDAL:/data -t pdal/pdal \
+       pdal translate \
+       /data/exercises/analysis/ground/CSite1_orig-utm.laz \
+       -o /data/exercises/analysis/ground/denoised-ground-only.laz \
+       outlier pmf \
+       --filters.outlier.method="statistical" \
+       --filters.outlier.mean_k=8 \
+       --filters.outlier.multiplier=3.0 \
+       --filters.pmf.cell_size=1.5 \
+       --filters.pmf.extract=true \
+       --writers.las.compression=true --verbose 4
diff --git a/doc/workshop/exercises/analysis/thinning/thinning-run-dartsample.txt b/doc/workshop/exercises/analysis/thinning/thinning-run-dartsample.txt
index 64043f8..bf74173 100644
--- a/doc/workshop/exercises/analysis/thinning/thinning-run-dartsample.txt
+++ b/doc/workshop/exercises/analysis/thinning/thinning-run-dartsample.txt
@@ -2,5 +2,5 @@ docker run -v /c/Users/Howard/PDAL:/data -t pdal/pdal \
        pdal translate \
        /data/exercises/analysis/density/uncompahgre.laz \
        /data/exercises/analysis/thinning/uncompahgre-thin.laz \
-       dartsample \
-       --filters.dartsample.radius=20
+       sample \
+       --filters.sample.radius=20
diff --git a/doc/workshop/exercises/analysis/thinning/thinning.rst b/doc/workshop/exercises/analysis/thinning/thinning.rst
index aa75d08..85a0efb 100644
--- a/doc/workshop/exercises/analysis/thinning/thinning.rst
+++ b/doc/workshop/exercises/analysis/thinning/thinning.rst
@@ -27,7 +27,7 @@ sampling strategies we could choose. We can attempt to preserve shape, we can tr
 randomly sample, and we can attempt to normalize posting density. PDAL
 provides capability for all three:
 
-* Poisson using the :ref:`filters.dartsample`
+* Poisson using the :ref:`filters.sample`
 
 * Random using a combination of :ref:`filters.decimation` and :ref:`filters.randomize`
 
@@ -76,5 +76,4 @@ Notes
 --------------------------------------------------------------------------------
 
 1. Poisson sampling is non-destructive. Points that are filtered with
-   :ref:`filters.dartsample` will retain all attribute information.
-
+   :ref:`filters.sample` will retain all attribute information.
diff --git a/doc/workshop/pdal-introduction.rst b/doc/workshop/pdal-introduction.rst
index b0f5fec..b3fded7 100644
--- a/doc/workshop/pdal-introduction.rst
+++ b/doc/workshop/pdal-introduction.rst
@@ -86,10 +86,8 @@ PCL
 
 .. index:: PCL
 
-`PCL`_ is a complementary, rather than substitute, open source software processing
-suite for point cloud data. PDAL itself uses PCL to provide sophisticated algorithmic
-exploitation, :ref:`statistical filtering <filters.statisticaloutlier>`, and
-:ref:`dynamic filtering <pcl_block_tutorial>`. The developer community of the PCL
+`PCL`_ is a complementary, rather than substitute, open source software
+processing suite for point cloud data. The developer community of the PCL
 library is focused on algorithm development, robotic and computer vision, and
 real-time laser scanner processing. PDAL links and uses PCL, and PDAL provides a
 convenient pipeline mechanism to orchestrate PCL operations.
diff --git a/doc/workshop/slides/source/denoising.rst b/doc/workshop/slides/source/denoising.rst
index 408132f..c6cdf25 100644
--- a/doc/workshop/slides/source/denoising.rst
+++ b/doc/workshop/slides/source/denoising.rst
@@ -14,7 +14,7 @@ Pipeline
 ================================================================================
 
 1. :ref:`readers.las`
-2. :ref:`filters.statisticaloutlier`
+2. :ref:`filters.outlier`
 3. :ref:`filters.range`
 4. :ref:`writers.las`
 
diff --git a/doc/workshop/slides/source/ground.rst b/doc/workshop/slides/source/ground.rst
index 0cbfcc1..2fb6415 100644
--- a/doc/workshop/slides/source/ground.rst
+++ b/doc/workshop/slides/source/ground.rst
@@ -44,19 +44,12 @@ Ground (ground only)
     :linenos:
     :emphasize-lines: 6
 
-Ground (PCL pipeline)
+Ground (denoised first)
 ================================================================================
 
-.. literalinclude:: ../../exercises/analysis/ground/filter.json
+.. literalinclude:: ../../exercises/analysis/ground/translate-run-ground-only.txt
     :linenos:
 
-Ground (PCL pipeline)
-================================================================================
-
-.. literalinclude:: ../../exercises/analysis/ground/ground-run-pcl-filter.txt
-    :linenos:
-    :emphasize-lines: 5
-
 Ground (view)
 ================================================================================
 
diff --git a/examples/writing-filter/CMakeLists.txt b/examples/writing-filter/CMakeLists.txt
index 587a352..62e8fc7 100644
--- a/examples/writing-filter/CMakeLists.txt
+++ b/examples/writing-filter/CMakeLists.txt
@@ -2,10 +2,10 @@ cmake_minimum_required(VERSION 2.8.12)
 project(FilterTutorial)
 
 find_package(PDAL 1.0.0 REQUIRED CONFIG)
-include_directories(${PDAL_INCLUDE_DIRS})
-
-include_directories(/Users/chambbj/loki/pdal/repo/filters)
 
 set(CMAKE_CXX_FLAGS "-std=c++11")
 add_library(pdal_plugin_filter_myfilter SHARED MyFilter.cpp)
-target_link_libraries(pdal_plugin_filter_myfilter ${PDAL_LIBRARIES})
+target_link_libraries(pdal_plugin_filter_myfilter PRIVATE ${PDAL_LIBRARIES})
+target_include_directories(pdal_plugin_filter_myfilter PRIVATE
+    ${PDAL_INCLUDE_DIRS}
+    /Users/chambbj/loki/pdal/repo/filters)
diff --git a/examples/writing-kernel/CMakeLists.txt b/examples/writing-kernel/CMakeLists.txt
index 635c01a..e42fddb 100644
--- a/examples/writing-kernel/CMakeLists.txt
+++ b/examples/writing-kernel/CMakeLists.txt
@@ -2,10 +2,10 @@ cmake_minimum_required(VERSION 2.8.12)
 project(KernelTutorial)
 
 find_package(PDAL 1.0.0 REQUIRED CONFIG)
-include_directories(${PDAL_INCLUDE_DIRS})
-
-include_directories(/Users/chambbj/loki/pdal/repo/kernels)
 
 set(CMAKE_CXX_FLAGS "-std=c++11")
 add_library(pdal_plugin_kernel_mykernel SHARED MyKernel.cpp)
-target_link_libraries(pdal_plugin_kernel_mykernel ${PDAL_LIBRARIES})
+target_link_libraries(pdal_plugin_kernel_mykernel PRIVATE ${PDAL_LIBRARIES})
+target_include_directories(pdal_plugin_kernel_mykernel PRIVATE
+    ${PDAL_INCLUDE_DIRS}
+    /Users/chambbj/loki/pdal/repo/kernels)
diff --git a/examples/writing-reader/CMakeLists.txt b/examples/writing-reader/CMakeLists.txt
index 6176580..1a9f46b 100644
--- a/examples/writing-reader/CMakeLists.txt
+++ b/examples/writing-reader/CMakeLists.txt
@@ -2,10 +2,10 @@ cmake_minimum_required(VERSION 2.8.12)
 project(ReaderTutorial)
 
 find_package(PDAL 1.0.0 REQUIRED CONFIG)
-include_directories(${PDAL_INCLUDE_DIRS})
-
-include_directories(/Users/chambbj/loki/pdal/repo/readers)
 
 set(CMAKE_CXX_FLAGS "-std=c++11")
 add_library(pdal_plugin_reader_myreader SHARED MyReader.cpp)
-target_link_libraries(pdal_plugin_reader_myreader ${PDAL_LIBRARIES})
+target_link_libraries(pdal_plugin_reader_myreader PRIVATE ${PDAL_LIBRARIES})
+target_include_directories(pdal_plugin_reader_myreader PRIVATE
+    ${PDAL_INCLUDE_DIRS}
+    /Users/chambbj/loki/pdal/repo/readers)
diff --git a/examples/writing-reader/MyReader.cpp b/examples/writing-reader/MyReader.cpp
index 39beca4..b662ef5 100644
--- a/examples/writing-reader/MyReader.cpp
+++ b/examples/writing-reader/MyReader.cpp
@@ -88,7 +88,8 @@ namespace pdal
       if (s.size() != 4)
       {
         std::stringstream oss;
-        oss << "Unable to split proper number of fields.  Expected 4, got " << s.size();
+        oss << "Unable to split proper number of fields.  Expected 4, got "
+            << s.size();
         throw pdal_error(oss.str());
       }
 
@@ -103,7 +104,9 @@ namespace pdal
       view->setField(Dimension::Id::Z, nextId, z);
 
       name = "MyData";
-      view->setField(layout->findProprietaryDim(name), nextId, convert<unsigned int>(s, name, 3));
+      view->setField(layout->findProprietaryDim(name),
+                     nextId,
+                     convert<unsigned int>(s, name, 3));
 
       nextId++;
       if (m_cb)
diff --git a/examples/writing-writer/CMakeLists.txt b/examples/writing-writer/CMakeLists.txt
index 9b4ef31..243bff0 100644
--- a/examples/writing-writer/CMakeLists.txt
+++ b/examples/writing-writer/CMakeLists.txt
@@ -2,8 +2,9 @@ cmake_minimum_required(VERSION 2.8.12)
 project(WriterTutorial)
 
 find_package(PDAL 1.0.0 REQUIRED CONFIG)
-include_directories(${PDAL_INCLUDE_DIRS})
 
 set(CMAKE_CXX_FLAGS "-std=c++11")
 add_library(pdal_plugin_writer_mywriter SHARED MyWriter.cpp)
-target_link_libraries(pdal_plugin_writer_mywriter ${PDAL_LIBRARIES})
+target_link_libraries(pdal_plugin_writer_mywriter PRIVATE ${PDAL_LIBRARIES})
+target_include_directories(pdal_plugin_writer_mywriter PRIVATE
+    ${PDAL_INCLUDE_DIRS})
diff --git a/examples/writing-writer/MyWriter.cpp b/examples/writing-writer/MyWriter.cpp
index ca83b6a..dc97d0b 100644
--- a/examples/writing-writer/MyWriter.cpp
+++ b/examples/writing-writer/MyWriter.cpp
@@ -2,6 +2,7 @@
 
 #include "MyWriter.hpp"
 #include <pdal/pdal_macros.hpp>
+#include <pdal/util/FileUtils.hpp>
 #include <pdal/util/ProgramArgs.hpp>
 
 namespace pdal
diff --git a/examples/writing/CMakeLists.txt b/examples/writing/CMakeLists.txt
index 97d09e8..96319f9 100644
--- a/examples/writing/CMakeLists.txt
+++ b/examples/writing/CMakeLists.txt
@@ -2,8 +2,10 @@ cmake_minimum_required(VERSION 2.8.12)
 project(WritingTutorial)
 
 find_package(PDAL 1.0.0 REQUIRED CONFIG)
-include_directories(${PDAL_INCLUDE_DIRS})
 
 set(CMAKE_CXX_FLAGS "-std=c++11")
 add_executable(tutorial tutorial.cpp)
-target_link_libraries(tutorial ${PDAL_LIBRARIES})
+target_link_libraries(tutorial PRIVATE ${PDAL_LIBRARIES})
+target_include_directories(tutorial PRIVATE
+    ${PDAL_INCLUDE_DIRS}
+    ${PDAL_INCLUDE_DIRS}/pdal)
diff --git a/examples/writing/tutorial.cpp b/examples/writing/tutorial.cpp
index 5a9ea62..2b9dd7b 100644
--- a/examples/writing/tutorial.cpp
+++ b/examples/writing/tutorial.cpp
@@ -1,10 +1,11 @@
 #include <pdal/PointView.hpp>
-#include <pdal/BufferReader.hpp>
 #include <pdal/PointTable.hpp>
 #include <pdal/Dimension.hpp>
 #include <pdal/Options.hpp>
 #include <pdal/StageFactory.hpp>
 
+#include <io/BufferReader.hpp>
+
 #include <vector>
 
 void fillView(pdal::PointViewPtr view)
diff --git a/filters/ApproximateCoplanarFilter.cpp b/filters/ApproximateCoplanarFilter.cpp
new file mode 100644
index 0000000..600cbb9
--- /dev/null
+++ b/filters/ApproximateCoplanarFilter.cpp
@@ -0,0 +1,104 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "ApproximateCoplanarFilter.hpp"
+
+#include <pdal/EigenUtils.hpp>
+#include <pdal/KDIndex.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <Eigen/Dense>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.approximatecoplanar", "ApproximateCoplanar Filter", 
+               "http://pdal.io/stages/filters.approximatecoplanar.html");
+
+CREATE_STATIC_PLUGIN(1, 0, ApproximateCoplanarFilter, Filter, s_info)
+
+std::string ApproximateCoplanarFilter::getName() const
+{
+    return s_info.name;
+}
+
+
+void ApproximateCoplanarFilter::addArgs(ProgramArgs& args)
+{
+    args.add("knn", "k-Nearest Neighbors", m_knn, 8);
+    args.add("thresh1", "Threshold 1", m_thresh1, 25.0);
+    args.add("thresh2", "Threshold 2", m_thresh2, 6.0);
+}
+
+
+void ApproximateCoplanarFilter::addDimensions(PointLayoutPtr layout)
+{
+    m_coplanar = layout->registerOrAssignDim("Coplanar", Dimension::Type::Unsigned8);
+}
+
+void ApproximateCoplanarFilter::filter(PointView& view)
+{
+    using namespace Eigen;
+
+    KD3Index kdi(view);
+    kdi.build();
+
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        // find the k-nearest neighbors
+        auto ids = kdi.neighbors(i, m_knn);
+
+        // compute covariance of the neighborhood
+        auto B = eigen::computeCovariance(view, ids);
+
+        // perform the eigen decomposition
+        SelfAdjointEigenSolver<Matrix3f> solver(B);
+        if (solver.info() != Success)
+            throw pdal_error("Cannot perform eigen decomposition.");
+        auto ev = solver.eigenvalues();
+        
+        // test eigenvalues to label points that are approximately coplanar
+        if ((ev[1] > m_thresh1 * ev[0]) && (m_thresh2 * ev[1] > ev[2]))
+            view.setField(m_coplanar, i, 1u);
+        else
+            view.setField(m_coplanar, i, 0u);
+    }
+}
+
+} // namespace pdal
diff --git a/filters/approximatecoplanar/ApproximateCoplanarFilter.hpp b/filters/ApproximateCoplanarFilter.hpp
similarity index 100%
rename from filters/approximatecoplanar/ApproximateCoplanarFilter.hpp
rename to filters/ApproximateCoplanarFilter.hpp
diff --git a/filters/AttributeFilter.cpp b/filters/AttributeFilter.cpp
new file mode 100644
index 0000000..9268a5c
--- /dev/null
+++ b/filters/AttributeFilter.cpp
@@ -0,0 +1,238 @@
+/******************************************************************************
+* Copyright (c) 2014, Howard Butler, howard at hobu.co
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "AttributeFilter.hpp"
+
+#include <memory>
+#include <vector>
+
+#include <pdal/GDALUtils.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/Polygon.hpp>
+#include <pdal/QuadIndex.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "filters.attribute",
+    "Assign values for a dimension using a specified value, \n" \
+        "an OGR-readable data source, or an OGR SQL query.",
+    "http://pdal.io/stages/filters.attribute.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, AttributeFilter, Filter, s_info)
+
+struct OGRDataSourceDeleter
+{
+    template <typename T>
+    void operator()(T* ptr)
+    {
+        if (ptr)
+            ::OGR_DS_Destroy(ptr);
+    }
+};
+
+struct OGRFeatureDeleter
+{
+    template <typename T>
+    void operator()(T* ptr)
+    {
+        if (ptr)
+            ::OGR_F_Destroy(ptr);
+    }
+};
+
+
+void AttributeFilter::addArgs(ProgramArgs& args)
+{
+    args.add("dimension", "Dimension on which to filter", m_dimName).
+        setPositional();
+    m_valArg = &args.add("value", "Value to set on matching points", m_value,
+        std::numeric_limits<double>::quiet_NaN());
+    m_dsArg = &args.add("datasource", "OGR-readable datasource for Polygon or "
+        "Multipolygon data", m_datasource);
+    m_colArg = &args.add("column", "OGR datasource column from which to "
+        "read the attribute.", m_column);
+    m_queryArg = &args.add("query", "OGR SQL query to execute on the "
+        "datasource to fetch geometry and attributes", m_query);
+    m_layerArg = &args.add("layer", "Datasource layer to use", m_layer);
+}
+
+
+void AttributeFilter::initialize()
+{
+    if (m_valArg->set() && m_dsArg->set())
+    {
+        std::ostringstream oss;
+        oss << getName() << ": options 'value' and 'datasource' mutually "
+            "exclusive.";
+        throw pdal_error(oss.str());
+    }
+
+    if (!m_valArg->set() && !m_dsArg->set())
+    {
+        std::ostringstream oss;
+        oss << getName() << ": Either option 'value' or 'datasource' must "
+            "be specified.";
+        throw pdal_error(oss.str());
+    }
+
+    Arg *args[] = { m_colArg, m_queryArg, m_layerArg };
+    for (auto& a : args)
+    {
+        if (m_valArg->set() && a->set())
+        {
+            std::ostringstream oss;
+            oss << getName() << ": option '" << a->longname() << "' invalid "
+                "with option 'value'.";
+            throw pdal_error(oss.str());
+        }
+    }
+    gdal::registerDrivers();
+}
+
+
+void AttributeFilter::prepared(PointTableRef table)
+{
+    m_dim = table.layout()->findDim(m_dimName);
+    if (m_dim == Dimension::Id::Unknown)
+    {
+        std::ostringstream oss;
+        oss << getName() << ": Dimension '" << m_dimName << "' not found.";
+        throw pdal_error(oss.str());
+    }
+}
+
+
+void AttributeFilter::ready(PointTableRef table)
+{
+    if (m_value != m_value)
+    {
+        m_ds = OGRDSPtr(OGROpen(m_datasource.c_str(), 0, 0),
+            OGRDataSourceDeleter());
+        if (!m_ds)
+        {
+            std::ostringstream oss;
+            oss << getName() << ": Unable to open data source '" <<
+                    m_datasource << "'";
+            throw pdal_error(oss.str());
+        }
+    }
+}
+
+
+void AttributeFilter::UpdateGEOSBuffer(PointView& view)
+{
+    QuadIndex idx(view);
+
+    if (m_layer.size())
+        m_lyr = OGR_DS_GetLayerByName(m_ds.get(), m_layer.c_str());
+    else if (m_query.size())
+        m_lyr = OGR_DS_ExecuteSQL(m_ds.get(), m_query.c_str(), 0, 0);
+    else
+        m_lyr = OGR_DS_GetLayer(m_ds.get(), 0);
+
+    if (!m_lyr)
+    {
+        std::ostringstream oss;
+        oss << getName() << ": Unable to select layer '" << m_layer << "'";
+        throw pdal_error(oss.str());
+    }
+
+    OGRFeaturePtr feature = OGRFeaturePtr(OGR_L_GetNextFeature(m_lyr),
+        OGRFeatureDeleter());
+
+    int field_index(1); // default to first column if nothing was set
+    if (m_column.size())
+    {
+        field_index = OGR_F_GetFieldIndex(feature.get(), m_column.c_str());
+        if (field_index == -1)
+        {
+            std::ostringstream oss;
+            oss << getName() << ": No column name '" << m_column <<
+                "' was found.";
+            throw pdal_error(oss.str());
+        }
+    }
+
+    while (feature)
+    {
+        OGRGeometryH geom = OGR_F_GetGeometryRef(feature.get());
+        OGRwkbGeometryType t = OGR_G_GetGeometryType(geom);
+        int32_t fieldVal = OGR_F_GetFieldAsInteger(feature.get(), field_index);
+
+        if (!(t == wkbPolygon ||
+            t == wkbMultiPolygon ||
+            t == wkbPolygon25D ||
+            t == wkbMultiPolygon25D))
+        {
+            std::ostringstream oss;
+            oss << getName() << ": Geometry is not Polygon or MultiPolygon!";
+            throw pdal::pdal_error(oss.str());
+        }
+
+        pdal::Polygon p(geom, view.spatialReference());
+
+        // Compute a total bounds for the geometry. Query the QuadTree to
+        // find out the points that are inside the bbox. Then test each
+        // point in the bbox against the prepared geometry.
+        BOX3D box = p.bounds();
+        std::vector<PointId> ids = idx.getPoints(box);
+
+
+        for (const auto& i : ids)
+        {
+            PointRef ref(view, i);
+            if (p.covers(ref))
+                view.setField(m_dim, i, fieldVal);
+        }
+        feature = OGRFeaturePtr(OGR_L_GetNextFeature(m_lyr),
+            OGRFeatureDeleter());
+    }
+}
+
+
+void AttributeFilter::filter(PointView& view)
+{
+    if (m_value == m_value)
+        for (PointId i = 0; i < view.size(); ++i)
+            view.setField(m_dim, i, m_value);
+    else
+        UpdateGEOSBuffer(view);
+}
+
+} // namespace pdal
+
diff --git a/filters/attribute/AttributeFilter.hpp b/filters/AttributeFilter.hpp
similarity index 100%
rename from filters/attribute/AttributeFilter.hpp
rename to filters/AttributeFilter.hpp
diff --git a/filters/CMakeLists.txt b/filters/CMakeLists.txt
deleted file mode 100644
index 78d0b25..0000000
--- a/filters/CMakeLists.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-add_subdirectory(approximatecoplanar)
-add_subdirectory(attribute)
-add_subdirectory(chipper)
-add_subdirectory(colorization)
-add_subdirectory(crop)
-add_subdirectory(decimation)
-add_subdirectory(divider)
-add_subdirectory(eigenvalues)
-add_subdirectory(estimaterank)
-add_subdirectory(ferry)
-add_subdirectory(hag)
-add_subdirectory(merge)
-add_subdirectory(mongus)
-add_subdirectory(mortonorder)
-add_subdirectory(normal)
-add_subdirectory(outlier)
-add_subdirectory(pmf)
-add_subdirectory(randomize)
-add_subdirectory(range)
-add_subdirectory(reprojection)
-add_subdirectory(sample)
-add_subdirectory(smrf)
-add_subdirectory(sort)
-add_subdirectory(splitter)
-add_subdirectory(stats)
-add_subdirectory(streamcallback)
-add_subdirectory(transformation)
-
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} PARENT_SCOPE)
diff --git a/filters/ChipperFilter.cpp b/filters/ChipperFilter.cpp
new file mode 100644
index 0000000..ab67176
--- /dev/null
+++ b/filters/ChipperFilter.cpp
@@ -0,0 +1,338 @@
+/******************************************************************************
+ * Copyright (c) 2010, Andrew Bell
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of the Andrew Bell or libLAS nor the names of
+ *       its contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#include "ChipperFilter.hpp"
+
+#include <iostream>
+#include <limits>
+
+/**
+The objective is to split the region into non-overlapping blocks, each
+containing approximately the same number of points, as specified by the
+user.  We'd also like the blocks closer to square than not.
+
+First, the points are read into arrays - one for the x direction, and one for
+the y direction.  The arrays are sorted and are initialized with indices into
+the other array of the location of the other coordinate of the same point.
+
+Partitions are created that place the maximum number of points in a
+block, subject to the user-defined threshold, using a cumulate and round
+procedure.
+
+The distance of the point-space is checked in each direction and the
+wider dimension is chosen for splitting at an appropriate partition point.
+The points in the narrower direction are copied to locations in the spare
+array at one side or the other of the chosen partition, and that portion
+of the spare array then becomes the active array for the narrow direction.
+This avoids resorting of the arrays, which are already sorted.
+
+This procedure is then recursively applied to the created blocks until
+they contains only one or two partitions.  In the case of one partition,
+we are done, and we simply store away the contents of the block.  If there are
+two partitions in a block, we avoid the recopying the narrow array to the
+spare since the wide array already contains the desired points partitioned
+into two blocks.  We simply need to locate the maximum and minimum values
+from the narrow array so that the approriate extrema of the block can
+be stored.
+**/
+
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "filters.chipper",
+    "Organize points into spatially contiguous, squarish, and non-overlapping chips.",
+    "http://pdal.io/stages/filters.chipper.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, ChipperFilter, Filter, s_info)
+
+std::string ChipperFilter::getName() const { return s_info.name; }
+
+void ChipperFilter::addArgs(ProgramArgs& args)
+{
+    args.add("capacity", "Maximum number of points per cell", m_threshold,
+        (PointId) 5000u);
+}
+
+
+PointViewSet ChipperFilter::run(PointViewPtr view)
+{
+    if (view->size() == 0)
+        return m_outViews;
+
+    m_inView = view;
+    load(*view.get(), m_xvec, m_yvec, m_spare);
+    partition(m_xvec.size());
+    decideSplit(m_xvec, m_yvec, m_spare, 0, m_partitions.size() - 1);
+    return m_outViews;
+}
+
+
+void ChipperFilter::load(PointView& view, ChipRefList& xvec, ChipRefList& yvec,
+    ChipRefList& spare)
+{
+    point_count_t idx;
+    std::vector<ChipPtRef>::iterator it;
+
+    xvec.reserve(view.size());
+    yvec.reserve(view.size());
+    spare.resize(view.size());
+
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        ChipPtRef xref;
+
+        xref.m_pos = view.getFieldAs<double>(Dimension::Id::X, i);
+        xref.m_ptindex = i;
+        xvec.push_back(xref);
+
+        ChipPtRef yref;
+
+        yref.m_pos = view.getFieldAs<double>(Dimension::Id::Y, i);
+        yref.m_ptindex = i;
+        yvec.push_back(yref);
+    }
+
+    // Sort xvec and assign other index in yvec to sorted indices in xvec.
+    std::stable_sort(xvec.begin(), xvec.end());
+    for (size_t i = 0; i < xvec.size(); ++i)
+    {
+        idx = xvec[i].m_ptindex;
+        yvec[idx].m_oindex = i;
+    }
+
+    // Sort yvec.
+    std::stable_sort(yvec.begin(), yvec.end());
+
+    // Iterate through the yvector, setting the xvector appropriately.
+    for (size_t i = 0; i < yvec.size(); ++i)
+        xvec[yvec[i].m_oindex].m_oindex = i;
+}
+
+
+
+
+#ifdef _WIN32
+inline long lround(double d)
+{
+	long l;
+
+	if (d < 0)
+		l = (long)ceil(d - .5);
+	else
+		l = (long)floor(d + .5);
+	return l;
+}
+#endif
+
+
+// Build a list of partitions.  The partition is the size of each block in
+// the x and y directions in number of points.
+void ChipperFilter::partition(point_count_t size)
+{
+    size_t num_partitions;
+
+    num_partitions = size / m_threshold;
+    if (size % m_threshold)
+        num_partitions++;
+
+    // This is a standard statistics cumulate and round.  It distributes
+    // the points into partitions such the "extra" points are reasonably
+    // distributed among the partitions.
+    double total(0.0);
+    double partition_size = static_cast<double>(size) / num_partitions;
+    m_partitions.push_back(0);
+    for (size_t i = 0; i < num_partitions; ++i)
+    {
+        total += partition_size;
+        size_t itotal = lround(total);
+        m_partitions.push_back(itotal);
+    }
+}
+
+
+void ChipperFilter::decideSplit(ChipRefList& v1, ChipRefList& v2, ChipRefList& spare,
+    PointId pleft, PointId pright)
+{
+    double v1range;
+    double v2range;
+    uint32_t left = m_partitions[pleft];
+    uint32_t right = m_partitions[pright] - 1;
+
+    // Decide the wider direction of the block, and split in that direction
+    // to maintain squareness.
+    v1range = v1[right].m_pos - v1[left].m_pos;
+    v2range = v2[right].m_pos - v2[left].m_pos;
+    if (v1range > v2range)
+        split(v1, v2, spare, pleft, pright);
+    else
+        split(v2, v1, spare, pleft, pright);
+}
+
+void ChipperFilter::split(ChipRefList& wide, ChipRefList& narrow, ChipRefList& spare,
+    PointId pleft, PointId pright)
+{
+    PointId lstart;
+    PointId rstart;
+    PointId pcenter;
+    PointId left;
+    PointId right;
+    PointId center;
+
+    left = m_partitions[pleft];
+    right = m_partitions[pright] - 1;
+
+    // There are two cases in which we are done.
+    // 1) We have a distance of two between left and right.
+    // 2) We have a distance of three between left and right.
+
+    if (pright - pleft == 1)
+        emit(wide, left, right);
+    else if (pright - pleft == 2)
+        finalSplit(wide, narrow, pleft, pright);
+    else
+    {
+        pcenter = (pleft + pright) / 2;
+        center = m_partitions[pcenter];
+
+        // We are splitting in the wide direction - split elements in the
+        // narrow array by copying them to the spare array in the correct
+        // partition.  The spare array then becomes the active narrow array
+        // for the [left,right] partition.
+        lstart = left;
+        rstart = center;
+        for (PointId i = left; i <= right; ++i)
+        {
+            if (narrow[i].m_oindex < center)
+            {
+                spare[lstart] = narrow[i];
+                wide[narrow[i].m_oindex].m_oindex = lstart;
+                lstart++;
+            }
+            else
+            {
+                spare[rstart] = narrow[i];
+                wide[narrow[i].m_oindex].m_oindex = rstart;
+                rstart++;
+            }
+        }
+
+        // Save away the direction so we know which array is X and which is Y
+        // so that when we emit, we can properly label the max/min points.
+        Direction dir = narrow.m_dir;
+        spare.m_dir = dir;
+        decideSplit(wide, spare, narrow, pleft, pcenter);
+        decideSplit(wide, spare, narrow, pcenter, pright);
+        narrow.m_dir = dir;
+    }
+}
+
+// In this case the wide array is like we want it.  The narrow array is
+// ordered, but not for our split, so we have to find the max/min entries
+// for each partition in the final split.
+void ChipperFilter::finalSplit(ChipRefList& wide, ChipRefList& narrow,
+    PointId pleft, PointId pright)
+{
+
+    int64_t left1 = -1;
+    int64_t left2 = -1;
+    int64_t right1 = -1;
+    int64_t right2 = -1;
+
+    // It appears we're using int64_t here because we're using -1 as
+    // an indicator.  I'm not 100% sure that i ends up <0, but I don't
+    // think so.  These casts will at least shut up the compiler, but
+    // I think this code should be revisited to use std::vector<uint32_t>::const_iterator
+    // or std::vector<uint32_t>::size_type instead of this int64_t stuff -- hobu 11/15/10
+    int64_t left = m_partitions[pleft];
+    int64_t right = static_cast<int64_t>(m_partitions[pright] - 1);
+    int64_t center = static_cast<int64_t>(m_partitions[pright - 1]);
+
+    // Find left values for the partitions.
+    for (int64_t i = left; i <= right; ++i)
+    {
+        int64_t idx = static_cast<int64_t>(narrow[static_cast<uint32_t>(i)].m_oindex);
+        if (left1 < 0 && (idx < center))
+        {
+            left1 = i;
+            if (left2 >= 0)
+                break;
+        }
+        else if (left2 < 0 && (idx >= center))
+        {
+            left2 = i;
+            if (left1 >= 0)
+                break;
+        }
+    }
+    // Find right values for the partitions.
+    for (int64_t i = right; i >= left; --i)
+    {
+        int64_t idx = static_cast<int64_t>(narrow[static_cast<uint32_t>(i)].m_oindex);
+        if (right1 < 0 && (idx < center))
+        {
+            right1 = i;
+            if (right2 >= 0)
+                break;
+        }
+        else if (right2 < 0 && (idx >= center))
+        {
+            right2 = i;
+            if (right1 >= 0)
+                break;
+        }
+    }
+
+    // Emit results.
+    emit(wide,
+         left,
+         center - 1);
+    emit(wide,
+         center,
+         right);
+}
+
+void ChipperFilter::emit(ChipRefList& wide, PointId widemin, PointId widemax)
+{
+    PointViewPtr view = m_inView->makeNew();
+    for (size_t idx = widemin; idx <= widemax; ++idx)
+        view->appendPoint(*m_inView.get(), wide[idx].m_ptindex);
+
+    m_outViews.insert(view);
+}
+
+} // namespace pdal
+
diff --git a/filters/chipper/ChipperFilter.hpp b/filters/ChipperFilter.hpp
similarity index 100%
rename from filters/chipper/ChipperFilter.hpp
rename to filters/ChipperFilter.hpp
diff --git a/filters/ColorInterpRamps.hpp b/filters/ColorInterpRamps.hpp
new file mode 100644
index 0000000..e09fc4c
--- /dev/null
+++ b/filters/ColorInterpRamps.hpp
@@ -0,0 +1,407 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler, hobu.inc at gmail.com
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+unsigned char awesome_green[] = {
+    137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,1,0,0,0,0,1,8,2,0,0,
+    0,190,59,231,32,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,
+    0,65,100,111,98,101,32,73,109,97,103,101,82,101,97,100,121,113,201,101,
+    60,0,0,3,35,105,84,88,116,88,77,76,58,99,111,109,46,97,100,111,98,101,
+    46,120,109,112,0,0,0,0,0,60,63,120,112,97,99,107,101,116,32,98,101,103,
+    105,110,61,34,239,187,191,34,32,105,100,61,34,87,53,77,48,77,112,67,101,
+    104,105,72,122,114,101,83,122,78,84,99,122,107,99,57,100,34,63,62,32,
+    60,120,58,120,109,112,109,101,116,97,32,120,109,108,110,115,58,120,61,
+    34,97,100,111,98,101,58,110,115,58,109,101,116,97,47,34,32,120,58,120,
+    109,112,116,107,61,34,65,100,111,98,101,32,88,77,80,32,67,111,114,101,
+    32,53,46,53,45,99,48,49,52,32,55,57,46,49,53,49,52,56,49,44,32,50,48,
+    49,51,47,48,51,47,49,51,45,49,50,58,48,57,58,49,53,32,32,32,32,32,32,
+    32,32,34,62,32,60,114,100,102,58,82,68,70,32,120,109,108,110,115,58,114,
+    100,102,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,
+    103,47,49,57,57,57,47,48,50,47,50,50,45,114,100,102,45,115,121,110,116,
+    97,120,45,110,115,35,34,62,32,60,114,100,102,58,68,101,115,99,114,105,
+    112,116,105,111,110,32,114,100,102,58,97,98,111,117,116,61,34,34,32,120,
+    109,108,110,115,58,120,109,112,61,34,104,116,116,112,58,47,47,110,115,
+    46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,48,47,34,32,
+    120,109,108,110,115,58,120,109,112,77,77,61,34,104,116,116,112,58,47,
+    47,110,115,46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,
+    48,47,109,109,47,34,32,120,109,108,110,115,58,115,116,82,101,102,61,34,
+    104,116,116,112,58,47,47,110,115,46,97,100,111,98,101,46,99,111,109,47,
+    120,97,112,47,49,46,48,47,115,84,121,112,101,47,82,101,115,111,117,114,
+    99,101,82,101,102,35,34,32,120,109,112,58,67,114,101,97,116,111,114,84,
+    111,111,108,61,34,65,100,111,98,101,32,80,104,111,116,111,115,104,111,
+    112,32,67,67,32,40,77,97,99,105,110,116,111,115,104,41,34,32,120,109,
+    112,77,77,58,73,110,115,116,97,110,99,101,73,68,61,34,120,109,112,46,
+    105,105,100,58,54,67,51,51,51,52,67,52,57,57,55,68,49,49,69,51,57,54,
+    66,50,66,68,56,52,57,52,66,49,65,52,55,57,34,32,120,109,112,77,77,58,
+    68,111,99,117,109,101,110,116,73,68,61,34,120,109,112,46,100,105,100,
+    58,54,67,51,51,51,52,67,53,57,57,55,68,49,49,69,51,57,54,66,50,66,68,
+    56,52,57,52,66,49,65,52,55,57,34,62,32,60,120,109,112,77,77,58,68,101,
+    114,105,118,101,100,70,114,111,109,32,115,116,82,101,102,58,105,110,115,
+    116,97,110,99,101,73,68,61,34,120,109,112,46,105,105,100,58,54,67,51,
+    51,51,52,67,50,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,
+    66,49,65,52,55,57,34,32,115,116,82,101,102,58,100,111,99,117,109,101,
+    110,116,73,68,61,34,120,109,112,46,100,105,100,58,54,67,51,51,51,52,67,
+    51,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,66,49,65,52,
+    55,57,34,47,62,32,60,47,114,100,102,58,68,101,115,99,114,105,112,116,
+    105,111,110,62,32,60,47,114,100,102,58,82,68,70,62,32,60,47,120,58,120,
+    109,112,109,101,116,97,62,32,60,63,120,112,97,99,107,101,116,32,101,110,
+    100,61,34,114,34,63,62,222,55,196,68,0,0,0,81,73,68,65,84,120,218,98,
+    100,176,112,96,128,0,70,36,10,202,198,45,14,23,164,68,132,12,46,35,35,
+    81,82,72,108,70,70,70,20,219,113,40,195,206,102,68,179,133,32,27,213,
+    16,34,53,146,100,11,186,69,100,107,36,142,13,15,64,2,90,80,83,8,62,41,
+    82,184,20,137,16,149,152,1,2,12,0,100,146,1,32,27,160,155,116,0,0,0,0,
+    73,69,78,68,174,66,96,130
+};
+
+unsigned char black_orange[] = {
+    137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,1,0,0,0,0,1,8,2,0,0,
+    0,190,59,231,32,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,
+    0,65,100,111,98,101,32,73,109,97,103,101,82,101,97,100,121,113,201,101,
+    60,0,0,3,35,105,84,88,116,88,77,76,58,99,111,109,46,97,100,111,98,101,
+    46,120,109,112,0,0,0,0,0,60,63,120,112,97,99,107,101,116,32,98,101,103,
+    105,110,61,34,239,187,191,34,32,105,100,61,34,87,53,77,48,77,112,67,101,
+    104,105,72,122,114,101,83,122,78,84,99,122,107,99,57,100,34,63,62,32,
+    60,120,58,120,109,112,109,101,116,97,32,120,109,108,110,115,58,120,61,
+    34,97,100,111,98,101,58,110,115,58,109,101,116,97,47,34,32,120,58,120,
+    109,112,116,107,61,34,65,100,111,98,101,32,88,77,80,32,67,111,114,101,
+    32,53,46,53,45,99,48,49,52,32,55,57,46,49,53,49,52,56,49,44,32,50,48,
+    49,51,47,48,51,47,49,51,45,49,50,58,48,57,58,49,53,32,32,32,32,32,32,
+    32,32,34,62,32,60,114,100,102,58,82,68,70,32,120,109,108,110,115,58,114,
+    100,102,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,
+    103,47,49,57,57,57,47,48,50,47,50,50,45,114,100,102,45,115,121,110,116,
+    97,120,45,110,115,35,34,62,32,60,114,100,102,58,68,101,115,99,114,105,
+    112,116,105,111,110,32,114,100,102,58,97,98,111,117,116,61,34,34,32,120,
+    109,108,110,115,58,120,109,112,61,34,104,116,116,112,58,47,47,110,115,
+    46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,48,47,34,32,
+    120,109,108,110,115,58,120,109,112,77,77,61,34,104,116,116,112,58,47,
+    47,110,115,46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,
+    48,47,109,109,47,34,32,120,109,108,110,115,58,115,116,82,101,102,61,34,
+    104,116,116,112,58,47,47,110,115,46,97,100,111,98,101,46,99,111,109,47,
+    120,97,112,47,49,46,48,47,115,84,121,112,101,47,82,101,115,111,117,114,
+    99,101,82,101,102,35,34,32,120,109,112,58,67,114,101,97,116,111,114,84,
+    111,111,108,61,34,65,100,111,98,101,32,80,104,111,116,111,115,104,111,
+    112,32,67,67,32,40,77,97,99,105,110,116,111,115,104,41,34,32,120,109,
+    112,77,77,58,73,110,115,116,97,110,99,101,73,68,61,34,120,109,112,46,
+    105,105,100,58,51,49,49,55,52,54,70,66,57,57,55,70,49,49,69,51,57,54,
+    66,50,66,68,56,52,57,52,66,49,65,52,55,57,34,32,120,109,112,77,77,58,
+    68,111,99,117,109,101,110,116,73,68,61,34,120,109,112,46,100,105,100,
+    58,51,49,49,55,52,54,70,67,57,57,55,70,49,49,69,51,57,54,66,50,66,68,
+    56,52,57,52,66,49,65,52,55,57,34,62,32,60,120,109,112,77,77,58,68,101,
+    114,105,118,101,100,70,114,111,109,32,115,116,82,101,102,58,105,110,115,
+    116,97,110,99,101,73,68,61,34,120,109,112,46,105,105,100,58,54,67,51,
+    51,51,52,67,54,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,
+    66,49,65,52,55,57,34,32,115,116,82,101,102,58,100,111,99,117,109,101,
+    110,116,73,68,61,34,120,109,112,46,100,105,100,58,51,49,49,55,52,54,70,
+    65,57,57,55,70,49,49,69,51,57,54,66,50,66,68,56,52,57,52,66,49,65,52,
+    55,57,34,47,62,32,60,47,114,100,102,58,68,101,115,99,114,105,112,116,
+    105,111,110,62,32,60,47,114,100,102,58,82,68,70,62,32,60,47,120,58,120,
+    109,112,109,101,116,97,62,32,60,63,120,112,97,99,107,101,116,32,101,110,
+    100,61,34,114,34,63,62,180,255,164,13,0,0,0,122,73,68,65,84,120,218,140,
+    145,139,13,0,33,8,67,41,195,184,255,134,92,78,81,1,241,99,136,41,143,
+    146,128,162,148,2,250,15,106,172,194,6,145,32,227,45,248,2,37,120,248,
+    46,132,51,190,129,242,236,116,195,240,227,60,144,117,199,202,179,125,
+    29,148,224,1,204,243,194,60,248,208,240,206,192,161,31,97,82,245,208,
+    114,143,170,187,83,115,255,117,155,78,152,165,73,175,10,29,143,142,93,
+    161,58,102,115,158,163,158,237,161,180,75,13,105,213,79,128,1,0,108,210,
+    10,124,149,27,171,85,0,0,0,0,73,69,78,68,174,66,96,130
+};
+
+unsigned char blue_hue[] = {
+    137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,1,0,0,0,0,1,8,2,0,0,
+    0,190,59,231,32,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,
+    0,65,100,111,98,101,32,73,109,97,103,101,82,101,97,100,121,113,201,101,
+    60,0,0,3,35,105,84,88,116,88,77,76,58,99,111,109,46,97,100,111,98,101,
+    46,120,109,112,0,0,0,0,0,60,63,120,112,97,99,107,101,116,32,98,101,103,
+    105,110,61,34,239,187,191,34,32,105,100,61,34,87,53,77,48,77,112,67,101,
+    104,105,72,122,114,101,83,122,78,84,99,122,107,99,57,100,34,63,62,32,
+    60,120,58,120,109,112,109,101,116,97,32,120,109,108,110,115,58,120,61,
+    34,97,100,111,98,101,58,110,115,58,109,101,116,97,47,34,32,120,58,120,
+    109,112,116,107,61,34,65,100,111,98,101,32,88,77,80,32,67,111,114,101,
+    32,53,46,53,45,99,48,49,52,32,55,57,46,49,53,49,52,56,49,44,32,50,48,
+    49,51,47,48,51,47,49,51,45,49,50,58,48,57,58,49,53,32,32,32,32,32,32,
+    32,32,34,62,32,60,114,100,102,58,82,68,70,32,120,109,108,110,115,58,114,
+    100,102,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,
+    103,47,49,57,57,57,47,48,50,47,50,50,45,114,100,102,45,115,121,110,116,
+    97,120,45,110,115,35,34,62,32,60,114,100,102,58,68,101,115,99,114,105,
+    112,116,105,111,110,32,114,100,102,58,97,98,111,117,116,61,34,34,32,120,
+    109,108,110,115,58,120,109,112,61,34,104,116,116,112,58,47,47,110,115,
+    46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,48,47,34,32,
+    120,109,108,110,115,58,120,109,112,77,77,61,34,104,116,116,112,58,47,
+    47,110,115,46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,
+    48,47,109,109,47,34,32,120,109,108,110,115,58,115,116,82,101,102,61,34,
+    104,116,116,112,58,47,47,110,115,46,97,100,111,98,101,46,99,111,109,47,
+    120,97,112,47,49,46,48,47,115,84,121,112,101,47,82,101,115,111,117,114,
+    99,101,82,101,102,35,34,32,120,109,112,58,67,114,101,97,116,111,114,84,
+    111,111,108,61,34,65,100,111,98,101,32,80,104,111,116,111,115,104,111,
+    112,32,67,67,32,40,77,97,99,105,110,116,111,115,104,41,34,32,120,109,
+    112,77,77,58,73,110,115,116,97,110,99,101,73,68,61,34,120,109,112,46,
+    105,105,100,58,50,65,51,70,52,67,55,68,57,57,55,68,49,49,69,51,57,54,
+    66,50,66,68,56,52,57,52,66,49,65,52,55,57,34,32,120,109,112,77,77,58,
+    68,111,99,117,109,101,110,116,73,68,61,34,120,109,112,46,100,105,100,
+    58,50,65,51,70,52,67,55,69,57,57,55,68,49,49,69,51,57,54,66,50,66,68,
+    56,52,57,52,66,49,65,52,55,57,34,62,32,60,120,109,112,77,77,58,68,101,
+    114,105,118,101,100,70,114,111,109,32,115,116,82,101,102,58,105,110,115,
+    116,97,110,99,101,73,68,61,34,120,109,112,46,105,105,100,58,50,65,51,
+    70,52,67,55,66,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,
+    66,49,65,52,55,57,34,32,115,116,82,101,102,58,100,111,99,117,109,101,
+    110,116,73,68,61,34,120,109,112,46,100,105,100,58,50,65,51,70,52,67,55,
+    67,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,66,49,65,52,
+    55,57,34,47,62,32,60,47,114,100,102,58,68,101,115,99,114,105,112,116,
+    105,111,110,62,32,60,47,114,100,102,58,82,68,70,62,32,60,47,120,58,120,
+    109,112,109,101,116,97,62,32,60,63,120,112,97,99,107,101,116,32,101,110,
+    100,61,34,114,34,63,62,152,227,70,152,0,0,0,138,73,68,65,84,120,218,124,
+    82,107,26,128,32,8,99,215,237,140,29,174,47,5,183,161,213,143,196,61,
+    144,154,136,235,142,8,32,198,179,214,64,174,137,201,150,16,150,182,234,
+    193,45,213,251,86,182,81,19,104,20,188,191,91,192,2,81,250,52,88,195,
+    129,126,131,98,57,128,213,188,40,214,174,223,100,121,16,146,249,166,202,
+    222,234,245,175,142,70,29,143,147,252,80,144,134,155,236,248,81,125,230,
+    54,131,41,45,187,61,110,230,110,193,105,28,30,132,100,170,212,220,145,
+    162,144,172,212,98,110,215,149,151,219,100,143,0,3,0,91,96,2,109,253,
+    96,243,247,0,0,0,0,73,69,78,68,174,66,96,130
+};
+
+unsigned char blue_orange[] = {
+    137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,1,0,0,0,0,1,8,2,0,0,
+    0,190,59,231,32,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,
+    0,65,100,111,98,101,32,73,109,97,103,101,82,101,97,100,121,113,201,101,
+    60,0,0,3,35,105,84,88,116,88,77,76,58,99,111,109,46,97,100,111,98,101,
+    46,120,109,112,0,0,0,0,0,60,63,120,112,97,99,107,101,116,32,98,101,103,
+    105,110,61,34,239,187,191,34,32,105,100,61,34,87,53,77,48,77,112,67,101,
+    104,105,72,122,114,101,83,122,78,84,99,122,107,99,57,100,34,63,62,32,
+    60,120,58,120,109,112,109,101,116,97,32,120,109,108,110,115,58,120,61,
+    34,97,100,111,98,101,58,110,115,58,109,101,116,97,47,34,32,120,58,120,
+    109,112,116,107,61,34,65,100,111,98,101,32,88,77,80,32,67,111,114,101,
+    32,53,46,53,45,99,48,49,52,32,55,57,46,49,53,49,52,56,49,44,32,50,48,
+    49,51,47,48,51,47,49,51,45,49,50,58,48,57,58,49,53,32,32,32,32,32,32,
+    32,32,34,62,32,60,114,100,102,58,82,68,70,32,120,109,108,110,115,58,114,
+    100,102,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,
+    103,47,49,57,57,57,47,48,50,47,50,50,45,114,100,102,45,115,121,110,116,
+    97,120,45,110,115,35,34,62,32,60,114,100,102,58,68,101,115,99,114,105,
+    112,116,105,111,110,32,114,100,102,58,97,98,111,117,116,61,34,34,32,120,
+    109,108,110,115,58,120,109,112,61,34,104,116,116,112,58,47,47,110,115,
+    46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,48,47,34,32,
+    120,109,108,110,115,58,120,109,112,77,77,61,34,104,116,116,112,58,47,
+    47,110,115,46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,
+    48,47,109,109,47,34,32,120,109,108,110,115,58,115,116,82,101,102,61,34,
+    104,116,116,112,58,47,47,110,115,46,97,100,111,98,101,46,99,111,109,47,
+    120,97,112,47,49,46,48,47,115,84,121,112,101,47,82,101,115,111,117,114,
+    99,101,82,101,102,35,34,32,120,109,112,58,67,114,101,97,116,111,114,84,
+    111,111,108,61,34,65,100,111,98,101,32,80,104,111,116,111,115,104,111,
+    112,32,67,67,32,40,77,97,99,105,110,116,111,115,104,41,34,32,120,109,
+    112,77,77,58,73,110,115,116,97,110,99,101,73,68,61,34,120,109,112,46,
+    105,105,100,58,50,65,51,70,52,67,55,57,57,57,55,68,49,49,69,51,57,54,
+    66,50,66,68,56,52,57,52,66,49,65,52,55,57,34,32,120,109,112,77,77,58,
+    68,111,99,117,109,101,110,116,73,68,61,34,120,109,112,46,100,105,100,
+    58,50,65,51,70,52,67,55,65,57,57,55,68,49,49,69,51,57,54,66,50,66,68,
+    56,52,57,52,66,49,65,52,55,57,34,62,32,60,120,109,112,77,77,58,68,101,
+    114,105,118,101,100,70,114,111,109,32,115,116,82,101,102,58,105,110,115,
+    116,97,110,99,101,73,68,61,34,120,109,112,46,105,105,100,58,50,65,51,
+    70,52,67,55,55,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,
+    66,49,65,52,55,57,34,32,115,116,82,101,102,58,100,111,99,117,109,101,
+    110,116,73,68,61,34,120,109,112,46,100,105,100,58,50,65,51,70,52,67,55,
+    56,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,66,49,65,52,
+    55,57,34,47,62,32,60,47,114,100,102,58,68,101,115,99,114,105,112,116,
+    105,111,110,62,32,60,47,114,100,102,58,82,68,70,62,32,60,47,120,58,120,
+    109,112,109,101,116,97,62,32,60,63,120,112,97,99,107,101,116,32,101,110,
+    100,61,34,114,34,63,62,108,96,84,160,0,0,0,144,73,68,65,84,120,218,140,
+    82,65,14,128,32,12,107,61,250,19,159,228,255,31,130,64,6,116,99,26,53,
+    154,186,174,5,92,121,157,55,0,182,167,180,183,225,122,23,193,245,42,36,
+    180,211,154,217,169,8,154,220,192,180,234,96,154,79,55,82,228,125,45,
+    209,26,53,132,242,73,211,194,85,86,93,59,17,235,193,39,101,179,158,40,
+    217,132,91,241,112,212,203,18,73,209,81,71,186,165,175,179,164,187,253,
+    113,192,141,197,231,143,205,39,69,13,73,54,169,48,110,159,16,132,12,228,
+    65,90,114,77,166,139,241,8,228,8,240,50,215,24,63,2,12,0,90,141,90,125,
+    199,3,102,129,0,0,0,0,73,69,78,68,174,66,96,130
+};
+
+unsigned char blue_red[] = {
+    137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,1,0,0,0,0,1,8,2,0,0,
+    0,190,59,231,32,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,
+    0,65,100,111,98,101,32,73,109,97,103,101,82,101,97,100,121,113,201,101,
+    60,0,0,3,35,105,84,88,116,88,77,76,58,99,111,109,46,97,100,111,98,101,
+    46,120,109,112,0,0,0,0,0,60,63,120,112,97,99,107,101,116,32,98,101,103,
+    105,110,61,34,239,187,191,34,32,105,100,61,34,87,53,77,48,77,112,67,101,
+    104,105,72,122,114,101,83,122,78,84,99,122,107,99,57,100,34,63,62,32,
+    60,120,58,120,109,112,109,101,116,97,32,120,109,108,110,115,58,120,61,
+    34,97,100,111,98,101,58,110,115,58,109,101,116,97,47,34,32,120,58,120,
+    109,112,116,107,61,34,65,100,111,98,101,32,88,77,80,32,67,111,114,101,
+    32,53,46,53,45,99,48,49,52,32,55,57,46,49,53,49,52,56,49,44,32,50,48,
+    49,51,47,48,51,47,49,51,45,49,50,58,48,57,58,49,53,32,32,32,32,32,32,
+    32,32,34,62,32,60,114,100,102,58,82,68,70,32,120,109,108,110,115,58,114,
+    100,102,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,
+    103,47,49,57,57,57,47,48,50,47,50,50,45,114,100,102,45,115,121,110,116,
+    97,120,45,110,115,35,34,62,32,60,114,100,102,58,68,101,115,99,114,105,
+    112,116,105,111,110,32,114,100,102,58,97,98,111,117,116,61,34,34,32,120,
+    109,108,110,115,58,120,109,112,61,34,104,116,116,112,58,47,47,110,115,
+    46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,48,47,34,32,
+    120,109,108,110,115,58,120,109,112,77,77,61,34,104,116,116,112,58,47,
+    47,110,115,46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,
+    48,47,109,109,47,34,32,120,109,108,110,115,58,115,116,82,101,102,61,34,
+    104,116,116,112,58,47,47,110,115,46,97,100,111,98,101,46,99,111,109,47,
+    120,97,112,47,49,46,48,47,115,84,121,112,101,47,82,101,115,111,117,114,
+    99,101,82,101,102,35,34,32,120,109,112,58,67,114,101,97,116,111,114,84,
+    111,111,108,61,34,65,100,111,98,101,32,80,104,111,116,111,115,104,111,
+    112,32,67,67,32,40,77,97,99,105,110,116,111,115,104,41,34,32,120,109,
+    112,77,77,58,73,110,115,116,97,110,99,101,73,68,61,34,120,109,112,46,
+    105,105,100,58,49,67,66,49,67,49,55,57,57,65,54,54,49,49,69,51,65,56,
+    70,52,70,57,57,55,66,49,51,52,55,51,68,57,34,32,120,109,112,77,77,58,
+    68,111,99,117,109,101,110,116,73,68,61,34,120,109,112,46,100,105,100,
+    58,49,67,66,49,67,49,55,65,57,65,54,54,49,49,69,51,65,56,70,52,70,57,
+    57,55,66,49,51,52,55,51,68,57,34,62,32,60,120,109,112,77,77,58,68,101,
+    114,105,118,101,100,70,114,111,109,32,115,116,82,101,102,58,105,110,115,
+    116,97,110,99,101,73,68,61,34,120,109,112,46,105,105,100,58,49,67,66,
+    49,67,49,55,55,57,65,54,54,49,49,69,51,65,56,70,52,70,57,57,55,66,49,
+    51,52,55,51,68,57,34,32,115,116,82,101,102,58,100,111,99,117,109,101,
+    110,116,73,68,61,34,120,109,112,46,100,105,100,58,49,67,66,49,67,49,55,
+    56,57,65,54,54,49,49,69,51,65,56,70,52,70,57,57,55,66,49,51,52,55,51,
+    68,57,34,47,62,32,60,47,114,100,102,58,68,101,115,99,114,105,112,116,
+    105,111,110,62,32,60,47,114,100,102,58,82,68,70,62,32,60,47,120,58,120,
+    109,112,109,101,116,97,62,32,60,63,120,112,97,99,107,101,116,32,101,110,
+    100,61,34,114,34,63,62,92,101,36,72,0,0,0,68,73,68,65,84,120,218,98,100,
+    96,248,207,200,240,159,129,129,1,43,137,139,139,204,192,195,198,228,226,
+    17,164,58,98,98,248,71,188,56,154,32,217,92,210,217,64,242,31,85,140,
+    162,144,75,70,160,81,29,17,147,90,8,38,60,252,73,23,141,4,8,48,0,237,
+    88,232,1,203,157,22,9,0,0,0,0,73,69,78,68,174,66,96,130
+};
+
+unsigned char heat_map[] = {
+    137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,1,0,0,0,0,1,8,2,0,0,
+    0,190,59,231,32,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,
+    0,65,100,111,98,101,32,73,109,97,103,101,82,101,97,100,121,113,201,101,
+    60,0,0,3,35,105,84,88,116,88,77,76,58,99,111,109,46,97,100,111,98,101,
+    46,120,109,112,0,0,0,0,0,60,63,120,112,97,99,107,101,116,32,98,101,103,
+    105,110,61,34,239,187,191,34,32,105,100,61,34,87,53,77,48,77,112,67,101,
+    104,105,72,122,114,101,83,122,78,84,99,122,107,99,57,100,34,63,62,32,
+    60,120,58,120,109,112,109,101,116,97,32,120,109,108,110,115,58,120,61,
+    34,97,100,111,98,101,58,110,115,58,109,101,116,97,47,34,32,120,58,120,
+    109,112,116,107,61,34,65,100,111,98,101,32,88,77,80,32,67,111,114,101,
+    32,53,46,53,45,99,48,49,52,32,55,57,46,49,53,49,52,56,49,44,32,50,48,
+    49,51,47,48,51,47,49,51,45,49,50,58,48,57,58,49,53,32,32,32,32,32,32,
+    32,32,34,62,32,60,114,100,102,58,82,68,70,32,120,109,108,110,115,58,114,
+    100,102,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,
+    103,47,49,57,57,57,47,48,50,47,50,50,45,114,100,102,45,115,121,110,116,
+    97,120,45,110,115,35,34,62,32,60,114,100,102,58,68,101,115,99,114,105,
+    112,116,105,111,110,32,114,100,102,58,97,98,111,117,116,61,34,34,32,120,
+    109,108,110,115,58,120,109,112,61,34,104,116,116,112,58,47,47,110,115,
+    46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,48,47,34,32,
+    120,109,108,110,115,58,120,109,112,77,77,61,34,104,116,116,112,58,47,
+    47,110,115,46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,
+    48,47,109,109,47,34,32,120,109,108,110,115,58,115,116,82,101,102,61,34,
+    104,116,116,112,58,47,47,110,115,46,97,100,111,98,101,46,99,111,109,47,
+    120,97,112,47,49,46,48,47,115,84,121,112,101,47,82,101,115,111,117,114,
+    99,101,82,101,102,35,34,32,120,109,112,58,67,114,101,97,116,111,114,84,
+    111,111,108,61,34,65,100,111,98,101,32,80,104,111,116,111,115,104,111,
+    112,32,67,67,32,40,77,97,99,105,110,116,111,115,104,41,34,32,120,109,
+    112,77,77,58,73,110,115,116,97,110,99,101,73,68,61,34,120,109,112,46,
+    105,105,100,58,54,67,51,51,51,52,66,67,57,57,55,68,49,49,69,51,57,54,
+    66,50,66,68,56,52,57,52,66,49,65,52,55,57,34,32,120,109,112,77,77,58,
+    68,111,99,117,109,101,110,116,73,68,61,34,120,109,112,46,100,105,100,
+    58,54,67,51,51,51,52,66,68,57,57,55,68,49,49,69,51,57,54,66,50,66,68,
+    56,52,57,52,66,49,65,52,55,57,34,62,32,60,120,109,112,77,77,58,68,101,
+    114,105,118,101,100,70,114,111,109,32,115,116,82,101,102,58,105,110,115,
+    116,97,110,99,101,73,68,61,34,120,109,112,46,105,105,100,58,50,65,51,
+    70,52,67,55,70,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,
+    66,49,65,52,55,57,34,32,115,116,82,101,102,58,100,111,99,117,109,101,
+    110,116,73,68,61,34,120,109,112,46,100,105,100,58,50,65,51,70,52,67,56,
+    48,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,66,49,65,52,
+    55,57,34,47,62,32,60,47,114,100,102,58,68,101,115,99,114,105,112,116,
+    105,111,110,62,32,60,47,114,100,102,58,82,68,70,62,32,60,47,120,58,120,
+    109,112,109,101,116,97,62,32,60,63,120,112,97,99,107,101,116,32,101,110,
+    100,61,34,114,34,63,62,119,108,41,245,0,0,0,119,73,68,65,84,120,218,156,
+    144,89,14,128,48,8,68,89,254,188,255,73,60,160,91,171,64,101,168,137,
+    9,25,31,227,116,131,23,90,153,118,38,106,234,37,35,32,221,36,113,135,
+    224,220,170,201,57,85,47,181,128,249,22,126,131,229,31,136,190,7,194,
+    170,97,115,120,92,60,247,201,128,171,142,154,159,233,60,153,24,39,238,
+    99,167,246,241,146,2,36,128,181,130,90,88,90,183,90,128,126,153,255,126,
+    65,152,223,176,42,70,156,103,5,161,213,33,192,0,98,67,127,252,195,202,
+    106,213,0,0,0,0,73,69,78,68,174,66,96,130
+};
+
+unsigned char pestel_shades[] = {
+    137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,1,0,0,0,0,1,8,2,0,0,
+    0,190,59,231,32,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,
+    0,65,100,111,98,101,32,73,109,97,103,101,82,101,97,100,121,113,201,101,
+    60,0,0,3,35,105,84,88,116,88,77,76,58,99,111,109,46,97,100,111,98,101,
+    46,120,109,112,0,0,0,0,0,60,63,120,112,97,99,107,101,116,32,98,101,103,
+    105,110,61,34,239,187,191,34,32,105,100,61,34,87,53,77,48,77,112,67,101,
+    104,105,72,122,114,101,83,122,78,84,99,122,107,99,57,100,34,63,62,32,
+    60,120,58,120,109,112,109,101,116,97,32,120,109,108,110,115,58,120,61,
+    34,97,100,111,98,101,58,110,115,58,109,101,116,97,47,34,32,120,58,120,
+    109,112,116,107,61,34,65,100,111,98,101,32,88,77,80,32,67,111,114,101,
+    32,53,46,53,45,99,48,49,52,32,55,57,46,49,53,49,52,56,49,44,32,50,48,
+    49,51,47,48,51,47,49,51,45,49,50,58,48,57,58,49,53,32,32,32,32,32,32,
+    32,32,34,62,32,60,114,100,102,58,82,68,70,32,120,109,108,110,115,58,114,
+    100,102,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,
+    103,47,49,57,57,57,47,48,50,47,50,50,45,114,100,102,45,115,121,110,116,
+    97,120,45,110,115,35,34,62,32,60,114,100,102,58,68,101,115,99,114,105,
+    112,116,105,111,110,32,114,100,102,58,97,98,111,117,116,61,34,34,32,120,
+    109,108,110,115,58,120,109,112,61,34,104,116,116,112,58,47,47,110,115,
+    46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,48,47,34,32,
+    120,109,108,110,115,58,120,109,112,77,77,61,34,104,116,116,112,58,47,
+    47,110,115,46,97,100,111,98,101,46,99,111,109,47,120,97,112,47,49,46,
+    48,47,109,109,47,34,32,120,109,108,110,115,58,115,116,82,101,102,61,34,
+    104,116,116,112,58,47,47,110,115,46,97,100,111,98,101,46,99,111,109,47,
+    120,97,112,47,49,46,48,47,115,84,121,112,101,47,82,101,115,111,117,114,
+    99,101,82,101,102,35,34,32,120,109,112,58,67,114,101,97,116,111,114,84,
+    111,111,108,61,34,65,100,111,98,101,32,80,104,111,116,111,115,104,111,
+    112,32,67,67,32,40,77,97,99,105,110,116,111,115,104,41,34,32,120,109,
+    112,77,77,58,73,110,115,116,97,110,99,101,73,68,61,34,120,109,112,46,
+    105,105,100,58,54,67,51,51,51,52,67,48,57,57,55,68,49,49,69,51,57,54,
+    66,50,66,68,56,52,57,52,66,49,65,52,55,57,34,32,120,109,112,77,77,58,
+    68,111,99,117,109,101,110,116,73,68,61,34,120,109,112,46,100,105,100,
+    58,54,67,51,51,51,52,67,49,57,57,55,68,49,49,69,51,57,54,66,50,66,68,
+    56,52,57,52,66,49,65,52,55,57,34,62,32,60,120,109,112,77,77,58,68,101,
+    114,105,118,101,100,70,114,111,109,32,115,116,82,101,102,58,105,110,115,
+    116,97,110,99,101,73,68,61,34,120,109,112,46,105,105,100,58,54,67,51,
+    51,51,52,66,69,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,
+    66,49,65,52,55,57,34,32,115,116,82,101,102,58,100,111,99,117,109,101,
+    110,116,73,68,61,34,120,109,112,46,100,105,100,58,54,67,51,51,51,52,66,
+    70,57,57,55,68,49,49,69,51,57,54,66,50,66,68,56,52,57,52,66,49,65,52,
+    55,57,34,47,62,32,60,47,114,100,102,58,68,101,115,99,114,105,112,116,
+    105,111,110,62,32,60,47,114,100,102,58,82,68,70,62,32,60,47,120,58,120,
+    109,112,109,101,116,97,62,32,60,63,120,112,97,99,107,101,116,32,101,110,
+    100,61,34,114,34,63,62,123,175,1,177,0,0,0,169,73,68,65,84,120,218,116,
+    144,209,1,195,32,8,68,241,92,178,127,221,127,9,168,32,92,169,177,134,
+    144,119,136,24,24,242,122,143,181,68,70,124,214,131,13,177,240,133,244,
+    132,245,130,17,178,3,10,74,94,216,115,38,18,14,57,145,105,5,136,45,66,
+    203,193,152,81,115,102,29,202,188,107,238,226,9,85,225,224,60,88,255,
+    220,97,252,198,99,50,100,20,175,233,161,102,133,96,79,147,148,49,85,65,
+    248,109,157,203,44,193,76,40,205,189,184,55,137,184,123,7,75,56,216,244,
+    194,170,77,170,104,177,210,55,216,187,7,223,101,157,162,252,225,71,50,
+    139,60,35,87,105,118,94,212,123,209,222,14,59,250,51,129,167,113,104,
+    34,31,1,6,0,93,65,233,131,137,74,65,77,0,0,0,0,73,69,78,68,174,66,96,
+    130
+};
+
diff --git a/filters/ColorinterpFilter.cpp b/filters/ColorinterpFilter.cpp
new file mode 100644
index 0000000..028528c
--- /dev/null
+++ b/filters/ColorinterpFilter.cpp
@@ -0,0 +1,243 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler, hobu.inc at gmail.com
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "ColorinterpFilter.hpp"
+
+#include <pdal/PointView.hpp>
+#include <pdal/GDALUtils.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <gdal.h>
+#include <cpl_vsi.h>
+#include <ogr_spatialref.h>
+
+#include <array>
+#include <algorithm>
+#include <cmath>
+
+#include "ColorInterpRamps.hpp"
+
+namespace pdal
+{
+
+static std::vector<std::string> ramps = {"awesome_green", "black_orange", "blue_hue", "blue_red", "heat_map", "pestel_shades", "blue_orange"};
+
+static PluginInfo const s_info = PluginInfo(
+    "filters.colorinterp",
+    "Assigns RGB colors based on a dimension and a ramp",
+    "http://pdal.io/stages/filters.colorinterp.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, ColorinterpFilter, Filter, s_info)
+
+std::string ColorinterpFilter::getName() const { return s_info.name; }
+
+// The VSIFILE* that VSIFileFromMemBuffer creates in this
+// macro is never cleaned up. We're opening seven PNGs in the
+// ColorInterpRamps-ramps.hpp header. We always open them so they're available.
+#define GETRAMP(name) \
+    if (pdal::Utils::iequals(#name, rampFilename)) \
+    { \
+        GByte* location(0); \
+        int size (0); \
+        location = name; \
+        size = sizeof(name); \
+        rampFilename = "/vsimem/" + std::string(#name) + ".png"; \
+        (void)VSIFileFromMemBuffer(rampFilename.c_str(), location, size, FALSE); \
+    }
+//
+std::shared_ptr<pdal::gdal::Raster> openRamp(std::string& rampFilename)
+{
+    // If the user] selected a default ramp name, it will be opened by
+    // one of these macros if it matches. Otherwise, we just open with the
+    // GDALOpen'able the user gave us
+
+    // GETRAMP will set rampFilename to the /vsimem filename it
+    // used to actually open the file, and from then on it can be treated
+    // like any other GDAL datasource.
+
+    GETRAMP(awesome_green);
+    GETRAMP(black_orange);
+    GETRAMP(blue_hue);
+    GETRAMP(blue_red);
+    GETRAMP(heat_map);
+    GETRAMP(pestel_shades);
+
+    std::shared_ptr<pdal::gdal::Raster> output (new pdal::gdal::Raster(rampFilename.c_str()));
+    return output;
+}
+
+void ColorinterpFilter::addArgs(ProgramArgs& args)
+{
+    args.add("dimension", "Dimension to interpolate", m_interpDimString, "Z");
+    args.add("minimum", "Minimum value to use for scaling", m_min);
+    args.add("maximum", "Maximum value to use for scaling", m_max);
+    args.add("ramp", "GDAL-readable color ramp image to use", m_colorramp, "pestel_shades");
+    args.add("invert", "Invert the ramp direction", m_invertRamp, false);
+    args.add("mad", "Use Median Absolute Deviation to compute ramp bounds in combination with 'k' ", m_useMAD, false);
+    args.add("mad_multiplier", "MAD threshold multiplier", m_madMultiplier, 1.4862);
+    args.add("k", "Number of deviations to compute minimum/maximum ", m_stdDevThreshold, 0.0);
+}
+
+void ColorinterpFilter::addDimensions(PointLayoutPtr layout)
+{
+    layout->registerDims({Dimension::Id::Red, Dimension::Id::Green, Dimension::Id::Blue});
+}
+
+void ColorinterpFilter::initialize()
+{
+    gdal::registerDrivers();
+
+    m_raster = openRamp(m_colorramp);
+    m_raster->open();
+
+    log()->get(LogLevel::Debug) << getName() << " raster connection: "
+                                             << m_raster->filename() << std::endl;
+
+    m_interpDim = Dimension::id(m_interpDimString);
+    if (m_interpDim == Dimension::Id::Unknown)
+        throw pdal_error("Dimension name is not known!");
+}
+
+void ColorinterpFilter::filter(PointView& view)
+{
+    double median(0.0);
+    double mad(0.0);
+
+    // If the user set a 'k' value, we use that
+    // defaulting to using a computed stddev if we
+    // were not told to use MAD.
+    if ( m_stdDevThreshold != 0.0)
+    {
+        std::vector<double> values(view.size());
+
+        pdal::stats::Summary summary(pdal::Dimension::name(m_interpDim), pdal::stats::Summary::NoEnum);
+        for (PointId idx = 0; idx < view.size(); ++idx)
+        {
+            double v = view.getFieldAs<double>(m_interpDim, idx);
+            summary.insert(v);
+            values[idx] = v;
+        }
+
+        auto compute_median = [](std::vector<double> vals)
+        {
+            std::nth_element(vals.begin(), vals.begin()+vals.size()/2, vals.end());
+
+            return *(vals.begin()+vals.size()/2);
+        };
+
+        median = compute_median(values);
+        if (m_useMAD)
+        {
+             std::transform(values.begin(), values.end(), values.begin(),
+                   [median](double v) { return std::fabs(v - median); });
+             mad = compute_median(values);
+
+             mad = mad * m_madMultiplier;
+             double threshold = m_stdDevThreshold * mad;
+             m_min = median - threshold;
+             m_max = median + threshold;
+
+             log()->get(LogLevel::Debug) << getName() << " mad " << mad << std::endl;
+             log()->get(LogLevel::Debug) << getName() << " median " << median << std::endl;
+             log()->get(LogLevel::Debug) << getName() << " minimum " << m_min << std::endl;
+             log()->get(LogLevel::Debug) << getName() << " maximum " << m_max << std::endl;
+        }
+        else
+        {
+             double threshold = (m_stdDevThreshold * summary.stddev());
+             m_min = median - threshold;
+             m_max = median + threshold;
+
+             log()->get(LogLevel::Debug) << getName() << " stddev threshold " << threshold << std::endl;
+             log()->get(LogLevel::Debug) << getName() << " median " << median << std::endl;
+             log()->get(LogLevel::Debug) << getName() << " minimum " << m_min << std::endl;
+             log()->get(LogLevel::Debug) << getName() << " maximum " << m_max << std::endl;
+        }
+
+    }
+
+    // If the user didn't set min/max values and hadn't set a stddev, we
+    // compute them.
+    else if ((m_min == 0.0 && m_max == 0.0) )
+    {
+        pdal::stats::Summary summary(pdal::Dimension::name(m_interpDim), pdal::stats::Summary::NoEnum);
+        for (PointId idx = 0; idx < view.size(); ++idx)
+        {
+            double v = view.getFieldAs<double>(m_interpDim, idx);
+            summary.insert(v);
+        }
+
+        m_min = summary.minimum();
+        m_max = summary.maximum();
+    }
+
+    m_raster->readBand(m_redBand, 1);
+    m_raster->readBand(m_greenBand, 2 );
+    m_raster->readBand(m_blueBand, 3);
+
+
+
+    for (PointId idx = 0; idx < view.size(); ++idx)
+    {
+
+        double v = view.getFieldAs<double>(m_interpDim, idx);
+
+        size_t img_width = m_redBand.size();
+        double factor = (v - m_min) / (m_max - m_min);
+
+        if (m_invertRamp)
+            factor = 1 - factor;
+
+        size_t position(std::floor(factor * img_width));
+
+        // Don't color points whose computed position
+        // would fall outside the image
+        if (position > img_width)
+        {
+            continue;
+        }
+
+        double red = m_redBand[position];
+        double blue = m_blueBand[position];
+        double green = m_greenBand[position];
+
+        view.setField(Dimension::Id::Red, idx, red);
+        view.setField(Dimension::Id::Green, idx, green);
+        view.setField(Dimension::Id::Blue, idx, blue);
+
+    }
+}
+
+} // namespace pdal
diff --git a/filters/ColorinterpFilter.hpp b/filters/ColorinterpFilter.hpp
new file mode 100644
index 0000000..a9252ec
--- /dev/null
+++ b/filters/ColorinterpFilter.hpp
@@ -0,0 +1,102 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler, howard at hobu.co
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/plugin.hpp>
+#include <pdal/Filter.hpp>
+#include <filters/StatsFilter.hpp>
+
+#include <gdal.h>
+#include <ogr_spatialref.h>
+#include <pdal/GDALUtils.hpp>
+
+#include <map>
+
+extern "C" int32_t ColorinterpFilter_ExitFunc();
+extern "C" PF_ExitFunc ColorinterpFilter_InitPlugin();
+
+namespace pdal
+{
+
+// Interpolates color ramp into Red, Green, and Blue dimensions
+// for a given dimension
+// specified dimensions. It also supports scaling the data by a multiplier
+// on a per-dimension basis.
+class PDAL_DLL ColorinterpFilter : public Filter
+{
+public:
+
+    ColorinterpFilter()
+        : m_interpDim(Dimension::Id::Z)
+        , m_interpDimString("Z")
+        , m_min(0.0)
+        , m_max(0.0)
+        , m_rampFilename("/vsimem/colorramp.png")
+        , m_invertRamp(false)
+        , m_stdDevThreshold(0.0)
+        , m_useMAD(false)
+        , m_madMultiplier(1.4862)
+    {}
+    ColorinterpFilter& operator=(const ColorinterpFilter&) = delete;
+    ColorinterpFilter(const ColorinterpFilter&) = delete;
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    virtual void addArgs(ProgramArgs& args);
+    virtual void filter(PointView& view);
+    virtual void initialize();
+    virtual void addDimensions(PointLayoutPtr layout);
+
+
+    Dimension::Id m_interpDim;
+    std::string m_interpDimString;
+    double m_min;
+    double m_max;
+    std::string m_colorramp;
+    std::shared_ptr<pdal::gdal::Raster> m_raster;
+    std::string m_rampFilename;
+    std::vector<uint8_t> m_redBand;
+    std::vector<uint8_t> m_greenBand;
+    std::vector<uint8_t> m_blueBand;
+    bool m_invertRamp;
+    double m_stdDevThreshold;
+    bool m_useMAD;
+    double m_madMultiplier;
+};
+
+} // namespace pdal
diff --git a/filters/colorization/ColorizationFilter.cpp b/filters/ColorizationFilter.cpp
similarity index 100%
rename from filters/colorization/ColorizationFilter.cpp
rename to filters/ColorizationFilter.cpp
diff --git a/filters/colorization/ColorizationFilter.hpp b/filters/ColorizationFilter.hpp
similarity index 100%
rename from filters/colorization/ColorizationFilter.hpp
rename to filters/ColorizationFilter.hpp
diff --git a/filters/ComputeRangeFilter.cpp b/filters/ComputeRangeFilter.cpp
new file mode 100644
index 0000000..80d6300
--- /dev/null
+++ b/filters/ComputeRangeFilter.cpp
@@ -0,0 +1,115 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "ComputeRangeFilter.hpp"
+
+#include <pdal/pdal_macros.hpp>
+
+#include <string>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.computerange", "Compute Range Filter",
+               "http://pdal.io/stages/filters.computerange.html");
+
+CREATE_STATIC_PLUGIN(1, 0, ComputeRangeFilter, Filter, s_info)
+
+std::string ComputeRangeFilter::getName() const
+{
+    return s_info.name;
+}
+
+void ComputeRangeFilter::addDimensions(PointLayoutPtr layout)
+{
+    using namespace Dimension;
+    m_range = layout->registerOrAssignDim("Range", Type::Double);
+}
+
+void ComputeRangeFilter::prepared(PointTableRef table)
+{
+    using namespace Dimension;
+
+    const PointLayoutPtr layout(table.layout());
+
+    m_frameNumber = layout->findDim("Frame Number");
+    if (m_frameNumber == Id::Unknown)
+        throw pdal_error("ComputeRangeFilter: missing Frame Number dimension in input PointView");
+
+    m_pixelNumber = layout->findDim("Pixel Number");
+    if (m_pixelNumber == Id::Unknown)
+        throw pdal_error("ComputeRangeFilter: missing Pixel Number dimension in input PointView");
+}
+
+void ComputeRangeFilter::filter(PointView& view)
+{
+    using namespace Dimension;
+
+    // Sensor coordinates are provided for each frame. The pixel number -5 is
+    // used to flag the sensor position.
+    log()->get(LogLevel::Debug) << "Stash sensor positions...\n";
+    struct sp
+    {
+        double sx, sy, sz;
+    };
+    std::map<int, sp> smap;
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        int f = view.getFieldAs<int>(m_frameNumber, i);
+        int p = view.getFieldAs<int>(m_pixelNumber, i);
+        if (p == -5)
+        {
+            smap[f].sx = view.getFieldAs<double>(Id::X, i);
+            smap[f].sy = view.getFieldAs<double>(Id::Y, i);
+            smap[f].sz = view.getFieldAs<double>(Id::Z, i);
+        }
+    }
+
+    // For each XYZ coordinate, we look up the sensor position for the
+    // corresponding frame and compute the Euclidean distance. This is
+    // recorded in the Range dimension.
+    log()->get(LogLevel::Debug) << "Compute ranges...\n";
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        int f = view.getFieldAs<int>(m_frameNumber, i);
+        double dx = smap[f].sx - view.getFieldAs<double>(Id::X, i);
+        double dy = smap[f].sy - view.getFieldAs<double>(Id::Y, i);
+        double dz = smap[f].sz - view.getFieldAs<double>(Id::Z, i);
+        double r = std::sqrt(dx * dx + dy * dy + dz * dz);
+        view.setField(m_range, i, r);
+    }
+}
+
+} // namespace pdal
diff --git a/filters/ComputeRangeFilter.hpp b/filters/ComputeRangeFilter.hpp
new file mode 100644
index 0000000..a958bee
--- /dev/null
+++ b/filters/ComputeRangeFilter.hpp
@@ -0,0 +1,72 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+#include <string>
+
+extern "C" int32_t ComputeRangeFilter_ExitFunc();
+extern "C" PF_ExitFunc ComputeRangeFilter_InitPlugin();
+
+namespace pdal
+{
+
+class PointLayout;
+class PointView;
+
+class PDAL_DLL ComputeRangeFilter : public Filter
+{
+public:
+    ComputeRangeFilter() : Filter()
+    {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    Dimension::Id m_pixelNumber, m_frameNumber, m_range;
+
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual void filter(PointView& view);
+    virtual void prepared(PointTableRef table);
+
+    ComputeRangeFilter& operator=(const ComputeRangeFilter&); // not implemented
+    ComputeRangeFilter(const ComputeRangeFilter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/filters/CropFilter.cpp b/filters/CropFilter.cpp
new file mode 100644
index 0000000..8ee1282
--- /dev/null
+++ b/filters/CropFilter.cpp
@@ -0,0 +1,243 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "CropFilter.hpp"
+
+#include <iomanip>
+
+#include <pdal/PointView.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/Polygon.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+#include <pdal/KDIndex.hpp>
+
+#include <sstream>
+#include <cstdarg>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "filters.crop",
+    "Filter points inside or outside a bounding box or a polygon",
+    "http://pdal.io/stages/filters.crop.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, CropFilter, Filter, s_info)
+
+std::string CropFilter::getName() const { return s_info.name; }
+
+CropFilter::CropFilter() : pdal::Filter()
+{
+    m_cropOutside = false;
+}
+
+
+void CropFilter::addArgs(ProgramArgs& args)
+{
+    args.add("outside", "Whether we keep points inside or outside of the "
+        "bounding region", m_cropOutside);
+    args.add("a_srs", "Spatial reference for bounding region", m_assignedSrs);
+    args.add("bounds", "Point box for cropped points", m_bounds);
+    args.add("point", "Crop within 'distance' from a 2D or 3D point", m_points).
+        setErrorText("Invalid point specification must be in the form \"(1.00, 1.00)\""
+                "or \"(1.00, 1.00, 1.00)\"");
+    args.add("distance", "Crop with this distance from 2D or 3D 'point'", m_distance);
+    args.add("polygon", "Bounding polying for cropped points", m_polys).
+        setErrorText("Invalid polygon specification.  "
+            "Must be valid GeoJSON/WKT");
+}
+
+
+void CropFilter::initialize()
+{
+
+    // Set geometry from polygons.
+    if (m_polys.size())
+    {
+        m_geoms.clear();
+        for (Polygon& poly : m_polys)
+        {
+            GeomPkg g;
+
+            // Throws if invalid.
+            poly.valid();
+            if (!m_assignedSrs.empty())
+                poly.setSpatialReference(m_assignedSrs);
+            g.m_geom = poly;
+            m_geoms.push_back(g);
+        }
+    }
+
+}
+
+
+void CropFilter::ready(PointTableRef table)
+{
+    for (auto& geom : m_geoms)
+    {
+        // If we already overrode the SRS, use that instead
+        if (m_assignedSrs.empty())
+            geom.m_geom.setSpatialReference(table.anySpatialReference());
+    }
+}
+
+
+bool CropFilter::processOne(PointRef& point)
+{
+    for (auto& geom : m_geoms)
+        if (!crop(point, geom))
+            return false;
+
+    for (auto& box : m_bounds)
+        if (!crop(point, box.to2d()))
+            return false;
+
+    return true;
+}
+
+
+PointViewSet CropFilter::run(PointViewPtr view)
+{
+    PointViewSet viewSet;
+    SpatialReference srs = view->spatialReference();
+
+    for (auto& geom : m_geoms)
+    {
+        // If this is the first time through or the SRS has changed,
+        // prepare the crop polygon.
+        if (srs != m_lastSrs)
+        {
+            geom.m_geom = geom.m_geom.transform(srs);
+        }
+
+        PointViewPtr outView = view->makeNew();
+        crop(geom, *view, *outView);
+        viewSet.insert(outView);
+    }
+    m_lastSrs = srs;
+
+    for (auto& box : m_bounds)
+    {
+        PointViewPtr outView = view->makeNew();
+        crop(box.to2d(), *view, *outView);
+        viewSet.insert(outView);
+    }
+
+    for (auto& point: m_points)
+    {
+        PointViewPtr outView = view->makeNew();
+        crop(point, m_distance, *view, *outView);
+        viewSet.insert(outView);
+    }
+
+    return viewSet;
+}
+
+
+bool CropFilter::crop(PointRef& point, const BOX2D& box)
+{
+    double x = point.getFieldAs<double>(Dimension::Id::X);
+    double y = point.getFieldAs<double>(Dimension::Id::Y);
+
+    // Return true if we're keeping a point.
+    return (m_cropOutside != box.contains(x, y));
+}
+
+
+void CropFilter::crop(const BOX2D& box, PointView& input, PointView& output)
+{
+    PointRef point = input.point(0);
+    for (PointId idx = 0; idx < input.size(); ++idx)
+    {
+        point.setPointId(idx);
+        if (crop(point, box))
+            output.appendPoint(input, idx);
+    }
+}
+
+bool CropFilter::crop(PointRef& point, const GeomPkg& g)
+{
+    bool covers = g.m_geom.covers(point);
+    bool keep = (m_cropOutside != covers);
+    return keep;
+}
+
+void CropFilter::crop(const GeomPkg& g, PointView& input, PointView& output)
+{
+    PointRef point = input.point(0);
+    for (PointId idx = 0; idx < input.size(); ++idx)
+    {
+        point.setPointId(idx);
+        bool covers = g.m_geom.covers(point);
+        bool keep = (m_cropOutside != covers);
+        if (keep)
+            output.appendPoint(input, idx);
+    }
+}
+
+void CropFilter::crop(const cropfilter::Point& point, double distance, PointView& input, PointView& output)
+{
+
+    bool bIs3D = point.is3d();
+
+    if (bIs3D)
+    {
+        KD3Index index(input);
+        index.build();
+        std::vector<PointId> points = index.radius(point.x, point.y, point.z, m_distance);
+        for (PointId idx = 0; idx < points.size(); ++idx)
+        {
+            if (!m_cropOutside)
+                output.appendPoint(input, idx);
+        }
+    }
+
+    else
+    {
+        KD2Index index(input);
+        index.build();
+        std::vector<PointId> points = index.radius(point.x, point.y, m_distance);
+
+        for (PointId idx = 0; idx < points.size(); ++idx)
+        {
+            if (!m_cropOutside)
+                output.appendPoint(input, idx);
+        }
+
+    }
+}
+
+
+} // namespace pdal
diff --git a/filters/CropFilter.hpp b/filters/CropFilter.hpp
new file mode 100644
index 0000000..b9614e8
--- /dev/null
+++ b/filters/CropFilter.hpp
@@ -0,0 +1,97 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/Polygon.hpp>
+#include <pdal/plugin.hpp>
+#include "filters/private/crop/Point.hpp"
+
+extern "C" int32_t CropFilter_ExitFunc();
+extern "C" PF_ExitFunc CropFilter_InitPlugin();
+
+namespace pdal
+{
+
+class ProgramArgs;
+
+// removes any points outside of the given range
+// updates the header accordingly
+class PDAL_DLL CropFilter : public Filter
+{
+public:
+    CropFilter();
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    std::vector<Bounds> m_bounds;
+    bool m_cropOutside;
+    std::vector<Polygon> m_polys;
+    SpatialReference m_assignedSrs;
+    SpatialReference m_lastSrs;
+    double m_distance;
+    std::vector<cropfilter::Point> m_points;
+
+    struct GeomPkg
+    {
+        GeomPkg()
+        {}
+
+        Polygon m_geom;
+        Polygon m_geomXform;
+    };
+
+    std::vector<GeomPkg> m_geoms;
+
+    void addArgs(ProgramArgs& args);
+    virtual void initialize();
+    virtual void ready(PointTableRef table);
+    virtual bool processOne(PointRef& point);
+    virtual PointViewSet run(PointViewPtr view);
+    bool crop(PointRef& point, const BOX2D& box);
+    void crop(const BOX2D& box, PointView& input, PointView& output);
+    bool crop(PointRef& point, const GeomPkg& g);
+    void crop(const GeomPkg& g, PointView& input, PointView& output);
+    void crop(const cropfilter::Point& point, double distance, PointView& input, PointView& output);
+
+    CropFilter& operator=(const CropFilter&); // not implemented
+    CropFilter(const CropFilter&); // not implemented
+};
+
+} // namespace pdal
+
diff --git a/filters/decimation/DecimationFilter.cpp b/filters/DecimationFilter.cpp
similarity index 100%
rename from filters/decimation/DecimationFilter.cpp
rename to filters/DecimationFilter.cpp
diff --git a/filters/decimation/DecimationFilter.hpp b/filters/DecimationFilter.hpp
similarity index 100%
rename from filters/decimation/DecimationFilter.hpp
rename to filters/DecimationFilter.hpp
diff --git a/filters/divider/DividerFilter.cpp b/filters/DividerFilter.cpp
similarity index 100%
rename from filters/divider/DividerFilter.cpp
rename to filters/DividerFilter.cpp
diff --git a/filters/divider/DividerFilter.hpp b/filters/DividerFilter.hpp
similarity index 100%
rename from filters/divider/DividerFilter.hpp
rename to filters/DividerFilter.hpp
diff --git a/filters/EigenvaluesFilter.cpp b/filters/EigenvaluesFilter.cpp
new file mode 100644
index 0000000..aea169b
--- /dev/null
+++ b/filters/EigenvaluesFilter.cpp
@@ -0,0 +1,102 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "EigenvaluesFilter.hpp"
+
+#include <pdal/EigenUtils.hpp>
+#include <pdal/KDIndex.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <Eigen/Dense>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.eigenvalues", "Eigenvalues Filter",
+               "http://pdal.io/stages/filters.eigenvalues.html");
+
+CREATE_STATIC_PLUGIN(1, 0, EigenvaluesFilter, Filter, s_info)
+
+std::string EigenvaluesFilter::getName() const
+{
+    return s_info.name;
+}
+
+
+void EigenvaluesFilter::addArgs(ProgramArgs& args)
+{
+    args.add("knn", "k-Nearest neighbors", m_knn, 8);
+}
+
+
+void EigenvaluesFilter::addDimensions(PointLayoutPtr layout)
+{
+    m_e0 = layout->registerOrAssignDim("Eigenvalue0", Dimension::Type::Double);
+    m_e1 = layout->registerOrAssignDim("Eigenvalue1", Dimension::Type::Double);
+    m_e2 = layout->registerOrAssignDim("Eigenvalue2", Dimension::Type::Double);
+}
+
+void EigenvaluesFilter::filter(PointView& view)
+{
+    using namespace Eigen;
+
+    KD3Index kdi(view);
+    kdi.build();
+
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        // find the k-nearest neighbors
+        auto ids = kdi.neighbors(i, m_knn);
+
+        // compute covariance of the neighborhood
+        auto B = eigen::computeCovariance(view, ids);
+
+        // perform the eigen decomposition
+        SelfAdjointEigenSolver<Matrix3f> solver(B);
+        if (solver.info() != Success)
+            throw pdal_error("Cannot perform eigen decomposition.");
+        auto ev = solver.eigenvalues();
+
+        view.setField(m_e0, i, ev[0]);
+        view.setField(m_e1, i, ev[1]);
+        view.setField(m_e2, i, ev[2]);
+    }
+}
+
+} // namespace pdal
diff --git a/filters/eigenvalues/EigenvaluesFilter.hpp b/filters/EigenvaluesFilter.hpp
similarity index 100%
rename from filters/eigenvalues/EigenvaluesFilter.hpp
rename to filters/EigenvaluesFilter.hpp
diff --git a/filters/EstimateRankFilter.cpp b/filters/EstimateRankFilter.cpp
new file mode 100644
index 0000000..fe897a8
--- /dev/null
+++ b/filters/EstimateRankFilter.cpp
@@ -0,0 +1,86 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "EstimateRankFilter.hpp"
+
+#include <pdal/EigenUtils.hpp>
+#include <pdal/KDIndex.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.estimaterank", "EstimateRank Filter", 
+               "http://pdal.io/stages/filters.estimaterank.html");
+
+CREATE_STATIC_PLUGIN(1, 0, EstimateRankFilter, Filter, s_info)
+
+std::string EstimateRankFilter::getName() const
+{
+    return s_info.name;
+}
+
+
+void EstimateRankFilter::addArgs(ProgramArgs& args)
+{
+    args.add("knn", "k-Nearest Neighbors", m_knn, 8);
+    args.add("thresh", "Threshold", m_thresh, 0.01);
+}
+
+
+void EstimateRankFilter::addDimensions(PointLayoutPtr layout)
+{
+    m_rank = layout->registerOrAssignDim("Rank", Dimension::Type::Unsigned8);
+}
+
+void EstimateRankFilter::filter(PointView& view)
+{
+    KD3Index kdi(view);
+    kdi.build();
+
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        // find the k-nearest neighbors
+        auto ids = kdi.neighbors(i, m_knn);
+
+        view.setField(m_rank, i, eigen::computeRank(view, ids, m_thresh));
+    }
+}
+
+} // namespace pdal
diff --git a/filters/estimaterank/EstimateRankFilter.hpp b/filters/EstimateRankFilter.hpp
similarity index 100%
rename from filters/estimaterank/EstimateRankFilter.hpp
rename to filters/EstimateRankFilter.hpp
diff --git a/filters/FerryFilter.cpp b/filters/FerryFilter.cpp
new file mode 100644
index 0000000..3316e2f
--- /dev/null
+++ b/filters/FerryFilter.cpp
@@ -0,0 +1,141 @@
+/******************************************************************************
+* Copyright (c) 2014, Howard Butler, howard at hobu.co
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "FerryFilter.hpp"
+
+#include <pdal/pdal_export.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "filters.ferry",
+    "Copy data from one dimension to another.",
+    "http://pdal.io/stages/filters.ferry.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, FerryFilter, Filter, s_info)
+
+std::string FerryFilter::getName() const { return s_info.name; }
+
+void FerryFilter::addArgs(ProgramArgs& args)
+{
+    args.add("dimensions", "List of dimensions to ferry",
+        m_dimSpec).setPositional();
+}
+
+
+void FerryFilter::initialize()
+{
+    for (auto& dim : m_dimSpec)
+    {
+        StringList s = Utils::split2(dim, '=');
+        if (s.size() != 2)
+        {
+            std::ostringstream oss;
+            oss << "Invalid dimension specified '" << dim <<
+                "'.  Need <from dimension>=<to dimension>.  See "
+                "documentation for details.";
+            throw pdal_error(oss.str());
+        }
+        Utils::trim(s[0]);
+        Utils::trim(s[1]);
+        if (s[0] == s[1])
+        {
+            std::ostringstream oss;
+            oss << "Can't ferry dimension '" << s[0] << "' to itself.";
+            throw pdal_error(oss.str());
+        }
+        m_name_map[s[0]] = s[1];
+    }
+}
+
+
+void FerryFilter::addDimensions(PointLayoutPtr layout)
+{
+    for (const auto& dim_par : m_name_map)
+    {
+        layout->registerOrAssignDim(dim_par.second, Dimension::Type::Double);
+    }
+}
+
+
+void FerryFilter::prepared(PointTableRef table)
+{
+    for (const auto& dims : m_name_map)
+        if (table.layout()->findDim(dims.first) == Dimension::Id::Unknown)
+        {
+            std::ostringstream oss;
+            oss << "Can't ferry dimension '" << dims.first << "'. "
+                "Dimension doesn't exist.";
+            throw pdal_error(oss.str());
+        }
+}
+
+void FerryFilter::ready(PointTableRef table)
+{
+    const PointLayoutPtr layout(table.layout());
+    for (const auto& dim_par : m_name_map)
+    {
+        Dimension::Id f = layout->findDim(dim_par.first);
+        Dimension::Id t = layout->findDim(dim_par.second);
+        m_dimensions_map.insert(std::make_pair(f,t));
+    }
+}
+
+
+bool FerryFilter::processOne(PointRef& point)
+{
+    for (const auto& dim_par : m_dimensions_map)
+    {
+        double v = point.getFieldAs<double>(dim_par.first);
+        point.setField(dim_par.second, v);
+    }
+    return true;
+}
+
+
+void FerryFilter::filter(PointView& view)
+{
+    PointRef point(view, 0);
+    for (PointId id = 0; id < view.size(); ++id)
+    {
+        point.setPointId(id);
+        processOne(point);
+    }
+}
+
+} // namespace pdal
+
diff --git a/filters/ferry/FerryFilter.hpp b/filters/FerryFilter.hpp
similarity index 100%
rename from filters/ferry/FerryFilter.hpp
rename to filters/FerryFilter.hpp
diff --git a/filters/HAGFilter.cpp b/filters/HAGFilter.cpp
new file mode 100644
index 0000000..64cf7fa
--- /dev/null
+++ b/filters/HAGFilter.cpp
@@ -0,0 +1,116 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "HAGFilter.hpp"
+
+#include <pdal/KDIndex.hpp>
+#include <pdal/pdal_macros.hpp>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.hag", "HAG Filter",
+               "http://pdal.io/stages/filters.hag.html");
+
+CREATE_STATIC_PLUGIN(1, 0, HAGFilter, Filter, s_info)
+
+std::string HAGFilter::getName() const
+{
+    return s_info.name;
+}
+
+void HAGFilter::addDimensions(PointLayoutPtr layout)
+{
+    layout->registerDim(Dimension::Id::HeightAboveGround);
+}
+
+void HAGFilter::prepared(PointTableRef table)
+{
+    const PointLayoutPtr layout(table.layout());
+    if (!layout->hasDim(Dimension::Id::Classification))
+        throw pdal_error("HAGFilter: missing Classification dimension in input PointView");
+}
+
+void HAGFilter::filter(PointView& view)
+{
+    PointViewPtr gView = view.makeNew();
+    PointViewPtr ngView = view.makeNew();
+    std::vector<PointId> gIdx, ngIdx;
+
+    // First pass: Separate into ground and non-ground views.
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        double c = view.getFieldAs<double>(Dimension::Id::Classification, i);
+        if (c == 2)
+        {
+            gView->appendPoint(view, i);
+            gIdx.push_back(i);
+        }
+        else
+        {
+            ngView->appendPoint(view, i);
+            ngIdx.push_back(i);
+        }
+    }
+
+    // Bail if there weren't any points classified as ground.
+    if (gView->size() == 0)
+        throw pdal_error("HAGFilter: the input PointView does not appear to have any points classified as ground");
+
+    // Build the 2D KD-tree.
+    KD2Index kdi(*gView);
+    kdi.build();
+
+    // Second pass: Find Z difference between non-ground points and the nearest 
+    // neighbor (2D) in the ground view.
+    for (PointId i = 0; i < ngView->size(); ++i)
+    {
+        PointRef point = ngView->point(i);
+        double z0 = point.getFieldAs<double>(Dimension::Id::Z);
+        auto ids = kdi.neighbors(point, 1);
+        double z1 = gView->getFieldAs<double>(Dimension::Id::Z, ids[0]);
+        view.setField(Dimension::Id::HeightAboveGround, ngIdx[i], z0 - z1);
+    }
+
+    // Final pass: Ensure that all ground points have height value pegged at 0.
+    for (auto const& i : gIdx)
+        view.setField(Dimension::Id::HeightAboveGround, i, 0.0);
+}
+
+
+} // namespace pdal
diff --git a/filters/hag/HAGFilter.hpp b/filters/HAGFilter.hpp
similarity index 100%
rename from filters/hag/HAGFilter.hpp
rename to filters/HAGFilter.hpp
diff --git a/filters/IQRFilter.cpp b/filters/IQRFilter.cpp
new file mode 100644
index 0000000..de47a24
--- /dev/null
+++ b/filters/IQRFilter.cpp
@@ -0,0 +1,123 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "IQRFilter.hpp"
+
+#include <pdal/pdal_macros.hpp>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.iqr", "Interquartile Range Filter",
+               "http://pdal.io/stages/filters.iqr.html");
+
+CREATE_STATIC_PLUGIN(1, 0, IQRFilter, Filter, s_info)
+
+std::string IQRFilter::getName() const
+{
+    return s_info.name;
+}
+
+void IQRFilter::addArgs(ProgramArgs& args)
+{
+    args.add("k", "Number of deviations", m_multiplier, 1.5);
+    args.add("dimension", "Dimension on which to calculate statistics",
+        m_dimName);
+}
+
+void IQRFilter::prepared(PointTableRef table)
+{
+    PointLayoutPtr layout(table.layout());
+    m_dimId = layout->findDim(m_dimName);
+    if (m_dimId == Dimension::Id::Unknown)
+    {
+        std::ostringstream oss;
+        oss << "Invalid dimension name in filters.iqr 'dimension' "
+            "option: '" << m_dimName << "'.";
+        throw pdal_error(oss.str());
+    }
+}
+
+PointViewSet IQRFilter::run(PointViewPtr view)
+{
+    using namespace Dimension;
+
+    PointViewSet viewSet;
+    PointViewPtr output = view->makeNew();
+
+    auto quartile = [](std::vector<double> vals, double percent)
+    {
+        std::nth_element(vals.begin(), vals.begin()+int(vals.size()*percent), vals.end());
+
+        return *(vals.begin()+int(vals.size()*percent));
+    };
+
+    std::vector<double> z(view->size());
+    for (PointId j = 0; j < view->size(); ++j)
+        z[j] = view->getFieldAs<double>(m_dimId, j);
+    
+    
+    double pc25 = quartile(z, 0.25);
+    log()->get(LogLevel::Debug) << "25th percentile: " << pc25 << std::endl;
+
+    double pc75 = quartile(z, 0.75);
+    log()->get(LogLevel::Debug) << "75th percentile: " << pc75 << std::endl;
+    
+    double iqr = pc75-pc25;
+    log()->get(LogLevel::Debug) << "IQR: " << iqr << std::endl;
+    
+    double low_fence = pc25 - m_multiplier * iqr;
+    double hi_fence = pc75 + m_multiplier * iqr;
+    
+    for (PointId j = 0; j < view->size(); ++j)
+    {
+        double val = view->getFieldAs<double>(m_dimId, j);
+        if (val > low_fence && val < hi_fence)
+            output->appendPoint(*view, j);
+    }
+    log()->get(LogLevel::Debug) << "Cropping " << m_dimName
+                                << " in the range (" << low_fence
+                                << "," << hi_fence << ")" << std::endl;
+
+    viewSet.erase(view);
+    viewSet.insert(output);
+
+    return viewSet;
+}
+
+} // namespace pdal
diff --git a/filters/IQRFilter.hpp b/filters/IQRFilter.hpp
new file mode 100644
index 0000000..03e9c54
--- /dev/null
+++ b/filters/IQRFilter.hpp
@@ -0,0 +1,75 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+#include <string>
+
+extern "C" int32_t IQRFilter_ExitFunc();
+extern "C" PF_ExitFunc IQRFilter_InitPlugin();
+
+namespace pdal
+{
+
+class ProgramArgs;
+class PointTable;
+class PointView;
+
+class PDAL_DLL IQRFilter : public Filter
+{
+public:
+    IQRFilter() : Filter()
+    {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    double m_multiplier;
+    std::string m_dimName;
+    Dimension::Id m_dimId;
+
+    virtual void addArgs(ProgramArgs& args);
+    virtual void prepared(PointTableRef table);
+    virtual PointViewSet run(PointViewPtr view);
+
+    IQRFilter& operator=(const IQRFilter&); // not implemented
+    IQRFilter(const IQRFilter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/filters/KDistanceFilter.cpp b/filters/KDistanceFilter.cpp
new file mode 100644
index 0000000..c8581a1
--- /dev/null
+++ b/filters/KDistanceFilter.cpp
@@ -0,0 +1,93 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "KDistanceFilter.hpp"
+
+#include <pdal/KDIndex.hpp>
+#include <pdal/pdal_macros.hpp>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.kdistance", "K-Distance Filter",
+               "http://pdal.io/stages/filters.kdistance.html");
+
+CREATE_STATIC_PLUGIN(1, 0, KDistanceFilter, Filter, s_info)
+
+std::string KDistanceFilter::getName() const
+{
+    return s_info.name;
+}
+
+void KDistanceFilter::addArgs(ProgramArgs& args)
+{
+    args.add("k", "k neighbors", m_k, 10);
+}
+
+void KDistanceFilter::addDimensions(PointLayoutPtr layout)
+{
+    using namespace Dimension;
+    m_kdist = layout->registerOrAssignDim("KDistance", Type::Double);
+}
+
+void KDistanceFilter::filter(PointView& view)
+{
+    using namespace Dimension;
+    
+    // Build the 3D KD-tree.
+    log()->get(LogLevel::Debug) << "Building 3D KD-tree...\n";
+    KD3Index index(view);
+    index.build();
+    
+    // Increment the minimum number of points, as knnSearch will be returning
+    // the neighbors along with the query point.
+    m_k++;
+  
+    // Compute the k-distance for each point. The k-distance is the Euclidean
+    // distance to k-th nearest neighbor.
+    log()->get(LogLevel::Debug) << "Computing k-distances...\n";
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        std::vector<PointId> indices(m_k);
+        std::vector<double> sqr_dists(m_k);
+        index.knnSearch(i, m_k, &indices, &sqr_dists);
+        view.setField(m_kdist, i, std::sqrt(sqr_dists[m_k-1]));
+    }
+}
+
+} // namespace pdal
diff --git a/filters/KDistanceFilter.hpp b/filters/KDistanceFilter.hpp
new file mode 100644
index 0000000..12f4bec
--- /dev/null
+++ b/filters/KDistanceFilter.hpp
@@ -0,0 +1,74 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+#include <memory>
+
+extern "C" int32_t KDistanceFilter_ExitFunc();
+extern "C" PF_ExitFunc KDistanceFilter_InitPlugin();
+
+namespace pdal
+{
+
+class PointLayout;
+class PointView;
+class ProgramArgs;
+
+class PDAL_DLL KDistanceFilter : public Filter
+{
+public:
+    KDistanceFilter() : Filter()
+    {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    Dimension::Id m_kdist;
+    int m_k;
+    
+    virtual void addArgs(ProgramArgs& args);
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual void filter(PointView& view);
+
+    KDistanceFilter& operator=(const KDistanceFilter&); // not implemented
+    KDistanceFilter(const KDistanceFilter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/filters/LOFFilter.cpp b/filters/LOFFilter.cpp
new file mode 100644
index 0000000..6e1535d
--- /dev/null
+++ b/filters/LOFFilter.cpp
@@ -0,0 +1,135 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "LOFFilter.hpp"
+
+#include <pdal/KDIndex.hpp>
+#include <pdal/pdal_macros.hpp>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.lof", "LOF Filter",
+               "http://pdal.io/stages/filters.lof.html");
+
+CREATE_STATIC_PLUGIN(1, 0, LOFFilter, Filter, s_info)
+
+std::string LOFFilter::getName() const
+{
+    return s_info.name;
+}
+
+void LOFFilter::addArgs(ProgramArgs& args)
+{
+    args.add("minpts", "Minimum number of points", m_minpts, 10);
+}
+
+void LOFFilter::addDimensions(PointLayoutPtr layout)
+{
+    using namespace Dimension;
+    m_kdist = layout->registerOrAssignDim("KDistance", Type::Double);
+    m_lrd = layout->registerOrAssignDim("LocalReachabilityDistance", Type::Double);
+    m_lof = layout->registerOrAssignDim("LocalOutlierFactor", Type::Double);
+}
+
+void LOFFilter::filter(PointView& view)
+{
+    using namespace Dimension;
+    
+    // Build the 3D KD-tree.
+    KD3Index index(view);
+    log()->get(LogLevel::Debug) << "Building 3D KD-tree...\n";
+    index.build();
+    
+    // Increment the minimum number of points, as knnSearch will be returning
+    // the neighbors along with the query point.
+    m_minpts++;
+  
+    // First pass: Compute the k-distance for each point.
+    // The k-distance is the Euclidean distance to k-th nearest neighbor.
+    log()->get(LogLevel::Debug) << "Computing k-distances...\n";
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        std::vector<PointId> indices(m_minpts);
+        std::vector<double> sqr_dists(m_minpts);
+        index.knnSearch(i, m_minpts, &indices, &sqr_dists);
+        view.setField(m_kdist, i, std::sqrt(sqr_dists[m_minpts-1]));
+    }
+    
+    // Second pass: Compute the local reachability distance for each point.
+    // For each neighbor point, the reachability distance is the maximum value
+    // of that neighbor's k-distance and the distance between the neighbor and
+    // the current point. The lrd is the inverse of the mean of the reachability
+    // distances.
+    log()->get(LogLevel::Debug) << "Computing lrd...\n";
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        std::vector<PointId> indices(m_minpts);
+        std::vector<double> sqr_dists(m_minpts);
+        index.knnSearch(i, m_minpts, &indices, &sqr_dists);
+        double M1 = 0.0;
+        point_count_t n = 0;
+        for (PointId j = 0; j < indices.size(); ++j)
+        {
+            double k = view.getFieldAs<double>(m_kdist, indices[j]);
+            double reachdist = std::max(k, std::sqrt(sqr_dists[j]));
+            M1 += (reachdist - M1) / ++n;
+        }
+        view.setField(m_lrd, i, 1.0 / M1);
+    }
+    
+    // Third pass: Compute the local outlier factor for each point.
+    // The LOF is the average of the lrd's for a neighborhood of points.
+    log()->get(LogLevel::Debug) << "Computing LOF...\n";
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        double lrdp = view.getFieldAs<double>(m_lrd, i);
+        std::vector<PointId> indices(m_minpts);
+        std::vector<double> sqr_dists(m_minpts);
+        index.knnSearch(i, m_minpts, &indices, &sqr_dists);
+        double M1 = 0.0;
+        point_count_t n = 0;
+        for (auto const& j : indices)
+        {
+            M1 += (view.getFieldAs<double>(m_lrd, j) / lrdp - M1) / ++n;
+        }
+        view.setField(m_lof, i, M1);
+    }
+}
+
+} // namespace pdal
diff --git a/filters/LOFFilter.hpp b/filters/LOFFilter.hpp
new file mode 100644
index 0000000..6260c7d
--- /dev/null
+++ b/filters/LOFFilter.hpp
@@ -0,0 +1,74 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+#include <memory>
+
+extern "C" int32_t LOFFilter_ExitFunc();
+extern "C" PF_ExitFunc LOFFilter_InitPlugin();
+
+namespace pdal
+{
+
+class PointLayout;
+class PointView;
+class ProgramArgs;
+
+class PDAL_DLL LOFFilter : public Filter
+{
+public:
+    LOFFilter() : Filter()
+    {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    Dimension::Id m_kdist, m_lrd, m_lof;
+    int m_minpts;
+    
+    virtual void addArgs(ProgramArgs& args);
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual void filter(PointView& view);
+
+    LOFFilter& operator=(const LOFFilter&); // not implemented
+    LOFFilter(const LOFFilter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/filters/MADFilter.cpp b/filters/MADFilter.cpp
new file mode 100644
index 0000000..8cc6fb4
--- /dev/null
+++ b/filters/MADFilter.cpp
@@ -0,0 +1,121 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "MADFilter.hpp"
+
+#include <pdal/pdal_macros.hpp>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.mad", "Median Absolute Deviation Filter",
+               "http://pdal.io/stages/filters.mad.html");
+
+CREATE_STATIC_PLUGIN(1, 0, MADFilter, Filter, s_info)
+
+std::string MADFilter::getName() const
+{
+    return s_info.name;
+}
+
+void MADFilter::addArgs(ProgramArgs& args)
+{
+    args.add("k", "Number of deviations", m_multiplier, 2.0);
+    args.add("dimension", "Dimension on which to calculate statistics",
+        m_dimName);
+    args.add("mad_multiplier", "MAD threshold multiplier", m_madMultiplier, 1.4862);
+}
+
+void MADFilter::prepared(PointTableRef table)
+{
+    PointLayoutPtr layout(table.layout());
+    m_dimId = layout->findDim(m_dimName);
+    if (m_dimId == Dimension::Id::Unknown)
+    {
+        std::ostringstream oss;
+        oss << "Invalid dimension name in filters.mad 'dimension' "
+            "option: '" << m_dimName << "'.";
+        throw pdal_error(oss.str());
+    }
+}
+
+PointViewSet MADFilter::run(PointViewPtr view)
+{
+    using namespace Dimension;
+
+    PointViewSet viewSet;
+    PointViewPtr output = view->makeNew();
+
+    auto estimate_median = [](std::vector<double> vals)
+    {
+        std::nth_element(vals.begin(), vals.begin()+vals.size()/2, vals.end());
+        return *(vals.begin()+vals.size()/2);
+    };
+
+    std::vector<double> z(view->size());
+    for (PointId j = 0; j < view->size(); ++j)
+        z[j] = view->getFieldAs<double>(m_dimId, j);
+
+    double median = estimate_median(z);
+    log()->get(LogLevel::Debug) << getName() << " estimated median value: " << median << std::endl;
+
+    std::transform(z.begin(), z.end(), z.begin(),
+       [median](double v) { return std::fabs(v - median); });
+    double mad = estimate_median(z)*m_madMultiplier;
+    log()->get(LogLevel::Debug) << getName() << " mad " << mad << std::endl;
+
+    for (PointId j = 0; j < view->size(); ++j)
+    {
+        if (z[j]/mad < m_multiplier)
+            output->appendPoint(*view, j);
+    }
+
+    double low_fence = median - m_multiplier * mad;
+    double hi_fence = median + m_multiplier * mad;
+
+    log()->get(LogLevel::Debug) << getName() << " cropping " << m_dimName
+                                << " in the range (" << low_fence
+                                << "," << hi_fence << ")" << std::endl;
+
+    viewSet.erase(view);
+    viewSet.insert(output);
+
+    return viewSet;
+}
+
+} // namespace pdal
diff --git a/filters/MADFilter.hpp b/filters/MADFilter.hpp
new file mode 100644
index 0000000..6f3fd0f
--- /dev/null
+++ b/filters/MADFilter.hpp
@@ -0,0 +1,76 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+#include <string>
+
+extern "C" int32_t MADFilter_ExitFunc();
+extern "C" PF_ExitFunc MADFilter_InitPlugin();
+
+namespace pdal
+{
+
+class ProgramArgs;
+class PointTable;
+class PointView;
+
+class PDAL_DLL MADFilter : public Filter
+{
+public:
+    MADFilter() : Filter()
+    {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    double m_multiplier;
+    std::string m_dimName;
+    Dimension::Id m_dimId;
+    double m_madMultiplier;
+
+    virtual void addArgs(ProgramArgs& args);
+    virtual void prepared(PointTableRef table);
+    virtual PointViewSet run(PointViewPtr view);
+
+    MADFilter& operator=(const MADFilter&); // not implemented
+    MADFilter(const MADFilter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/filters/merge/MergeFilter.cpp b/filters/MergeFilter.cpp
similarity index 100%
rename from filters/merge/MergeFilter.cpp
rename to filters/MergeFilter.cpp
diff --git a/filters/merge/MergeFilter.hpp b/filters/MergeFilter.hpp
similarity index 100%
rename from filters/merge/MergeFilter.hpp
rename to filters/MergeFilter.hpp
diff --git a/filters/MongusFilter.cpp b/filters/MongusFilter.cpp
new file mode 100644
index 0000000..e64f900
--- /dev/null
+++ b/filters/MongusFilter.cpp
@@ -0,0 +1,555 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "MongusFilter.hpp"
+
+#include <pdal/pdal_macros.hpp>
+#include <pdal/EigenUtils.hpp>
+#include <pdal/PipelineManager.hpp>
+#include <pdal/SpatialReference.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+#include <pdal/util/Utils.hpp>
+#include <io/BufferReader.hpp>
+
+#include <Eigen/Dense>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.mongus", "Mongus and Zalik (2012)",
+               "http://pdal.io/stages/filters.mongus.html");
+
+CREATE_STATIC_PLUGIN(1, 0, MongusFilter, Filter, s_info)
+
+std::string MongusFilter::getName() const
+{
+    return s_info.name;
+}
+
+void MongusFilter::addArgs(ProgramArgs& args)
+{
+    args.add("cell", "Cell size", m_cellSize, 1.0);
+    args.add("k", "Stdev multiplier for threshold", m_k, 3.0);
+    args.add("l", "Max level", m_l, 8);
+    args.add("classify", "Apply classification labels?", m_classify, true);
+    args.add("extract", "Extract ground returns?", m_extract);
+}
+
+void MongusFilter::addDimensions(PointLayoutPtr layout)
+{
+    layout->registerDim(Dimension::Id::Classification);
+}
+
+int MongusFilter::getColIndex(double x, double cell_size)
+{
+    return static_cast<int>(floor((x - m_bounds.minx) / cell_size));
+}
+
+int MongusFilter::getRowIndex(double y, double cell_size)
+{
+    return static_cast<int>(floor((m_maxRow - y) / cell_size));
+}
+
+void MongusFilter::writeControl(Eigen::MatrixXd cx, Eigen::MatrixXd cy, Eigen::MatrixXd cz, std::string filename)
+{
+    using namespace Dimension;
+
+    PipelineManager m;
+
+    PointTable table;
+    PointViewPtr view(new PointView(table));
+
+    table.layout()->registerDim(Id::X);
+    table.layout()->registerDim(Id::Y);
+    table.layout()->registerDim(Id::Z);
+
+    PointId i = 0;
+    for (auto j = 0; j < cz.size(); ++j)
+    {
+        if (std::isnan(cx(j)) || std::isnan(cy(j)) || std::isnan(cz(j)))
+            continue;
+        view->setField(Id::X, i, cx(j));
+        view->setField(Id::Y, i, cy(j));
+        view->setField(Id::Z, i, cz(j));
+        i++;
+    }
+
+    BufferReader r;
+    r.addView(view);
+
+    Stage& w = m.makeWriter(filename, "writers.las", r);
+    w.prepare(table);
+    w.execute(table);
+}
+
+std::vector<PointId> MongusFilter::processGround(PointViewPtr view)
+{
+    using namespace Eigen;
+
+    point_count_t np(view->size());
+
+    std::vector<PointId> groundIdx;
+
+    // initialization
+
+    view->calculateBounds(m_bounds);
+    SpatialReference srs(view->spatialReference());
+
+    m_numCols =
+        static_cast<int>(ceil((m_bounds.maxx - m_bounds.minx)/m_cellSize)) + 1;
+    m_numRows =
+        static_cast<int>(ceil((m_bounds.maxy - m_bounds.miny)/m_cellSize)) + 1;
+    m_maxRow = m_bounds.miny + m_numRows * m_cellSize;
+
+    // create control points matrix at default cell size
+    MatrixXd cx(m_numRows, m_numCols);
+    cx.setConstant(std::numeric_limits<double>::quiet_NaN());
+
+    MatrixXd cy(m_numRows, m_numCols);
+    cy.setConstant(std::numeric_limits<double>::quiet_NaN());
+
+    MatrixXd cz(m_numRows, m_numCols);
+    cz.setConstant(std::numeric_limits<double>::max());
+
+    // find initial set of Z minimums at native resolution
+    for (point_count_t i = 0; i < np; ++i)
+    {
+        using namespace Dimension;
+        double x = view->getFieldAs<double>(Id::X, i);
+        double y = view->getFieldAs<double>(Id::Y, i);
+        double z = view->getFieldAs<double>(Id::Z, i);
+
+        int c = Utils::clamp(getColIndex(x, m_cellSize), 0, m_numCols-1);
+        int r = Utils::clamp(getRowIndex(y, m_cellSize), 0, m_numRows-1);
+
+        if (z < cz(r, c))
+        {
+            cx(r, c) = x;
+            cy(r, c) = y;
+            cz(r, c) = z;
+        }
+    }
+
+    writeControl(cx, cy, cz, "grid_mins.laz");
+
+    // In our case, 2D structural elements of circular shape are employed and
+    // sufficient accuracy is achieved by using a larger window size for opening
+    // (W11) than for closing (W9).
+    MatrixXd mo = eigen::matrixOpen(cz, 11);
+    writeControl(cx, cy, mo, "grid_open.laz");
+    MatrixXd mc = eigen::matrixClose(mo, 9);
+    writeControl(cx, cy, mc, "grid_close.laz");
+
+    // ...in order to minimize the distortions caused by such filtering, the
+    // output points ... are compared to C and only ci with significantly lower
+    // elevation [are] replaced... In our case, d = 1.0 m was used.
+    for (auto i = 0; i < cz.size(); ++i)
+    {
+        if ((mc(i) - cz(i)) >= 1.0)
+            cz(i) = mc(i);
+    }
+    // cz is still at native resolution, with low points replaced by morphological operators
+    writeControl(cx, cy, cz, "grid_mins_adjusted.laz");
+
+    // downsample control at max_level
+    int level = m_l;
+    double cur_cell_size = m_cellSize * std::pow(2, level);
+    // for max level = 8 and cell size 1, this is 256
+
+    MatrixXd x_prev, y_prev, z_prev;
+
+    // Top-level control samples are assumed to be ground points, no filtering
+    // is applied.
+    downsampleMin(&cx, &cy, &cz, &x_prev, &y_prev, &z_prev, cur_cell_size);
+    // x|y|z_prev are control points downsampled to coarsest resolution for the hierarchy, e.g., for 512x512, this would be 2x2
+    writeControl(x_prev, y_prev, z_prev, "control_init.laz");
+
+    // Point-filtering is performed iteratively at each level of the
+    // control-points hierarchy in a top-down fashion
+    for (auto l = level-1; l > 0; --l)
+    {
+        std::cerr << "Level " << l << std::endl;
+        cur_cell_size /= 2;
+        // 128, 64, 32, 16, 8, 4, 1
+
+        // compute TPS with update control at level
+
+        // The interpolated surface is estimated based on the filtered set of
+        // TPS control-points at the previous level of hierarchy
+        // MatrixXd surface = TPS(x_prev, y_prev, z_prev, cur_cell_size);
+        // 4x4, 8x8, 16x16, 32x32, 64x64, 128x128, 256x256
+
+        // downsample control at level
+        MatrixXd x_samp, y_samp, z_samp;
+        downsampleMin(&cx, &cy, &cz, &x_samp, &y_samp, &z_samp, cur_cell_size);
+        // 4x4, 8x8, 16x16, 32x32, 64x64, 128x128, 256x256
+
+        MatrixXd surface = eigen::computeSpline(x_prev, y_prev, z_prev, x_samp, y_samp);
+
+        // if (l == 3)
+        // {
+        //     log()->get(LogLevel::Debug) << cx.rows() << "\t" << cx.cols() << std::endl;
+        //     log()->get(LogLevel::Debug) << x_prev.rows() << "\t" << x_prev.cols() << std::endl;
+        //     log()->get(LogLevel::Debug) << x_samp.rows() << "\t" << x_samp.cols() << std::endl;
+        //     log()->get(LogLevel::Debug) << surface.rows() << "\t" << surface.cols() << std::endl;
+        //     log()->get(LogLevel::Debug) << "x: " << cx.row(1) << std::endl;
+        //     log()->get(LogLevel::Debug) << "z: " << cz.row(1) << std::endl;
+        //     log()->get(LogLevel::Debug) << "control_x: " << x_prev.row(0) << std::endl;
+        //     log()->get(LogLevel::Debug) << "control_z: " << z_prev.row(0) << std::endl;
+        //     log()->get(LogLevel::Debug) << "samples_x: " << x_samp.row(0) << std::endl;
+        //     log()->get(LogLevel::Debug) << "samples_z: " << z_samp.row(0) << std::endl;
+        //     log()->get(LogLevel::Debug) << "spline: " << surface.row(0) << std::endl;
+        // }
+
+        char bufs[256];
+        sprintf(bufs, "cur_control_%d.laz", l);
+        std::string names(bufs);
+        writeControl(x_samp, y_samp, z_samp, names);
+
+        MatrixXd R = z_samp - surface;
+
+        if (l == 7)
+            log()->get(LogLevel::Debug) << R << std::endl;
+
+        double sum = 0.0;
+        double maxcoeff = std::numeric_limits<double>::lowest();
+        double mincoeff = std::numeric_limits<double>::max();
+        for (auto i = 0; i < R.size(); ++i)
+        {
+            if (std::isnan(R(i)))
+                continue;
+            if (R(i) > maxcoeff)
+                maxcoeff = R(i);
+            if (R(i) < mincoeff)
+                mincoeff = R(i);
+            sum += R(i);
+        }
+
+        log()->get(LogLevel::Debug) << "R: max=" << maxcoeff
+                                    << "; min=" << mincoeff
+                                    << "; sum=" << sum
+                                    << "; size=" << R.size() << std::endl;
+
+        // median takes an unsorted vector, possibly containing NANs, and
+        // returns the median value.
+        auto median = [&](std::vector<double> vals)
+        {
+            // Begin by partitioning the vector by isnan.
+            auto ptr = std::partition(vals.begin(), vals.end(), [](double p)
+            {
+                return std::isnan(p);
+            });
+
+            // Copy the actual values, thus eliminating NANs, and sort it.
+            std::vector<double> cp(ptr, vals.end());
+            std::sort(cp.begin(), cp.end());
+
+            std::cerr << "median troubleshooting\n";
+            std::cerr << vals.size() << "\t" << cp.size() << std::endl;
+            std::cerr << cp.size() % 2 << std::endl;
+            std::cerr << cp[cp.size()/2-1] << "\t" << cp[cp.size()/2] << std::endl;
+            if (l == 7)
+            {
+                for (auto const& v : cp)
+                    std::cerr << v << ", ";
+                std::cerr << std::endl;
+            }
+
+            // Compute the median value. For even sized vectors, this is the
+            // average of the midpoints, otherwise it is the midpoint.
+            double median = 0.0;
+            if (cp.size() % 2 == 0)
+                median = (cp[cp.size()/2-1]+cp[cp.size()/2])/2;
+            else
+                median = cp[cp.size()/2];
+
+            return median;
+        };
+
+        // Compute median of residuals.
+        std::vector<double> allres(R.data(), R.data()+R.size());
+        double m = median(allres);
+
+        // Compute absolute difference of the residuals from the median.
+        ArrayXd ad = (R.array()-m).abs();
+
+        // Compute median of absolute differences, with scale factor (1.4862)
+        // for a normal distribution.
+        std::vector<double> absdiff(ad.data(), ad.data()+ad.size());
+        double mad = 1.4862 * median(absdiff);
+
+        // Divide absolute differences by MAD. Values greater than 2 are
+        // considered outliers.
+        MatrixXd M = (ad / mad).matrix();
+
+        sum = 0.0;
+        maxcoeff = std::numeric_limits<double>::lowest();
+        mincoeff = std::numeric_limits<double>::max();
+        for (auto i = 0; i < M.size(); ++i)
+        {
+            if (std::isnan(M(i)))
+                continue;
+            if (M(i) > maxcoeff)
+                maxcoeff = M(i);
+            if (M(i) < mincoeff)
+                mincoeff = M(i);
+            sum += M(i);
+        }
+
+        log()->get(LogLevel::Debug) << "M: max=" << maxcoeff
+                                    << "; min=" << mincoeff
+                                    << "; sum=" << sum
+                                    << "; size=" << M.size() << std::endl;
+
+        double madthresh = 2.0;
+        // Just computing the percent outlier FYI.
+        double perc = static_cast<double>((M.array() > madthresh).count());
+        perc /= static_cast<double>(R.size());
+        perc *= 100.0;
+        log()->get(LogLevel::Debug) << "median=" << m
+                                    << "; MAD=" << mad
+                                    << "; " << (M.array() > madthresh).count()
+                                    << " outliers out of " << R.size()
+                                    << " control points (" << perc << "%)\n";
+
+        // If the TPS control-point is recognized as a non-ground point, it is
+        // replaced by the interpolated point. The time complexity of the
+        // approach is reduced by filtering only the control-points in each
+        // iteration.
+        if (l < 3)
+        {
+            for (auto i = 0; i < M.size(); ++i)
+            {
+                if (M(i) > madthresh)
+                    z_samp(i) = std::numeric_limits<double>::quiet_NaN();
+                // z_samp(i) = surface(i);
+            }
+        }
+
+        if (log()->getLevel() > LogLevel::Debug5)
+        {
+            char buffer[256];
+            sprintf(buffer, "interp_surface_%d.laz", l);
+            std::string name(buffer);
+            // eigen::writeMatrix(surface, name, cur_cell_size, m_bounds, srs);
+            writeControl(x_samp, y_samp, surface, name);
+
+            char bufm[256];
+            sprintf(bufm, "master_control_%d.laz", l);
+            std::string namem(bufm);
+            writeControl(cx, cy, cz, namem);
+
+            // this is identical to filtered control when written here - should move it...
+            char buf3[256];
+            sprintf(buf3, "prev_control_%d.laz", l);
+            std::string name3(buf3);
+            writeControl(x_prev, y_prev, z_prev, name3);
+
+            char rbuf[256];
+            sprintf(rbuf, "residual_%d.laz", l);
+            std::string rbufn(rbuf);
+            // eigen::writeMatrix(R, rbufn, cur_cell_size, m_bounds, srs);
+            writeControl(x_samp, y_samp, R, rbufn);
+
+            char mbuf[256];
+            sprintf(mbuf, "median_%d.laz", l);
+            std::string mbufn(mbuf);
+            // eigen::writeMatrix(M, mbufn, cur_cell_size, m_bounds, srs);
+            writeControl(x_samp, y_samp, M, mbufn);
+
+            char buf2[256];
+            sprintf(buf2, "adjusted_control_%d.laz", l);
+            std::string name2(buf2);
+            writeControl(x_samp, y_samp, z_samp, name2);
+        }
+
+        x_prev = x_samp;
+        y_prev = y_samp;
+        z_prev = z_samp;
+    }
+
+    MatrixXd surface = eigen::computeSpline(x_prev, y_prev, z_prev, cx, cy);
+
+    if (log()->getLevel() > LogLevel::Debug5)
+    {
+        //     writeControl(cx, cy, mc, "closed.laz");
+        //
+        char buffer[256];
+        sprintf(buffer, "final_surface.tif");
+        std::string name(buffer);
+        eigen::writeMatrix(surface, name, "GTiff", m_cellSize, m_bounds, srs);
+        //
+        //     char rbuf[256];
+        //     sprintf(rbuf, "final_residual.tif");
+        //     std::string rbufn(rbuf);
+        //     eigen::writeMatrix(R, rbufn, cur_cell_size, m_bounds, srs);
+        //
+        //     char obuf[256];
+        //     sprintf(obuf, "final_opened.tif");
+        //     std::string obufn(obuf);
+        //     eigen::writeMatrix(maxZ, obufn, cur_cell_size, m_bounds, srs);
+        //
+        //     char Tbuf[256];
+        //     sprintf(Tbuf, "final_tophat.tif");
+        //     std::string Tbufn(Tbuf);
+        //     eigen::writeMatrix(T, Tbufn, cur_cell_size, m_bounds, srs);
+        //
+        //     char tbuf[256];
+        //     sprintf(tbuf, "final_thresh.tif");
+        //     std::string tbufn(tbuf);
+        //     eigen::writeMatrix(t, tbufn, cur_cell_size, m_bounds, srs);
+    }
+
+    // apply final filtering (top hat) using raw points against TPS
+
+    // ...the LiDAR points are filtered only at the bottom level.
+    for (point_count_t i = 0; i < np; ++i)
+    {
+        using namespace Dimension;
+
+        double x = view->getFieldAs<double>(Id::X, i);
+        double y = view->getFieldAs<double>(Id::Y, i);
+        double z = view->getFieldAs<double>(Id::Z, i);
+
+        int c = Utils::clamp(getColIndex(x, cur_cell_size), 0, m_numCols-1);
+        int r = Utils::clamp(getRowIndex(y, cur_cell_size), 0, m_numRows-1);
+
+        double res = z - surface(r, c);
+        if (res < 1.0)
+            groundIdx.push_back(i);
+    }
+
+    return groundIdx;
+}
+
+void MongusFilter::downsampleMin(Eigen::MatrixXd *cx, Eigen::MatrixXd *cy,
+                                 Eigen::MatrixXd* cz, Eigen::MatrixXd *dcx,
+                                 Eigen::MatrixXd *dcy, Eigen::MatrixXd* dcz,
+                                 double cell_size)
+{
+    int nr = ceil(cz->rows() / cell_size);
+    int nc = ceil(cz->cols() / cell_size);
+
+    // std::cerr << nr << "\t" << nc << "\t" << cell_size << std::endl;
+
+    dcx->resize(nr, nc);
+    dcx->setConstant(std::numeric_limits<double>::quiet_NaN());
+
+    dcy->resize(nr, nc);
+    dcy->setConstant(std::numeric_limits<double>::quiet_NaN());
+
+    dcz->resize(nr, nc);
+    dcz->setConstant(std::numeric_limits<double>::max());
+
+    for (auto c = 0; c < cz->cols(); ++c)
+    {
+        for (auto r = 0; r < cz->rows(); ++r)
+        {
+            if ((*cz)(r, c) == std::numeric_limits<double>::max())
+                continue;
+
+            int rr = std::floor(r/cell_size);
+            int cc = std::floor(c/cell_size);
+
+            if ((*cz)(r, c) < (*dcz)(rr, cc))
+            {
+                (*dcx)(rr, cc) = (*cx)(r, c);
+                (*dcy)(rr, cc) = (*cy)(r, c);
+                (*dcz)(rr, cc) = (*cz)(r, c);
+            }
+        }
+    }
+}
+
+PointViewSet MongusFilter::run(PointViewPtr view)
+{
+    bool logOutput = log()->getLevel() > LogLevel::Debug1;
+    if (logOutput)
+        log()->floatPrecision(8);
+    log()->get(LogLevel::Debug2) << "Process MongusFilter...\n";
+
+    std::vector<PointId> idx = processGround(view);
+    std::cerr << idx.size() << std::endl;
+
+    PointViewSet viewSet;
+
+    if (!idx.empty() && (m_classify || m_extract))
+    {
+
+        if (m_classify)
+        {
+            log()->get(LogLevel::Debug2) << "Labeled " << idx.size() << " ground returns!\n";
+
+            // set the classification label of ground returns as 2
+            // (corresponding to ASPRS LAS specification)
+            for (const auto& i : idx)
+            {
+                view->setField(Dimension::Id::Classification, i, 2);
+            }
+
+            viewSet.insert(view);
+        }
+
+        if (m_extract)
+        {
+            log()->get(LogLevel::Debug2) << "Extracted " << idx.size() << " ground returns!\n";
+
+            // create new PointView containing only ground returns
+            PointViewPtr output = view->makeNew();
+            for (const auto& i : idx)
+            {
+                output->appendPoint(*view, i);
+            }
+
+            viewSet.erase(view);
+            viewSet.insert(output);
+        }
+    }
+    else
+    {
+        if (idx.empty())
+            log()->get(LogLevel::Debug2) << "Filtered cloud has no ground returns!\n";
+
+        if (!(m_classify || m_extract))
+            log()->get(LogLevel::Debug2) << "Must choose --classify or --extract\n";
+
+        // return the view buffer unchanged
+        viewSet.insert(view);
+    }
+
+    return viewSet;
+}
+
+} // namespace pdal
diff --git a/filters/MongusFilter.hpp b/filters/MongusFilter.hpp
new file mode 100644
index 0000000..dcf9fc5
--- /dev/null
+++ b/filters/MongusFilter.hpp
@@ -0,0 +1,93 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+#include <Eigen/Dense>
+
+#include <memory>
+#include <unordered_map>
+
+extern "C" int32_t MongusFilter_ExitFunc();
+extern "C" PF_ExitFunc MongusFilter_InitPlugin();
+
+namespace pdal
+{
+
+class PointLayout;
+class PointView;
+
+typedef std::unordered_map<int, std::vector<PointId>> PointIdHash;
+
+class PDAL_DLL MongusFilter : public Filter
+{
+public:
+    MongusFilter() : Filter()
+    {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    bool m_classify;
+    bool m_extract;
+    int m_numRows;
+    int m_numCols;
+    int m_maxRow;
+    double m_cellSize;
+    double m_k;
+    int m_l;
+    BOX2D m_bounds;
+
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual void addArgs(ProgramArgs& args);
+    int getColIndex(double x, double cell_size);
+    int getRowIndex(double y, double cell_size);
+    void writeControl(Eigen::MatrixXd cx, Eigen::MatrixXd cy, Eigen::MatrixXd cz, std::string filename);
+    void downsampleMin(Eigen::MatrixXd *cx, Eigen::MatrixXd *cy,
+                       Eigen::MatrixXd* cz, Eigen::MatrixXd *dcx,
+                       Eigen::MatrixXd *dcy, Eigen::MatrixXd* dcz,
+                       double cell_size);
+    std::vector<PointId> processGround(PointViewPtr view);
+    virtual PointViewSet run(PointViewPtr view);
+
+    MongusFilter& operator=(const MongusFilter&); // not implemented
+    MongusFilter(const MongusFilter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/filters/mortonorder/MortonOrderFilter.cpp b/filters/MortonOrderFilter.cpp
similarity index 100%
rename from filters/mortonorder/MortonOrderFilter.cpp
rename to filters/MortonOrderFilter.cpp
diff --git a/filters/mortonorder/MortonOrderFilter.hpp b/filters/MortonOrderFilter.hpp
similarity index 100%
rename from filters/mortonorder/MortonOrderFilter.hpp
rename to filters/MortonOrderFilter.hpp
diff --git a/filters/NormalFilter.cpp b/filters/NormalFilter.cpp
new file mode 100644
index 0000000..e31459f
--- /dev/null
+++ b/filters/NormalFilter.cpp
@@ -0,0 +1,110 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "NormalFilter.hpp"
+
+#include <pdal/EigenUtils.hpp>
+#include <pdal/KDIndex.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <Eigen/Dense>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.normal", "Normal Filter", 
+               "http://pdal.io/stages/filters.normal.html");
+
+CREATE_STATIC_PLUGIN(1, 0, NormalFilter, Filter, s_info)
+
+std::string NormalFilter::getName() const
+{
+    return s_info.name;
+}
+
+
+void NormalFilter::addArgs(ProgramArgs& args)
+{
+    args.add("knn", "k-Nearest Neighbors", m_knn, 8);
+}
+
+
+void NormalFilter::addDimensions(PointLayoutPtr layout)
+{
+    m_nx = layout->registerOrAssignDim("NormalX", Dimension::Type::Double);
+    m_ny = layout->registerOrAssignDim("NormalY", Dimension::Type::Double);
+    m_nz = layout->registerOrAssignDim("NormalZ", Dimension::Type::Double);
+    m_curvature = layout->registerOrAssignDim("Curvature", Dimension::Type::Double);
+}
+
+void NormalFilter::filter(PointView& view)
+{
+    using namespace Eigen;
+
+    KD3Index kdi(view);
+    kdi.build();
+
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        // find the k-nearest neighbors
+        auto ids = kdi.neighbors(i, m_knn);
+
+        // compute covariance of the neighborhood
+        auto B = eigen::computeCovariance(view, ids);
+
+        // perform the eigen decomposition
+        SelfAdjointEigenSolver<Matrix3f> solver(B);
+        if (solver.info() != Success)
+            throw pdal_error("Cannot perform eigen decomposition.");
+        auto eval = solver.eigenvalues();
+        auto evec = solver.eigenvectors().col(0);
+
+        view.setField(m_nx, i, evec[0]);
+        view.setField(m_ny, i, evec[1]);
+        view.setField(m_nz, i, evec[2]);
+
+        double sum = eval[0] + eval[1] + eval[2];
+        if (sum != 0)
+            view.setField(m_curvature, i, std::fabs(eval[0]/sum));
+        else
+            view.setField(m_curvature, i, 0);
+    }
+}
+
+} // namespace pdal
diff --git a/filters/normal/NormalFilter.hpp b/filters/NormalFilter.hpp
similarity index 100%
rename from filters/normal/NormalFilter.hpp
rename to filters/NormalFilter.hpp
diff --git a/filters/OutlierFilter.cpp b/filters/OutlierFilter.cpp
new file mode 100644
index 0000000..f99757c
--- /dev/null
+++ b/filters/OutlierFilter.cpp
@@ -0,0 +1,229 @@
+/******************************************************************************
+ * Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+ *       names of its contributors may be used to endorse or promote
+ *       products derived from this software without specific prior
+ *       written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#include "OutlierFilter.hpp"
+
+#include <pdal/KDIndex.hpp>
+#include <pdal/util/Utils.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.outlier", "Outlier removal",
+               "http://pdal.io/stages/filters.outlier.html");
+
+CREATE_STATIC_PLUGIN(1, 0, OutlierFilter, Filter, s_info)
+
+std::string OutlierFilter::getName() const
+{
+    return s_info.name;
+}
+
+
+void OutlierFilter::addArgs(ProgramArgs& args)
+{
+    args.add("method", "Method [default: statistical]", m_method,
+        "statistical");
+    args.add("min_k", "Minimum number of neighbors in radius", m_minK, 2);
+    args.add("radius", "Radius", m_radius, 1.0);
+    args.add("mean_k", "Mean number of neighbors", m_meanK, 8);
+    args.add("multiplier", "Standard deviation threshold", m_multiplier, 2.0);
+    args.add("classify", "Apply classification labels?", m_classify, true);
+    args.add("extract", "Extract ground returns?", m_extract);
+}
+
+
+void OutlierFilter::addDimensions(PointLayoutPtr layout)
+{
+    layout->registerDim(Dimension::Id::Classification);
+}
+
+
+Indices OutlierFilter::processRadius(PointViewPtr inView)
+{
+    KD3Index index(*inView);
+    index.build();
+
+    point_count_t np = inView->size();
+
+    std::vector<PointId> inliers, outliers;
+
+    for (PointId i = 0; i < np; ++i)
+    {
+        auto ids = index.radius(i, m_radius);
+        if (ids.size() > size_t(m_minK))
+            inliers.push_back(i);
+        else
+            outliers.push_back(i);
+    }
+
+    return Indices{inliers, outliers};
+}
+
+
+Indices OutlierFilter::processStatistical(PointViewPtr inView)
+{
+    KD3Index index(*inView);
+    index.build();
+
+    point_count_t np = inView->size();
+
+    std::vector<PointId> inliers, outliers;
+
+    std::vector<double> distances(np);
+    for (PointId i = 0; i < np; ++i)
+    {
+        // we increase the count by one because the query point itself will
+        // be included with a distance of 0
+        point_count_t count = m_meanK + 1;
+
+        std::vector<PointId> indices(count);
+        std::vector<double> sqr_dists(count);
+        index.knnSearch(i, count, &indices, &sqr_dists);
+
+        double dist_sum = 0.0;
+        for (auto const& d : sqr_dists)
+            dist_sum += sqrt(d);
+        distances[i] = dist_sum / m_meanK;
+    }
+
+    double sum = 0.0, sq_sum = 0.0;
+    for (auto const& d : distances)
+    {
+        sum += d;
+        sq_sum += d * d;
+    }
+    double mean = sum / np;
+    double variance = (sq_sum - sum * sum / np) / (np - 1);
+    double stdev = sqrt(variance);
+    double threshold = mean + m_multiplier * stdev;
+
+    for (PointId i = 0; i < np; ++i)
+    {
+        if (distances[i] < threshold)
+            inliers.push_back(i);
+        else
+            outliers.push_back(i);
+    }
+
+    return Indices{inliers, outliers};
+}
+
+
+PointViewSet OutlierFilter::run(PointViewPtr inView)
+{
+    PointViewSet viewSet;
+    if (!inView->size())
+        return viewSet;
+
+    Indices indices;
+    if (Utils::iequals(m_method, "statistical"))
+    {
+        indices = processStatistical(inView);
+    }
+    else if (Utils::iequals(m_method, "radius"))
+    {
+        indices = processRadius(inView);
+    }
+    else
+    {
+        log()->get(LogLevel::Warning) << "Requested method is unrecognized. "
+                                      << "Please choose from \"statistical\" " << "or \"radius\".\n";
+        viewSet.insert(inView);
+        return viewSet;
+    }
+
+    if (indices.inliers.empty())
+    {
+        log()->get(LogLevel::Warning) << "Requested filter would remove all "
+                                      << "points. Try a larger radius/smaller " << "minimum neighbors.\n";
+        viewSet.insert(inView);
+        return viewSet;
+    }
+
+    if (!indices.outliers.empty() && (m_classify || m_extract))
+    {
+        if (m_classify)
+        {
+            log()->get(LogLevel::Debug2) << "Labeled "
+                                         << indices.outliers.size()
+                                         << " outliers as noise!\n";
+
+            // set the classification label of outlier returns as 18
+            // (corresponding to ASPRS LAS specification for high noise)
+            for (const auto& i : indices.outliers)
+                inView->setField(Dimension::Id::Classification, i, 18);
+
+            viewSet.insert(inView);
+        }
+
+        if (m_extract)
+        {
+            log()->get(LogLevel::Debug2) << "Extracted "
+                                         << indices.inliers.size()
+                                         << " inliers!\n";
+
+            // create new PointView containing only outliers
+            PointViewPtr output = inView->makeNew();
+            for (const auto& i : indices.inliers)
+                output->appendPoint(*inView, i);
+
+            viewSet.erase(inView);
+            viewSet.insert(output);
+        }
+    }
+    else
+    {
+        if (indices.outliers.empty())
+            log()->get(LogLevel::Warning) << "Filtered cloud has no "
+                                          << "outliers!\n";
+
+        if (!(m_classify || m_extract))
+            log()->get(LogLevel::Warning) << "Must choose --classify or "
+                                          << "--extract\n";
+
+        // return the input buffer unchanged
+        viewSet.insert(inView);
+    }
+
+    return viewSet;
+}
+
+} // namespace pdal
diff --git a/filters/outlier/OutlierFilter.hpp b/filters/OutlierFilter.hpp
similarity index 100%
rename from filters/outlier/OutlierFilter.hpp
rename to filters/OutlierFilter.hpp
diff --git a/filters/PMFFilter.cpp b/filters/PMFFilter.cpp
new file mode 100644
index 0000000..3b25383
--- /dev/null
+++ b/filters/PMFFilter.cpp
@@ -0,0 +1,347 @@
+/******************************************************************************
+* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "PMFFilter.hpp"
+
+#include <pdal/EigenUtils.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/QuadIndex.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <Eigen/Dense>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.pmf", "Progressive morphological filter",
+               "http://pdal.io/stages/filters.pmf.html");
+
+CREATE_STATIC_PLUGIN(1, 0, PMFFilter, Filter, s_info)
+
+std::string PMFFilter::getName() const
+{
+    return s_info.name;
+}
+
+
+void PMFFilter::addArgs(ProgramArgs& args)
+{
+    args.add("max_window_size", "Maximum window size", m_maxWindowSize, 33.0);
+    args.add("slope", "Slope", m_slope, 1.0);
+    args.add("max_distance", "Maximum distance", m_maxDistance, 2.5);
+    args.add("initial_distance", "Initial distance", m_initialDistance, 0.15);
+    args.add("cell_size", "Cell size", m_cellSize, 1.0);
+    args.add("classify", "Apply classification labels?", m_classify, true);
+    args.add("extract", "Extract ground returns?", m_extract);
+    args.add("approximate", "Use approximate algorithm?", m_approximate);
+}
+
+
+void PMFFilter::addDimensions(PointLayoutPtr layout)
+{
+    layout->registerDim(Dimension::Id::Classification);
+}
+
+std::vector<double> PMFFilter::morphOpen(PointViewPtr view, float radius)
+{
+    point_count_t np(view->size());
+
+    QuadIndex idx(*view);
+
+    std::vector<double> minZ(np), maxZ(np);
+
+    // erode
+    for (PointId i = 0; i < np; ++i)
+    {
+        double x = view->getFieldAs<double>(Dimension::Id::X, i);
+        double y = view->getFieldAs<double>(Dimension::Id::Y, i);
+
+        std::vector<PointId> ids = idx.getPoints(x-radius, y-radius, x+radius, y+radius);
+
+        double localMin(std::numeric_limits<double>::max());
+        for (auto const& j : ids)
+        {
+            double z = view->getFieldAs<double>(Dimension::Id::Z, j);
+            if (z < localMin)
+                localMin = z;
+        }
+        minZ[i] = localMin;
+    }
+
+    // dilate
+    for (PointId i = 0; i < np; ++i)
+    {
+        double x = view->getFieldAs<double>(Dimension::Id::X, i);
+        double y = view->getFieldAs<double>(Dimension::Id::Y, i);
+
+        std::vector<PointId> ids = idx.getPoints(x-radius, y-radius, x+radius, y+radius);
+
+        double localMax(std::numeric_limits<double>::lowest());
+        for (auto const& j : ids)
+        {
+            double z = minZ[j];
+            if (z > localMax)
+                localMax = z;
+        }
+        maxZ[i] = localMax;
+    }
+
+    return maxZ;
+}
+
+std::vector<PointId> PMFFilter::processGround(PointViewPtr view)
+{
+    // Compute the series of window sizes and height thresholds
+    std::vector<float> htvec;
+    std::vector<float> wsvec;
+    int iter = 0;
+    float ws = 0.0f;
+    float ht = 0.0f;
+
+    while (ws < m_maxWindowSize)
+    {
+        // Determine the initial window size.
+        if (1) // exponential
+            ws = m_cellSize * (2.0f * std::pow(2, iter) + 1.0f);
+        else
+            ws = m_cellSize * (2.0f * (iter+1) * 2 + 1.0f);
+
+        // Calculate the height threshold to be used in the next iteration.
+        if (iter == 0)
+            ht = m_initialDistance;
+        else
+            ht = m_slope * (ws - wsvec[iter-1]) * m_cellSize + m_initialDistance;
+
+        // Enforce max distance on height threshold
+        if (ht > m_maxDistance)
+            ht = m_maxDistance;
+
+        wsvec.push_back(ws);
+        htvec.push_back(ht);
+
+        iter++;
+    }
+
+    std::vector<PointId> groundIdx;
+    for (PointId i = 0; i < view->size(); ++i)
+        groundIdx.push_back(i);
+
+    // Progressively filter ground returns using morphological open
+    for (size_t j = 0; j < wsvec.size(); ++j)
+    {
+        // Limit filtering to those points currently considered ground returns
+        PointViewPtr ground = view->makeNew();
+        for (auto const& i : groundIdx)
+            ground->appendPoint(*view, i);
+
+        log()->get(LogLevel::Debug) <<  "Iteration " << j
+                                    << " (height threshold = " << htvec[j]
+                                    << ", window size = " << wsvec[j]
+                                    << ")...\n";
+
+        // Create new cloud to hold the filtered results. Apply the
+        // morphological opening operation at the current window size.
+        auto maxZ = morphOpen(ground, wsvec[j]*0.5);
+
+        // Find indices of the points whose difference between the source and
+        // filtered point clouds is less than the current height threshold.
+        std::vector<PointId> groundNewIdx;
+        for (PointId i = 0; i < ground->size(); ++i)
+        {
+            double z0 = ground->getFieldAs<double>(Dimension::Id::Z, i);
+            float diff = z0 - maxZ[i];
+            if (diff < htvec[j])
+                groundNewIdx.push_back(groundIdx[i]);
+        }
+
+        groundIdx.swap(groundNewIdx);
+
+        log()->get(LogLevel::Debug) << "Ground now has " << groundIdx.size()
+                                    << " points.\n";
+    }
+
+    return groundIdx;
+}
+
+std::vector<PointId> PMFFilter::processGroundApprox(PointViewPtr view)
+{
+    using namespace Eigen;
+
+    BOX2D bounds;
+    view->calculateBounds(bounds);
+
+    double extent_x = floor(bounds.maxx) - ceil(bounds.minx);
+    double extent_y = floor(bounds.maxy) - ceil(bounds.miny);
+
+    int cols = static_cast<int>(ceil(extent_x/m_cellSize)) + 1;
+    int rows = static_cast<int>(ceil(extent_y/m_cellSize)) + 1;
+
+    // Compute the series of window sizes and height thresholds
+    std::vector<float> htvec;
+    std::vector<float> wsvec;
+    int iter = 0;
+    float ws = 0.0f;
+    float ht = 0.0f;
+
+    while (ws < m_maxWindowSize)
+    {
+        // Determine the initial window size.
+        if (1) // exponential
+            ws = m_cellSize * (2.0f * std::pow(2, iter) + 1.0f);
+        else
+            ws = m_cellSize * (2.0f * (iter+1) * 2 + 1.0f);
+
+        // Calculate the height threshold to be used in the next iteration.
+        if (iter == 0)
+            ht = m_initialDistance;
+        else
+            ht = m_slope * (ws - wsvec[iter-1]) * m_cellSize + m_initialDistance;
+
+        // Enforce max distance on height threshold
+        if (ht > m_maxDistance)
+            ht = m_maxDistance;
+
+        wsvec.push_back(ws);
+        htvec.push_back(ht);
+
+        iter++;
+    }
+
+    std::vector<PointId> groundIdx;
+    for (PointId i = 0; i < view->size(); ++i)
+        groundIdx.push_back(i);
+
+    MatrixXd ZImin = eigen::createMinMatrix(*view.get(), rows, cols, m_cellSize,
+                                            bounds);
+
+    // Progressively filter ground returns using morphological open
+    for (size_t j = 0; j < wsvec.size(); ++j)
+    {
+        log()->get(LogLevel::Debug) <<  "Iteration " << j
+                                    << " (height threshold = " << htvec[j]
+                                    << ", window size = " << wsvec[j]
+                                    << ")...\n";
+
+        MatrixXd mo = eigen::matrixOpen(ZImin, 0.5*(wsvec[j]-1));
+
+        std::vector<PointId> groundNewIdx;
+        for (auto p_idx : groundIdx)
+        {
+            double x = view->getFieldAs<double>(Dimension::Id::X, p_idx);
+            double y = view->getFieldAs<double>(Dimension::Id::Y, p_idx);
+            double z = view->getFieldAs<double>(Dimension::Id::Z, p_idx);
+
+            int r = static_cast<int>(std::floor((y-bounds.miny) / m_cellSize));
+            int c = static_cast<int>(std::floor((x-bounds.minx) / m_cellSize));
+
+            float diff = z - mo(r, c);
+            if (diff < htvec[j])
+                groundNewIdx.push_back(p_idx);
+        }
+
+        ZImin.swap(mo);
+        groundIdx.swap(groundNewIdx);
+
+        log()->get(LogLevel::Debug) << "Ground now has " << groundIdx.size()
+                                    << " points.\n";
+    }
+
+    return groundIdx;
+}
+
+PointViewSet PMFFilter::run(PointViewPtr input)
+{
+    bool logOutput = log()->getLevel() > LogLevel::Debug1;
+    if (logOutput)
+        log()->floatPrecision(8);
+    log()->get(LogLevel::Debug2) << "Process PMFFilter...\n";
+
+    std::vector<PointId> idx;
+    if (m_approximate)
+        idx = processGroundApprox(input);
+    else
+        idx = processGround(input);
+
+    PointViewSet viewSet;
+    if (!idx.empty() && (m_classify || m_extract))
+    {
+
+        if (m_classify)
+        {
+            log()->get(LogLevel::Debug2) << "Labeled " << idx.size()
+                                         << " ground returns!\n";
+
+            // set the classification label of ground returns as 2
+            // (corresponding to ASPRS LAS specification)
+            for (const auto& i : idx)
+            {
+                input->setField(Dimension::Id::Classification, i, 2);
+            }
+
+            viewSet.insert(input);
+        }
+
+        if (m_extract)
+        {
+            log()->get(LogLevel::Debug2) << "Extracted " << idx.size()
+                                         << " ground returns!\n";
+
+            // create new PointView containing only ground returns
+            PointViewPtr output = input->makeNew();
+            for (const auto& i : idx)
+            {
+                output->appendPoint(*input, i);
+            }
+
+            viewSet.erase(input);
+            viewSet.insert(output);
+        }
+    }
+    else
+    {
+        if (idx.empty())
+            log()->get(LogLevel::Debug2) << "Filtered cloud has no ground returns!\n";
+
+        if (!(m_classify || m_extract))
+            log()->get(LogLevel::Debug2) << "Must choose --classify or --extract\n";
+
+        // return the input buffer unchanged
+        viewSet.insert(input);
+    }
+
+    return viewSet;
+}
+
+} // namespace pdal
diff --git a/filters/PMFFilter.hpp b/filters/PMFFilter.hpp
new file mode 100644
index 0000000..6d71767
--- /dev/null
+++ b/filters/PMFFilter.hpp
@@ -0,0 +1,84 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+#include <memory>
+
+extern "C" int32_t PMFFilter_ExitFunc();
+extern "C" PF_ExitFunc PMFFilter_InitPlugin();
+
+namespace pdal
+{
+
+class Options;
+class PointLayout;
+class PointTable;
+class PointView;
+
+class PDAL_DLL PMFFilter : public Filter
+{
+public:
+    PMFFilter() : Filter()
+    {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    double m_maxWindowSize;
+    double m_slope;
+    double m_maxDistance;
+    double m_initialDistance;
+    double m_cellSize;
+    bool m_classify;
+    bool m_extract;
+    bool m_approximate;
+
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual void addArgs(ProgramArgs& args);
+    std::vector<double> morphOpen(PointViewPtr view, float radius);
+    std::vector<PointId> processGround(PointViewPtr view);
+    std::vector<PointId> processGroundApprox(PointViewPtr view);
+    virtual PointViewSet run(PointViewPtr view);
+
+    PMFFilter& operator=(const PMFFilter&); // not implemented
+    PMFFilter(const PMFFilter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/filters/RadialDensityFilter.cpp b/filters/RadialDensityFilter.cpp
new file mode 100644
index 0000000..c351eb9
--- /dev/null
+++ b/filters/RadialDensityFilter.cpp
@@ -0,0 +1,89 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "RadialDensityFilter.hpp"
+
+#include <pdal/KDIndex.hpp>
+#include <pdal/pdal_macros.hpp>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.radialdensity", "RadialDensity Filter",
+               "http://pdal.io/stages/filters.radialdensity.html");
+
+CREATE_STATIC_PLUGIN(1, 0, RadialDensityFilter, Filter, s_info)
+
+std::string RadialDensityFilter::getName() const
+{
+    return s_info.name;
+}
+
+void RadialDensityFilter::addArgs(ProgramArgs& args)
+{
+    args.add("radius", "Radius", m_rad, 1.0);
+}
+
+void RadialDensityFilter::addDimensions(PointLayoutPtr layout)
+{
+    using namespace Dimension;
+    m_rdens = layout->registerOrAssignDim("RadialDensity", Type::Double);
+}
+
+void RadialDensityFilter::filter(PointView& view)
+{
+    using namespace Dimension;
+    
+    // Build the 3D KD-tree.
+    log()->get(LogLevel::Debug) << "Building 3D KD-tree...\n";
+    KD3Index index(view);
+    index.build();
+ 
+    // Search for neighboring points within the specified radius. The number of
+    // neighbors (which includes the query point) is normalized by the volume
+    // of the search sphere and recorded as the density.
+    log()->get(LogLevel::Debug) << "Computing densities...\n";
+    double factor = 1.0 / ((4.0 / 3.0) * 3.14159 * (m_rad * m_rad * m_rad));
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        std::vector<PointId> pts = index.radius(i, m_rad);
+        view.setField(m_rdens, i, pts.size() * factor);
+    } 
+}
+
+} // namespace pdal
diff --git a/filters/RadialDensityFilter.hpp b/filters/RadialDensityFilter.hpp
new file mode 100644
index 0000000..675585f
--- /dev/null
+++ b/filters/RadialDensityFilter.hpp
@@ -0,0 +1,74 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+#include <memory>
+
+extern "C" int32_t RadialDensityFilter_ExitFunc();
+extern "C" PF_ExitFunc RadialDensityFilter_InitPlugin();
+
+namespace pdal
+{
+
+class PointLayout;
+class PointView;
+class ProgramArgs;
+
+class PDAL_DLL RadialDensityFilter : public Filter
+{
+public:
+    RadialDensityFilter() : Filter()
+    {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    Dimension::Id m_rdens;
+    double m_rad;
+    
+    virtual void addArgs(ProgramArgs& args);
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual void filter(PointView& view);
+
+    RadialDensityFilter& operator=(const RadialDensityFilter&); // not implemented
+    RadialDensityFilter(const RadialDensityFilter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/filters/randomize/RandomizeFilter.cpp b/filters/RandomizeFilter.cpp
similarity index 100%
rename from filters/randomize/RandomizeFilter.cpp
rename to filters/RandomizeFilter.cpp
diff --git a/filters/randomize/RandomizeFilter.hpp b/filters/RandomizeFilter.hpp
similarity index 100%
rename from filters/randomize/RandomizeFilter.hpp
rename to filters/RandomizeFilter.hpp
diff --git a/filters/range/RangeFilter.cpp b/filters/RangeFilter.cpp
similarity index 100%
rename from filters/range/RangeFilter.cpp
rename to filters/RangeFilter.cpp
diff --git a/filters/range/RangeFilter.hpp b/filters/RangeFilter.hpp
similarity index 100%
rename from filters/range/RangeFilter.hpp
rename to filters/RangeFilter.hpp
diff --git a/filters/ReprojectionFilter.cpp b/filters/ReprojectionFilter.cpp
new file mode 100644
index 0000000..db7d1c6
--- /dev/null
+++ b/filters/ReprojectionFilter.cpp
@@ -0,0 +1,199 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "ReprojectionFilter.hpp"
+
+#include <pdal/PointView.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/GDALUtils.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <gdal.h>
+#include <ogr_spatialref.h>
+
+#include <memory>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "filters.reprojection",
+    "Reproject data using GDAL from one coordinate system to another.",
+    "http://pdal.io/stages/filters.reprojection.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, ReprojectionFilter, Filter, s_info)
+
+std::string ReprojectionFilter::getName() const { return s_info.name; }
+
+ReprojectionFilter::ReprojectionFilter()
+    : m_inferInputSRS(true)
+    , m_in_ref_ptr(NULL)
+    , m_out_ref_ptr(NULL)
+    , m_transform_ptr(NULL)
+    , m_errorHandler(new gdal::ErrorHandler())
+{}
+
+ReprojectionFilter::~ReprojectionFilter()
+{
+    if (m_transform_ptr)
+        OCTDestroyCoordinateTransformation(m_transform_ptr);
+    if (m_in_ref_ptr)
+        OSRDestroySpatialReference(m_in_ref_ptr);
+    if (m_out_ref_ptr)
+        OSRDestroySpatialReference(m_out_ref_ptr);
+}
+
+
+void ReprojectionFilter::addArgs(ProgramArgs& args)
+{
+    args.add("out_srs", "Output spatial reference", m_outSRS).setPositional();
+    args.add("in_srs", "Input spatial reference", m_inSRS);
+}
+
+
+void ReprojectionFilter::initialize()
+{
+    m_inferInputSRS = m_inSRS.empty();
+
+    m_out_ref_ptr = OSRNewSpatialReference(0);
+    if (!m_out_ref_ptr)
+        throw pdal::pdal_error("Unable to allocate new OSR SpatialReference "
+            "in initialize()!");
+
+    int result = OSRSetFromUserInput(m_out_ref_ptr, m_outSRS.getWKT().c_str());
+    if (result != OGRERR_NONE)
+    {
+        std::ostringstream oss;
+        oss << getName() << ": Invalid output spatial reference '" <<
+            m_outSRS.getWKT() << "'.  This is usually caused by a "
+            "bad value for the 'out_srs' option.";
+        throw pdal_error(oss.str());
+    }
+}
+
+
+void ReprojectionFilter::ready(PointTableRef table)
+{
+    if (!table.supportsView())
+        createTransform(table.anySpatialReference());
+}
+
+
+void ReprojectionFilter::createTransform(const SpatialReference& srsSRS)
+{
+    if (m_inferInputSRS)
+    {
+        m_inSRS = srsSRS;
+        if (m_inSRS.empty())
+        {
+            std::ostringstream oss;
+            oss << getName() << ": source data has no spatial reference and "
+                "none is specified with the 'in_srs' option.";
+            throw pdal_error(oss.str());
+        }
+    }
+
+    if (m_in_ref_ptr)
+        OSRDestroySpatialReference(m_in_ref_ptr);
+    m_in_ref_ptr = OSRNewSpatialReference(0);
+    if (!m_in_ref_ptr)
+        throw pdal::pdal_error("Unable to allocate new OSR SpatialReference for input coordinate system in createTransform()!");
+
+    int result = OSRSetFromUserInput(m_in_ref_ptr, m_inSRS.getWKT().c_str());
+    if (result != OGRERR_NONE)
+    {
+        std::ostringstream oss;
+        oss << getName() << ": Invalid input spatial reference '" <<
+            m_inSRS.getWKT() << "'.  This is usually caused by "
+            "a bad value for the 'in_srs' option or an invalid "
+            "spatial reference in the source file.";
+        throw pdal_error(oss.str());
+    }
+    if (m_transform_ptr)
+        OCTDestroyCoordinateTransformation(m_transform_ptr);
+    m_transform_ptr = OCTNewCoordinateTransformation(m_in_ref_ptr,
+        m_out_ref_ptr);
+    if (!m_transform_ptr)
+    {
+        std::ostringstream oss;
+        oss << getName() << ": Could not construct coordinate "
+            "transformation object in createTransform";
+        throw pdal_error(oss.str());
+    }
+}
+
+PointViewSet ReprojectionFilter::run(PointViewPtr view)
+{
+    PointViewSet viewSet;
+    PointViewPtr outView = view->makeNew();
+
+    createTransform(view->spatialReference());
+
+    PointRef point(*view, 0);
+    for (PointId id = 0; id < view->size(); ++id)
+    {
+        point.setPointId(id);
+        if (processOne(point))
+            outView->appendPoint(*view, id);
+    }
+
+    viewSet.insert(outView);
+    view->setSpatialReference(m_outSRS);
+    outView->setSpatialReference(m_outSRS);
+
+    return viewSet;
+}
+
+
+bool ReprojectionFilter::processOne(PointRef& point)
+{
+    double x(point.getFieldAs<double>(Dimension::Id::X));
+    double y(point.getFieldAs<double>(Dimension::Id::Y));
+    double z(point.getFieldAs<double>(Dimension::Id::Z));
+
+    if (OCTTransform(m_transform_ptr, 1, &x, &y, &z))
+    {
+        point.setField(Dimension::Id::X, x);
+        point.setField(Dimension::Id::Y, y);
+        point.setField(Dimension::Id::Z, z);
+
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+} // namespace pdal
diff --git a/filters/reprojection/ReprojectionFilter.hpp b/filters/ReprojectionFilter.hpp
similarity index 100%
rename from filters/reprojection/ReprojectionFilter.hpp
rename to filters/ReprojectionFilter.hpp
diff --git a/filters/SMRFilter.cpp b/filters/SMRFilter.cpp
new file mode 100644
index 0000000..b523b1f
--- /dev/null
+++ b/filters/SMRFilter.cpp
@@ -0,0 +1,950 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "SMRFilter.hpp"
+
+#include <pdal/EigenUtils.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/PipelineManager.hpp>
+#include <pdal/SpatialReference.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+#include <pdal/util/Utils.hpp>
+#include <io/BufferReader.hpp>
+
+#include <Eigen/Dense>
+#include <Eigen/Sparse>
+
+namespace pdal
+{
+using namespace Eigen;
+
+static PluginInfo const s_info =
+    PluginInfo("filters.smrf", "Pingel et al. (2013)",
+               "http://pdal.io/stages/filters.smrf.html");
+
+CREATE_STATIC_PLUGIN(1, 0, SMRFilter, Filter, s_info)
+
+struct distElev
+{
+    double dist;
+    double elev;
+};
+
+struct by_dist
+{
+    bool operator()(distElev const& a, distElev const& b)
+    {
+        return a.dist < b.dist;
+    }
+};
+
+std::string SMRFilter::getName() const
+{
+    return s_info.name;
+}
+
+void SMRFilter::addArgs(ProgramArgs& args)
+{
+    args.add("classify", "Apply classification labels?", m_classify, true);
+    args.add("extract", "Extract ground returns?", m_extract);
+    args.add("cell", "Cell size?", m_cellSize, 1.0);
+    args.add("slope", "Slope?", m_percentSlope, 0.15);
+    args.add("window", "Max window size?", m_maxWindow, 18.0);
+    args.add("scalar", "Elevation scalar?", m_scalar, 1.25);
+    args.add("threshold", "Elevation threshold?", m_threshold, 0.5);
+    args.add("cut", "Cut net size?", m_cutNet, 0.0);
+    args.add("outdir", "Optional output directory for debugging", m_outDir);
+}
+
+void SMRFilter::addDimensions(PointLayoutPtr layout)
+{
+    layout->registerDim(Dimension::Id::Classification);
+}
+
+void SMRFilter::ready(PointTableRef table)
+{
+    if (m_outDir.empty())
+        return;
+        
+    if (!FileUtils::directoryExists(m_outDir))
+        throw pdal_error("Output directory does not exist");
+}
+
+MatrixXd SMRFilter::inpaintKnn(MatrixXd cx, MatrixXd cy, MatrixXd cz)
+{
+    MatrixXd out = cz;
+
+    for (auto c = 0; c < m_numCols; ++c)
+    {
+        for (auto r = 0; r < m_numRows; ++r)
+        {
+            if (!std::isnan(cz(r, c)))
+                continue;
+
+            int radius = 1;
+            bool enough = false;
+
+            while (!enough)
+            {
+                // log()->get(LogLevel::Debug) << r << "\t" << c << "\t" << radius << std::endl;
+                int cs = Utils::clamp(c-radius, 0, m_numCols-1);
+                int ce = Utils::clamp(c+radius, 0, m_numCols-1);
+                int col_size = ce - cs + 1;
+                int rs = Utils::clamp(r-radius, 0, m_numRows-1);
+                int re = Utils::clamp(r+radius, 0, m_numRows-1);
+                int row_size = re - rs + 1;
+
+                // MatrixXd Xn = cx.block(rs, cs, row_size, col_size);
+                // MatrixXd Yn = cy.block(rs, cs, row_size, col_size);
+                MatrixXd Zn = cz.block(rs, cs, row_size, col_size);
+
+                auto notNaN = [](double x)
+                {
+                    return !std::isnan(x);
+                };
+
+                enough = Zn.unaryExpr(notNaN).count() >= 8;
+                if (!enough)
+                {
+                    ++radius;
+                    continue;
+                }
+
+                // auto zNotNaN = [](double x)
+                // {
+                //     if (!std::isnan(x))
+                //         return x;
+                //     else
+                //         return 0.0;
+                // };
+                //
+                // // proceed to find 8 nearest neighbors and average the z values
+                // // std::cerr << Zn.unaryExpr(zNotNaN).sum() << "\t" << Zn.size() << "\t" << Zn.unaryExpr(zNotNaN).sum() / Zn.size() << std::endl;
+                // out(r, c) = Zn.unaryExpr(zNotNaN).sum() / Zn.size();
+
+                std::vector<distElev> de;
+
+                for (auto cc = cs; cc <= ce; ++cc)
+                {
+                    for (auto rr = rs; rr <= re; ++rr)
+                    {
+                        if (std::isnan(cz(rr, cc)))
+                            continue;
+
+                        // compute distance to !isnan neighbor
+                        double dx = cx(rr, cc) - cx(r, c);
+                        double dy = cy(rr, cc) - cy(r, c);
+                        double sqrdist = dx * dx + dy * dy;
+                        de.push_back(distElev{sqrdist, cz(rr, cc)});
+                    }
+                }
+                // sort dists
+                std::sort(de.begin(), de.end(), by_dist());
+
+                // average elevatio of lowest eight dists
+                double sum = 0.0;
+                for (auto i = 0; i < 8; ++i)
+                {
+                    sum += de[i].elev;
+                }
+                sum /= 8.0;
+
+                out(r, c) = sum;
+            }
+        }
+    }
+
+    return out;
+}
+
+std::vector<PointId> SMRFilter::processGround(PointViewPtr view)
+{
+    log()->get(LogLevel::Info) << "processGround: Running SMRF...\n";
+
+    // The algorithm consists of four conceptually distinct stages. The first is
+    // the creation of the minimum surface (ZImin). The second is the processing
+    // of the minimum surface, in which grid cells from the raster are
+    // identified as either containing bare earth (BE) or objects (OBJ). This
+    // second stage represents the heart of the algorithm. The third step is the
+    // creation of a DEM from these gridded points. The fourth step is the
+    // identification of the original LIDAR points as either BE or OBJ based on
+    // their relationship to the interpolated
+
+    std::vector<PointId> groundIdx;
+    
+    BOX2D bounds;
+    view->calculateBounds(bounds);
+    SpatialReference srs(view->spatialReference());
+
+    // Determine the number of rows and columns at the given cell size.
+    m_numCols = ((bounds.maxx - bounds.minx) / m_cellSize) + 1;
+    m_numRows = ((bounds.maxy - bounds.miny) / m_cellSize) + 1;
+
+    MatrixXd cx(m_numRows, m_numCols);
+    MatrixXd cy(m_numRows, m_numCols);
+    for (auto c = 0; c < m_numCols; ++c)
+    {
+        for (auto r = 0; r < m_numRows; ++r)
+        {
+            cx(r, c) = bounds.minx + (c + 0.5) * m_cellSize;
+            cy(r, c) = bounds.miny + (r + 0.5) * m_cellSize;
+        }
+    }
+
+    // STEP 1:
+
+    // As with many other ground filtering algorithms, the first step is
+    // generation of ZImin from the cell size parameter and the extent of the
+    // data. The two vectors corresponding to [min:cellSize:max] for each
+    // coordinate – xi and yi – may be supplied by the user or may be easily and
+    // automatically calculated from the data. Without supplied ranges, the SMRF
+    // algorithm creates a raster from the ceiling of the minimum to the floor
+    // of the maximum values for each of the (x,y) dimensions. If the supplied
+    // cell size parameter is not an integer, the same general rule applies to
+    // values evenly divisible by the cell size. For example, if cell size is
+    // equal to 0.5 m, and the x values range from 52345.6 to 52545.4, the range
+    // would be [52346 52545].
+
+    // The minimum surface grid ZImin defined by vectors (xi,yi) is filled with
+    // the nearest, lowest elevation from the original point cloud (x,y,z)
+    // values, provided that the distance to the nearest point does not exceed
+    // the supplied cell size parameter. This provision means that some grid
+    // points of ZImin will go unfilled. To fill these values, we rely on
+    // computationally inexpensive image inpainting techniques. Image inpainting
+    // involves the replacement of the empty cells in an image (or matrix) with
+    // values calculated from other nearby values. It is a type of interpolation
+    // technique derived from artistic replacement of damaged portions of
+    // photographs and paintings, where preservation of texture is an important
+    // concern (Bertalmio et al., 2000). When empty values are spread through
+    // the image, and the ratio of filled to empty pixels is quite high, most
+    // methods of inpainting will produce satisfactory results. In an evaluation
+    // of inpainting methods on ground identification from the final terrain
+    // model, we found that Laplacian techniques produced error rates nearly
+    // three times higher than either an average of the eight nearest neighbors
+    // or D’Errico’s spring-metaphor inpainting technique (D’Errico, 2004). The
+    // spring-metaphor technique imagines springs connecting each cell with its
+    // eight adjacent neighbors, where the inpainted value corresponds to the
+    // lowest energy state of the set, and where the entire (sparse) set of
+    // linear equations is solved using partial differential equations. Both of
+    // these latter techniques were nearly the same with regards to total error,
+    // with the spring technique performing slightly better than the k-nearest
+    // neighbor (KNN) approach.
+
+    MatrixXd ZImin = eigen::createMinMatrix(*view.get(), m_numRows, m_numCols,
+                                            m_cellSize, bounds);
+    
+    // MatrixXd ZImin_painted = inpaintKnn(cx, cy, ZImin);
+    // MatrixXd ZImin_painted = TPS(cx, cy, ZImin);
+    MatrixXd ZImin_painted = expandingTPS(cx, cy, ZImin);
+    
+    if (!m_outDir.empty())
+    {
+        std::string filename = FileUtils::toAbsolutePath("zimin.tif", m_outDir);
+        eigen::writeMatrix(ZImin, filename, "GTiff", m_cellSize, bounds, srs);
+        
+        filename = FileUtils::toAbsolutePath("zimin_painted.tif", m_outDir);
+        eigen::writeMatrix(ZImin_painted, filename, "GTiff", m_cellSize, bounds, srs);
+    }
+
+    ZImin = ZImin_painted;
+
+    // STEP 2:
+
+    // The second stage of the ground identification algorithm involves the
+    // application of a progressive morphological filter to the minimum surface
+    // grid (ZImin). At the first iteration, the filter applies an image opening
+    // operation to the minimum surface. An opening operation consists of an
+    // application of an erosion filter followed by a dilation filter. The
+    // erosion acts to snap relative high values to relative lows, where a
+    // supplied window radius and shape (or structuring element) defines the
+    // search neighborhood. The dilation uses the same window radius and
+    // structuring element, acting to outwardly expand relative highs. Fig. 2
+    // illustrates an opening operation on a cross section of a transect from
+    // Sample 1–1 in the ISPRS LIDAR reference dataset (Sithole and Vosselman,
+    // 2003), following Zhang et al. (2003).
+
+    // paper has low point happening later, i guess it doesn't matter too much, this is where he does it in matlab code
+    MatrixXi Low = progressiveFilter(-ZImin, m_cellSize, 5.0, 1.0);
+
+    // matlab code has net cutting occurring here
+    MatrixXd ZInet = ZImin;
+    MatrixXi isNetCell = MatrixXi::Zero(m_numRows, m_numCols);
+    if (m_cutNet > 0.0)
+    {
+        MatrixXd bigOpen = eigen::matrixOpen(ZImin, 2*std::ceil(m_cutNet / m_cellSize));
+        for (auto c = 0; c < m_numCols; c += std::ceil(m_cutNet/m_cellSize))
+        {
+            for (auto r = 0; r < m_numRows; ++r)
+            {
+                isNetCell(r, c) = 1;
+            }
+        }
+        for (auto c = 0; c < m_numCols; ++c)
+        {
+            for (auto r = 0; r < m_numRows; r += std::ceil(m_cutNet/m_cellSize))
+            {
+                isNetCell(r, c) = 1;
+            }
+        }
+        for (auto c = 0; c < m_numCols; ++c)
+        {
+            for (auto r = 0; r < m_numRows; ++r)
+            {
+                if (isNetCell(r, c)==1)
+                    ZInet(r, c) = bigOpen(r, c);
+            }
+        }
+    }
+
+    // and finally object detection
+    MatrixXi Obj = progressiveFilter(ZInet, m_cellSize, m_percentSlope, m_maxWindow);
+
+    // STEP 3:
+
+    // The end result of the iteration process described above is a binary grid
+    // where each cell is classified as being either bare earth (BE) or object
+    // (OBJ). The algorithm then applies this mask to the starting minimum
+    // surface to eliminate nonground cells. These cells are then inpainted
+    // according to the same process described previously, producing a
+    // provisional DEM (ZIpro).
+
+    // we currently aren't checking for net cells or empty cells (haven't i already marked empty cells as NaNs?)
+    MatrixXd ZIpro = ZImin;
+    for (int i = 0; i < Obj.size(); ++i)
+    {
+        if (Obj(i) == 1 || Low(i) == 1 || isNetCell(i) == 1)
+            ZIpro(i) = std::numeric_limits<double>::quiet_NaN();
+    }
+
+    // MatrixXd ZIpro_painted = inpaintKnn(cx, cy, ZIpro);
+    // MatrixXd ZIpro_painted = TPS(cx, cy, ZIpro);
+    MatrixXd ZIpro_painted = expandingTPS(cx, cy, ZIpro);
+    
+    if (!m_outDir.empty())
+    {
+        std::string filename = FileUtils::toAbsolutePath("zilow.tif", m_outDir);
+        eigen::writeMatrix(Low.cast<double>(), filename, "GTiff", m_cellSize, bounds, srs);
+        
+        filename = FileUtils::toAbsolutePath("zinet.tif", m_outDir);
+        eigen::writeMatrix(ZInet, filename, "GTiff", m_cellSize, bounds, srs);
+        
+        filename = FileUtils::toAbsolutePath("ziobj.tif", m_outDir);
+        eigen::writeMatrix(Obj.cast<double>(), filename, "GTiff", m_cellSize, bounds, srs);
+        
+        filename = FileUtils::toAbsolutePath("zipro.tif", m_outDir);
+        eigen::writeMatrix(ZIpro, filename, "GTiff", m_cellSize, bounds, srs);
+        
+        filename = FileUtils::toAbsolutePath("zipro_painted.tif", m_outDir);
+        eigen::writeMatrix(ZIpro_painted, filename, "GTiff", m_cellSize, bounds, srs);
+    }
+
+    ZIpro = ZIpro_painted;
+
+    // STEP 4:
+
+    // The final step of the algorithm is the identification of ground/object
+    // LIDAR points. This is accomplished by measuring the vertical distance
+    // between each LIDAR point and the provisional DEM, and applying a
+    // threshold calculation. While many authors use a single value for the
+    // elevation threshold, we suggest that a second parameter be used to
+    // increase the threshold on steep slopes, transforming the threshold to a
+    // slope-dependent value. The total permissible distance is then equal to a
+    // fixed elevation threshold plus the scaling value multiplied by the slope
+    // of the DEM at each LIDAR point. The rationale behind this approach is
+    // that small horizontal and vertical displacements yield larger errors on
+    // steep slopes, and as a result the BE/OBJ threshold distance should be
+    // more per- missive at these points.
+
+    // The calculation requires that both elevation and slope are interpolated
+    // from the provisional DEM. There are any number of interpolation
+    // techniques that might be used, and even nearest neighbor approaches work
+    // quite well, so long as the cell size of the DEM nearly corresponds to the
+    // resolution of the LIDAR data. A comparison of how well these different
+    // methods of interpolation perform is given in the next section. Based on
+    // these results, we find that a splined cubic interpolation provides the
+    // best results.
+
+    // It is common in LIDAR point clouds to have a small number of outliers
+    // which may be either above or below the terrain surface. While
+    // above-ground outliers (e.g., a random return from a bird in flight) are
+    // filtered during the normal algorithm routine, the below-ground outliers
+    // (e.g., those caused by a reflection) require a separate approach. Early
+    // in the routine and along a separate processing fork, the minimum surface
+    // is checked for low outliers by inverting the point cloud in the z-axis
+    // and applying the filter with parameters (slope = 500%, maxWindowSize =
+    // 1). The resulting mask is used to flag low outlier cells as OBJ before
+    // the inpainting of the provisional DEM. This outlier identification
+    // methodology is functionally the same as that of Zhang et al. (2003).
+
+    // The provisional DEM (ZIpro), created by removing OBJ cells from the
+    // original minimum surface (ZImin) and then inpainting, tends to be less
+    // smooth than one might wish, especially when the surfaces are to be used
+    // to create visual products like immersive geographic virtual environments.
+    // As a result, it is often worthwhile to reinter- polate a final DEM from
+    // the identified ground points of the original LIDAR data (ZIfin). Surfaces
+    // created from these data tend to be smoother and more visually satisfying
+    // than those derived from the provisional DEM.
+
+    // Very large (>40m in length) buildings can sometimes prove troublesome to
+    // remove on highly differentiated terrain. To accommodate the removal of
+    // such objects, we implemented a feature in the published SMRF algorithm
+    // which is helpful in removing such features. We accomplish this by
+    // introducing into the initial minimum surface a ‘‘net’’ of minimum values
+    // at a spacing equal to the maximum window diameter, where these minimum
+    // values are found by applying a morphological open operation with a disk
+    // shaped structuring element of radius (2?wkmax). Since only one example in
+    // this dataset had features this large (Sample 4–2, a trainyard) we did not
+    // include this portion of the algorithm in the formal testing procedure,
+    // though we provide a brief analysis of the effect of using this net filter
+    // in the next section.
+    
+    MatrixXd scaled = ZIpro / m_cellSize;
+
+    MatrixXd gx = eigen::gradX(scaled);
+    MatrixXd gy = eigen::gradY(scaled);
+    MatrixXd gsurfs = (gx.cwiseProduct(gx) + gy.cwiseProduct(gy)).cwiseSqrt();
+
+    // MatrixXd gsurfs_painted = inpaintKnn(cx, cy, gsurfs);
+    // MatrixXd gsurfs_painted = TPS(cx, cy, gsurfs);
+    MatrixXd gsurfs_painted = expandingTPS(cx, cy, gsurfs);
+    
+    if (!m_outDir.empty())
+    {
+        std::string filename = FileUtils::toAbsolutePath("gx.tif", m_outDir);
+        eigen::writeMatrix(gx, filename, "GTiff", m_cellSize, bounds, srs);
+        
+        filename = FileUtils::toAbsolutePath("gy.tif", m_outDir);
+        eigen::writeMatrix(gy, filename, "GTiff", m_cellSize, bounds, srs);
+        
+        filename = FileUtils::toAbsolutePath("gsurfs.tif", m_outDir);
+        eigen::writeMatrix(gsurfs, filename, "GTiff", m_cellSize, bounds, srs);
+        
+        filename = FileUtils::toAbsolutePath("gsurfs_painted.tif", m_outDir);
+        eigen::writeMatrix(gsurfs_painted, filename, "GTiff", m_cellSize, bounds, srs);
+    }
+
+    gsurfs = gsurfs_painted;
+
+    MatrixXd thresh = (m_threshold + m_scalar * gsurfs.array()).matrix();
+    
+    if (!m_outDir.empty())
+    {
+        std::string filename = FileUtils::toAbsolutePath("thresh.tif", m_outDir);
+        eigen::writeMatrix(thresh, filename, "GTiff", m_cellSize, bounds, srs);
+    }
+
+    for (PointId i = 0; i < view->size(); ++i)
+    {
+        using namespace Dimension;
+        double x = view->getFieldAs<double>(Id::X, i);
+        double y = view->getFieldAs<double>(Id::Y, i);
+        double z = view->getFieldAs<double>(Id::Z, i);
+
+        int c = Utils::clamp(static_cast<int>(floor(x - bounds.minx) / m_cellSize), 0, m_numCols-1);
+        int r = Utils::clamp(static_cast<int>(floor(y - bounds.miny) / m_cellSize), 0, m_numRows-1);
+
+        // author uses spline interpolation to get value from ZIpro and gsurfs
+
+        if (std::isnan(ZIpro(r, c)))
+            continue;
+
+        // not sure i should just brush this under the rug...
+        if (std::isnan(gsurfs(r, c)))
+            continue;
+
+        double ez = ZIpro(r, c);
+        // double ez = interp2(r, c, cx, cy, ZIpro);
+        // double si = gsurfs(r, c);
+        // double si = interp2(r, c, cx, cy, gsurfs);
+        // double reqVal = m_threshold + 1.2 * si;
+
+        if (std::abs(ez - z) > thresh(r, c))
+            continue;
+
+        // if (std::abs(ZIpro(r, c) - z) > m_threshold)
+        //     continue;
+
+        groundIdx.push_back(i);
+    }
+
+    return groundIdx;
+}
+
+MatrixXi SMRFilter::progressiveFilter(MatrixXd const& ZImin, double cell_size,
+                                      double slope, double max_window)
+{
+    log()->get(LogLevel::Info) << "progressiveFilter: Progressive filtering...\n";
+
+    MatrixXi Obj(m_numRows, m_numCols);
+    Obj.setZero();
+
+    // In this case, we selected a disk-shaped structuring element, and the
+    // radius of the element at each step was increased by one pixel from a
+    // starting value of one pixel to the pixel equivalent of the maximum value
+    // (wkmax). The maximum window radius is supplied as a distance metric
+    // (e.g., 21 m), but is internally converted to a pixel equivalent by
+    // dividing it by the cell size and rounding the result toward positive
+    // infinity (i.e., taking the ceiling value). For example, for a supplied
+    // maximum window radius of 21 m, and a cell size of 2m per pixel, the
+    // result would be a maximum window radius of 11 pixels. While this
+    // represents a relatively slow progression in the expansion of the window
+    // radius, we believe that the high efficiency associated with the opening
+    // operation mitigates the potential for computational waste. The
+    // improvements in classification accuracy using slow, linear progressions
+    // are documented in the next section.
+    int max_radius = ceil(max_window/cell_size);
+    MatrixXd ZIlocal = ZImin;
+    for (int radius = 1; radius <= max_radius; ++radius)
+    {
+        // On the first iteration, the minimum surface (ZImin) is opened using a
+        // disk-shaped structuring element with a radius of one pixel.
+        MatrixXd mo = eigen::matrixOpen(ZIlocal, radius);
+
+        // An elevation threshold is then calculated, where the value is equal
+        // to the supplied slope tolerance parameter multiplied by the product
+        // of the window radius and the cell size. For example, if the user
+        // supplied a slope tolerance parameter of 15%, a cell size of 2m per
+        // pixel, the elevation threshold would be 0.3m at a window of one pixel
+        // (0.15 ? 1 ? 2).
+        double threshold = slope * cell_size * radius;
+
+        // This elevation threshold is applied to the difference of the minimum
+        // and the opened surfaces.
+        MatrixXd diff = ZIlocal - mo;
+
+        // Any grid cell with a difference value exceeding the calculated
+        // elevation threshold for the iteration is then flagged as an OBJ cell.
+        for (int i = 0; i < diff.size(); ++i)
+        {
+            if (diff(i) > threshold)
+                Obj(i) = 1;
+        }
+        // eigen::writeMatrix(Obj, "obj.tif", "GTiff", m_cellSize, bounds, srs);
+
+        // The algorithm then proceeds to the next window radius (up to the
+        // maximum), and proceeds as above with the last opened surface acting
+        // as the ‘‘minimum surface’’ for the next difference calculation.
+        ZIlocal = mo;
+
+        log()->get(LogLevel::Info) << "progressiveFilter: Radius = " << radius
+                                   << ", " << Obj.sum() << " object pixels\n";
+    }
+
+    return Obj;
+}
+
+PointViewSet SMRFilter::run(PointViewPtr view)
+{
+    log()->get(LogLevel::Info) << "run: Process SMRFilter...\n";
+
+    std::vector<PointId> idx = processGround(view);
+
+    PointViewSet viewSet;
+
+    if (!idx.empty() && (m_classify || m_extract))
+    {
+
+        if (m_classify)
+        {
+            log()->get(LogLevel::Info) << "run: Labeled " << idx.size() << " ground returns!\n";
+
+            // set the classification label of ground returns as 2
+            // (corresponding to ASPRS LAS specification)
+            for (const auto& i : idx)
+            {
+                view->setField(Dimension::Id::Classification, i, 2);
+            }
+
+            viewSet.insert(view);
+        }
+
+        if (m_extract)
+        {
+            log()->get(LogLevel::Info) << "run: Extracted " << idx.size() << " ground returns!\n";
+
+            // create new PointView containing only ground returns
+            PointViewPtr output = view->makeNew();
+            for (const auto& i : idx)
+            {
+                output->appendPoint(*view, i);
+            }
+
+            viewSet.erase(view);
+            viewSet.insert(output);
+        }
+    }
+    else
+    {
+        if (idx.empty())
+            log()->get(LogLevel::Info) << "run: Filtered cloud has no ground returns!\n";
+
+        if (!(m_classify || m_extract))
+            log()->get(LogLevel::Info) << "run: Must choose --classify or --extract\n";
+
+        // return the view buffer unchanged
+        viewSet.insert(view);
+    }
+
+    return viewSet;
+}
+
+MatrixXd SMRFilter::TPS(MatrixXd cx, MatrixXd cy, MatrixXd cz)
+{
+    log()->get(LogLevel::Info) << "TPS: Reticulating splines...\n";
+
+    MatrixXd S = cz;
+
+    int num_nan_detect(0);
+    int num_nan_replace(0);
+
+    for (auto outer_col = 0; outer_col < m_numCols; ++outer_col)
+    {
+        for (auto outer_row = 0; outer_row < m_numRows; ++outer_row)
+        {
+            if (!std::isnan(S(outer_row, outer_col)))
+                continue;
+
+            num_nan_detect++;
+
+            // Further optimizations are achieved by estimating only the
+            // interpolated surface within a local neighbourhood (e.g. a 7 x 7
+            // neighbourhood is used in our case) of the cell being filtered.
+            int radius = 3;
+
+            int cs = Utils::clamp(outer_col-radius, 0, m_numCols-1);
+            int ce = Utils::clamp(outer_col+radius, 0, m_numCols-1);
+            int col_size = ce - cs + 1;
+            int rs = Utils::clamp(outer_row-radius, 0, m_numRows-1);
+            int re = Utils::clamp(outer_row+radius, 0, m_numRows-1);
+            int row_size = re - rs + 1;
+
+            MatrixXd Xn = cx.block(rs, cs, row_size, col_size);
+            MatrixXd Yn = cy.block(rs, cs, row_size, col_size);
+            MatrixXd Hn = cz.block(rs, cs, row_size, col_size);
+
+            int nsize = Hn.size();
+            VectorXd T = VectorXd::Zero(nsize);
+            MatrixXd P = MatrixXd::Zero(nsize, 3);
+            MatrixXd K = MatrixXd::Zero(nsize, nsize);
+
+            int numK(0);
+            for (auto id = 0; id < Hn.size(); ++id)
+            {
+                double xj = Xn(id);
+                double yj = Yn(id);
+                double zj = Hn(id);
+                if (std::isnan(zj))
+                    continue;
+                numK++;
+                T(id) = zj;
+                P.row(id) << 1, xj, yj;
+                for (auto id2 = 0; id2 < Hn.size(); ++id2)
+                {
+                    if (id == id2)
+                        continue;
+                    double xk = Xn(id2);
+                    double yk = Yn(id2);
+                    double rsqr = (xj - xk) * (xj - xk) + (yj - yk) * (yj - yk);
+                    if (rsqr == 0.0)
+                        continue;
+                    K(id, id2) = rsqr * std::log10(std::sqrt(rsqr));
+                }
+            }
+
+            if (numK < 20)
+                continue;
+
+            MatrixXd A = MatrixXd::Zero(nsize+3, nsize+3);
+            A.block(0,0,nsize,nsize) = K;
+            A.block(0,nsize,nsize,3) = P;
+            A.block(nsize,0,3,nsize) = P.transpose();
+
+            VectorXd b = VectorXd::Zero(nsize+3);
+            b.head(nsize) = T;
+
+            VectorXd x = A.fullPivHouseholderQr().solve(b);
+
+            Vector3d a = x.tail(3);
+            VectorXd w = x.head(nsize);
+
+            double sum = 0.0;
+            double xi2 = cx(outer_row, outer_col);
+            double yi2 = cy(outer_row, outer_col);
+            for (auto j = 0; j < nsize; ++j)
+            {
+                double xj = Xn(j);
+                double yj = Yn(j);
+                double rsqr = (xj - xi2) * (xj - xi2) + (yj - yi2) * (yj - yi2);
+                if (rsqr == 0.0)
+                    continue;
+                sum += w(j) * rsqr * std::log10(std::sqrt(rsqr));
+            }
+
+            S(outer_row, outer_col) = a(0) + a(1)*xi2 + a(2)*yi2 + sum;
+
+            if (!std::isnan(S(outer_row, outer_col)))
+                num_nan_replace++;
+
+            // std::cerr << std::fixed;
+            // std::cerr << std::setprecision(3)
+            //           << std::left
+            //           << "S(" << outer_row << "," << outer_col << "): "
+            //           << std::setw(10)
+            //           << S(outer_row, outer_col)
+            //           // << std::setw(3)
+            //           // << "\tz: "
+            //           // << std::setw(10)
+            //           // << zi
+            //           // << std::setw(7)
+            //           // << "\tzdiff: "
+            //           // << std::setw(5)
+            //           // << zi - S(outer_row, outer_col)
+            //           // << std::setw(7)
+            //           // << "\txdiff: "
+            //           // << std::setw(5)
+            //           // << xi2 - xi
+            //           // << std::setw(7)
+            //           // << "\tydiff: "
+            //           // << std::setw(5)
+            //           // << yi2 - yi
+            //           << std::setw(7)
+            //           << "\t# pts: "
+            //           << std::setw(3)
+            //           << nsize
+            //           << std::setw(5)
+            //           << "\tsum: "
+            //           << std::setw(10)
+            //           << sum
+            //           << std::setw(9)
+            //           << "\tw.sum(): "
+            //           << std::setw(5)
+            //           << w.sum()
+            //           << std::setw(6)
+            //           << "\txsum: "
+            //           << std::setw(5)
+            //           << w.dot(P.col(1))
+            //           << std::setw(6)
+            //           << "\tysum: "
+            //           << std::setw(5)
+            //           << w.dot(P.col(2))
+            //           << std::setw(3)
+            //           << "\ta: "
+            //           << std::setw(8)
+            //           << a.transpose()
+            //           << std::endl;
+        }
+    }
+
+    double frac = static_cast<double>(num_nan_replace);
+    frac /= static_cast<double>(num_nan_detect);
+    log()->get(LogLevel::Info) << "TPS: Filled " << num_nan_replace << " of "
+                               << num_nan_detect << " holes ("
+                               << frac * 100.0 << "%)\n";
+
+    return S;
+}
+
+MatrixXd SMRFilter::expandingTPS(MatrixXd cx, MatrixXd cy, MatrixXd cz)
+{
+    log()->get(LogLevel::Info) << "TPS: Reticulating splines...\n";
+
+    MatrixXd S = cz;
+
+    int num_nan_detect(0);
+    int num_nan_replace(0);
+
+    for (auto outer_col = 0; outer_col < m_numCols; ++outer_col)
+    {
+        for (auto outer_row = 0; outer_row < m_numRows; ++outer_row)
+        {
+            if (!std::isnan(S(outer_row, outer_col)))
+                continue;
+
+            num_nan_detect++;
+
+            // Further optimizations are achieved by estimating only the
+            // interpolated surface within a local neighbourhood (e.g. a 7 x 7
+            // neighbourhood is used in our case) of the cell being filtered.
+            int radius = 3;
+            bool solution = false;
+
+            while (!solution)
+            {
+                // std::cerr << radius;
+                int cs = Utils::clamp(outer_col-radius, 0, m_numCols-1);
+                int ce = Utils::clamp(outer_col+radius, 0, m_numCols-1);
+                int col_size = ce - cs + 1;
+                int rs = Utils::clamp(outer_row-radius, 0, m_numRows-1);
+                int re = Utils::clamp(outer_row+radius, 0, m_numRows-1);
+                int row_size = re - rs + 1;
+
+                MatrixXd Xn = cx.block(rs, cs, row_size, col_size);
+                MatrixXd Yn = cy.block(rs, cs, row_size, col_size);
+                MatrixXd Hn = cz.block(rs, cs, row_size, col_size);
+
+                int nsize = Hn.size();
+                VectorXd T = VectorXd::Zero(nsize);
+                MatrixXd P = MatrixXd::Zero(nsize, 3);
+                MatrixXd K = MatrixXd::Zero(nsize, nsize);
+
+                int numK(0);
+                for (auto id = 0; id < Hn.size(); ++id)
+                {
+                    double xj = Xn(id);
+                    double yj = Yn(id);
+                    double zj = Hn(id);
+                    if (std::isnan(zj))
+                        continue;
+                    numK++;
+                    T(id) = zj;
+                    P.row(id) << 1, xj, yj;
+                    for (auto id2 = 0; id2 < Hn.size(); ++id2)
+                    {
+                        if (id == id2)
+                            continue;
+                        double xk = Xn(id2);
+                        double yk = Yn(id2);
+                        double rsqr = (xj - xk) * (xj - xk) + (yj - yk) * (yj - yk);
+                        if (rsqr == 0.0)
+                            continue;
+                        K(id, id2) = rsqr * std::log10(std::sqrt(rsqr));
+                    }
+                }
+
+                // if (numK < 20)
+                //     continue;
+
+                MatrixXd A = MatrixXd::Zero(nsize+3, nsize+3);
+                A.block(0,0,nsize,nsize) = K;
+                A.block(0,nsize,nsize,3) = P;
+                A.block(nsize,0,3,nsize) = P.transpose();
+
+                VectorXd b = VectorXd::Zero(nsize+3);
+                b.head(nsize) = T;
+
+                VectorXd x = A.fullPivHouseholderQr().solve(b);
+
+                Vector3d a = x.tail(3);
+                VectorXd w = x.head(nsize);
+
+                double sum = 0.0;
+                double xi2 = cx(outer_row, outer_col);
+                double yi2 = cy(outer_row, outer_col);
+                for (auto j = 0; j < nsize; ++j)
+                {
+                    double xj = Xn(j);
+                    double yj = Yn(j);
+                    double rsqr = (xj - xi2) * (xj - xi2) + (yj - yi2) * (yj - yi2);
+                    if (rsqr == 0.0)
+                        continue;
+                    sum += w(j) * rsqr * std::log10(std::sqrt(rsqr));
+                }
+
+                double val = a(0) + a(1)*xi2 + a(2)*yi2 + sum;
+                solution = !std::isnan(val);
+
+                if (!solution)
+                {
+                    std::cerr << "..." << radius << std::endl;;
+                    ++radius;
+                    continue;
+                }
+
+                S(outer_row, outer_col) = val;
+                num_nan_replace++;
+
+                // std::cerr << std::endl;
+
+                // std::cerr << std::fixed;
+                // std::cerr << std::setprecision(3)
+                //           << std::left
+                //           << "S(" << outer_row << "," << outer_col << "): "
+                //           << std::setw(10)
+                //           << S(outer_row, outer_col)
+                //           // << std::setw(3)
+                //           // << "\tz: "
+                //           // << std::setw(10)
+                //           // << zi
+                //           // << std::setw(7)
+                //           // << "\tzdiff: "
+                //           // << std::setw(5)
+                //           // << zi - S(outer_row, outer_col)
+                //           // << std::setw(7)
+                //           // << "\txdiff: "
+                //           // << std::setw(5)
+                //           // << xi2 - xi
+                //           // << std::setw(7)
+                //           // << "\tydiff: "
+                //           // << std::setw(5)
+                //           // << yi2 - yi
+                //           << std::setw(7)
+                //           << "\t# pts: "
+                //           << std::setw(3)
+                //           << nsize
+                //           << std::setw(5)
+                //           << "\tsum: "
+                //           << std::setw(10)
+                //           << sum
+                //           << std::setw(9)
+                //           << "\tw.sum(): "
+                //           << std::setw(5)
+                //           << w.sum()
+                //           << std::setw(6)
+                //           << "\txsum: "
+                //           << std::setw(5)
+                //           << w.dot(P.col(1))
+                //           << std::setw(6)
+                //           << "\tysum: "
+                //           << std::setw(5)
+                //           << w.dot(P.col(2))
+                //           << std::setw(3)
+                //           << "\ta: "
+                //           << std::setw(8)
+                //           << a.transpose()
+                //           << std::endl;
+            }
+        }
+    }
+
+    double frac = static_cast<double>(num_nan_replace);
+    frac /= static_cast<double>(num_nan_detect);
+    log()->get(LogLevel::Info) << "TPS: Filled " << num_nan_replace << " of "
+                               << num_nan_detect << " holes ("
+                               << frac * 100.0 << "%)\n";
+
+    return S;
+}
+
+} // namespace pdal
diff --git a/filters/SMRFilter.hpp b/filters/SMRFilter.hpp
new file mode 100644
index 0000000..23d27b6
--- /dev/null
+++ b/filters/SMRFilter.hpp
@@ -0,0 +1,103 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+#include <Eigen/Dense>
+
+#include <memory>
+#include <unordered_map>
+
+extern "C" int32_t SMRFilter_ExitFunc();
+extern "C" PF_ExitFunc SMRFilter_InitPlugin();
+
+namespace pdal
+{
+
+using namespace Eigen;
+
+class PointLayout;
+class PointView;
+
+class PDAL_DLL SMRFilter : public Filter
+{
+public:
+    SMRFilter() : Filter()
+    {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    bool m_classify;
+    bool m_extract;
+    int m_numRows;
+    int m_numCols;
+    double m_cellSize;
+    double m_cutNet;
+    double m_percentSlope;
+    double m_maxWindow;
+    double m_scalar;
+    double m_threshold;
+    std::string m_outDir;
+
+    virtual void addArgs(ProgramArgs& args);
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual void ready(PointTableRef table);
+
+    MatrixXd inpaintKnn(MatrixXd cx, MatrixXd cy, MatrixXd cz);
+
+    // processGround implements the SMRF algorithm, returning a vector
+    // of ground indices.
+    std::vector<PointId> processGround(PointViewPtr view);
+
+    // progressiveFilter is the core of the SMRF algorithm.
+    MatrixXi progressiveFilter(MatrixXd const& ZImin, double cell_size,
+                               double slope, double max_window);
+
+    virtual PointViewSet run(PointViewPtr view);
+
+    // TPS returns an interpolated matrix using thin plate splines.
+    MatrixXd TPS(MatrixXd cx, MatrixXd cy, MatrixXd cz);
+    MatrixXd expandingTPS(MatrixXd cx, MatrixXd cy, MatrixXd cz);
+
+    SMRFilter& operator=(const SMRFilter&); // not implemented
+    SMRFilter(const SMRFilter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/filters/SampleFilter.cpp b/filters/SampleFilter.cpp
new file mode 100644
index 0000000..e6a5a47
--- /dev/null
+++ b/filters/SampleFilter.cpp
@@ -0,0 +1,126 @@
+/******************************************************************************
+ * Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+ *       names of its contributors may be used to endorse or promote
+ *       products derived from this software without specific prior
+ *       written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#include "SampleFilter.hpp"
+
+#include <pdal/KDIndex.hpp>
+#include <pdal/util/Utils.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("filters.sample", "Subsampling filter",
+               "http://pdal.io/stages/filters.sample.html");
+
+CREATE_STATIC_PLUGIN(1, 0, SampleFilter, Filter, s_info)
+
+std::string SampleFilter::getName() const
+{
+    return s_info.name;
+}
+
+
+void SampleFilter::addArgs(ProgramArgs& args)
+{
+    args.add("radius", "Radius", m_radius, 1.0);
+}
+
+
+void SampleFilter::addDimensions(PointLayoutPtr layout)
+{
+    layout->registerDim(Dimension::Id::Classification);
+}
+
+
+PointViewSet SampleFilter::run(PointViewPtr inView)
+{
+    point_count_t np = inView->size();
+
+    // Return empty PointViewSet if the input PointView has no points.
+    // Otherwise, make a new output PointView.
+    PointViewSet viewSet;
+    if (!np)
+        return viewSet;
+    PointViewPtr outView = inView->makeNew();
+
+    // Build the 3D KD-tree.
+    KD3Index index(*inView);
+    index.build();
+
+    // The result looks much better if we take some time to shuffle the indices.
+    std::srand(std::time(NULL));
+    std::vector<PointId> indices(np);
+    for (PointId i = 0; i < np; ++i)
+        indices[i] = i;
+    std::random_shuffle(indices.begin(), indices.end());
+
+    // All points are marked as kept (1) by default. As they are masked by
+    // neighbors within the user-specified radius, their value is changed to 0.
+    std::vector<int> keep(np, 1);
+
+    // We are able to subsample in a single pass over the shufflled indices.
+    for (auto const& i : indices)
+    {
+        // If a point is masked, it is forever masked, and cannot be part of the
+        // sampled cloud. Otherwise, the current index is appended to the output
+        // PointView.
+        if (keep[i] == 0)
+            continue;
+        outView->appendPoint(*inView, i);
+
+        // We now proceed to mask all neighbors within m_radius of the kept
+        // point.
+        auto ids = index.radius(i, m_radius);
+        for (PointId j = 1; j < ids.size(); ++j)
+            keep[ids[j]] = 0;
+    }
+
+    // Simply calculate the percentage of retained points.
+    double frac = (double)outView->size() / (double)inView->size();
+    log()->get(LogLevel::Debug2) << "Retaining "
+                                 << outView->size() << " of "
+                                 << inView->size() << " points ("
+                                 << 100*frac << "%)\n";
+
+    viewSet.insert(outView);
+    return viewSet;
+}
+
+} // namespace pdal
diff --git a/filters/sample/SampleFilter.hpp b/filters/SampleFilter.hpp
similarity index 100%
rename from filters/sample/SampleFilter.hpp
rename to filters/SampleFilter.hpp
diff --git a/filters/sort/SortFilter.cpp b/filters/SortFilter.cpp
similarity index 100%
rename from filters/sort/SortFilter.cpp
rename to filters/SortFilter.cpp
diff --git a/filters/SortFilter.hpp b/filters/SortFilter.hpp
new file mode 100644
index 0000000..ad3649c
--- /dev/null
+++ b/filters/SortFilter.hpp
@@ -0,0 +1,94 @@
+/******************************************************************************
+* Copyright (c) 2014, Hobu Inc. (hobu at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/PointViewIter.hpp>
+#include <pdal/plugin.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+extern "C" int32_t SortFilter_ExitFunc();
+extern "C" PF_ExitFunc SortFilter_InitPlugin();
+
+namespace pdal
+{
+
+class PDAL_DLL SortFilter : public Filter
+{
+public:
+    SortFilter()
+    {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    // Dimension on which to sort.
+    Dimension::Id m_dim;
+    // Dimension name.
+    std::string m_dimName;
+
+    virtual void addArgs(ProgramArgs& args)
+    {
+        args.add("dimension", "Dimension on which to sort", m_dimName).
+            setPositional();
+    }
+
+    virtual void prepared(PointTableRef table)
+    {
+        m_dim = table.layout()->findDim(m_dimName);
+        if (m_dim == Dimension::Id::Unknown)
+        {
+            std::ostringstream oss;
+            oss << getName() << ": Invalid sort dimension '" << m_dimName <<
+                "'.";
+            throw oss.str();
+        }
+    }
+
+    virtual void filter(PointView& view)
+    {
+        auto cmp = [this](const PointIdxRef& p1, const PointIdxRef& p2)
+            { return p1.compare(m_dim, p2); };
+
+        std::sort(view.begin(), view.end(), cmp);
+    }
+
+    SortFilter& operator=(const SortFilter&) = delete;
+    SortFilter(const SortFilter&) = delete;
+};
+
+} // namespace pdal
diff --git a/filters/SplitterFilter.cpp b/filters/SplitterFilter.cpp
new file mode 100644
index 0000000..52608c6
--- /dev/null
+++ b/filters/SplitterFilter.cpp
@@ -0,0 +1,112 @@
+/******************************************************************************
+ * Copyright (c) 2013, Bradley J Chambers (brad.chambers at gmail.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+ *       names of its contributors may be used to endorse or promote
+ *       products derived from this software without specific prior
+ *       written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#include "SplitterFilter.hpp"
+
+#include <cmath>
+#include <iostream>
+#include <limits>
+
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "filters.splitter",
+    "Split data based on a X/Y box length.",
+    "http://pdal.io/stages/filters.splitter.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, SplitterFilter, Filter, s_info)
+
+SplitterFilter::SplitterFilter() : m_viewMap(CoordCompare())
+{}
+
+std::string SplitterFilter::getName() const { return s_info.name; }
+
+void SplitterFilter::addArgs(ProgramArgs& args)
+{
+    args.add("length", "Edge length of cell", m_length, 1000.0);
+    args.add("origin_x", "X origin for a cell", m_xOrigin,
+        std::numeric_limits<double>::quiet_NaN());
+    args.add("origin_y", "Y origin for a cell", m_yOrigin,
+        std::numeric_limits<double>::quiet_NaN());
+}
+
+
+PointViewSet SplitterFilter::run(PointViewPtr inView)
+{
+    PointViewSet viewSet;
+    if (!inView->size())
+        return viewSet;
+
+    // Use the location of the first point as the origin, unless specified.
+    // (!= test == isnan(), which doesn't exist on windows)
+    if (m_xOrigin != m_xOrigin)
+        m_xOrigin = inView->getFieldAs<double>(Dimension::Id::X, 0);
+    if (m_yOrigin != m_yOrigin)
+        m_yOrigin = inView->getFieldAs<double>(Dimension::Id::Y, 0);
+    // Overlay a grid of squares on the points (m_length sides).  Each square
+    // corresponds to a new point buffer.  Place the points falling in the
+    // each square in the corresponding point buffer.
+    for (PointId idx = 0; idx < inView->size(); idx++)
+    {
+        double x = inView->getFieldAs<double>(Dimension::Id::X, idx);
+        x -= m_xOrigin;
+        int xpos = x / m_length;
+        if (x < 0)
+            xpos--;
+
+        double y = inView->getFieldAs<double>(Dimension::Id::Y, idx);
+        y -= m_yOrigin;
+        int ypos = y / m_length;
+        if (y < 0)
+            ypos--;
+
+        Coord loc(xpos, ypos);
+        PointViewPtr& outView = m_viewMap[loc];
+        if (!outView)
+            outView = inView->makeNew();
+        outView->appendPoint(*inView.get(), idx);
+    }
+
+    // Pull the buffers out of the map and stick them in the standard
+    // output set.
+    for (auto bi = m_viewMap.begin(); bi != m_viewMap.end(); ++bi)
+        viewSet.insert(bi->second);
+    return viewSet;
+}
+
+} // pdal
diff --git a/filters/SplitterFilter.hpp b/filters/SplitterFilter.hpp
new file mode 100644
index 0000000..6711b35
--- /dev/null
+++ b/filters/SplitterFilter.hpp
@@ -0,0 +1,83 @@
+/******************************************************************************
+ * Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+ *       names of its contributors may be used to endorse or promote
+ *       products derived from this software without specific prior
+ *       written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+extern "C" int32_t SplitterFilter_ExitFunc();
+extern "C" PF_ExitFunc SplitterFilter_InitPlugin();
+
+namespace pdal
+{
+
+class PDAL_DLL SplitterFilter : public pdal::Filter
+{
+private:
+    //This used to be a lambda, but the VS compiler exploded, I guess.
+    typedef std::pair<int, int> Coord;
+    class CoordCompare
+    {
+    public:
+        bool operator () (const Coord& c1, const Coord& c2) const
+        {
+            return c1.first < c2.first ? true :
+                c1.first > c2.first ? false :
+                c1.second < c2.second ? true :
+                false;
+        };
+    };
+
+public:
+    SplitterFilter();
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    double m_length;
+    double m_xOrigin;
+    double m_yOrigin;
+    std::map<Coord, PointViewPtr, CoordCompare> m_viewMap;
+
+    virtual void addArgs(ProgramArgs& args);
+    virtual PointViewSet run(PointViewPtr view);
+
+    SplitterFilter& operator=(const SplitterFilter&); // not implemented
+    SplitterFilter(const SplitterFilter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/filters/StatsFilter.cpp b/filters/StatsFilter.cpp
new file mode 100644
index 0000000..67839c4
--- /dev/null
+++ b/filters/StatsFilter.cpp
@@ -0,0 +1,314 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "StatsFilter.hpp"
+
+#include <cmath>
+#include <unordered_map>
+
+#include <pdal/pdal_export.hpp>
+#include <pdal/Options.hpp>
+#include <pdal/Polygon.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <json/json.h>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "filters.stats",
+    "Compute statistics about each dimension (mean, min, max, etc.)",
+    "http://pdal.io/stages/filters.stats.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, StatsFilter, Filter, s_info)
+
+std::string StatsFilter::getName() const { return s_info.name; }
+
+namespace stats
+{
+
+
+void Summary::extractMetadata(MetadataNode &m)
+{
+    uint32_t cnt = static_cast<uint32_t>(count());
+    m.add("count", cnt, "count");
+    m.add("minimum", minimum(), "minimum");
+    m.add("maximum", maximum(), "maximum");
+    m.add("average", average(), "average");
+
+    double std = stddev();
+    if (!std::isinf(std) && !std::isnan(std))
+        m.add("stddev", std, "standard deviation");
+
+    double k = kurtosis();
+    if (!std::isinf(k) && !std::isnan(k))
+        m.add("kurtosis", k, "kurtosis");
+
+    double sk = skewness();
+    if (!std::isinf(sk) && !std::isnan(sk))
+        m.add("skewness", skewness(), "skewness");
+
+    double v = variance();
+    if (!std::isinf(v) && !std::isnan(v))
+        m.add("variance", v, "variance");
+    m.add("name", m_name, "name");
+    if (m_enumerate == Enumerate)
+    {
+        for (auto& v : m_values)
+            m.addList("values", v.first);
+    }
+    else if (m_enumerate == Global)
+    {
+        computeGlobalStats();
+        m.add("median", m_median);
+        m.add("mad", m_mad);
+    }
+    else if (m_enumerate == Count)
+    {
+        for (auto& v : m_values)
+        {
+            std::string val =
+                std::to_string(v.first) + "/" + std::to_string(v.second);
+            m.addList("counts", val);
+        }
+    }
+}
+
+void Summary::computeGlobalStats()
+{
+    auto compute_median = [](std::vector<double> vals)
+    {
+        std::nth_element(vals.begin(), vals.begin()+vals.size()/2, vals.end());
+
+        return *(vals.begin()+vals.size()/2);
+    };
+
+    // TODO add quantiles
+    m_median = compute_median(m_data);
+    std::transform(m_data.begin(), m_data.end(), m_data.begin(),
+       [this](double v) { return std::fabs(v - this->m_median); });
+    m_mad = compute_median(m_data);
+
+
+}
+
+
+} // namespace stats
+
+using namespace stats;
+
+bool StatsFilter::processOne(PointRef& point)
+{
+    for (auto p = m_stats.begin(); p != m_stats.end(); ++p)
+    {
+        Dimension::Id d = p->first;
+        Summary& c = p->second;
+        c.insert(point.getFieldAs<double>(d));
+    }
+    return true;
+}
+
+
+void StatsFilter::filter(PointView& view)
+{
+    PointRef point(view, 0);
+    for (PointId idx = 0; idx < view.size(); ++idx)
+    {
+        point.setPointId(idx);
+        processOne(point);
+    }
+}
+
+
+void StatsFilter::done(PointTableRef table)
+{
+    extractMetadata(table);
+}
+
+
+void StatsFilter::addArgs(ProgramArgs& args)
+{
+    args.add("dimensions", "Dimensions on which to calculate statistics",
+        m_dimNames);
+    args.add("enumerate", "Dimensions whose values should be enumerated",
+        m_enums);
+    args.add("global", "Dimensions to compute global stats (median, mad, mode)",
+        m_global);
+    args.add("count", "Dimensions whose values should be counted", m_counts);
+}
+
+
+void StatsFilter::prepared(PointTableRef table)
+{
+    PointLayoutPtr layout(table.layout());
+    std::unordered_map<std::string, Summary::EnumType> dims;
+
+    auto getWarn([this]()->std::ostream&
+    {
+        return log()->get(LogLevel::Warning);
+    });
+
+    // Add dimensions to the list.
+    if (m_dimNames.empty())
+    {
+        for (auto id : layout->dims())
+            dims[layout->dimName(id)] = Summary::NoEnum;
+    }
+    else
+    {
+        for (auto& s : m_dimNames)
+        {
+            if (layout->findDim(s) == Dimension::Id::Unknown)
+                getWarn() << "Dimension '" << s << "' listed in --dimensions "
+                    "option does not exist.  Ignoring." << std::endl;
+            else
+                dims[s] = Summary::NoEnum;
+        }
+    }
+
+    // Set the enumeration flag for those dimensions specified.
+    for (auto& s : m_enums)
+    {
+        if (dims.find(s) == dims.end())
+            getWarn() << "Dimension '" << s << "' listed in --enumerate option "
+                "does not exist.  Ignoring." << std::endl;
+        else
+            dims[s] = Summary::Enumerate;
+    }
+
+    // Set the count flag for those dimensions specified.
+    for (auto& s : m_counts)
+    {
+        if (dims.find(s) == dims.end())
+            getWarn() << "Dimension '" << s << "' listed in --count option "
+                "does not exist.  Ignoring." << std::endl;
+        else
+            dims[s] = Summary::Count;
+    }
+
+    // Set the global flag for those dimensions specified.
+    for (auto& s : m_global)
+    {
+        if (dims.find(s) == dims.end())
+            getWarn() << "Dimension '" << s << "' listed in --global option "
+                "does not exist.  Ignoring." << std::endl;
+        else
+            dims[s] = Summary::Global;
+    }
+    // Create the summary objects.
+    for (auto& dv : dims)
+        m_stats.insert(std::make_pair(layout->findDim(dv.first),
+            Summary(dv.first, dv.second)));
+}
+
+
+void StatsFilter::extractMetadata(PointTableRef table)
+{
+    uint32_t position(0);
+
+    bool bNoPoints(true);
+    for (auto di = m_stats.begin(); di != m_stats.end(); ++di)
+    {
+        Summary& s = di->second;
+
+        bNoPoints = (bool)s.count();
+
+        MetadataNode t = m_metadata.addList("statistic");
+        t.add("position", position++);
+        s.extractMetadata(t);
+    }
+
+    // If we have X, Y, & Z dims, output bboxes
+    auto xs = m_stats.find(Dimension::Id::X);
+    auto ys = m_stats.find(Dimension::Id::Y);
+    auto zs = m_stats.find(Dimension::Id::Z);
+    if (xs != m_stats.end() &&
+        ys != m_stats.end() &&
+        zs != m_stats.end() &&
+        bNoPoints)
+    {
+        BOX3D box(xs->second.minimum(), ys->second.minimum(), zs->second.minimum(),
+                  xs->second.maximum(), ys->second.maximum(), zs->second.maximum());
+        pdal::Polygon p(box);
+
+        MetadataNode mbox = Utils::toMetadata(box);
+        MetadataNode box_metadata = m_metadata.add("bbox");
+        MetadataNode metadata = box_metadata.add("native");
+
+        Json::Reader jsonReader;
+        Json::Value json;
+        jsonReader.parse(p.json(), json);
+
+        MetadataNode boundary = metadata.addWithType("boundary", json.toStyledString(), "json", "GeoJSON boundary");
+        MetadataNode bbox = metadata.add(mbox);
+        SpatialReference ref = table.anySpatialReference();
+        // if we don't get an SRS from the PointTableRef,
+        // we won't add another metadata node
+        if (!ref.empty())
+        {
+            p.setSpatialReference(ref);
+            SpatialReference epsg4326("EPSG:4326");
+            pdal::Polygon pdd = p.transform(epsg4326);
+            BOX3D ddbox = pdd.bounds();
+            MetadataNode epsg_4326_box = Utils::toMetadata(ddbox);
+            MetadataNode dddbox = box_metadata.add("EPSG:4326");
+            dddbox.add(epsg_4326_box);
+
+            Json::Reader jsonReader;
+            Json::Value json;
+            jsonReader.parse(pdd.json(), json);
+
+            MetadataNode ddboundary = dddbox.addWithType("boundary", json.toStyledString(), "json", "GeoJSON boundary");
+
+
+        }
+    }
+}
+
+
+const Summary& StatsFilter::getStats(Dimension::Id dim) const
+{
+    for (auto di = m_stats.begin(); di != m_stats.end(); ++di)
+    {
+        Dimension::Id d = di->first;
+        if (d == dim)
+            return di->second;
+    }
+    throw pdal_error("Dimension not found");
+}
+
+} // namespace pdal
diff --git a/filters/StatsFilter.hpp b/filters/StatsFilter.hpp
new file mode 100644
index 0000000..ae571bd
--- /dev/null
+++ b/filters/StatsFilter.hpp
@@ -0,0 +1,188 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Filter.hpp>
+#include <pdal/plugin.hpp>
+
+extern "C" int32_t StatsFilter_ExitFunc();
+extern "C" PF_ExitFunc StatsFilter_InitPlugin();
+
+namespace pdal
+{
+namespace stats
+{
+
+class PDAL_DLL Summary
+{
+public:
+    enum EnumType
+    {
+        NoEnum,
+        Enumerate,
+        Count,
+        Global
+    };
+
+typedef std::map<double, point_count_t> EnumMap;
+typedef std::vector<double> DataVector;
+
+public:
+    Summary(std::string name, EnumType enumerate) :
+        m_name(name), m_enumerate(enumerate)
+    { reset(); }
+
+    double minimum() const
+        { return m_min; }
+    double maximum() const
+        { return m_max; }
+    double average() const
+        { return m_avg; }
+    double variance() const
+        { return M2/(m_cnt - 1.0); }
+    double stddev() const
+        { return std::sqrt(variance()); }
+    double skewness() const
+        { return std::sqrt(double(m_cnt)) * M3 / std::pow(M2, 1.5); }
+    double kurtosis() const
+        { return double(m_cnt)*M4 / (M2*M2) - 3.0; }
+    double median() const
+        { return m_median; }
+    double mad() const
+        { return m_mad; }
+    point_count_t count() const
+        { return m_cnt; }
+    std::string name() const
+        { return m_name; }
+    const EnumMap& values() const
+        { return m_values; }
+
+    void extractMetadata(MetadataNode &m);
+    void computeGlobalStats();
+
+    void reset()
+    {
+        m_max = (std::numeric_limits<double>::lowest)();
+        m_min = (std::numeric_limits<double>::max)();
+        m_cnt = 0;
+        m_avg = 0.0;
+        m_median = 0.0;
+        m_mad = 0.0;
+        M1 = M2 = M3 = M4 = 0.0;
+    }
+
+    void insert(double value)
+    {
+        double delta, delta_n, delta_n2, term1;
+
+        point_count_t n1(m_cnt);
+
+        m_cnt++;
+        point_count_t n(m_cnt);
+        m_min = (std::min)(m_min, value);
+        m_max = (std::max)(m_max, value);
+        m_avg += (value - m_avg) / m_cnt;
+        if (m_enumerate != NoEnum)
+            m_values[value]++;
+        if (m_enumerate == Global)
+        {
+            if (m_data.capacity() - m_data.size() < 10000)
+                m_data.reserve(m_data.capacity() + m_cnt);
+            m_data.push_back(value);
+        }
+
+        // stolen from http://www.johndcook.com/blog/skewness_kurtosis/
+
+        n1 = n;
+        delta = value - M1;
+        delta_n = delta / n;
+        delta_n2 = delta_n * delta_n;
+        term1 = delta * delta_n * n1;
+        M1 += delta_n;
+        M4 += term1 * delta_n2 * (n*n - 3*n + 3) +
+            (6 * delta_n2 * M2) - (4 * delta_n * M3);
+        M3 += term1 * delta_n * (n - 2) - 3 * delta_n * M2;
+        M2 += term1;
+    }
+
+private:
+    std::string m_name;
+    EnumType m_enumerate;
+    double m_max;
+    double m_min;
+    double m_avg;
+    double m_mad;
+    double m_median;
+    EnumMap m_values;
+    DataVector m_data;
+    point_count_t m_cnt;
+    double M1, M2, M3, M4;
+};
+
+} // namespace stats
+
+// This is just a pass-through filter, which collects some stats about
+// the points that are fed through it
+class PDAL_DLL StatsFilter : public Filter
+{
+public:
+    StatsFilter() : Filter()
+        {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    const stats::Summary& getStats(Dimension::Id d) const;
+    void reset();
+
+private:
+    StatsFilter& operator=(const StatsFilter&); // not implemented
+    StatsFilter(const StatsFilter&); // not implemented
+    virtual void addArgs(ProgramArgs& args);
+    virtual bool processOne(PointRef& point);
+    virtual void prepared(PointTableRef table);
+    virtual void done(PointTableRef table);
+    virtual void filter(PointView& view);
+    void extractMetadata(PointTableRef table);
+
+    StringList m_dimNames;
+    StringList m_enums;
+    StringList m_counts;
+    StringList m_global;
+    std::map<Dimension::Id, stats::Summary> m_stats;
+};
+
+} // namespace pdal
diff --git a/filters/streamcallback/StreamCallbackFilter.cpp b/filters/StreamCallbackFilter.cpp
similarity index 100%
rename from filters/streamcallback/StreamCallbackFilter.cpp
rename to filters/StreamCallbackFilter.cpp
diff --git a/filters/streamcallback/StreamCallbackFilter.hpp b/filters/StreamCallbackFilter.hpp
similarity index 100%
rename from filters/streamcallback/StreamCallbackFilter.hpp
rename to filters/StreamCallbackFilter.hpp
diff --git a/filters/TransformationFilter.cpp b/filters/TransformationFilter.cpp
new file mode 100644
index 0000000..eb135ce
--- /dev/null
+++ b/filters/TransformationFilter.cpp
@@ -0,0 +1,128 @@
+/******************************************************************************
+* Copyright (c) 2014, Pete Gadomski <pete.gadomski at gmail.com>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "TransformationFilter.hpp"
+
+#include <pdal/pdal_export.hpp>
+#include <pdal/pdal_macros.hpp>
+
+#include <sstream>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "filters.transformation",
+    "Transform each point using a 4x4 transformation matrix",
+    "http://pdal.io/stages/filters.transformation.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, TransformationFilter, Filter, s_info)
+
+std::string TransformationFilter::getName() const { return s_info.name; }
+
+TransformationMatrix transformationMatrixFromString(const std::string& s)
+{
+    std::istringstream iss(s);
+	TransformationMatrix matrix{{ 0 }};
+    double entry;
+    TransformationMatrix::size_type i = 0;
+    while (iss >> entry)
+    {
+        if (i + 1 > matrix.size())
+        {
+            std::stringstream msg;
+            msg << "Too many entries in transformation matrix, should be "
+                << matrix.size();
+            throw pdal_error(msg.str());
+        }
+        matrix[i++] = entry;
+    }
+
+    if (i != matrix.size())
+    {
+        std::stringstream msg;
+        msg << "Too few entries in transformation matrix: "
+            << i
+            << " (should be "
+            << matrix.size()
+            << ")";
+
+        throw pdal_error(msg.str());
+    }
+
+    return matrix;
+}
+
+
+void TransformationFilter::addArgs(ProgramArgs& args)
+{
+    args.add("matrix", "Transformation matrix", m_matrixSpec).setPositional();
+}
+
+
+void TransformationFilter::initialize()
+{
+    m_matrix = transformationMatrixFromString(m_matrixSpec);
+}
+
+
+bool TransformationFilter::processOne(PointRef& point)
+{
+    double x = point.getFieldAs<double>(Dimension::Id::X);
+    double y = point.getFieldAs<double>(Dimension::Id::Y);
+    double z = point.getFieldAs<double>(Dimension::Id::Z);
+
+    point.setField(Dimension::Id::X,
+        x * m_matrix[0] + y * m_matrix[1] + z * m_matrix[2] + m_matrix[3]);
+
+    point.setField(Dimension::Id::Y,
+        x * m_matrix[4] + y * m_matrix[5] + z * m_matrix[6] + m_matrix[7]);
+
+    point.setField(Dimension::Id::Z,
+        x * m_matrix[8] + y * m_matrix[9] + z * m_matrix[10] + m_matrix[11]);
+    return true;
+}
+
+
+void TransformationFilter::filter(PointView& view)
+{
+    PointRef point(view, 0);
+    for (PointId idx = 0; idx < view.size(); ++idx)
+    {
+        point.setPointId(idx);
+        processOne(point);
+    }
+}
+
+} // namespace pdal
diff --git a/filters/transformation/TransformationFilter.hpp b/filters/TransformationFilter.hpp
similarity index 100%
rename from filters/transformation/TransformationFilter.hpp
rename to filters/TransformationFilter.hpp
diff --git a/filters/approximatecoplanar/ApproximateCoplanarFilter.cpp b/filters/approximatecoplanar/ApproximateCoplanarFilter.cpp
deleted file mode 100644
index cb46239..0000000
--- a/filters/approximatecoplanar/ApproximateCoplanarFilter.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "ApproximateCoplanarFilter.hpp"
-
-#include <pdal/Eigen.hpp>
-#include <pdal/KDIndex.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <Eigen/Dense>
-
-#include <string>
-#include <vector>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.approximatecoplanar", "ApproximateCoplanar Filter", 
-               "http://pdal.io/stages/filters.approximatecoplanar.html");
-
-CREATE_STATIC_PLUGIN(1, 0, ApproximateCoplanarFilter, Filter, s_info)
-
-std::string ApproximateCoplanarFilter::getName() const
-{
-    return s_info.name;
-}
-
-
-void ApproximateCoplanarFilter::addArgs(ProgramArgs& args)
-{
-    args.add("knn", "k-Nearest Neighbors", m_knn, 8);
-    args.add("thresh1", "Threshold 1", m_thresh1, 25.0);
-    args.add("thresh2", "Threshold 2", m_thresh1, 6.0);
-}
-
-
-void ApproximateCoplanarFilter::addDimensions(PointLayoutPtr layout)
-{
-    m_coplanar = layout->registerOrAssignDim("Coplanar", Dimension::Type::Unsigned8);
-}
-
-void ApproximateCoplanarFilter::filter(PointView& view)
-{
-    using namespace Eigen;
-
-    KD3Index kdi(view);
-    kdi.build();
-
-    for (PointId i = 0; i < view.size(); ++i)
-    {
-        // find the k-nearest neighbors
-        double x = view.getFieldAs<double>(Dimension::Id::X, i);
-        double y = view.getFieldAs<double>(Dimension::Id::Y, i);
-        double z = view.getFieldAs<double>(Dimension::Id::Z, i);
-        auto ids = kdi.neighbors(x, y, z, m_knn);
-
-        // compute covariance of the neighborhood
-        auto B = computeCovariance(view, ids);
-
-        // perform the eigen decomposition
-        SelfAdjointEigenSolver<Matrix3f> solver(B);
-        if (solver.info() != Success)
-            throw pdal_error("Cannot perform eigen decomposition.");
-        auto ev = solver.eigenvalues();
-
-        // test eigenvalues to label points that are approximately coplanar
-        if ((ev[1] > m_thresh1 * ev[0]) && (m_thresh2 * ev[1] > ev[2]))
-            view.setField(m_coplanar, i, 1);
-        else
-            view.setField(m_coplanar, i, 0);
-    }
-}
-
-} // namespace pdal
diff --git a/filters/approximatecoplanar/CMakeLists.txt b/filters/approximatecoplanar/CMakeLists.txt
deleted file mode 100644
index f71b348..0000000
--- a/filters/approximatecoplanar/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PDAL_ADD_DRIVER(filter approximatecoplanar "ApproximateCoplanarFilter.cpp" "ApproximateCoplanarFilter.hpp" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/attribute/AttributeFilter.cpp b/filters/attribute/AttributeFilter.cpp
deleted file mode 100644
index 47488a5..0000000
--- a/filters/attribute/AttributeFilter.cpp
+++ /dev/null
@@ -1,239 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Howard Butler, howard at hobu.co
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "AttributeFilter.hpp"
-
-#include <memory>
-#include <vector>
-
-#include <pdal/GDALUtils.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/Polygon.hpp>
-#include <pdal/QuadIndex.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "filters.attribute",
-    "Assign values for a dimension using a specified value, \n" \
-        "an OGR-readable data source, or an OGR SQL query.",
-    "http://pdal.io/stages/filters.attribute.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, AttributeFilter, Filter, s_info)
-
-struct OGRDataSourceDeleter
-{
-    template <typename T>
-    void operator()(T* ptr)
-    {
-        if (ptr)
-            ::OGR_DS_Destroy(ptr);
-    }
-};
-
-struct OGRFeatureDeleter
-{
-    template <typename T>
-    void operator()(T* ptr)
-    {
-        if (ptr)
-            ::OGR_F_Destroy(ptr);
-    }
-};
-
-
-void AttributeFilter::addArgs(ProgramArgs& args)
-{
-    args.add("dimension", "Dimension on which to filter", m_dimName).
-        setPositional();
-    m_valArg = &args.add("value", "Value to set on matching points", m_value,
-        std::numeric_limits<double>::quiet_NaN());
-    m_dsArg = &args.add("datasource", "OGR-readable datasource for Polygon or "
-        "Multipolygon data", m_datasource);
-    m_colArg = &args.add("column", "OGR datasource column from which to "
-        "read the attribute.", m_column);
-    m_queryArg = &args.add("query", "OGR SQL query to execute on the "
-        "datasource to fetch geometry and attributes", m_query);
-    m_layerArg = &args.add("layer", "Datasource layer to use", m_layer);
-}
-
-
-void AttributeFilter::initialize()
-{
-    if (m_valArg->set() && m_dsArg->set())
-    {
-        std::ostringstream oss;
-        oss << getName() << ": options 'value' and 'datasource' mutually "
-            "exclusive.";
-        throw pdal_error(oss.str());
-    }
-
-    if (!m_valArg->set() && !m_dsArg->set())
-    {
-        std::ostringstream oss;
-        oss << getName() << ": Either option 'value' or 'datasource' must "
-            "be specified.";
-        throw pdal_error(oss.str());
-    }
-
-    Arg *args[] = { m_colArg, m_queryArg, m_layerArg };
-    for (auto& a : args)
-    {
-        if (m_valArg->set() && a->set())
-        {
-            std::ostringstream oss;
-            oss << getName() << ": option '" << a->longname() << "' invalid "
-                "with option 'value'.";
-            throw pdal_error(oss.str());
-        }
-    }
-    gdal::registerDrivers();
-}
-
-
-void AttributeFilter::prepared(PointTableRef table)
-{
-    m_dim = table.layout()->findDim(m_dimName);
-    if (m_dim == Dimension::Id::Unknown)
-    {
-        std::ostringstream oss;
-        oss << getName() << ": Dimension '" << m_dimName << "' not found.";
-        throw pdal_error(oss.str());
-    }
-}
-
-
-void AttributeFilter::ready(PointTableRef table)
-{
-    if (m_value != m_value)
-    {
-        m_ds = OGRDSPtr(OGROpen(m_datasource.c_str(), 0, 0),
-            OGRDataSourceDeleter());
-        if (!m_ds)
-        {
-            std::ostringstream oss;
-            oss << getName() << ": Unable to open data source '" <<
-                    m_datasource << "'";
-            throw pdal_error(oss.str());
-        }
-    }
-}
-
-
-void AttributeFilter::UpdateGEOSBuffer(PointView& view)
-{
-    QuadIndex idx(view);
-
-    if (m_layer.size())
-        m_lyr = OGR_DS_GetLayerByName(m_ds.get(), m_layer.c_str());
-    else if (m_query.size())
-        m_lyr = OGR_DS_ExecuteSQL(m_ds.get(), m_query.c_str(), 0, 0);
-    else
-        m_lyr = OGR_DS_GetLayer(m_ds.get(), 0);
-
-    if (!m_lyr)
-    {
-        std::ostringstream oss;
-        oss << getName() << ": Unable to select layer '" << m_layer << "'";
-        throw pdal_error(oss.str());
-    }
-
-    OGRFeaturePtr feature = OGRFeaturePtr(OGR_L_GetNextFeature(m_lyr),
-        OGRFeatureDeleter());
-
-    int field_index(1); // default to first column if nothing was set
-    if (m_column.size())
-    {
-        field_index = OGR_F_GetFieldIndex(feature.get(), m_column.c_str());
-        if (field_index == -1)
-        {
-            std::ostringstream oss;
-            oss << getName() << ": No column name '" << m_column <<
-                "' was found.";
-            throw pdal_error(oss.str());
-        }
-    }
-
-    while (feature)
-    {
-        OGRGeometryH geom = OGR_F_GetGeometryRef(feature.get());
-        OGRwkbGeometryType t = OGR_G_GetGeometryType(geom);
-        int32_t fieldVal = OGR_F_GetFieldAsInteger(feature.get(), field_index);
-
-        if (!(t == wkbPolygon ||
-            t == wkbMultiPolygon ||
-            t == wkbPolygon25D ||
-            t == wkbMultiPolygon25D))
-        {
-            std::ostringstream oss;
-            oss << getName() << ": Geometry is not Polygon or MultiPolygon!";
-            throw pdal::pdal_error(oss.str());
-        }
-
-        pdal::Polygon p(geom, view.spatialReference(),
-            geos::ErrorHandler::get());
-
-        // Compute a total bounds for the geometry. Query the QuadTree to
-        // find out the points that are inside the bbox. Then test each
-        // point in the bbox against the prepared geometry.
-        BOX3D box = p.bounds();
-        std::vector<PointId> ids = idx.getPoints(box);
-
-
-        for (const auto& i : ids)
-        {
-            PointRef ref(view, i);
-            if (p.covers(ref))
-                view.setField(m_dim, i, fieldVal);
-        }
-        feature = OGRFeaturePtr(OGR_L_GetNextFeature(m_lyr),
-            OGRFeatureDeleter());
-    }
-}
-
-
-void AttributeFilter::filter(PointView& view)
-{
-    if (m_value == m_value)
-        for (PointId i = 0; i < view.size(); ++i)
-            view.setField(m_dim, i, m_value);
-    else
-        UpdateGEOSBuffer(view);
-}
-
-} // namespace pdal
-
diff --git a/filters/attribute/CMakeLists.txt b/filters/attribute/CMakeLists.txt
deleted file mode 100644
index 788761a..0000000
--- a/filters/attribute/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-#
-# Attribute filter CMake configuration
-#
-
-set(srcs AttributeFilter.cpp)
-set(incs AttributeFilter.hpp)
-
-PDAL_ADD_DRIVER(filter attribute "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/chipper/CMakeLists.txt b/filters/chipper/CMakeLists.txt
deleted file mode 100644
index 9b75e10..0000000
--- a/filters/chipper/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Chipper filter CMake configuration
-#
-
-#
-# Chipper Filter
-#
-set(srcs
-    ChipperFilter.cpp
-)
-
-set(incs
-    ChipperFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter chipper "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/chipper/ChipperFilter.cpp b/filters/chipper/ChipperFilter.cpp
deleted file mode 100644
index 5e01c35..0000000
--- a/filters/chipper/ChipperFilter.cpp
+++ /dev/null
@@ -1,322 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2010, Andrew Bell
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of the Andrew Bell or libLAS nor the names of
- *       its contributors may be used to endorse or promote products derived
- *       from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#include "ChipperFilter.hpp"
-
-#include <iostream>
-#include <limits>
-
-/**
-The objective is to split the region into non-overlapping blocks, each
-containing approximately the same number of points, as specified by the
-user.  We'd also like the blocks closer to square than not.
-
-First, the points are read into arrays - one for the x direction, and one for
-the y direction.  The arrays are sorted and are initialized with indices into
-the other array of the location of the other coordinate of the same point.
-
-Partitions are created that place the maximum number of points in a
-block, subject to the user-defined threshold, using a cumulate and round
-procedure.
-
-The distance of the point-space is checked in each direction and the
-wider dimension is chosen for splitting at an appropriate partition point.
-The points in the narrower direction are copied to locations in the spare
-array at one side or the other of the chosen partition, and that portion
-of the spare array then becomes the active array for the narrow direction.
-This avoids resorting of the arrays, which are already sorted.
-
-This procedure is then recursively applied to the created blocks until
-they contains only one or two partitions.  In the case of one partition,
-we are done, and we simply store away the contents of the block.  If there are
-two partitions in a block, we avoid the recopying the narrow array to the
-spare since the wide array already contains the desired points partitioned
-into two blocks.  We simply need to locate the maximum and minimum values
-from the narrow array so that the approriate extrema of the block can
-be stored.
-**/
-
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "filters.chipper",
-    "Organize points into spatially contiguous, squarish, and non-overlapping chips.",
-    "http://pdal.io/stages/filters.chipper.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, ChipperFilter, Filter, s_info)
-
-std::string ChipperFilter::getName() const { return s_info.name; }
-
-void ChipperFilter::addArgs(ProgramArgs& args)
-{
-    args.add("capacity", "Maximum number of points per cell", m_threshold,
-        (PointId) 5000u);
-}
-
-
-PointViewSet ChipperFilter::run(PointViewPtr view)
-{
-    if (view->size() == 0)
-        return m_outViews;
-
-    m_inView = view;
-    load(*view.get(), m_xvec, m_yvec, m_spare);
-    partition(m_xvec.size());
-    decideSplit(m_xvec, m_yvec, m_spare, 0, m_partitions.size() - 1);
-    return m_outViews;
-}
-
-
-void ChipperFilter::load(PointView& view, ChipRefList& xvec, ChipRefList& yvec,
-    ChipRefList& spare)
-{
-    point_count_t idx;
-    std::vector<ChipPtRef>::iterator it;
-
-    xvec.reserve(view.size());
-    yvec.reserve(view.size());
-    spare.resize(view.size());
-
-    for (PointId i = 0; i < view.size(); ++i)
-    {
-        ChipPtRef xref;
-
-        xref.m_pos = view.getFieldAs<double>(Dimension::Id::X, i);
-        xref.m_ptindex = i;
-        xvec.push_back(xref);
-
-        ChipPtRef yref;
-
-        yref.m_pos = view.getFieldAs<double>(Dimension::Id::Y, i);
-        yref.m_ptindex = i;
-        yvec.push_back(yref);
-    }
-
-    // Sort xvec and assign other index in yvec to sorted indices in xvec.
-    std::stable_sort(xvec.begin(), xvec.end());
-    for (size_t i = 0; i < xvec.size(); ++i)
-    {
-        idx = xvec[i].m_ptindex;
-        yvec[idx].m_oindex = i;
-    }
-
-    // Sort yvec.
-    std::stable_sort(yvec.begin(), yvec.end());
-
-    // Iterate through the yvector, setting the xvector appropriately.
-    for (size_t i = 0; i < yvec.size(); ++i)
-        xvec[yvec[i].m_oindex].m_oindex = i;
-}
-
-
-// Build a list of partitions.  The partition is the size of each block in
-// the x and y directions in number of points.
-void ChipperFilter::partition(point_count_t size)
-{
-    size_t num_partitions;
-
-    num_partitions = size / m_threshold;
-    if (size % m_threshold)
-        num_partitions++;
-
-    // This is a standard statistics cumulate and round.  It distributes
-    // the points into partitions such the "extra" points are reasonably
-    // distributed among the partitions.
-    double total(0.0);
-    double partition_size = static_cast<double>(size) / num_partitions;
-    m_partitions.push_back(0);
-    for (size_t i = 0; i < num_partitions; ++i)
-    {
-        total += partition_size;
-        size_t itotal = lround(total);
-        m_partitions.push_back(itotal);
-    }
-}
-
-
-void ChipperFilter::decideSplit(ChipRefList& v1, ChipRefList& v2, ChipRefList& spare,
-    PointId pleft, PointId pright)
-{
-    double v1range;
-    double v2range;
-    uint32_t left = m_partitions[pleft];
-    uint32_t right = m_partitions[pright] - 1;
-
-    // Decide the wider direction of the block, and split in that direction
-    // to maintain squareness.
-    v1range = v1[right].m_pos - v1[left].m_pos;
-    v2range = v2[right].m_pos - v2[left].m_pos;
-    if (v1range > v2range)
-        split(v1, v2, spare, pleft, pright);
-    else
-        split(v2, v1, spare, pleft, pright);
-}
-
-void ChipperFilter::split(ChipRefList& wide, ChipRefList& narrow, ChipRefList& spare,
-    PointId pleft, PointId pright)
-{
-    PointId lstart;
-    PointId rstart;
-    PointId pcenter;
-    PointId left;
-    PointId right;
-    PointId center;
-
-    left = m_partitions[pleft];
-    right = m_partitions[pright] - 1;
-
-    // There are two cases in which we are done.
-    // 1) We have a distance of two between left and right.
-    // 2) We have a distance of three between left and right.
-
-    if (pright - pleft == 1)
-        emit(wide, left, right);
-    else if (pright - pleft == 2)
-        finalSplit(wide, narrow, pleft, pright);
-    else
-    {
-        pcenter = (pleft + pright) / 2;
-        center = m_partitions[pcenter];
-
-        // We are splitting in the wide direction - split elements in the
-        // narrow array by copying them to the spare array in the correct
-        // partition.  The spare array then becomes the active narrow array
-        // for the [left,right] partition.
-        lstart = left;
-        rstart = center;
-        for (PointId i = left; i <= right; ++i)
-        {
-            if (narrow[i].m_oindex < center)
-            {
-                spare[lstart] = narrow[i];
-                wide[narrow[i].m_oindex].m_oindex = lstart;
-                lstart++;
-            }
-            else
-            {
-                spare[rstart] = narrow[i];
-                wide[narrow[i].m_oindex].m_oindex = rstart;
-                rstart++;
-            }
-        }
-
-        // Save away the direction so we know which array is X and which is Y
-        // so that when we emit, we can properly label the max/min points.
-        Direction dir = narrow.m_dir;
-        spare.m_dir = dir;
-        decideSplit(wide, spare, narrow, pleft, pcenter);
-        decideSplit(wide, spare, narrow, pcenter, pright);
-        narrow.m_dir = dir;
-    }
-}
-
-// In this case the wide array is like we want it.  The narrow array is
-// ordered, but not for our split, so we have to find the max/min entries
-// for each partition in the final split.
-void ChipperFilter::finalSplit(ChipRefList& wide, ChipRefList& narrow,
-    PointId pleft, PointId pright)
-{
-
-    int64_t left1 = -1;
-    int64_t left2 = -1;
-    int64_t right1 = -1;
-    int64_t right2 = -1;
-
-    // It appears we're using int64_t here because we're using -1 as
-    // an indicator.  I'm not 100% sure that i ends up <0, but I don't
-    // think so.  These casts will at least shut up the compiler, but
-    // I think this code should be revisited to use std::vector<uint32_t>::const_iterator
-    // or std::vector<uint32_t>::size_type instead of this int64_t stuff -- hobu 11/15/10
-    int64_t left = m_partitions[pleft];
-    int64_t right = static_cast<int64_t>(m_partitions[pright] - 1);
-    int64_t center = static_cast<int64_t>(m_partitions[pright - 1]);
-
-    // Find left values for the partitions.
-    for (int64_t i = left; i <= right; ++i)
-    {
-        int64_t idx = static_cast<int64_t>(narrow[static_cast<uint32_t>(i)].m_oindex);
-        if (left1 < 0 && (idx < center))
-        {
-            left1 = i;
-            if (left2 >= 0)
-                break;
-        }
-        else if (left2 < 0 && (idx >= center))
-        {
-            left2 = i;
-            if (left1 >= 0)
-                break;
-        }
-    }
-    // Find right values for the partitions.
-    for (int64_t i = right; i >= left; --i)
-    {
-        int64_t idx = static_cast<int64_t>(narrow[static_cast<uint32_t>(i)].m_oindex);
-        if (right1 < 0 && (idx < center))
-        {
-            right1 = i;
-            if (right2 >= 0)
-                break;
-        }
-        else if (right2 < 0 && (idx >= center))
-        {
-            right2 = i;
-            if (right1 >= 0)
-                break;
-        }
-    }
-
-    // Emit results.
-    emit(wide,
-         left,
-         center - 1);
-    emit(wide,
-         center,
-         right);
-}
-
-void ChipperFilter::emit(ChipRefList& wide, PointId widemin, PointId widemax)
-{
-    PointViewPtr view = m_inView->makeNew();
-    for (size_t idx = widemin; idx <= widemax; ++idx)
-        view->appendPoint(*m_inView.get(), wide[idx].m_ptindex);
-
-    m_outViews.insert(view);
-}
-
-} // namespace pdal
-
diff --git a/filters/colorization/CMakeLists.txt b/filters/colorization/CMakeLists.txt
deleted file mode 100644
index 21c35e3..0000000
--- a/filters/colorization/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Colorization filter CMake configuration
-#
-
-#
-# Colorization Filter
-#
-set(srcs
-    ColorizationFilter.cpp
-)
-
-set(incs
-    ColorizationFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter colorization "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/crop/CMakeLists.txt b/filters/crop/CMakeLists.txt
deleted file mode 100644
index 71920eb..0000000
--- a/filters/crop/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Crop filter CMake configuration
-#
-
-#
-# Crop Filter
-#
-set(srcs
-    CropFilter.cpp
-)
-
-set(incs
-    CropFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter crop "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/crop/CropFilter.cpp b/filters/crop/CropFilter.cpp
deleted file mode 100644
index e312055..0000000
--- a/filters/crop/CropFilter.cpp
+++ /dev/null
@@ -1,204 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "CropFilter.hpp"
-
-#include <iomanip>
-
-#include <pdal/PointView.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/Polygon.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <sstream>
-#include <cstdarg>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "filters.crop",
-    "Filter points inside or outside a bounding box or a polygon if PDAL was built with GEOS support.",
-    "http://pdal.io/stages/filters.crop.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, CropFilter, Filter, s_info)
-
-std::string CropFilter::getName() const { return s_info.name; }
-
-CropFilter::CropFilter() : pdal::Filter()
-{
-    m_cropOutside = false;
-}
-
-
-void CropFilter::addArgs(ProgramArgs& args)
-{
-    args.add("outside", "Whether we keep points inside or outside of the "
-        "bounding region", m_cropOutside);
-    args.add("a_srs", "Spatial reference for bounding region", m_assignedSrs);
-    args.add("bounds", "Bounds box for cropped points", m_bounds);
-    args.add("polygon", "Bounding polying for cropped points", m_polys).
-        setErrorText("Invalid polygon specification.  "
-            "Must be valid GeoJSON/WKT");
-}
-
-
-void CropFilter::initialize()
-{
-    // Set geometry from polygons.
-    if (m_polys.size())
-    {
-        m_geoms.clear();
-        for (Polygon& poly : m_polys)
-        {
-            GeomPkg g;
-
-            // Throws if invalid.
-            poly.valid();
-            if (!m_assignedSrs.empty())
-                poly.setSpatialReference(m_assignedSrs);
-            g.m_geom = poly;
-            m_geoms.push_back(g);
-        }
-    }
-}
-
-
-void CropFilter::ready(PointTableRef table)
-{
-    for (auto& geom : m_geoms)
-    {
-        // If we already overrode the SRS, use that instead
-        if (m_assignedSrs.empty())
-            geom.m_geom.setSpatialReference(table.anySpatialReference());
-    }
-}
-
-
-bool CropFilter::processOne(PointRef& point)
-{
-    for (auto& geom : m_geoms)
-        if (!crop(point, geom))
-            return false;
-
-    for (auto& box : m_bounds)
-        if (!crop(point, box.to2d()))
-            return false;
-
-    return true;
-}
-
-
-PointViewSet CropFilter::run(PointViewPtr view)
-{
-    PointViewSet viewSet;
-    SpatialReference srs = view->spatialReference();
-
-    // Don't do anything if no bounds have been specified.
-    if (m_geoms.empty() && m_bounds.empty())
-    {
-        viewSet.insert(view);
-        return viewSet;
-    }
-
-    for (auto& geom : m_geoms)
-    {
-        // If this is the first time through or the SRS has changed,
-        // prepare the crop polygon.
-        if (srs != m_lastSrs)
-        {
-            geom.m_geom = geom.m_geom.transform(srs);
-        }
-
-        PointViewPtr outView = view->makeNew();
-        crop(geom, *view, *outView);
-        viewSet.insert(outView);
-    }
-    m_lastSrs = srs;
-
-    for (auto& box : m_bounds)
-    {
-        PointViewPtr outView = view->makeNew();
-        crop(box.to2d(), *view, *outView);
-        viewSet.insert(outView);
-    }
-    return viewSet;
-}
-
-
-bool CropFilter::crop(PointRef& point, const BOX2D& box)
-{
-    double x = point.getFieldAs<double>(Dimension::Id::X);
-    double y = point.getFieldAs<double>(Dimension::Id::Y);
-
-    // Return true if we're keeping a point.
-    return (m_cropOutside != box.contains(x, y));
-}
-
-
-void CropFilter::crop(const BOX2D& box, PointView& input, PointView& output)
-{
-    PointRef point = input.point(0);
-    for (PointId idx = 0; idx < input.size(); ++idx)
-    {
-        point.setPointId(idx);
-        if (crop(point, box))
-            output.appendPoint(input, idx);
-    }
-}
-
-bool CropFilter::crop(PointRef& point, const GeomPkg& g)
-{
-    bool covers = g.m_geom.covers(point);
-    bool keep = (m_cropOutside != covers);
-    return keep;
-}
-
-void CropFilter::crop(const GeomPkg& g, PointView& input, PointView& output)
-{
-    PointRef point = input.point(0);
-    for (PointId idx = 0; idx < input.size(); ++idx)
-    {
-        point.setPointId(idx);
-        bool covers = g.m_geom.covers(point);
-        bool keep = (m_cropOutside != covers);
-        if (keep)
-            output.appendPoint(input, idx);
-    }
-}
-
-
-
-} // namespace pdal
diff --git a/filters/crop/CropFilter.hpp b/filters/crop/CropFilter.hpp
deleted file mode 100644
index 7314ee4..0000000
--- a/filters/crop/CropFilter.hpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/Polygon.hpp>
-#include <pdal/plugin.hpp>
-
-extern "C" int32_t CropFilter_ExitFunc();
-extern "C" PF_ExitFunc CropFilter_InitPlugin();
-
-namespace pdal
-{
-
-class ProgramArgs;
-
-// removes any points outside of the given range
-// updates the header accordingly
-class PDAL_DLL CropFilter : public Filter
-{
-public:
-    CropFilter();
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    std::vector<std::string> m_boundsTxt;
-    std::vector<Bounds> m_bounds;
-    bool m_cropOutside;
-    std::vector<Polygon> m_polys;
-    SpatialReference m_assignedSrs;
-    SpatialReference m_lastSrs;
-
-    struct GeomPkg
-    {
-        GeomPkg()
-        {}
-
-        Polygon m_geom;
-        Polygon m_geomXform;
-    };
-
-    std::vector<GeomPkg> m_geoms;
-
-    void addArgs(ProgramArgs& args);
-    virtual void initialize();
-    virtual void ready(PointTableRef table);
-    virtual bool processOne(PointRef& point);
-    virtual PointViewSet run(PointViewPtr view);
-    bool crop(PointRef& point, const BOX2D& box);
-    void crop(const BOX2D& box, PointView& input, PointView& output);
-    bool crop(PointRef& point, const GeomPkg& g);
-    void crop(const GeomPkg& g, PointView& input, PointView& output);
-
-    CropFilter& operator=(const CropFilter&); // not implemented
-    CropFilter(const CropFilter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/filters/decimation/CMakeLists.txt b/filters/decimation/CMakeLists.txt
deleted file mode 100644
index 162ba23..0000000
--- a/filters/decimation/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Decimation filter CMake configuration
-#
-
-#
-# Decimation Filter
-#
-set(srcs
-    DecimationFilter.cpp
-)
-
-set(incs
-    DecimationFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter decimation "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/divider/CMakeLists.txt b/filters/divider/CMakeLists.txt
deleted file mode 100644
index 6af45b7..0000000
--- a/filters/divider/CMakeLists.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-#
-# Divider filter CMake configuration
-#
-
-set(srcs
-    DividerFilter.cpp
-)
-
-set(incs
-    DividerFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter divider "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/eigenvalues/CMakeLists.txt b/filters/eigenvalues/CMakeLists.txt
deleted file mode 100644
index b08fda5..0000000
--- a/filters/eigenvalues/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PDAL_ADD_DRIVER(filter eigenvalues "EigenvaluesFilter.cpp" "EigenvaluesFilter.hpp" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/eigenvalues/EigenvaluesFilter.cpp b/filters/eigenvalues/EigenvaluesFilter.cpp
deleted file mode 100644
index b06a8e2..0000000
--- a/filters/eigenvalues/EigenvaluesFilter.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "EigenvaluesFilter.hpp"
-
-#include <pdal/Eigen.hpp>
-#include <pdal/KDIndex.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <Eigen/Dense>
-
-#include <string>
-#include <vector>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.eigenvalues", "Eigenvalues Filter",
-               "http://pdal.io/stages/filters.eigenvalues.html");
-
-CREATE_STATIC_PLUGIN(1, 0, EigenvaluesFilter, Filter, s_info)
-
-std::string EigenvaluesFilter::getName() const
-{
-    return s_info.name;
-}
-
-
-void EigenvaluesFilter::addArgs(ProgramArgs& args)
-{
-    args.add("knn", "k-Nearest neighbors", m_knn, 8);
-}
-
-
-void EigenvaluesFilter::addDimensions(PointLayoutPtr layout)
-{
-    m_e0 = layout->registerOrAssignDim("Eigenvalue0", Dimension::Type::Double);
-    m_e1 = layout->registerOrAssignDim("Eigenvalue1", Dimension::Type::Double);
-    m_e2 = layout->registerOrAssignDim("Eigenvalue2", Dimension::Type::Double);
-}
-
-void EigenvaluesFilter::filter(PointView& view)
-{
-    using namespace Eigen;
-
-    KD3Index kdi(view);
-    kdi.build();
-
-    for (PointId i = 0; i < view.size(); ++i)
-    {
-        // find the k-nearest neighbors
-        double x = view.getFieldAs<double>(Dimension::Id::X, i);
-        double y = view.getFieldAs<double>(Dimension::Id::Y, i);
-        double z = view.getFieldAs<double>(Dimension::Id::Z, i);
-        auto ids = kdi.neighbors(x, y, z, m_knn);
-
-        // compute covariance of the neighborhood
-        auto B = computeCovariance(view, ids);
-
-        // perform the eigen decomposition
-        SelfAdjointEigenSolver<Matrix3f> solver(B);
-        if (solver.info() != Success)
-            throw pdal_error("Cannot perform eigen decomposition.");
-        auto ev = solver.eigenvalues();
-
-        view.setField(m_e0, i, ev[0]);
-        view.setField(m_e1, i, ev[1]);
-        view.setField(m_e2, i, ev[2]);
-    }
-}
-
-} // namespace pdal
diff --git a/filters/estimaterank/CMakeLists.txt b/filters/estimaterank/CMakeLists.txt
deleted file mode 100644
index 21212a1..0000000
--- a/filters/estimaterank/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PDAL_ADD_DRIVER(filter estimaterank "EstimateRankFilter.cpp" "EstimateRankFilter.hpp" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/estimaterank/EstimateRankFilter.cpp b/filters/estimaterank/EstimateRankFilter.cpp
deleted file mode 100644
index 6205c94..0000000
--- a/filters/estimaterank/EstimateRankFilter.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "EstimateRankFilter.hpp"
-
-#include <pdal/Eigen.hpp>
-#include <pdal/KDIndex.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <string>
-#include <vector>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.estimaterank", "EstimateRank Filter", 
-               "http://pdal.io/stages/filters.estimaterank.html");
-
-CREATE_STATIC_PLUGIN(1, 0, EstimateRankFilter, Filter, s_info)
-
-std::string EstimateRankFilter::getName() const
-{
-    return s_info.name;
-}
-
-
-void EstimateRankFilter::addArgs(ProgramArgs& args)
-{
-    args.add("knn", "k-Nearest Neighbors", m_knn, 8);
-    args.add("thresh", "Threshold", m_thresh, 0.01);
-}
-
-
-void EstimateRankFilter::addDimensions(PointLayoutPtr layout)
-{
-    m_rank = layout->registerOrAssignDim("Rank", Dimension::Type::Unsigned8);
-}
-
-void EstimateRankFilter::filter(PointView& view)
-{
-    KD3Index kdi(view);
-    kdi.build();
-
-    for (PointId i = 0; i < view.size(); ++i)
-    {
-        // find the k-nearest neighbors
-        double x = view.getFieldAs<double>(Dimension::Id::X, i);
-        double y = view.getFieldAs<double>(Dimension::Id::Y, i);
-        double z = view.getFieldAs<double>(Dimension::Id::Z, i);
-        auto ids = kdi.neighbors(x, y, z, m_knn);
-
-        view.setField(m_rank, i, computeRank(view, ids, m_thresh));
-    }
-}
-
-} // namespace pdal
diff --git a/filters/ferry/CMakeLists.txt b/filters/ferry/CMakeLists.txt
deleted file mode 100644
index 9c5328b..0000000
--- a/filters/ferry/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Ferry filter CMake configuration
-#
-
-#
-# Ferry Filter
-#
-set(srcs
-    FerryFilter.cpp
-)
-
-set(incs
-    FerryFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter ferry "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/ferry/FerryFilter.cpp b/filters/ferry/FerryFilter.cpp
deleted file mode 100644
index 87aa75a..0000000
--- a/filters/ferry/FerryFilter.cpp
+++ /dev/null
@@ -1,141 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Howard Butler, howard at hobu.co
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "FerryFilter.hpp"
-
-#include <pdal/pdal_export.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "filters.ferry",
-    "Copy date from one dimension to another.",
-    "http://pdal.io/stages/filters.ferry.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, FerryFilter, Filter, s_info)
-
-std::string FerryFilter::getName() const { return s_info.name; }
-
-void FerryFilter::addArgs(ProgramArgs& args)
-{
-    args.add("dimensions", "List of dimensions to ferry",
-        m_dimSpec).setPositional();
-}
-
-
-void FerryFilter::initialize()
-{
-    for (auto& dim : m_dimSpec)
-    {
-        StringList s = Utils::split2(dim, '=');
-        if (s.size() != 2)
-        {
-            std::ostringstream oss;
-            oss << "Invalid dimension specified '" << dim <<
-                "'.  Need <from dimension>=<to dimension>.  See "
-                "documentation for details.";
-            throw pdal_error(oss.str());
-        }
-        Utils::trim(s[0]);
-        Utils::trim(s[1]);
-        if (s[0] == s[1])
-        {
-            std::ostringstream oss;
-            oss << "Can't ferry dimension '" << s[0] << "' to itself.";
-            throw pdal_error(oss.str());
-        }
-        m_name_map[s[0]] = s[1];
-    }
-}
-
-
-void FerryFilter::addDimensions(PointLayoutPtr layout)
-{
-    for (const auto& dim_par : m_name_map)
-    {
-        layout->registerOrAssignDim(dim_par.second, Dimension::Type::Double);
-    }
-}
-
-
-void FerryFilter::prepared(PointTableRef table)
-{
-    for (const auto& dims : m_name_map)
-        if (table.layout()->findDim(dims.first) == Dimension::Id::Unknown)
-        {
-            std::ostringstream oss;
-            oss << "Can't ferry dimension '" << dims.first << "'. "
-                "Dimension doesn't exist.";
-            throw pdal_error(oss.str());
-        }
-}
-
-void FerryFilter::ready(PointTableRef table)
-{
-    const PointLayoutPtr layout(table.layout());
-    for (const auto& dim_par : m_name_map)
-    {
-        Dimension::Id f = layout->findDim(dim_par.first);
-        Dimension::Id t = layout->findDim(dim_par.second);
-        m_dimensions_map.insert(std::make_pair(f,t));
-    }
-}
-
-
-bool FerryFilter::processOne(PointRef& point)
-{
-    for (const auto& dim_par : m_dimensions_map)
-    {
-        double v = point.getFieldAs<double>(dim_par.first);
-        point.setField(dim_par.second, v);
-    }
-    return true;
-}
-
-
-void FerryFilter::filter(PointView& view)
-{
-    PointRef point(view, 0);
-    for (PointId id = 0; id < view.size(); ++id)
-    {
-        point.setPointId(id);
-        processOne(point);
-    }
-}
-
-} // namespace pdal
-
diff --git a/filters/hag/CMakeLists.txt b/filters/hag/CMakeLists.txt
deleted file mode 100644
index c113296..0000000
--- a/filters/hag/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PDAL_ADD_DRIVER(filter hag "HAGFilter.cpp" "HAGFilter.hpp" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/hag/HAGFilter.cpp b/filters/hag/HAGFilter.cpp
deleted file mode 100644
index 6b8b2d1..0000000
--- a/filters/hag/HAGFilter.cpp
+++ /dev/null
@@ -1,117 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "HAGFilter.hpp"
-
-#include <pdal/KDIndex.hpp>
-#include <pdal/pdal_macros.hpp>
-
-#include <string>
-#include <vector>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.hag", "HAG Filter",
-               "http://pdal.io/stages/filters.hag.html");
-
-CREATE_STATIC_PLUGIN(1, 0, HAGFilter, Filter, s_info)
-
-std::string HAGFilter::getName() const
-{
-    return s_info.name;
-}
-
-void HAGFilter::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(Dimension::Id::HeightAboveGround);
-}
-
-void HAGFilter::prepared(PointTableRef table)
-{
-    const PointLayoutPtr layout(table.layout());
-    if (!layout->hasDim(Dimension::Id::Classification))
-        throw pdal_error("HAGFilter: missing Classification dimension in input PointView");
-}
-
-void HAGFilter::filter(PointView& view)
-{
-    PointViewPtr gView = view.makeNew();
-    PointViewPtr ngView = view.makeNew();
-    std::vector<PointId> gIdx, ngIdx;
-
-    // First pass: Separate into ground and non-ground views.
-    for (PointId i = 0; i < view.size(); ++i)
-    {
-        double c = view.getFieldAs<double>(Dimension::Id::Classification, i);
-        if (c == 2)
-        {
-            gView->appendPoint(view, i);
-            gIdx.push_back(i);
-        }
-        else
-        {
-            ngView->appendPoint(view, i);
-            ngIdx.push_back(i);
-        }
-    }
-
-    // Bail if there weren't any points classified as ground.
-    if (gView->size() == 0)
-        throw pdal_error("HAGFilter: the input PointView does not appear to have any points classified as ground");
-
-    // Build the 2D KD-tree.
-    KD2Index kdi(*gView);
-    kdi.build();
-
-    // Second pass: Find Z difference between non-ground points and the nearest 
-    // neighbor (2D) in the ground view.
-    for (PointId i = 0; i < ngView->size(); ++i)
-    {
-        double x0 = ngView->getFieldAs<double>(Dimension::Id::X, i);
-        double y0 = ngView->getFieldAs<double>(Dimension::Id::Y, i);
-        double z0 = ngView->getFieldAs<double>(Dimension::Id::Z, i);
-        auto ids = kdi.neighbors(x0, y0, 1);
-        double z1 = gView->getFieldAs<double>(Dimension::Id::Z, ids[0]);
-        view.setField(Dimension::Id::HeightAboveGround, ngIdx[i], z0 - z1);
-    }
-
-    // Final pass: Ensure that all ground points have height value pegged at 0.
-    for (auto const& i : gIdx)
-        view.setField(Dimension::Id::HeightAboveGround, i, 0.0);
-}
-
-
-} // namespace pdal
diff --git a/filters/merge/CMakeLists.txt b/filters/merge/CMakeLists.txt
deleted file mode 100644
index 2f12079..0000000
--- a/filters/merge/CMakeLists.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-set(srcs MergeFilter.cpp)
-set(incs MergeFilter.hpp)
-
-PDAL_ADD_DRIVER(filter merge "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/mongus/CMakeLists.txt b/filters/mongus/CMakeLists.txt
deleted file mode 100644
index c9c0c18..0000000
--- a/filters/mongus/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PDAL_ADD_DRIVER(filter mongus "MongusFilter.cpp" "MongusFilter.hpp" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/mongus/MongusFilter.cpp b/filters/mongus/MongusFilter.cpp
deleted file mode 100644
index d313687..0000000
--- a/filters/mongus/MongusFilter.cpp
+++ /dev/null
@@ -1,894 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "MongusFilter.hpp"
-
-#include <pdal/pdal_macros.hpp>
-#include <pdal/PipelineManager.hpp>
-#include <buffer/BufferReader.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <Eigen/Dense>
-
-#include "gdal_priv.h" // For File I/O
-#include "gdal_version.h" // For version info
-#include "ogr_spatialref.h"  //For Geographic Information/Transformations
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.mongus", "Mongus and Zalik (2012)",
-               "http://pdal.io/stages/filters.mongus.html");
-
-CREATE_STATIC_PLUGIN(1, 0, MongusFilter, Filter, s_info)
-
-std::string MongusFilter::getName() const
-{
-    return s_info.name;
-}
-
-void MongusFilter::addArgs(ProgramArgs& args)
-{
-    args.add("cell", "Cell size", m_cellSize, 1.0);
-    args.add("k", "Stdev multiplier for threshold", m_k, 3.0);
-    args.add("l", "Max level", m_l, 8);
-    args.add("classify", "Apply classification labels?", m_classify, true);
-    args.add("extract", "Extract ground returns?", m_extract);
-}
-
-void MongusFilter::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(Dimension::Id::Classification);
-}
-
-int MongusFilter::clamp(int t, int min, int max)
-{
-    return ((t < min) ? min : ((t > max) ? max : t));
-}
-
-int MongusFilter::getColIndex(double x, double cell_size)
-{
-    return static_cast<int>(floor((x - m_bounds.minx) / cell_size));
-}
-
-int MongusFilter::getRowIndex(double y, double cell_size)
-{
-    return static_cast<int>(floor((m_maxRow - y) / cell_size));
-}
-
-Eigen::MatrixXd MongusFilter::computeSpline(Eigen::MatrixXd x_prev,
-        Eigen::MatrixXd y_prev,
-        Eigen::MatrixXd z_prev,
-        Eigen::MatrixXd x_samp,
-        Eigen::MatrixXd y_samp)
-{
-    using namespace Eigen;
-
-// maybe make sure that all prevs are the same size, same with samps
-
-    int num_rows = x_samp.rows();
-    int num_cols = x_samp.cols();
-
-    MatrixXd S = MatrixXd::Zero(num_rows, num_cols);
-
-    for (auto outer_col = 0; outer_col < num_cols; ++outer_col)
-    {
-        for (auto outer_row = 0; outer_row < num_rows; ++outer_row)
-        {
-            // Further optimizations are achieved by estimating only the
-            // interpolated surface within a local neighbourhood (e.g. a 7 x 7
-            // neighbourhood is used in our case) of the cell being filtered.
-            int radius = 3;
-
-            int inner_col = std::floor(outer_col/2);
-            int inner_row = std::floor(outer_row/2);
-
-            int cs = clamp(inner_col-radius, 0, z_prev.cols()-1);
-            int ce = clamp(inner_col+radius, 0, z_prev.cols()-1);
-            int col_size = ce - cs + 1;
-            int rs = clamp(inner_row-radius, 0, z_prev.rows()-1);
-            int re = clamp(inner_row+radius, 0, z_prev.rows()-1);
-            int row_size = re - rs + 1;
-
-            MatrixXd Xn = x_prev.block(rs, cs, row_size, col_size);
-            MatrixXd Yn = y_prev.block(rs, cs, row_size, col_size);
-            MatrixXd Hn = z_prev.block(rs, cs, row_size, col_size);
-
-            int nsize = Hn.size();
-            VectorXd T = VectorXd::Zero(nsize);
-            MatrixXd P = MatrixXd::Zero(nsize, 3);
-            MatrixXd K = MatrixXd::Zero(nsize, nsize);
-
-            for (auto id = 0; id < Hn.size(); ++id)
-            {
-                double xj = Xn(id);
-                double yj = Yn(id);
-                double zj = Hn(id);
-                if (std::isnan(xj) || std::isnan(yj) || std::isnan(zj))
-                    continue;
-                T(id) = zj;
-                P.row(id) << 1, xj, yj;
-                for (auto id2 = 0; id2 < Hn.size(); ++id2)
-                {
-                    if (id == id2)
-                        continue;
-                    double xk = Xn(id2);
-                    double yk = Yn(id2);
-                    double zk = Hn(id2);
-                    if (std::isnan(xk) || std::isnan(yk) || std::isnan(zk))
-                        continue;
-                    double rsqr = (xj - xk) * (xj - xk) + (yj - yk) * (yj - yk);
-                    if (rsqr == 0.0)
-                        continue;
-                    K(id, id2) = rsqr * std::log10(std::sqrt(rsqr));
-                }
-            }
-
-            MatrixXd A = MatrixXd::Zero(nsize+3, nsize+3);
-            A.block(0,0,nsize,nsize) = K;
-            A.block(0,nsize,nsize,3) = P;
-            A.block(nsize,0,3,nsize) = P.transpose();
-
-            VectorXd b = VectorXd::Zero(nsize+3);
-            b.head(nsize) = T;
-
-            VectorXd x = A.fullPivHouseholderQr().solve(b);
-
-            Vector3d a = x.tail(3);
-            VectorXd w = x.head(nsize);
-
-            double sum = 0.0;
-            double xi2 = x_samp(outer_row, outer_col);
-            double yi2 = y_samp(outer_row, outer_col);
-            for (auto j = 0; j < nsize; ++j)
-            {
-                double xj = Xn(j);
-                double yj = Yn(j);
-                double zj = Hn(j);
-                if (std::isnan(xj) || std::isnan(yj) || std::isnan(zj))
-                    continue;
-                double rsqr = (xj - xi2) * (xj - xi2) + (yj - yi2) * (yj - yi2);
-                if (rsqr == 0.0)
-                    continue;
-                sum += w(j) * rsqr * std::log10(std::sqrt(rsqr));
-            }
-
-            S(outer_row, outer_col) = a(0) + a(1)*xi2 + a(2)*yi2 + sum;
-        }
-    }
-
-    return S;
-}
-
-void MongusFilter::writeMatrix(Eigen::MatrixXd data, std::string filename, double cell_size, PointViewPtr view)
-{
-    int cols = data.cols();
-    int rows = data.rows();
-
-    GDALAllRegister();
-
-    GDALDataset *mpDstDS(0);
-
-    char **papszMetadata;
-
-    // parse the format driver, hardcoded for the time being
-    std::string tFormat("GTIFF");
-    const char *pszFormat = tFormat.c_str();
-    GDALDriver* tpDriver = GetGDALDriverManager()->GetDriverByName(pszFormat);
-
-    // try to create a file of the requested format
-    if (tpDriver != NULL)
-    {
-        papszMetadata = tpDriver->GetMetadata();
-        if (CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATE, FALSE))
-        {
-            char **papszOptions = NULL;
-
-            mpDstDS = tpDriver->Create(filename.c_str(), cols, rows, 1,
-                                       GDT_Float32, papszOptions);
-
-            // set the geo transformation
-            double adfGeoTransform[6];
-            adfGeoTransform[0] = m_bounds.minx; // - 0.5*m_GRID_DIST_X;
-            adfGeoTransform[1] = cell_size;
-            adfGeoTransform[2] = 0.0;
-            adfGeoTransform[3] = m_bounds.maxy; // + 0.5*m_GRID_DIST_Y;
-            adfGeoTransform[4] = 0.0;
-            adfGeoTransform[5] = -1 * cell_size;
-            mpDstDS->SetGeoTransform(adfGeoTransform);
-
-            // set the projection
-            mpDstDS->SetProjection(view->spatialReference().getWKT().c_str());
-        }
-    }
-
-    // if we have a valid file
-    if (mpDstDS)
-    {
-        // loop over the raster and determine max slope at each location
-        int cs = 0, ce = cols;
-        int rs = 0, re = rows;
-        float *poRasterData = new float[cols*rows];
-        for (auto i=0; i<cols*rows; i++)
-        {
-            poRasterData[i] = std::numeric_limits<float>::min();
-        }
-
-        #pragma omp parallel for
-        for (auto c = cs; c < ce; ++c)
-        {
-            for (auto r = rs; r < re; ++r)
-            {
-                if (data(r, c) == 0.0 || std::isnan(data(r, c)) || data(r, c) == std::numeric_limits<double>::max())
-                    continue;
-                poRasterData[(r * cols) + c] =
-                    data(r, c);
-            }
-        }
-
-        // write the data
-        if (poRasterData)
-        {
-            GDALRasterBand *tBand = mpDstDS->GetRasterBand(1);
-
-            tBand->SetNoDataValue(std::numeric_limits<float>::min());
-
-            if (cols > 0 && rows > 0)
-#if GDAL_VERSION_MAJOR <= 1
-                tBand->RasterIO(GF_Write, 0, 0, cols, rows,
-                                poRasterData, cols, rows,
-                                GDT_Float32, 0, 0);
-#else
-
-                int ret = tBand->RasterIO(GF_Write, 0, 0, cols, rows,
-                                          poRasterData, cols, rows,
-                                          GDT_Float32, 0, 0, 0);
-#endif
-        }
-
-        GDALClose((GDALDatasetH) mpDstDS);
-
-        delete [] poRasterData;
-    }
-}
-
-void MongusFilter::writeControl(Eigen::MatrixXd cx, Eigen::MatrixXd cy, Eigen::MatrixXd cz, std::string filename)
-{
-    using namespace Dimension;
-
-    PipelineManager m;
-
-    PointTable table;
-    PointViewPtr view(new PointView(table));
-
-    table.layout()->registerDim(Id::X);
-    table.layout()->registerDim(Id::Y);
-    table.layout()->registerDim(Id::Z);
-
-    PointId i = 0;
-    for (auto j = 0; j < cz.size(); ++j)
-    {
-        if (std::isnan(cx(j)) || std::isnan(cy(j)) || std::isnan(cz(j)))
-            continue;
-        view->setField(Id::X, i, cx(j));
-        view->setField(Id::Y, i, cy(j));
-        view->setField(Id::Z, i, cz(j));
-        i++;
-    }
-
-    BufferReader r;
-    r.addView(view);
-
-    Stage& w = m.makeWriter(filename, "writers.las", r);
-    w.prepare(table);
-    w.execute(table);
-}
-
-std::vector<PointId> MongusFilter::processGround(PointViewPtr view)
-{
-    using namespace Eigen;
-
-    point_count_t np(view->size());
-
-    std::vector<PointId> groundIdx;
-
-    // initialization
-
-    view->calculateBounds(m_bounds);
-
-    m_numCols =
-        static_cast<int>(ceil((m_bounds.maxx - m_bounds.minx)/m_cellSize)) + 1;
-    m_numRows =
-        static_cast<int>(ceil((m_bounds.maxy - m_bounds.miny)/m_cellSize)) + 1;
-    m_maxRow = m_bounds.miny + m_numRows * m_cellSize;
-
-    // create control points matrix at default cell size
-    MatrixXd cx(m_numRows, m_numCols);
-    cx.setConstant(std::numeric_limits<double>::quiet_NaN());
-
-    MatrixXd cy(m_numRows, m_numCols);
-    cy.setConstant(std::numeric_limits<double>::quiet_NaN());
-
-    MatrixXd cz(m_numRows, m_numCols);
-    cz.setConstant(std::numeric_limits<double>::max());
-
-    // find initial set of Z minimums at native resolution
-    for (point_count_t i = 0; i < np; ++i)
-    {
-        using namespace Dimension;
-        double x = view->getFieldAs<double>(Id::X, i);
-        double y = view->getFieldAs<double>(Id::Y, i);
-        double z = view->getFieldAs<double>(Id::Z, i);
-
-        int c = clamp(getColIndex(x, m_cellSize), 0, m_numCols-1);
-        int r = clamp(getRowIndex(y, m_cellSize), 0, m_numRows-1);
-
-        if (z < cz(r, c))
-        {
-            cx(r, c) = x;
-            cy(r, c) = y;
-            cz(r, c) = z;
-        }
-    }
-
-    writeControl(cx, cy, cz, "grid_mins.laz");
-
-    // In our case, 2D structural elements of circular shape are employed and
-    // sufficient accuracy is achieved by using a larger window size for opening
-    // (W11) than for closing (W9).
-    MatrixXd mo = matrixOpen(cz, 11);
-    writeControl(cx, cy, mo, "grid_open.laz");
-    MatrixXd mc = matrixClose(mo, 9);
-    writeControl(cx, cy, mc, "grid_close.laz");
-
-    // ...in order to minimize the distortions caused by such filtering, the
-    // output points ... are compared to C and only ci with significantly lower
-    // elevation [are] replaced... In our case, d = 1.0 m was used.
-    for (auto i = 0; i < cz.size(); ++i)
-    {
-        if ((mc(i) - cz(i)) >= 1.0)
-            cz(i) = mc(i);
-    }
-    // cz is still at native resolution, with low points replaced by morphological operators
-    writeControl(cx, cy, cz, "grid_mins_adjusted.laz");
-
-    // downsample control at max_level
-    int level = m_l;
-    double cur_cell_size = m_cellSize * std::pow(2, level);
-    // for max level = 8 and cell size 1, this is 256
-
-    MatrixXd x_prev, y_prev, z_prev;
-
-    // Top-level control samples are assumed to be ground points, no filtering
-    // is applied.
-    downsampleMin(&cx, &cy, &cz, &x_prev, &y_prev, &z_prev, cur_cell_size);
-    // x|y|z_prev are control points downsampled to coarsest resolution for the hierarchy, e.g., for 512x512, this would be 2x2
-    writeControl(x_prev, y_prev, z_prev, "control_init.laz");
-
-    // Point-filtering is performed iteratively at each level of the
-    // control-points hierarchy in a top-down fashion
-    for (auto l = level-1; l > 0; --l)
-    {
-        std::cerr << "Level " << l << std::endl;
-        cur_cell_size /= 2;
-        // 128, 64, 32, 16, 8, 4, 1
-
-        // compute TPS with update control at level
-
-        // The interpolated surface is estimated based on the filtered set of
-        // TPS control-points at the previous level of hierarchy
-        // MatrixXd surface = TPS(x_prev, y_prev, z_prev, cur_cell_size);
-        // 4x4, 8x8, 16x16, 32x32, 64x64, 128x128, 256x256
-
-        // downsample control at level
-        MatrixXd x_samp, y_samp, z_samp;
-        downsampleMin(&cx, &cy, &cz, &x_samp, &y_samp, &z_samp, cur_cell_size);
-        // 4x4, 8x8, 16x16, 32x32, 64x64, 128x128, 256x256
-
-        MatrixXd surface = computeSpline(x_prev, y_prev, z_prev, x_samp, y_samp);
-
-        // if (l == 3)
-        // {
-        //     log()->get(LogLevel::Debug) << cx.rows() << "\t" << cx.cols() << std::endl;
-        //     log()->get(LogLevel::Debug) << x_prev.rows() << "\t" << x_prev.cols() << std::endl;
-        //     log()->get(LogLevel::Debug) << x_samp.rows() << "\t" << x_samp.cols() << std::endl;
-        //     log()->get(LogLevel::Debug) << surface.rows() << "\t" << surface.cols() << std::endl;
-        //     log()->get(LogLevel::Debug) << "x: " << cx.row(1) << std::endl;
-        //     log()->get(LogLevel::Debug) << "z: " << cz.row(1) << std::endl;
-        //     log()->get(LogLevel::Debug) << "control_x: " << x_prev.row(0) << std::endl;
-        //     log()->get(LogLevel::Debug) << "control_z: " << z_prev.row(0) << std::endl;
-        //     log()->get(LogLevel::Debug) << "samples_x: " << x_samp.row(0) << std::endl;
-        //     log()->get(LogLevel::Debug) << "samples_z: " << z_samp.row(0) << std::endl;
-        //     log()->get(LogLevel::Debug) << "spline: " << surface.row(0) << std::endl;
-        // }
-
-        char bufs[256];
-        sprintf(bufs, "cur_control_%d.laz", l);
-        std::string names(bufs);
-        writeControl(x_samp, y_samp, z_samp, names);
-
-        MatrixXd R = z_samp - surface;
-
-        if (l == 7)
-            log()->get(LogLevel::Debug) << R << std::endl;
-
-        double sum = 0.0;
-        double maxcoeff = std::numeric_limits<double>::lowest();
-        double mincoeff = std::numeric_limits<double>::max();
-        for (auto i = 0; i < R.size(); ++i)
-        {
-            if (std::isnan(R(i)))
-                continue;
-            if (R(i) > maxcoeff)
-                maxcoeff = R(i);
-            if (R(i) < mincoeff)
-                mincoeff = R(i);
-            sum += R(i);
-        }
-
-        log()->get(LogLevel::Debug) << "R: max=" << maxcoeff
-                                    << "; min=" << mincoeff
-                                    << "; sum=" << sum
-                                    << "; size=" << R.size() << std::endl;
-
-        // median takes an unsorted vector, possibly containing NANs, and
-        // returns the median value.
-        auto median = [&](std::vector<double> vals)
-        {
-            // Begin by partitioning the vector by isnan.
-            auto ptr = std::partition(vals.begin(), vals.end(), [](double p)
-            {
-                return std::isnan(p);
-            });
-
-            // Copy the actual values, thus eliminating NANs, and sort it.
-            std::vector<double> cp(ptr, vals.end());
-            std::sort(cp.begin(), cp.end());
-
-            std::cerr << "median troubleshooting\n";
-            std::cerr << vals.size() << "\t" << cp.size() << std::endl;
-            std::cerr << cp.size() % 2 << std::endl;
-            std::cerr << cp[cp.size()/2-1] << "\t" << cp[cp.size()/2] << std::endl;
-            if (l == 7)
-            {
-                for (auto const& v : cp)
-                    std::cerr << v << ", ";
-                std::cerr << std::endl;
-            }
-
-            // Compute the median value. For even sized vectors, this is the
-            // average of the midpoints, otherwise it is the midpoint.
-            double median = 0.0;
-            if (cp.size() % 2 == 0)
-                median = (cp[cp.size()/2-1]+cp[cp.size()/2])/2;
-            else
-                median = cp[cp.size()/2];
-
-            return median;
-        };
-
-        // Compute median of residuals.
-        std::vector<double> allres(R.data(), R.data()+R.size());
-        double m = median(allres);
-
-        // Compute absolute difference of the residuals from the median.
-        ArrayXd ad = (R.array()-m).abs();
-
-        // Compute median of absolute differences, with scale factor (1.4862)
-        // for a normal distribution.
-        std::vector<double> absdiff(ad.data(), ad.data()+ad.size());
-        double mad = 1.4862 * median(absdiff);
-
-        // Divide absolute differences by MAD. Values greater than 2 are
-        // considered outliers.
-        MatrixXd M = (ad / mad).matrix();
-
-        sum = 0.0;
-        maxcoeff = std::numeric_limits<double>::lowest();
-        mincoeff = std::numeric_limits<double>::max();
-        for (auto i = 0; i < M.size(); ++i)
-        {
-            if (std::isnan(M(i)))
-                continue;
-            if (M(i) > maxcoeff)
-                maxcoeff = M(i);
-            if (M(i) < mincoeff)
-                mincoeff = M(i);
-            sum += M(i);
-        }
-
-        log()->get(LogLevel::Debug) << "M: max=" << maxcoeff
-                                    << "; min=" << mincoeff
-                                    << "; sum=" << sum
-                                    << "; size=" << M.size() << std::endl;
-
-        double madthresh = 2.0;
-        // Just computing the percent outlier FYI.
-        double perc = static_cast<double>((M.array() > madthresh).count());
-        perc /= static_cast<double>(R.size());
-        perc *= 100.0;
-        log()->get(LogLevel::Debug) << "median=" << m
-                                    << "; MAD=" << mad
-                                    << "; " << (M.array() > madthresh).count()
-                                    << " outliers out of " << R.size()
-                                    << " control points (" << perc << "%)\n";
-
-        // If the TPS control-point is recognized as a non-ground point, it is
-        // replaced by the interpolated point. The time complexity of the
-        // approach is reduced by filtering only the control-points in each
-        // iteration.
-        if (l < 3)
-        {
-            for (auto i = 0; i < M.size(); ++i)
-            {
-                if (M(i) > madthresh)
-                    z_samp(i) = std::numeric_limits<double>::quiet_NaN();
-                // z_samp(i) = surface(i);
-            }
-        }
-
-        if (log()->getLevel() > LogLevel::Debug5)
-        {
-            char buffer[256];
-            sprintf(buffer, "interp_surface_%d.laz", l);
-            std::string name(buffer);
-            // writeMatrix(surface, name, cur_cell_size, view);
-            writeControl(x_samp, y_samp, surface, name);
-
-            char bufm[256];
-            sprintf(bufm, "master_control_%d.laz", l);
-            std::string namem(bufm);
-            writeControl(cx, cy, cz, namem);
-
-            // this is identical to filtered control when written here - should move it...
-            char buf3[256];
-            sprintf(buf3, "prev_control_%d.laz", l);
-            std::string name3(buf3);
-            writeControl(x_prev, y_prev, z_prev, name3);
-
-            char rbuf[256];
-            sprintf(rbuf, "residual_%d.laz", l);
-            std::string rbufn(rbuf);
-            // writeMatrix(R, rbufn, cur_cell_size, view);
-            writeControl(x_samp, y_samp, R, rbufn);
-
-            char mbuf[256];
-            sprintf(mbuf, "median_%d.laz", l);
-            std::string mbufn(mbuf);
-            // writeMatrix(M, mbufn, cur_cell_size, view);
-            writeControl(x_samp, y_samp, M, mbufn);
-
-            char buf2[256];
-            sprintf(buf2, "adjusted_control_%d.laz", l);
-            std::string name2(buf2);
-            writeControl(x_samp, y_samp, z_samp, name2);
-        }
-
-        x_prev = x_samp;
-        y_prev = y_samp;
-        z_prev = z_samp;
-    }
-
-    MatrixXd surface = computeSpline(x_prev, y_prev, z_prev, cx, cy);
-
-    if (log()->getLevel() > LogLevel::Debug5)
-    {
-        //     writeControl(cx, cy, mc, "closed.laz");
-        //
-        char buffer[256];
-        sprintf(buffer, "final_surface.tif");
-        std::string name(buffer);
-        writeMatrix(surface, name, m_cellSize, view);
-        //
-        //     char rbuf[256];
-        //     sprintf(rbuf, "final_residual.tif");
-        //     std::string rbufn(rbuf);
-        //     writeMatrix(R, rbufn, cur_cell_size, view);
-        //
-        //     char obuf[256];
-        //     sprintf(obuf, "final_opened.tif");
-        //     std::string obufn(obuf);
-        //     writeMatrix(maxZ, obufn, cur_cell_size, view);
-        //
-        //     char Tbuf[256];
-        //     sprintf(Tbuf, "final_tophat.tif");
-        //     std::string Tbufn(Tbuf);
-        //     writeMatrix(T, Tbufn, cur_cell_size, view);
-        //
-        //     char tbuf[256];
-        //     sprintf(tbuf, "final_thresh.tif");
-        //     std::string tbufn(tbuf);
-        //     writeMatrix(t, tbufn, cur_cell_size, view);
-    }
-
-    // apply final filtering (top hat) using raw points against TPS
-
-    // ...the LiDAR points are filtered only at the bottom level.
-    for (point_count_t i = 0; i < np; ++i)
-    {
-        using namespace Dimension;
-
-        double x = view->getFieldAs<double>(Id::X, i);
-        double y = view->getFieldAs<double>(Id::Y, i);
-        double z = view->getFieldAs<double>(Id::Z, i);
-
-        int c = clamp(getColIndex(x, cur_cell_size), 0, m_numCols-1);
-        int r = clamp(getRowIndex(y, cur_cell_size), 0, m_numRows-1);
-
-        double res = z - surface(r, c);
-        if (res < 1.0)
-            groundIdx.push_back(i);
-    }
-
-    return groundIdx;
-}
-
-void MongusFilter::downsampleMin(Eigen::MatrixXd *cx, Eigen::MatrixXd *cy,
-                                 Eigen::MatrixXd* cz, Eigen::MatrixXd *dcx,
-                                 Eigen::MatrixXd *dcy, Eigen::MatrixXd* dcz,
-                                 double cell_size)
-{
-    int nr = ceil(cz->rows() / cell_size);
-    int nc = ceil(cz->cols() / cell_size);
-
-    // std::cerr << nr << "\t" << nc << "\t" << cell_size << std::endl;
-
-    dcx->resize(nr, nc);
-    dcx->setConstant(std::numeric_limits<double>::quiet_NaN());
-
-    dcy->resize(nr, nc);
-    dcy->setConstant(std::numeric_limits<double>::quiet_NaN());
-
-    dcz->resize(nr, nc);
-    dcz->setConstant(std::numeric_limits<double>::max());
-
-    for (auto c = 0; c < cz->cols(); ++c)
-    {
-        for (auto r = 0; r < cz->rows(); ++r)
-        {
-            if ((*cz)(r, c) == std::numeric_limits<double>::max())
-                continue;
-
-            int rr = std::floor(r/cell_size);
-            int cc = std::floor(c/cell_size);
-
-            if ((*cz)(r, c) < (*dcz)(rr, cc))
-            {
-                (*dcx)(rr, cc) = (*cx)(r, c);
-                (*dcy)(rr, cc) = (*cy)(r, c);
-                (*dcz)(rr, cc) = (*cz)(r, c);
-            }
-        }
-    }
-}
-
-Eigen::MatrixXd MongusFilter::padMatrix(Eigen::MatrixXd d, int r)
-{
-    using namespace Eigen;
-
-    MatrixXd out = MatrixXd::Zero(d.rows()+2*r, d.cols()+2*r);
-    out.block(r, r, d.rows(), d.cols()) = d;
-    out.block(r, 0, d.rows(), r) =
-        d.block(0, 0, d.rows(), r).rowwise().reverse();
-    out.block(r, d.cols()+r, d.rows(), r) =
-        d.block(0, d.cols()-r, d.rows(), r).rowwise().reverse();
-    out.block(0, 0, r, out.cols()) =
-        out.block(r, 0, r, out.cols()).colwise().reverse();
-    out.block(d.rows()+r, 0, r, out.cols()) =
-        out.block(out.rows()-r, 0, r, out.cols()).colwise().reverse();
-
-    return out;
-}
-
-Eigen::MatrixXd MongusFilter::matrixOpen(Eigen::MatrixXd data, int radius)
-{
-    using namespace Eigen;
-
-    MatrixXd data2 = padMatrix(data, radius);
-
-    int nrows = data2.rows();
-    int ncols = data2.cols();
-
-    // first min, then max of min
-    MatrixXd minZ = MatrixXd::Constant(nrows, ncols,
-                                       std::numeric_limits<double>::max());
-    MatrixXd maxZ = MatrixXd::Constant(nrows, ncols,
-                                       std::numeric_limits<double>::lowest());
-    for (auto c = 0; c < ncols; ++c)
-    {
-        for (auto r = 0; r < nrows; ++r)
-        {
-            int cs = clamp(c-radius, 0, ncols-1);
-            int ce = clamp(c+radius, 0, ncols-1);
-            int rs = clamp(r-radius, 0, nrows-1);
-            int re = clamp(r+radius, 0, nrows-1);
-
-            for (auto col = cs; col <= ce; ++col)
-            {
-                for (auto row = rs; row <= re; ++row)
-                {
-                    if ((row-r)*(row-r)+(col-c)*(col-c) > radius*radius)
-                        continue;
-                    if (data2(row, col) < minZ(r, c))
-                        minZ(r, c) = data2(row, col);
-                }
-            }
-        }
-    }
-    for (auto c = 0; c < ncols; ++c)
-    {
-        for (auto r = 0; r < nrows; ++r)
-        {
-            int cs = clamp(c-radius, 0, ncols-1);
-            int ce = clamp(c+radius, 0, ncols-1);
-            int rs = clamp(r-radius, 0, nrows-1);
-            int re = clamp(r+radius, 0, nrows-1);
-
-            for (auto col = cs; col <= ce; ++col)
-            {
-                for (auto row = rs; row <= re; ++row)
-                {
-                    if ((row-r)*(row-r)+(col-c)*(col-c) > radius*radius)
-                        continue;
-                    if (minZ(row, col) > maxZ(r, c))
-                        maxZ(r, c) = minZ(row, col);
-                }
-            }
-        }
-    }
-
-    return maxZ.block(radius, radius, data.rows(), data.cols());
-}
-
-Eigen::MatrixXd MongusFilter::matrixClose(Eigen::MatrixXd data, int radius)
-{
-    using namespace Eigen;
-
-    MatrixXd data2 = padMatrix(data, radius);
-
-    int nrows = data2.rows();
-    int ncols = data2.cols();
-
-    // first min, then max of min
-    MatrixXd minZ = MatrixXd::Constant(nrows, ncols,
-                                       std::numeric_limits<double>::max());
-    MatrixXd maxZ = MatrixXd::Constant(nrows, ncols,
-                                       std::numeric_limits<double>::lowest());
-    for (auto c = 0; c < ncols; ++c)
-    {
-        for (auto r = 0; r < nrows; ++r)
-        {
-            int cs = clamp(c-radius, 0, ncols-1);
-            int ce = clamp(c+radius, 0, ncols-1);
-            int rs = clamp(r-radius, 0, nrows-1);
-            int re = clamp(r+radius, 0, nrows-1);
-
-            for (auto col = cs; col <= ce; ++col)
-            {
-                for (auto row = rs; row <= re; ++row)
-                {
-                    if ((row-r)*(row-r)+(col-c)*(col-c) > radius*radius)
-                        continue;
-                    if (data2(row, col) > maxZ(r, c))
-                        maxZ(r, c) = data2(row, col);
-                }
-            }
-        }
-    }
-    for (auto c = 0; c < ncols; ++c)
-    {
-        for (auto r = 0; r < nrows; ++r)
-        {
-            int cs = clamp(c-radius, 0, ncols-1);
-            int ce = clamp(c+radius, 0, ncols-1);
-            int rs = clamp(r-radius, 0, nrows-1);
-            int re = clamp(r+radius, 0, nrows-1);
-
-            for (auto col = cs; col <= ce; ++col)
-            {
-                for (auto row = rs; row <= re; ++row)
-                {
-                    if ((row-r)*(row-r)+(col-c)*(col-c) > radius*radius)
-                        continue;
-                    if (maxZ(row, col) < minZ(r, c))
-                        minZ(r, c) = maxZ(row, col);
-                }
-            }
-        }
-    }
-
-    return minZ.block(radius, radius, data.rows(), data.cols());
-}
-
-PointViewSet MongusFilter::run(PointViewPtr view)
-{
-    bool logOutput = log()->getLevel() > LogLevel::Debug1;
-    if (logOutput)
-        log()->floatPrecision(8);
-    log()->get(LogLevel::Debug2) << "Process MongusFilter...\n";
-
-    std::vector<PointId> idx = processGround(view);
-    std::cerr << idx.size() << std::endl;
-
-    PointViewSet viewSet;
-
-    if (!idx.empty() && (m_classify || m_extract))
-    {
-
-        if (m_classify)
-        {
-            log()->get(LogLevel::Debug2) << "Labeled " << idx.size() << " ground returns!\n";
-
-            // set the classification label of ground returns as 2
-            // (corresponding to ASPRS LAS specification)
-            for (const auto& i : idx)
-            {
-                view->setField(Dimension::Id::Classification, i, 2);
-            }
-
-            viewSet.insert(view);
-        }
-
-        if (m_extract)
-        {
-            log()->get(LogLevel::Debug2) << "Extracted " << idx.size() << " ground returns!\n";
-
-            // create new PointView containing only ground returns
-            PointViewPtr output = view->makeNew();
-            for (const auto& i : idx)
-            {
-                output->appendPoint(*view, i);
-            }
-
-            viewSet.erase(view);
-            viewSet.insert(output);
-        }
-    }
-    else
-    {
-        if (idx.empty())
-            log()->get(LogLevel::Debug2) << "Filtered cloud has no ground returns!\n";
-
-        if (!(m_classify || m_extract))
-            log()->get(LogLevel::Debug2) << "Must choose --classify or --extract\n";
-
-        // return the view buffer unchanged
-        viewSet.insert(view);
-    }
-
-    return viewSet;
-}
-
-} // namespace pdal
diff --git a/filters/mongus/MongusFilter.hpp b/filters/mongus/MongusFilter.hpp
deleted file mode 100644
index 2dbc888..0000000
--- a/filters/mongus/MongusFilter.hpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/plugin.hpp>
-
-#include <Eigen/Dense>
-
-#include <memory>
-#include <unordered_map>
-
-extern "C" int32_t MongusFilter_ExitFunc();
-extern "C" PF_ExitFunc MongusFilter_InitPlugin();
-
-namespace pdal
-{
-
-class PointLayout;
-class PointView;
-
-typedef std::unordered_map<int, std::vector<PointId>> PointIdHash;
-
-class PDAL_DLL MongusFilter : public Filter
-{
-public:
-    MongusFilter() : Filter()
-    {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    bool m_classify;
-    bool m_extract;
-    int m_numRows;
-    int m_numCols;
-    int m_maxRow;
-    double m_cellSize;
-    double m_k;
-    int m_l;
-    BOX2D m_bounds;
-
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual void addArgs(ProgramArgs& args);
-    int clamp(int t, int min, int max);
-    int getColIndex(double x, double cell_size);
-    int getRowIndex(double y, double cell_size);
-    void writeMatrix(Eigen::MatrixXd data, std::string filename,
-                     double cell_size, PointViewPtr view);
-    Eigen::MatrixXd computeSpline(Eigen::MatrixXd x_prev,
-                                  Eigen::MatrixXd y_prev,
-                                  Eigen::MatrixXd z_prev,
-                                  Eigen::MatrixXd x_samp,
-                                  Eigen::MatrixXd y_samp);
-    void writeControl(Eigen::MatrixXd cx, Eigen::MatrixXd cy, Eigen::MatrixXd cz, std::string filename);
-    Eigen::MatrixXd padMatrix(Eigen::MatrixXd data, int radius);
-    Eigen::MatrixXd matrixOpen(Eigen::MatrixXd data, int radius);
-    Eigen::MatrixXd matrixClose(Eigen::MatrixXd data, int radius);
-    void downsampleMin(Eigen::MatrixXd *cx, Eigen::MatrixXd *cy,
-                       Eigen::MatrixXd* cz, Eigen::MatrixXd *dcx,
-                       Eigen::MatrixXd *dcy, Eigen::MatrixXd* dcz,
-                       double cell_size);
-    std::vector<PointId> processGround(PointViewPtr view);
-    virtual PointViewSet run(PointViewPtr view);
-
-    MongusFilter& operator=(const MongusFilter&); // not implemented
-    MongusFilter(const MongusFilter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/filters/mortonorder/CMakeLists.txt b/filters/mortonorder/CMakeLists.txt
deleted file mode 100644
index 47b3e77..0000000
--- a/filters/mortonorder/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# MortonOrder filter CMake configuration
-#
-
-#
-# MortonOrder Filter
-#
-set(srcs
-    MortonOrderFilter.cpp
-)
-
-set(incs
-    MortonOrderFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter mortonorder "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/normal/CMakeLists.txt b/filters/normal/CMakeLists.txt
deleted file mode 100644
index 567c537..0000000
--- a/filters/normal/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PDAL_ADD_DRIVER(filter normal "NormalFilter.cpp" "NormalFilter.hpp" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/normal/NormalFilter.cpp b/filters/normal/NormalFilter.cpp
deleted file mode 100644
index cf3fec1..0000000
--- a/filters/normal/NormalFilter.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "NormalFilter.hpp"
-
-#include <pdal/Eigen.hpp>
-#include <pdal/KDIndex.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <Eigen/Dense>
-
-#include <string>
-#include <vector>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.normal", "Normal Filter", 
-               "http://pdal.io/stages/filters.normal.html");
-
-CREATE_STATIC_PLUGIN(1, 0, NormalFilter, Filter, s_info)
-
-std::string NormalFilter::getName() const
-{
-    return s_info.name;
-}
-
-
-void NormalFilter::addArgs(ProgramArgs& args)
-{
-    args.add("knn", "k-Nearest Neighbors", m_knn, 8);
-}
-
-
-void NormalFilter::addDimensions(PointLayoutPtr layout)
-{
-    m_nx = layout->registerOrAssignDim("NormalX", Dimension::Type::Double);
-    m_ny = layout->registerOrAssignDim("NormalY", Dimension::Type::Double);
-    m_nz = layout->registerOrAssignDim("NormalZ", Dimension::Type::Double);
-    m_curvature = layout->registerOrAssignDim("Curvature", Dimension::Type::Double);
-}
-
-void NormalFilter::filter(PointView& view)
-{
-    using namespace Eigen;
-
-    KD3Index kdi(view);
-    kdi.build();
-
-    for (PointId i = 0; i < view.size(); ++i)
-    {
-        // find the k-nearest neighbors
-        double x = view.getFieldAs<double>(Dimension::Id::X, i);
-        double y = view.getFieldAs<double>(Dimension::Id::Y, i);
-        double z = view.getFieldAs<double>(Dimension::Id::Z, i);
-        auto ids = kdi.neighbors(x, y, z, m_knn);
-
-        // compute covariance of the neighborhood
-        auto B = computeCovariance(view, ids);
-
-        // perform the eigen decomposition
-        SelfAdjointEigenSolver<Matrix3f> solver(B);
-        if (solver.info() != Success)
-            throw pdal_error("Cannot perform eigen decomposition.");
-        auto eval = solver.eigenvalues();
-        auto evec = solver.eigenvectors().col(0);
-
-        view.setField(m_nx, i, evec[0]);
-        view.setField(m_ny, i, evec[1]);
-        view.setField(m_nz, i, evec[2]);
-
-        double sum = eval[0] + eval[1] + eval[2];
-        if (sum != 0)
-            view.setField(m_curvature, i, std::fabs(eval[0]/sum));
-        else
-            view.setField(m_curvature, i, 0);
-    }
-}
-
-} // namespace pdal
diff --git a/filters/outlier/CMakeLists.txt b/filters/outlier/CMakeLists.txt
deleted file mode 100644
index d78a90f..0000000
--- a/filters/outlier/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PDAL_ADD_DRIVER(filter outlier "OutlierFilter.cpp" "OutlierFilter.hpp" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/outlier/OutlierFilter.cpp b/filters/outlier/OutlierFilter.cpp
deleted file mode 100644
index db1f401..0000000
--- a/filters/outlier/OutlierFilter.cpp
+++ /dev/null
@@ -1,237 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
- *       names of its contributors may be used to endorse or promote
- *       products derived from this software without specific prior
- *       written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#include "OutlierFilter.hpp"
-
-#include <pdal/KDIndex.hpp>
-#include <pdal/util/Utils.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <string>
-#include <vector>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.outlier", "Outlier removal",
-               "http://pdal.io/stages/filters.outlier.html");
-
-CREATE_STATIC_PLUGIN(1, 0, OutlierFilter, Filter, s_info)
-
-std::string OutlierFilter::getName() const
-{
-    return s_info.name;
-}
-
-
-void OutlierFilter::addArgs(ProgramArgs& args)
-{
-    args.add("method", "Method [default: statistical]", m_method,
-        "statistical");
-    args.add("min_k", "Minimum number of neighbors in radius", m_minK, 2);
-    args.add("radius", "Radius", m_radius, 1.0);
-    args.add("mean_k", "Mean number of neighbors", m_meanK, 8);
-    args.add("multiplier", "Standard deviation threshold", m_multiplier, 2.0);
-    args.add("classify", "Apply classification labels?", m_classify, true);
-    args.add("extract", "Extract ground returns?", m_extract);
-}
-
-
-void OutlierFilter::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(Dimension::Id::Classification);
-}
-
-
-Indices OutlierFilter::processRadius(PointViewPtr inView)
-{
-    KD3Index index(*inView);
-    index.build();
-
-    point_count_t np = inView->size();
-
-    std::vector<PointId> inliers, outliers;
-
-    for (PointId i = 0; i < np; ++i)
-    {
-        double x = inView->getFieldAs<double>(Dimension::Id::X, i);
-        double y = inView->getFieldAs<double>(Dimension::Id::Y, i);
-        double z = inView->getFieldAs<double>(Dimension::Id::Z, i);
-
-        auto ids = index.radius(x, y, z, m_radius);
-        if (ids.size() > size_t(m_minK))
-            inliers.push_back(i);
-        else
-            outliers.push_back(i);
-    }
-
-    return Indices{inliers, outliers};
-}
-
-
-Indices OutlierFilter::processStatistical(PointViewPtr inView)
-{
-    KD3Index index(*inView);
-    index.build();
-
-    point_count_t np = inView->size();
-
-    std::vector<PointId> inliers, outliers;
-
-    std::vector<double> distances(np);
-    for (PointId i = 0; i < np; ++i)
-    {
-        double x = inView->getFieldAs<double>(Dimension::Id::X, i);
-        double y = inView->getFieldAs<double>(Dimension::Id::Y, i);
-        double z = inView->getFieldAs<double>(Dimension::Id::Z, i);
-
-        // we increase the count by one because the query point itself will
-        // be included with a distance of 0
-        point_count_t count = m_meanK + 1;
-
-        std::vector<PointId> indices(count);
-        std::vector<double> sqr_dists(count);
-        index.knnSearch(x, y, z, count, &indices, &sqr_dists);
-
-        double dist_sum = 0.0;
-        for (auto const& d : sqr_dists)
-            dist_sum += sqrt(d);
-        distances[i] = dist_sum / m_meanK;
-    }
-
-    double sum = 0.0, sq_sum = 0.0;
-    for (auto const& d : distances)
-    {
-        sum += d;
-        sq_sum += d * d;
-    }
-    double mean = sum / np;
-    double variance = (sq_sum - sum * sum / np) / (np - 1);
-    double stdev = sqrt(variance);
-    double threshold = mean + m_multiplier * stdev;
-
-    for (PointId i = 0; i < np; ++i)
-    {
-        if (distances[i] < threshold)
-            inliers.push_back(i);
-        else
-            outliers.push_back(i);
-    }
-
-    return Indices{inliers, outliers};
-}
-
-
-PointViewSet OutlierFilter::run(PointViewPtr inView)
-{
-    PointViewSet viewSet;
-    if (!inView->size())
-        return viewSet;
-
-    Indices indices;
-    if (Utils::iequals(m_method, "statistical"))
-    {
-        indices = processStatistical(inView);
-    }
-    else if (Utils::iequals(m_method, "radius"))
-    {
-        indices = processRadius(inView);
-    }
-    else
-    {
-        log()->get(LogLevel::Warning) << "Requested method is unrecognized. "
-                                      << "Please choose from \"statistical\" " << "or \"radius\".\n";
-        viewSet.insert(inView);
-        return viewSet;
-    }
-
-    if (indices.inliers.empty())
-    {
-        log()->get(LogLevel::Warning) << "Requested filter would remove all "
-                                      << "points. Try a larger radius/smaller " << "minimum neighbors.\n";
-        viewSet.insert(inView);
-        return viewSet;
-    }
-
-    if (!indices.outliers.empty() && (m_classify || m_extract))
-    {
-        if (m_classify)
-        {
-            log()->get(LogLevel::Debug2) << "Labeled "
-                                         << indices.outliers.size()
-                                         << " outliers as noise!\n";
-
-            // set the classification label of outlier returns as 18
-            // (corresponding to ASPRS LAS specification for high noise)
-            for (const auto& i : indices.outliers)
-                inView->setField(Dimension::Id::Classification, i, 18);
-
-            viewSet.insert(inView);
-        }
-
-        if (m_extract)
-        {
-            log()->get(LogLevel::Debug2) << "Extracted "
-                                         << indices.inliers.size()
-                                         << " inliers!\n";
-
-            // create new PointView containing only outliers
-            PointViewPtr output = inView->makeNew();
-            for (const auto& i : indices.inliers)
-                output->appendPoint(*inView, i);
-
-            viewSet.erase(inView);
-            viewSet.insert(output);
-        }
-    }
-    else
-    {
-        if (indices.outliers.empty())
-            log()->get(LogLevel::Warning) << "Filtered cloud has no "
-                                          << "outliers!\n";
-
-        if (!(m_classify || m_extract))
-            log()->get(LogLevel::Warning) << "Must choose --classify or "
-                                          << "--extract\n";
-
-        // return the input buffer unchanged
-        viewSet.insert(inView);
-    }
-
-    return viewSet;
-}
-
-} // namespace pdal
diff --git a/filters/pmf/CMakeLists.txt b/filters/pmf/CMakeLists.txt
deleted file mode 100644
index 8deae2a..0000000
--- a/filters/pmf/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PDAL_ADD_DRIVER(filter pmf "PMFFilter.cpp" "PMFFilter.hpp" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/pmf/PMFFilter.cpp b/filters/pmf/PMFFilter.cpp
deleted file mode 100644
index 88e4eec..0000000
--- a/filters/pmf/PMFFilter.cpp
+++ /dev/null
@@ -1,249 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "PMFFilter.hpp"
-
-#include <pdal/KDIndex.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.pmf", "Progressive morphological filter",
-               "http://pdal.io/stages/filters.pmf.html");
-
-CREATE_STATIC_PLUGIN(1, 0, PMFFilter, Filter, s_info)
-
-std::string PMFFilter::getName() const
-{
-    return s_info.name;
-}
-
-
-void PMFFilter::addArgs(ProgramArgs& args)
-{
-    args.add("max_window_size", "Maximum window size", m_maxWindowSize, 33.0);
-    args.add("slope", "Slope", m_slope, 1.0);
-    args.add("max_distance", "Maximum distance", m_maxDistance, 2.5);
-    args.add("initial_distance", "Initial distance", m_initialDistance, 0.15);
-    args.add("cell_size", "Cell size", m_cellSize, 1.0);
-    args.add("classify", "Apply classification labels?", m_classify, true);
-    args.add("extract", "Extract ground returns?", m_extract);
-    args.add("approximate", "Use approximate algorithm?", m_approximate);
-}
-
-
-void PMFFilter::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(Dimension::Id::Classification);
-}
-
-std::vector<double> PMFFilter::morphOpen(PointViewPtr view, float radius)
-{
-    point_count_t np(view->size());
-
-    KD2Index index(*view);
-    index.build();
-
-    std::vector<double> minZ(np), maxZ(np);
-    typedef std::vector<PointId> PointIdVec;
-    std::map<PointId, PointIdVec> neighborMap;
-
-    // erode
-    for (PointId i = 0; i < np; ++i)
-    {
-        double x = view->getFieldAs<double>(Dimension::Id::X, i);
-        double y = view->getFieldAs<double>(Dimension::Id::Y, i);
-        auto ids = index.radius(x, y, radius);
-
-        // neighborMap.insert(std::pair<PointId, std::vector<PointId>(i, ids));
-        neighborMap[i] = ids;
-        double localMin(std::numeric_limits<double>::max());
-        for (auto const& j : ids)
-        {
-            double z = view->getFieldAs<double>(Dimension::Id::Z, j);
-            if (z < localMin)
-                localMin = z;
-        }
-        minZ[i] = localMin;
-    }
-
-    // dilate
-    for (PointId i = 0; i < np; ++i)
-    {
-        auto ids = neighborMap[i];
-        double localMax(std::numeric_limits<double>::lowest());
-        for (auto const& j : ids)
-        {
-            double z = minZ[j];
-            if (z > localMax)
-                localMax = z;
-        }
-        maxZ[i] = localMax;
-    }
-
-    return maxZ;
-}
-
-std::vector<PointId> PMFFilter::processGround(PointViewPtr view)
-{
-    point_count_t np(view->size());
-
-    // Compute the series of window sizes and height thresholds
-    std::vector<float> height_thresholds;
-    std::vector<float> window_sizes;
-    int iteration = 0;
-    float window_size = 0.0f;
-    float height_threshold = 0.0f;
-
-    while (window_size < m_maxWindowSize)
-    {
-        // Determine the initial window size.
-        if (1) // exponential
-            window_size = m_cellSize * (2.0f * std::pow(2, iteration) + 1.0f);
-        else
-            window_size = m_cellSize * (2.0f * (iteration+1) * 2 + 1.0f);
-
-        // Calculate the height threshold to be used in the next iteration.
-        if (iteration == 0)
-            height_threshold = m_initialDistance;
-        else
-            height_threshold = m_slope * (window_size - window_sizes[iteration-1]) * m_cellSize + m_initialDistance;
-
-        // Enforce max distance on height threshold
-        if (height_threshold > m_maxDistance)
-            height_threshold = m_maxDistance;
-
-        window_sizes.push_back(window_size);
-        height_thresholds.push_back(height_threshold);
-
-        iteration++;
-    }
-
-    std::vector<PointId> groundIdx;
-    for (PointId i = 0; i < np; ++i)
-        groundIdx.push_back(i);
-
-    // Progressively filter ground returns using morphological open
-    for (size_t j = 0; j < window_sizes.size(); ++j)
-    {
-        // Limit filtering to those points currently considered ground returns
-        PointViewPtr ground = view->makeNew();
-        for (PointId i = 0; i < groundIdx.size(); ++i)
-            ground->appendPoint(*view, groundIdx[i]);
-
-        printf("      Iteration %ld (height threshold = %f, window size = %f)...",
-               j, height_thresholds[j], window_sizes[j]);
-
-        // Create new cloud to hold the filtered results. Apply the morphological
-        // opening operation at the current window size.
-        auto maxZ = morphOpen(ground, window_sizes[j]*0.5);
-
-        // Find indices of the points whose difference between the source and
-        // filtered point clouds is less than the current height threshold.
-        std::vector<PointId> pt_indices;
-        for (PointId i = 0; i < ground->size(); ++i)
-        {
-            double z0 = ground->getFieldAs<double>(Dimension::Id::Z, i);
-            double z1 = maxZ[i];
-            float diff = z0 - z1;
-            if (diff < height_thresholds[j])
-                pt_indices.push_back(groundIdx[i]);
-        }
-        groundIdx.swap(pt_indices);
-    }
-
-    return groundIdx;
-}
-
-PointViewSet PMFFilter::run(PointViewPtr input)
-{
-    bool logOutput = log()->getLevel() > LogLevel::Debug1;
-    if (logOutput)
-        log()->floatPrecision(8);
-    log()->get(LogLevel::Debug2) << "Process PMFFilter...\n";
-
-    auto idx = processGround(input);
-
-    PointViewSet viewSet;
-    if (!idx.empty() && (m_classify || m_extract))
-    {
-
-        if (m_classify)
-        {
-            log()->get(LogLevel::Debug2) << "Labeled " << idx.size() << " ground returns!\n";
-
-            // set the classification label of ground returns as 2
-            // (corresponding to ASPRS LAS specification)
-            for (const auto& i : idx)
-            {
-                input->setField(Dimension::Id::Classification, i, 2);
-            }
-
-            viewSet.insert(input);
-        }
-
-        if (m_extract)
-        {
-            log()->get(LogLevel::Debug2) << "Extracted " << idx.size() << " ground returns!\n";
-
-            // create new PointView containing only ground returns
-            PointViewPtr output = input->makeNew();
-            for (const auto& i : idx)
-            {
-                output->appendPoint(*input, i);
-            }
-
-            viewSet.erase(input);
-            viewSet.insert(output);
-        }
-    }
-    else
-    {
-        if (idx.empty())
-            log()->get(LogLevel::Debug2) << "Filtered cloud has no ground returns!\n";
-
-        if (!(m_classify || m_extract))
-            log()->get(LogLevel::Debug2) << "Must choose --classify or --extract\n";
-
-        // return the input buffer unchanged
-        viewSet.insert(input);
-    }
-
-    return viewSet;
-}
-
-} // namespace pdal
diff --git a/filters/pmf/PMFFilter.hpp b/filters/pmf/PMFFilter.hpp
deleted file mode 100644
index a480384..0000000
--- a/filters/pmf/PMFFilter.hpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/plugin.hpp>
-
-#include <memory>
-
-extern "C" int32_t PMFFilter_ExitFunc();
-extern "C" PF_ExitFunc PMFFilter_InitPlugin();
-
-namespace pdal
-{
-
-class Options;
-class PointLayout;
-class PointTable;
-class PointView;
-
-class PDAL_DLL PMFFilter : public Filter
-{
-public:
-    PMFFilter() : Filter()
-    {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    double m_maxWindowSize;
-    double m_slope;
-    double m_maxDistance;
-    double m_initialDistance;
-    double m_cellSize;
-    bool m_classify;
-    bool m_extract;
-    bool m_approximate;
-
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual void addArgs(ProgramArgs& args);
-    std::vector<double> morphOpen(PointViewPtr view, float radius);
-    std::vector<PointId> processGround(PointViewPtr view);
-    virtual PointViewSet run(PointViewPtr view);
-
-    PMFFilter& operator=(const PMFFilter&); // not implemented
-    PMFFilter(const PMFFilter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/filters/private/crop/Point.cpp b/filters/private/crop/Point.cpp
new file mode 100644
index 0000000..d1f476e
--- /dev/null
+++ b/filters/private/crop/Point.cpp
@@ -0,0 +1,124 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "Point.hpp"
+
+namespace pdal
+{
+
+namespace
+{
+
+const double LOWEST = (std::numeric_limits<double>::lowest)();
+const double HIGHEST = (std::numeric_limits<double>::max)();
+
+}
+
+namespace cropfilter
+{
+
+Point::Point()
+    : Geometry()
+    , x(LOWEST)
+    , y(LOWEST)
+    , z(LOWEST)
+{
+
+};
+
+Point::Point(const std::string& wkt_or_json, SpatialReference ref)
+    : Geometry(wkt_or_json, ref)
+{
+
+}
+
+void Point::update(const std::string& wkt_or_json, SpatialReference ref)
+{
+
+    Geometry::update(wkt_or_json, ref);
+
+    int t = GEOSGeomTypeId_r(m_geoserr.ctx(), m_geom.get());
+    if (t == -1)
+        throw pdal_error("Unable to fetch geometry point type");
+    if (t > 0)
+        throw pdal_error("Geometry type is not point!");
+
+    int nGeometries = GEOSGetNumGeometries_r(m_geoserr.ctx(), m_geom.get());
+    if (nGeometries > 1)
+        throw pdal_error("Geometry count is > 1!");
+
+    const GEOSGeometry* g = GEOSGetGeometryN_r(m_geoserr.ctx(), m_geom.get(), 0);
+
+    GEOSCoordSequence const* coords = GEOSGeom_getCoordSeq_r(m_geoserr.ctx(),  g);
+
+    uint32_t numInputDims;
+    GEOSCoordSeq_getDimensions_r(m_geoserr.ctx(), coords, &numInputDims);
+
+    uint32_t count(0);
+    GEOSCoordSeq_getSize_r(m_geoserr.ctx(), coords, &count);
+    if (count == 0)
+        throw pdal_error("No coordinates in geometry!");
+
+    for (unsigned i = 0; i < count; ++i)
+    {
+        GEOSCoordSeq_getOrdinate_r(m_geoserr.ctx(), coords, i, 0, &x);
+        GEOSCoordSeq_getOrdinate_r(m_geoserr.ctx(), coords, i, 1, &y);
+        if (numInputDims > 2)
+            GEOSCoordSeq_getOrdinate_r(m_geoserr.ctx(), coords, i, 2, &z);
+    }
+
+}
+
+
+void Point::clear()
+{
+    x = LOWEST; y = LOWEST; z = LOWEST;
+}
+
+
+bool Point::empty() const
+{
+    return  x == LOWEST && y == LOWEST && z == LOWEST;
+}
+
+
+bool Point::is3d() const
+{
+    return (z != LOWEST );
+}
+
+} //namespace cropfilter
+
+} //namespace pdal
+
diff --git a/filters/private/crop/Point.hpp b/filters/private/crop/Point.hpp
new file mode 100644
index 0000000..6115a42
--- /dev/null
+++ b/filters/private/crop/Point.hpp
@@ -0,0 +1,67 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Geometry.hpp>
+
+namespace pdal
+{
+
+
+namespace cropfilter
+{
+
+class PDAL_DLL Point : public Geometry
+{
+public:
+
+    Point();
+    Point(const std::string& wkt_or_json,
+           SpatialReference ref);
+    bool is3d() const;
+    bool empty() const;
+    void clear();
+
+
+    virtual void update(const std::string& wkt_or_json,
+        SpatialReference ref = SpatialReference());
+
+    double x;
+    double y;
+    double z;
+
+};
+} // namespace cropfilter
+} // namespace pdal
diff --git a/filters/randomize/CMakeLists.txt b/filters/randomize/CMakeLists.txt
deleted file mode 100644
index 81b44cc..0000000
--- a/filters/randomize/CMakeLists.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-set(srcs RandomizeFilter.cpp)
-set(incs RandomizeFilter.hpp)
-
-PDAL_ADD_DRIVER(filter randomize "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/range/CMakeLists.txt b/filters/range/CMakeLists.txt
deleted file mode 100644
index 6575bf5..0000000
--- a/filters/range/CMakeLists.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-set(srcs RangeFilter.cpp)
-set(incs RangeFilter.hpp)
-
-PDAL_ADD_DRIVER(filter range "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/reprojection/CMakeLists.txt b/filters/reprojection/CMakeLists.txt
deleted file mode 100644
index b7a738c..0000000
--- a/filters/reprojection/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Reprojection filter CMake configuration
-#
-
-#
-# Reprojection Filter
-#
-set(srcs
-    ReprojectionFilter.cpp
-)
-
-set(incs
-    ReprojectionFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter reprojection "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/reprojection/ReprojectionFilter.cpp b/filters/reprojection/ReprojectionFilter.cpp
deleted file mode 100644
index 432dc2f..0000000
--- a/filters/reprojection/ReprojectionFilter.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "ReprojectionFilter.hpp"
-
-#include <pdal/PointView.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/GDALUtils.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <gdal.h>
-#include <ogr_spatialref.h>
-
-#include <memory>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "filters.reprojection",
-    "Reproject data using GDAL from one coordinate system to another.",
-    "http://pdal.io/stages/filters.reprojection.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, ReprojectionFilter, Filter, s_info)
-
-std::string ReprojectionFilter::getName() const { return s_info.name; }
-
-ReprojectionFilter::ReprojectionFilter()
-    : m_inferInputSRS(true)
-    , m_in_ref_ptr(NULL)
-    , m_out_ref_ptr(NULL)
-    , m_transform_ptr(NULL)
-    , m_errorHandler(new gdal::ErrorHandler())
-{}
-
-ReprojectionFilter::~ReprojectionFilter()
-{
-    if (m_transform_ptr)
-        OCTDestroyCoordinateTransformation(m_transform_ptr);
-    if (m_in_ref_ptr)
-        OSRDestroySpatialReference(m_in_ref_ptr);
-    if (m_out_ref_ptr)
-        OSRDestroySpatialReference(m_out_ref_ptr);
-}
-
-
-void ReprojectionFilter::addArgs(ProgramArgs& args)
-{
-    args.add("out_srs", "Output spatial reference", m_outSRS).setPositional();
-    args.add("in_srs", "Input spatial reference", m_inSRS);
-}
-
-
-void ReprojectionFilter::initialize()
-{
-    m_inferInputSRS = !m_inSRS.valid();
-
-    m_out_ref_ptr = OSRNewSpatialReference(0);
-    if (!m_out_ref_ptr)
-        throw pdal::pdal_error("Unable to allocate new OSR SpatialReference "
-            "in initialize()!");
-
-    int result = OSRSetFromUserInput(m_out_ref_ptr,
-        m_outSRS.getWKT(pdal::SpatialReference::eCompoundOK).c_str());
-    if (result != OGRERR_NONE)
-    {
-        std::ostringstream oss;
-        oss << getName() << ": Invalid output spatial reference '" <<
-            m_outSRS.getWKT() << "'.  This is usually caused by a bad value "
-            "for the 'out_srs' option.";
-        throw pdal_error(oss.str());
-    }
-}
-
-
-void ReprojectionFilter::ready(PointTableRef table)
-{
-    if (!table.supportsView())
-        createTransform(table.anySpatialReference());
-}
-
-
-void ReprojectionFilter::createTransform(const SpatialReference& srsSRS)
-{
-    if (m_inferInputSRS)
-    {
-        m_inSRS = srsSRS;
-        if (m_inSRS.empty())
-        {
-            std::ostringstream oss;
-            oss << getName() << ": source data has no spatial reference and "
-                "none is specified with the 'in_srs' option.";
-            throw pdal_error(oss.str());
-        }
-    }
-
-    if (m_in_ref_ptr)
-        OSRDestroySpatialReference(m_in_ref_ptr);
-    m_in_ref_ptr = OSRNewSpatialReference(0);
-    if (!m_in_ref_ptr)
-        throw pdal::pdal_error("Unable to allocate new OSR SpatialReference for input coordinate system in createTransform()!");
-
-    int result =
-        OSRSetFromUserInput(m_in_ref_ptr,
-            m_inSRS.getWKT(pdal::SpatialReference::eCompoundOK).c_str());
-    if (result != OGRERR_NONE)
-    {
-        std::ostringstream oss;
-        oss << getName() << ": Invalid input spatial reference '" <<
-            m_inSRS.getWKT() << "'.  This is usually caused by a bad " <<
-            "value for the 'in_srs' option or an invalid spatial reference " <<
-            "in the source file.";
-        throw pdal_error(oss.str());
-    }
-    if (m_transform_ptr)
-        OCTDestroyCoordinateTransformation(m_transform_ptr);
-    m_transform_ptr = OCTNewCoordinateTransformation(m_in_ref_ptr,
-        m_out_ref_ptr);
-    if (!m_transform_ptr)
-    {
-        std::ostringstream oss;
-        oss << getName() << ": Could not construct coordinate transformation object in createTransform";
-        throw pdal_error(oss.str());
-    }
-}
-
-PointViewSet ReprojectionFilter::run(PointViewPtr view)
-{
-    PointViewSet viewSet;
-    PointViewPtr outView = view->makeNew();
-
-    createTransform(view->spatialReference());
-
-    PointRef point(*view, 0);
-    for (PointId id = 0; id < view->size(); ++id)
-    {
-        point.setPointId(id);
-        if (processOne(point))
-            outView->appendPoint(*view, id);
-    }
-
-    viewSet.insert(outView);
-    view->setSpatialReference(m_outSRS);
-    outView->setSpatialReference(m_outSRS);
-
-    return viewSet;
-}
-
-
-bool ReprojectionFilter::processOne(PointRef& point)
-{
-    double x(point.getFieldAs<double>(Dimension::Id::X));
-    double y(point.getFieldAs<double>(Dimension::Id::Y));
-    double z(point.getFieldAs<double>(Dimension::Id::Z));
-
-    if (OCTTransform(m_transform_ptr, 1, &x, &y, &z))
-    {
-        point.setField(Dimension::Id::X, x);
-        point.setField(Dimension::Id::Y, y);
-        point.setField(Dimension::Id::Z, z);
-
-        return true;
-    }
-    else
-    {
-        return false;
-    }
-}
-
-} // namespace pdal
diff --git a/filters/sample/CMakeLists.txt b/filters/sample/CMakeLists.txt
deleted file mode 100644
index 6480805..0000000
--- a/filters/sample/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PDAL_ADD_DRIVER(filter sample "SampleFilter.cpp" "SampleFilter.hpp" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/sample/SampleFilter.cpp b/filters/sample/SampleFilter.cpp
deleted file mode 100644
index 70dbef5..0000000
--- a/filters/sample/SampleFilter.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
- *       names of its contributors may be used to endorse or promote
- *       products derived from this software without specific prior
- *       written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#include "SampleFilter.hpp"
-
-#include <pdal/KDIndex.hpp>
-#include <pdal/util/Utils.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <string>
-#include <vector>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.sample", "Subsampling filter",
-               "http://pdal.io/stages/filters.sample.html");
-
-CREATE_STATIC_PLUGIN(1, 0, SampleFilter, Filter, s_info)
-
-std::string SampleFilter::getName() const
-{
-    return s_info.name;
-}
-
-
-void SampleFilter::addArgs(ProgramArgs& args)
-{
-    args.add("radius", "Radius", m_radius, 1.0);
-}
-
-
-void SampleFilter::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(Dimension::Id::Classification);
-}
-
-
-PointViewSet SampleFilter::run(PointViewPtr inView)
-{
-    point_count_t np = inView->size();
-
-    // Return empty PointViewSet if the input PointView has no points.
-    // Otherwise, make a new output PointView.
-    PointViewSet viewSet;
-    if (!np)
-        return viewSet;
-    PointViewPtr outView = inView->makeNew();
-
-    // Build the 3D KD-tree.
-    KD3Index index(*inView);
-    index.build();
-
-    // The result looks much better if we take some time to shuffle the indices.
-    std::srand(std::time(NULL));
-    std::vector<PointId> indices(np);
-    for (PointId i = 0; i < np; ++i)
-        indices[i] = i;
-    std::random_shuffle(indices.begin(), indices.end());
-
-    // All points are marked as kept (1) by default. As they are masked by
-    // neighbors within the user-specified radius, their value is changed to 0.
-    std::vector<int> keep(np, 1);
-
-    // We are able to subsample in a single pass over the shufflled indices.
-    for (auto const& i : indices)
-    {
-        // If a point is masked, it is forever masked, and cannot be part of the
-        // sampled cloud. Otherwise, the current index is appended to the output
-        // PointView.
-        if (keep[i] == 0)
-            continue;
-        outView->appendPoint(*inView, i);
-
-        // We now proceed to mask all neighbors within m_radius of the kept
-        // point.
-        double x = inView->getFieldAs<double>(Dimension::Id::X, i);
-        double y = inView->getFieldAs<double>(Dimension::Id::Y, i);
-        double z = inView->getFieldAs<double>(Dimension::Id::Z, i);
-        auto ids = index.radius(x, y, z, m_radius);
-        for (PointId j = 1; j < ids.size(); ++j)
-            keep[ids[j]] = 0;
-    }
-
-    // Simply calculate the percentage of retained points.
-    double frac = (double)outView->size() / (double)inView->size();
-    log()->get(LogLevel::Debug2) << "Retaining "
-                                 << outView->size() << " of "
-                                 << inView->size() << " points ("
-                                 << 100*frac << "%)\n";
-
-    viewSet.insert(outView);
-    return viewSet;
-}
-
-} // namespace pdal
diff --git a/filters/smrf/CMakeLists.txt b/filters/smrf/CMakeLists.txt
deleted file mode 100644
index 10213c0..0000000
--- a/filters/smrf/CMakeLists.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-PDAL_ADD_DRIVER(filter smrf "SMRFilter.cpp" "SMRFilter.hpp" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/smrf/SMRFilter.cpp b/filters/smrf/SMRFilter.cpp
deleted file mode 100644
index 44239e1..0000000
--- a/filters/smrf/SMRFilter.cpp
+++ /dev/null
@@ -1,1097 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "SMRFilter.hpp"
-
-#include <pdal/Eigen.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/PipelineManager.hpp>
-#include <buffer/BufferReader.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <Eigen/Dense>
-#include <Eigen/Sparse>
-
-#include "gdal_priv.h" // For File I/O
-#include "gdal_version.h" // For version info
-#include "ogr_spatialref.h"  //For Geographic Information/Transformations
-
-namespace pdal
-{
-using namespace Eigen;
-
-static PluginInfo const s_info =
-    PluginInfo("filters.smrf", "Pingel et al. (2013)",
-               "http://pdal.io/stages/filters.smrf.html");
-
-CREATE_STATIC_PLUGIN(1, 0, SMRFilter, Filter, s_info)
-
-struct distElev
-{
-    double dist;
-    double elev;
-};
-
-struct by_dist
-{
-    bool operator()(distElev const& a, distElev const& b)
-    {
-        return a.dist < b.dist;
-    }
-};
-
-std::string SMRFilter::getName() const
-{
-    return s_info.name;
-}
-
-void SMRFilter::addArgs(ProgramArgs& args)
-{
-    args.add("classify", "Apply classification labels?", m_classify, true);
-    args.add("extract", "Extract ground returns?", m_extract);
-    args.add("cell", "Cell size?", m_cellSize, 1.0);
-    args.add("slope", "Slope?", m_percentSlope, 0.15);
-    args.add("window", "Max window size?", m_maxWindow, 21.0);
-    args.add("threshold", "Threshold?", m_threshold, 0.15);
-    args.add("cut", "Cut net size?", m_cutNet, 0.0);
-}
-
-void SMRFilter::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(Dimension::Id::Classification);
-}
-
-int SMRFilter::clamp(int t, int min, int max)
-{
-    return ((t < min) ? min : ((t > max) ? max : t));
-}
-
-int SMRFilter::getColIndex(double x, double cell_size)
-{
-    return static_cast<int>(floor((x - m_bounds.minx) / cell_size));
-}
-
-int SMRFilter::getRowIndex(double y, double cell_size)
-{
-    return static_cast<int>(floor((m_maxRow - y) / cell_size));
-}
-
-MatrixXd SMRFilter::matrixOpen(MatrixXd data, int radius)
-{
-    MatrixXd data2 = padMatrix(data, radius);
-
-    int nrows = data2.rows();
-    int ncols = data2.cols();
-
-    // first min, then max of min
-    MatrixXd minZ = MatrixXd::Constant(nrows, ncols,
-                                       std::numeric_limits<double>::max());
-    MatrixXd maxZ = MatrixXd::Constant(nrows, ncols,
-                                       std::numeric_limits<double>::lowest());
-    for (int c = 0; c < ncols; ++c)
-    {
-        for (int r = 0; r < nrows; ++r)
-        {
-            int cs = clamp(c-radius, 0, ncols-1);
-            int ce = clamp(c+radius, 0, ncols-1);
-            int rs = clamp(r-radius, 0, nrows-1);
-            int re = clamp(r+radius, 0, nrows-1);
-
-            for (int col = cs; col <= ce; ++col)
-            {
-                for (int row = rs; row <= re; ++row)
-                {
-                    if ((row-r)*(row-r)+(col-c)*(col-c) > radius*radius)
-                        continue;
-                    if (data2(row, col) < minZ(r, c))
-                        minZ(r, c) = data2(row, col);
-                }
-            }
-        }
-    }
-    for (int c = 0; c < ncols; ++c)
-    {
-        for (int r = 0; r < nrows; ++r)
-        {
-            int cs = clamp(c-radius, 0, ncols-1);
-            int ce = clamp(c+radius, 0, ncols-1);
-            int rs = clamp(r-radius, 0, nrows-1);
-            int re = clamp(r+radius, 0, nrows-1);
-
-            for (int col = cs; col <= ce; ++col)
-            {
-                for (int row = rs; row <= re; ++row)
-                {
-                    if ((row-r)*(row-r)+(col-c)*(col-c) > radius*radius)
-                        continue;
-                    if (minZ(row, col) > maxZ(r, c))
-                        maxZ(r, c) = minZ(row, col);
-                }
-            }
-        }
-    }
-
-    return maxZ.block(radius, radius, data.rows(), data.cols());
-}
-
-MatrixXd SMRFilter::inpaintKnn(MatrixXd cx, MatrixXd cy, MatrixXd cz)
-{
-    MatrixXd out = cz;
-
-    for (auto c = 0; c < m_numCols; ++c)
-    {
-        for (auto r = 0; r < m_numRows; ++r)
-        {
-            if (!std::isnan(cz(r, c)))
-                continue;
-
-            int radius = 1;
-            bool enough = false;
-
-            while (!enough)
-            {
-                // log()->get(LogLevel::Debug) << r << "\t" << c << "\t" << radius << std::endl;
-                int cs = clamp(c-radius, 0, m_numCols-1);
-                int ce = clamp(c+radius, 0, m_numCols-1);
-                int col_size = ce - cs + 1;
-                int rs = clamp(r-radius, 0, m_numRows-1);
-                int re = clamp(r+radius, 0, m_numRows-1);
-                int row_size = re - rs + 1;
-
-                // MatrixXd Xn = cx.block(rs, cs, row_size, col_size);
-                // MatrixXd Yn = cy.block(rs, cs, row_size, col_size);
-                MatrixXd Zn = cz.block(rs, cs, row_size, col_size);
-
-                auto notNaN = [](double x)
-                {
-                    return !std::isnan(x);
-                };
-
-                enough = Zn.unaryExpr(notNaN).count() >= 8;
-                if (!enough)
-                {
-                    ++radius;
-                    continue;
-                }
-
-                // auto zNotNaN = [](double x)
-                // {
-                //     if (!std::isnan(x))
-                //         return x;
-                //     else
-                //         return 0.0;
-                // };
-                //
-                // // proceed to find 8 nearest neighbors and average the z values
-                // // std::cerr << Zn.unaryExpr(zNotNaN).sum() << "\t" << Zn.size() << "\t" << Zn.unaryExpr(zNotNaN).sum() / Zn.size() << std::endl;
-                // out(r, c) = Zn.unaryExpr(zNotNaN).sum() / Zn.size();
-
-                std::vector<distElev> de;
-
-                for (auto cc = cs; cc <= ce; ++cc)
-                {
-                    for (auto rr = rs; rr <= re; ++rr)
-                    {
-                        if (std::isnan(cz(rr, cc)))
-                            continue;
-
-                        // compute distance to !isnan neighbor
-                        double dx = cx(rr, cc) - cx(r, c);
-                        double dy = cy(rr, cc) - cy(r, c);
-                        double sqrdist = dx * dx + dy * dy;
-                        de.push_back(distElev{sqrdist, cz(rr, cc)});
-                    }
-                }
-                // sort dists
-                std::sort(de.begin(), de.end(), by_dist());
-
-                // average elevatio of lowest eight dists
-                double sum = 0.0;
-                for (auto i = 0; i < 8; ++i)
-                {
-                    sum += de[i].elev;
-                }
-                sum /= 8.0;
-
-                out(r, c) = sum;
-            }
-        }
-    }
-
-    return out;
-}
-
-MatrixXd SMRFilter::padMatrix(MatrixXd data, int radius)
-{
-    MatrixXd data2 = MatrixXd::Zero(data.rows()+2*radius, data.cols()+2*radius);
-    data2.block(radius, radius, data.rows(), data.cols()) = data;
-    data2.block(radius, 0, data.rows(), radius) =
-        data.block(0, 0, data.rows(), radius).rowwise().reverse();
-    data2.block(radius, data.cols()+radius, data.rows(), radius) =
-        data.block(0, data.cols()-radius, data.rows(), radius).rowwise().reverse();
-    data2.block(0, 0, radius, data2.cols()) =
-        data2.block(radius, 0, radius, data2.cols()).colwise().reverse();
-    data2.block(data.rows()+radius, 0, radius, data2.cols()) =
-        data2.block(data2.rows()-radius, 0, radius, data2.cols()).colwise().reverse();
-
-    return data2;
-}
-
-std::vector<PointId> SMRFilter::processGround(PointViewPtr view)
-{
-    log()->get(LogLevel::Info) << "processGround: Running SMRF...\n";
-
-    // The algorithm consists of four conceptually distinct stages. The first is
-    // the creation of the minimum surface (ZImin). The second is the processing
-    // of the minimum surface, in which grid cells from the raster are
-    // identified as either containing bare earth (BE) or objects (OBJ). This
-    // second stage represents the heart of the algorithm. The third step is the
-    // creation of a DEM from these gridded points. The fourth step is the
-    // identification of the original LIDAR points as either BE or OBJ based on
-    // their relationship to the interpolated
-
-    std::vector<PointId> groundIdx;
-    view->calculateBounds(m_bounds);
-
-    double extent_x = floor(m_bounds.maxx) - ceil(m_bounds.minx);
-    double extent_y = floor(m_bounds.maxy) - ceil(m_bounds.miny);
-
-    m_numCols = static_cast<int>(ceil(extent_x/m_cellSize)) + 1;
-    m_numRows = static_cast<int>(ceil(extent_y/m_cellSize)) + 1;
-    m_maxRow = m_bounds.miny + m_numRows * m_cellSize;
-
-    MatrixXd cx(m_numRows, m_numCols);
-    MatrixXd cy(m_numRows, m_numCols);
-    for (auto c = 0; c < m_numCols; ++c)
-    {
-        for (auto r = 0; r < m_numRows; ++r)
-        {
-            cx(r, c) = m_bounds.minx + (c + 0.5) * m_cellSize;
-            cy(r, c) = m_bounds.miny + (r + 0.5) * m_cellSize;
-        }
-    }
-
-    // STEP 1:
-
-    // As with many other ground filtering algorithms, the first step is
-    // generation of ZImin from the cell size parameter and the extent of the
-    // data. The two vectors corresponding to [min:cellSize:max] for each
-    // coordinate – xi and yi – may be supplied by the user or may be easily and
-    // automatically calculated from the data. Without supplied ranges, the SMRF
-    // algorithm creates a raster from the ceiling of the minimum to the floor
-    // of the maximum values for each of the (x,y) dimensions. If the supplied
-    // cell size parameter is not an integer, the same general rule applies to
-    // values evenly divisible by the cell size. For example, if cell size is
-    // equal to 0.5 m, and the x values range from 52345.6 to 52545.4, the range
-    // would be [52346 52545].
-
-    // The minimum surface grid ZImin defined by vectors (xi,yi) is filled with
-    // the nearest, lowest elevation from the original point cloud (x,y,z)
-    // values, provided that the distance to the nearest point does not exceed
-    // the supplied cell size parameter. This provision means that some grid
-    // points of ZImin will go unfilled. To fill these values, we rely on
-    // computationally inexpensive image inpainting techniques. Image inpainting
-    // involves the replacement of the empty cells in an image (or matrix) with
-    // values calculated from other nearby values. It is a type of interpolation
-    // technique derived from artistic replacement of damaged portions of
-    // photographs and paintings, where preservation of texture is an important
-    // concern (Bertalmio et al., 2000). When empty values are spread through
-    // the image, and the ratio of filled to empty pixels is quite high, most
-    // methods of inpainting will produce satisfactory results. In an evaluation
-    // of inpainting methods on ground identification from the final terrain
-    // model, we found that Laplacian techniques produced error rates nearly
-    // three times higher than either an average of the eight nearest neighbors
-    // or D’Errico’s spring-metaphor inpainting technique (D’Errico, 2004). The
-    // spring-metaphor technique imagines springs connecting each cell with its
-    // eight adjacent neighbors, where the inpainted value corresponds to the
-    // lowest energy state of the set, and where the entire (sparse) set of
-    // linear equations is solved using partial differential equations. Both of
-    // these latter techniques were nearly the same with regards to total error,
-    // with the spring technique performing slightly better than the k-nearest
-    // neighbor (KNN) approach.
-    MatrixXd ZImin = createDSM(*view.get(), m_numRows, m_numCols, m_cellSize,
-                               m_bounds);
-    writeMatrix(ZImin, "zimin.tif", m_cellSize, view);
-
-    // MatrixXd ZImin_painted = inpaintKnn(cx, cy, ZImin);
-    // MatrixXd ZImin_painted = TPS(cx, cy, ZImin);
-    MatrixXd ZImin_painted = expandingTPS(cx, cy, ZImin);
-    writeMatrix(ZImin_painted, "zimin_painted.tif", m_cellSize, view);
-
-    ZImin = ZImin_painted;
-
-    // STEP 2:
-
-    // The second stage of the ground identification algorithm involves the
-    // application of a progressive morphological filter to the minimum surface
-    // grid (ZImin). At the first iteration, the filter applies an image opening
-    // operation to the minimum surface. An opening operation consists of an
-    // application of an erosion filter followed by a dilation filter. The
-    // erosion acts to snap relative high values to relative lows, where a
-    // supplied window radius and shape (or structuring element) defines the
-    // search neighborhood. The dilation uses the same window radius and
-    // structuring element, acting to outwardly expand relative highs. Fig. 2
-    // illustrates an opening operation on a cross section of a transect from
-    // Sample 1–1 in the ISPRS LIDAR reference dataset (Sithole and Vosselman,
-    // 2003), following Zhang et al. (2003).
-
-    // paper has low point happening later, i guess it doesn't matter too much, this is where he does it in matlab code
-    MatrixXi Low = progressiveFilter(-ZImin, m_cellSize, 5.0, 1.0);
-    writeMatrix(Low.cast<double>(), "zilow.tif", m_cellSize, view);
-
-    // matlab code has net cutting occurring here
-    MatrixXd ZInet = ZImin;
-    MatrixXi isNetCell = MatrixXi::Zero(m_numRows, m_numCols);
-    if (m_cutNet > 0.0)
-    {
-        MatrixXd bigOpen = matrixOpen(ZImin, 2*std::ceil(m_cutNet / m_cellSize));
-        for (auto c = 0; c < m_numCols; c += std::ceil(m_cutNet/m_cellSize))
-        {
-            for (auto r = 0; r < m_numRows; ++r)
-            {
-                isNetCell(r, c) = 1;
-            }
-        }
-        for (auto c = 0; c < m_numCols; ++c)
-        {
-            for (auto r = 0; r < m_numRows; r += std::ceil(m_cutNet/m_cellSize))
-            {
-                isNetCell(r, c) = 1;
-            }
-        }
-        for (auto c = 0; c < m_numCols; ++c)
-        {
-            for (auto r = 0; r < m_numRows; ++r)
-            {
-                if (isNetCell(r, c)==1)
-                    ZInet(r, c) = bigOpen(r, c);
-            }
-        }
-        writeMatrix(ZInet, "zinet.tif", m_cellSize, view);
-    }
-
-    // and finally object detection
-    MatrixXi Obj = progressiveFilter(ZInet, m_cellSize, m_percentSlope, m_maxWindow);
-    writeMatrix(Obj.cast<double>(), "ziobj.tif", m_cellSize, view);
-
-    // STEP 3:
-
-    // The end result of the iteration process described above is a binary grid
-    // where each cell is classified as being either bare earth (BE) or object
-    // (OBJ). The algorithm then applies this mask to the starting minimum
-    // surface to eliminate nonground cells. These cells are then inpainted
-    // according to the same process described previously, producing a
-    // provisional DEM (ZIpro).
-
-    // we currently aren't checking for net cells or empty cells (haven't i already marked empty cells as NaNs?)
-    MatrixXd ZIpro = ZImin;
-    for (int i = 0; i < Obj.size(); ++i)
-    {
-        if (Obj(i) == 1 || Low(i) == 1 || isNetCell(i) == 1)
-            ZIpro(i) = std::numeric_limits<double>::quiet_NaN();
-    }
-    writeMatrix(ZIpro, "zipro.tif", m_cellSize, view);
-
-    // MatrixXd ZIpro_painted = inpaintKnn(cx, cy, ZIpro);
-    // MatrixXd ZIpro_painted = TPS(cx, cy, ZIpro);
-    MatrixXd ZIpro_painted = expandingTPS(cx, cy, ZIpro);
-    writeMatrix(ZIpro_painted, "zipro_painted.tif", m_cellSize, view);
-
-    ZIpro = ZIpro_painted;
-
-    // STEP 4:
-
-    // The final step of the algorithm is the identification of ground/object
-    // LIDAR points. This is accomplished by measuring the vertical distance
-    // between each LIDAR point and the provisional DEM, and applying a
-    // threshold calculation. While many authors use a single value for the
-    // elevation threshold, we suggest that a second parameter be used to
-    // increase the threshold on steep slopes, transforming the threshold to a
-    // slope-dependent value. The total permissible distance is then equal to a
-    // fixed elevation threshold plus the scaling value multiplied by the slope
-    // of the DEM at each LIDAR point. The rationale behind this approach is
-    // that small horizontal and vertical displacements yield larger errors on
-    // steep slopes, and as a result the BE/OBJ threshold distance should be
-    // more per- missive at these points.
-
-    // The calculation requires that both elevation and slope are interpolated
-    // from the provisional DEM. There are any number of interpolation
-    // techniques that might be used, and even nearest neighbor approaches work
-    // quite well, so long as the cell size of the DEM nearly corresponds to the
-    // resolution of the LIDAR data. A comparison of how well these different
-    // methods of interpolation perform is given in the next section. Based on
-    // these results, we find that a splined cubic interpolation provides the
-    // best results.
-
-    // It is common in LIDAR point clouds to have a small number of outliers
-    // which may be either above or below the terrain surface. While
-    // above-ground outliers (e.g., a random return from a bird in flight) are
-    // filtered during the normal algorithm routine, the below-ground outliers
-    // (e.g., those caused by a reflection) require a separate approach. Early
-    // in the routine and along a separate processing fork, the minimum surface
-    // is checked for low outliers by inverting the point cloud in the z-axis
-    // and applying the filter with parameters (slope = 500%, maxWindowSize =
-    // 1). The resulting mask is used to flag low outlier cells as OBJ before
-    // the inpainting of the provisional DEM. This outlier identification
-    // methodology is functionally the same as that of Zhang et al. (2003).
-
-    // The provisional DEM (ZIpro), created by removing OBJ cells from the
-    // original minimum surface (ZImin) and then inpainting, tends to be less
-    // smooth than one might wish, especially when the surfaces are to be used
-    // to create visual products like immersive geographic virtual environments.
-    // As a result, it is often worthwhile to reinter- polate a final DEM from
-    // the identified ground points of the original LIDAR data (ZIfin). Surfaces
-    // created from these data tend to be smoother and more visually satisfying
-    // than those derived from the provisional DEM.
-
-    // Very large (>40m in length) buildings can sometimes prove troublesome to
-    // remove on highly differentiated terrain. To accommodate the removal of
-    // such objects, we implemented a feature in the published SMRF algorithm
-    // which is helpful in removing such features. We accomplish this by
-    // introducing into the initial minimum surface a ‘‘net’’ of minimum values
-    // at a spacing equal to the maximum window diameter, where these minimum
-    // values are found by applying a morphological open operation with a disk
-    // shaped structuring element of radius (2?wkmax). Since only one example in
-    // this dataset had features this large (Sample 4–2, a trainyard) we did not
-    // include this portion of the algorithm in the formal testing procedure,
-    // though we provide a brief analysis of the effect of using this net filter
-    // in the next section.
-
-    auto diffX = [&](MatrixXd mat)
-    {
-        MatrixXd data = padMatrix(mat, 1);
-        MatrixXd data2 = data.rightCols(data.cols()-1) - data.leftCols(data.cols()-1);
-        return data2.block(1, 1, mat.rows(), mat.cols());
-    };
-
-    auto diffY = [&](MatrixXd mat)
-    {
-        MatrixXd data = padMatrix(mat, 1);
-        MatrixXd data2 = data.bottomRows(data.rows()-1) - data.topRows(data.rows()-1);
-        return data2.block(1, 1, mat.rows(), mat.cols());
-    };
-
-    MatrixXd gx = diffX(ZIpro / m_cellSize);
-    writeMatrix(gx, "gx.tif", m_cellSize, view);
-    MatrixXd gy = diffY(ZIpro / m_cellSize);
-    writeMatrix(gy, "gy.tif", m_cellSize, view);
-    MatrixXd gsurfs = (gx.cwiseProduct(gx) + gy.cwiseProduct(gy)).cwiseSqrt();
-    writeMatrix(gsurfs, "gsurfs.tif", m_cellSize, view);
-
-    // MatrixXd gsurfs_painted = inpaintKnn(cx, cy, gsurfs);
-    // MatrixXd gsurfs_painted = TPS(cx, cy, gsurfs);
-    MatrixXd gsurfs_painted = expandingTPS(cx, cy, gsurfs);
-    writeMatrix(gsurfs_painted, "gsurfs_painted.tif", m_cellSize, view);
-
-    gsurfs = gsurfs_painted;
-
-    MatrixXd thresh = (m_threshold + 1.2 * gsurfs.array()).matrix();
-    writeMatrix(thresh, "thresh.tif", m_cellSize, view);
-
-    for (PointId i = 0; i < view->size(); ++i)
-    {
-        using namespace Dimension;
-        double x = view->getFieldAs<double>(Id::X, i);
-        double y = view->getFieldAs<double>(Id::Y, i);
-        double z = view->getFieldAs<double>(Id::Z, i);
-
-        int c = clamp(getColIndex(x, m_cellSize), 0, m_numCols-1);
-        int r = clamp(getRowIndex(y, m_cellSize), 0, m_numRows-1);
-
-        // author uses spline interpolation to get value from ZIpro and gsurfs
-
-        if (std::isnan(ZIpro(r, c)))
-            continue;
-
-        // not sure i should just brush this under the rug...
-        if (std::isnan(gsurfs(r, c)))
-            continue;
-
-        double ez = ZIpro(r, c);
-        // double ez = interp2(r, c, cx, cy, ZIpro);
-        // double si = gsurfs(r, c);
-        // double si = interp2(r, c, cx, cy, gsurfs);
-        // double reqVal = m_threshold + 1.2 * si;
-
-        if (std::abs(ez - z) > thresh(r, c))
-            continue;
-
-        // if (std::abs(ZIpro(r, c) - z) > m_threshold)
-        //     continue;
-
-        groundIdx.push_back(i);
-    }
-
-    return groundIdx;
-}
-
-MatrixXi SMRFilter::progressiveFilter(MatrixXd const& ZImin, double cell_size,
-                                      double slope, double max_window)
-{
-    log()->get(LogLevel::Info) << "progressiveFilter: Progressive filtering...\n";
-
-    MatrixXi Obj(m_numRows, m_numCols);
-    Obj.setZero();
-
-    // In this case, we selected a disk-shaped structuring element, and the
-    // radius of the element at each step was increased by one pixel from a
-    // starting value of one pixel to the pixel equivalent of the maximum value
-    // (wkmax). The maximum window radius is supplied as a distance metric
-    // (e.g., 21 m), but is internally converted to a pixel equivalent by
-    // dividing it by the cell size and rounding the result toward positive
-    // infinity (i.e., taking the ceiling value). For example, for a supplied
-    // maximum window radius of 21 m, and a cell size of 2m per pixel, the
-    // result would be a maximum window radius of 11 pixels. While this
-    // represents a relatively slow progression in the expansion of the window
-    // radius, we believe that the high efficiency associated with the opening
-    // operation mitigates the potential for computational waste. The
-    // improvements in classification accuracy using slow, linear progressions
-    // are documented in the next section.
-    int max_radius = ceil(max_window/cell_size);
-    MatrixXd ZIlocal = ZImin;
-    for (int radius = 1; radius <= max_radius; ++radius)
-    {
-        // On the first iteration, the minimum surface (ZImin) is opened using a
-        // disk-shaped structuring element with a radius of one pixel.
-        MatrixXd mo = matrixOpen(ZIlocal, radius);
-
-        // An elevation threshold is then calculated, where the value is equal
-        // to the supplied slope tolerance parameter multiplied by the product
-        // of the window radius and the cell size. For example, if the user
-        // supplied a slope tolerance parameter of 15%, a cell size of 2m per
-        // pixel, the elevation threshold would be 0.3m at a window of one pixel
-        // (0.15 ? 1 ? 2).
-        double threshold = slope * cell_size * radius;
-
-        // This elevation threshold is applied to the difference of the minimum
-        // and the opened surfaces.
-        MatrixXd diff = ZIlocal - mo;
-
-        // Any grid cell with a difference value exceeding the calculated
-        // elevation threshold for the iteration is then flagged as an OBJ cell.
-        for (int i = 0; i < diff.size(); ++i)
-        {
-            if (diff(i) > threshold)
-                Obj(i) = 1;
-        }
-        // writeMatrix(Obj, "obj.tif", m_cellSize, view);
-
-        // The algorithm then proceeds to the next window radius (up to the
-        // maximum), and proceeds as above with the last opened surface acting
-        // as the ‘‘minimum surface’’ for the next difference calculation.
-        ZIlocal = mo;
-
-        log()->get(LogLevel::Info) << "progressiveFilter: Radius = " << radius
-                                   << ", " << Obj.sum() << " object pixels\n";
-    }
-
-    return Obj;
-}
-
-PointViewSet SMRFilter::run(PointViewPtr view)
-{
-    log()->get(LogLevel::Info) << "run: Process SMRFilter...\n";
-
-    std::vector<PointId> idx = processGround(view);
-
-    PointViewSet viewSet;
-
-    if (!idx.empty() && (m_classify || m_extract))
-    {
-
-        if (m_classify)
-        {
-            log()->get(LogLevel::Info) << "run: Labeled " << idx.size() << " ground returns!\n";
-
-            // set the classification label of ground returns as 2
-            // (corresponding to ASPRS LAS specification)
-            for (const auto& i : idx)
-            {
-                view->setField(Dimension::Id::Classification, i, 2);
-            }
-
-            viewSet.insert(view);
-        }
-
-        if (m_extract)
-        {
-            log()->get(LogLevel::Info) << "run: Extracted " << idx.size() << " ground returns!\n";
-
-            // create new PointView containing only ground returns
-            PointViewPtr output = view->makeNew();
-            for (const auto& i : idx)
-            {
-                output->appendPoint(*view, i);
-            }
-
-            viewSet.erase(view);
-            viewSet.insert(output);
-        }
-    }
-    else
-    {
-        if (idx.empty())
-            log()->get(LogLevel::Info) << "run: Filtered cloud has no ground returns!\n";
-
-        if (!(m_classify || m_extract))
-            log()->get(LogLevel::Info) << "run: Must choose --classify or --extract\n";
-
-        // return the view buffer unchanged
-        viewSet.insert(view);
-    }
-
-    return viewSet;
-}
-
-MatrixXd SMRFilter::TPS(MatrixXd cx, MatrixXd cy, MatrixXd cz)
-{
-    log()->get(LogLevel::Info) << "TPS: Reticulating splines...\n";
-
-    MatrixXd S = cz;
-
-    int num_nan_detect(0);
-    int num_nan_replace(0);
-
-    for (auto outer_col = 0; outer_col < m_numCols; ++outer_col)
-    {
-        for (auto outer_row = 0; outer_row < m_numRows; ++outer_row)
-        {
-            if (!std::isnan(S(outer_row, outer_col)))
-                continue;
-
-            num_nan_detect++;
-
-            // Further optimizations are achieved by estimating only the
-            // interpolated surface within a local neighbourhood (e.g. a 7 x 7
-            // neighbourhood is used in our case) of the cell being filtered.
-            int radius = 3;
-
-            int cs = clamp(outer_col-radius, 0, m_numCols-1);
-            int ce = clamp(outer_col+radius, 0, m_numCols-1);
-            int col_size = ce - cs + 1;
-            int rs = clamp(outer_row-radius, 0, m_numRows-1);
-            int re = clamp(outer_row+radius, 0, m_numRows-1);
-            int row_size = re - rs + 1;
-
-            MatrixXd Xn = cx.block(rs, cs, row_size, col_size);
-            MatrixXd Yn = cy.block(rs, cs, row_size, col_size);
-            MatrixXd Hn = cz.block(rs, cs, row_size, col_size);
-
-            int nsize = Hn.size();
-            VectorXd T = VectorXd::Zero(nsize);
-            MatrixXd P = MatrixXd::Zero(nsize, 3);
-            MatrixXd K = MatrixXd::Zero(nsize, nsize);
-
-            int numK(0);
-            for (auto id = 0; id < Hn.size(); ++id)
-            {
-                double xj = Xn(id);
-                double yj = Yn(id);
-                double zj = Hn(id);
-                if (std::isnan(zj))
-                    continue;
-                numK++;
-                T(id) = zj;
-                P.row(id) << 1, xj, yj;
-                for (auto id2 = 0; id2 < Hn.size(); ++id2)
-                {
-                    if (id == id2)
-                        continue;
-                    double xk = Xn(id2);
-                    double yk = Yn(id2);
-                    double rsqr = (xj - xk) * (xj - xk) + (yj - yk) * (yj - yk);
-                    if (rsqr == 0.0)
-                        continue;
-                    K(id, id2) = rsqr * std::log10(std::sqrt(rsqr));
-                }
-            }
-
-            if (numK < 20)
-                continue;
-
-            MatrixXd A = MatrixXd::Zero(nsize+3, nsize+3);
-            A.block(0,0,nsize,nsize) = K;
-            A.block(0,nsize,nsize,3) = P;
-            A.block(nsize,0,3,nsize) = P.transpose();
-
-            VectorXd b = VectorXd::Zero(nsize+3);
-            b.head(nsize) = T;
-
-            VectorXd x = A.fullPivHouseholderQr().solve(b);
-
-            Vector3d a = x.tail(3);
-            VectorXd w = x.head(nsize);
-
-            double sum = 0.0;
-            double xi2 = cx(outer_row, outer_col);
-            double yi2 = cy(outer_row, outer_col);
-            for (auto j = 0; j < nsize; ++j)
-            {
-                double xj = Xn(j);
-                double yj = Yn(j);
-                double rsqr = (xj - xi2) * (xj - xi2) + (yj - yi2) * (yj - yi2);
-                if (rsqr == 0.0)
-                    continue;
-                sum += w(j) * rsqr * std::log10(std::sqrt(rsqr));
-            }
-
-            S(outer_row, outer_col) = a(0) + a(1)*xi2 + a(2)*yi2 + sum;
-
-            if (!std::isnan(S(outer_row, outer_col)))
-                num_nan_replace++;
-
-            // std::cerr << std::fixed;
-            // std::cerr << std::setprecision(3)
-            //           << std::left
-            //           << "S(" << outer_row << "," << outer_col << "): "
-            //           << std::setw(10)
-            //           << S(outer_row, outer_col)
-            //           // << std::setw(3)
-            //           // << "\tz: "
-            //           // << std::setw(10)
-            //           // << zi
-            //           // << std::setw(7)
-            //           // << "\tzdiff: "
-            //           // << std::setw(5)
-            //           // << zi - S(outer_row, outer_col)
-            //           // << std::setw(7)
-            //           // << "\txdiff: "
-            //           // << std::setw(5)
-            //           // << xi2 - xi
-            //           // << std::setw(7)
-            //           // << "\tydiff: "
-            //           // << std::setw(5)
-            //           // << yi2 - yi
-            //           << std::setw(7)
-            //           << "\t# pts: "
-            //           << std::setw(3)
-            //           << nsize
-            //           << std::setw(5)
-            //           << "\tsum: "
-            //           << std::setw(10)
-            //           << sum
-            //           << std::setw(9)
-            //           << "\tw.sum(): "
-            //           << std::setw(5)
-            //           << w.sum()
-            //           << std::setw(6)
-            //           << "\txsum: "
-            //           << std::setw(5)
-            //           << w.dot(P.col(1))
-            //           << std::setw(6)
-            //           << "\tysum: "
-            //           << std::setw(5)
-            //           << w.dot(P.col(2))
-            //           << std::setw(3)
-            //           << "\ta: "
-            //           << std::setw(8)
-            //           << a.transpose()
-            //           << std::endl;
-        }
-    }
-
-    double frac = static_cast<double>(num_nan_replace);
-    frac /= static_cast<double>(num_nan_detect);
-    log()->get(LogLevel::Info) << "TPS: Filled " << num_nan_replace << " of "
-                               << num_nan_detect << " holes ("
-                               << frac * 100.0 << "%)\n";
-
-    return S;
-}
-
-MatrixXd SMRFilter::expandingTPS(MatrixXd cx, MatrixXd cy, MatrixXd cz)
-{
-    log()->get(LogLevel::Info) << "TPS: Reticulating splines...\n";
-
-    MatrixXd S = cz;
-
-    int num_nan_detect(0);
-    int num_nan_replace(0);
-
-    for (auto outer_col = 0; outer_col < m_numCols; ++outer_col)
-    {
-        for (auto outer_row = 0; outer_row < m_numRows; ++outer_row)
-        {
-            if (!std::isnan(S(outer_row, outer_col)))
-                continue;
-
-            num_nan_detect++;
-
-            // Further optimizations are achieved by estimating only the
-            // interpolated surface within a local neighbourhood (e.g. a 7 x 7
-            // neighbourhood is used in our case) of the cell being filtered.
-            int radius = 3;
-            bool solution = false;
-
-            while (!solution)
-            {
-                // std::cerr << radius;
-                int cs = clamp(outer_col-radius, 0, m_numCols-1);
-                int ce = clamp(outer_col+radius, 0, m_numCols-1);
-                int col_size = ce - cs + 1;
-                int rs = clamp(outer_row-radius, 0, m_numRows-1);
-                int re = clamp(outer_row+radius, 0, m_numRows-1);
-                int row_size = re - rs + 1;
-
-                MatrixXd Xn = cx.block(rs, cs, row_size, col_size);
-                MatrixXd Yn = cy.block(rs, cs, row_size, col_size);
-                MatrixXd Hn = cz.block(rs, cs, row_size, col_size);
-
-                int nsize = Hn.size();
-                VectorXd T = VectorXd::Zero(nsize);
-                MatrixXd P = MatrixXd::Zero(nsize, 3);
-                MatrixXd K = MatrixXd::Zero(nsize, nsize);
-
-                int numK(0);
-                for (auto id = 0; id < Hn.size(); ++id)
-                {
-                    double xj = Xn(id);
-                    double yj = Yn(id);
-                    double zj = Hn(id);
-                    if (std::isnan(zj))
-                        continue;
-                    numK++;
-                    T(id) = zj;
-                    P.row(id) << 1, xj, yj;
-                    for (auto id2 = 0; id2 < Hn.size(); ++id2)
-                    {
-                        if (id == id2)
-                            continue;
-                        double xk = Xn(id2);
-                        double yk = Yn(id2);
-                        double rsqr = (xj - xk) * (xj - xk) + (yj - yk) * (yj - yk);
-                        if (rsqr == 0.0)
-                            continue;
-                        K(id, id2) = rsqr * std::log10(std::sqrt(rsqr));
-                    }
-                }
-
-                // if (numK < 20)
-                //     continue;
-
-                MatrixXd A = MatrixXd::Zero(nsize+3, nsize+3);
-                A.block(0,0,nsize,nsize) = K;
-                A.block(0,nsize,nsize,3) = P;
-                A.block(nsize,0,3,nsize) = P.transpose();
-
-                VectorXd b = VectorXd::Zero(nsize+3);
-                b.head(nsize) = T;
-
-                VectorXd x = A.fullPivHouseholderQr().solve(b);
-
-                Vector3d a = x.tail(3);
-                VectorXd w = x.head(nsize);
-
-                double sum = 0.0;
-                double xi2 = cx(outer_row, outer_col);
-                double yi2 = cy(outer_row, outer_col);
-                for (auto j = 0; j < nsize; ++j)
-                {
-                    double xj = Xn(j);
-                    double yj = Yn(j);
-                    double rsqr = (xj - xi2) * (xj - xi2) + (yj - yi2) * (yj - yi2);
-                    if (rsqr == 0.0)
-                        continue;
-                    sum += w(j) * rsqr * std::log10(std::sqrt(rsqr));
-                }
-
-                double val = a(0) + a(1)*xi2 + a(2)*yi2 + sum;
-                solution = !std::isnan(val);
-
-                if (!solution)
-                {
-                    std::cerr << "..." << radius << std::endl;;
-                    ++radius;
-                    continue;
-                }
-
-                S(outer_row, outer_col) = val;
-                num_nan_replace++;
-
-                // std::cerr << std::endl;
-
-                // std::cerr << std::fixed;
-                // std::cerr << std::setprecision(3)
-                //           << std::left
-                //           << "S(" << outer_row << "," << outer_col << "): "
-                //           << std::setw(10)
-                //           << S(outer_row, outer_col)
-                //           // << std::setw(3)
-                //           // << "\tz: "
-                //           // << std::setw(10)
-                //           // << zi
-                //           // << std::setw(7)
-                //           // << "\tzdiff: "
-                //           // << std::setw(5)
-                //           // << zi - S(outer_row, outer_col)
-                //           // << std::setw(7)
-                //           // << "\txdiff: "
-                //           // << std::setw(5)
-                //           // << xi2 - xi
-                //           // << std::setw(7)
-                //           // << "\tydiff: "
-                //           // << std::setw(5)
-                //           // << yi2 - yi
-                //           << std::setw(7)
-                //           << "\t# pts: "
-                //           << std::setw(3)
-                //           << nsize
-                //           << std::setw(5)
-                //           << "\tsum: "
-                //           << std::setw(10)
-                //           << sum
-                //           << std::setw(9)
-                //           << "\tw.sum(): "
-                //           << std::setw(5)
-                //           << w.sum()
-                //           << std::setw(6)
-                //           << "\txsum: "
-                //           << std::setw(5)
-                //           << w.dot(P.col(1))
-                //           << std::setw(6)
-                //           << "\tysum: "
-                //           << std::setw(5)
-                //           << w.dot(P.col(2))
-                //           << std::setw(3)
-                //           << "\ta: "
-                //           << std::setw(8)
-                //           << a.transpose()
-                //           << std::endl;
-            }
-        }
-    }
-
-    double frac = static_cast<double>(num_nan_replace);
-    frac /= static_cast<double>(num_nan_detect);
-    log()->get(LogLevel::Info) << "TPS: Filled " << num_nan_replace << " of "
-                               << num_nan_detect << " holes ("
-                               << frac * 100.0 << "%)\n";
-
-    return S;
-}
-
-void SMRFilter::writeMatrix(MatrixXd data, std::string filename,
-                            double cell_size, PointViewPtr view)
-{
-    int cols = data.cols();
-    int rows = data.rows();
-
-    GDALAllRegister();
-
-    GDALDataset *mpDstDS = NULL;
-
-    char **papszMetadata;
-
-    // parse the format driver, hardcoded for the time being
-    std::string tFormat("GTIFF");
-    const char *pszFormat = tFormat.c_str();
-    GDALDriver* tpDriver = GetGDALDriverManager()->GetDriverByName(pszFormat);
-
-    // try to create a file of the requested format
-    if (tpDriver != NULL)
-    {
-        papszMetadata = tpDriver->GetMetadata();
-        if (CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATE, FALSE))
-        {
-            char **papszOptions = NULL;
-
-            mpDstDS = tpDriver->Create(filename.c_str(), cols, rows, 1,
-                                       GDT_Float32, papszOptions);
-
-            // set the geo transformation
-            double adfGeoTransform[6];
-            adfGeoTransform[0] = m_bounds.minx; // - 0.5*m_GRID_DIST_X;
-            adfGeoTransform[1] = cell_size;
-            adfGeoTransform[2] = 0.0;
-            adfGeoTransform[3] = m_bounds.maxy; // + 0.5*m_GRID_DIST_Y;
-            adfGeoTransform[4] = 0.0;
-            adfGeoTransform[5] = -1 * cell_size;
-            mpDstDS->SetGeoTransform(adfGeoTransform);
-
-            // set the projection
-            mpDstDS->SetProjection(view->spatialReference().getWKT().c_str());
-        }
-    }
-
-    // if we have a valid file
-    if (mpDstDS)
-    {
-        // loop over the raster and determine max slope at each location
-        int cs = 1, ce = cols - 1;
-        int rs = 1, re = rows - 1;
-        float *poRasterData = new float[cols*rows];
-        for (auto i=0; i<cols*rows; i++)
-        {
-            poRasterData[i] = std::numeric_limits<float>::min();
-        }
-
-        // #pragma omp parallel for
-        for (auto c = cs; c < ce; ++c)
-        {
-            for (auto r = rs; r < re; ++r)
-            {
-                if (data(r, c) == 0.0 || std::isnan(data(r, c)) || data(r, c) == std::numeric_limits<double>::max())
-                    continue;
-                poRasterData[(r * cols) + c] =
-                    data(r, c);
-            }
-        }
-
-        // write the data
-        if (poRasterData)
-        {
-            GDALRasterBand *tBand = mpDstDS->GetRasterBand(1);
-
-            tBand->SetNoDataValue(std::numeric_limits<float>::min());
-
-            if (cols > 0 && rows > 0)
-#if GDAL_VERSION_MAJOR <= 1
-                tBand->RasterIO(GF_Write, 0, 0, cols, rows,
-                                poRasterData, cols, rows,
-                                GDT_Float32, 0, 0);
-#else
-
-                int ret = tBand->RasterIO(GF_Write, 0, 0, cols, rows,
-                                          poRasterData, cols, rows,
-                                          GDT_Float32, 0, 0, 0);
-#endif
-        }
-
-        GDALClose((GDALDatasetH) mpDstDS);
-
-        delete [] poRasterData;
-    }
-}
-
-} // namespace pdal
diff --git a/filters/smrf/SMRFilter.hpp b/filters/smrf/SMRFilter.hpp
deleted file mode 100644
index a60c3da..0000000
--- a/filters/smrf/SMRFilter.hpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/plugin.hpp>
-
-#include <Eigen/Dense>
-
-#include <memory>
-#include <unordered_map>
-
-extern "C" int32_t SMRFilter_ExitFunc();
-extern "C" PF_ExitFunc SMRFilter_InitPlugin();
-
-namespace pdal
-{
-
-using namespace Eigen;
-
-class PointLayout;
-class PointView;
-
-class PDAL_DLL SMRFilter : public Filter
-{
-public:
-    SMRFilter() : Filter()
-    {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    bool m_classify;
-    bool m_extract;
-    int m_numRows;
-    int m_numCols;
-    int m_maxRow;
-    double m_cellSize;
-    double m_cutNet;
-    double m_percentSlope;
-    double m_maxWindow;
-    double m_threshold;
-    BOX2D m_bounds;
-
-    virtual void addArgs(ProgramArgs& args);
-    virtual void addDimensions(PointLayoutPtr layout);
-
-    // clamp is used to help ensure that row and column indices remain
-    // within valid bounds.
-    int clamp(int t, int min, int max);
-
-    // getColIndex gets the corresponding column index for a given x.
-    int getColIndex(double x, double cell_size);
-
-    // getRowIndex gets the corresponding row index for a given y.
-    int getRowIndex(double y, double cell_size);
-
-    // matrixOpen applies a morphological opening with the given radius.
-    MatrixXd matrixOpen(MatrixXd data, int radius);
-
-    MatrixXd inpaintKnn(MatrixXd cx, MatrixXd cy, MatrixXd cz);
-
-    // padMatrx pads the matrix symmetrically with given radius.
-    MatrixXd padMatrix(MatrixXd data, int radius);
-
-    // processGround implements the SMRF algorithm, returning a vector
-    // of ground indices.
-    std::vector<PointId> processGround(PointViewPtr view);
-
-    // progressiveFilter is the core of the SMRF algorithm.
-    MatrixXi progressiveFilter(MatrixXd const& ZImin, double cell_size,
-                               double slope, double max_window);
-
-    virtual PointViewSet run(PointViewPtr view);
-
-    // TPS returns an interpolated matrix using thin plate splines.
-    MatrixXd TPS(MatrixXd cx, MatrixXd cy, MatrixXd cz);
-    MatrixXd expandingTPS(MatrixXd cx, MatrixXd cy, MatrixXd cz);
-
-    // writeMatrix writes out Eigen matrices to GeoTIFFs for debugging.
-    void writeMatrix(MatrixXd data, std::string filename,
-                     double cell_size, PointViewPtr view);
-
-    SMRFilter& operator=(const SMRFilter&); // not implemented
-    SMRFilter(const SMRFilter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/filters/sort/CMakeLists.txt b/filters/sort/CMakeLists.txt
deleted file mode 100644
index 275a9bd..0000000
--- a/filters/sort/CMakeLists.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-set(srcs SortFilter.cpp)
-set(incs SortFilter.hpp)
-
-PDAL_ADD_DRIVER(filter sort "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/sort/SortFilter.hpp b/filters/sort/SortFilter.hpp
deleted file mode 100644
index 6893faa..0000000
--- a/filters/sort/SortFilter.hpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Hobu Inc. (hobu at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/PointViewIter.hpp>
-#include <pdal/plugin.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-extern "C" int32_t SortFilter_ExitFunc();
-extern "C" PF_ExitFunc SortFilter_InitPlugin();
-
-namespace pdal
-{
-
-class PDAL_DLL SortFilter : public Filter
-{
-public:
-    SortFilter()
-    {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    // Dimension on which to sort.
-    Dimension::Id m_dim;
-    // Dimension name.
-    std::string m_dimName;
-
-    virtual void addArgs(ProgramArgs& args)
-    {
-        args.add("dimension", "Dimension on which to sort", m_dimName).
-            setPositional();
-    }
-
-    virtual void ready(PointTableRef table)
-        { m_dim = table.layout()->findDim(m_dimName); }
-
-    virtual void filter(PointView& view)
-    {
-        if (m_dim == Dimension::Id::Unknown)
-            return;
-
-        auto cmp = [this](const PointIdxRef& p1, const PointIdxRef& p2)
-            { return p1.compare(m_dim, p2); };
-
-        std::sort(view.begin(), view.end(), cmp);
-    }
-
-    SortFilter& operator=(const SortFilter&) = delete;
-    SortFilter(const SortFilter&) = delete;
-};
-
-} // namespace pdal
diff --git a/filters/splitter/CMakeLists.txt b/filters/splitter/CMakeLists.txt
deleted file mode 100644
index ef64905..0000000
--- a/filters/splitter/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Splitter filter CMake configuration
-#
-
-#
-# Splitter Filter
-#
-set(srcs
-    SplitterFilter.cpp
-)
-
-set(incs
-    SplitterFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter splitter "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/splitter/SplitterFilter.cpp b/filters/splitter/SplitterFilter.cpp
deleted file mode 100644
index a174a2c..0000000
--- a/filters/splitter/SplitterFilter.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2013, Bradley J Chambers (brad.chambers at gmail.com)
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
- *       names of its contributors may be used to endorse or promote
- *       products derived from this software without specific prior
- *       written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#include "SplitterFilter.hpp"
-
-#include <cmath>
-#include <iostream>
-#include <limits>
-
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "filters.splitter",
-    "Split data based on a X/Y box length.",
-    "http://pdal.io/stages/filters.splitter.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, SplitterFilter, Filter, s_info)
-
-std::string SplitterFilter::getName() const { return s_info.name; }
-
-void SplitterFilter::addArgs(ProgramArgs& args)
-{
-    args.add("length", "Edge length of cell", m_length, 1000.0);
-    args.add("origin_x", "X origin for a cell", m_xOrigin,
-        std::numeric_limits<double>::quiet_NaN());
-    args.add("origin_y", "Y origin for a cell", m_yOrigin,
-        std::numeric_limits<double>::quiet_NaN());
-}
-
-
-//This used to be a lambda, but the VS compiler exploded, I guess.
-typedef std::pair<int, int> Coord;
-namespace
-{
-class CoordCompare
-{
-public:
-    bool operator () (const Coord& c1, const Coord& c2) const
-    {
-        return c1.first < c2.first ? true :
-            c1.first > c2.first ? false :
-            c1.second < c2.second ? true :
-            false;
-    };
-};
-}
-
-PointViewSet SplitterFilter::run(PointViewPtr inView)
-{
-    PointViewSet viewSet;
-    if (!inView->size())
-        return viewSet;
-
-    CoordCompare compare;
-    std::map<Coord, PointViewPtr, CoordCompare> viewMap(compare);
-
-    // Use the location of the first point as the origin, unless specified.
-    // (!= test == isnan(), which doesn't exist on windows)
-    if (m_xOrigin != m_xOrigin)
-        m_xOrigin = inView->getFieldAs<double>(Dimension::Id::X, 0);
-    if (m_yOrigin != m_yOrigin)
-        m_yOrigin = inView->getFieldAs<double>(Dimension::Id::Y, 0);
-    // Overlay a grid of squares on the points (m_length sides).  Each square
-    // corresponds to a new point buffer.  Place the points falling in the
-    // each square in the corresponding point buffer.
-    for (PointId idx = 0; idx < inView->size(); idx++)
-    {
-        double x = inView->getFieldAs<double>(Dimension::Id::X, idx);
-        int xpos = (x - m_xOrigin) / m_length;
-        double y = inView->getFieldAs<double>(Dimension::Id::Y, idx);
-        int ypos = (y - m_yOrigin) / m_length;
-
-        Coord loc(xpos, ypos);
-        PointViewPtr& outView = viewMap[loc];
-        if (!outView)
-            outView = inView->makeNew();
-        outView->appendPoint(*inView.get(), idx);
-    }
-
-    // Pull the buffers out of the map and stick them in the standard
-    // output set, setting the bounds as we go.
-    for (auto bi = viewMap.begin(); bi != viewMap.end(); ++bi)
-        viewSet.insert(bi->second);
-    return viewSet;
-}
-
-} // pdal
diff --git a/filters/splitter/SplitterFilter.hpp b/filters/splitter/SplitterFilter.hpp
deleted file mode 100644
index 7926dd6..0000000
--- a/filters/splitter/SplitterFilter.hpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
- *       names of its contributors may be used to endorse or promote
- *       products derived from this software without specific prior
- *       written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/plugin.hpp>
-
-extern "C" int32_t SplitterFilter_ExitFunc();
-extern "C" PF_ExitFunc SplitterFilter_InitPlugin();
-
-namespace pdal
-{
-
-class PDAL_DLL SplitterFilter : public pdal::Filter
-{
-public:
-    SplitterFilter() : Filter()
-        {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    double m_length;
-    double m_xOrigin;
-    double m_yOrigin;
-
-    virtual void addArgs(ProgramArgs& args);
-    virtual PointViewSet run(PointViewPtr view);
-
-    SplitterFilter& operator=(const SplitterFilter&); // not implemented
-    SplitterFilter(const SplitterFilter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/filters/stats/CMakeLists.txt b/filters/stats/CMakeLists.txt
deleted file mode 100644
index fe1cabe..0000000
--- a/filters/stats/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Stats filter CMake configuration
-#
-
-#
-# Stats Filter
-#
-set(srcs
-    StatsFilter.cpp
-)
-
-set(incs
-    StatsFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter stats "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/stats/StatsFilter.cpp b/filters/stats/StatsFilter.cpp
deleted file mode 100644
index f05279f..0000000
--- a/filters/stats/StatsFilter.cpp
+++ /dev/null
@@ -1,257 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "StatsFilter.hpp"
-
-#include <cmath>
-#include <unordered_map>
-
-#include <pdal/pdal_export.hpp>
-#include <pdal/Options.hpp>
-#include <pdal/Polygon.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "filters.stats",
-    "Compute statistics about each dimension (mean, min, max, etc.)",
-    "http://pdal.io/stages/filters.stats.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, StatsFilter, Filter, s_info)
-
-std::string StatsFilter::getName() const { return s_info.name; }
-
-namespace stats
-{
-
-void Summary::extractMetadata(MetadataNode &m) const
-{
-    uint32_t cnt = static_cast<uint32_t>(count());
-    m.add("count", cnt, "count");
-    m.add("minimum", minimum(), "minimum");
-    m.add("maximum", maximum(), "maximum");
-    m.add("average", average(), "average");
-
-    double std = stddev();
-    if (!std::isinf(std) && !std::isnan(std))
-        m.add("stddev", std, "standard deviation");
-
-    double k = kurtosis();
-    if (!std::isinf(k) && !std::isnan(k))
-        m.add("kurtosis", k, "kurtosis");
-
-    double sk = skewness();
-    if (!std::isinf(sk) && !std::isnan(sk))
-        m.add("skewness", skewness(), "skewness");
-
-    double v = variance();
-    if (!std::isinf(v) && !std::isnan(v))
-        m.add("variance", v, "variance");
-    m.add("name", m_name, "name");
-    if (m_enumerate == Enumerate)
-        for (auto& v : m_values)
-            m.addList("values", v.first);
-    else if (m_enumerate == Count)
-        for (auto& v : m_values)
-        {
-            std::string val =
-                std::to_string(v.first) + "/" + std::to_string(v.second);
-            m.addList("counts", val);
-        }
-}
-
-} // namespace stats
-
-using namespace stats;
-
-bool StatsFilter::processOne(PointRef& point)
-{
-    for (auto p = m_stats.begin(); p != m_stats.end(); ++p)
-    {
-        Dimension::Id d = p->first;
-        Summary& c = p->second;
-        c.insert(point.getFieldAs<double>(d));
-    }
-    return true;
-}
-
-
-void StatsFilter::filter(PointView& view)
-{
-    PointRef point(view, 0);
-    for (PointId idx = 0; idx < view.size(); ++idx)
-    {
-        point.setPointId(idx);
-        processOne(point);
-    }
-}
-
-
-void StatsFilter::done(PointTableRef table)
-{
-    extractMetadata(table);
-}
-
-
-void StatsFilter::addArgs(ProgramArgs& args)
-{
-    args.add("dimensions", "Dimensions on which to calculate statistics",
-        m_dimNames);
-    args.add("enumerate", "Dimensions whose values should be enumerated",
-        m_enums);
-    args.add("count", "Dimensions whose values should be counted", m_counts);
-}
-
-
-void StatsFilter::prepared(PointTableRef table)
-{
-    PointLayoutPtr layout(table.layout());
-    std::unordered_map<std::string, Summary::EnumType> dims;
-    std::ostream& out = log()->get(LogLevel::Warning);
-
-    // Add dimensions to the list.
-    if (m_dimNames.empty())
-    {
-        for (auto id : layout->dims())
-            dims[layout->dimName(id)] = Summary::NoEnum;
-    }
-    else
-    {
-        for (auto& s : m_dimNames)
-        {
-            if (layout->findDim(s) == Dimension::Id::Unknown)
-                out << "Dimension '" << s << "' listed in --dimensions "
-                    "option does not exist.  Ignoring." << std::endl;
-            else
-                dims[s] = Summary::NoEnum;
-        }
-    }
-
-    // Set the enumeration flag for those dimensions specified.
-    for (auto& s : m_enums)
-    {
-        if (dims.find(s) == dims.end())
-            out << "Dimension '" << s << "' listed in --enumerate option "
-                "does not exist.  Ignoring." << std::endl;
-        else
-            dims[s] = Summary::Enumerate;
-    }
-
-    // Set the enumeration flag for those dimensions specified.
-    for (auto& s : m_counts)
-    {
-        if (dims.find(s) == dims.end())
-            out << "Dimension '" << s << "' listed in --count option "
-                "does not exist.  Ignoring." << std::endl;
-        else
-            dims[s] = Summary::Count;
-    }
-
-    // Create the summary objects.
-    for (auto& dv : dims)
-        m_stats.insert(std::make_pair(layout->findDim(dv.first),
-            Summary(dv.first, dv.second)));
-}
-
-
-void StatsFilter::extractMetadata(PointTableRef table)
-{
-    uint32_t position(0);
-
-    bool bNoPoints(true);
-    for (auto di = m_stats.begin(); di != m_stats.end(); ++di)
-    {
-        const Summary& s = di->second;
-
-        bNoPoints = (bool)s.count();
-
-        MetadataNode t = m_metadata.addList("statistic");
-        t.add("position", position++);
-        s.extractMetadata(t);
-    }
-
-    // If we have X, Y, & Z dims, output bboxes
-    auto xs = m_stats.find(Dimension::Id::X);
-    auto ys = m_stats.find(Dimension::Id::Y);
-    auto zs = m_stats.find(Dimension::Id::Z);
-    if (xs != m_stats.end() &&
-        ys != m_stats.end() &&
-        zs != m_stats.end() &&
-        bNoPoints)
-    {
-        BOX3D box(xs->second.minimum(), ys->second.minimum(), zs->second.minimum(),
-                  xs->second.maximum(), ys->second.maximum(), zs->second.maximum());
-        pdal::Polygon p(box);
-
-        MetadataNode mbox = Utils::toMetadata(box);
-        MetadataNode box_metadata = m_metadata.add("bbox");
-        MetadataNode metadata = box_metadata.add("native");
-        MetadataNode boundary = metadata.add("boundary", p.json());
-        MetadataNode bbox = metadata.add(mbox);
-        SpatialReference ref = table.anySpatialReference();
-        // if we don't get an SRS from the PointTableRef,
-        // we won't add another metadata node
-        if (!ref.empty())
-        {
-            p.setSpatialReference(ref);
-            SpatialReference epsg4326("EPSG:4326");
-            pdal::Polygon pdd = p.transform(epsg4326);
-            BOX3D ddbox = pdd.bounds();
-            MetadataNode epsg_4326_box = Utils::toMetadata(ddbox);
-            MetadataNode dddbox = box_metadata.add("EPSG:4326");
-            dddbox.add(epsg_4326_box);
-            MetadataNode ddboundary = dddbox.add("boundary", pdd.json());
-
-
-        }
-    }
-}
-
-
-const Summary& StatsFilter::getStats(Dimension::Id dim) const
-{
-    for (auto di = m_stats.begin(); di != m_stats.end(); ++di)
-    {
-        Dimension::Id d = di->first;
-        if (d == dim)
-            return di->second;
-    }
-    throw pdal_error("Dimension not found");
-}
-
-} // namespace pdal
diff --git a/filters/stats/StatsFilter.hpp b/filters/stats/StatsFilter.hpp
deleted file mode 100644
index b7ada25..0000000
--- a/filters/stats/StatsFilter.hpp
+++ /dev/null
@@ -1,169 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/plugin.hpp>
-
-extern "C" int32_t StatsFilter_ExitFunc();
-extern "C" PF_ExitFunc StatsFilter_InitPlugin();
-
-namespace pdal
-{
-namespace stats
-{
-
-class PDAL_DLL Summary
-{
-public:
-    enum EnumType
-    {
-        NoEnum,
-        Enumerate,
-        Count
-    };
-
-typedef std::map<double, point_count_t> EnumMap;
-
-public:
-    Summary(std::string name, EnumType enumerate) :
-        m_name(name), m_enumerate(enumerate)
-    { reset(); }
-
-    double minimum() const
-        { return m_min; }
-    double maximum() const
-        { return m_max; }
-    double average() const
-        { return m_avg; }
-    double variance() const
-        { return M2/(m_cnt - 1.0); }
-    double stddev() const
-        { return std::sqrt(variance()); }
-    double skewness() const
-        { return std::sqrt(double(m_cnt)) * M3 / std::pow(M2, 1.5); }
-    double kurtosis() const
-        { return double(m_cnt)*M4 / (M2*M2) - 3.0; }
-    point_count_t count() const
-        { return m_cnt; }
-    std::string name() const
-        { return m_name; }
-    const EnumMap& values() const
-        { return m_values; }
-
-    void extractMetadata(MetadataNode &m) const;
-
-    void reset()
-    {
-        m_max = (std::numeric_limits<double>::lowest)();
-        m_min = (std::numeric_limits<double>::max)();
-        m_cnt = 0;
-        m_avg = 0.0;
-        M1 = M2 = M3 = M4 = 0.0;
-    }
-
-    void insert(double value)
-    {
-        double delta, delta_n, delta_n2, term1;
-
-        point_count_t n1(m_cnt);
-
-        m_cnt++;
-        point_count_t n(m_cnt);
-        m_min = (std::min)(m_min, value);
-        m_max = (std::max)(m_max, value);
-        m_avg += (value - m_avg) / m_cnt;
-        if (m_enumerate != NoEnum)
-            m_values[value]++;
-
-        // stolen from http://www.johndcook.com/blog/skewness_kurtosis/
-
-        n1 = n;
-        delta = value - M1;
-        delta_n = delta / n;
-        delta_n2 = delta_n * delta_n;
-        term1 = delta * delta_n * n1;
-        M1 += delta_n;
-        M4 += term1 * delta_n2 * (n*n - 3*n + 3) +
-            (6 * delta_n2 * M2) - (4 * delta_n * M3);
-        M3 += term1 * delta_n * (n - 2) - 3 * delta_n * M2;
-        M2 += term1;
-    }
-
-private:
-    std::string m_name;
-    EnumType m_enumerate;
-    double m_max;
-    double m_min;
-    double m_avg;
-    EnumMap m_values;
-    point_count_t m_cnt;
-    double M1, M2, M3, M4;
-};
-
-} // namespace stats
-
-// This is just a pass-through filter, which collects some stats about
-// the points that are fed through it
-class PDAL_DLL StatsFilter : public Filter
-{
-public:
-    StatsFilter() : Filter()
-        {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-    const stats::Summary& getStats(Dimension::Id d) const;
-    void reset();
-
-private:
-    StatsFilter& operator=(const StatsFilter&); // not implemented
-    StatsFilter(const StatsFilter&); // not implemented
-    virtual void addArgs(ProgramArgs& args);
-    virtual bool processOne(PointRef& point);
-    virtual void prepared(PointTableRef table);
-    virtual void done(PointTableRef table);
-    virtual void filter(PointView& view);
-    void extractMetadata(PointTableRef table);
-
-    StringList m_dimNames;
-    StringList m_enums;
-    StringList m_counts;
-    std::map<Dimension::Id, stats::Summary> m_stats;
-};
-
-} // namespace pdal
diff --git a/filters/streamcallback/CMakeLists.txt b/filters/streamcallback/CMakeLists.txt
deleted file mode 100644
index 7ca05f0..0000000
--- a/filters/streamcallback/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-#
-# StreamCallback filter CMake configuration
-#
-
-set(srcs StreamCallbackFilter.cpp)
-
-set(incs StreamCallbackFilter.hpp)
-
-PDAL_ADD_DRIVER(filter streamcallback "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/filters/transformation/CMakeLists.txt b/filters/transformation/CMakeLists.txt
deleted file mode 100644
index 6890fa8..0000000
--- a/filters/transformation/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-#
-# Transformation filter CMake configuration
-#
-
-#
-# Transformation Filter
-#
-set(srcs
-    TransformationFilter.cpp
-)
-
-set(incs
-    TransformationFilter.hpp
-)
-
-PDAL_ADD_DRIVER(filter transformation "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
-
diff --git a/filters/transformation/TransformationFilter.cpp b/filters/transformation/TransformationFilter.cpp
deleted file mode 100644
index 7ff369b..0000000
--- a/filters/transformation/TransformationFilter.cpp
+++ /dev/null
@@ -1,128 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Pete Gadomski <pete.gadomski at gmail.com>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "TransformationFilter.hpp"
-
-#include <pdal/pdal_export.hpp>
-#include <pdal/pdal_macros.hpp>
-
-#include <sstream>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "filters.transformation",
-    "Transform each point using a 4x4 transformation matrix",
-    "http://pdal.io/stages/filters.transformation.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, TransformationFilter, Filter, s_info)
-
-std::string TransformationFilter::getName() const { return s_info.name; }
-
-TransformationMatrix transformationMatrixFromString(const std::string& s)
-{
-    std::istringstream iss(s);
-    TransformationMatrix matrix;
-    double entry;
-    TransformationMatrix::size_type i = 0;
-    while (iss >> entry)
-    {
-        if (i + 1 > matrix.size())
-        {
-            std::stringstream msg;
-            msg << "Too many entries in transformation matrix, should be "
-                << matrix.size();
-            throw pdal_error(msg.str());
-        }
-        matrix[i++] = entry;
-    }
-
-    if (i != matrix.size())
-    {
-        std::stringstream msg;
-        msg << "Too few entries in transformation matrix: "
-            << i
-            << " (should be "
-            << matrix.size()
-            << ")";
-
-        throw pdal_error(msg.str());
-    }
-
-    return matrix;
-}
-
-
-void TransformationFilter::addArgs(ProgramArgs& args)
-{
-    args.add("matrix", "Transformation matrix", m_matrixSpec).setPositional();
-}
-
-
-void TransformationFilter::initialize()
-{
-    m_matrix = transformationMatrixFromString(m_matrixSpec);
-}
-
-
-bool TransformationFilter::processOne(PointRef& point)
-{
-    double x = point.getFieldAs<double>(Dimension::Id::X);
-    double y = point.getFieldAs<double>(Dimension::Id::Y);
-    double z = point.getFieldAs<double>(Dimension::Id::Z);
-
-    point.setField(Dimension::Id::X,
-        x * m_matrix[0] + y * m_matrix[1] + z * m_matrix[2] + m_matrix[3]);
-
-    point.setField(Dimension::Id::Y,
-        x * m_matrix[4] + y * m_matrix[5] + z * m_matrix[6] + m_matrix[7]);
-
-    point.setField(Dimension::Id::Z,
-        x * m_matrix[8] + y * m_matrix[9] + z * m_matrix[10] + m_matrix[11]);
-    return true;
-}
-
-
-void TransformationFilter::filter(PointView& view)
-{
-    PointRef point(view, 0);
-    for (PointId idx = 0; idx < view.size(); ++idx)
-    {
-        point.setPointId(idx);
-        processOne(point);
-    }
-}
-
-} // namespace pdal
diff --git a/include/pdal/DimUtil.hpp b/include/pdal/DimUtil.hpp
deleted file mode 100644
index fe5fcb6..0000000
--- a/include/pdal/DimUtil.hpp
+++ /dev/null
@@ -1,192 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2016, Hobu Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of the Martin Isenburg or Iowa Department
- *       of Natural Resources nor the names of its contributors may be
- *       used to endorse or promote products derived from this software
- *       without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#pragma once
-
-#include <string>
-#include <vector>
-
-#include <pdal/util/Utils.hpp>
-
-namespace pdal
-{
-namespace Dimension
-{
-
-enum class BaseType
-{
-    None = 0x000,
-    Signed = 0x100,
-    Unsigned = 0x200,
-    Floating = 0x400
-};
-
-inline BaseType fromName(std::string name)
-{
-    if (name == "signed")
-        return BaseType::Signed;
-    else if (name == "unsigned")
-        return BaseType::Unsigned;
-    else if (name == "floating")
-        return BaseType::Floating;
-    return BaseType::None;
-}
-
-inline std::string toName(BaseType b)
-{
-    switch (b)
-    {
-    case BaseType::Signed:
-        return "signed";
-    case BaseType::Unsigned:
-        return "unsigned";
-    case BaseType::Floating:
-        return "floating";
-    default:
-        return "";
-    }
-}
-
-enum class Type
-{
-    None = 0,
-    Unsigned8 = Utils::toNative(BaseType::Unsigned) | 1,
-    Signed8 = Utils::toNative(BaseType::Signed) | 1,
-    Unsigned16 = Utils::toNative(BaseType::Unsigned) | 2,
-    Signed16 = Utils::toNative(BaseType::Signed) | 2,
-    Unsigned32 = Utils::toNative(BaseType::Unsigned) | 4,
-    Signed32 = Utils::toNative(BaseType::Signed) | 4,
-    Unsigned64 = Utils::toNative(BaseType::Unsigned )| 8,
-    Signed64 = Utils::toNative(BaseType::Signed) | 8,
-    Float = Utils::toNative(BaseType::Floating) | 4,
-    Double = Utils::toNative(BaseType::Floating) | 8
-};
-
-inline std::size_t size(Type t)
-{
-    return Utils::toNative(t) & 0xFF;
-}
-
-inline BaseType base(Type t)
-{
-    return BaseType(Utils::toNative(t) & 0xFF00);
-}
-
-static const int COUNT = std::numeric_limits<uint16_t>::max();
-static const int PROPRIETARY = 0xFF00;
-
-/// Get a string reresentation of a datatype.
-/// \param[in] dimtype  Dimension type.
-/// \return  String representation of dimension type.
-inline std::string interpretationName(Type dimtype)
-{
-    switch (dimtype)
-    {
-    case Type::None:
-        return "unknown";
-    case Type::Signed8:
-        return "int8_t";
-    case Type::Signed16:
-        return "int16_t";
-    case Type::Signed32:
-        return "int32_t";
-    case Type::Signed64:
-        return "int64_t";
-    case Type::Unsigned8:
-        return "uint8_t";
-    case Type::Unsigned16:
-        return "uint16_t";
-    case Type::Unsigned32:
-        return "uint32_t";
-    case Type::Unsigned64:
-        return "uint64_t";
-    case Type::Float:
-        return "float";
-    case Type::Double:
-        return "double";
-    }
-    return "unknown";
-}
-
-
-/// Get the type corresponding to a type name.
-/// \param s  Name of type.
-/// \return  Corresponding type enumeration value.
-inline Type type(std::string s)
-{
-    s = Utils::tolower(s);
-
-    if (s == "int8_t" || s == "int8")
-       return Type::Signed8;
-    if (s == "int16_t" || s == "int16")
-       return Type::Signed16;
-    if (s == "int32_t" || s == "int32")
-       return Type::Signed32;
-    if (s == "int64_t" || s == "int64")
-       return Type::Signed64;
-    if (s == "uint8_t" || s == "uint8")
-        return Type::Unsigned8;
-    if (s == "uint16_t" || s == "uint16")
-        return Type::Unsigned16;
-    if (s == "uint32_t" || s == "uint32")
-        return Type::Unsigned32;
-    if (s == "uint64_t" || s == "uint64")
-        return Type::Unsigned64;
-    if (s == "float")
-        return Type::Float;
-    if (s == "double")
-        return Type::Double;
-    return Type::None;
-}
-
-
-/// Extract a dimension name of a string.  Dimension names start with an alpha
-/// and continue with numbers or underscores.
-/// \param s  String from which to extract dimension name.
-/// \param p  Position at which to start extracting.
-/// \return  Number of characters in the extracted name.
-inline std::size_t extractName(const std::string& s, std::string::size_type p)
-{
-    if (!std::isalpha(s[p++]))
-        return 0;
-    auto isvalid = [](int c)
-    {
-        return std::isalpha(c) || std::isdigit(c) || c == '_';
-    };
-    return Utils::extract(s, p, isvalid) + 1;
-}
-
-} // namespace Dimension
-} // namespace pdal
-
diff --git a/include/pdal/Eigen.hpp b/include/pdal/Eigen.hpp
deleted file mode 100644
index f43237e..0000000
--- a/include/pdal/Eigen.hpp
+++ /dev/null
@@ -1,140 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_internal.hpp>
-#include <pdal/util/Bounds.hpp>
-
-#include <Eigen/Dense>
-
-#include <vector>
-
-namespace pdal
-{
-class PointView;
-
-/**
- * \brief Compute the centroid of a collection of points.
- *
- * Computes the 3D centroid of a collection of points (specified by PointId)
- * sampled from the input PointView.
- *
- * \code
- * // build 3D kd-tree
- * KD3Index kdi(view);
- * kdi.build();
- *
- * // find the k-nearest neighbors of the first point (k=8)
- * double x = view.getFieldAs<double>(Dimension::Id::X, 0);
- * double y = view.getFieldAs<double>(Dimension::Id::Y, 0);
- * double z = view.getFieldAs<double>(Dimension::Id::Z, 0);
- * auto ids = kdi.neighbors(x, y, z, 8);
- *
- * // compute the centroid
- * auto centroid = computeCentroid(view, ids);
- * \endcode
- *
- * \param view the source PointView.
- * \param ids a vector of PointIds specifying a subset of points.
- * \return the 3D centroid of the XYZ dimensions.
- */
-PDAL_DLL Eigen::Vector3f computeCentroid(PointView& view, std::vector<PointId> ids);
-
-/**
- * \brief Compute the covariance matrix of a collection of points.
- *
- * Computes the covariance matrix of a collection of points (specified by
- * PointId) sampled from the input PointView.
- *
- * \code
- * // build 3D kd-tree
- * KD3Index kdi(view);
- * kdi.build();
- *
- * // find the k-nearest neighbors of the first point (k=8)
- * double x = view.getFieldAs<double>(Dimension::Id::X, 0);
- * double y = view.getFieldAs<double>(Dimension::Id::Y, 0);
- * double z = view.getFieldAs<double>(Dimension::Id::Z, 0);
- * auto ids = kdi.neighbors(x, y, z, 8);
- *
- * // compute the covariance
- * auto cov = computeCovariance(view, ids);
- * \endcode
- *
- * \param view the source PointView.
- * \param ids a vector of PointIds specifying a subset of points.
- * \return the covariance matrix of the XYZ dimensions.
- */
-PDAL_DLL Eigen::Matrix3f computeCovariance(PointView& view, std::vector<PointId> ids);
-
-/**
- * \brief Compute the rank of a collection of points.
- *
- * Computes the rank of a collection of points (specified by PointId) sampled
- * from the input PointView. This method uses Eigen's JacobiSVD class to solve
- * the singular value decomposition and to estimate the rank using the given
- * threshold. A singular value will be considered nonzero if its absolute value
- * is greater than the product of the user-supplied threshold and the absolute
- * value of the maximum singular value.
- *
- * More on JacobiSVD can be found at
- * https://eigen.tuxfamily.org/dox/classEigen_1_1JacobiSVD.html.
- *
- * \code
- * // build 3D kd-tree
- * KD3Index kdi(view);
- * kdi.build();
- *
- * // find the k-nearest neighbors of the first point (k=8)
- * double x = view.getFieldAs<double>(Dimension::Id::X, 0);
- * double y = view.getFieldAs<double>(Dimension::Id::Y, 0);
- * double z = view.getFieldAs<double>(Dimension::Id::Z, 0);
- * auto ids = kdi.neighbors(x, y, z, 8);
- *
- * // compute the rank using threshold of 0.01
- * auto rank = computeRank(view, ids, 0.01);
- * \endcode
- *
- * \param view the source PointView.
- * \param ids a vector of PointIds specifying a subset of points.
- * \return the estimated rank.
- */
-PDAL_DLL uint8_t computeRank(PointView& view, std::vector<PointId> ids, double threshold);
-
-// createDSM returns a matrix with minimum Z values from the provided
-// PointView.
-PDAL_DLL Eigen::MatrixXd createDSM(PointView& view, int rows, int cols,
-                                   double cell_size, BOX2D bounds);
-} // namespace pdal
diff --git a/include/pdal/FlexWriter.hpp b/include/pdal/FlexWriter.hpp
deleted file mode 100644
index ede9ad7..0000000
--- a/include/pdal/FlexWriter.hpp
+++ /dev/null
@@ -1,141 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Hobu Inc. (hobu at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. nor the names of its contributors
-*       may be used to endorse or promote products derived from this
-*       software without specific prior written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/PDALUtils.hpp>
-#include <pdal/Scaling.hpp>
-#include <pdal/Writer.hpp>
-
-namespace pdal
-{
-
-class PDAL_DLL FlexWriter : public Writer
-{
-protected:
-    FlexWriter() : m_filenum(1)
-    {}
-
-    void validateFilename(PointTableRef table)
-    {
-        if (!table.supportsView() && (m_hashPos != std::string::npos))
-        {
-            std::ostringstream oss;
-            oss << getName() << ": Can't write with template-based "
-                "filename using streaming point table.";
-            throw oss.str();
-        }
-    }
-
-    Scaling m_scaling;
-
-private:
-    virtual void writerInitialize(PointTableRef table)
-    {
-        Writer::writerInitialize(table);
-        handleFilenameTemplate();
-    }
-
-    std::string generateFilename()
-    {
-        std::string filename = m_filename;
-        if (m_hashPos != std::string::npos) {
-            std::string fileCount = std::to_string(m_filenum++);
-            filename.replace(m_hashPos, 1, fileCount);
-        }
-        return filename;
-    }
-
-#if (__GNUG__ < 4 || (__GNUG__ == 4 && __GNUG_MINOR__ < 7))
-#define final
-#endif
-
-    virtual void ready(PointTableRef table) final
-    {
-        readyTable(table);
-        if (m_hashPos == std::string::npos)
-        {
-            if (!table.spatialReferenceUnique())
-            {
-                std::ostringstream oss;
-                oss << getName() << ": Attempting to write '" << m_filename <<
-                    "' with multiple spatial references.";
-                Utils::printError(oss.str());
-            }
-            readyFile(generateFilename(), table.spatialReference());
-        }
-    }
-
-    // This essentially moves ready() and done() into write(), which means
-    // that they get executed once for each view.  The check for m_hashPos
-    // is a test to see if the filename specification is a template.  If it's
-    // not a template, ready() and done() are taken care of in the ready()
-    // and done() functions in this class.
-    virtual void write(const PointViewPtr view) final
-    {
-        if (m_hashPos != std::string::npos)
-            readyFile(generateFilename(), view->spatialReference());
-        writeView(view);
-        if (m_hashPos != std::string::npos)
-            doneFile();
-    }
-
-    virtual void done(PointTableRef table) final
-    {
-        if (m_hashPos == std::string::npos)
-            doneFile();
-        doneTable(table);
-    }
-
-#undef final
-
-    virtual void readyTable(PointTableRef table)
-    {}
-
-    virtual void doneTable(PointTableRef table)
-    {}
-
-    virtual void readyFile(const std::string& filename,
-        const SpatialReference& srs) = 0;
-    virtual void writeView(const PointViewPtr view) = 0;
-    virtual void doneFile()
-    {}
-
-    size_t m_filenum;
-
-    FlexWriter& operator=(const FlexWriter&); // not implemented
-    FlexWriter(const FlexWriter&); // not implemented
-};
-
-} // namespace pdal
-
diff --git a/include/pdal/GDALUtils.hpp b/include/pdal/GDALUtils.hpp
deleted file mode 100644
index fd7a61b..0000000
--- a/include/pdal/GDALUtils.hpp
+++ /dev/null
@@ -1,307 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_internal.hpp>
-#include <pdal/Dimension.hpp>
-#include <pdal/util/Bounds.hpp>
-
-#include <pdal/Log.hpp>
-
-#include <array>
-#include <functional>
-#include <sstream>
-#include <vector>
-
-#include <cpl_port.h>
-#include <gdal.h>
-#include <cpl_vsi.h>
-#include <cpl_conv.h>
-#include <ogr_api.h>
-#include <ogr_srs_api.h>
-
-namespace pdal
-{
-
-class SpatialReference;
-
-namespace gdal
-{
-
-PDAL_DLL void registerDrivers();
-PDAL_DLL void unregisterDrivers();
-PDAL_DLL bool reprojectBounds(BOX3D& box, const std::string& srcSrs,
-    const std::string& dstSrs);
-PDAL_DLL std::string lastError();
-
-typedef std::shared_ptr<void> RefPtr;
-
-class SpatialRef
-{
-public:
-    SpatialRef()
-        { newRef(OSRNewSpatialReference("")); }
-    SpatialRef(const std::string& srs)
-    {
-        newRef(OSRNewSpatialReference(""));
-        OSRSetFromUserInput(get(), srs.data());
-    }
-
-    void setFromLayer(OGRLayerH layer)
-        {
-            if (layer)
-            {
-                OGRSpatialReferenceH s = OGR_L_GetSpatialRef(layer);
-                if (s)
-                {
-                    OGRSpatialReferenceH clone = OSRClone(s);
-                    newRef(clone);
-                }
-
-            }
-        }
-    operator bool () const
-        { return m_ref.get() != NULL; }
-    OGRSpatialReferenceH get() const
-        { return m_ref.get(); }
-    std::string wkt() const
-    {
-        char *pszWKT = NULL;
-        OSRExportToWkt(m_ref.get(), &pszWKT);
-        bool valid = (bool)*pszWKT;
-        std::string output(pszWKT);
-        CPLFree(pszWKT);
-        return output;
-    }
-
-    bool empty() const
-    {
-        return wkt().empty();
-    }
-
-private:
-    void newRef(void *v)
-    {
-        m_ref = RefPtr(v, [](void* t){ OSRDestroySpatialReference(t); } );
-    }
-
-    RefPtr m_ref;
-};
-
-class Geometry
-{
-public:
-    Geometry()
-        {}
-    Geometry(const std::string& wkt, const SpatialRef& srs)
-    {
-        OGRGeometryH geom;
-
-        char *p_wkt = const_cast<char *>(wkt.data());
-        OGRSpatialReferenceH ref = srs.get();
-        if (srs.empty())
-        {
-            ref = NULL;
-        }
-        OGRErr err = OGR_G_CreateFromWkt(&p_wkt, ref, &geom);
-        if (err != OGRERR_NONE)
-            throw pdal::pdal_error("unable to construct OGR geometry from wkt!");
-        newRef(geom);
-    }
-
-    operator bool () const
-        { return get() != NULL; }
-    OGRGeometryH get() const
-        { return m_ref.get(); }
-
-    void transform(const SpatialRef& out_srs)
-    {
-        OGR_G_TransformTo(m_ref.get(), out_srs.get());
-    }
-
-    std::string wkt() const
-    {
-        char* p_wkt = 0;
-        OGRErr err = OGR_G_ExportToWkt(m_ref.get(), &p_wkt);
-        return std::string(p_wkt);
-    }
-
-    void setFromGeometry(OGRGeometryH geom)
-        {
-            if (geom)
-                newRef(OGR_G_Clone(geom));
-        }
-
-private:
-    void newRef(void *v)
-    {
-        m_ref = RefPtr(v, [](void* t){ OGR_G_DestroyGeometry(t); } );
-    }
-    RefPtr m_ref;
-};
-
-
-class PDAL_DLL ErrorHandler
-{
-public:
-
-    /**
-      Get the singleton error handler.
-
-      \return  Reference to the error handler.
-    */
-    static ErrorHandler& getGlobalErrorHandler();
-
-    /**
-      Set the log and debug state of the error handler.  This is
-      a convenience and is equivalent to calling setLog() and setDebug().
-
-      \param log  Log to write to.
-      \param doDebug  Debug state of the error handler.
-    */
-    void set(LogPtr log, bool doDebug);
-
-    /**
-      Set the log to which error/debug messages should be written.
-
-      \param log  Log to write to.
-    */
-    void setLog(LogPtr log);
-
-    /**
-      Set the debug state of the error handler.  Setting to true will also
-      set the environment variable CPL_DEBUG to "ON".  This will force GDAL
-      to emit debug error messages which will be logged by this handler.
-
-      \param doDebug  Whether we're setting or clearing the debug state.
-    */
-    void setDebug(bool doDebug);
-
-    /**
-      Get the last error and clear the error last error value.
-
-      \return  The last error number.
-    */
-    int errorNum();
-
-    static void CPL_STDCALL trampoline(::CPLErr code, int num, char const* msg)
-    {
-        ErrorHandler::getGlobalErrorHandler().handle(code, num, msg);
-    }
-
-    ErrorHandler();
-
-private:
-
-    void handle(::CPLErr level, int num, const char *msg);
-
-private:
-    bool m_debug;
-    pdal::LogPtr m_log;
-    int m_errorNum;
-    bool m_cplSet;
-
-};
-
-
-
-enum class GDALError
-{
-    None,
-    NotOpen,
-    CantOpen,
-    NoData,
-    InvalidBand,
-    NoTransform,
-    NotInvertible,
-    CantReadBlock
-};
-
-class PDAL_DLL Raster
-{
-
-public:
-    Raster(const std::string& filename);
-    ~Raster();
-    GDALError open();
-    void close();
-
-    GDALError read(double x, double y, std::vector<double>& data);
-    std::vector<pdal::Dimension::Type> getPDALDimensionTypes() const
-       { return m_types; }
-    /**
-      Read a raster band (layer) into a vector.
-
-      \param band  Vector into which data will be read.  The vector will
-        be resized appropriately to hold the data.
-      \param nBand  Band number to read.  Band numbers start at 1.
-    */
-    GDALError readBand(std::vector<uint8_t>& band, int nBand);
-
-    void pixelToCoord(int column, int row, std::array<double, 2>& output) const;
-    SpatialReference getSpatialRef() const;
-    std::string errorMsg() const
-        { return m_errorMsg; }
-
-    std::string m_filename;
-
-    std::array<double, 6> m_forward_transform;
-    std::array<double, 6> m_inverse_transform;
-
-    int m_raster_x_size;
-    int m_raster_y_size;
-
-    int m_band_count;
-    mutable std::vector<pdal::Dimension::Type> m_types;
-    std::vector<std::array<double, 2>> m_block_sizes;
-
-    GDALDatasetH m_ds;
-    std::string m_errorMsg;
-
-private:
-    bool getPixelAndLinePosition(double x, double y,
-        int32_t& pixel, int32_t& line);
-    GDALError computePDALDimensionTypes();
-};
-
-} // namespace gdal
-
-
-PDAL_DLL std::string transformWkt(std::string wkt, const SpatialReference& from,
-    const SpatialReference& to);
-
-
-} // namespace pdal
-
diff --git a/include/pdal/GEOSUtils.hpp b/include/pdal/GEOSUtils.hpp
deleted file mode 100644
index e9d1540..0000000
--- a/include/pdal/GEOSUtils.hpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Hobu Inc. (info at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-#pragma once
-
-#include <memory>
-
-#include <geos_c.h>
-
-#include <pdal/pdal_types.hpp>
-#include <pdal/Log.hpp>
-
-namespace pdal
-{
-
-namespace geos
-{
-
-class PDAL_DLL ErrorHandler
-{
-public:
-    ~ErrorHandler();
-
-    /**
-      Get the singleton error handler.
-
-      \return  Reference to the error handler.
-    */
-    static ErrorHandler& get();
-
-    /**
-      Set the log and debug state of the error handler.  This is a convenience
-      and is equivalent to calling setLog() and setDebug().
-
-      \param log  Log to write to.
-      \param doDebug  Debug state of the error handler.
-    */
-    void set(LogPtr log, bool doDebug);
-
-    /**
-      Set the log to which error/debug messages should be written.
-
-      \param log  Log to write to.
-    */
-    void setLog(LogPtr log);
-
-    /**
-      Set the debug state of the error handler.  If the error handler is set
-      to debug, output is logged instead of causing an exception.
-
-      \param debug  The debug state of the error handler.
-    */
-    void setDebug(bool debug);
-    
-    /**
-      Get the GEOS context handle.
-
-      \return  The GEOS context handle.
-    */
-    GEOSContextHandle_t ctx() const;
-
-private:
-    ErrorHandler();
-
-    void handle(const char *msg, bool notice);
-    static void vaErrorCb(const char *msg, ...);
-    static void vaNoticeCb(const char *msg, ...);
-
-    GEOSContextHandle_t m_ctx;
-    bool m_debug;
-    LogPtr m_log;
-    static std::unique_ptr<ErrorHandler> m_instance;
-};
-
-} // namespace geos
-} // namespace pdal
-
diff --git a/include/pdal/KDIndex.hpp b/include/pdal/KDIndex.hpp
deleted file mode 100644
index fcca425..0000000
--- a/include/pdal/KDIndex.hpp
+++ /dev/null
@@ -1,348 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include "nanoflann.hpp"
-
-#include <memory>
-#include <pdal/PointView.hpp>
-
-namespace nanoflann
-{
-    template<typename Distance, class DatasetAdaptor, int DIM,
-        typename IndexType> class KDTreeSingleIndexAdaptor;
-
-    template<class T, class DataSource, typename _DistanceType>
-    struct L2_Adaptor;
-}
-
-namespace pdal
-{
-
-template<int DIM>
-class PDAL_DLL KDIndex
-{
-protected:
-    KDIndex(const PointView& buf) : m_buf(buf)
-    {}
-   
-    ~KDIndex()
-    {}
-
-public:
-    std::size_t kdtree_get_point_count() const
-        { return m_buf.size(); }
-
-    double kdtree_get_pt(const PointId idx, int dim) const;
-    double kdtree_distance(const double *p1, const PointId p2_idx,
-        size_t /*numDims*/) const;
-    template <class BBOX> bool kdtree_get_bbox(BBOX& bb) const;
-    void build()
-    {
-        m_index.reset(new my_kd_tree_t(DIM, *this,
-            nanoflann::KDTreeSingleIndexAdaptorParams(10, DIM)));
-        m_index->buildIndex();
-    }
-
-protected:
-    const PointView& m_buf;
-
-    typedef nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<
-        double, KDIndex, double>, KDIndex, -1, std::size_t> my_kd_tree_t;
-
-    std::unique_ptr<my_kd_tree_t> m_index;
-
-private:
-    KDIndex(const KDIndex&);
-    KDIndex& operator=(KDIndex&);
-};
-
-class PDAL_DLL KD2Index : public KDIndex<2>
-{
-public:
-    KD2Index(const PointView& buf) : KDIndex<2>(buf)
-    {
-        if (!buf.hasDim(Dimension::Id::X))
-            throw pdal_error("KD2Index: point view missing 'X' dimension.");
-        if (!buf.hasDim(Dimension::Id::Y))
-            throw pdal_error("KD2Index: point view missing 'Y' dimension.");
-    }
-
-    PointId neighbor(double x, double y)
-    {
-        std::vector<PointId> ids = neighbors(x, y, 1);
-        return (ids.size() ? ids[0] : 0);
-    }
-
-    std::vector<PointId> neighbors(double x, double y, point_count_t k)
-    {
-        k = std::min(m_buf.size(), k);
-        std::vector<PointId> output(k);
-        std::vector<double> out_dist_sqr(k);
-        nanoflann::KNNResultSet<double, PointId, point_count_t> resultSet(k);
-
-        resultSet.init(&output[0], &out_dist_sqr[0]);
-
-        std::vector<double> pt;
-        pt.push_back(x);
-        pt.push_back(y);
-        m_index->findNeighbors(resultSet, &pt[0], nanoflann::SearchParams(10));
-        return output;
-    }
-
-    std::vector<PointId> radius(double const& x, double const& y,
-        double const& r) const
-    {
-        std::vector<PointId> output;
-        std::vector<std::pair<std::size_t, double>> ret_matches;
-        nanoflann::SearchParams params;
-        params.sorted = true;
-
-        std::vector<double> pt;
-        pt.push_back(x);
-        pt.push_back(y);
-
-        // Our distance metric is square distance, so we use the square of
-        // the radius.
-        const std::size_t count =
-            m_index->radiusSearch(&pt[0], r * r, ret_matches, params);
-
-        for (std::size_t i = 0; i < count; ++i)
-            output.push_back(ret_matches[i].first);
-        return output;
-    }
-};
-
-class PDAL_DLL KD3Index : public KDIndex<3>
-{
-public:
-    KD3Index(const PointView& buf) : KDIndex<3>(buf)
-    {
-        if (!buf.hasDim(Dimension::Id::X))
-            throw pdal_error("KD3Index: point view missing 'X' dimension.");
-        if (!buf.hasDim(Dimension::Id::Y))
-            throw pdal_error("KD3Index: point view missing 'Y' dimension.");
-        if (!buf.hasDim(Dimension::Id::Z))
-            throw pdal_error("KD3Index: point view missing 'Z' dimension.");
-    }
-
-    PointId neighbor(double x, double y, double z)
-    {
-        std::vector<PointId> ids = neighbors(x, y, z, 1);
-        return (ids.size() ? ids[0] : 0);
-    }
-
-    std::vector<PointId> neighbors(double x, double y, double z,
-        point_count_t k)
-    {
-        k = std::min(m_buf.size(), k);
-        std::vector<PointId> output(k);
-        std::vector<double> out_dist_sqr(k);
-        nanoflann::KNNResultSet<double, PointId, point_count_t> resultSet(k);
-
-        resultSet.init(&output[0], &out_dist_sqr[0]);
-
-        std::vector<double> pt;
-        pt.push_back(x);
-        pt.push_back(y);
-        pt.push_back(z);
-        m_index->findNeighbors(resultSet, &pt[0], nanoflann::SearchParams(10));
-        return output;
-    }
-    
-    void knnSearch(double x, double y, double z, point_count_t k,
-        std::vector<PointId> *indices, std::vector<double> *sqr_dists)
-    {
-        k = std::min(m_buf.size(), k);
-        nanoflann::KNNResultSet<double, PointId, point_count_t> resultSet(k);
-        
-        resultSet.init(&indices->front(), &sqr_dists->front());
-        
-        std::vector<double> pt;
-        pt.push_back(x);
-        pt.push_back(y);
-        pt.push_back(z);
-        m_index->findNeighbors(resultSet, &pt[0], nanoflann::SearchParams(10));
-    }
-
-    std::vector<PointId> radius(double x, double y, double z, double r) const
-    {
-        std::vector<PointId> output;
-        std::vector<std::pair<std::size_t, double>> ret_matches;
-        nanoflann::SearchParams params;
-        params.sorted = true;
-
-        std::vector<double> pt;
-        pt.push_back(x);
-        pt.push_back(y);
-        pt.push_back(z);
-
-        // Our distance metric is square distance, so we use the square of
-        // the radius.
-        const std::size_t count =
-            m_index->radiusSearch(&pt[0], r * r, ret_matches, params);
-
-        for (std::size_t i = 0; i < count; ++i)
-            output.push_back(ret_matches[i].first);
-        return output;
-    }
-};
-
-template<>
-inline
-double KDIndex<2>::kdtree_get_pt(const PointId idx, int dim) const
-{
-    if (idx >= m_buf.size())
-        return 0.0;
-
-    Dimension::Id id = Dimension::Id::Unknown;
-    switch (dim)
-    {
-    case 0:
-        id = Dimension::Id::X;
-        break;
-    case 1:
-        id = Dimension::Id::Y;
-        break;
-    default:
-        throw pdal_error("kdtree_get_pt: Request for invalid dimension "
-            "from nanoflann");
-    }
-    return m_buf.getFieldAs<double>(id, idx);
-}
-
-template<>
-inline
-double KDIndex<3>::kdtree_get_pt(const PointId idx, int dim) const
-{
-    if (idx >= m_buf.size())
-        return 0.0;
-
-    Dimension::Id id = Dimension::Id::Unknown;
-    switch (dim)
-    {
-    case 0:
-        id = Dimension::Id::X;
-        break;
-    case 1:
-        id = Dimension::Id::Y;
-        break;
-    case 2:
-        id = Dimension::Id::Z;
-        break;
-    default:
-        throw pdal_error("kdtree_get_pt: Request for invalid dimension "
-            "from nanoflann");
-    }
-    return m_buf.getFieldAs<double>(id, idx);
-}
-
-// nanoflann hands us a vector that represents the position of p1.  We fetch
-// the position of p2 and and compute the square distance.
-template<>
-inline double KDIndex<2>::kdtree_distance(const double *p1, const PointId idx,
-    size_t /*numDims*/) const
-{
-    double d0 = p1[0] - m_buf.getFieldAs<double>(Dimension::Id::X, idx);
-    double d1 = p1[1] - m_buf.getFieldAs<double>(Dimension::Id::Y, idx);
-
-    return (d0 * d0 + d1 * d1);
-}
-
-template<>
-inline double KDIndex<3>::kdtree_distance(const double *p1, const PointId idx,
-    size_t /*numDims*/) const
-{
-    double d0 = p1[0] - m_buf.getFieldAs<double>(Dimension::Id::X, idx);
-    double d1 = p1[1] - m_buf.getFieldAs<double>(Dimension::Id::Y, idx);
-    double d2 = p1[2] - m_buf.getFieldAs<double>(Dimension::Id::Z, idx);
-
-    return (d0 * d0 + d1 * d1 + d2 * d2);
-}
-
-
-template<>
-template <class BBOX>
-bool KDIndex<2>::kdtree_get_bbox(BBOX& bb) const
-{
-    if (m_buf.empty())
-    {
-        bb[0].low = 0.0;
-        bb[0].high = 0.0;
-        bb[1].low = 0.0;
-        bb[1].high = 0.0;
-    }
-    else
-    {
-        BOX2D bounds;
-        m_buf.calculateBounds(bounds);
-
-        bb[0].low = bounds.minx;
-        bb[0].high = bounds.maxx;
-        bb[1].low = bounds.miny;
-        bb[1].high = bounds.maxy;
-    }
-    return true;
-}
-
-template<>
-template <class BBOX>
-bool KDIndex<3>::kdtree_get_bbox(BBOX& bb) const
-{
-    if (m_buf.empty())
-    {
-        bb[0].low = 0.0;
-        bb[0].high = 0.0;
-        bb[1].low = 0.0;
-        bb[1].high = 0.0;
-        bb[2].low = 0.0;
-        bb[2].high = 0.0;
-    }
-    else
-    {
-        BOX3D bounds;
-        m_buf.calculateBounds(bounds);
-
-        bb[0].low = bounds.minx;
-        bb[0].high = bounds.maxx;
-        bb[1].low = bounds.miny;
-        bb[1].high = bounds.maxy;
-        bb[2].low = bounds.minz;
-        bb[2].high = bounds.maxz;
-    }
-    return true;
-}
-
-} // namespace pdal
diff --git a/include/pdal/Kernel.hpp b/include/pdal/Kernel.hpp
deleted file mode 100644
index 957007b..0000000
--- a/include/pdal/Kernel.hpp
+++ /dev/null
@@ -1,143 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <cstdint>
-#include <iosfwd>
-#include <map>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include <pdal/PipelineManager.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-class Options;
-class PointView;
-
-typedef std::shared_ptr<PointView> PointViewPtr;
-
-//
-// The application base class gives us these common options:
-//    --help / -h
-//    --verbose / -v
-//    --version
-//
-class PDAL_DLL Kernel
-{
-    FRIEND_TEST(KernelTest, parseOption);
-
-public:
-    virtual ~Kernel()
-    {}
-
-    // call this, to start the machine
-    int run(const StringList& cmdArgs, LogPtr& log);
-
-    virtual std::string getName() const = 0;
-    std::string getShortName() const
-    {
-        StringList names = Utils::split2(getName(), '.');
-        return names.size() == 2 ? names[1] : std::string();
-    }
-    bool isVisualize() const;
-    void visualize(PointViewPtr view);
-
-protected:
-    // this is protected; your derived class ctor will be the public entry point
-    Kernel();
-    Stage& makeReader(const std::string& inputFile, std::string driver);
-    Stage& makeReader(const std::string& inputFile, std::string driver,
-        Options options);
-    Stage& makeFilter(const std::string& driver, Stage& parent);
-    Stage& makeFilter(const std::string& driver, Stage& parent,
-        Options options);
-    Stage& makeFilter(const std::string& driver);
-    Stage& makeWriter(const std::string& outputFile, Stage& parent,
-        std::string driver);
-    Stage& makeWriter(const std::string& outputFile, Stage& parent,
-        std::string driver, Options options);
-
-public:
-    virtual void addSwitches(ProgramArgs& args)
-    {}
-
-    // implement this, to do sanity checking of cmd line
-    // will throw if the user gave us bad options
-    virtual void validateSwitches(ProgramArgs& args)
-    {}
-
-    // implement this, to do your actual work
-    // it will be wrapped in a global catch try/block for you
-    virtual int execute() = 0;
-
-protected:
-    LogPtr m_log;
-    PipelineManager m_manager;
-    std::string m_driverOverride;
-
-private:
-    int innerRun(ProgramArgs& args);
-    void outputHelp(ProgramArgs& args);
-    void outputVersion();
-    void addBasicSwitches(ProgramArgs& args);
-    void parseCommonOptions();
-
-    void doSwitches(const StringList& cmdArgs, ProgramArgs& args);
-    int doStartup();
-    int doExecution(ProgramArgs& args);
-
-    static bool test_parseOption(std::string o, std::string& stage,
-        std::string& option, std::string& value);
-
-    bool m_showHelp;
-    bool m_showOptions;
-    bool m_showTime;
-    bool m_hardCoreDebug;
-    std::string m_scales;
-    std::string m_offsets;
-    bool m_visualize;
-    std::string m_label;
-
-    Kernel& operator=(const Kernel&); // not implemented
-    Kernel(const Kernel&); // not implemented
-};
-
-PDAL_DLL std::ostream& operator<<(std::ostream& ostr, const Kernel&);
-
-} // namespace pdal
diff --git a/include/pdal/Log.hpp b/include/pdal/Log.hpp
deleted file mode 100644
index 5b8edf6..0000000
--- a/include/pdal/Log.hpp
+++ /dev/null
@@ -1,135 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Howard Butler, hobu.inc at gmail.com
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_internal.hpp>
-
-#include <pdal/util/FileUtils.hpp>
-#include <iosfwd>
-#include <memory>
-
-// Adapted from http://drdobbs.com/cpp/201804215
-
-namespace pdal
-{
-
-
-/// pdal::Log is a logging object that is provided by pdal::Stage to
-/// facilitate logging operations.
-class PDAL_DLL Log
-{
-public:
-
-    /// Constructs a pdal::Log instance.
-    /// @param leaderString A string to presage all log entries with
-    /// @param outputName A filename or one of 'stdout', 'stdlog', or 'stderr'
-    ///                   to use for outputting log information.
-    Log(std::string const& leaderString, std::string const& outputName);
-
-    /// Constructs a pdal::Log instance.
-    /// @param leaderString A string to presage all log entries with
-    /// @param v An existing std::ostream to use for logging (instead of the
-    ///          the instance creating its own)
-    Log(std::string const& leaderString, std::ostream* v);
-
-    /** @name Destructor
-    */
-    /// The destructor will clean up its own internal log stream, but it will
-    /// not touch one that is given via the constructor
-    ~Log();
-
-    /** @name Logging level
-    */
-    /// @return the logging level of the pdal::Log instance
-    LogLevel getLevel()
-    {
-        return m_level;
-    }
-
-    /// Sets the logging level of the pdal::Log instance
-    /// @param v logging level to use for get() comparison operations
-    void setLevel(LogLevel v)
-    {
-        m_level = v;
-    }
-
-    /// Set the leader string.
-    /// \param[in]  leader  Leader string.
-    void setLeader(const std::string& leader)
-        { m_leader = leader; }
-
-    /// @return A string representing the LogLevel
-    std::string getLevelString(LogLevel v) const;
-
-    /** @name Log stream operations
-    */
-    /// @return the stream object that is currently being used to for log
-    /// operations regardless of logging level of the instance.
-    std::ostream* getLogStream()
-    {
-        return m_log;
-    }
-
-    /// Returns the log stream given the logging level.
-    /// @param level logging level to request
-    /// If the logging level asked for with
-    /// pdal::Log::get is less than the logging level of the pdal::Log instance
-    std::ostream& get(LogLevel level = LogLevel::Info);
-
-    /// Sets the floating point precision
-    void floatPrecision(int level);
-
-    /// Clears the floating point precision settings of the streams
-    void clearFloat();
-
-protected:
-    std::ostream *m_log;
-    std::ostream *m_nullStream;
-
-private:
-    Log(const Log&);
-    Log& operator =(const Log&);
-
-    void makeNullStream();
-
-    LogLevel m_level;
-    bool m_deleteStreamOnCleanup;
-    std::string m_leader;
-};
-
-typedef std::shared_ptr<Log> LogPtr;
-
-} // namespace pdal
-
diff --git a/include/pdal/PDALUtils.hpp b/include/pdal/PDALUtils.hpp
deleted file mode 100644
index ac3845e..0000000
--- a/include/pdal/PDALUtils.hpp
+++ /dev/null
@@ -1,270 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2014, Hobu Inc.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of the Martin Isenburg or Iowa Department
- *       of Natural Resources nor the names of its contributors may be
- *       used to endorse or promote products derived from this software
- *       without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#pragma once
-
-#include <pdal/Metadata.hpp>
-#include <pdal/Dimension.hpp>
-#include <pdal/pdal_defines.h>
-#include <pdal/pdal_export.hpp>
-#include <pdal/util/Inserter.hpp>
-#include <pdal/util/Extractor.hpp>
-
-#ifndef WIN32
-#include <sys/fcntl.h>
-#include <unistd.h>
-#endif
-
-namespace pdal
-{
-class Options;
-
-namespace Utils
-{
-
-inline void printError(const std::string& s)
-{
-    std::cerr << "PDAL: " << s << std::endl;
-    std::cerr << std::endl;
-}
-
-inline double toDouble(const Everything& e, Dimension::Type type)
-{
-    using Type = Dimension::Type;
-
-    double d = 0;
-    switch (type)
-    {
-    case Type::Unsigned8:
-        d = e.u8;
-        break;
-    case Type::Unsigned16:
-        d = e.u16;
-        break;
-    case Type::Unsigned32:
-        d = e.u32;
-        break;
-    case Type::Unsigned64:
-        d = (double)e.u64;
-        break;
-    case Type::Signed8:
-        d = e.s8;
-        break;
-    case Type::Signed16:
-        d = e.s16;
-        break;
-    case Type::Signed32:
-        d = e.s32;
-        break;
-    case Type::Signed64:
-        d = (double)e.s64;
-        break;
-    case Type::Float:
-        d = e.f;
-        break;
-    case Type::Double:
-        d = e.d;
-        break;
-    default:
-        break;
-    }
-    return d;
-}
-
-inline Everything extractDim(Extractor& ext, Dimension::Type type)
-{
-    using Type = Dimension::Type;
-
-    Everything e;
-    switch (type)
-    {
-        case Type::Unsigned8:
-            ext >> e.u8;
-            break;
-        case Type::Unsigned16:
-            ext >> e.u16;
-            break;
-        case Type::Unsigned32:
-            ext >> e.u32;
-            break;
-        case Type::Unsigned64:
-            ext >> e.u64;
-            break;
-        case Type::Signed8:
-            ext >> e.s8;
-            break;
-        case Type::Signed16:
-            ext >> e.s16;
-            break;
-        case Type::Signed32:
-            ext >> e.s32;
-            break;
-        case Type::Signed64:
-            ext >> e.s64;
-            break;
-        case Type::Float:
-            ext >> e.f;
-            break;
-        case Type::Double:
-            ext >> e.d;
-            break;
-        case Type::None:
-            break;
-    }
-    return e;
-}
-
-inline void insertDim(Inserter& ins, Dimension::Type type,
-    const Everything& e)
-{
-    using Type = Dimension::Type;
-
-    switch (type)
-    {
-        case Type::Unsigned8:
-            ins << e.u8;
-            break;
-        case Type::Unsigned16:
-            ins << e.u16;
-            break;
-        case Type::Unsigned32:
-            ins << e.u32;
-            break;
-        case Type::Unsigned64:
-            ins << e.u64;
-            break;
-        case Type::Signed8:
-            ins << e.s8;
-            break;
-        case Type::Signed16:
-            ins << e.s16;
-            break;
-        case Type::Signed32:
-            ins << e.s32;
-            break;
-        case Type::Signed64:
-            ins << e.s64;
-            break;
-        case Type::Float:
-            ins << e.f;
-            break;
-        case Type::Double:
-            ins << e.d;
-            break;
-        case Type::None:
-            break;
-    }
-}
-
-
-
-inline MetadataNode toMetadata(const BOX2D& bounds)
-{
-    MetadataNode output("bbox");
-    output.add("minx", bounds.minx);
-    output.add("miny", bounds.miny);
-    output.add("maxx", bounds.maxx);
-    output.add("maxy", bounds.maxy);
-    return output;
-}
-
-inline MetadataNode toMetadata(const BOX3D& bounds)
-{
-    MetadataNode output("bbox");
-    output.add("minx", bounds.minx);
-    output.add("miny", bounds.miny);
-    output.add("minz", bounds.minz);
-    output.add("maxx", bounds.maxx);
-    output.add("maxy", bounds.maxy);
-    output.add("maxz", bounds.maxz);
-    return output;
-}
-
-inline int openProgress(const std::string& filename)
-{
-#ifdef WIN32
-    return -1;
-#else
-    int fd = open(filename.c_str(), O_WRONLY | O_NONBLOCK);
-    if (fd == -1)
-    {
-        std::string out = "Can't open progress file '";
-        out += filename + "'.";
-        printError(out);
-    }
-    return fd;
-#endif
-}
-
-
-inline void closeProgress(int fd)
-{
-#ifdef WIN32
-#else
-    if (fd >= 0)
-        close(fd);
-#endif
-}
-
-
-inline void writeProgress(int fd, const std::string& type,
-    const std::string& text)
-{
-#ifdef WIN32
-#else
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-result"
-    if (fd >= 0)
-    {
-        std::string out = type + ':' + text + '\n';
-
-        // This may error, but we don't care.
-        write(fd, out.c_str(), out.length());
-    }
-#pragma GCC diagnostic pop
-#endif
-}
-
-std::string PDAL_DLL toJSON(const MetadataNode& m);
-void PDAL_DLL toJSON(const MetadataNode& m, std::ostream& o);
-std::istream PDAL_DLL *openFile(const std::string& path, bool asBinary = true);
-std::ostream PDAL_DLL *createFile(const std::string& path,
-    bool asBinary = true);
-void PDAL_DLL closeFile(std::istream *in);
-void PDAL_DLL closeFile(std::ostream *out);
-bool PDAL_DLL fileExists(const std::string& path);
-
-} // namespace Utils
-} // namespace pdal
-
diff --git a/include/pdal/PipelineManager.hpp b/include/pdal/PipelineManager.hpp
deleted file mode 100644
index bf71f16..0000000
--- a/include/pdal/PipelineManager.hpp
+++ /dev/null
@@ -1,145 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_internal.hpp>
-#include <pdal/StageFactory.hpp>
-
-#include <vector>
-#include <string>
-
-namespace pdal
-{
-
-class Options;
-
-class PDAL_DLL PipelineManager
-{
-public:
-    PipelineManager() : m_tablePtr(new PointTable()), m_table(*m_tablePtr),
-            m_progressFd(-1), m_input(nullptr)
-        {}
-    PipelineManager(int progressFd) : m_tablePtr(new PointTable()),
-            m_table(*m_tablePtr), m_progressFd(progressFd), m_input(nullptr)
-        {}
-    PipelineManager(PointTableRef table) : m_table(table), m_progressFd(-1),
-            m_input(nullptr)
-        {}
-    PipelineManager(PointTableRef table, int progressFd) : m_table(table),
-            m_progressFd(progressFd), m_input(nullptr)
-        {}
-    ~PipelineManager();
-
-    void readPipeline(std::istream& input);
-    void readPipeline(const std::string& filename);
-
-    // Use these to manually add stages into the pipeline manager.
-    Stage& addReader(const std::string& type);
-    Stage& addFilter(const std::string& type);
-    Stage& addWriter(const std::string& type);
-
-    // These add stages, hook dependencies and set necessary options.
-    // They're preferable to the above as they're more flexible and safer.
-    Stage& makeReader(const std::string& inputFile, std::string driver);
-    Stage& makeReader(const std::string& inputFile, std::string driver,
-        Options options);
-
-    Stage& makeFilter(const std::string& driver);
-    Stage& makeFilter(const std::string& driver, Options options);
-    Stage& makeFilter(const std::string& driver, Stage& parent);
-    Stage& makeFilter(const std::string& driver, Stage& parent,
-        Options options);
-
-    Stage& makeWriter(const std::string& outputFile, std::string driver);
-    Stage& makeWriter(const std::string& outputFile, std::string driver,
-        Options options);
-    Stage& makeWriter(const std::string& outputFile, std::string driver,
-        Stage& parent);
-    Stage& makeWriter(const std::string& outputFile, std::string driver,
-        Stage& parent, Options options);
-
-    // returns true if the pipeline endpoint is a writer
-    bool isWriterPipeline() const
-        { return (bool)getStage(); }
-
-    // return the pipeline reader endpoint (or nullptr, if not a reader
-    // pipeline)
-    Stage* getStage() const
-        { return m_stages.empty() ? nullptr : m_stages.back(); }
-
-    // Set the log to be available to stages.
-    void setLog(LogPtr& log)
-        { m_log = log; }
-
-    QuickInfo preview() const;
-    void prepare() const;
-    point_count_t execute();
-    void validateStageOptions() const;
-
-    // Get the resulting point views.
-    const PointViewSet& views() const
-        { return m_viewSet; }
-
-    // Get the point table data.
-    PointTableRef pointTable() const
-        { return m_table; }
-
-    MetadataNode getMetadata() const;
-    Options& commonOptions()
-        { return m_commonOptions; }
-    OptionsMap& stageOptions()
-        { return m_stageOptions; }
-    Options& stageOptions(Stage& stage);
-
-private:
-    void setOptions(Stage& stage, const Options& addOps);
-
-    StageFactory m_factory;
-    std::unique_ptr<PointTable> m_tablePtr;
-    PointTableRef m_table;
-    Options m_commonOptions;
-    OptionsMap m_stageOptions;
-    PointViewSet m_viewSet;
-    std::vector<Stage*> m_stages; // stage observer, never owner
-    int m_progressFd;
-    std::istream *m_input;
-    LogPtr m_log;
-
-    PipelineManager& operator=(const PipelineManager&); // not implemented
-    PipelineManager(const PipelineManager&); // not implemented
-};
-typedef std::unique_ptr<PipelineManager> PipelineManagerPtr;
-
-} // namespace pdal
diff --git a/include/pdal/PointLayout.hpp b/include/pdal/PointLayout.hpp
deleted file mode 100644
index 905a272..0000000
--- a/include/pdal/PointLayout.hpp
+++ /dev/null
@@ -1,246 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Hobu Inc.
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <cstddef>
-#include <map>
-#include <string>
-#include <vector>
-
-#include <pdal/DimDetail.hpp>
-#include <pdal/DimType.hpp>
-
-namespace pdal
-{
-
-class PDAL_DLL PointLayout
-{
-public:
-    /**
-      Default constructor.
-    */
-    PointLayout();
-    virtual ~PointLayout() {}
-
-    /**
-      Mark a layout as finalized.  Dimensions can't be added to a finalized
-      PointLayout.
-    */
-    void finalize();
-
-    /**
-      Determine if the PointLayout is finalized.
-
-      \return  Whether the PointLayout is finalized.
-    */
-    bool finalized() const
-        { return m_finalized; }
-
-    /**
-      Register a vector of dimensions.
-
-      \param ids  Vector of IDs to register.
-    */
-    void registerDims(std::vector<Dimension::Id> ids);
-
-    /**
-      Register a list of dimensions.
-
-      \param id  Pointer to list of IDs to register.  The last ID in the list
-        must have the value Unknown.
-    */
-    void registerDims(Dimension::Id *id);
-
-    /**
-      Register use of a standard dimension (declare that a point will contain
-      data for this dimension).  Use the default type for the dimension.
-
-      \param id  ID of dimension to be registered.
-    */
-    void registerDim(Dimension::Id id);
-
-    /**
-      Register use of a standard dimension (declare that a point will contain
-      data for this dimension) if it hasn't already been registered with a
-      "larger" type.  It the dimension already exists with a larger type, this
-      does nothing.
-
-      \param id  ID of dimension to be registered.
-      \param type  Minimum type to assign to the dimension.
-    */
-    void registerDim(Dimension::Id id, Dimension::Type type);
-
-    /**
-      Assign a non-existing (proprietary) dimension with the given name and
-      type.  No check is made to see if the dimension exists as a standard
-      (non-propietary) dimension.  If the dimension has already been
-      assigned as a proprietary dimension, update the type but use the
-      existing Id.  If the dimension has already been assigned with a 
-      larger type, this does nothing.
-
-      \param name  Name of the proprietary dimension to add.
-      \param type  Minimum type to assign to the dimension.
-      \return  ID of the new or existing dimension, or Unknown on failure.
-    */
-    Dimension::Id assignDim( const std::string& name,
-        Dimension::Type type);
-
-    /**
-      Register a dimension if one already exists with the given name using the
-      provided type.  If the dimension doesn't already exist, create it.
-
-      \param name  Name of the dimension to register or assign.
-      \param type  Requested type of the dimension.  Dimension will at least
-        accomodate values of this type.
-      \return  ID of dimension registered or assigned.
-    */
-    Dimension::Id registerOrAssignDim(const std::string name,
-        Dimension::Type type);
-
-    /**
-      Get a list of DimType objects that define the layout.
-
-      \return  A list of DimType objects.
-    */
-    DimTypeList dimTypes() const;
-
-    /**
-      Get a DimType structure for a named dimension.
-
-      \param name  Name of the dimension
-      \return  A DimType associated with the named dimension.  Returns a
-        DimType with an Unknown ID if the dimension isn't part of the layout.
-    */
-    DimType findDimType(const std::string& name) const;
-
-    /**
-      Get the ID of a dimension (standard or proprietary) given its name.
-
-      \param name  Name of the dimension.
-      \return  ID of the dimension or Unknown.
-    */
-    Dimension::Id findDim(const std::string& name) const;
-
-    /**
-      Get the ID of a proprietary dimension given its name.
-
-      \param name  Name of the dimension.
-      \return  ID of the dimension or Unknown.
-    */
-    Dimension::Id findProprietaryDim(const std::string& name) const;
-
-    /**
-      Get the name of a dimension give its ID.  A dimension may have more
-      than one name.  The first one associated with the ID is returned.
-
-      \param id  ID of the dimension.
-      \return  A name associated with the dimension, or a NULL string.
-    */
-    std::string dimName(Dimension::Id id) const;
-
-    /**
-      Determine if the PointLayout uses the dimension with the given ID.
-      
-      \param id  ID of the dimension to check.
-      \return \c true if the layout uses the dimension, \c false otherwise.
-    */
-    bool hasDim(Dimension::Id id) const;
-
-    /**
-      Get a reference to vector of the IDs of currently used dimensions.
-
-      \return  Vector of IDs of dimensions that are part of the layout.
-    */
-    const Dimension::IdList& dims() const;
-
-    /**
-      Get the type of a dimension.
-      
-      \param id  ID of the dimension.
-      \return  Type of the dimension.
-    */
-    Dimension::Type dimType(Dimension::Id id) const;
-
-    /**
-      Get the current size in bytes of the dimension.
-      
-      \param id  ID of the dimension.
-      \return  Size of the dimension in bytes.
-    */
-    size_t dimSize(Dimension::Id id) const;
-
-    /**
-      Get the offset of the dimension in the layout.
-
-      \param id  ID of the dimension.
-      \return  Offset of the dimension in bytes.
-    */
-    size_t dimOffset(Dimension::Id id) const;
-
-    /**
-      Get number of bytes that make up a point.  Returns the sum of the dimSize
-      for all dimensions in the layout.
-
-      \return  Size of a point in bytes.
-    */
-    size_t pointSize() const;
-
-    /**
-      Get a pointer to a dimension's detail information.
-
-      \param id  ID of the dimension.
-      \return  A pointer a dimension's detail.
-    */
-    const Dimension::Detail *dimDetail(Dimension::Id id) const;
-
-private:
-    virtual bool update(Dimension::Detail dd, const std::string& name);
-
-    Dimension::Type resolveType( Dimension::Type t1,
-        Dimension::Type t2);
-
-protected:
-    std::vector<Dimension::Detail> m_detail;
-    Dimension::IdList m_used;
-    std::map<std::string, Dimension::Id> m_propIds;
-    int m_nextFree;
-    std::size_t m_pointSize;
-    bool m_finalized;
-};
-
-typedef PointLayout* PointLayoutPtr;
-
-} // namespace pdal
-
diff --git a/include/pdal/PointView.hpp b/include/pdal/PointView.hpp
deleted file mode 100644
index d457337..0000000
--- a/include/pdal/PointView.hpp
+++ /dev/null
@@ -1,573 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Hobu Inc.
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/DimDetail.hpp>
-#include <pdal/DimType.hpp>
-#include <pdal/PointContainer.hpp>
-#include <pdal/PointLayout.hpp>
-#include <pdal/PointRef.hpp>
-#include <pdal/PointTable.hpp>
-#include <pdal/util/Bounds.hpp>
-
-#include <memory>
-#include <queue>
-#include <set>
-#include <vector>
-#include <deque>
-
-#ifdef PDAL_COMPILER_MSVC
-#  pragma warning(disable: 4244)  // conversion from 'type1' to 'type2', possible loss of data
-#endif
-
-namespace pdal
-{
-namespace plang
-{
-    class BufferedInvocation;
-}
-
-struct PointViewLess;
-class PointView;
-class PointViewIter;
-
-typedef std::shared_ptr<PointView> PointViewPtr;
-typedef std::set<PointViewPtr, PointViewLess> PointViewSet;
-
-class PDAL_DLL PointView : public PointContainer
-{
-    friend class plang::BufferedInvocation;
-    friend class PointIdxRef;
-    friend struct PointViewLess;
-public:
-	PointView(PointTableRef pointTable);
-	PointView(PointTableRef pointTable, const SpatialReference& srs);
-
-    virtual ~PointView()
-    {}
-
-    PointViewIter begin();
-    PointViewIter end();
-
-    int id() const
-        { return m_id; }
-
-    point_count_t size() const
-        { return m_size; }
-
-    bool empty() const
-        { return m_size == 0; }
-
-    inline void appendPoint(const PointView& buffer, PointId id);
-    void append(const PointView& buf)
-    {
-        // We use size() instead of the index end because temp points
-        // might have been placed at the end of the buffer.
-        auto thisEnd = m_index.begin() + size();
-        auto bufEnd = buf.m_index.begin() + buf.size();
-        m_index.insert(thisEnd, buf.m_index.begin(), bufEnd);
-        m_size += buf.size();
-        clearTemps();
-    }
-
-    /// Return a new point view with the same point table as this
-    /// point buffer.
-    PointViewPtr makeNew() const
-    {
-        return PointViewPtr( new PointView(m_pointTable, m_spatialReference));
-    }
-
-    PointRef point(PointId id)
-        { return PointRef(*this, id); }
-
-    template<class T>
-    T getFieldAs(Dimension::Id dim, PointId pointIndex) const;
-
-    inline void getField(char *pos, Dimension::Id d,
-        Dimension::Type type, PointId id) const;
-
-    template<typename T>
-    void setField(Dimension::Id dim, PointId idx, T val);
-
-    inline void setField(Dimension::Id dim, Dimension::Type type,
-        PointId idx, const void *val);
-
-    template <typename T>
-    bool compare(Dimension::Id dim, PointId id1, PointId id2)
-    {
-        return (getFieldInternal<T>(dim, id1) < getFieldInternal<T>(dim, id2));
-    }
-
-    bool compare(Dimension::Id dim, PointId id1, PointId id2)
-    {
-        const Dimension::Detail *dd = layout()->dimDetail(dim);
-
-        switch (dd->type())
-        {
-            case Dimension::Type::Float:
-                return compare<float>(dim, id1, id2);
-                break;
-            case Dimension::Type::Double:
-                return compare<double>(dim, id1, id2);
-                break;
-            case Dimension::Type::Signed8:
-                return compare<int8_t>(dim, id1, id2);
-                break;
-            case Dimension::Type::Signed16:
-                return compare<int16_t>(dim, id1, id2);
-                break;
-            case Dimension::Type::Signed32:
-                return compare<int32_t>(dim, id1, id2);
-                break;
-            case Dimension::Type::Signed64:
-                return compare<int64_t>(dim, id1, id2);
-                break;
-            case Dimension::Type::Unsigned8:
-                return compare<uint8_t>(dim, id1, id2);
-                break;
-            case Dimension::Type::Unsigned16:
-                return compare<uint16_t>(dim, id1, id2);
-                break;
-            case Dimension::Type::Unsigned32:
-                return compare<uint32_t>(dim, id1, id2);
-                break;
-            case Dimension::Type::Unsigned64:
-                return compare<uint64_t>(dim, id1, id2);
-                break;
-            case Dimension::Type::None:
-            default:
-                return false;
-                break;
-        }
-    }
-
-    void getRawField(Dimension::Id dim, PointId idx, void *buf) const
-    {
-        getFieldInternal(dim, idx, buf);
-    }
-
-    /*! @return a cumulated bounds of all points in the PointView.
-        \verbatim embed:rst
-        .. note::
-
-            This method requires that an `X`, `Y`, and `Z` dimension be
-            available, and that it can be casted into a *double* data
-            type using the :cpp:func:`pdal::Dimension::applyScaling`
-            method. Otherwise, an exception will be thrown.
-        \endverbatim
-    */
-    void calculateBounds(BOX2D& box) const;
-    static void calculateBounds(const PointViewSet&, BOX2D& box);
-    void calculateBounds(BOX3D& box) const;
-    static void calculateBounds(const PointViewSet&, BOX3D& box);
-
-    void dump(std::ostream& ostr) const;
-    bool hasDim(Dimension::Id id) const
-        { return layout()->hasDim(id); }
-    std::string dimName(Dimension::Id id) const
-        { return layout()->dimName(id); }
-    Dimension::IdList dims() const
-        { return layout()->dims(); }
-    std::size_t pointSize() const
-        { return layout()->pointSize(); }
-    std::size_t dimSize(Dimension::Id id) const
-        { return layout()->dimSize(id); }
-    Dimension::Type dimType(Dimension::Id id) const
-         { return layout()->dimType(id);}
-    DimTypeList dimTypes() const
-        { return layout()->dimTypes(); }
-    PointLayoutPtr layout() const
-        { return m_pointTable.layout(); }
-    void setSpatialReference(const SpatialReference& spatialRef)
-        { m_spatialReference = spatialRef; }
-    SpatialReference spatialReference() const
-        { return m_spatialReference; }
-
-    /// Fill a buffer with point data specified by the dimension list.
-    /// \param[in] dims  List of dimensions/types to retrieve.
-    /// \param[in] idx   Index of point to get.
-    /// \param[in] buf   Pointer to buffer to fill.
-    void getPackedPoint(const DimTypeList& dims, PointId idx, char *buf) const
-    {
-        for (auto di = dims.begin(); di != dims.end(); ++di)
-        {
-            getField(buf, di->m_id, di->m_type, idx);
-            buf += Dimension::size(di->m_type);
-        }
-    }
-
-    /// Load the point buffer from memory whose arrangement is specified
-    /// by the dimension list.
-    /// \param[in] dims  Dimension/types of data in packed order
-    /// \param[in] idx   Index of point to write.
-    /// \param[in] buf   Packed data buffer.
-    void setPackedPoint(const DimTypeList& dims, PointId idx, const char *buf)
-    {
-        for (auto di = dims.begin(); di != dims.end(); ++di)
-        {
-            setField(di->m_id, di->m_type, idx, (const void *)buf);
-            buf += Dimension::size(di->m_type);
-        }
-    }
-
-
-    /// Provides access to the memory storing the point data.  Though this
-    /// function is public, other access methods are safer and preferred.
-    char *getPoint(PointId id)
-        { return m_pointTable.getPoint(m_index[id]); }
-
-    // The standard idiom is swapping with a stack-created empty queue, but
-    // that invokes the ctor and probably allocates.  We've probably only got
-    // one or two things in our queue, so just pop until we're empty.
-    void clearTemps()
-    {
-        while (!m_temps.empty())
-            m_temps.pop();
-    }
-    MetadataNode toMetadata() const;
-
-protected:
-    PointTableRef m_pointTable;
-    std::deque<PointId> m_index;
-    // The index might be larger than the size to support temporary point
-    // references.
-    point_count_t m_size;
-    int m_id;
-    std::queue<PointId> m_temps;
-    SpatialReference m_spatialReference;
-
-private:
-    static int m_lastId;
-
-    template<typename T_IN, typename T_OUT>
-    bool convertAndSet(Dimension::Id dim, PointId idx, T_IN in);
-
-    virtual void setFieldInternal(Dimension::Id dim, PointId idx,
-        const void *buf);
-    virtual void getFieldInternal(Dimension::Id dim, PointId idx,
-        void *buf) const
-    { m_pointTable.getFieldInternal(dim, m_index[idx], buf); }
-
-    template<class T>
-    T getFieldInternal(Dimension::Id dim, PointId pointIndex) const;
-    inline PointId getTemp(PointId id);
-    void freeTemp(PointId id)
-        { m_temps.push(id); }
-};
-
-struct PointViewLess
-{
-    bool operator () (const PointViewPtr& p1, const PointViewPtr& p2) const
-        { return p1->m_id < p2->m_id; }
-};
-
-template <class T>
-T PointView::getFieldInternal(Dimension::Id dim, PointId id) const
-{
-    T t;
-
-    getFieldInternal(dim, id, &t);
-    return t;
-}
-
-inline void PointView::getField(char *pos, Dimension::Id d,
-    Dimension::Type type, PointId id) const
-{
-    Everything e;
-
-    switch (type)
-    {
-    case Dimension::Type::Float:
-        e.f = getFieldAs<float>(d, id);
-        break;
-    case Dimension::Type::Double:
-        e.d = getFieldAs<double>(d, id);
-        break;
-    case Dimension::Type::Signed8:
-        e.s8 = getFieldAs<int8_t>(d, id);
-        break;
-    case Dimension::Type::Signed16:
-        e.s16 = getFieldAs<int16_t>(d, id);
-        break;
-    case Dimension::Type::Signed32:
-        e.s32 = getFieldAs<int32_t>(d, id);
-        break;
-    case Dimension::Type::Signed64:
-        e.s64 = getFieldAs<int64_t>(d, id);
-        break;
-    case Dimension::Type::Unsigned8:
-        e.u8 = getFieldAs<uint8_t>(d, id);
-        break;
-    case Dimension::Type::Unsigned16:
-        e.u16 = getFieldAs<uint16_t>(d, id);
-        break;
-    case Dimension::Type::Unsigned32:
-        e.u32 = getFieldAs<uint32_t>(d, id);
-        break;
-    case Dimension::Type::Unsigned64:
-        e.u64 = getFieldAs<uint64_t>(d, id);
-        break;
-    case Dimension::Type::None:
-        break;
-    }
-    memcpy(pos, &e, Dimension::size(type));
-}
-
-inline void PointView::setField(Dimension::Id dim,
-    Dimension::Type type, PointId idx, const void *val)
-{
-    Everything e;
-
-    memcpy(&e, val, Dimension::size(type));
-    switch (type)
-    {
-        case Dimension::Type::Float:
-            setField(dim, idx, e.f);
-            break;
-        case Dimension::Type::Double:
-            setField(dim, idx, e.d);
-            break;
-        case Dimension::Type::Signed8:
-            setField(dim, idx, e.s8);
-            break;
-        case Dimension::Type::Signed16:
-            setField(dim, idx, e.s16);
-            break;
-        case Dimension::Type::Signed32:
-            setField(dim, idx, e.s32);
-            break;
-        case Dimension::Type::Signed64:
-            setField(dim, idx, e.s64);
-            break;
-        case Dimension::Type::Unsigned8:
-            setField(dim, idx, e.u8);
-            break;
-        case Dimension::Type::Unsigned16:
-            setField(dim, idx, e.u16);
-            break;
-        case Dimension::Type::Unsigned32:
-            setField(dim, idx, e.u32);
-            break;
-        case Dimension::Type::Unsigned64:
-            setField(dim, idx, e.u64);
-            break;
-        case Dimension::Type::None:
-            break;
-    }
-}
-
-template <class T>
-inline T PointView::getFieldAs(Dimension::Id dim,
-    PointId pointIndex) const
-{
-    assert(pointIndex < m_size);
-    T retval;
-    const Dimension::Detail *dd = layout()->dimDetail(dim);
-    double val;
-
-    switch (dd->type())
-    {
-    case Dimension::Type::Float:
-        val = getFieldInternal<float>(dim, pointIndex);
-        break;
-    case Dimension::Type::Double:
-        val = getFieldInternal<double>(dim, pointIndex);
-        break;
-    case Dimension::Type::Signed8:
-        val = getFieldInternal<int8_t>(dim, pointIndex);
-        break;
-    case Dimension::Type::Signed16:
-        val = getFieldInternal<int16_t>(dim, pointIndex);
-        break;
-    case Dimension::Type::Signed32:
-        val = getFieldInternal<int32_t>(dim, pointIndex);
-        break;
-    case Dimension::Type::Signed64:
-        val = static_cast<double>(getFieldInternal<int64_t>(dim, pointIndex));
-        break;
-    case Dimension::Type::Unsigned8:
-        val = getFieldInternal<uint8_t>(dim, pointIndex);
-        break;
-    case Dimension::Type::Unsigned16:
-        val = getFieldInternal<uint16_t>(dim, pointIndex);
-        break;
-    case Dimension::Type::Unsigned32:
-        val = getFieldInternal<uint32_t>(dim, pointIndex);
-        break;
-    case Dimension::Type::Unsigned64:
-        val = static_cast<double>(getFieldInternal<uint64_t>(dim, pointIndex));
-        break;
-    case Dimension::Type::None:
-    default:
-        val = 0;
-        break;
-    }
-
-    if (!Utils::numericCast(val, retval))
-    {
-        std::ostringstream oss;
-        oss << "Unable to fetch data and convert as requested: ";
-        oss << Dimension::name(dim) << ":" <<
-            Dimension::interpretationName(dd->type()) <<
-            "(" << (double)val << ") -> " << Utils::typeidName<T>();
-        throw pdal_error(oss.str());
-    }
-
-    return retval;
-}
-
-
-template<typename T_IN, typename T_OUT>
-bool PointView::convertAndSet(Dimension::Id dim, PointId idx, T_IN in)
-{
-    T_OUT out;
-
-    bool success = Utils::numericCast(in, out);
-    if (success)
-        setFieldInternal(dim, idx, &out);
-    return success;
-}
-
-
-template<typename T>
-void PointView::setField(Dimension::Id dim, PointId idx, T val)
-{
-    const Dimension::Detail *dd = layout()->dimDetail(dim);
-
-    bool ok = true;
-    switch (dd->type())
-    {
-    case Dimension::Type::Float:
-        ok = convertAndSet<T, float>(dim, idx, val);
-        break;
-    case Dimension::Type::Double:
-        ok = convertAndSet<T, double>(dim, idx, val);
-        break;
-    case Dimension::Type::Signed8:
-        ok = convertAndSet<T, int8_t>(dim, idx, val);
-        break;
-    case Dimension::Type::Signed16:
-        ok = convertAndSet<T, int16_t>(dim, idx, val);
-        break;
-    case Dimension::Type::Signed32:
-        ok = convertAndSet<T, int32_t>(dim, idx, val);
-        break;
-    case Dimension::Type::Signed64:
-        ok = convertAndSet<T, int64_t>(dim, idx, val);
-        break;
-    case Dimension::Type::Unsigned8:
-        ok = convertAndSet<T, uint8_t>(dim, idx, val);
-        break;
-    case Dimension::Type::Unsigned16:
-        ok = convertAndSet<T, uint16_t>(dim, idx, val);
-        break;
-    case Dimension::Type::Unsigned32:
-        ok = convertAndSet<T, uint32_t>(dim, idx, val);
-        break;
-    case Dimension::Type::Unsigned64:
-        ok = convertAndSet<T, uint64_t>(dim, idx, val);
-        break;
-    case Dimension::Type::None:
-        val = 0;
-        break;
-    }
-    if (!ok)
-    {
-        std::ostringstream oss;
-        oss << "Unable to set data and convert as requested: ";
-        oss << Dimension::name(dim) << ":" << Utils::typeidName<T>() <<
-            "(" << (double)val << ") -> " <<
-            Dimension::interpretationName(dd->type());
-        throw pdal_error(oss.str());
-    }
-}
-
-/**
-void PointView::setFieldInternal(Dimension::Id dim, PointId idx,
-    const void *value)
-{
-    PointId rawId = 0;
-    if (idx == size())
-    {
-        rawId = m_pointTable.addPoint();
-        m_index.push_back(rawId);
-        m_size++;
-        assert(m_temps.empty());
-    }
-    else if (idx > size())
-    {
-        std::cerr << "Point index must increment.\n";
-        //error - throw?
-        return;
-    }
-    else
-    {
-        rawId = m_index[idx];
-    }
-    m_pointTable.setFieldInternal(dim, rawId, value);
-}
-**/
-
-inline void PointView::appendPoint(const PointView& buffer, PointId id)
-{
-    // Invalid 'id' is a programmer error.
-    PointId rawId = buffer.m_index[id];
-    m_index.push_back(rawId);
-    m_size++;
-    assert(m_temps.empty());
-}
-
-
-// Make a temporary copy of a point by adding an entry to the index.
-inline PointId PointView::getTemp(PointId id)
-{
-    PointId newid;
-    if (m_temps.size())
-    {
-        newid = m_temps.front();
-        m_temps.pop();
-        m_index[newid] = m_index[id];
-    }
-    else
-    {
-        newid = (PointId)m_index.size();
-        m_index.push_back(m_index[id]);
-    }
-    return newid;
-}
-
-PDAL_DLL std::ostream& operator<<(std::ostream& ostr, const PointView&);
-
-} // namespace pdal
diff --git a/include/pdal/PointViewIter.hpp b/include/pdal/PointViewIter.hpp
deleted file mode 100644
index ddba768..0000000
--- a/include/pdal/PointViewIter.hpp
+++ /dev/null
@@ -1,171 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Hobu Inc.
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <iterator>
-
-#include <pdal/PointView.hpp>
-
-namespace pdal
-{
-
-class PointIdxRef
-{
-private:
-    PointView *m_buf;
-    PointId m_id;
-    bool m_tmp;
-
-public:
-    PointIdxRef() : m_buf(NULL), m_id(0), m_tmp(false)
-    {}
-    PointIdxRef(const PointIdxRef& r) : m_buf(r.m_buf)
-    {
-        m_id = m_buf->getTemp(r.m_id);
-        m_tmp = true;
-    }
-    // This is the ctor used to make a PointIdxRef from an iterator.
-    PointIdxRef(PointView *buf, PointId id) : m_buf(buf), m_id(id), m_tmp(false)
-    {}
-
-    ~PointIdxRef()
-    {
-        if (m_tmp)
-            m_buf->freeTemp(m_id);
-    }
-
-    PointIdxRef& operator=(const PointIdxRef& r)
-    {
-        assert(m_buf == NULL || r.m_buf == m_buf);
-        if (!m_buf)
-        {
-            m_buf = r.m_buf;
-            m_id = m_buf->getTemp(r.m_id);
-            m_tmp = true;
-        }
-        else
-            m_buf->m_index[m_id] = r.m_buf->m_index[r.m_id];
-        return *this;
-    }
-
-    bool compare(Dimension::Id dim, const PointIdxRef& p) const
-        { return m_buf->compare(dim, m_id, p.m_id); }
-
-    void swap(PointIdxRef& p)
-    {
-        PointId id = m_buf->m_index[m_id];
-        m_buf->m_index[m_id] = p.m_buf->m_index[p.m_id];
-        p.m_buf->m_index[p.m_id] = id;
-    }
-};
-
-inline void swap(PointIdxRef && p1, PointIdxRef && p2)
-{
-    p1.swap(p2);
-}
-
-class PointViewIter :
-    public std::iterator<std::random_access_iterator_tag, PointIdxRef,
-        point_count_t>
-{
-protected:
-    PointView *m_buf;
-    PointId m_id;
-
-public:
-    typedef std::random_access_iterator_tag iterator_category;
-    typedef std::iterator<iterator_category, PointIdxRef>::value_type
-            value_type;
-    typedef std::iterator<iterator_category, PointIdxRef>::difference_type
-            difference_type;
-    typedef PointIdxRef reference;
-    typedef void * pointer;
-
-
-    PointViewIter(PointView *buf, PointId id) : m_buf(buf), m_id(id)
-    {}
-
-    PointViewIter& operator++()
-        { ++m_id; return *this; }
-    PointViewIter operator++(int)
-        { return PointViewIter(m_buf, m_id++); }
-    PointViewIter& operator--()
-        { --m_id; return *this; }
-    PointViewIter operator--(int)
-        { return PointViewIter(m_buf, m_id--); }
-
-    PointViewIter operator+(const difference_type& n) const
-        { return PointViewIter(m_buf, m_id + n); }
-    PointViewIter operator+=(const difference_type& n)
-        { m_id += n; return *this; }
-    PointViewIter operator-(const difference_type& n) const
-        { return PointViewIter(m_buf, m_id - n); }
-    PointViewIter operator-=(const difference_type& n)
-        { m_id -= n; return *this; }
-    difference_type operator-(const PointViewIter& i)
-        { return m_id - i.m_id; }
-
-    bool operator==(const PointViewIter& i)
-        { return m_id == i.m_id; }
-    bool operator!=(const PointViewIter& i)
-        { return m_id != i.m_id; }
-    bool operator>=(const PointViewIter& i)
-        { return m_id <= i.m_id; }
-    bool operator<(const PointViewIter& i)
-        { return m_id < i.m_id; }
-    bool operator>(const PointViewIter& i)
-        { return m_id > i.m_id; }
-
-    PointIdxRef operator*() const
-        { return PointIdxRef(m_buf, m_id); }
-    pointer operator->() const
-        { return NULL; }
-    PointIdxRef operator[](const difference_type& /*n*/) const
-        { return PointIdxRef(m_buf, m_id); }
-};
-
-} // namespace pdal
-
-/**
-namespace std
-{
-template<>
-inline void iter_swap(pdal::PointViewIter a, pdal::PointViewIter b)
-{
-    swap(*a, *b);
-}
-}
-**/
-
diff --git a/include/pdal/Polygon.hpp b/include/pdal/Polygon.hpp
deleted file mode 100644
index b5c6854..0000000
--- a/include/pdal/Polygon.hpp
+++ /dev/null
@@ -1,129 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-#pragma once
-
-#include <pdal/GDALUtils.hpp>
-#include <pdal/GEOSUtils.hpp>
-#include <pdal/Log.hpp>
-#include <pdal/PointRef.hpp>
-#include <pdal/SpatialReference.hpp>
-#include <pdal/util/Bounds.hpp>
-
-#include <geos_c.h>
-
-namespace pdal
-{
-
-namespace geos { class ErrorHandler; }
-
-class PDAL_DLL Polygon
-{
-public:
-    Polygon();
-    Polygon(const std::string& wkt_or_json,
-           SpatialReference ref = SpatialReference(),
-           geos::ErrorHandler& handler = geos::ErrorHandler::get());
-    Polygon(const BOX2D&);
-    Polygon(const BOX3D&);
-    Polygon(const Polygon&);
-    Polygon(GEOSGeometry* g, const SpatialReference& srs,
-        geos::ErrorHandler& ctx);
-    Polygon(OGRGeometryH g, const SpatialReference& srs,
-        geos::ErrorHandler& ctx);
-    Polygon& operator=(const Polygon&);
-private:
-    Polygon(const std::string& wkt_or_json, SpatialReference ref,
-        GEOSContextHandle_t ctx);
-    Polygon(GEOSGeometry* g, const SpatialReference& srs,
-        GEOSContextHandle_t ctx);
-
-public:
-    ~Polygon();
-    void update(const std::string& wkt_or_json,
-        SpatialReference ref = SpatialReference());
-
-    void setSpatialReference( const SpatialReference& ref)
-        { m_srs = ref; }
-
-    const SpatialReference& getSpatialReference() const
-        { return m_srs; }
-
-    Polygon transform(const SpatialReference& ref) const;
-
-    bool equals(const Polygon& other, double tolerance=0.0001) const;
-    bool operator==(const Polygon& other) const;
-    bool operator!=(const Polygon& other) const;
-    bool operator<(const Polygon& other) const
-        { return wkt() < other.wkt(); }
-
-    Polygon simplify(double distance_tolerance, double area_tolerance) const;
-    double area() const;
-
-    bool covers(PointRef& ref) const;
-    bool equal(const Polygon& p) const;
-
-    bool valid() const;
-    std::string validReason() const;
-
-    std::string wkt(double precision=8, bool bOutputZ=false) const;
-    std::string json(double precision=8) const;
-
-    BOX3D bounds() const;
-
-    operator bool () const
-        { return m_geom != NULL; }
-
-private:
-    void initializeFromBounds(const BOX3D& b);
-    GEOSGeometry *m_geom;
-    const GEOSPreparedGeometry *m_prepGeom;
-
-    SpatialReference m_srs;
-    GEOSContextHandle_t m_ctx;
-
-    void prepare();
-
-    friend PDAL_DLL std::ostream& operator<<(std::ostream& ostr,
-        const Polygon& p);
-    friend PDAL_DLL std::istream& operator>>(std::istream& istr,
-        Polygon& p);
-};
-
-
-PDAL_DLL std::ostream& operator<<(std::ostream& ostr,
-    const Polygon& p);
-PDAL_DLL std::istream& operator>>(std::istream& istr, Polygon& p);
-
-} // namespace pdal
-
diff --git a/include/pdal/SpatialReference.hpp b/include/pdal/SpatialReference.hpp
deleted file mode 100644
index d7e5265..0000000
--- a/include/pdal/SpatialReference.hpp
+++ /dev/null
@@ -1,188 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2009, Howard Butler
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of the Martin Isenburg or Iowa Department
- *       of Natural Resources nor the names of its contributors may be
- *       used to endorse or promote products derived from this software
- *       without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_internal.hpp>
-
-namespace pdal
-{
-
-class PDAL_DLL BOX3D;
-class PDAL_DLL MetadataNode;
-
-/// A SpatialReference defines a model of the earth that is used to describe
-/// the location of points.
-/// A SpatialReference is part of input data and is automatically loaded
-/// into PDAL by readers, or it's provided explicitly by a user through an
-/// option.  All points in a point view share a common spatial reference.  When
-/// a stage finishes processing point view, the point view takes on the
-/// spatial reference of that stage, if it had one.
-/// A point table tracks the spatial references of the views currently being
-/// processed by a stage.  If a point table being processed by a stage has
-/// more than one spatial reference, PointTable::spatialReference() will
-/// return an empty spatial reference and PointTable::spatialReferenceUnique()
-/// will return false.
-class PDAL_DLL SpatialReference
-{
-public:
-    enum WKTModeFlag
-    {
-        eHorizontalOnly = 1,  ///<  Only Consider horizontal SRS.
-        eCompoundOK = 2       ///<  Consider horizontal and vertical SRS
-    };
-
-    /**
-      Constructor.  Create an empty SRS.
-    */
-    SpatialReference()
-    {}
-
-    /**
-      Construct a spatial reference from well-known text.
-
-      \param wkt  Well-known text from which to construct SRS.
-    */
-    SpatialReference(const std::string& wkt);
-
-    /**
-      Determine if this spatial reference is the same as another.
-
-      \param other  SRS to compare with this one.
-      \return  \c true if the SRSs match
-    */
-    bool equals(const SpatialReference& other) const;
-
-    /**
-      See \ref equals.
-    */
-    bool operator==(const SpatialReference& other) const;
-
-    /**
-      Determine if this spatial reference is different from another.
-
-      \param other  SRS to compare with this one.
-      \return \c true if the SRSs don't match.
-    */
-    bool operator!=(const SpatialReference& other) const;
-
-    /**
-       Determine if the well-known text representation of this SRS is
-       lexographically less than that of another.
-
-       \param other  SRS to compare with this one.
-       \return  \c true if this SRS is lexographically less.
-    */
-    bool operator<(const SpatialReference& other) const
-        { return m_wkt < other.m_wkt; }
-
-    /**
-      Returns true iff the object doesn't contain a valid srs.
-
-      \return  Whether the SRS is empty.
-    */
-    bool empty() const;
-
-
-    // Returns true of OSR can validate the SRS
-    bool valid() const;
-
-    // (this is a cleaner way of saying "getWKT() == "")
-    /// Returns the OGC WKT describing Spatial Reference System.
-    /// If GDAL is linked, it uses GDAL's operations and methods to determine
-    /// the WKT.  If GDAL is not linked, no WKT is returned.
-    /// \param mode_flag May be eHorizontalOnly indicating the WKT will not
-    /// include vertical coordinate system info (the default), or
-    /// eCompoundOK indicating the the returned WKT may be a compound
-    /// coordinate system if there is vertical coordinate system info
-    /// available.
-    std::string getWKT(WKTModeFlag mode_flag = eHorizontalOnly) const;
-    std::string getWKT(WKTModeFlag mode_flag, bool pretty) const;
-
-    /// Sets the SRS using GDAL's OGC WKT. If GDAL is not linked, this
-    /// operation has no effect.
-    /// \param v - a string containing the WKT string.
-    void setWKT(std::string const& v)
-        { m_wkt = v; }
-
-    /// Sets the SRS using GDAL's SetFromUserInput function. If GDAL is
-    /// not linked, this operation has no effect.
-    /// \param v - a string containing the definition (filename, proj4,
-    ///    wkt, etc).
-    void setFromUserInput(std::string const& v);
-
-    /// Returns the Proj.4 string describing the Spatial Reference System.
-    /// If GDAL is linked, it uses GDAL's operations and methods to determine
-    /// the Proj.4 string -- otherwise, if libgeotiff is linked, it uses
-    /// that.  Note that GDAL's operations are much more mature and
-    /// support more coordinate systems and descriptions.
-    std::string getProj4() const;
-
-    std::string getHorizontal() const;
-    std::string getHorizontalUnits() const;
-    std::string getVertical() const;
-    std::string getVerticalUnits() const;
-
-    /// Sets the Proj.4 string describing the Spatial Reference System.
-    /// If GDAL is linked, it uses GDAL's operations and methods to determine
-    /// the Proj.4 string -- otherwise, if libgeotiff is linked, it uses
-    /// that.  Note that GDAL's operations are much more mature and
-    /// support more coordinate systems and descriptions.
-    /// \param v - a string containing the Proj.4 string.
-    void setProj4(std::string const& v);
-
-    void dump() const;
-    MetadataNode toMetadata() const;
-
-    bool isGeographic() const;
-    bool isGeocentric() const;
-    int computeUTMZone(const BOX3D& box) const;
-
-    const std::string& getName() const;
-    static int calculateZone(double lon, double lat);
-
-private:
-    std::string m_wkt;
-    friend PDAL_DLL std::ostream& operator<<(std::ostream& ostr,
-        const SpatialReference& srs);
-    friend PDAL_DLL std::istream& operator>>(std::istream& istr,
-        SpatialReference& srs);
-};
-
-PDAL_DLL std::ostream& operator<<(std::ostream& ostr,
-    const SpatialReference& srs);
-PDAL_DLL std::istream& operator>>(std::istream& istr, SpatialReference& srs);
-
-} // namespace pdal
-
diff --git a/include/pdal/Stage.hpp b/include/pdal/Stage.hpp
deleted file mode 100644
index 7bc45da..0000000
--- a/include/pdal/Stage.hpp
+++ /dev/null
@@ -1,425 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <list>
-
-#include <pdal/pdal_internal.hpp>
-
-#include <pdal/Dimension.hpp>
-#include <pdal/DimType.hpp>
-#include <pdal/Log.hpp>
-#include <pdal/Metadata.hpp>
-#include <pdal/Options.hpp>
-#include <pdal/PipelineWriter.hpp>
-#include <pdal/PointTable.hpp>
-#include <pdal/PointRef.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/QuickInfo.hpp>
-#include <pdal/SpatialReference.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-class ProgramArgs;
-class StageRunner;
-class StageWrapper;
-
-/**
-  A stage performs the actual processing in PDAL.  Stages may read data,
-  modify or filter read data, create metadata or write processed data.
-
-  Stages are linked with setInput() into a pipeline.  The pipeline is
-  run with by calling in sequence \ref prepare() and \ref execute() on the
-  stage at the end of the pipeline.  PipelineManager can also be used to
-  create and run a pipeline.
-*/
-class PDAL_DLL Stage
-{
-    FRIEND_TEST(OptionsTest, conditional);
-    friend class StageWrapper;
-    friend class StageRunner;
-public:
-    Stage();
-    virtual ~Stage()
-        {}
-
-    /**
-      Add a stage to the input list of this stage.
-
-      \param input  Stage to use as input.
-    */
-    void setInput(Stage& input)
-        { m_inputs.push_back(&input); }
-
-    /**
-      Set a file descriptor to which progress information should be written.
-
-      \param fd  Progress file descriptor.
-    */
-    void setProgressFd(int fd)
-        { m_progressFd = fd; }
-
-    /**
-      Retrieve some basic point information without reading all data when
-      possible.  Usually implemented only by Readers.
-    */
-    QuickInfo preview();
-
-    /**
-      Prepare a stage for execution.  This function needs to be called on the
-      terminal stage of a pipeline (linked set of stages) before \ref execute
-      can be called.  Prepare recurses through all input stages.
-
-      \param table  PointTable being used for stage pipeline.
-    */
-    void prepare(PointTableRef table);
-
-    /**
-      Execute a prepared pipeline (linked set of stages).
-
-      This performs the action associated with the stage by executing the
-      \ref run function of each stage in depth first order.  Each stage is run
-      to completion (all points are processed) before the next stages is run.o
-
-      \param table  Point table being used for stage pipeline.  This must be
-        the same \ref table used in the \ref prepare function.
-    */
-    PointViewSet execute(PointTableRef table);
-
-    /**
-      Execute a prepared pipeline (linked set of stages) in streaming mode.
-
-      This performs the action associated with the stage by executing the
-      \ref processOne function of each stage in depth first order.  Points
-      are processed up to the capacity of the provided StreamPointTable.
-      Not all stages support streaming mode and an exception will be thrown
-      when attempting to \ref execute an unsupported stage.
-
-      Streaming points can reduce memory consumption, but may limit access
-      to algorithms that need to operate on full point sets.
-
-      \param table  Streming point table used for stage pipeline.  This must be
-        the same \ref table used in the \ref prepare function.
-
-    */
-    void execute(StreamPointTable& table);
-
-    /**
-      Set the spatial reference of a stage.
-
-      Set the spatial reference that will override that being carried by the
-      PointView being processed.  This is usually used when reprojecting data
-      to a new spatial reference.  The stage spatial reference will be carried
-      by PointViews processes by this stage to subsequent stages.
-
-      \param srs  Spatial reference to set.
-    */
-    void setSpatialReference(SpatialReference const& srs);
-
-    /**
-      Get the spatial reference of the stage.
-
-      Get the spatial reference that will override that being carried by the
-      PointView being processed.  This is usually used when reprojecting data
-      to a new spatial reference.  The stage spatial reference will be carried
-      by PointViews processes by this stage to subsequent stages.
-
-      \return  The stage's spatial reference.
-    */
-    const SpatialReference& getSpatialReference() const;
-
-    /**
-      Set a stage's options.
-
-      Set the options on a stage, clearing all previously set options.
-
-      \param options  Options to set.
-    */
-    void setOptions(Options options)
-        { m_options = options; }
-
-    /**
-      Add options if an option with the same name doesn't already exist on
-      the stage.
-
-      \param opts  Options to add.
-    */
-    void addConditionalOptions(const Options& opts);
-
-    /**
-      Add a stage's options to a ProgramArgs set.
-
-      \param args  ProgramArgs to add to.
-    */
-    void addAllArgs(ProgramArgs& args);
-
-    /**
-      Add options to the existing option set.
-
-      \param opts  Options to add.
-    */
-    void addOptions(const Options& opts)
-    {
-        for (const auto& o : opts.getOptions())
-            m_options.add(o);
-    }
-
-    /**
-      Remove options from a stage's option set.
-
-      \param opts  Options to remove.
-    */
-    void removeOptions(const Options& opts)
-    {
-        for (const auto& o : opts.getOptions())
-            m_options.remove(o);
-    }
-
-    /**
-      Set the stage's log.
-
-      \param log  Log pointer.
-    */
-    void setLog(LogPtr& log)
-        { m_log = log; }
-
-    /**
-      Return the stage's log pointer.
-
-      \return  Log pointer.
-    */
-    virtual LogPtr log() const
-        { return m_log; }
-
-    /**
-      Determine whether the stage is in debug mode or not.
-
-      \return  The stage's debug state.
-    */
-    bool isDebug() const
-        { return m_debug; }
-
-    /**
-      Return the name of a stage.
-
-      \return  The stage's name.
-    */
-    virtual std::string getName() const = 0;
-
-    /**
-      Return the tag name of a stage.
-
-      The tag name is used when writing a JSON pipeline.  It is generally
-      the same as the stage name, but a number is appended to maintain
-      uniqueness when stages appear more than once in a pipeline.
-      the same as
-
-      \return  The tag's name.
-    */
-    virtual std::string tagName() const
-        { return getName(); }
-
-    /**
-      Return a list of the stage's inputs.
-
-      \return  A vector pointers to input stages.
-    **/
-    const std::vector<Stage*>& getInputs() const
-        { return m_inputs; }
-
-    /**
-      Get the stage's metadata node.
-
-      \return  Stage's metadata.
-    */
-    MetadataNode getMetadata() const
-        { return m_metadata; }
-
-    /**
-      Serialize a stage by inserting apporpritate data into the provided
-      MetadataNode.  Used to dump a pipeline specification in a portable
-      format.
-
-      \param root  Node to which a stages meatdata should be added.
-      \param tags  Pipeline writer's current list of stage tags.
-    */
-    void serialize(MetadataNode root, PipelineWriter::TagMap& tags) const;
-
-protected:
-    Options m_options;          ///< Stage's options.
-    MetadataNode m_metadata;    ///< Stage's metadata.
-    int m_progressFd;           ///< Descriptor for progress info.
-
-    void setSpatialReference(MetadataNode& m, SpatialReference const&);
-    void addSpatialReferenceArg(ProgramArgs& args);
-
-private:
-    bool m_debug;
-    uint32_t m_verbose;
-    std::string m_logname;
-    std::vector<Stage *> m_inputs;
-    LogPtr m_log;
-    SpatialReference m_spatialReference;
-    std::unique_ptr<ProgramArgs> m_args;
-
-    Stage& operator=(const Stage&); // not implemented
-    Stage(const Stage&); // not implemented
-
-    void setupLog();
-    void handleOptions();
-
-    virtual void readerAddArgs(ProgramArgs& /*args*/)
-        {}
-    void l_addArgs(ProgramArgs& args);
-
-    virtual void writerInitialize(PointTableRef /*table*/)
-        {}
-
-    void l_initialize(PointTableRef table);
-
-    /**
-      Get basic metadata (avoids reading points).  Implement in subclass.
-
-      \return  QuickInfo data.
-    */
-    virtual QuickInfo inspect()
-        { return QuickInfo(); }
-
-    /**
-      Add arguments(options) handled by this stage.  Implement in subclass.
-
-      \param args  ProgramArgs object to which arguments should be added.
-    */
-    virtual void addArgs(ProgramArgs& /*args*/)
-    {}
-
-    /**
-      Process options.  Implement in subclass.
-
-      \param options  Options to process.
-    */
-    virtual void processOptions(const Options& /*options*/)
-        {}
-
-    /**
-      Initialize stage after options have been processed.  Implement in
-      subclass.  If you don't require the \ref table argument, you
-      can implement the version of this function that takes no arguments
-      instead of this function.
-
-      \param table  PointTable associated with pipeline.
-    */
-    virtual void initialize(PointTableRef /*table*/)
-        { initialize(); }
-
-    /**
-      Initialize stage after options have been processed.  Implement in
-      subclass.
-    */
-    virtual void initialize()
-        {}
-
-    /**
-      Add dimensions to a layout.
-
-      \param layout  Point layout.
-    */
-    virtual void addDimensions(PointLayoutPtr /*layout*/)
-        {}
-
-    /**
-      Functions called after dimensions have been added.  Implement in
-      subclass.
-
-      \param table  PointTable associated with pipeline.
-    */
-    virtual void prepared(PointTableRef /*table*/)
-        {}
-
-    /**
-      First part of the execute step.  Called after all stages have been
-      prepared.  Implement in subclass.
-
-      \param table  PointTable associated with the pipeline.
-    */
-    virtual void ready(PointTableRef /*table*/)
-        {}
-
-    /**
-      Process a single point (streaming mode).  Implement in sublcass.
-
-      \param point  Point to process.
-      \return  Readers return false when no more points are to be read.
-        Filters return false if a point is to be filtered-out (not passed
-        to subsequent stages).
-    */
-    virtual bool processOne(PointRef& /*point*/)
-    {
-        std::ostringstream oss;
-        oss << "Point streaming not supported for stage " << getName() << ".";
-        throw pdal_error(oss.str());
-    }
-
-    /**
-      Process all points in a view.  Implement in subclass.
-
-      \param view  PointView to process.
-    */
-    virtual PointViewSet run(PointViewPtr /*view*/)
-    {
-        std::cerr << "Can't run stage = " << getName() << "!\n";
-        return PointViewSet();
-    }
-
-    /**
-      Called after all point views have been processed.  Implement in subclass.
-
-      \param table  PointTable associated with pipeline.
-    */
-    virtual void done(PointTableRef /*table*/)
-        {}
-
-    void execute(StreamPointTable& table, std::list<Stage *>& stages);
-
-    /*
-      Test hook.
-    */
-    const Options& getOptions() const
-        { return m_options; }
-};
-
-} // namespace pdal
diff --git a/include/pdal/Writer.hpp b/include/pdal/Writer.hpp
deleted file mode 100644
index e705c34..0000000
--- a/include/pdal/Writer.hpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Options.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/Stage.hpp>
-
-namespace pdal
-{
-
-class Writer;
-class UserCallback;
-
-/**
-  A Writer is a terminal stage for a PDAL pipeline.  It usually writes output
-  to a file, but this isn't a requirement.  The class provides support for
-  some operations common for producing point output.
-*/
-class PDAL_DLL Writer : public Stage
-{
-    friend class WriterWrapper;
-    friend class DbWriter;
-    friend class FlexWriter;
-
-public:
-    /**
-      Construct a writer.
-    */
-    Writer() : m_hashPos(std::string::npos)
-        {}
-
-protected:
-    std::string m_filename;  ///< Output filename
-    std::string::size_type m_hashPos;
-
-    /**
-      Locate template placeholder ('#') and validate filename with respect
-      to placeholder.
-    */
-    void handleFilenameTemplate();
-
-private:
-    virtual PointViewSet run(PointViewPtr view)
-    {
-        PointViewSet viewSet;
-        write(view);
-        viewSet.insert(view);
-        return viewSet;
-    }
-    virtual void writerInitialize(PointTableRef table)
-    {}
-
-    /**
-      Write the point in a PointView.  This is a simplification of the
-      \ref run() interface for convenience.  Impelment in subclass if desired.
-    */
-    virtual void write(const PointViewPtr /*view*/)
-        { std::cerr << "Can't write with stage = " << getName() << "!\n"; }
-
-    Writer& operator=(const Writer&); // not implemented
-    Writer(const Writer&); // not implemented
-};
-
-} // namespace pdal
-
diff --git a/include/pdal/pdal_macros.hpp b/include/pdal/pdal_macros.hpp
deleted file mode 100644
index 9830d5f..0000000
--- a/include/pdal/pdal_macros.hpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2012, Howard Butler (hobu.inc at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_export.hpp>
-#include <pdal/plugin.hpp>
-#include <pdal/PluginManager.hpp>
-
-namespace pdal
-{
-
-struct PluginInfo
-{
-    std::string name;
-    std::string description;
-    std::string link;
-    PluginInfo(const std::string& n, const std::string& d, const std::string& l)
-      : name(n), description(d), link(l)
-    {}
-};
-
-}
-
-#define CREATE_SHARED_PLUGIN(version_major, version_minor, T, type, info) \
-    extern "C" PDAL_DLL int32_t ExitFunc() \
-    { return 0; } \
-    extern "C" PDAL_DLL PF_ExitFunc PF_initPlugin() \
-    { \
-        int res = 0; \
-        PF_RegisterParams rp; \
-        rp.version.major = version_major; \
-        rp.version.minor = version_minor; \
-        rp.createFunc = pdal::T::create; \
-        rp.destroyFunc = pdal::T::destroy; \
-        rp.description = info.description; \
-        rp.link = info.link; \
-        rp.pluginType = PF_PluginType_ ## type; \
-        if (!pdal::PluginManager::registerObject(info.name, &rp)) \
-            return NULL; \
-        return ExitFunc; \
-    } \
-    void * pdal::T::create() { return new pdal::T(); } \
-    int32_t pdal::T::destroy(void *p) \
-    { \
-        if (!p) \
-            return -1; \
-        delete (pdal::T *)p; \
-        return 0; \
-    }
-
-#define CREATE_STATIC_PLUGIN(version_major, version_minor, T, type, info) \
-    extern "C" PDAL_DLL int32_t T ## _ExitFunc() \
-    { return 0; } \
-    extern "C" PDAL_DLL PF_ExitFunc T ## _InitPlugin() \
-    { \
-        int res = 0; \
-        PF_RegisterParams rp; \
-        rp.version.major = version_major; \
-        rp.version.minor = version_minor; \
-        rp.createFunc = pdal::T::create; \
-        rp.destroyFunc = pdal::T::destroy; \
-        rp.description = info.description; \
-        rp.link = info.link; \
-        rp.pluginType = PF_PluginType_ ## type; \
-        if (!pdal::PluginManager::registerObject(info.name, &rp)) \
-            return NULL; \
-        return T ## _ExitFunc; \
-    } \
-    void * pdal::T::create() { return new pdal::T(); } \
-    int32_t pdal::T::destroy(void *p) \
-    { \
-        if (!p) \
-            return -1; \
-        delete (pdal::T *)p; \
-        return 0; \
-    }
-
-#ifdef _WIN32
-inline long lround(double d)
-{
-    long l;
-
-    if (d < 0)
-        l = (long)ceil(d - .5);
-    else
-        l = (long)floor(d + .5);
-    return l;
-}
-#endif
-
diff --git a/include/pdal/pdal_types.hpp b/include/pdal/pdal_types.hpp
deleted file mode 100644
index 3e32fa5..0000000
--- a/include/pdal/pdal_types.hpp
+++ /dev/null
@@ -1,178 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <stdint.h>
-#include <cstdlib>
-#include <istream>
-#include <stdexcept>
-#include <string>
-#include <vector>
-
-#include <iostream>
-
-namespace pdal
-{
-
-typedef uint64_t PointId;
-typedef uint64_t point_count_t;
-typedef std::vector<std::string> StringList;
-
-typedef union
-{
-    float f;
-    double d;
-    int8_t s8;
-    int16_t s16;
-    int32_t s32;
-    int64_t s64;
-    uint8_t u8;
-    uint16_t u16;
-    uint32_t u32;
-    uint64_t u64;
-} Everything;
-
-struct XForm
-{
-    struct XFormComponent
-    {
-        XFormComponent() : m_val(0.0), m_auto(false)
-        {}
-
-        XFormComponent(double val) : m_val(val), m_auto(false)
-        {}
-
-        double m_val;
-        bool m_auto;
-
-        bool set(const std::string& sval)
-        {
-            if (sval == "auto")
-                m_auto = true;
-            else
-            {
-                size_t pos;
-                try
-                {
-                    m_val = std::stod(sval, &pos);
-                }
-                catch (...)
-                {
-                    // Set error condition.
-                    pos = sval.size() + 1;
-                }
-                if (pos != sval.size())
-                {
-                    m_val = 0;
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        friend std::istream& operator>>(std::istream& in, XFormComponent& xfc);
-        friend std::ostream& operator<<(std::ostream& in,
-            const XFormComponent& xfc);
-    };
-
-    XForm() : m_scale(1.0), m_offset(0.0)
-    {}
-
-    XForm(double scale, double offset) : m_scale(scale), m_offset(offset)
-    {}
-
-    // Scale component of the transform.
-    XFormComponent m_scale;
-    // Offset component of the transform.
-    XFormComponent m_offset;
-
-    double toScaled(double val) const
-        { return (val - m_offset.m_val) / m_scale.m_val; }
-
-    bool nonstandard() const
-    {
-        return m_scale.m_auto || m_offset.m_auto ||
-            m_scale.m_val != 1.0 || m_offset.m_val != 0.0;
-    }
-};
-
-inline std::istream& operator>>(std::istream& in, XForm::XFormComponent& xfc)
-{
-    std::string sval;
-
-    in >> sval;
-    if (!xfc.set(sval))
-        in.setstate(std::ios_base::failbit);
-    return in;
-}
-
-inline std::ostream& operator<<(std::ostream& out,
-    const XForm::XFormComponent& xfc)
-{
-    if (xfc.m_auto)
-        out << "auto";
-    else
-        out << xfc.m_val;
-    return out;
-}
-
-enum class LogLevel
-{
-    Error = 0,
-    Warning,
-    Info,
-    Debug,
-    Debug1,
-    Debug2,
-    Debug3,
-    Debug4,
-    Debug5
-};
-
-enum class Orientation
-{
-    PointMajor,
-    DimensionMajor
-};
-
-class pdal_error : public std::runtime_error
-{
-public:
-    inline pdal_error(std::string const& msg) : std::runtime_error(msg)
-        {}
-};
-
-} // namespace pdal
-
diff --git a/include/pdal/plang/Redirector.hpp b/include/pdal/plang/Redirector.hpp
deleted file mode 100644
index 60ef65a..0000000
--- a/include/pdal/plang/Redirector.hpp
+++ /dev/null
@@ -1,51 +0,0 @@
-//
-// Copyright (C) 2011 Mateusz Loskot <mateusz at loskot.net>
-// Distributed under the Boost Software License, Version 1.0.
-// (See accompanying file LICENSE_1_0.txt or copy at
-// http://www.boost.org/LICENSE_1_0.txt)
-//
-// Blog article: http://mateusz.loskot.net/?p=2819
-
-// http://python3porting.com/cextensions.html
-
-#pragma once
-
-#include <functional>
-
-#include <Python.h>
-
-#undef toupper
-#undef tolower
-#undef isspace
-
-namespace pdal
-{
-namespace plang
-{
-
-PyMODINIT_FUNC redirector_init(void);
-
-class Redirector
-{
-public:
-    Redirector();
-    ~Redirector();
-
-    static PyObject* init();
-    void set_stdout(std::ostream* ostr);
-    void reset_stdout();
-
-    typedef std::function<void(std::string)> stdout_write_type;
-    typedef std::function<void()> stdout_flush_type;
-
-private:
-    void set_stdout(stdout_write_type write, stdout_flush_type flush);
-
-    // Internal state
-    PyObject* m_stdout;
-    PyObject* m_stdout_saved;
-};
-
-} // namespace plang
-} // namespace pdal
-
diff --git a/include/pdal/util/Bounds.hpp b/include/pdal/util/Bounds.hpp
deleted file mode 100644
index fc8c2d8..0000000
--- a/include/pdal/util/Bounds.hpp
+++ /dev/null
@@ -1,639 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2010, Howard Butler
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of the Martin Isenburg or Iowa Department
- *       of Natural Resources nor the names of its contributors may be
- *       used to endorse or promote products derived from this software
- *       without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#pragma once
-
-#include <cstdint>
-#include <sstream>
-
-#include "pdal_util_export.hpp"
-
-namespace pdal
-{
-
-/**
-  BOX2D represents a two-dimensional box with double-precision bounds.
-*/
-class PDAL_DLL BOX2D
-{
-public:
-    double minx;  ///< Minimum X value.
-    double maxx;  ///< Maximum X value.
-    double miny;  ///< Minimum Y value.
-    double maxy;  ///< Maximum Y value.
-
-    /**
-      Construct an "empty" bounds box.
-    */
-    BOX2D()
-        { clear(); }
-
-    /**
-      Construct and initialize a bounds box.
-
-      \param minx  Minimum X value.
-      \param miny  Minimum Y value.
-      \param maxx  Maximum X value.
-      \param maxy  Maximum Y value.
-    */
-    BOX2D(double minx, double miny, double maxx, double maxy) :
-        minx(minx), maxx(maxx), miny(miny), maxy(maxy)
-    {}
-
-    /**
-      Determine whether a bounds box has any bounds set (is in a state
-      as if default-constructed).
-
-      \return  Whether the bounds box is empty.
-    */
-    bool empty() const;
-
-    /**
-      Clear the bounds box to an empty state.
-    */
-    void clear();
-
-    /**
-      Expand the bounds of the box if a value is less than the current
-      minimum or greater than the current maximum.  If the bounds box is
-      currently empty, both minimum and maximum box bounds will be set to
-      the provided value.
-
-      \param x  X dimension value.
-      \param y  Y dimension value.
-    */
-    void grow(double x, double y);
-
-    /**
-      Determine if a bounds box contains a point.
-
-      \param x  X dimension value.
-      \param y  Y dimension value.
-      \return  Whether both dimensions are equal to or less than the maximum
-        box values and equal to or more than the minimum box values.
-    */
-    bool contains(double x, double y) const
-        { return minx <= x && x <= maxx && miny <= y && y <= maxy; }
-
-    /**
-      Determine if the bounds of this box are the same as that of another
-      box.  Empty bounds boxes are always equal.
-
-      \param other  Bounds box to check for equality.
-      \return \c true if the provided box has equal limits to this box,
-        \c false otherwise.
-    */
-    bool equal(const BOX2D& other) const
-    {
-        return  minx == other.minx && maxx == other.maxx &&
-            miny == other.miny && maxy == other.maxy;
-    }
-
-    /**
-      Determine if the bounds of this box are the same as that of another
-      box.  Empty bounds boxes are always equal.
-
-      \param other  Bounds box to check for equality.
-      \return \c true if the provided box has equal limits to this box,
-        \c false otherwise.
-    */
-    bool operator==(BOX2D const& other) const
-    {
-        return equal(other);
-    }
-
-    /**
-      Determine if the bounds of this box are different from that of another
-      box.  Empty bounds boxes are never unequal.
-
-      \param other  Bounds box to check for inequality.
-      \return \c true if the provided box has limits different from this box,
-        \c false otherwise.
-    */
-    bool operator!=(BOX2D const& other) const
-    {
-        return (!equal(other));
-    }
-
-    /**
-      Expand this box to contain another box.
-
-      \param other  Box that this box should contain.
-    */
-    void grow(const BOX2D& other)
-    {
-        if (other.minx < minx) minx = other.minx;
-        if (other.maxx > maxx) maxx = other.maxx;
-
-        if (other.miny < miny) miny = other.miny;
-        if (other.maxy > maxy) maxy = other.maxy;
-    }
-
-    /**
-      Clip this bounds box by another so it will be contained by the
-      other box.
-
-      \param other  Clipping box for this box.
-    */
-    void clip(const BOX2D& other)
-    {
-        if (other.minx > minx) minx = other.minx;
-        if (other.maxx < maxx) maxx = other.maxx;
-
-        if (other.miny > miny) miny = other.miny;
-        if (other.maxy < maxy) maxy = other.maxy;
-    }
-
-    /**
-      Determine if another bounds box is contained in this bounds box.
-      Equal limits are considered to be contained.
-
-      \param other  Bounds box to check for containment.
-      \return  \c true if the provided box is contained in this box,
-        \c false otherwise.
-    **/
-    bool contains(const BOX2D& other) const
-    {
-        return minx <= other.minx && maxx >= other.maxx &&
-            miny <= other.miny && maxy >= other.maxy;
-    }
-
-    /**
-      Determine if another box overlaps this box.
-
-      \param other  Box to test for overlap.
-      \return  Whether the provided box overlaps this box.
-    */
-    bool overlaps(const BOX2D& other)
-    {
-        return minx <= other.maxx && maxx >= other.minx &&
-            miny <= other.maxy && maxy >= other.miny;
-    }
-
-    /**
-      Convert this box to a string suitable for use in SQLite.
-
-      \param precision  Precision for output [default: 8]
-      \return  String format of this box.
-    */
-    std::string toBox(uint32_t precision = 8) const
-    {
-        std::stringstream oss;
-
-        oss.precision(precision);
-        oss.setf(std::ios_base::fixed, std::ios_base::floatfield);
-
-        oss << "box2d(";
-            oss << minx << " " << miny << ", ";
-            oss << maxx << " " << maxy << ")";
-        return oss.str();
-    }
-
-    /**
-      Convert this box to a well-known text string.
-
-      \param precision  Precision for output [default: 8]
-      \return  String format of this box.
-    */
-    std::string toWKT(uint32_t precision = 8) const
-    {
-        if (empty())
-            return std::string();
-
-        std::stringstream oss;
-
-        oss.precision(precision);
-        oss.setf(std::ios_base::fixed, std::ios_base::floatfield);
-
-        oss << "POLYGON ((";
-        oss << minx << " " << miny << ", ";
-        oss << minx << " " << maxy << ", ";
-        oss << maxx << " " << maxy << ", ";
-        oss << maxx << " " << miny << ", ";
-        oss << minx << " " << miny;
-        oss << "))";
-
-        return oss.str();
-    }
-
-    /**
-      Convert this box to a GeoJSON text string.
-
-      \param precision  Precision for output [default: 8]
-      \return  String format of this box.
-    */
-    std::string toGeoJSON(uint32_t precision = 8) const
-    {
-        if (empty())
-            return std::string();
-
-        std::stringstream oss;
-
-        oss.precision(precision);
-        oss.setf(std::ios_base::fixed, std::ios_base::floatfield);
-        oss << "{\"bbox\":[" << minx << ", " << miny << ", " <<
-            maxx <<  "," << maxy << "]}";
-        return oss.str();
-    }
-
-    /**
-      Return a staticly-allocated Bounds extent that represents infinity
-
-      \return  A bounds box with infinite bounds,
-    */
-    static const BOX2D& getDefaultSpatialExtent();
-};
-
-/**
-  BOX3D represents a three-dimensional box with double-precision bounds.
-*/
-class PDAL_DLL BOX3D : private BOX2D
-{
-public:
-    using BOX2D::minx;
-    using BOX2D::maxx;
-    using BOX2D::miny;
-    using BOX2D::maxy;
-    double minz;   ///< Minimum Z value.
-    double maxz;   ///< Maximum Z value.
-
-    /**
-      Clear the bounds box to an empty state.
-    */
-    BOX3D()
-       { clear(); }
-
-    BOX3D(const BOX3D& box) :
-        BOX2D(box), minz(box.minz), maxz(box.maxz)
-    {}
-
-    explicit BOX3D(const BOX2D& box) :
-        BOX2D(box), minz(0), maxz(0)
-    {}
-
-    /**
-      Construct and initialize a bounds box.
-
-      \param minx  Minimum X value.
-      \param miny  Minimum Y value.
-      \param minx  Minimum Z value.
-      \param maxx  Maximum X value.
-      \param maxy  Maximum Y value.
-      \param maxz  Maximum Z value.
-    */
-    BOX3D(double minx, double miny, double minz, double maxx, double maxy,
-        double maxz) : BOX2D(minx, miny, maxx, maxy), minz(minz), maxz(maxz)
-    {}
-
-    /**
-      Determine whether a bounds box has any bounds set (is in a state
-      as if default-constructed).
-
-      \return  Whether the bounds box is empty.
-    */
-    bool empty() const;
-
-    /**
-      Expand the bounds of the box if a value is less than the current
-      minimum or greater than the current maximum.  If the bounds box is
-      currently empty, both minimum and maximum box bounds will be set to
-      the provided value.
-
-      \param x  X dimension value.
-      \param y  Y dimension value.
-      \param z  Z dimension value.
-    */
-    void grow(double x, double y, double z);
-
-    /**
-      Clear the bounds box to an empty state.
-    */
-    void clear();
-
-
-    /**
-      Determine if a bounds box contains a point.
-
-      \param x  X dimension value.
-      \param y  Y dimension value.
-      \param z  Z dimension value.
-      \return  Whether both dimensions are equal to or less than the maximum
-        box values and equal to or more than the minimum box values.
-    */
-    bool contains(double x, double y, double z) const
-    {
-        return BOX2D::contains(x, y) && minz <= z && z <= maxz;
-    }
-
-    /**
-      Determine if another bounds box is contained in this bounds box.
-      Equal limits are considered to be contained.
-
-      \param other  Bounds box to check for containment.
-      \return  \c true if the provided box is contained in this box,
-        \c false otherwise.
-    **/
-    bool contains(const BOX3D& other) const
-    {
-        return BOX2D::contains(other) &&
-            minz <= other.minz && other.maxz <= maxz;
-    }
-
-    /**
-      Determine if the bounds of this box are the same as that of another
-      box.  Empty bounds boxes are always equal.
-
-      \param other  Bounds box to check for equality.
-      \return \c true if the provided box has equal limits to this box,
-        \c false otherwise.
-    */
-    bool equal(const BOX3D& other) const
-    {
-        return  BOX2D::contains(other) &&
-            minz == other.minz && maxz == other.maxz;
-    }
-
-    /**
-      Determine if the bounds of this box are the same as that of another
-      box.  Empty bounds boxes are always equal.
-
-      \param other  Bounds box to check for equality.
-      \return \c true if the provided box has equal limits to this box,
-        \c false otherwise.
-    */
-    bool operator==(BOX3D const& rhs) const
-    {
-        return equal(rhs);
-    }
-
-    /**
-      Determine if the bounds of this box are different from that of another
-      box.  Empty bounds boxes are never unequal.
-
-      \param other  Bounds box to check for inequality.
-      \return \c true if the provided box has limits different from this box,
-        \c false otherwise.
-    */
-    bool operator!=(BOX3D const& rhs) const
-    {
-        return (!equal(rhs));
-    }
-
-    /**
-      Expand this box to contain another box.
-
-      \param other  Box that this box should contain.
-    */
-    void grow(const BOX3D& other)
-    {
-        BOX2D::grow(other);
-        if (other.minz < minz) minz = other.minz;
-        if (other.maxz > maxz) maxz = other.maxz;
-    }
-
-    /**
-      Clip this bounds box by another so it will be contained by the
-      other box.
-
-      \param other  Clipping box for this box.
-    */
-    void clip(const BOX3D& other)
-    {
-        BOX2D::clip(other);
-        if (other.minz < minz) minz = other.minz;
-        if (other.maxz > maxz) maxz = other.maxz;
-    }
-
-    /**
-      Determine if another box overlaps this box.
-
-      \param other  Box to test for overlap.
-      \return  Whether the provided box overlaps this box.
-    */
-    bool overlaps(const BOX3D& other)
-    {
-        return BOX2D::overlaps(other) &&
-           minz <= other.maxz && maxz >= other.minz;
-    }
-
-    /**
-      Convert this box to 2-dimensional bounding box.
-
-      \return  Bounding box with Z dimension stripped.
-    */
-    BOX2D to2d() const
-    {
-        return *this;
-    }
-
-    /**
-      Convert this box to a string suitable for use in SQLite.
-
-      \param precision  Precision for output [default: 8]
-      \return  String format of this box.
-    */
-    std::string toBox(uint32_t precision = 8) const
-    {
-        std::stringstream oss;
-
-        oss.precision(precision);
-        oss.setf(std::ios_base::fixed, std::ios_base::floatfield);
-
-        oss << "box3d(" << minx << " " << miny << " " << minz << ", " <<
-            maxx << " " << maxy << " " << maxz << ")";
-        return oss.str();
-    }
-
-    /**
-      Convert this box to a well-known text string.
-      
-      \param precision  Precision for output [default: 8]
-      \return  String format of this box.
-    */
-    std::string toWKT(uint32_t precision = 8) const
-    {
-        if (empty())
-            return std::string();
-
-        std::stringstream oss;
-
-        oss.precision(precision);
-        oss.setf(std::ios_base::fixed, std::ios_base::floatfield);
-
-        oss << "POLYHEDRON Z ( ";
-
-        oss << "((" << minx << " " << miny << " " << minz << ", " <<
-                       maxx << " " << miny << " " << minz << ", " <<
-                       maxx << " " << maxy << " " << minz << ", " <<
-                       minx << " " << maxy << " " << minz << ", " <<
-                       minx << " " << miny << " " << minz << ", " <<
-               ")), ";
-        oss << "((" << minx << " " << miny << " " << minz << ", " <<
-                       maxx << " " << miny << " " << minz << ", " <<
-                       maxx << " " << miny << " " << maxz << ", " <<
-                       minx << " " << miny << " " << maxz << ", " <<
-                       minx << " " << miny << " " << minz << ", " <<
-               ")), ";
-        oss << "((" << maxx << " " << miny << " " << minz << ", " <<
-                       maxx << " " << maxy << " " << minz << ", " <<
-                       maxx << " " << maxy << " " << maxz << ", " <<
-                       maxx << " " << miny << " " << maxz << ", " <<
-                       maxx << " " << miny << " " << minz << ", " <<
-               ")), ";
-        oss << "((" << maxx << " " << maxy << " " << minz << ", " <<
-                       minx << " " << maxy << " " << minz << ", " <<
-                       minx << " " << maxy << " " << maxz << ", " <<
-                       maxx << " " << maxy << " " << maxz << ", " <<
-                       maxx << " " << maxy << " " << minz << ", " <<
-               ")), ";
-        oss << "((" << minx << " " << maxy << " " << minz << ", " <<
-                       minx << " " << miny << " " << minz << ", " <<
-                       minx << " " << miny << " " << maxz << ", " <<
-                       minx << " " << maxy << " " << maxz << ", " <<
-                       minx << " " << maxy << " " << minz << ", " <<
-               ")), ";
-        oss << "((" << minx << " " << miny << " " << maxz << ", " <<
-                       maxx << " " << miny << " " << maxz << ", " <<
-                       maxx << " " << maxy << " " << maxz << ", " <<
-                       minx << " " << maxy << " " << maxz << ", " <<
-                       minx << " " << miny << " " << maxz << ", " <<
-               "))";
-
-        oss << " )";
-
-        return oss.str();
-    }
-
-    /**
-      Return a staticly-allocated Bounds extent that represents infinity
-
-      \return  A bounds box with infinite bounds,
-    */
-    static const BOX3D& getDefaultSpatialExtent();
-};
-
-/**
-  Wrapper for BOX3D and BOX2D to allow extraction as either.  Typically used
-  to facilitate streaming either a BOX2D or BOX3D 
-*/
-class PDAL_DLL Bounds
-{
-public:
-    Bounds()
-    {}
-
-    Bounds(const BOX3D& box);
-    Bounds(const BOX2D& box);
-
-    BOX3D to3d() const;
-    BOX2D to2d() const;
-    bool is3d() const;
-
-    friend PDAL_DLL std::istream& operator >> (std::istream& in, Bounds& bounds);
-
-private:
-    BOX3D m_box;
-
-    void set(const BOX3D& box);
-    void set(const BOX2D& box);
-};
-
-/**
-  Write a 2D bounds box to a stream in a format used by PDAL options.
-
-  \param ostr  Stream to write to.
-  \param bounds  Box to write.
-*/
-inline std::ostream& operator << (std::ostream& ostr, const BOX2D& bounds)
-{
-    if (bounds.empty())
-    {
-        ostr << "()";
-        return ostr;
-    }
-
-    auto savedPrec = ostr.precision();
-    ostr.precision(16); // or..?
-    ostr << "(";
-    ostr << "[" << bounds.minx << ", " << bounds.maxx << "], " <<
-            "[" << bounds.miny << ", " << bounds.maxy << "]";
-    ostr << ")";
-    ostr.precision(savedPrec);
-    return ostr;
-}
-
-/**
-  Write a 3D bounds box to a stream in a format used by PDAL options.
-
-  \param ostr  Stream to write to.
-  \param bounds  Box to write.
-*/
-inline std::ostream& operator << (std::ostream& ostr, const BOX3D& bounds)
-{
-    if (bounds.empty())
-    {
-        ostr << "()";
-        return ostr;
-    }
-
-    auto savedPrec = ostr.precision();
-    ostr.precision(16); // or..?
-    ostr << "(";
-    ostr << "[" << bounds.minx << ", " << bounds.maxx << "], " <<
-            "[" << bounds.miny << ", " << bounds.maxy << "], " <<
-            "[" << bounds.minz << ", " << bounds.maxz << "]";
-    ostr << ")";
-    ostr.precision(savedPrec);
-    return ostr;
-}
-
-/**
-  Read a 2D bounds box from a stream in a format provided by PDAL options.
-
-  \param istr  Stream to read from.
-  \param bounds  Bounds box to populate.
-*/
-extern PDAL_DLL std::istream& operator>>(std::istream& istr, BOX2D& bounds);
-
-/**
-  Read a 3D bounds box from a stream in a format provided by PDAL options.
-
-  \param istr  Stream to read from.
-  \param bounds  Bounds box to populate.
-*/
-extern PDAL_DLL std::istream& operator>>(std::istream& istr, BOX3D& bounds);
-
-PDAL_DLL std::istream& operator >> (std::istream& in, Bounds& bounds);
-
-} // namespace pdal
diff --git a/include/pdal/util/FileUtils.hpp b/include/pdal/util/FileUtils.hpp
deleted file mode 100644
index acb5ba3..0000000
--- a/include/pdal/util/FileUtils.hpp
+++ /dev/null
@@ -1,248 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_internal.hpp>
-
-#include <cassert>
-#include <cmath>
-#include <cstdint>
-#include <istream>
-#include <ostream>
-#include <stdexcept>
-#include <string>
-
-#include "pdal_util_export.hpp"
-
-namespace pdal
-{
-
-namespace FileUtils
-{
-    /**
-      Open an existing file for reading.
-
-      \param filename  Filename.
-      \param asBinary  Read as binary file (don't convert /r/n to /n)
-      \return  Pointer to opened stream.
-    */
-    PDAL_DLL std::istream* openFile(std::string const& filename,
-        bool asBinary=true);
-
-    /**
-      Create a file and open for writing.
-
-      \param filename  Filename.
-      \param asBinary  Write as binary file (don't convert /n to /r/n)
-      \return  Point to opened stream.
-    */
-    PDAL_DLL std::ostream* createFile(std::string const& filename,
-        bool asBinary=true);
-
-    /**
-      Determine if a directory exists.
-
-      \param dirname  Name of directory.
-      \return  Whether a directory exists.
-    */
-    PDAL_DLL bool directoryExists(const std::string& dirname);
-
-    /**
-      Create a directory.
-
-      \param dirname  Directory name.
-      \return  Whether the directory was created.
-    */
-    PDAL_DLL bool createDirectory(const std::string& dirname);
-
-    /**
-      Delete a directory and its contents.
-
-      \param dirname  Directory name.
-    */
-    PDAL_DLL void deleteDirectory(const std::string& dirname);
-
-    /**
-      List the contents of a directory.
-
-      \param dirname  Name of directory to list.
-      \return  List of entries in the directory.
-    */
-    PDAL_DLL StringList directoryList(const std::string& dirname);
-
-    /**
-      Close a file created with createFile.
-
-      \param ofs  Pointer to stream to close.
-    */
-    PDAL_DLL void closeFile(std::ostream* ofs);
-
-    /**
-      Close a file created with openFile.
-
-      \param ifs  Pointer to stream to close.
-    */
-    PDAL_DLL void closeFile(std::istream* ifs);
-
-    /**
-      Delete a file.
-
-      \param filename  Name of file to delete.
-      \return  \c true if successful, \c false otherwise
-    */
-    PDAL_DLL bool deleteFile(const std::string& filename);
-
-    /**
-      Rename a file.
-
-      \param dest  Desired filename.
-      \param src   Source filename.
-    */
-    PDAL_DLL void renameFile(const std::string& dest, const std::string& src);
-
-    /**
-      Determine if a file exists.
-
-      \param  Filename.
-      \return  Whether the file exists.
-    */
-    PDAL_DLL bool fileExists(const std::string& filename);
-
-    /**
-      Get the size of a file.
-
-      \param filename  Filename.
-      \return  Size of file.
-    */
-    PDAL_DLL uintmax_t fileSize(const std::string& filename);
-
-    /**
-      Read a file into a string.
-
-      \param filename  Filename.
-      \return  File contents as a string
-    */
-    PDAL_DLL std::string readFileIntoString(const std::string& filename);
-
-    /**
-      Get the current working directory with trailing separator.
-
-      \return  The current working directory.
-    */
-    PDAL_DLL std::string getcwd();
-
-    /**
-      Return the file component of the given path,
-      e.g. "d:/foo/bar/a.c" -> "a.c"
-
-      \param path  Path from which to extract file component.
-      \return  File part of path.
-    */
-    PDAL_DLL std::string getFilename(const std::string& path);
-
-    /**
-      Return the directory component of the given path,
-      e.g. "d:/foo/bar/a.c" -> "d:/foo/bar/"
-
-      \param path  Path from which to extract directory component.
-      \return  Directory part of path.
-    */
-    PDAL_DLL std::string getDirectory(const std::string& path);
-
-    /**
-      Determine if the path is an absolute path.
-
-      \param path  Path to test.
-      \return  Whether the path is absolute.
-    */
-    PDAL_DLL bool isAbsolutePath(const std::string& path);
-
-    /**
-      Determine if path is a directory.
-
-      \param path  Directory to check.
-      \return  Whether the path represents a directory.
-    */
-    PDAL_DLL bool isDirectory(const std::string& path);
-
-    /**
-      If the filename is an absolute path, just return it otherwise,
-      make it absolute (relative to current working dir) and return it.
-
-      \param filename  Name of file to convert to absolute path.
-      \return  Absolute version of provided filename.
-    */
-    PDAL_DLL std::string toAbsolutePath(const std::string& filename);
-
-    /**
-      If the filename is an absolute path, just return it otherwise,
-      make it absolute (relative to base dir) and return that.
-
-      \param filename  Name of file to convert to absolute path.
-      \param base  Base name to use.
-      \return  Absolute version of provided filename relative to base.
-    */
-    PDAL_DLL std::string toAbsolutePath(const std::string& filename,
-        const std::string base);
-    
-    /**
-      Get the file creation and modification times.
-
-      \param filename  Filename.
-      \param createTime  Pointer to creation time structure.
-      \param modTime  Pointer to modification time structure.
-    */
-    PDAL_DLL void fileTimes(const std::string& filename, struct tm *createTime,
-        struct tm *modTime);
-
-    /**
-      Return the extension of the filename, including the separator (.).
-
-      \param path  File path from which to extract extension.
-      \return  Extension of filename.
-    */
-    PDAL_DLL std::string extension(const std::string& path);
-
-    /**
-      Return the filename stripped of the extension.  . and .. are returned
-      unchanged.
-
-      \param path  File path from which to extract file stem.
-      \return  Stem of filename.
-    */
-    PDAL_DLL std::string stem(const std::string& path);
-}
-
-} // namespace pdal
diff --git a/include/pdal/util/IStream.hpp b/include/pdal/util/IStream.hpp
deleted file mode 100644
index 731510b..0000000
--- a/include/pdal/util/IStream.hpp
+++ /dev/null
@@ -1,545 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Andrew Bell
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <sys/types.h>
-#include <stdint.h>
-
-#include <fstream>
-#include <memory>
-#include <stack>
-#include <vector>
-#include <cstring>
-
-#include "portable_endian.hpp"
-#include "pdal_util_export.hpp"
-
-namespace pdal
-{
-
-class IStreamMarker;
-
-/**
-  Stream wrapper for input of binary data.
-*/
-class IStream
-{
-public:
-    /**
-      Default constructor.
-    */
-    PDAL_DLL IStream() : m_stream(NULL), m_fstream(NULL)
-        {}
-
-    /**
-      Construct an IStream from a filename.
-
-      \param filename  File from which to read.
-    */
-    PDAL_DLL IStream(const std::string& filename) :
-        m_stream(NULL), m_fstream(NULL)
-    { open(filename); }
-
-    /**
-      Construct an IStream from an input stream pointer.
-
-      \param stream  Stream from which to read.
-    */
-    PDAL_DLL IStream(std::istream *stream) : m_stream(stream), m_fstream(NULL)
-        {}
-
-    PDAL_DLL ~IStream()
-        { delete m_fstream; }
-
-    /**
-      Open a file to extract.
-
-      \param filename  Filename.
-      \return  -1 if a stream is already assigned, 0 otherwise.
-    */
-    PDAL_DLL int open(const std::string& filename)
-    {
-        if (m_stream)
-             return -1;
-        m_stream = m_fstream = new std::ifstream(filename,
-            std::ios_base::in | std::ios_base::binary);
-        return 0;
-    }
-
-    /**
-      Close the underlying stream.
-    */
-    PDAL_DLL void close()
-    {
-        delete m_fstream;
-        m_fstream = NULL;
-        m_stream = NULL;
-    }
-
-    /**
-      Return the state of the stream.
-
-      \return  The state of the underlying stream.
-    */
-    PDAL_DLL operator bool ()
-        { return (bool)(*m_stream); }
-
-    /**
-      Seek to a position in the underlying stream.
-
-      \param pos  Position to seek to,
-    */
-    PDAL_DLL void seek(std::streampos pos)
-        { m_stream->seekg(pos, std::istream::beg); }
-
-
-    /**
-      Seek to an offset from a specified position.
-
-      \param off  Offset.
-      \param way  Absolute position for offset (beg, end or cur)
-    */
-    PDAL_DLL void seek(std::streampos off, std::ios_base::seekdir way)
-        { m_stream->seekg(off, way); }
-
-    /**
-      Skip relative to the current position.
-
-      \param offset  Offset from the current position.
-    */
-    PDAL_DLL void skip(std::streamoff offset)
-        { m_stream->seekg(offset, std::istream::cur); }
-
-    /**
-      Determine the position of the get pointer.
-
-      \return  Current get position.
-    */
-    PDAL_DLL std::streampos position() const
-        { return m_stream->tellg(); }
-
-    /**
-      Determine if the underlying stream is good.
-
-      \return  Whether the underlying stream is good.
-    */
-    PDAL_DLL bool good() const
-        { return m_stream->good(); }
-
-    /**
-      Fetch a pointer to the underlying stream.
-
-      \return  Pointer to the underlying stream.
-    */
-    PDAL_DLL std::istream *stream()
-        { return m_stream; }
-
-    /**
-      Temporarily push a stream to read from.
-
-      \param strm  New stream to read from.
-    */
-    PDAL_DLL void pushStream(std::istream *strm)
-    {
-        m_streams.push(m_stream);
-        m_stream = strm;
-    }
-
-    /**
-      Pop the current stream and return it.  The last stream on the stack
-      cannot be popped.
-
-      \return  Pointer to the popped stream.
-    */
-    PDAL_DLL std::istream *popStream()
-    {
-        // Can't pop the last stream for now.
-        if (m_streams.empty())
-            return nullptr;
-        std::istream *strm = m_stream;
-        m_stream = m_streams.top();
-        m_streams.pop();
-        return strm;
-    }
-
-    /**
-      Fetch data from the stream into a string.
-
-      \param s  String to fill.
-      \param size  Number of bytes to extract.
-    */
-    PDAL_DLL void get(std::string& s, size_t size)
-    {
-        // Could do this by appending to a string with a stream, but this
-        // is probably fast enough for now (there's only a simple increment
-        // to advance an istream iterator, which you'd have to call in a loop).
-        std::unique_ptr<char[]> buf(new char[size+1]);
-        m_stream->read(buf.get(), size);
-        buf[size] = '\0';
-        s = buf.get();
-    }
-
-    /**
-      Fetch data from the stream into a vector of char.
-
-      \param buf  Buffer to fill.
-    */
-    PDAL_DLL void get(std::vector<char>& buf)
-        { m_stream->read(&buf[0], buf.size()); }
-
-    /**
-      Fetch data from the stream into a vector of unsigned char.
-
-      \param buf  Buffer to fill.
-    */
-    PDAL_DLL void get(std::vector<unsigned char>& buf)
-        { m_stream->read((char *)&buf[0], buf.size()); }
-
-    /**
-      Fetch data from the stream into the specified buffer of char.
-
-      \param buf  Buffer to fill.
-      \param size  Number of bytes to extract.
-    */
-    PDAL_DLL void get(char *buf, size_t size)
-        { m_stream->read(buf, size); }
-
-    /**
-      Fetch data from the stream into the specified buffer of unsigned char.
-
-      \param buf  Buffer to fill.
-      \param size  Number of bytes to extract.
-    */
-    PDAL_DLL void get(unsigned char *buf, size_t size)
-        { m_stream->read((char *)buf, size); }
-
-protected:
-    std::istream *m_stream;
-    std::ifstream *m_fstream; // Dup of above to facilitate cleanup.
-
-private:
-    std::stack<std::istream *> m_streams;
-	IStream(const IStream&);
-};
-
-/**
-  Stream wrapper for input of binary data that converts from little-endian
-  to host ordering.
-*/
-class ILeStream : public IStream
-{
-public:
-    /**
-      Default constructor.
-    */
-    PDAL_DLL ILeStream()
-    {}
-
-    /**
-      Constructor that opens the file and maps it to a stream.
-
-      \param filename  Filename.
-    */
-    PDAL_DLL ILeStream(const std::string& filename) : IStream(filename)
-    {}
-
-    /**
-      Constructor that maps to a provided stream.
-
-      \param stream  Stream to extract from.
-    */
-    PDAL_DLL ILeStream(std::istream *stream) : IStream(stream)
-    {}
-
-    /**
-      Extract an unsigned byte from the stream.
-
-      \param v  unsigned byte to populate
-      \return  This stream.
-    */
-    PDAL_DLL ILeStream& operator >> (uint8_t& v)
-    {
-        v = (uint8_t)m_stream->get();
-        return *this;
-    }
-
-    /**
-      Extract an unsigned byte from the stream.
-
-      \param v  unsigned byte to populate
-      \return  This stream.
-    */
-    PDAL_DLL ILeStream& operator >> (int8_t& v)
-    {
-        v = (int8_t)m_stream->get();
-        return *this;
-    }
-
-    /**
-      Extract an unsigned short from the stream.
-
-      \param v  unsigned short to populate
-      \return  This stream.
-    */
-    PDAL_DLL ILeStream& operator >> (uint16_t& v)
-    {
-        m_stream->read((char *)&v, sizeof(v));
-        v = le16toh(v);
-        return *this;
-    }
-
-    /**
-      Extract an short from the stream.
-
-      \param v  short to populate
-      \return  This stream.
-    */
-    PDAL_DLL ILeStream& operator >> (int16_t& v)
-    {
-        m_stream->read((char *)&v, sizeof(v));
-        v = (int16_t)le16toh((uint16_t)v);
-        return *this;
-    }
-
-    /**
-      Extract an unsigned int from the stream.
-
-      \param v  unsigned int to populate
-      \return  This stream.
-    */
-    PDAL_DLL ILeStream& operator >> (uint32_t& v)
-    {
-        m_stream->read((char *)&v, sizeof(v));
-        v = le32toh(v);
-        return *this;
-    }
-
-    /**
-      Extract an int from the stream.
-
-      \param v  int to populate
-      \return  This stream.
-    */
-    PDAL_DLL ILeStream& operator >> (int32_t& v)
-    {
-        m_stream->read((char *)&v, sizeof(v));
-        v = (int32_t)le32toh((uint32_t)v);
-        return *this;
-    }
-
-    /**
-      Extract an unsigned long int from the stream.
-
-      \param v  unsigned long int to populate
-      \return  This stream.
-    */
-    PDAL_DLL ILeStream& operator >> (uint64_t& v)
-    {
-        m_stream->read((char *)&v, sizeof(v));
-        v = le64toh(v);
-        return *this;
-    }
-
-    /**
-      Extract a long int from the stream.
-
-      \param v  long int to populate
-      \return  This stream.
-    */
-    PDAL_DLL ILeStream& operator >> (int64_t& v)
-    {
-        m_stream->read((char *)&v, sizeof(v));
-        v = (int64_t)le64toh((uint64_t)v);
-        return *this;
-    }
-
-    /**
-      Extract a float from the stream.
-
-      \param v  float to populate
-      \return  This stream.
-    */
-    PDAL_DLL ILeStream& operator >> (float& v)
-    {
-        m_stream->read((char *)&v, sizeof(v));
-        uint32_t tmp = le32toh(*(uint32_t *)(&v));
-        std::memcpy(&v, &tmp, sizeof(tmp));
-        return *this;
-    }
-
-    /**
-      Extract a double from the stream.
-
-      \param v  double to populate
-      \return  This stream.
-    */
-    PDAL_DLL ILeStream& operator >> (double& v)
-    {
-        m_stream->read((char *)&v, sizeof(v));
-        uint64_t tmp = le64toh(*(uint64_t *)(&v));
-        std::memcpy(&v, &tmp, sizeof(tmp));
-        return *this;
-    }
-};
-
-
-/**
-  Stream wrapper for input of binary data that converts from either
-  little-endian or big-endian to host ordering, depending on object
-  settings.
-*/
-class ISwitchableStream : public IStream
-{
-public:
-    static const bool DefaultIsLittleEndian = true;
-
-    PDAL_DLL ISwitchableStream() : m_isLittleEndian(DefaultIsLittleEndian)
-    {}
-
-    PDAL_DLL ISwitchableStream(const std::string& filename)
-        : IStream(filename)
-        , m_isLittleEndian(DefaultIsLittleEndian)
-    {}
-    
-    PDAL_DLL ISwitchableStream(std::istream* stream)
-        : IStream(stream)
-        , m_isLittleEndian(DefaultIsLittleEndian)
-    {}
-
-    PDAL_DLL bool isLittleEndian() const
-        { return m_isLittleEndian; }
-    PDAL_DLL void switchToLittleEndian()
-        { m_isLittleEndian = true; }
-    PDAL_DLL void switchToBigEndian()
-        { m_isLittleEndian = false; }
-
-    PDAL_DLL ISwitchableStream& operator>>(uint8_t& v)
-    {
-        v = (uint8_t)m_stream->get();
-        return *this;
-    }
-
-    PDAL_DLL ISwitchableStream& operator>>(int8_t& v)
-    {
-        v = (int8_t)m_stream->get();
-        return *this;
-    }
-
-    PDAL_DLL ISwitchableStream& operator>>(uint16_t& v)
-    {
-        m_stream->read((char*)&v, sizeof(v));
-        v = isLittleEndian() ? le16toh(v) : be16toh(v);
-        return *this;
-    }
-
-    PDAL_DLL ISwitchableStream& operator>>(int16_t& v)
-    {
-        m_stream->read((char*)&v, sizeof(v));
-        v = isLittleEndian() ? (int16_t)le16toh((uint16_t)v)
-                             : (int16_t)be16toh((uint16_t)v);
-        return *this;
-    }
-
-    PDAL_DLL ISwitchableStream& operator>>(uint32_t& v)
-    {
-        m_stream->read((char*)&v, sizeof(v));
-        v = isLittleEndian() ? le32toh(v) : be32toh(v);
-        return *this;
-    }
-
-    PDAL_DLL ISwitchableStream& operator>>(int32_t& v)
-    {
-        m_stream->read((char*)&v, sizeof(v));
-        v = isLittleEndian() ? (int32_t)le32toh((uint32_t)v)
-                             : (int32_t)be32toh((uint32_t)v);
-        return *this;
-    }
-
-    PDAL_DLL ISwitchableStream& operator>>(uint64_t& v)
-    {
-        m_stream->read((char*)&v, sizeof(v));
-        v = isLittleEndian() ? le64toh(v) : be64toh(v);
-        return *this;
-    }
-
-    PDAL_DLL ISwitchableStream& operator>>(int64_t& v)
-    {
-        m_stream->read((char*)&v, sizeof(v));
-        v = isLittleEndian() ? (int64_t)le64toh((uint64_t)v)
-                             : (int64_t)be64toh((uint64_t)v);
-        return *this;
-    }
-
-    PDAL_DLL ISwitchableStream& operator>>(float& v)
-    {
-        m_stream->read((char*)&v, sizeof(v));
-        uint32_t tmp = isLittleEndian() ? le32toh(*(uint32_t*)(&v))
-                                        : be32toh(*(uint32_t*)(&v));
-        std::memcpy(&v, &tmp, sizeof(tmp));
-        return *this;
-    }
-
-    PDAL_DLL ISwitchableStream& operator>>(double& v)
-    {
-        m_stream->read((char*)&v, sizeof(v));
-        uint64_t tmp = isLittleEndian() ? be64toh(*(uint64_t*)(&v))
-                                        : be64toh(*(uint64_t*)(&v));
-        std::memcpy(&v, &tmp, sizeof(tmp));
-        return *this;
-    }
-
-private:
-    bool m_isLittleEndian;
-};
-
-
-/// Stream position marker with rewinding support.
-class IStreamMarker
-{
-public:
-    PDAL_DLL IStreamMarker(IStream& stream) : m_stream(stream)
-        { m_pos = m_stream.position(); }
-
-    PDAL_DLL void rewind()
-        { m_stream.seek(m_pos); }
-
-private:
-    std::streampos m_pos;
-    IStream& m_stream;
-	IStreamMarker(const IStreamMarker&);
-    IStreamMarker& operator=(const IStreamMarker&); // not implemented
-};
-
-} // namespace pdal
diff --git a/include/pdal/util/ProgramArgs.hpp b/include/pdal/util/ProgramArgs.hpp
deleted file mode 100644
index fa8b50e..0000000
--- a/include/pdal/util/ProgramArgs.hpp
+++ /dev/null
@@ -1,1417 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Hobu Inc., hobu at hobu.co
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-****************************************************************************/
-
-#pragma once
-
-#include <map>
-#include <memory>
-#include <vector>
-
-#include <pdal/util/Utils.hpp>
-
-namespace pdal
-{
-
-class arg_error
-{
-public:
-    arg_error(const std::string& error) : m_error(error)
-    {}
-
-    std::string m_error;
-};
-
-namespace
-{
-
-class ArgValList
-{
-    struct ArgVal
-    {
-        std::string m_val;
-        bool m_consumed;
-
-        ArgVal(const std::string& s) :
-            m_val(s), m_consumed(false)
-        {}
-    };
-
-public:
-    ArgValList(const std::vector<std::string>& slist) : m_unconsumedStart(0)
-    {
-        for (const std::string& s : slist)
-            add(s);
-    }
-
-    void add(const std::string& s)
-    {
-        if (s.empty())
-            return;
-        if (s.size() > 1 && s[0] == '-' && s[1] != '-')
-            for (size_t i = 1; i < s.size(); i++)
-                m_vals.push_back({std::string("-") + s[i]});
-        else
-            m_vals.push_back({s});
-    }
-
-    void consume(size_t i)
-    {
-        m_vals[i].m_consumed = true;
-        if (i == m_unconsumedStart)
-            while (i < m_vals.size() && consumed(++i))
-                m_unconsumedStart++;
-    }
-
-    std::vector<std::string> unconsumedArgs() const
-    {
-        std::vector<std::string> remainingVals;
-
-        for (size_t i = firstUnconsumed(); i < size(); ++i)
-            if (!consumed(i))
-                remainingVals.push_back(m_vals[i].m_val);
-        return remainingVals;
-    }
-
-    size_t size() const
-        { return m_vals.size(); }
-    const std::string& operator[](size_t i) const
-        { return m_vals[i].m_val; }
-    bool consumed(size_t i) const
-        { return m_vals[i].m_consumed; }
-    size_t firstUnconsumed() const
-        { return m_unconsumedStart; }
-private:
-    std::vector<ArgVal> m_vals;
-    size_t m_unconsumedStart;
-};
-
-} // unnamed namespace
-
-
-/**
-   Description of an argument that can be parsed by \class ProgramArgs.
-
-   Stores information about each argument including the required "longname",
-   an optional single-character shortname, a description, and an indicator
-   of the positional-type of the argument.
-*/
-class Arg
-{
-public:
-/**
-  Positional type.  Either None, Optional or Required.
-*/
-enum class PosType
-{
-    None,       ///< Not positional
-    Required,   ///< Required positional
-    Optional    ///< Optional positional
-};
-
-protected:
-    /**
-      Constructor.
-
-      \param longname  Name of argument specified on command line with "--"
-        prefix.
-      \param shortname  Optional name of argument specified on command
-        line with "-" prefix.
-      \param description  Argument description.
-    */
-    Arg(const std::string& longname, const std::string& shortname,
-        const std::string& description) : m_longname(longname),
-        m_shortname(shortname), m_description(description), m_set(false),
-        m_hidden(false), m_positional(PosType::None)
-    {}
-
-public:
-    /**
-      Indicate that the argument shouldn't be shown in help text.
-
-      \param hidden  Whether the argument should be hidden or not
-        [default: true].
-      \return  A reference to this \class Arg, to allow the function
-        call to be chained.
-    */
-    Arg& setHidden(bool hidden = true)
-    {
-        m_hidden = true;
-        return *this;
-    }
-    /**
-      Indicate that the argument is positional.
-
-      Positional arguments may be specified on the command line without
-      any argument name.  Such arguments are required to be specified
-      either with the argument name as a normal option or positionally.
-      Missing positional arguments will raise an exception when the
-      command line is parsed
-    */
-    virtual Arg& setPositional()
-    {
-        m_positional = PosType::Required;
-        return *this;
-    }
-    /**
-      Indicate that the argument is positional and optional.
-
-      Positional arguments may be specified on the command line without
-      any argument name.  Optional positional arguments must be added to
-      \class ProgramArgs after any non-optional arguments.  If optional
-      positional arguments are not found, no exception is raised when
-      the command line is parsed.
-    */
-    virtual Arg& setOptionalPositional()
-    {
-        m_positional = PosType::Optional;
-        return *this;
-    }
-    /**
-      Provide error text for the argument to override the default.
-
-      \param error  Error text.
-    */
-    virtual Arg& setErrorText(const std::string& error)
-    {
-        m_error = error;
-        return *this;
-    }
-    /**
-      Return whether the argument was set during command-line parsing.
-    */
-    bool set() const
-        { return m_set; }
-    /**
-      Return whether a default value was provided for the argument.
-
-      \return  Whether a default was provided.
-    */
-    virtual bool defaultProvided() const
-        { return false; }
-    /**
-      Return a string representation of an Arg's default value, or an
-      empty string if none exists.
-
-      \return  Default value as a string.
-    */
-    virtual std::string defaultVal() const
-        { return std::string(); }
-
-public:
-    /**
-      Return whether an option needs a value to be valid.  Generally true
-      for all options not bound to boolean values.
-      \note  Not intended to be called from user code.
-    */
-    virtual bool needsValue() const
-        { return true; }
-
-    /**
-      Set a an argument's value from a string.
-
-      Throws an arg_error exception if \a s can't be converted to
-      the argument's type.
-      \note  Not intended to be called from user code.
-
-      \param s  Value to set.
-    */
-    virtual void setValue(const std::string& s) = 0;
-
-    /**
-      Reset the argument's state.
-
-      Set the internal state of the argument and it's referenced variable
-      as if no command-line parsing had occurred.
-      \note  For testing.  Not intended to be called from user code.
-    */
-    virtual void reset() = 0;
-
-    /**
-      Set the argument's value from the list of command-line args.
-      \note  Not intended to be called from user code.
-
-      \param vals  The list of command-line argument values.
-    */
-    virtual void assignPositional(ArgValList& vals)
-    {}
-
-    /**
-      Returns the positional type of the argument.
-      \note  Not intended to be called from user code.
-    */
-    PosType positional() const
-        { return m_positional; }
-
-    /**
-      Returns whether the argument is hidden or not.
-      \note  Not intended to be called from user code.
-    */
-    bool hidden() const
-        { return m_hidden; }
-
-    /**
-      Returns the description of the argument.
-      \note  Not intended to be called from user code.
-      \return  Argument description.
-    */
-    std::string description() const
-        { return m_description; }
-
-    /**
-      Returns the longname of the argument.
-      \note  Not intended to be called from user code.
-      \return  Argument long name.
-    */
-    std::string longname() const
-        { return m_longname; }
-
-    /**
-      Returns text indicating the longname and shortname of the option
-      suitable for displaying in help information.
-      \note  Not intended to be called from user code.
-    */
-    std::string nameDescrip() const
-    {
-        std::string s("--");
-        s += m_longname;
-        if (m_shortname.size())
-            s += ", -" + m_shortname;
-        return s;
-    }
-    /**
-      Returns text indicating the name of the option suitable for displaying
-      in "usage" text.
-      \note  Not intended to be called from user code.
-    */
-    std::string commandLine() const
-    {
-        std::string s;
-        if (m_positional == PosType::Required)
-            s =  m_longname;
-        else if (m_positional == PosType::Optional)
-            s += '[' + m_longname + ']';
-        return s;
-    }
-
-protected:
-    std::string m_longname;
-    std::string m_shortname;
-    std::string m_description;
-    std::string m_rawVal;
-    bool m_set;
-    bool m_hidden;
-    PosType m_positional;
-    std::string m_error;
-};
-
-/**
-  Description of an argument.  Boolean arguments and vector (list-based)
-  arguments are handled separately.
-*/
-template <typename T>
-class TArg : public Arg
-{
-public:
-    /**
-      Constructor that takes a default argument.
-
-      \param longname  Name of argument specified on command line with "--"
-        prefix.
-      \param shortname  Optional name of argument specified on command
-        line with "-" prefix.
-      \param description  Argument description.
-      \param variable  Variable to which the value of the argument should
-        be bound.
-      \param def  Default value to be assigned to the bound variable.
-    */
-    TArg(const std::string& longname, const std::string& shortname,
-        const std::string& description, T& variable, T def) :
-        Arg(longname, shortname, description), m_var(variable),
-        m_defaultVal(def), m_defaultProvided(true)
-    { m_var = m_defaultVal; }
-
-    /**
-      Constructor.
-
-      \param longname  Name of argument specified on command line with "--"
-        prefix.
-      \param shortname  Optional name of argument specified on command
-        line with "-" prefix.
-      \param description  Argument description.
-      \param variable  Variable to which the value of the argument should
-        be bound.
-    */
-    TArg(const std::string& longname, const std::string& shortname,
-        const std::string& description, T& variable) :
-        Arg(longname, shortname, description), m_var(variable),
-        m_defaultVal(T()), m_defaultProvided(false)
-    { m_var = m_defaultVal; }
-
-    /**
-      Set a an argument's value from a string.
-
-      Throws an arg_error exception if \a s can't be converted to
-      the argument's type.  Values must be provided for with the
-      option name.
-      \note  Not intended to be called from user code.
-
-      \param s  Value to set.
-    */
-    virtual void setValue(const std::string& s)
-    {
-        if (m_set)
-        {
-            std::ostringstream oss;
-            oss << "Attempted to set value twice for argument '" <<
-                m_longname << "'.";
-            throw arg_error(oss.str());
-        }
-        if (s.empty())
-        {
-            std::stringstream oss;
-            oss << "Argument '" << m_longname << "' needs a value and none "
-                "was provided.";
-            throw arg_error(oss.str());
-        }
-        m_rawVal = s;
-        if (!Utils::fromString(s, m_var))
-        {
-            std::ostringstream oss;
-            if (m_error.size())
-                throw arg_error(m_error);
-            else
-            {
-                oss << "Invalid value for argument '" << m_longname << "'.";
-                throw arg_error(oss.str());
-            }
-        }
-        m_set = true;
-    }
-
-    /**
-      Reset the argument's state.
-
-      Set the interval state of the argument and it's referenced variable
-      as if no command-line parsing had occurred.
-      \note  For testing.  Not intended to be called from user code.
-    */
-    virtual void reset()
-    {
-        m_var = m_defaultVal;
-        m_set = false;
-        m_hidden = false;
-    }
-
-    /**
-      Set the argument's value from the command-line args.
-
-      If no value is provided for a required positional option, an arg_error
-      exception is thrown.
-      \note  Not intended to be called from user code.
-
-      \param vals  The list of command-line args.
-    */
-    virtual void assignPositional(ArgValList& vals)
-    {
-        if (m_positional == PosType::None || m_set)
-            return;
-        for (size_t i = vals.firstUnconsumed(); i < vals.size(); ++i)
-        {
-            const std::string& val = vals[i];
-            if ((val.size() && val[0] == '-') || vals.consumed(i))
-                continue;
-            setValue(val);
-            vals.consume(i);
-            return;
-        }
-        if (m_positional == PosType::Required)
-        {
-            std::ostringstream oss;
-
-            oss << "Missing value for positional argument '" <<
-                m_longname << "'.";
-            throw arg_error(oss.str());
-        }
-    }
-
-    /**
-      Return whether a default value was provided for the argument.
-
-      \return  Whether a default was provided.
-    */
-    virtual bool defaultProvided() const
-        { return m_defaultProvided; }
-
-    /**
-      Return a string representation of an Arg's default value.
-
-      \return  Default value as a string.
-    */
-    virtual std::string defaultVal() const
-        { return Utils::toString(m_defaultVal); }
-
-private:
-    T& m_var;
-    T m_defaultVal;
-    bool m_defaultProvided;
-};
-
-/**
-  Description of a boolean argument.  Boolean arguments don't take values.
-  Setting a boolean argument inverts its default value.  Boolean arguments
-  are normally 'false' by default.
-*/
-template <>
-class TArg<bool> : public Arg
-{
-public:
-    /**
-      Constructor for boolean arguments with default value.
-
-      \param longname  Name of argument specified on command line with "--"
-        prefix.
-      \param shortname  Optional name of argument specified on command
-        line with "-" prefix.
-      \param description  Argument description.
-      \param variable  bool variable to which the value of the argument should
-        be bound.
-      \param def  Default value to be assigned to the bound variable.
-    */
-    TArg(const std::string& longname, const std::string& shortname,
-        const std::string& description, bool& variable, bool def) :
-        Arg(longname, shortname, description), m_val(variable),
-        m_defaultVal(def), m_defaultProvided(true)
-    { m_val = m_defaultVal; }
-
-    /**
-      Constructor for boolean arguments without default value.
-
-      \param longname  Name of argument specified on command line with "--"
-        prefix.
-      \param shortname  Optional name of argument specified on command
-        line with "-" prefix.
-      \param description  Argument description.
-      \param variable  bool variable to which the value of the argument should
-        be bound.
-    */
-    TArg(const std::string& longname, const std::string& shortname,
-        const std::string& description, bool& variable) :
-        Arg(longname, shortname, description), m_val(variable),
-        m_defaultVal(false), m_defaultProvided(false)
-    { m_val = m_defaultVal; }
-
-    /**
-      Return whether an option needs a value to be valid.
-
-      \return false  Boolean values don't need a value.
-      \note  Not intended to be called from user code.
-    */
-    virtual bool needsValue() const
-        { return false; }
-
-    /**
-      Set a an argument's value from a string.
-
-      \note  The argumet is either 'true' or 'false'.  True means that we're
-        setting the option, which sets the negative of the default value.
-        False sets the option to the default value (essentially a no-op).
-      \note  Not intended to be called from user code.
-
-      \param s  Value to set [ignored].
-    */
-    virtual void setValue(const std::string& s)
-    {
-        if (s.size() && s[0] == '-')
-        {
-            std::stringstream oss;
-            oss << "Argument '" << m_longname << "' needs a value and none "
-                "was provided.";
-            throw arg_error(oss.str());
-        }
-        if (s == "invert")
-            m_val = !m_defaultVal;
-        else if (s == "true")
-            m_val = true;
-        else
-            m_val = false;
-        m_set = true;
-    }
-
-    /**
-      Reset the argument's state.
-
-      Set the internal state of the argument and it's referenced variable
-      as if no command-line parsing had occurred.
-      \note  For testing.  Not intended to be called from user code.
-    */
-    virtual void reset()
-    {
-        m_val = m_defaultVal;
-        m_set = false;
-        m_hidden = false;
-    }
-
-    /**
-      Indicate that the argument is positional.
-
-      Throws an exception to indicate that boolean arguments can't
-      positional.
-    */
-    virtual Arg& setPositional()
-    {
-        std::ostringstream oss;
-        oss << "Boolean argument '" << m_longname << "' can't be positional.";
-        throw arg_error(oss.str());
-        return *this;
-    }
-
-    /**
-      Indicate that the argument is positional and optional.
-
-      Throws an exception to indicate that boolean arguments can't
-      positional.
-    */
-    virtual Arg& setOptionalPositional()
-    {
-        std::ostringstream oss;
-        oss << "Boolean argument '" << m_longname << "' can't be positional.";
-        throw arg_error(oss.str());
-        return *this;
-    }
-    /**
-      Return whether a default value was provided for the argument.
-
-      \return  Whether a default was provided.
-    */
-    virtual bool defaultProvided() const
-        { return m_defaultProvided; }
-    /**
-      Return a string representation of an Arg's default value.
-
-      \return  Default value as a string.
-    */
-    virtual std::string defaultVal() const
-        { return Utils::toString(m_defaultVal); }
-
-private:
-    bool& m_val;
-    bool m_defaultVal;
-    bool m_defaultProvided;
-};
-
-/**
-  Description of a list-based (vector) argument.  List-based arguments can
-  be specified multiple times, taking multiple values.  List-based
-  arguments are necessarily bound to variables that are vectors.
-  \note  Doesn't properly support list-based boolean values.
-*/
-class BaseVArg : public Arg
-{
-public:
-    /**
-      Constructor.
-
-      \param longname  Name of argument specified on command line with "--"
-        prefix.
-      \param shortname  Optional name of argument specified on command
-        line with "-" prefix.
-      \param description  Argument description.
-    */
-    BaseVArg(const std::string& longname, const std::string& shortname,
-        const std::string& description) : Arg(longname, shortname, description)
-    {}
-
-    /**
-      Set the argument's value from the command-line args.
-
-      List-based arguments consume ALL positional arguments until
-      one is found that can't be converted to the type of the bound variable.
-      \note  Not intended to be called from user code.
-
-      \param vals  The list of command-line args.
-    */
-    virtual void assignPositional(ArgValList& vals)
-    {
-        if (m_positional == PosType::None || m_set)
-            return;
-
-        int cnt = 0;
-        for (size_t i = vals.firstUnconsumed(); i < vals.size(); ++i)
-        {
-            const std::string& val = vals[i];
-            if ((val.size() && val[0] == '-') || vals.consumed(i))
-                continue;
-            try
-            {
-                setValue(val);
-                vals.consume(i);
-                cnt++;
-            }
-            catch (arg_error&)
-            {
-                break;
-            }
-        }
-        if (cnt == 0 && m_positional == PosType::Required)
-        {
-            std::ostringstream oss;
-
-            oss << "Missing value for positional argument '" <<
-                m_longname << "'.";
-            throw arg_error(oss.str());
-        }
-    }
-};
-
-/**
-  Description of a generic list-based (vector) argument.
-  \note  Doesn't properly support list-based boolean values.
-*/
-template <typename T>
-class VArg : public BaseVArg
-{
-public:
-    /**
-      Constructor.
-
-      \param longname  Name of argument specified on command line with "--"
-        prefix.
-      \param shortname  Optional name of argument specified on command
-        line with "-" prefix.
-      \param description  Argument description.
-      \param variable  Variable to which the argument value(s) should be bound.
-    */
-    VArg(const std::string& longname, const std::string& shortname,
-        const std::string& description, std::vector<T>& variable) :
-        BaseVArg(longname, shortname, description), m_var(variable)
-    {
-        // Clearing the vector resets to "default" value.
-        m_var.clear();
-    }
-
-    /**
-      Set a an argument's value from a string.
-
-      Throws an arg_error exception if \a s can't be converted to
-      the argument's type.
-      \note  Not intended to be called from user code.
-
-      \param s  Value to set.
-    */
-    virtual void setValue(const std::string& s)
-    {
-        if (s.size() && s[0] == '-')
-        {
-            std::stringstream oss;
-            oss << "Argument '" << m_longname << "' needs a value and none "
-                "was provided.";
-            throw arg_error(oss.str());
-        }
-        m_rawVal = s;
-        T var;
-        if (!Utils::fromString(s, var))
-        {
-            std::ostringstream oss;
-            oss << "Invalid value for argument '" << m_longname << "'.";
-            throw arg_error(oss.str());
-        }
-        m_var.push_back(var);
-        m_set = true;
-    }
-
-    /**
-      Reset the argument's state.
-
-      Set the internal state of the argument and it's referenced variable
-      as if no command-line parsing had occurred.
-      \note  For testing.  Not intended to be called from user code.
-    */
-    virtual void reset()
-    {
-        m_var.clear();
-        m_set = false;
-        m_hidden = false;
-    }
-
-private:
-    std::vector<T>& m_var;
-};
-
-/**
-  Description of an argument tied to a string vector.
-*/
-template <>
-class VArg<std::string> : public BaseVArg
-{
-public:
-    /**
-      Constructor.
-
-      \param longname  Name of argument specified on command line with "--"
-        prefix.
-      \param shortname  Optional name of argument specified on command
-        line with "-" prefix.
-      \param description  Argument description.
-      \param variable  Variable to which the argument value(s) should be bound.
-    */
-    VArg(const std::string& longname, const std::string& shortname,
-        const std::string& description, std::vector<std::string>& variable) :
-        BaseVArg(longname, shortname, description), m_var(variable)
-    {}
-
-    /**
-      Set a an argument's value from a string.
-
-      Throws an arg_error exception if \a s can't be converted to
-      the argument's type.
-      \note  Not intended to be called from user code.
-
-      \param s  Value to set.
-    */
-    virtual void setValue(const std::string& s)
-    {
-        std::vector<std::string> slist = Utils::split2(s, ',');
-        for (auto& ts : slist)
-            Utils::trim(ts);
-
-        if ((s.size() && s[0] == '-') || slist.empty())
-        {
-            std::ostringstream oss;
-
-            oss << "Missing value for argument '" << m_longname << "'.";
-            throw arg_error(oss.str());
-        }
-        m_rawVal = s;
-        m_var.reserve(m_var.size() + slist.size());
-        m_var.insert(m_var.end(), slist.begin(), slist.end());
-        m_set = true;
-    }
-
-    /**
-      Reset the argument's state.
-
-      Set the internal state of the argument and it's referenced variable
-      as if no command-line parsing had occurred.
-      \note  For testing.  Not intended to be called from user code.
-    */
-    virtual void reset()
-    {
-        m_var.clear();
-        m_set = false;
-        m_hidden = false;
-    }
-
-private:
-    std::vector<std::string>& m_var;
-};
-
-/**
-  Parses command lines, provides validation and stores found values in
-  bound variables.  Add arguments with \ref add.  When all arguments
-  have been added, use \ref parse to validate command line and assign
-  values to variables bound with \ref add.
-*/
-class ProgramArgs
-{
-
-public:
-    /**
-      Add a string argument to the list of arguments.
-
-      \param name  Name of argument.  Argument names are specified as
-        "longname[,shortname]", where shortname is an optional one-character
-        abbreviation.
-      \param description  Description of the argument.
-      \param var  Reference to variable to bind to argument.
-      \param def  Default value of argument.
-      \return  Reference to the new argument.
-    */
-    Arg& add(const std::string& name, const std::string description,
-        std::string& var, std::string def)
-    {
-        return add<std::string>(name, description, var, def);
-    }
-
-    /**
-      Add a list-based (vector) string argument
-
-      \param name  Name of argument.  Argument names are specified as
-        "longname[,shortname]", where shortname is an optional one-character
-        abbreviation.
-      \param description  Description of the argument.
-      \param var  Reference to variable to bind to argument.
-      \return  Reference to the new argument.
-    */
-    Arg& add(const std::string& name, const std::string& description,
-        std::vector<std::string>& var)
-    {
-        return add<std::string>(name, description, var);
-    }
-
-    /**
-      Return whether the argument (as specified by it's longname) had
-      its value set during parsing.
-    */
-    bool set(const std::string& name) const
-    {
-        Arg *arg = findLongArg(name);
-        if (arg)
-            return arg->set();
-        return false;
-    }
-
-    /**
-      Add a list-based (vector) argument.
-
-      \param name  Name of argument.  Argument names are specified as
-        "longname[,shortname]", where shortname is an optional one-character
-        abbreviation.
-      \param description  Description of the argument.
-      \param var  Reference to variable to bind to argument.
-      \return  Reference to the new argument.
-    */
-    template<typename T>
-    Arg& add(const std::string& name, const std::string& description,
-        std::vector<T>& var)
-    {
-        std::string longname, shortname;
-        splitName(name, longname, shortname);
-
-        Arg *arg = new VArg<T>(longname, shortname, description, var);
-        addLongArg(longname, arg);
-        addShortArg(shortname, arg);
-        m_args.push_back(std::unique_ptr<Arg>(arg));
-        return *arg;
-    }
-
-    /**
-      Add an argument to the list of arguments with a default.
-
-      \param name  Name of argument.  Argument names are specified as
-        "longname[,shortname]", where shortname is an optional one-character
-        abbreviation.
-      \param description  Description of the argument.
-      \param var  Reference to variable to bind to argument.
-      \param def  Default value of argument.  If not specified, a
-        default-constructed value is used.
-      \return  Reference to the new argument.
-    */
-    template<typename T>
-    Arg& add(const std::string& name, const std::string description, T& var,
-        T def)
-    {
-        std::string longname, shortname;
-        splitName(name, longname, shortname);
-
-        Arg *arg = new TArg<T>(longname, shortname, description, var, def);
-        addLongArg(longname, arg);
-        addShortArg(shortname, arg);
-        m_args.push_back(std::unique_ptr<Arg>(arg));
-        return *arg;
-    }
-
-    /**
-      Add an argument to the list of arguments.
-
-      \param name  Name of argument.  Argument names are specified as
-        "longname[,shortname]", where shortname is an optional one-character
-        abbreviation.
-      \param description  Description of the argument.
-      \param var  Reference to variable to bind to argument.
-      \return  Reference to the new argument.
-    */
-    template<typename T>
-    Arg& add(const std::string& name, const std::string description, T& var)
-    {
-        std::string longname, shortname;
-        splitName(name, longname, shortname);
-
-        Arg *arg = new TArg<T>(longname, shortname, description, var);
-        addLongArg(longname, arg);
-        addShortArg(shortname, arg);
-        m_args.push_back(std::unique_ptr<Arg>(arg));
-        return *arg;
-    }
-
-    /**
-      Parse a command line as specified by its argument vector.  No validation
-      occurs and no exceptions are raised, but assignments are made
-      to bound variables where possible.
-
-      \param s  List of strings that constitute the argument list.
-    */
-    void parseSimple(std::vector<std::string>& s)
-    {
-        ArgValList vals(s);
-
-        for (size_t i = 0; i < vals.size();)
-        {
-            const std::string& arg = vals[i];
-            // This may be the value, or it may not.  We're passing it along
-            // just in case.  If there is no value, pass along "" to make
-            // clear that there is none.
-            std::string value((i != vals.size() - 1) ? vals[i + 1] : "");
-            try
-            {
-                int matched = parseArg(arg, value);
-                if (!matched)
-                    i++;
-                else
-                    while (matched--)
-                        vals.consume(i++);
-            }
-            catch (arg_error&)
-            {
-                i++;
-            }
-        }
-
-        // Go through args and assign those unset to items from the command
-        // line not already consumed.
-        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
-        {
-            Arg *arg = ai->get();
-            try
-            {
-                arg->assignPositional(vals);
-            }
-            catch (arg_error&)
-            {}
-        }
-        s = vals.unconsumedArgs();
-    }
-
-    /**
-      Parse a command line as specified by its argument list.  Parsing
-      validates the argument vector and assigns values to variables bound
-      to added arguments.
-
-      \param s  List of strings that constitute the argument list.
-    */
-    void parse(std::vector<std::string>& s)
-    {
-        validate();
-        ArgValList vals(s);
-        for (size_t i = 0; i < vals.size();)
-        {
-            const std::string& arg = vals[i];
-            // This may be the value, or it may not.  We're passing it along
-            // just in case.  If there is no value, pass along "" to make
-            // clear that there is none.
-            std::string value((i != vals.size() - 1) ? vals[i + 1] : "");
-            size_t matched = parseArg(arg, value);
-            if (!matched)
-                i++;
-            else
-                while (matched--)
-                    vals.consume(i++);
-        }
-
-        // Go through args and assign those unset to items from the command
-        // line not already consumed.
-        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
-        {
-            Arg *arg = ai->get();
-            arg->assignPositional(vals);
-        }
-    }
-
-    /**
-      Reset the state of all arguments and bound variables as if no parsing
-      had occurred.
-    */
-    void reset()
-    {
-        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
-            (*ai)->reset();
-    }
-
-    /**
-      Return a string suitable for use in a "usage" line for display to
-      users as help.
-    */
-    std::string commandLine() const
-    {
-        std::string s;
-
-        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
-        {
-            Arg *a = ai->get();
-
-            if (a->hidden())
-                continue;
-            std::string o = a->commandLine();
-            if (o.size())
-                s += o + " ";
-        }
-        if (s.size())
-            s = s.substr(0, s.size() - 1);
-        return s;
-    }
-
-    /**
-      Write a formatted description of arguments to an output stream.
-
-      Write a list of the names and descriptions of arguments suitable for
-      display as help information.
-
-      \param out  Stream to which output should be written.
-      \param indent  Number of characters to indent all text.
-      \param totalWidth  Total width to assume for formatting output.
-        Typically this is the width of a terminal window.
-    */
-    void dump(std::ostream& out, size_t indent, size_t totalWidth) const
-    {
-        size_t namelen = 0;
-        std::vector<std::pair<std::string, std::string>> info;
-
-        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
-        {
-            Arg *a = ai->get();
-            if (a->hidden())
-                continue;
-
-            std::string nameDescrip = a->nameDescrip();
-
-            info.push_back(std::make_pair(nameDescrip, a->description()));
-            namelen = std::max(namelen, nameDescrip.size());
-        }
-        size_t secondIndent = indent + 4;
-        int postNameSpacing = 2;
-        size_t leadlen = namelen + indent + postNameSpacing;
-        size_t firstlen = totalWidth - leadlen - 1;
-        size_t secondLen = totalWidth - secondIndent - 1;
-
-        bool skipfirst = (firstlen < 10);
-        if (skipfirst)
-            firstlen = secondLen;
-
-        for (auto i : info)
-        {
-            std::vector<std::string> descrip =
-                Utils::wordWrap(i.second, secondLen, firstlen);
-
-            std::string name = i.first;
-            out << std::string(indent, ' ');
-            if (skipfirst)
-                out << name << std::endl;
-            else
-            {
-                name.resize(namelen, ' ');
-                out << name << std::string(postNameSpacing, ' ') <<
-                    descrip[0] << std::endl;
-            }
-            for (size_t i = 1; i < descrip.size(); ++i)
-                out << std::string(secondIndent, ' ') <<
-                    descrip[i] << std::endl;
-        }
-    }
-
-    /**
-      Write a verbose description of arguments to an output stream.  Each
-      argument is on its own line.  The argument's description follows
-      on subsequent lines.
-
-      \param out  Stream to which output should be written.
-      \param nameIndent  Number of characters to indent argument lines.
-      \param descripIndent  Number of characters to indent description lines.
-      \param totalWidth  Total line width.
-
-    */
-    void dump2(std::ostream& out, size_t nameIndent, size_t descripIndent,
-        size_t totalWidth) const
-    {
-        size_t width = totalWidth - descripIndent;
-        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
-        {
-            Arg *a = ai->get();
-            out << std::string(nameIndent, ' ') << a->longname();
-            if (a->defaultProvided())
-                out << " [" << a->defaultVal() << "]";
-            out << std::endl;
-            std::vector<std::string> descrip =
-                Utils::wordWrap(a->description(), width);
-            if (descrip.empty())
-                descrip.push_back("<no description available>");
-            for (std::string& s : descrip)
-                out << std::string(descripIndent, ' ') << s << std::endl;
-            out << std::endl;
-        }
-    }
-
-private:
-    /*
-      Split an argument name into longname and shortname.
-
-      \param name  Name of argument specified as "longname[,shortname]".
-      \param[out] longname  Parsed longname.
-      \param[out] shortname  Parsed shortname.
-    */
-    void splitName(const std::string& name, std::string& longname,
-        std::string& shortname)
-    {
-        // Arg names must be specified as "longname[,shortname]" where
-        // shortname is a single character.
-        std::vector<std::string> s = Utils::split(name, ',');
-        if (s.size() > 2)
-            throw arg_error("Invalid program argument specification");
-        if (s.size() == 2 && s[1].size() != 1)
-            throw arg_error("Short argument not specified as single character");
-        if (s.empty())
-            throw arg_error("No program argument provided.");
-        if (s.size() == 1)
-            s.push_back("");
-        longname = s[0];
-        shortname = s[1];
-    }
-
-    /*
-      Add an argument to the list of arguments based on its longname.
-
-      \param name  Argument longname.
-      \param arg   Pointer to argument.
-    */
-    void addLongArg(const std::string& name, Arg *arg)
-    {
-        if (name.empty())
-            return;
-        if (findLongArg(name))
-        {
-            std::ostringstream oss;
-
-            oss << "Argument --" << name << " already exists.";
-            throw arg_error(oss.str());
-        }
-        m_longargs[name] = arg;
-    }
-
-    /*
-      Add an argument to the list of arguments based on its shortname.
-
-      \param name  Argument shortname.
-      \param arg   Pointer to argument.
-    */
-    void addShortArg(const std::string& name, Arg *arg)
-    {
-        if (name.empty())
-            return;
-        if (findShortArg(name[0]))
-        {
-            std::ostringstream oss;
-
-            oss << "Argument -" << name << " already exists.";
-            throw arg_error(oss.str());
-        }
-        m_shortargs[name] = arg;
-    }
-
-    /*
-      Find an argument given its longname.
-
-      \param s  Longname of argument.
-      \return  Pointer to matching argument, or NULL if none was found.
-    */
-    Arg *findLongArg(const std::string& s) const
-    {
-        auto si = m_longargs.find(s);
-        if (si != m_longargs.end())
-            return si->second;
-        return NULL;
-    }
-
-    /*
-      Find an argument given its shortname.
-
-      \param c  Shortnamn of argument.
-      \return  Pointer to matching argument, or NULL if none was found.
-    */
-    Arg *findShortArg(char c) const
-    {
-        std::string s(1, c);
-        auto si = m_shortargs.find(s);
-        if (si != m_shortargs.end())
-            return si->second;
-        return NULL;
-    }
-
-    /*
-      Parse a string-specified argument name and value into its argument.
-
-      \param arg  Name of argument specified on command line.
-      \param value  Potential value assigned to argument.
-      \return  Number of strings consumed (1 for positional arguments or
-        arguments that don't take values or 2 otherwise).
-    */
-    int parseArg(const std::string& arg, const std::string& value)
-    {
-        if (arg.size() > 1 && arg[0] == '-' && arg[1] == '-')
-            return parseLongArg(arg, value);
-        else if (arg.size() && arg[0] == '-')
-            return parseShortArg(arg, value);
-        return 0;
-    }
-
-    /*
-      Parse an argument specified as a long argument (prefixed with "--")
-      Long arguments with values can be specified as
-      "--name=value" or "--name value".
-
-      \param name  Name of argument specified on command line.
-      \param value  Potential value assigned to argument.
-      \return  Number of strings consumed (1 for positional arguments or
-        arguments that don't take values or 2 otherwise).
-    */
-    int parseLongArg(const std::string& inName, const std::string& inValue)
-    {
-        bool attachedValue = false;
-
-        if (inName.size() == 2)
-            throw arg_error("No argument found following '--'.");
-
-        std::string name = inName.substr(2);
-        std::string value = inValue;
-
-        std::size_t pos = name.find_first_of("=");
-        if (pos != std::string::npos)
-        {
-            if (pos < name.size() + 1)
-            {
-                value = name.substr(pos + 1);
-                name = name.substr(0, pos);
-                attachedValue = true;
-            }
-        }
-        else if (value.size() && value[0] == '-')
-        {
-            // If a value starts with a '-' and isn't attached to a name,
-            // we assume it's an option and not a value.
-            value.clear();
-        }
-
-        Arg *arg = findLongArg(name);
-        if (!arg)
-        {
-            std::ostringstream oss;
-            oss << "Unexpected argument '" << name << "'.";
-            throw arg_error(oss.str());
-        }
-
-        if (!arg->needsValue())
-        {
-            if (attachedValue)
-            {
-                if (value != "true" && value != "false")
-                {
-                    std::ostringstream oss;
-                    oss << "Value '" << value << "' provided for argument '" <<
-                        name << "' when none is expected.";
-                    throw arg_error(oss.str());
-                }
-            }
-            else
-                value = "invert";
-            arg->setValue(value);
-            return 1;
-        }
-
-        arg->setValue(value);
-        return (attachedValue ? 1 : 2);
-    }
-
-    /*
-      Parse an argument specified as a short argument (prefixed with "-")
-      Short arguments with values are specified as "-name value".
-
-      \param name  Name of argument specified on command line.
-      \param value  Potential value assigned to argument.
-      \return  Number of strings consumed (1 for positional arguments or
-        arguments that don't take values or 2 otherwise).
-    */
-    int parseShortArg(const std::string& name, const std::string& value)
-    {
-        if (name.size() == 1)
-            throw arg_error("No argument found following '-'.");
-        assert(name.size() == 2);
-
-        Arg *arg = findShortArg(name[1]);
-        if (!arg)
-        {
-            std::ostringstream oss;
-            oss << "Unexpected argument '-" << name[1] << "'.";
-            throw arg_error(oss.str());
-        }
-
-        int cnt;
-        if (arg->needsValue())
-        {
-            // If the value starts with a '-', assume it's an option
-            // rather than a value.
-            if (value.empty() || value[0] == '-')
-            {
-                std::ostringstream oss;
-                oss << "Short option '" << name << "' expects value "
-                    "but none directly follows.";
-                throw arg_error(oss.str());
-            }
-            else
-            {
-                cnt = 2;
-                arg->setValue(value);
-            }
-        }
-        else
-        {
-            arg->setValue("true");
-            cnt = 1;
-        }
-        return cnt;
-    }
-
-    /*
-      Make sure we don't have any required positional args after
-      non-required positional args.
-    */
-    void validate()
-    {
-        bool opt = false;
-        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
-        {
-            Arg *arg = ai->get();
-            if (arg->positional() == Arg::PosType::Optional)
-                opt = true;
-            if (opt && (arg->positional() == Arg::PosType::Required))
-            {
-                std::ostringstream oss;
-                oss << "Found required positional argument '" <<
-                    arg->longname() << "' after optional positional argument.";
-                throw arg_error(oss.str());
-            }
-        }
-    }
-
-    std::vector<std::unique_ptr<Arg>> m_args;  /// Storage for arguments
-    std::map<std::string, Arg *> m_shortargs;  /// Map from shortname to args
-    std::map<std::string, Arg *> m_longargs;  /// Map from longname to args
-};
-
-} // namespace pdal
-
diff --git a/include/pdal/util/Utils.hpp b/include/pdal/util/Utils.hpp
deleted file mode 100644
index 9bce5f4..0000000
--- a/include/pdal/util/Utils.hpp
+++ /dev/null
@@ -1,905 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <algorithm>
-#include <cassert>
-#include <cctype>
-#include <cmath>
-#include <cstdint>
-#include <cstring>
-#include <fstream>
-#include <iomanip>
-#include <istream>
-#include <limits>
-#include <map>
-#include <sstream>
-#include <stdexcept>
-#include <string>
-#include <typeinfo>
-#include <type_traits>
-#include <vector>
-
-#include "pdal_util_export.hpp"
-
-namespace pdal
-{
-
-namespace Utils
-{
-    /**
-      Set a seed for random number generation.
-
-      \param seed  Seed value.
-    */
-    PDAL_DLL void random_seed(unsigned int seed);
-
-    /**
-      Generate a random value in the range [minimum, maximum].
-
-      \param minimum  Lower value of range for random number generation.
-      \param maximum  Upper value of range for random number generation.
-    */
-    PDAL_DLL double random(double minimum, double maximum);
-
-    /**
-      Generate values in a uniform distribution in the range [minimum, maximum]
-      using the provided seed value.
-
-      \param double  Lower value of range for random number generation.
-      \param double  Upper value of range for random number generation.
-      \param seed    Seed value for random number generation.
-    */
-    PDAL_DLL double uniform(const double& minimum, const double& maximum,
-        uint32_t seed);
-    /**
-      Generate values in a normal distribution in the range [minimum, maximum]
-      using the provided seed value.
-
-      \param double  Lower value of range for random number generation.
-      \param double  Upper value of range for random number generation.
-      \param seed    Seed value for random number generation.
-    */
-    PDAL_DLL double normal(const double& mean, const double& sigma,
-        uint32_t seed);
-
-    /**
-      Determine if two values are within a particular range of each other.
-
-      \param v1  First value to compare.
-      \param v2  Second value to compare.
-      \param tolerance  Maximum difference between \ref v1 and \ref v2
-    */
-    PDAL_DLL inline bool compare_approx(double v1, double v2, double tolerance)
-    {
-        double diff = std::abs(v1 - v2);
-        return diff <= std::abs(tolerance);
-    }
-
-    /**
-      Round double value to nearest integral value.
-
-      \param r  Value to round
-      \return  Rounded value
-    */
-    inline double sround(double r)
-        { return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5); }
-
-    /**
-      Convert a string to lowercase.
-
-      \return  Converted string.
-    **/
-    inline std::string tolower(const std::string& s)
-    {
-        std::string out;
-        for (size_t i = 0; i < s.size(); ++i)
-            out += (char)std::tolower(s[i]);
-        return out;
-    }
-
-    /**
-      Convert a string to uppercase.
-
-      \return  Converted string.
-    */
-    inline std::string toupper(const std::string& s)
-    {
-        std::string out;
-        for (size_t i = 0; i < s.size(); ++i)
-            out += (char)std::toupper(s[i]);
-        return out;
-    }
-
-    /**
-      Compare strings in a case-insensitive manner.
-
-      \param s  First string to compare.
-      \param s2  Second string to compare.
-      \return  Whether the strings are equal.
-    */
-    inline bool iequals(const std::string& s, const std::string& s2)
-    {
-        if (s.length() != s2.length())
-            return false;
-        for (size_t i = 0; i < s.length(); ++i)
-            if (std::toupper(s[i]) != std::toupper(s2[i]))
-                return false;
-        return true;
-    }
-
-    /**
-      Determine if a string starts with a particular prefix.
-
-      \param s  String to check for prefix.
-      \param prefix  Prefix to search for.
-      \return  Whether the string begins with the prefix.
-    */
-    inline bool startsWith(const std::string& s, const std::string& prefix)
-    {
-        if (prefix.size() > s.size())
-            return false;
-        return (strncmp(prefix.data(), s.data(), prefix.size()) == 0);
-    }
-
-    /**
-      Generate a checksum that is the integer sum of the values of the bytes
-      in a buffer.
-
-      \param buf  Pointer to buffer.
-      \param size  Size of buffer.
-      \return  Generated checksum.
-    */
-    inline int cksum(char *buf, size_t size)
-    {
-        int i = 0;
-        while (size--)
-            i += *buf++;
-        return i;
-    }
-
-    /**
-      Fetch the value of an environment variable.
-
-      \param name  Name of environment varaible.
-      \param name  Value of the environemnt variable if it exists, empty
-        otherwise.
-      \return  0 on success, -1 on failure
-    */
-    PDAL_DLL int getenv(std::string const& name, std::string& val);
-
-    /**
-      Set the value of an environment variable.
-
-      \param env  Name of environment variable.
-      \param val  Value of environment variable.
-      \return  0 on success, -1 on failure
-    */
-    PDAL_DLL int setenv(const std::string& env, const std::string& val);
-
-    /**
-      Clear the value of an environment variable.
-
-      \param env  Name of the environment variable to clear.
-      \return  0 on success, -1 on failure
-    */
-    PDAL_DLL int unsetenv(const std::string& env);
-
-    /**
-      Skip stream input until a non-space character is found.
-
-      \param s  Stream to process.
-    */
-    PDAL_DLL void eatwhitespace(std::istream& s);
-
-    /**
-      Remove whitspace from the beginning of a string.
-
-      \param s  String to be trimmed.
-    */
-    PDAL_DLL void trimLeading(std::string& s);
-
-    /**
-      Remove whitspace from the end of a string.
-
-      \param s  String to be trimmed.
-    */
-    PDAL_DLL void trimTrailing(std::string& s);
-
-    /**
-      Remove whitespace from the beginning and end of a string.
-
-      \param s  String to be trimmed.
-    */
-    inline void trim(std::string& s)
-    {
-        trimLeading(s);
-        trimTrailing(s);
-    }
-
-    /**
-      If specified character is at the current stream position, advance the
-      stream position by 1.
-
-      \param s  Stream to insect.
-      \param x  Character to check for.
-      \return \c true if the character is at the current stream position,
-        \c false otherwise.
-    */
-    PDAL_DLL bool eatcharacter(std::istream& s, char x);
-
-    /**
-      Convert a buffer to a string using base64 encoding.
-
-      \param buf  Pointer to buffer to encode.
-      \param size  Size of buffer.
-      \return  Encoded buffer.
-    */
-    PDAL_DLL std::string base64_encode(const unsigned char *buf, size_t size);
-
-    /**
-      Convert a buffer to a string using base64 encoding.
-
-      \param bytes  Pointer to buffer to encode.
-      \return  Encoded buffer.
-    */
-    inline std::string base64_encode(std::vector<uint8_t> const& bytes)
-        { return base64_encode(bytes.data(), bytes.size()); }
-
-    /**
-      Decode a base64-encoded string into a buffer.
-
-      \param input  String to decode.
-      \return  Buffer containing decoded string.
-    */
-    PDAL_DLL std::vector<uint8_t>
-    base64_decode(std::string const& input);
-
-    /**
-      Start a process to run a command and open a pipe to which input can
-      be written and from which output can be read.
-
-      \param command  Command to run in subprocess.
-      \mode  Either 'r', 'w' or 'r+' to specify if the pipe should be opened
-        as read-only, write-only or read-write.
-      \return  Pointer to FILE for input/output from the subprocess.
-    */
-    PDAL_DLL FILE* portable_popen(const std::string& command,
-        const std::string& mode);
-    /**
-      Close file opened with \ref portable_popen.
-
-      \param fp  Pointer to file to close.
-      \return  0 on success, -1 on failure.
-    */
-    PDAL_DLL int portable_pclose(FILE* fp);
-
-    /**
-      Create a subprocess and set the standard output of the command into the
-      provided output string.
-
-      \param cmd  Command to run.
-      \param output  String to which output from the command should be written,
-    */
-    PDAL_DLL int run_shell_command(const std::string& cmd, std::string& output);
-
-    /**
-      Replace all instances of one string found in the input with another value.
-
-      \param input  Input string to modify.
-      \param replaceWhat  Token to locate in input string.
-      \param replaceWithWhat  Replacement for found tokens.
-      \return  Modified version of input string.
-    */
-    PDAL_DLL std::string replaceAll(std::string input,
-        const std::string& replaceWhat , const std::string& replaceWithWhat);
-
-    /**
-      Break a string into a list of strings to not exceed a specified length.
-      Whitespace is condensed to a single space and each string is free of
-      whitespace at the beginning and end when possible.  Optionally, a line
-      length for the first line can be different from subsequent lines.
-
-      \param inputString  String to split into substrings.
-      \param lineLength  Maximum length of substrings.
-      \param firstLength  When non-zero, the maximum length of the first
-        substring.  When zero, the first firstLength is assigned the value
-        provided in lineLength.
-      \return  List of substrings generated from the input string.
-    */
-    PDAL_DLL std::vector<std::string> wordWrap(std::string const& inputString,
-        size_t lineLength, size_t firstLength = 0);
-
-    /**
-      Break a string into a list of strings to not exceed a specified length.
-      The concatanation of the returned substrings will yield the original
-      string.  The algorithm attempts to break the original string such that
-      each substring begins with a word.
-
-      \param inputString  String to split into substrings.
-      \param lineLength  Maximum length of substrings.
-      \param firstLength  When non-zero, the maximum length of the first
-        substring.  When zero, the first firstLength is assigned the value
-        provided in lineLength.
-      \return  List of substrings generated from the input string.
-    */
-    PDAL_DLL std::vector<std::string> wordWrap2(std::string const& inputString,
-        size_t lineLength, size_t firstLength = 0);
-
-    /**
-      Add escape characters or otherwise transform an input string so as to
-      be a valid JSON string.
-
-      \param s  Input string.
-      \return  Valid JSON version of input string.
-    */
-    PDAL_DLL std::string escapeJSON(const std::string &s);
-
-    /**
-      Demangle a C++ symbol into readable form.
-
-      \param s  String to demangle.
-      \return  Demangled symbol.
-    */
-    PDAL_DLL std::string demangle(const std::string& s);
-
-    /**
-      Return the screen width of an associated tty.
-
-      \return  The tty screen width or 80 if on Windows or it can't be
-        determined.
-    */
-    PDAL_DLL int screenWidth();
-
-    /**
-      Escape non-printing characters by using standard notation (i.e. \n)
-      or hex notation (\x10) as as necessary.
-
-      \param s  String to modify.
-      \return  Copy of input string with non-printing characters converted
-        to printable notation.
-    */
-    PDAL_DLL std::string escapeNonprinting(const std::string& s);
-
-    /**
-      Normalize longitude so that it's between (-180, 180].
-
-      \param longitude  Longitude to normalize.
-      \return  Normalized longitude.
-    */
-    PDAL_DLL double normalizeLongitude(double longitude);
-
-    /**
-      Convert an input buffer to a hexadecimal string representation similar
-      to the output of the UNIX command 'od'.  This is mostly used as an
-      occasional debugging aid.
-
-      \param buf  Point to buffer to dump.
-      \param count  Size of buffer.
-      \return  Buffer converted to hex string.
-    */
-    PDAL_DLL std::string hexDump(const char *buf, size_t count);
-
-    /**
-      Count the number of characters in a string that meet a predicate.
-
-      \param s  String in which to start counting characters.
-      \param p  Position in input string at which to start counting.
-      \param pred  Unary predicte that tests a character.
-      \return  Then number of characters matching the predicate.
-    */
-    template<typename PREDICATE>
-    PDAL_DLL std::string::size_type
-    extract(const std::string& s, std::string::size_type p, PREDICATE pred)
-    {
-        std::string::size_type count = 0;
-        while (pred(s[p++]))
-            count++;
-        return count;
-    }
-
-    /**
-      Split a string into substrings based on a predicate.  Characters
-      matching the predicate are discarded.
-
-      \param s  String to split.
-      \param p  Unary predicate that returns true to indicate that a character
-        is a split location.
-      \return  Substrings.
-    */
-    template<typename PREDICATE>
-    PDAL_DLL std::vector<std::string> split(const std::string& s, PREDICATE p)
-    {
-        std::vector<std::string> result;
-
-        if (s.empty())
-            return result;
-
-        auto it = s.cbegin();
-        auto const end = s.cend();
-        decltype(it) nextIt;
-        do
-        {
-            nextIt = std::find_if(it, end, p);
-            result.push_back(std::string(it, nextIt));
-
-            // Avoid advancing the iterator past the end to avoid UB.
-            if (nextIt != end)
-                it = nextIt + 1;
-        } while (nextIt != end);
-
-        return result;
-    }
-
-    /**
-      Split a string into substrings.  Characters matching the predicate are
-      discarded, as are empty strings otherwise produced by \ref split().
-
-      \param s  String to split.
-      \param p  Predicate returns true if a char in a string is a split
-        location.
-      \return  Vector of substrings.
-    */
-    template<typename PREDICATE>
-    PDAL_DLL std::vector<std::string> split2(const std::string& s, PREDICATE p)
-    {
-        std::vector<std::string> result;
-
-        if (s.empty())
-            return result;
-
-        auto it = s.cbegin();
-        auto const end = s.cend();
-        decltype(it) nextIt;
-        do
-        {
-            nextIt = std::find_if(it, end, p);
-            if (it != nextIt)
-                result.push_back(std::string(it, nextIt));
-
-            // Avoid advancing the iterator past the end to avoid UB.
-            if (nextIt != end)
-                it = nextIt + 1;
-        } while (nextIt != end);
-
-        return result;
-    }
-
-    /**
-      Split a string into substrings based a splitting character.  The
-      splitting characters are discarded.
-
-      \param s  String to split.
-      \param p  Character indicating split positions.
-      \return  Substrings.
-    */
-    inline PDAL_DLL std::vector<std::string>
-    split(const std::string& s, char tChar)
-    {
-        auto pred = [tChar](char c){ return(c == tChar); };
-        return split(s, pred);
-    }
-
-    /**
-      Split a string into substrings based a splitting character.  The
-      splitting characters are discarded as are empty strings otherwise
-      produced by \ref split().
-
-      \param s  String to split.
-      \param p  Character indicating split positions.
-      \return  Substrings.
-    */
-    inline PDAL_DLL std::vector<std::string>
-    split2(const std::string& s, char tChar)
-    {
-        auto pred = [tChar](char c){ return(c == tChar); };
-        return split2(s, pred);
-    }
-
-    /**
-      Return a string representation of a type specified by the template
-      argument.
-
-      \return  String representation of the type.
-    */
-    template<typename T>
-    std::string typeidName()
-        { return Utils::demangle(typeid(T).name()); }
-
-    struct RedirectStream
-    {
-        RedirectStream() : m_out(NULL), m_out2(NULL), m_buf(NULL)
-        {}
-
-        std::ofstream *m_out;
-        std::ostream *m_out2;
-        std::streambuf *m_buf;
-    };
-
-    /**
-      Redirect a stream to some other stream.
-
-      \param out   Stream to redirect.
-      \param dst   Destination stream.
-      \return  Context for stream restoration (see \ref restore()).
-    */
-    inline RedirectStream redirect(std::ostream& out, std::ostream& dst)
-    {
-        RedirectStream redir;
-
-        redir.m_out2 = &dst;
-        redir.m_buf = out.rdbuf();
-        out.rdbuf(redir.m_out2->rdbuf());
-        return redir;
-    }
-
-    /**
-      Redirect a stream to some file, by default /dev/null.
-
-      \param out   Stream to redirect.
-      \param file  Name of file where stream should be redirected.
-      \return  Context for stream restoration (see \ref restore()).
-    */
-    inline RedirectStream redirect(std::ostream& out,
-        const std::string& file = "/dev/null")
-    {
-        RedirectStream redir;
-
-        redir.m_out = new std::ofstream(file);
-        redir.m_buf = out.rdbuf();
-        out.rdbuf(redir.m_out->rdbuf());
-        return redir;
-    }
-
-    /**
-      Restore a stream redirected with redirect().
-
-      \param out  Stream to be restored.
-      \param redir RedirectStream returned from corresponding
-        \ref redirect() call.
-    */
-    inline void restore(std::ostream& out, RedirectStream redir)
-    {
-        out.rdbuf(redir.m_buf);
-        if (redir.m_out)
-            redir.m_out->close();
-        redir.m_out = NULL;
-        redir.m_out2 = NULL;
-        redir.m_buf = NULL;
-    }
-
-    //ABELL - This is certainly not as efficient as boost::numeric_cast, but
-    //  has the advantage of not requiring an exception to indicate an error.
-    //  We should investigate incorporating a version of boost::numeric_cast
-    //  that avoids the exception for an error.
-    /**
-      Determine whether a double value may be safely converted to the given
-      output type without over/underflow.  If the output type is integral the
-      input will be rounded before being tested.
-
-      \param in  Value to range test.
-      \return  Whether value can be safely converted to template type.
-    */
-    template<typename T_OUT>
-    bool inRange(double in)
-    {
-        if (std::is_integral<T_OUT>::value)
-        {
-            in = sround((double)in);
-        }
-
-        return std::is_same<double, T_OUT>::value ||
-           (in >= static_cast<double>(std::numeric_limits<T_OUT>::lowest()) &&
-            in <= static_cast<double>(std::numeric_limits<T_OUT>::max()));
-    }
-
-    /**
-      Determine whether a value may be safely converted to the given
-      output type without over/underflow.  If the output type is integral and
-      different from the input time, the value will be rounded before being
-      tested.
-
-      \param in  Value to range test.
-      \return  Whether value can be safely converted to template type.
-    */
-    template<typename T_IN, typename T_OUT>
-    bool inRange(T_IN in)
-    {
-        return std::is_same<T_IN, T_OUT>::value ||
-            inRange<T_OUT>(static_cast<double>(in));
-    }
-
-    /**
-      Convert a numeric value from one type to another.  Floating point
-      values are rounded to the nearest integer before a conversion is
-      attempted.
-
-      \param in  Value to convert.
-      \param out  Converted value.
-      \return  \c true if the conversion was successful, \c false if the
-        datatypes/input value don't allow conversion.
-    */
-    template<typename T_IN, typename T_OUT>
-    bool numericCast(T_IN in, T_OUT& out)
-    {
-        if (std::is_same<T_IN, T_OUT>::value)
-        {
-            out = static_cast<T_OUT>(in);
-            return true;
-        }
-        if (std::is_integral<T_OUT>::value)
-            in = static_cast<T_IN>(sround((double)in));
-        if ((std::is_same<T_OUT, double>::value) ||
-            (in <= static_cast<double>(std::numeric_limits<T_OUT>::max()) &&
-             in >= static_cast<double>(std::numeric_limits<T_OUT>::lowest())))
-        {
-            out = static_cast<T_OUT>(in);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-      Convert a value to its string representation by writing to a stringstream.
-
-      \param from  Value to convert.
-      \return  String representation.
-    */
-    template<typename T>
-    std::string toString(const T& from)
-    {
-        std::ostringstream oss;
-        oss << from;
-        return oss.str();
-    }
-
-    /**
-      Convert a bool to a string.
-    */
-    inline std::string toString(bool from)
-    {
-        return from ? "true" : "false";
-    }
-
-    /**
-      Convert a double to string with a precision of 10 decimal places.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(double from)
-    {
-        std::ostringstream oss;
-        oss << std::setprecision(10) << from;
-        return oss.str();
-    }
-
-    /**
-      Convert a float to string with a precision of 10 decimal places.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(float from)
-    {
-        std::ostringstream oss;
-        oss << std::setprecision(8) << from;
-        return oss.str();
-    }
-
-    /**
-      Convert a long long int to string.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(long long from)
-        { return std::to_string(from); }
-
-    /**
-      Convert an unsigned long long int to string.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(unsigned long from)
-        { return std::to_string(from); }
-
-    /**
-      Convert a long int to string.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(long from)
-        { return std::to_string(from); }
-
-    /**
-      Convert an unsigned int to string.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(unsigned int from)
-        { return std::to_string(from); }
-
-    /**
-      Convert an int to string.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(int from)
-        { return std::to_string(from); }
-
-    /**
-      Convert an unsigned short to string.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(unsigned short from)
-        { return std::to_string((int)from); }
-
-    /**
-      Convert a short int to string.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(short from)
-        { return std::to_string((int)from); }
-
-    /**
-      Convert a char (treated as numeric) to string.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(char from)
-        { return std::to_string((int)from); }
-
-    /**
-      Convert an unsigned char (treated as numeric) to string.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(unsigned char from)
-        { return std::to_string((int)from); }
-
-    /**
-      Convert a signed char (treated as numeric) to string.
-
-      \param from  Value to convert.
-      \return  String representation of numeric value.
-    */
-    inline std::string toString(signed char from)
-        { return std::to_string((int)from); }
-
-    /**
-      Convert a string to a value by reading from a string stream.
-
-      \param from  String to convert.
-      \param to  Converted value.
-      \return  \c true if the conversion was successful, \c false otherwise.
-    */
-    template<typename T>
-    bool fromString(const std::string& from, T& to)
-    {
-        std::istringstream iss(from);
-
-        iss >> to;
-        return !iss.fail();
-    }
-
-    // Optimization of above.
-    template<>
-    inline bool fromString(const std::string& from, std::string& to)
-    {
-        to = from;
-        return true;
-    }
-
-    /**
-      Convert a numeric string to a char numeric value.
-
-      \parm s  String to convert.
-      \param to  Converted numeric value.
-      \return  \c true if the conversion was successful, \c false otherwise.
-    */
-    template<>
-    inline bool fromString<char>(const std::string& s, char& to)
-    {
-        int i = std::stoi(s);
-        if (i >= std::numeric_limits<char>::lowest() &&
-            i <= std::numeric_limits<char>::max())
-        {
-            to = static_cast<char>(i);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-      Convert a numeric string to an unsigned char numeric value.
-
-      \parm s  String to convert.
-      \param to  Converted numeric value.
-      \return  \c true if the conversion was successful, \c false otherwise.
-    */
-    template<>
-    inline bool fromString<unsigned char>(const std::string& s,
-        unsigned char& to)
-    {
-        int i = std::stoi(s);
-        if (i >= std::numeric_limits<unsigned char>::lowest() &&
-            i <= std::numeric_limits<unsigned char>::max())
-        {
-            to = static_cast<unsigned char>(i);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-      Convert a numeric string to a signed char numeric value.
-
-      \parm s  String to convert.
-      \param to  Converted numeric value.
-      \return  \c true if the conversion was successful, \c false otherwise.
-    */
-    template<>
-    inline bool fromString<signed char>(const std::string& s, signed char& to)
-    {
-        int i = std::stoi(s);
-        if (i >= std::numeric_limits<signed char>::lowest() &&
-            i <= std::numeric_limits<signed char>::max())
-        {
-            to = static_cast<signed char>(i);
-            return true;
-        }
-        return false;
-    }
-
-    template<typename E>
-    constexpr typename std::underlying_type<E>::type toNative(E e)
-    {
-        return static_cast<typename std::underlying_type<E>::type>(e);
-    }
-
-} // namespace Utils
-} // namespace pdal
-
diff --git a/io/bpf/BpfCompressor.cpp b/io/BpfCompressor.cpp
similarity index 100%
rename from io/bpf/BpfCompressor.cpp
rename to io/BpfCompressor.cpp
diff --git a/io/bpf/BpfCompressor.hpp b/io/BpfCompressor.hpp
similarity index 100%
rename from io/bpf/BpfCompressor.hpp
rename to io/BpfCompressor.hpp
diff --git a/io/bpf/BpfHeader.cpp b/io/BpfHeader.cpp
similarity index 100%
rename from io/bpf/BpfHeader.cpp
rename to io/BpfHeader.cpp
diff --git a/io/BpfHeader.hpp b/io/BpfHeader.hpp
new file mode 100644
index 0000000..6c9c289
--- /dev/null
+++ b/io/BpfHeader.hpp
@@ -0,0 +1,261 @@
+/******************************************************************************
+* Copyright (c) 2014, Howard Butler, hobu.inc at gmail.com
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include <pdal/Dimension.hpp>
+#include <pdal/Log.hpp>
+#include <pdal/Metadata.hpp>
+
+namespace pdal
+{
+
+class ILeStream;
+class OLeStream;
+
+struct BpfMuellerMatrix
+{
+    BpfMuellerMatrix()
+    {
+        static const double vals[] = {1.0, 0.0, 0.0, 0.0,
+                         0.0, 1.0, 0.0, 0.0,
+                         0.0, 0.0, 1.0, 0.0,
+                         0.0, 0.0, 0.0, 1.0};
+        memcpy(m_vals, vals, sizeof(vals));
+    }
+
+    double m_vals[16];
+
+    void dump()
+    {
+        for (size_t i = 0; i < 4; ++i)
+            std::cerr << m_vals[i] << '\t';
+        std::cerr << "\n";
+        for (size_t i = 4; i < 8; ++i)
+            std::cerr << m_vals[i] << '\t';
+        std::cerr << "\n";
+        for (size_t i = 8; i < 12; ++i)
+            std::cerr << m_vals[i] << '\t';
+        std::cerr << "\n";
+        for (size_t i = 12; i < 16; ++i)
+            std::cerr << m_vals[i] << '\t';
+        std::cerr << "\n\n";
+
+    }
+
+    void apply(double& x, double& y, double& z)
+    {
+        double w = x * m_vals[12] + y * m_vals[13] + z * m_vals[14] +
+            m_vals[15];
+
+        x = (x * m_vals[0] + y * m_vals[1] + z * m_vals[2] + m_vals[3]) / w;
+        y = (x * m_vals[4] + y * m_vals[5] + z * m_vals[6] + m_vals[7]) / w;
+        z = (x * m_vals[8] + y * m_vals[9] + z * m_vals[10] + m_vals[11]) / w;
+    }
+};
+ILeStream& operator >> (ILeStream& stream, BpfMuellerMatrix& m);
+OLeStream& operator << (OLeStream& stream, BpfMuellerMatrix& m);
+
+enum class BpfFormat
+{
+    DimMajor,
+    PointMajor,
+    ByteMajor
+};
+
+std::istream& operator >> (std::istream& in, BpfFormat& format);
+std::ostream& operator << (std::ostream& in, const BpfFormat& format);
+
+enum class BpfCoordType
+{
+    Cartesian,
+    UTM,
+    TCR,
+    ENU
+};
+
+enum class BpfCompression
+{
+    None,
+    QuickLZ,
+    FastLZ,
+    Zlib
+};
+
+struct BpfDimension
+{
+    BpfDimension() : m_offset(0.0),
+        m_min((std::numeric_limits<double>::max)()),
+        m_max(std::numeric_limits<double>::lowest()),
+        m_id(Dimension::Id::Unknown)
+    {}
+
+    double m_offset;
+    double m_min;
+    double m_max;
+    std::string m_label;
+    Dimension::Id m_id;
+
+    static bool read(ILeStream& stream, std::vector<BpfDimension>& dims,
+        size_t start);
+    static bool write(OLeStream& stream, std::vector<BpfDimension>& dims);
+};
+typedef std::vector<BpfDimension> BpfDimensionList;
+
+struct BpfHeader
+{
+    BpfHeader() : m_version(0), m_len(176), m_numDim(0),
+        m_compression(Utils::toNative(BpfCompression::None)), m_numPts(0),
+        m_coordType(Utils::toNative(BpfCoordType::Cartesian)), m_coordId(0),
+        m_spacing(0.0), m_startTime(0.0), m_endTime(0.0)
+    {}
+
+    int32_t m_version;
+    std::string m_ver;
+    int32_t m_len;
+    int32_t m_numDim;
+    BpfFormat m_pointFormat;
+    uint8_t m_compression;
+    int32_t m_numPts;
+    int32_t m_coordType;
+    int32_t m_coordId;
+    float m_spacing;
+    BpfMuellerMatrix m_xform;
+    double m_startTime;
+    double m_endTime;
+    std::vector<BpfDimension> m_staticDims;
+    LogPtr m_log;
+
+    PDAL_DLL void setLog(const LogPtr& log)
+         { m_log = log; }
+    PDAL_DLL bool read(ILeStream& stream);
+    bool write(OLeStream& stream);
+    bool readV3(ILeStream& stream);
+    bool readV1(ILeStream& stream);
+    PDAL_DLL bool readDimensions(ILeStream& stream,
+        std::vector<BpfDimension>& dims);
+    void writeDimensions(OLeStream& stream, std::vector<BpfDimension>& dims);
+    void dump();
+};
+
+struct BpfUlemHeader
+{
+    uint32_t m_numFrames;
+    uint16_t m_year;
+    uint8_t m_month;
+    uint8_t m_day;
+    uint16_t m_lidarMode;
+    uint16_t m_wavelen;  // In nm.
+    uint16_t m_pulseFreq;  // In Hz.
+    uint16_t m_focalWidth;
+    uint16_t m_focalHeight;
+    float m_pixelPitchWidth;
+    float m_pixelPitchHeight;
+    std::string m_classCode;
+
+    bool read(ILeStream& stream);
+};
+
+struct BpfUlemFrame
+{
+    int32_t m_num;
+    double m_roll; //x
+    double m_pitch; //y
+    double m_heading; //z
+    BpfMuellerMatrix m_xform;
+    int16_t m_shortEncoder;
+    int16_t m_longEncoder;
+
+    bool read(ILeStream& stream);
+};
+
+struct BpfUlemFile
+{
+    uint32_t m_len;
+    std::string m_filename;
+    std::vector<char> m_buf;
+    std::string m_filespec;
+
+    BpfUlemFile() : m_len(0)
+    {}
+
+    BpfUlemFile(uint32_t len, const std::string& filename,
+            const std::string& filespec) :
+        m_len(len), m_filename(filename), m_filespec(filespec)
+    {}
+
+    bool read(ILeStream& stream);
+    bool write(OLeStream& stream);
+};
+
+struct BpfPolarStokesParam
+{
+    float m_x;
+    float m_y;
+    float m_z;
+    float m_a;
+
+    bool read(ILeStream& stream);
+};
+
+struct BpfPolarHeader
+{
+    uint32_t m_numFrames;
+    uint16_t m_fpaId;
+    uint32_t m_numXmit;
+    uint32_t m_numRcv;
+    std::vector<BpfPolarStokesParam> m_xmitStates;
+    std::vector<BpfMuellerMatrix> m_psaSettings;
+
+    bool read(ILeStream& stream);
+};
+
+struct BpfPolarFrame
+{
+public:
+    uint32_t m_num;
+    int16_t m_stokesIdx;
+    float m_stokesParam[4];
+    float m_stokesOutParam[4];
+    BpfMuellerMatrix m_xform;
+    int16_t m_truncation;
+
+    bool read(ILeStream& stream);
+};
+
+} //namespace pdal
diff --git a/io/BpfReader.cpp b/io/BpfReader.cpp
new file mode 100644
index 0000000..4dbb492
--- /dev/null
+++ b/io/BpfReader.cpp
@@ -0,0 +1,648 @@
+/******************************************************************************
+* Copyright (c) 2014, Andrew Bell
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "BpfReader.hpp"
+
+#include <climits>
+
+#include <zlib.h>
+
+#include <pdal/Options.hpp>
+#include <pdal/pdal_export.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "readers.bpf",
+    "\"Binary Point Format\" (BPF) reader support. BPF is a simple \n" \
+        "DoD and research format that is used by some sensor and \n" \
+        "processing chains.",
+    "http://pdal.io/stages/readers.bpf.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, BpfReader, Reader, s_info)
+
+std::string BpfReader::getName() const { return s_info.name; }
+
+QuickInfo BpfReader::inspect()
+{
+    QuickInfo qi;
+
+    initialize();
+    qi.m_valid = true;
+    qi.m_pointCount = m_header.m_numPts;
+    qi.m_srs = getSpatialReference();
+    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
+    {
+        BpfDimension& dim = *di;
+        qi.m_dimNames.push_back(dim.m_label);
+        if (dim.m_label == "X")
+        {
+            qi.m_bounds.minx = dim.m_min;
+            qi.m_bounds.maxx = dim.m_max;
+        }
+        if (dim.m_label == "Y")
+        {
+            qi.m_bounds.miny = dim.m_min;
+            qi.m_bounds.maxy = dim.m_max;
+        }
+        if (dim.m_label == "Z")
+        {
+            qi.m_bounds.minz = dim.m_min;
+            qi.m_bounds.maxz = dim.m_max;
+        }
+    }
+    return qi;
+}
+
+
+// When the stage is intialized, the schema needs to be populated with the
+// dimensions in order to allow subsequent stages to be aware of or append to
+// the dimensions in the PointView.
+void BpfReader::initialize()
+{
+    if (m_filename.empty())
+        throw pdal_error("Can't read BPF file without filename.");
+
+    // Logfile doesn't get set until options are processed.
+    m_header.setLog(log());
+
+    m_stream.open(m_filename);
+
+    // Resets the stream position in case it was already open.
+    m_stream.seek(0);
+    // In order to know the dimensions we must read the file header.
+    if (!m_header.read(m_stream))
+        return;
+
+    if (!m_header.readDimensions(m_stream, m_dims))
+        return;
+
+    std::string code("");
+    if (m_header.m_coordType == static_cast<int>(BpfCoordType::Cartesian))
+       code = std::string("EPSG:4326");
+    else if (m_header.m_coordType == static_cast<int>(BpfCoordType::UTM))
+    {
+       uint32_t zone(abs(m_header.m_coordId));
+
+       if (m_header.m_coordId > 0 && m_header.m_coordId <= 60)
+          code = std::string("EPSG:326") + (zone < 10 ? "0" : "") + Utils::toString(zone);
+       else if (m_header.m_coordId < 0 && m_header.m_coordId >= -60)
+          code = std::string("EPSG:327") + (zone < 10 ? "0" : "") + Utils::toString(zone);
+       else
+          throw pdal_error("BPF file contains an invalid UTM zone");
+    }
+    else
+    {
+       //BPF supports something called Terrestrial Centered Rotational (BpfCoordType::TCR) and East North Up (BpfCoordType::ENU)
+       //which we can figure out when we run into a file with these coordinate systems.
+       throw pdal_error("BPF file contains unsupported coordinate system");
+    }
+    SpatialReference srs(code);
+    setSpatialReference(srs);
+
+    try
+    {
+        SpatialReference srs(code);
+        setSpatialReference(srs);
+    }
+    catch (...)
+    {
+        log()->get(LogLevel::Error) << "Could not create an SRS" << std::endl;
+    }
+
+    if (m_header.m_version >= 3)
+    {
+        readUlemData();
+        if (!m_stream)
+            return;
+        readUlemFiles();
+        if (!m_stream)
+            return;
+        readPolarData();
+    }
+
+    // Read thing after the standard header as metadata->
+    readHeaderExtraData();
+
+    // Fast forward file to end of header as reported by base header.
+    std::streampos pos = m_stream.position();
+    if (pos > m_header.m_len)
+    {
+        std::ostringstream oss;
+        oss << getName() << ": BPF Header length exceeded that reported by "
+            "file.";
+        throw pdal_error(oss.str());
+    }
+    m_stream.close();
+}
+
+
+void BpfReader::addDimensions(PointLayoutPtr layout)
+{
+    for (size_t i = 0; i < m_dims.size(); ++i)
+    {
+        Dimension::Type type = Dimension::Type::Float;
+
+        BpfDimension& dim = m_dims[i];
+        if (dim.m_label == "X" ||
+            dim.m_label == "Y" ||
+            dim.m_label == "Z")
+            type = Dimension::Type::Double;
+        dim.m_id = layout->registerOrAssignDim(dim.m_label, type);
+    }
+}
+
+
+bool BpfReader::readUlemData()
+{
+    if (!m_ulemHeader.read(m_stream))
+        return false;
+
+    for (size_t i = 0; i < m_ulemHeader.m_numFrames; i++)
+    {
+        BpfUlemFrame frame;
+        if (!frame.read(m_stream))
+            return false;
+        m_ulemFrames.push_back(frame);
+    }
+    return (bool)m_stream;
+}
+
+
+bool BpfReader::readUlemFiles()
+{
+    BpfUlemFile file;
+    while (file.read(m_stream))
+    {
+        MetadataNode m = m_metadata.add("bundled_file");
+        m.addEncoded(file.m_filename,
+            (const unsigned char *)file.m_buf.data(), file.m_len);
+    }
+    return (bool)m_stream;
+}
+
+
+/// Encode all data that follows the headers as metadata->
+/// \return  Whether the stream is still valid.
+bool BpfReader::readHeaderExtraData()
+{
+    if (m_stream.position() < m_header.m_len)
+    {
+        std::streampos size = m_header.m_len - m_stream.position();
+        std::vector<uint8_t> buf(size);
+        m_stream.get(buf);
+        m_metadata.addEncoded("header_data", buf.data(), buf.size());
+    }
+    return (bool)m_stream;
+}
+
+
+bool BpfReader::readPolarData()
+{
+    if (!m_polarHeader.read(m_stream))
+        return false;
+    for (size_t i = 0; i < m_polarHeader.m_numFrames; ++i)
+    {
+        BpfPolarFrame frame;
+        if (!frame.read(m_stream))
+            return false;
+        m_polarFrames.push_back(frame);
+    }
+    return (bool)m_stream;
+}
+
+
+void BpfReader::ready(PointTableRef)
+{
+    m_stream.open(m_filename);
+    m_stream.seek(m_header.m_len);
+    m_index = 0;
+    m_start = m_stream.position();
+    if (m_header.m_compression)
+    {
+        m_deflateBuf.resize(numPoints() * m_dims.size() * sizeof(float));
+        size_t index = 0;
+        size_t bytesRead = 0;
+        do
+        {
+            bytesRead = readBlock(m_deflateBuf, index);
+            index += bytesRead;
+        } while (bytesRead > 0 && index < m_deflateBuf.size());
+        m_charbuf.initialize(m_deflateBuf.data(), m_deflateBuf.size(), m_start);
+        m_stream.pushStream(new std::istream(&m_charbuf));
+    }
+}
+
+
+void BpfReader::done(PointTableRef)
+{
+    if (auto s = m_stream.popStream())
+        delete s;
+    m_stream.close();
+}
+
+
+bool BpfReader::processOne(PointRef& point)
+{
+    switch (m_header.m_pointFormat)
+    {
+    case BpfFormat::PointMajor:
+        readPointMajor(point);
+        break;
+    case BpfFormat::DimMajor:
+        readDimMajor(point);
+        break;
+    case BpfFormat::ByteMajor:
+        readByteMajor(point);
+        break;
+    }
+    return !eof() && (m_index < m_count);
+}
+
+
+point_count_t BpfReader::read(PointViewPtr data, point_count_t count)
+{
+    switch (m_header.m_pointFormat)
+    {
+    case BpfFormat::PointMajor:
+        return readPointMajor(data, count);
+    case BpfFormat::DimMajor:
+        return readDimMajor(data, count);
+    case BpfFormat::ByteMajor:
+        return readByteMajor(data, count);
+    }
+    return 0;
+}
+
+
+size_t BpfReader::readBlock(std::vector<char>& outBuf, size_t index)
+{
+    uint32_t finalBytes;
+    uint32_t compressBytes;
+
+    m_stream >> finalBytes;
+    m_stream >> compressBytes;
+
+    std::vector<char> in(compressBytes);
+
+    // Fill the input bytes from the stream.
+    m_stream.get(in);
+    int ret = inflate(in.data(), compressBytes,
+        outBuf.data() + index, finalBytes);
+    return (ret ? 0 : finalBytes);
+}
+
+
+bool BpfReader::eof()
+{
+    return m_index >= numPoints();
+}
+
+
+void BpfReader::readPointMajor(PointRef& point)
+{
+    double x(0), y(0), z(0);
+
+    seekPointMajor(m_index);
+    for (size_t dim = 0; dim < m_dims.size(); ++dim)
+    {
+        float f;
+
+        m_stream >> f;
+        double d = f + m_dims[dim].m_offset;
+        if (m_dims[dim].m_id == Dimension::Id::X)
+            x = d;
+        else if (m_dims[dim].m_id == Dimension::Id::Y)
+            y = d;
+        else if (m_dims[dim].m_id == Dimension::Id::Z)
+            z = d;
+        else
+            point.setField(m_dims[dim].m_id, d);
+    }
+
+    m_header.m_xform.apply(x, y, z);
+    point.setField(Dimension::Id::X, x);
+    point.setField(Dimension::Id::Y, y);
+    point.setField(Dimension::Id::Z, z);
+    m_index++;
+}
+
+
+point_count_t BpfReader::readPointMajor(PointViewPtr view, point_count_t count)
+{
+    PointId nextId = view->size();
+    PointId idx = m_index;
+    point_count_t numRead = 0;
+    seekPointMajor(idx);
+    while (numRead < count && idx < numPoints())
+    {
+        for (size_t d = 0; d < m_dims.size(); ++d)
+        {
+            float f;
+
+            m_stream >> f;
+            view->setField(m_dims[d].m_id, nextId, f + m_dims[d].m_offset);
+        }
+
+        // Transformation only applies to X, Y and Z
+        double x = view->getFieldAs<double>(Dimension::Id::X, nextId);
+        double y = view->getFieldAs<double>(Dimension::Id::Y, nextId);
+        double z = view->getFieldAs<double>(Dimension::Id::Z, nextId);
+        m_header.m_xform.apply(x, y, z);
+        view->setField(Dimension::Id::X, nextId, x);
+        view->setField(Dimension::Id::Y, nextId, y);
+        view->setField(Dimension::Id::Z, nextId, z);
+        if (m_cb)
+            m_cb(*view, nextId);
+
+        idx++;
+        numRead++;
+        nextId++;
+    }
+    m_index = idx;
+    return numRead;
+}
+
+
+void BpfReader::readDimMajor(PointRef& point)
+{
+    if (m_streams.empty())
+    {
+        for (std::size_t dim(0); dim < m_dims.size(); ++dim)
+        {
+            std::streamoff offset = sizeof(float) * dim * numPoints();
+
+            m_streams.emplace_back(new ILeStream());
+            m_streams.back()->open(m_filename);
+
+            if (m_header.m_compression)
+            {
+                m_charbufs.emplace_back(new Charbuf());
+                m_charbufs.back()->initialize(
+                        m_deflateBuf.data(), m_deflateBuf.size(), m_start);
+
+                m_streams.back()->pushStream(
+                        new std::istream(m_charbufs.back().get()));
+            }
+
+            m_streams.back()->seek(m_start + offset);
+        }
+    }
+
+    double x(0), y(0), z(0);
+    float f(0);
+    double d(0);
+
+    for (size_t dim = 0; dim < m_dims.size(); ++dim)
+    {
+        *m_streams[dim] >> f;
+        d = f + m_dims[dim].m_offset;
+        if (m_dims[dim].m_id == Dimension::Id::X)
+            x = d;
+        else if (m_dims[dim].m_id == Dimension::Id::Y)
+            y = d;
+        else if (m_dims[dim].m_id == Dimension::Id::Z)
+            z = d;
+        else
+            point.setField(m_dims[dim].m_id, d);
+    }
+
+    // Transformation only applies to X, Y and Z
+    m_header.m_xform.apply(x, y, z);
+    point.setField(Dimension::Id::X, x);
+    point.setField(Dimension::Id::Y, y);
+    point.setField(Dimension::Id::Z, z);
+    m_index++;
+}
+
+
+point_count_t BpfReader::readDimMajor(PointViewPtr data, point_count_t count)
+{
+    PointId idx(0);
+    PointId startId = data->size();
+    point_count_t numRead = 0;
+    for (size_t d = 0; d < m_dims.size(); ++d)
+    {
+        idx = m_index;
+        PointId nextId = startId;
+        numRead = 0;
+        seekDimMajor(d, idx);
+        for (; numRead < count && idx < numPoints(); idx++, numRead++, nextId++)
+        {
+            float f;
+
+            m_stream >> f;
+            data->setField(m_dims[d].m_id, nextId, f + m_dims[d].m_offset);
+        }
+    }
+    m_index = idx;
+
+    // Transformation only applies to X, Y and Z
+    for (PointId idx = startId; idx < data->size(); idx++)
+    {
+        double x = data->getFieldAs<double>(Dimension::Id::X, idx);
+        double y = data->getFieldAs<double>(Dimension::Id::Y, idx);
+        double z = data->getFieldAs<double>(Dimension::Id::Z, idx);
+        m_header.m_xform.apply(x, y, z);
+        data->setField(Dimension::Id::X, idx, x);
+        data->setField(Dimension::Id::Y, idx, y);
+        data->setField(Dimension::Id::Z, idx, z);
+
+        if (m_cb)
+            m_cb(*data, idx);
+    }
+
+    return numRead;
+}
+
+
+void BpfReader::readByteMajor(PointRef& point)
+{
+    // We need a temp buffer for the point data
+    union uu
+    {
+        float f;
+        uint32_t u32;
+    } u;
+    double x(0), y(0), z(0);
+    uint8_t u8;
+
+    for (size_t dim = 0; dim < m_dims.size(); ++dim)
+    {
+        u.u32 = 0;
+        for (size_t b = 0; b < sizeof(float); ++b)
+        {
+            seekByteMajor(dim, b, m_index);
+
+            m_stream >> u8;
+            u.u32 |= ((uint32_t)u8 << (b * CHAR_BIT));
+        }
+        double d = u.f + m_dims[dim].m_offset;
+        if (m_dims[dim].m_id == Dimension::Id::X)
+            x = d;
+        else if (m_dims[dim].m_id == Dimension::Id::Y)
+            y = d;
+        else if (m_dims[dim].m_id == Dimension::Id::Z)
+            z = d;
+        else
+            point.setField(m_dims[dim].m_id, d);
+    }
+
+    m_header.m_xform.apply(x, y, z);
+    point.setField(Dimension::Id::X, x);
+    point.setField(Dimension::Id::Y, y);
+    point.setField(Dimension::Id::Z, z);
+    m_index++;
+}
+
+
+point_count_t BpfReader::readByteMajor(PointViewPtr data, point_count_t count)
+{
+    PointId idx(0);
+    PointId startId = data->size();
+    point_count_t numRead = 0;
+
+    // We need a temp buffer for the point data
+    union uu
+    {
+        float f;
+        uint32_t u32;
+    };
+    std::unique_ptr<union uu> uArr(
+        new uu[std::min(count, numPoints() - m_index)]);
+
+    for (size_t d = 0; d < m_dims.size(); ++d)
+    {
+        for (size_t b = 0; b < sizeof(float); ++b)
+        {
+            idx = m_index;
+            numRead = 0;
+            PointId nextId = startId;
+            seekByteMajor(d, b, idx);
+
+            for (;numRead < count && idx < numPoints();
+                idx++, numRead++, nextId++)
+            {
+                union uu& u = *(uArr.get() + numRead);
+
+                if (b == 0)
+                    u.u32 = 0;
+                uint8_t u8;
+                m_stream >> u8;
+                u.u32 |= ((uint32_t)u8 << (b * CHAR_BIT));
+                if (b == 3)
+                {
+                    u.f += m_dims[d].m_offset;
+                    data->setField(m_dims[d].m_id, nextId, u.f);
+                }
+            }
+        }
+    }
+    m_index = idx;
+
+    // Transformation only applies to X, Y and Z
+    for (PointId idx = startId; idx < data->size(); idx++)
+    {
+        double x = data->getFieldAs<double>(Dimension::Id::X, idx);
+        double y = data->getFieldAs<double>(Dimension::Id::Y, idx);
+        double z = data->getFieldAs<double>(Dimension::Id::Z, idx);
+        m_header.m_xform.apply(x, y, z);
+        data->setField(Dimension::Id::X, idx, x);
+        data->setField(Dimension::Id::Y, idx, y);
+        data->setField(Dimension::Id::Z, idx, z);
+
+        if (m_cb)
+            m_cb(*data, idx);
+    }
+
+    return numRead;
+}
+
+
+void BpfReader::seekPointMajor(PointId ptIdx)
+{
+    std::streamoff offset = ptIdx * sizeof(float) * m_dims.size();
+    m_stream.seek(m_start + offset);
+}
+
+
+void BpfReader::seekDimMajor(size_t dimIdx, PointId ptIdx)
+{
+    std::streamoff offset = ((sizeof(float) * dimIdx * numPoints()) +
+        (sizeof(float) * ptIdx));
+    m_stream.seek(m_start + offset);
+}
+
+
+void BpfReader::seekByteMajor(size_t dimIdx, size_t byteIdx, PointId ptIdx)
+{
+    std::streamoff offset =
+        (dimIdx * numPoints() * sizeof(float)) +
+        (byteIdx * numPoints()) +
+        ptIdx;
+    m_stream.seek(m_start + offset);
+}
+
+
+int BpfReader::inflate(char *buf, uint32_t insize,
+    char *outbuf, uint32_t outsize)
+{
+   if (insize == 0)
+        return 0;
+
+    int ret;
+    z_stream strm;
+
+    /* allocate inflate state */
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    strm.avail_in = 0;
+    strm.next_in = Z_NULL;
+    if (inflateInit(&strm) != Z_OK)
+        return -2;
+
+    strm.avail_in = insize;
+    strm.next_in = (unsigned char *)buf;
+    strm.avail_out = outsize;
+    strm.next_out = (unsigned char *)outbuf;
+
+    ret = ::inflate(&strm, Z_NO_FLUSH);
+    (void)inflateEnd(&strm);
+    return ret == Z_STREAM_END ? 0 : -1;
+}
+
+} //namespace pdal
diff --git a/io/BpfReader.hpp b/io/BpfReader.hpp
new file mode 100644
index 0000000..99549f4
--- /dev/null
+++ b/io/BpfReader.hpp
@@ -0,0 +1,116 @@
+/******************************************************************************
+* Copyright (c) 2014, Andrew Bell
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+// BPF is an NGA specification for point cloud data. The specification can be
+// found at https://nsgreg.nga.mil/doc/view?i=4202
+
+#pragma once
+
+#include <vector>
+
+#include <pdal/Reader.hpp>
+#include <pdal/util/Charbuf.hpp>
+#include <pdal/util/IStream.hpp>
+#include <pdal/pdal_export.hpp>
+#include <pdal/plugin.hpp>
+
+#include "BpfHeader.hpp"
+
+#include <vector>
+
+extern "C" int32_t BpfReader_ExitFunc();
+extern "C" PF_ExitFunc BpfReader_InitPlugin();
+
+namespace pdal
+{
+
+class PDAL_DLL BpfReader : public Reader
+{
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    virtual point_count_t numPoints() const
+        { return (point_count_t)m_header.m_numPts; }
+private:
+    ILeStream m_stream;
+    BpfHeader m_header;
+    BpfDimensionList m_dims;
+    Dimension::IdList m_schemaDims;
+    BpfUlemHeader m_ulemHeader;
+    std::vector<BpfUlemFrame> m_ulemFrames;
+    BpfPolarHeader m_polarHeader;
+    std::vector<BpfPolarFrame> m_polarFrames;
+    /// Stream position at the beginning of point records.
+    std::streampos m_start;
+    /// Index of the next point to read.
+    point_count_t m_index;
+    /// Buffer for deflated data.
+    std::vector<char> m_deflateBuf;
+    /// Streambuf for deflated data.
+    Charbuf m_charbuf;
+
+    // For dimension-major point-at-a-time usage.
+    std::vector<std::unique_ptr<ILeStream>> m_streams;
+    std::vector<std::unique_ptr<Charbuf>> m_charbufs;
+
+    virtual QuickInfo inspect();
+    virtual void initialize();
+    virtual void addDimensions(PointLayoutPtr Layout);
+    virtual void ready(PointTableRef table);
+    virtual bool processOne(PointRef& point);
+    virtual point_count_t read(PointViewPtr data, point_count_t num);
+    virtual void done(PointTableRef table);
+
+    bool readUlemData();
+    bool readUlemFiles();
+    bool readHeaderExtraData();
+    bool readPolarData();
+    void readPointMajor(PointRef& point);
+    point_count_t readPointMajor(PointViewPtr data, point_count_t count);
+    void readDimMajor(PointRef& point);
+    point_count_t readDimMajor(PointViewPtr data, point_count_t count);
+    void readByteMajor(PointRef& point);
+    point_count_t readByteMajor(PointViewPtr data, point_count_t count);
+    size_t readBlock(std::vector<char>& outBuf, size_t index);
+    bool eof();
+    int inflate(char *inbuf, uint32_t insize, char *outbuf, uint32_t outsize);
+
+    void seekPointMajor(PointId ptIdx);
+    void seekDimMajor(size_t dimIdx, PointId ptIdx);
+    void seekByteMajor(size_t dimIdx, size_t byteIdx, PointId ptIdx);
+};
+
+} // namespace pdal
diff --git a/io/BpfWriter.cpp b/io/BpfWriter.cpp
new file mode 100644
index 0000000..43b206a
--- /dev/null
+++ b/io/BpfWriter.cpp
@@ -0,0 +1,359 @@
+/******************************************************************************
+* Copyright (c) 2015, Hobu Inc., hobu at hobu.co
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "BpfWriter.hpp"
+
+#include <climits>
+
+#include <pdal/Options.hpp>
+#include <pdal/pdal_export.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <zlib.h>
+
+#include "BpfCompressor.hpp"
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/Utils.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "writers.bpf",
+    "\"Binary Point Format\" (BPF) writer support. BPF is a simple \n" \
+        "DoD and research format that is used by some sensor and \n" \
+        "processing chains.",
+    "http://pdal.io/stages/writers.bpf.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, BpfWriter, Writer, s_info)
+
+std::string BpfWriter::getName() const { return s_info.name; }
+
+void BpfWriter::addArgs(ProgramArgs& args)
+{
+    args.add("filename", "Output filename", m_filename).setPositional();
+    args.add("compression", "Output compression", m_compression);
+    args.add("header_data", "Base64-encoded header data", m_extraDataSpec);
+    args.add("format", "Output format", m_header.m_pointFormat,
+        BpfFormat::DimMajor);
+    args.add("coord_id", "UTM coordinate ID", m_header.m_coordId, -9999);
+    args.add("bundledfile", "List of files to bundle in output",
+        m_bundledFilesSpec);
+    args.add("output_dims", "Output dimensions", m_outputDims);
+    m_scaling.addArgs(args);
+}
+
+
+void BpfWriter::initialize()
+{
+    m_header.m_compression = Utils::toNative(
+            m_compression ? BpfCompression::Zlib : BpfCompression::None);
+    m_extraData = Utils::base64_decode(m_extraDataSpec);
+    if (m_header.m_coordId == -9999)
+    {
+        m_header.m_coordId = 0;
+        m_header.m_coordType = Utils::toNative(BpfCoordType::Cartesian);
+    }
+    else
+    {
+        m_header.m_coordType = Utils::toNative(BpfCoordType::UTM);
+    }
+
+    for (auto file : m_bundledFilesSpec)
+    {
+        if (!FileUtils::fileExists(file))
+        {
+            std::ostringstream oss;
+
+            oss << getName() << ": bundledfile '" << file << "' doesn't exist.";
+            throw pdal_error(oss.str());
+        }
+
+        size_t size = FileUtils::fileSize(file);
+        if (size > (std::numeric_limits<uint32_t>::max)())
+        {
+            std::ostringstream oss;
+            oss << getName() << ": bundledfile '" << file << "' too large.";
+            throw pdal_error(oss.str());
+        }
+
+        BpfUlemFile ulemFile(size, FileUtils::getFilename(file), file);
+        if (ulemFile.m_filename.length() > 32)
+        {
+            std::ostringstream oss;
+            oss << getName() << ": bundledfile '" << file << "' name "
+                "exceeds maximum length of 32.";
+            throw pdal_error(oss.str());
+        }
+        m_bundledFiles.push_back(ulemFile);
+    }
+
+    // BPF coordinates are always in UTM meters, which can be quite large.
+    // Allowing the writer to proceed with the default offset of 0 can lead to
+    // unexpected quantization of the coordinates. Instead, we force use of
+    // auto offset to subtract the minimum value in XYZ, unless of course, the
+    // user chooses to override with their own offset.
+    if (!m_scaling.m_xOffArg->set())
+        m_scaling.m_xXform.m_offset.m_auto = true;
+    if (!m_scaling.m_yOffArg->set())
+        m_scaling.m_yXform.m_offset.m_auto = true;
+    if (!m_scaling.m_zOffArg->set())
+        m_scaling.m_zXform.m_offset.m_auto = true;
+}
+
+
+void BpfWriter::prepared(PointTableRef table)
+{
+    loadBpfDimensions(table.layout());
+}
+
+
+void BpfWriter::readyFile(const std::string& filename, const SpatialReference&)
+{
+    m_curFilename = filename;
+    m_stream.open(filename);
+    m_header.m_version = 3;
+    m_header.m_numDim = m_dims.size();
+    m_header.m_numPts = 0;
+    m_header.setLog(log());
+
+    // We will re-write the header and dimensions to account for the point
+    // count and dimension min/max.
+    m_header.write(m_stream);
+    m_header.writeDimensions(m_stream, m_dims);
+    for (auto& file : m_bundledFiles)
+        file.write(m_stream);
+    m_stream.put((const char *)m_extraData.data(), m_extraData.size());
+
+    m_header.m_len = m_stream.position();
+
+    m_header.m_xform.m_vals[0] = m_scaling.m_xXform.m_scale.m_val;
+    m_header.m_xform.m_vals[5] = m_scaling.m_yXform.m_scale.m_val;
+    m_header.m_xform.m_vals[10] = m_scaling.m_zXform.m_scale.m_val;
+}
+
+
+void BpfWriter::loadBpfDimensions(PointLayoutPtr layout)
+{
+    Dimension::IdList dims;
+
+    if (m_outputDims.size())
+    {
+       for (std::string& s : m_outputDims)
+       {
+           Dimension::Id id = layout->findDim(s);
+           if (id == Dimension::Id::Unknown)
+           {
+               std::ostringstream oss;
+               oss << "Invalid dimension '" << s << "' specified for "
+                   "'output_dims' option.";
+               throw pdal_error(oss.str());
+            }
+            dims.push_back(id);
+       }
+    }
+    else
+        dims = layout->dims();
+
+    // Verify that we have X, Y and Z and that they're the first three
+    // dimensions.
+    std::sort(dims.begin(), dims.end());
+    if (dims.size() < 3 || dims[0] != Dimension::Id::X ||
+        dims[1] != Dimension::Id::Y || dims[2] != Dimension::Id::Z)
+    {
+        throw pdal_error("Missing one of dimensions X, Y or Z.  "
+            "Can't write BPF.");
+    }
+
+    for (auto id : dims)
+    {
+        BpfDimension dim;
+        dim.m_id = id;
+        dim.m_label = layout->dimName(id);
+        m_dims.push_back(dim);
+    }
+}
+
+
+void BpfWriter::writeView(const PointViewPtr dataShared)
+{
+    m_scaling.setAutoXForm(dataShared);
+
+    // Avoid reference count overhead internally.
+    const PointView* data(dataShared.get());
+
+    // We know that X, Y and Z are dimensions 0, 1 and 2.
+    m_dims[0].m_offset = m_scaling.m_xXform.m_offset.m_val;
+    m_dims[1].m_offset = m_scaling.m_yXform.m_offset.m_val;
+    m_dims[2].m_offset = m_scaling.m_zXform.m_offset.m_val;
+
+    switch (m_header.m_pointFormat)
+    {
+    case BpfFormat::PointMajor:
+        writePointMajor(data);
+        break;
+    case BpfFormat::DimMajor:
+        writeDimMajor(data);
+        break;
+    case BpfFormat::ByteMajor:
+        writeByteMajor(data);
+        break;
+    }
+    m_header.m_numPts += data->size();
+}
+
+
+void BpfWriter::writePointMajor(const PointView* data)
+{
+    // Blocks of 10,000 points will ensure that we're under 16MB, even
+    // for 255 dimensions.
+    size_t blockpoints = std::min<point_count_t>(10000UL, data->size());
+
+    // For compression we're going to write to a buffer so that it can be
+    // compressed before it's written to the file stream.
+    BpfCompressor compressor(m_stream,
+        blockpoints * sizeof(float) * m_dims.size());
+    PointId idx = 0;
+    while (idx < data->size())
+    {
+        if (m_header.m_compression)
+            compressor.startBlock();
+        size_t blockId;
+        for (blockId = 0; idx < data->size() && blockId < blockpoints;
+            ++idx, ++blockId)
+        {
+            for (auto & bpfDim : m_dims)
+            {
+                double d = getAdjustedValue(data, bpfDim, idx);
+                m_stream << (float)d;
+            }
+        }
+        if (m_header.m_compression)
+        {
+            compressor.compress();
+            compressor.finish();
+        }
+    }
+}
+
+
+void BpfWriter::writeDimMajor(const PointView* data)
+{
+    // We're going to pretend for now that we only even have one point buffer.
+    BpfCompressor compressor(m_stream, data->size() * sizeof(float));
+
+    for (auto & bpfDim : m_dims)
+    {
+
+        if (m_header.m_compression)
+            compressor.startBlock();
+        for (PointId idx = 0; idx < data->size(); ++idx)
+        {
+            double d = getAdjustedValue(data, bpfDim, idx);
+            m_stream << (float)d;
+        }
+        if (m_header.m_compression)
+        {
+            compressor.compress();
+            compressor.finish();
+        }
+    }
+}
+
+
+void BpfWriter::writeByteMajor(const PointView* data)
+{
+    union
+    {
+        float f;
+        uint32_t u32;
+    } uu;
+
+    // We're going to pretend for now that we only ever have one point buffer.
+    BpfCompressor compressor(m_stream,
+        data->size() * sizeof(float) * m_dims.size());
+
+    if (m_header.m_compression)
+        compressor.startBlock();
+    for (auto & bpfDim : m_dims)
+    {
+        for (size_t b = 0; b < sizeof(float); b++)
+        {
+            for (PointId idx = 0; idx < data->size(); ++idx)
+            {
+                uu.f = (float)getAdjustedValue(data, bpfDim, idx);
+                uint8_t u8 = (uint8_t)(uu.u32 >> (b * CHAR_BIT));
+                m_stream << u8;
+            }
+        }
+    }
+    if (m_header.m_compression)
+    {
+        compressor.compress();
+        compressor.finish();
+    }
+}
+
+
+double BpfWriter::getAdjustedValue(const PointView* data,
+    BpfDimension& bpfDim, PointId idx)
+{
+    double d = data->getFieldAs<double>(bpfDim.m_id, idx);
+    bpfDim.m_min = std::min(bpfDim.m_min, d);
+    bpfDim.m_max = std::max(bpfDim.m_max, d);
+
+    if (bpfDim.m_id == Dimension::Id::X)
+        d /= m_scaling.m_xXform.m_scale.m_val;
+    else if (bpfDim.m_id == Dimension::Id::Y)
+        d /= m_scaling.m_yXform.m_scale.m_val;
+    else if (bpfDim.m_id == Dimension::Id::Z)
+        d /= m_scaling.m_zXform.m_scale.m_val;
+    return (d - bpfDim.m_offset);
+}
+
+
+void BpfWriter::doneFile()
+{
+    // Rewrite the header to update the the correct number of points and
+    // statistics.
+    m_stream.seek(0);
+    m_header.write(m_stream);
+    m_header.writeDimensions(m_stream, m_dims);
+    m_stream.close();
+    getMetadata().addList("filename", m_curFilename);
+}
+
+} //namespace pdal
diff --git a/io/BpfWriter.hpp b/io/BpfWriter.hpp
new file mode 100644
index 0000000..46e507b
--- /dev/null
+++ b/io/BpfWriter.hpp
@@ -0,0 +1,91 @@
+/******************************************************************************
+* Copyright (c) 2015, Hobu Inc., hobu at hobu.co
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+// BPF is an NGA specification for point cloud data. The specification can be
+// found at https://nsgreg.nga.mil/doc/view?i=4202
+
+#pragma once
+
+#include "BpfHeader.hpp"
+
+#include <pdal/pdal_export.hpp>
+#include <pdal/FlexWriter.hpp>
+#include <pdal/util/OStream.hpp>
+#include <pdal/plugin.hpp>
+
+#include <vector>
+
+extern "C" int32_t BpfWriter_ExitFunc();
+extern "C" PF_ExitFunc BpfWriter_InitPlugin();
+
+namespace pdal
+{
+
+class PDAL_DLL BpfWriter : public FlexWriter
+{
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+private:
+    StringList m_outputDims; ///< List of dimensions to write
+    OLeStream m_stream;
+    BpfHeader m_header;
+    BpfDimensionList m_dims;
+    std::vector<uint8_t> m_extraData;
+    std::vector<BpfUlemFile> m_bundledFiles;
+    bool m_compression;
+    std::string m_extraDataSpec;
+    StringList m_bundledFilesSpec;
+    std::string m_curFilename;
+
+    virtual void addArgs(ProgramArgs& args);
+    virtual void initialize();
+    virtual void prepared(PointTableRef table);
+    virtual void readyFile(const std::string& filename,
+        const SpatialReference& srs);
+    virtual void writeView(const PointViewPtr data);
+    virtual void doneFile();
+
+    double getAdjustedValue(const PointView* data, BpfDimension& bpfDim,
+        PointId idx);
+    void loadBpfDimensions(PointLayoutPtr layout);
+    void writePointMajor(const PointView* data);
+    void writeDimMajor(const PointView* data);
+    void writeByteMajor(const PointView* data);
+    void writeCompressedBlock(char *buf, size_t size);
+};
+
+} // namespace pdal
diff --git a/io/buffer/BufferReader.hpp b/io/BufferReader.hpp
similarity index 100%
rename from io/buffer/BufferReader.hpp
rename to io/BufferReader.hpp
diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt
deleted file mode 100644
index 5b938ab..0000000
--- a/io/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-add_subdirectory(bpf)
-add_subdirectory(buffer)
-add_subdirectory(derivative)
-add_subdirectory(faux)
-add_subdirectory(ilvis2)
-add_subdirectory(las)
-add_subdirectory(gdal)
-add_subdirectory(null)
-add_subdirectory(optech)
-add_subdirectory(ply)
-add_subdirectory(pts)
-add_subdirectory(qfit)
-add_subdirectory(sbet)
-add_subdirectory(text)
-add_subdirectory(terrasolid)
-add_subdirectory(tindex)
-
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} PARENT_SCOPE)
diff --git a/io/DerivativeWriter.cpp b/io/DerivativeWriter.cpp
new file mode 100644
index 0000000..2821e69
--- /dev/null
+++ b/io/DerivativeWriter.cpp
@@ -0,0 +1,191 @@
+/******************************************************************************
+* Copyright (c) 2015-2016, Bradley J Chambers, brad.chambers at gmail.com
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "DerivativeWriter.hpp"
+
+#include <pdal/EigenUtils.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/util/Utils.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+static PluginInfo const s_info =
+    PluginInfo("writers.derivative", "Derivative writer",
+               "http://pdal.io/stages/writers.derivative.html");
+
+CREATE_STATIC_PLUGIN(1, 0, DerivativeWriter, Writer, s_info)
+
+std::string DerivativeWriter::getName() const
+{
+    return s_info.name;
+}
+
+void DerivativeWriter::addArgs(ProgramArgs& args)
+{
+    args.add("filename", "Output filename", m_filename).setPositional();
+    args.add("edge_length", "Edge length", m_edgeLength, 15.0);
+    args.add("primitive_type", "Primitive type", m_primTypesSpec, {"slope_d8"});
+    args.add("altitude", "Illumination altitude (degrees)", m_illumAltDeg,
+             45.0);
+    args.add("azimuth", "Illumination azimuth (degrees)", m_illumAzDeg, 315.0);
+    args.add("driver", "GDAL format driver", m_driver, "GTiff");
+}
+
+
+void DerivativeWriter::initialize()
+{
+    static std::map<std::string, PrimitiveType> primtypes =
+    {
+        {"slope_d8", SLOPE_D8},
+        {"slope_fd", SLOPE_FD},
+        {"aspect_d8", ASPECT_D8},
+        {"aspect_fd", ASPECT_FD},
+        {"hillshade", HILLSHADE},
+        {"contour_curvature", CONTOUR_CURVATURE},
+        {"profile_curvature", PROFILE_CURVATURE},
+        {"tangential_curvature", TANGENTIAL_CURVATURE},
+        {"total_curvature", TOTAL_CURVATURE}
+    };
+
+    auto hashPos = handleFilenameTemplate(m_filename);
+    if (hashPos == std::string::npos && m_primTypesSpec.size() > 1)
+    {
+        std::ostringstream oss;
+
+        oss << getName() << ": No template placeholder ('#') found in "
+            "filename '" << m_filename << "' when one is required with "
+            "multiple primitive types.";
+        throw pdal_error(oss.str());
+    }
+
+    for (std::string os : m_primTypesSpec)
+    {
+        std::string s = Utils::tolower(os);
+        auto pi = primtypes.find(s);
+        if (pi == primtypes.end())
+        {
+            std::ostringstream oss;
+            oss << getName() << ": Unrecognized primitive type '" << os <<
+                "'.";
+            throw pdal_error(oss.str());
+        }
+        TypeOutput to;
+        to.m_type = pi->second;
+        to.m_filename = generateFilename(pi->first, hashPos);
+        m_primitiveTypes.push_back(to);
+    }
+}
+
+
+std::string DerivativeWriter::generateFilename(const std::string& primName,
+        std::string::size_type hashPos) const
+{
+    std::string filename = m_filename;
+    if (hashPos != std::string::npos)
+        filename.replace(hashPos, 1, primName);
+    return filename;
+}
+
+
+void DerivativeWriter::write(const PointViewPtr data)
+{
+    using namespace eigen;
+    using namespace Eigen;
+
+    // Bounds are required for computing number of rows and columns, and for
+    // later indexing individual points into the appropriate raster cells.
+    BOX2D bounds;
+    data->calculateBounds(bounds);
+    SpatialReference srs = data->spatialReference();
+
+    // Determine the number of rows and columns at the given cell size.
+    size_t cols = ((bounds.maxx - bounds.minx) / m_edgeLength) + 1;
+    size_t rows = ((bounds.maxy - bounds.miny) / m_edgeLength) + 1;
+
+    // Begin by creating a DSM of max elevations per XY cell.
+    MatrixXd DSM = createMaxMatrix(*data.get(), rows, cols, m_edgeLength,
+                                   bounds);
+
+    // Continue by cleaning the DSM.
+    MatrixXd cleanedDSM = cleanDSM(DSM);
+
+    // We will pad the edges by 1 cell, though according to some texts we should
+    // simply compute forward- or backward-difference as opposed to centered
+    // difference at these points.
+    MatrixXd paddedDSM = padMatrix(cleanedDSM, 1);
+
+    // Prepare the out matrix.
+    MatrixXd out(cleanedDSM.rows(), cleanedDSM.cols());
+    out.setConstant(std::numeric_limits<double>::quiet_NaN());
+
+    for (TypeOutput& to : m_primitiveTypes)
+    {
+        for (int r = 1; r < paddedDSM.rows()-1; ++r)
+        {
+            for (int c = 1; c < paddedDSM.cols()-1; ++c)
+            {
+                double val = paddedDSM(r, c);
+                if (std::isnan(val))
+                    continue;
+                Matrix3d block = paddedDSM.block(r-1, c-1, 3, 3);
+                if (to.m_type == SLOPE_D8)
+                    out(r-1, c-1) = computeSlopeD8(block, m_edgeLength);
+                if (to.m_type == SLOPE_FD)
+                    out(r-1, c-1) = computeSlopeFD(block, m_edgeLength);
+                if (to.m_type == ASPECT_D8)
+                    out(r-1, c-1) = computeAspectD8(block, m_edgeLength);
+                if (to.m_type == ASPECT_FD)
+                    out(r-1, c-1) = computeAspectFD(block, m_edgeLength);
+                if (to.m_type == HILLSHADE)
+                    out(r-1, c-1) = computeHillshade(block, m_edgeLength,
+                                                     m_illumAltDeg,
+                                                     m_illumAzDeg);
+                if (to.m_type == CONTOUR_CURVATURE)
+                    out(r-1, c-1) = computeContour(block, m_edgeLength);
+                if (to.m_type == PROFILE_CURVATURE)
+                    out(r-1, c-1) = computeProfile(block, m_edgeLength);
+                if (to.m_type == TANGENTIAL_CURVATURE)
+                    out(r-1, c-1) = computeTangential(block, m_edgeLength);
+                if (to.m_type == TOTAL_CURVATURE)
+                    out(r-1, c-1) = computeTotal(block, m_edgeLength);
+            }
+        }
+
+        // Finally, write our Matrix as a GDAL raster (specifically GTiff).
+        writeMatrix(out, to.m_filename, m_driver, m_edgeLength, bounds, srs);
+    }
+}
+
+} // namespace pdal
diff --git a/io/DerivativeWriter.hpp b/io/DerivativeWriter.hpp
new file mode 100644
index 0000000..975ff58
--- /dev/null
+++ b/io/DerivativeWriter.hpp
@@ -0,0 +1,103 @@
+/******************************************************************************
+* Copyright (c) 2015-2016, Bradley J Chambers, brad.chambers at gmail.com
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Writer.hpp>
+#include <pdal/plugin.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <Eigen/Core>
+
+#include <string>
+#include <vector>
+
+extern "C" int32_t DerivativeWriter_ExitFunc();
+extern "C" PF_ExitFunc DerivativeWriter_InitPlugin();
+
+namespace pdal
+{
+
+class BOX2D;
+
+class PDAL_DLL DerivativeWriter : public Writer
+{
+    enum PrimitiveType
+    {
+        SLOPE_D8,
+        SLOPE_FD,
+        ASPECT_D8,
+        ASPECT_FD,
+        HILLSHADE,
+        CONTOUR_CURVATURE,
+        PROFILE_CURVATURE,
+        TANGENTIAL_CURVATURE,
+        TOTAL_CURVATURE
+    };
+
+    struct TypeOutput
+    {
+        PrimitiveType m_type;
+        std::string m_filename;
+    };
+
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    DerivativeWriter()
+    {}
+
+private:
+    virtual void addArgs(ProgramArgs& args);
+    virtual void initialize();
+    virtual void write(const PointViewPtr view);
+
+    std::string generateFilename(const std::string& primName,
+                                 std::string::size_type hashPos) const;
+
+    std::string m_filename;
+    std::string m_driver;
+    double m_edgeLength;
+    double m_illumAltDeg;
+    double m_illumAzDeg;
+    StringList m_primTypesSpec;
+    std::vector<TypeOutput> m_primitiveTypes;
+
+    DerivativeWriter& operator=(const DerivativeWriter&); // not implemented
+    DerivativeWriter(const DerivativeWriter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/io/faux/FauxReader.cpp b/io/FauxReader.cpp
similarity index 100%
rename from io/faux/FauxReader.cpp
rename to io/FauxReader.cpp
diff --git a/io/faux/FauxReader.hpp b/io/FauxReader.hpp
similarity index 100%
rename from io/faux/FauxReader.hpp
rename to io/FauxReader.hpp
diff --git a/io/GDALGrid.cpp b/io/GDALGrid.cpp
new file mode 100644
index 0000000..5329904
--- /dev/null
+++ b/io/GDALGrid.cpp
@@ -0,0 +1,407 @@
+/******************************************************************************
+* Copyright (c) 2016, Hobu Inc. (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "GDALGrid.hpp"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <iostream>
+
+namespace pdal
+{
+
+GDALGrid::GDALGrid(size_t width, size_t height, double edgeLength,
+        double radius, double noData, int outputTypes, size_t windowSize) :
+    m_width(width), m_height(height), m_windowSize(windowSize),
+    m_edgeLength(edgeLength), m_radius(radius), m_noData(noData),
+    m_outputTypes(outputTypes)
+{
+    size_t size(width * height);
+
+    m_count.reset(new DataVec(size));
+    if (m_outputTypes & statMin)
+        m_min.reset(new DataVec(size, std::numeric_limits<double>::max()));
+    if (m_outputTypes & statMax)
+        m_max.reset(new DataVec(size, std::numeric_limits<double>::lowest()));
+    if (m_outputTypes & statIdw)
+    {
+        m_idw.reset(new DataVec(size));
+        m_idwDist.reset(new DataVec(size));
+    }
+    if ((m_outputTypes & statMean) || (m_outputTypes & statStdDev))
+        m_mean.reset(new DataVec(size));
+    if (m_outputTypes & statStdDev)
+        m_stdDev.reset(new DataVec(size));
+}
+
+
+int GDALGrid::numBands() const
+{
+    int num = 0;
+
+    if (m_outputTypes & statCount)
+        num++;
+    if (m_outputTypes & statMin)
+        num++;
+    if (m_outputTypes & statMax)
+        num++;
+    if (m_outputTypes & statMean)
+        num++;
+    if (m_outputTypes & statIdw)
+        num++;
+    if (m_outputTypes & statStdDev)
+        num++;
+    return num;
+}
+
+
+uint8_t *GDALGrid::data(const std::string& name)
+{
+    if (name == "count")
+        return (m_outputTypes & statCount ?
+            (uint8_t *)m_count->data() : nullptr);
+    if (name == "min")
+        return (m_outputTypes & statMin ?
+            (uint8_t *)m_min->data() : nullptr);
+    if (name == "max")
+        return (m_outputTypes & statMax ?
+            (uint8_t *)m_max->data() : nullptr);
+    if (name == "mean")
+        return (m_outputTypes & statMean ?
+            (uint8_t *)m_mean->data() : nullptr);
+    if (name == "idw")
+        return (m_outputTypes & statIdw ?
+            (uint8_t *)m_idw->data() : nullptr);
+    if (name == "stdev")
+        return (m_outputTypes & statStdDev ?
+            (uint8_t *)m_stdDev->data() : nullptr);
+    return nullptr;
+}
+
+
+void GDALGrid::addPoint(double x, double y, double z)
+{
+    int iOrigin = horizontalIndex(x);
+    int jOrigin = verticalIndex(y);
+
+    // Here's the logic... we divide the cells around the subject cell
+    // (at iOrigin, jOrigin) into four quadrants.  We move outward from the
+    // subject cell, checking distance until we find that we're farther than
+    // permitted by the radius, then we move up (down, over, whatever) a row
+    // or column and do it again until we find a case where there's not a
+    // single cell in the row that meets our criteria.
+    // There are easier ways to figure out which cells will be close enough,
+    // be we need the precise distance for all those cells that we already
+    // know will qualify, so I don't think there's much overhead here that
+    // we can avoid.
+
+    // The four quadrant cases could certainly be merged into one, but I
+    // think it would be harder to follow and there's really not that
+    // much code here.
+
+    // The quadrants are the standard mathematical ones.  Here's a picture
+    // of how things kind of work.  At the end we update the central cell.
+
+    //            ^ ->
+    //          ^ | -->
+    //        ^ | | --->
+    //      ^ | | | ---->
+    //   <------- X ------> 
+    //    <------ | | | v
+    //     <----- | | v
+    //       <--- | v
+    //         <- v
+
+
+    // First quadrant;
+    int i = iOrigin + 1;
+    int j = jOrigin;
+    while (i < (int)m_width && j >= 0)
+    {
+        double d = distance(i, j, x, y);
+        if (d < m_radius)
+        {
+            update(i, j, z, d);
+            i++;
+        }
+        else
+        {
+            if (i == iOrigin + 1)
+                break;
+            i = iOrigin + 1;
+            j--;
+        }
+    }
+
+    // Second quadrant;
+    i = iOrigin;
+    j = jOrigin - 1;
+    while (i >= 0 && j >= 0)
+    {
+        double d = distance(i, j, x, y);
+        if (d < m_radius)
+        {
+            update(i, j, z, d);
+            j--;
+        }
+        else
+        {
+            if (j == jOrigin - 1)
+                break;
+            j = jOrigin - 1;
+            i--;
+        }
+    }
+
+    // Third quadrant;
+    i = iOrigin - 1;
+    j = jOrigin;
+    while (i >= 0 && j < (int)m_height)
+    {
+        double d = distance(i, j, x, y);
+        if (d < m_radius)
+        {
+            update(i, j, z, d);
+            i--;
+        }
+        else
+        {
+            if (i == iOrigin - 1)
+                break;
+            i = iOrigin - 1;
+            j++;
+        }
+    }
+    // Fourth quadrant;
+    i = iOrigin;
+    j = jOrigin + 1;
+    while (i < (int)m_width && j < (int)m_height)
+    {
+        double d = distance(i, j, x, y);
+        if (d < m_radius)
+        {
+            update(i, j, z, d);
+            j++;
+        }
+        else
+        {
+            if (j == jOrigin + 1)
+                break;
+            j = jOrigin + 1;
+            i++;
+        }
+    }
+
+    // This is a questionable case.  If a point is in a cell, shouldn't
+    // it just be counted?
+    double d = distance(iOrigin, jOrigin, x, y);
+    if (d < m_radius)
+        update(iOrigin, jOrigin, z, d);
+}
+
+void GDALGrid::update(int i, int j, double val, double dist)
+{
+    // Once we determine that a point is close enough to a cell to count it,
+    // this function does the actual math.  We use the value of the 
+    // point (val) and its distance from the cell center (dist).  There's
+    // a little math that needs to be done once all points are added.  See
+    // finalize() for that.
+
+    // See
+    // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
+    // https://en.wikipedia.org/wiki/Inverse_distance_weighting
+
+    size_t offset = index(i, j);
+
+    double& count = (*m_count)[offset];
+    count++;
+
+    if (m_min)
+    {
+        double& min = (*m_min)[offset];
+        min = std::min(val, min);
+    }
+
+    if (m_max)
+    {
+        double& max = (*m_max)[offset];
+        max = std::max(val, max);
+    }
+
+    if (m_mean)
+    {
+        double& mean = (*m_mean)[offset];
+        double delta = val - mean;
+
+        mean += delta / count;
+        if (m_stdDev)
+        {
+            double& stdDev = (*m_stdDev)[offset];
+            stdDev += delta * (val - mean);
+        }
+    }
+
+    if (m_idw)
+    {
+        double& idw = (*m_idw)[offset];
+        double& idwDist = (*m_idwDist)[offset];
+
+        // If the distance is 0, we set the idwDist to nan to signal that
+        // we should ignore the distance and take the value as is.
+        if (!std::isnan(idwDist))
+        {
+            if (dist == 0)
+            {
+                idw = val;
+                idwDist = std::numeric_limits<double>::quiet_NaN();   
+            }
+            else
+            {
+                idw += val / dist;
+                idwDist += 1 / dist;
+            }
+        }
+    }
+}
+
+void GDALGrid::finalize()
+{
+    // See
+    // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
+    // https://en.wikipedia.org/wiki/Inverse_distance_weighting
+    if (m_stdDev)
+        for (size_t i = 0; i < m_count->size(); ++i)
+            if (!empty(i))
+                (*m_stdDev)[i] = sqrt((*m_stdDev)[i] / (*m_count)[i]);
+
+    if (m_idw)
+        for (size_t i = 0; i < m_count->size(); ++i)
+            if (!empty(i))
+            {
+                double& distSum = (*m_idwDist)[i];
+
+                if (!std::isnan(distSum))
+                    (*m_idw)[i] /= distSum;
+            }
+
+    if (m_windowSize > 0)
+        windowFill();
+    else
+        for (size_t i = 0; i < m_count->size(); ++i)
+            if (empty(i))
+                fillNodata(i);
+}
+
+
+void GDALGrid::fillNodata(size_t i)
+{
+    if (m_min)
+        (*m_min)[i] = m_noData;
+    if (m_max)
+        (*m_max)[i] = m_noData;
+    if (m_mean)
+        (*m_mean)[i] = m_noData;
+    if (m_idw)
+        (*m_idw)[i] = m_noData;
+    if (m_stdDev)
+        (*m_stdDev)[i] = m_noData;
+}
+
+
+void GDALGrid::windowFill(size_t dstI, size_t dstJ)
+{
+    size_t istart = dstI > m_windowSize ? dstI - m_windowSize : (size_t)0;
+    size_t iend = std::min(width(), dstI + m_windowSize + 1);
+    size_t jstart = dstJ > m_windowSize ? dstJ - m_windowSize : (size_t)0;
+    size_t jend = std::min(height(), dstJ + m_windowSize + 1);
+
+    double distSum = 0;
+    size_t dstIdx = index(dstI, dstJ);
+
+    // Initialize to 0 (rather than numeric_limits::max/lowest) since we're
+    // going to accumulate and average.
+    if (m_min)
+        (*m_min)[dstIdx] = 0;
+    if (m_max)
+        (*m_max)[dstIdx] = 0;
+
+    for (size_t i = istart; i < iend; ++i)
+        for (size_t j = jstart; j < jend; ++j)
+        {
+            size_t srcIdx = index(i, j);
+            if ((srcIdx == dstIdx) || empty(srcIdx))
+                continue;
+            // The ternaries just avoid underflow UB.  We're just trying to
+            // find the distance from j to dstJ or i to dstI.
+            double distance = std::max(j > dstJ ? j - dstJ : dstJ - j,
+                i > dstI ? i - dstI : dstI - i);
+            windowFillCell(srcIdx, dstIdx, distance);
+            distSum += (1 / distance);
+        }
+
+    // Divide summed values by the (inverse) distance sum.
+    if (distSum > 0)
+    {
+        if (m_min)
+            (*m_min)[dstIdx] /= distSum;
+        if (m_max)
+            (*m_max)[dstIdx] /= distSum;
+        if (m_mean)
+            (*m_mean)[dstIdx] /= distSum;
+        if (m_idw)
+            (*m_idw)[dstIdx] /= distSum;
+        if (m_stdDev)
+            (*m_stdDev)[dstIdx] /= distSum;
+    }
+    else
+        fillNodata(dstIdx);
+}
+
+
+void GDALGrid::windowFillCell(size_t srcIdx, size_t dstIdx, double distance)
+{
+    if (m_min)
+        (*m_min)[dstIdx] += (*m_min)[srcIdx] / distance;
+    if (m_max)
+        (*m_max)[dstIdx] += (*m_max)[srcIdx] / distance;
+    if (m_mean)
+        (*m_mean)[dstIdx] += (*m_mean)[srcIdx] / distance;
+    if (m_idw)
+        (*m_idw)[dstIdx] += (*m_idw)[srcIdx] / distance;
+    if (m_stdDev)
+        (*m_stdDev)[dstIdx] += (*m_stdDev)[srcIdx] / distance;
+}
+
+} //namespace pdal
diff --git a/io/GDALGrid.hpp b/io/GDALGrid.hpp
new file mode 100644
index 0000000..da3719c
--- /dev/null
+++ b/io/GDALGrid.hpp
@@ -0,0 +1,162 @@
+/******************************************************************************
+* Copyright (c) 2016, Hobu Inc. (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <math.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+class GDALGrid
+{
+public:
+    static const int statCount = 1;
+    static const int statMin = 2;
+    static const int statMax = 4;
+    static const int statMean = 8;
+    static const int statStdDev = 16;
+    static const int statIdw = 32;
+
+    GDALGrid(size_t width, size_t height, double edgeLength, double radius,
+        double noData, int outputTypes, size_t windowSize);
+
+    // Get the number of bands represented by this grid.
+    int numBands() const;
+
+    // Return a pointer to the data in a raster band, row-major ordered.
+    uint8_t *data(const std::string& name);
+
+    // Add a point to the raster grid.
+    void addPoint(double x, double y, double z);
+
+    // Compute final values after all points have been added.
+    void finalize();
+
+    size_t width() const
+        { return m_width; }
+
+    size_t height() const
+        { return m_height; }
+
+    double noData() const
+        { return m_noData; }
+
+private:
+    size_t m_width;
+    size_t m_height;
+    size_t m_windowSize;
+    double m_edgeLength;
+    double m_radius;
+    double m_noData;
+
+    typedef std::vector<double> DataVec;
+    typedef std::unique_ptr<DataVec> DataPtr;
+    DataPtr m_count;
+    DataPtr m_min;
+    DataPtr m_max;
+    DataPtr m_mean;
+    DataPtr m_stdDev;
+    DataPtr m_idw;
+    DataPtr m_idwDist;
+
+    int m_outputTypes;
+
+    // Find an index into the actual storage given a grid coordinate.
+    size_t index(size_t i, size_t j)
+        { return (j * m_width) + i; }
+
+    // Determine if a cell i, j has no associated points.
+    bool empty(size_t i, size_t j)
+        { return empty(index(i, j)); }
+
+    // Determine if a cell with index \c idx has no associated points.
+    bool empty(size_t idx)
+        { return ((*m_count)[idx] <= 0); }
+
+    // Convert an absolute X position to a horizontal cell index.
+    int horizontalIndex(double x)
+        { return x / m_edgeLength; }
+
+    // Convert an absolute Y position to a vertical cell index.
+    int verticalIndex(double y)
+        { return m_height - (y / m_edgeLength) - 1; }
+
+    // Return the absolute horizontal position of the center of a cell given
+    // the cell i index.
+    double horizontalPos(size_t i)
+        { return (i + .5) * m_edgeLength; }
+
+    // Return the absolute vertical position of the center of a cell given
+    // the cell j index.
+    double verticalPos(size_t j)
+        { return (m_height - (j + .5)) * m_edgeLength; }
+
+    // Determine the distance from the center of cell at coordinate i, j to
+    // a point at absolute coordinate x, y.
+    double distance(size_t i, size_t j, double x, double y)
+    {
+        double x1 = horizontalPos(i);
+        double y1 = verticalPos(j);
+        return sqrt(pow(x1 - x, 2) + pow(y1 - y, 2));
+    }
+
+    // Update cell at i, j with value at a distance.
+    void update(int i, int j, double val, double dist);
+
+    // Fill cell at index \c i with the nondata value.
+    void fillNodata(size_t i);
+
+    // Fill an empty cell with a value inverse-distance averaged from
+    // surrounding cells.
+    void windowFill()
+    {
+        for (size_t i = 0; i < width(); ++i)
+            for (size_t j = 0; j < height(); ++j)
+                if (empty(i, j))
+                    windowFill(i, j);
+    }
+
+    // Fill empty cell at dstI, dstJ with inverse-distance weighted values
+    // from neighboring cells.
+    void windowFill(size_t dstI, size_t dstJ);
+
+    // Cumulate data from a source cell to a destination cell when doing
+    // a window fill.
+    void windowFillCell(size_t srcIdx, size_t dstIdx, double distance);
+};
+typedef std::unique_ptr<GDALGrid> GDALGridPtr;
+
+} //namespace pdal
diff --git a/io/GDALReader.cpp b/io/GDALReader.cpp
new file mode 100644
index 0000000..feed83d
--- /dev/null
+++ b/io/GDALReader.cpp
@@ -0,0 +1,172 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler <howard at hobu.co>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "GDALReader.hpp"
+
+#include <sstream>
+
+#include <pdal/GDALUtils.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+        "readers.gdal",
+        "Read GDAL rasters as point clouds.",
+        "http://pdal.io/stages/reader.gdal.html");
+
+
+CREATE_STATIC_PLUGIN(1, 0, GDALReader, Reader, s_info);
+
+
+std::string GDALReader::getName() const
+{
+    return s_info.name;
+}
+
+
+GDALReader::GDALReader()
+    : m_index(0)
+{}
+
+
+void GDALReader::initialize()
+{
+    gdal::registerDrivers();
+    m_raster.reset(new gdal::Raster(m_filename));
+
+    m_raster->open();
+    try
+    {
+        setSpatialReference(m_raster->getSpatialRef());
+    }
+    catch (...)
+    {
+        log()->get(LogLevel::Error) << "Could not create an SRS" << std::endl;
+    }
+
+    m_count = m_raster->width() * m_raster->height();
+    m_raster->close();
+}
+
+
+QuickInfo GDALReader::inspect()
+{
+    QuickInfo qi;
+    std::unique_ptr<PointLayout> layout(new PointLayout());
+
+    addDimensions(layout.get());
+    initialize();
+
+    m_raster = std::unique_ptr<gdal::Raster>(new gdal::Raster(m_filename));
+    if (m_raster->open() == gdal::GDALError::CantOpen)
+        throw pdal_error("Couldn't open raster file '" + m_filename + "'.");
+
+    qi.m_pointCount = m_raster->width() * m_raster->height();
+    // qi.m_bounds = ???;
+    qi.m_srs = m_raster->getSpatialRef();
+    qi.m_valid = true;
+
+    return qi;
+}
+
+
+void GDALReader::addDimensions(PointLayoutPtr layout)
+{
+    layout->registerDim(pdal::Dimension::Id::X);
+    layout->registerDim(pdal::Dimension::Id::Y);
+    for (int i = 0; i < m_raster->bandCount(); ++i)
+    {
+        std::ostringstream oss;
+        oss << "band-" << (i + 1);
+        layout->registerOrAssignDim(oss.str(), Dimension::Type::Double);
+    }
+}
+
+
+void GDALReader::ready(PointTableRef table)
+{
+    m_index = 0;
+    if (m_raster->open() == gdal::GDALError::CantOpen)
+        throw pdal_error("Couldn't open raster file '" + m_filename + "'.");
+}
+
+
+point_count_t GDALReader::read(PointViewPtr view, point_count_t num)
+{
+    point_count_t count = std::min(num, m_count - m_index);
+    PointId nextId = view->size();
+
+    std::array<double, 2> coords;
+    for (int row = 0; row < m_raster->height(); ++row)
+    {
+        for (int col = 0; col < m_raster->width(); ++col)
+        {
+            m_raster->pixelToCoord(col, row, coords);
+            view->setField(Dimension::Id::X, nextId, coords[0]);
+            view->setField(Dimension::Id::Y, nextId, coords[1]);
+            nextId++;
+        }
+    }
+
+    std::vector<uint8_t> band;
+    std::vector<Dimension::Type> band_types =
+        m_raster->getPDALDimensionTypes();
+
+    for (int b = 0; b < m_raster->bandCount(); ++b)
+    {
+        // Bands count from 1
+        m_raster->readBand(band, b + 1);
+        std::stringstream oss;
+        oss << "band-" << (b + 1);
+        log()->get(LogLevel::Info) << "Read band '" << oss.str() << "'" <<
+            std::endl;
+
+        Dimension::Id d = view->layout()->findDim(oss.str());
+        size_t dimSize = Dimension::size(band_types[b]);
+        uint8_t* p = band.data();
+        for (point_count_t i = 0; i < count; ++i)
+        {
+            view->setField(d, band_types[b], i, p);
+            p = p + dimSize;
+        }
+    }
+
+    return view->size();
+}
+
+} // namespace pdal
+
diff --git a/io/GDALReader.hpp b/io/GDALReader.hpp
new file mode 100644
index 0000000..d811a18
--- /dev/null
+++ b/io/GDALReader.hpp
@@ -0,0 +1,84 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler <howard at hobu.co>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <string>
+
+
+#include <pdal/Dimension.hpp>
+#include <pdal/Reader.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/GDALUtils.hpp>
+#include <pdal/plugin.hpp>
+
+extern "C" int32_t GDALReader_ExitFunc();
+extern "C" PF_ExitFunc GDALReader_InitPlugin();
+
+
+namespace pdal
+{
+
+
+typedef std::map<std::string, Dimension::Id> DimensionMap;
+
+
+
+class PDAL_DLL GDALReader : public Reader
+{
+public:
+    static void *create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    GDALReader();
+
+    static Dimension::IdList getDefaultDimensions();
+
+private:
+    virtual void initialize();
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual void ready(PointTableRef table);
+    virtual point_count_t read(PointViewPtr view, point_count_t num);
+    virtual void done(PointTableRef table)
+        { m_raster->close(); }
+    virtual QuickInfo inspect();
+
+    std::unique_ptr<gdal::Raster> m_raster;
+    point_count_t m_index;
+
+};
+
+} // namespace pdal
+
diff --git a/io/GDALWriter.cpp b/io/GDALWriter.cpp
new file mode 100644
index 0000000..f51859d
--- /dev/null
+++ b/io/GDALWriter.cpp
@@ -0,0 +1,204 @@
+/******************************************************************************
+* Copyright (c) 2016, Hobu Inc. <info at hobu.co>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its contributors
+*       may be used to endorse or promote products derived from this
+*       software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "GDALWriter.hpp"
+
+#include <sstream>
+
+#include <pdal/GDALUtils.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+        "writers.gdal",
+        "Write a point cloud as a GDAL raster.",
+        "http://pdal.io/stages/writers.gdal.html");
+
+
+CREATE_STATIC_PLUGIN(1, 0, GDALWriter, Writer, s_info);
+
+std::string GDALWriter::getName() const
+{
+    return s_info.name;
+}
+
+
+void GDALWriter::addArgs(ProgramArgs& args)
+{
+    args.add("filename", "Output filename", m_filename).setPositional();
+    args.add("resolution", "Cell edge size, in units of X/Y",
+        m_edgeLength).setPositional();
+    args.add("radius", "Radius from cell center to use to locate influencing "
+        "points", m_radius).setPositional();
+    args.add("gdaldriver", "GDAL writer driver name", m_drivername, "GTiff");
+    args.add("gdalopts", "GDAL driver options (name=value,name=value...)",
+        m_options);
+    args.add("output_type", "Statistics produced ('min', 'max', 'mean', "
+        "'idw', 'count', 'stdev' or 'all')", m_outputTypeString, {"all"} );
+    args.add("window_size", "Cell distance for fallback interpolation",
+        m_windowSize);
+    args.add("nodata", "No data value", m_noData, -9999.0);
+    args.add("dimension", "Dimension to use", m_interpDimString, "Z");
+}
+
+
+void GDALWriter::initialize()
+{
+    for (auto& ts : m_outputTypeString)
+    {
+       Utils::trim(ts);
+        if (ts == "all")
+        {
+            m_outputTypes = ~0;
+            break;
+        }
+        if (ts == "min")
+            m_outputTypes |= GDALGrid::statMin;
+        else if (ts == "max")
+            m_outputTypes |= GDALGrid::statMax;
+        else if (ts == "count")
+            m_outputTypes |= GDALGrid::statCount;
+        else if (ts == "mean")
+            m_outputTypes |= GDALGrid::statMean;
+        else if (ts == "idw")
+            m_outputTypes |= GDALGrid::statIdw;
+        else if (ts == "stdev")
+            m_outputTypes |= GDALGrid::statStdDev;
+        else
+        {
+            std::ostringstream oss;
+            oss << "Invalid writers.gdal output type: '" << ts << "'.";
+            throw pdal_error(oss.str());
+        }
+    }
+
+    gdal::registerDrivers();
+}
+
+
+void GDALWriter::prepared(PointTableRef table)
+{
+    m_interpDim = table.layout()->findDim(m_interpDimString);
+    if (m_interpDim == Dimension::Id::Unknown)
+    {
+        std::ostringstream oss;
+
+        oss << getName() << ": specified dimension '" << m_interpDimString <<
+            "' does not exist.";
+        throw pdal_error(oss.str());
+    }
+}
+
+
+void GDALWriter::ready(PointTableRef table)
+{
+    if (!table.spatialReferenceUnique())
+    {
+        std::ostringstream oss;
+
+        oss << getName() << ": Can't write output with multiple spatial "
+            "references.";
+        throw pdal_error(oss.str());
+    }
+}
+
+
+void GDALWriter::write(const PointViewPtr view)
+{
+    view->calculateBounds(m_bounds);
+    size_t width = ((m_bounds.maxx - m_bounds.minx) / m_edgeLength) + 1;
+    size_t height = ((m_bounds.maxy - m_bounds.miny) / m_edgeLength) + 1;
+    m_grid.reset(new GDALGrid(width, height, m_edgeLength, m_radius, m_noData,
+        m_outputTypes, m_windowSize));
+
+    for (PointId idx = 0; idx < view->size(); ++idx)
+    {
+        double x = view->getFieldAs<double>(Dimension::Id::X, idx) -
+            m_bounds.minx;
+        double y = view->getFieldAs<double>(Dimension::Id::Y, idx) -
+            m_bounds.miny;
+        double z = view->getFieldAs<double>(m_interpDim, idx);
+
+        m_grid->addPoint(x, y, z);
+   }
+}
+
+
+void GDALWriter::done(PointTableRef table)
+{
+    std::array<double, 6> pixelToPos;
+
+    pixelToPos[0] = m_bounds.minx;
+    pixelToPos[1] = m_edgeLength;
+    pixelToPos[2] = 0;
+    pixelToPos[3] = m_bounds.miny + (m_edgeLength * m_grid->height());
+    pixelToPos[4] = 0;
+    pixelToPos[5] = -m_edgeLength;
+    gdal::Raster raster(m_filename, m_drivername, table.spatialReference(),
+        pixelToPos);
+
+    m_grid->finalize();
+
+    gdal::GDALError err = raster.open(m_grid->width(), m_grid->height(),
+        m_grid->numBands(), Dimension::Type::Double, m_grid->noData());
+    if (err != gdal::GDALError::None)
+        throw pdal_error(raster.errorMsg());
+    int bandNum = 1;
+    uint8_t *buf;
+    buf = m_grid->data("min");
+    if (buf)
+        raster.writeBand(buf, bandNum++, "min");
+    buf = m_grid->data("max");
+    if (buf)
+        raster.writeBand(buf, bandNum++, "max");
+    buf = m_grid->data("mean");
+    if (buf)
+        raster.writeBand(buf, bandNum++, "mean");
+    buf = m_grid->data("idw");
+    if (buf)
+        raster.writeBand(buf, bandNum++, "idw");
+    buf = m_grid->data("count");
+    if (buf)
+        raster.writeBand(buf, bandNum++, "count");
+    buf = m_grid->data("stdev");
+    if (buf)
+        raster.writeBand(buf, bandNum++, "stdev");
+
+    getMetadata().addList("filename", m_filename);
+}
+
+} // namespace pdal
+
diff --git a/io/GDALWriter.hpp b/io/GDALWriter.hpp
new file mode 100644
index 0000000..2266c3f
--- /dev/null
+++ b/io/GDALWriter.hpp
@@ -0,0 +1,83 @@
+/******************************************************************************
+* Copyright (c) 2016, Hobu Inc. (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <algorithm>
+
+#include <pdal/PointView.hpp>
+#include <pdal/Writer.hpp>
+#include <pdal/plugin.hpp>
+
+#include "GDALGrid.hpp"
+
+extern "C" int32_t GDALWriter_ExitFunc();
+extern "C" PF_ExitFunc GDALWriter_InitPlugin();
+
+namespace pdal
+{
+
+class PDAL_DLL GDALWriter : public Writer
+{
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    GDALWriter() : m_outputTypes(0)
+    {}
+
+private:
+    virtual void addArgs(ProgramArgs& args);
+    virtual void initialize();
+    virtual void prepared(PointTableRef table);
+    virtual void ready(PointTableRef table);
+    virtual void write(const PointViewPtr data);
+    virtual void done(PointTableRef table);
+
+    std::string m_filename;
+    std::string m_drivername;
+    BOX2D m_bounds;
+    double m_edgeLength;
+    double m_radius;
+    StringList m_options;
+    StringList m_outputTypeString;
+    size_t m_windowSize;
+    int m_outputTypes;
+    GDALGridPtr m_grid;
+    double m_noData;
+    Dimension::Id m_interpDim;
+    std::string m_interpDimString;
+
+};
+
+}
diff --git a/io/GeotiffSupport.cpp b/io/GeotiffSupport.cpp
new file mode 100644
index 0000000..931a832
--- /dev/null
+++ b/io/GeotiffSupport.cpp
@@ -0,0 +1,280 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "GeotiffSupport.hpp"
+
+#include <sstream>
+
+// GDAL
+#include <geo_normalize.h>
+#include <ogr_spatialref.h>
+
+// See http://lists.osgeo.org/pipermail/gdal-dev/2013-November/037429.html
+#define CPL_SERV_H_INCLUDED
+
+#include <geo_simpletags.h>
+#include <cpl_conv.h>
+
+PDAL_C_START
+
+// These functions are available from GDAL, but they
+// aren't exported.
+char CPL_DLL * GTIFGetOGISDefn(GTIF*, GTIFDefn*);
+int CPL_DLL GTIFSetFromOGISDefn(GTIF*, const char*);
+
+PDAL_C_END
+
+#include <pdal/GDALUtils.hpp>
+
+struct StTiff : public ST_TIFF
+{};
+
+namespace pdal
+{
+
+GeotiffSupport::~GeotiffSupport()
+{
+    if (m_gtiff != 0)
+    {
+        GTIFFree(m_gtiff);
+        m_gtiff = 0;
+    }
+    if (m_tiff != 0)
+    {
+        ST_Destroy(m_tiff);
+        m_tiff = 0;
+    }
+}
+
+
+void GeotiffSupport::resetTags()
+{
+    // If we already have m_gtiff and m_tiff, that is because we have
+    // already called GetGTIF once before.  VLRs ultimately drive how the
+    // SpatialReference is defined, not the GeoTIFF keys.
+    if (m_tiff != 0)
+    {
+        ST_Destroy(m_tiff);
+        m_tiff = 0;
+    }
+
+    if (m_gtiff != 0)
+    {
+        GTIFFree(m_gtiff);
+        m_gtiff = 0;
+    }
+
+    m_tiff = (StTiff *)ST_Create();
+
+    return;
+}
+
+
+bool GeotiffSupport::setShortKeys(int tag, void *data, int size)
+{
+    // Make sure struct is 16 bytes.
+#pragma pack(push)
+#pragma pack(1)
+    struct ShortKeyHeader
+    {
+        uint16_t dirVersion;
+        uint16_t keyRev;
+        uint16_t minorRev;
+        uint16_t numKeys;
+    };
+#pragma pack(pop)
+
+    ShortKeyHeader *header = (ShortKeyHeader *)data;
+    int declaredSize = (header->numKeys + 1) * 4;
+    if (size < declaredSize)
+        return false;
+    ST_SetKey(m_tiff, tag, (1 + header->numKeys) * 4, STT_SHORT, data);
+    return true;
+}
+
+
+bool GeotiffSupport::setDoubleKeys(int tag, void *data, int size)
+{
+    ST_SetKey(m_tiff, tag, size / sizeof(double), STT_DOUBLE, data);
+    return true;
+}
+
+
+bool GeotiffSupport::setAsciiKeys(int tag, void *data, int size)
+{
+    ST_SetKey(m_tiff, tag, size, STT_ASCII, data);
+    return true;
+}
+
+
+/// Get the geotiff data associated with a tag.
+/// \param tag - geotiff tag.
+/// \param count - Number of items fetched.
+/// \param data_ptr - Pointer to fill with address of filled data.
+/// \return  Size of data referred to by \c data_ptr
+size_t GeotiffSupport::getKey(int tag, int *count, void **data_ptr) const
+{
+    int st_type;
+
+    if (m_tiff == 0)
+        return 0;
+
+    if (!ST_GetKey(m_tiff, tag, count, &st_type, data_ptr))
+        return 0;
+
+    if (st_type == STT_ASCII)
+        return *count;
+    else if (st_type == STT_SHORT)
+        return 2 * *count;
+    else if (st_type == STT_DOUBLE)
+        return 8 * *count;
+    return 8 * *count;
+}
+
+
+void GeotiffSupport::setTags()
+{
+    m_gtiff = GTIFNewSimpleTags(m_tiff);
+    if (!m_gtiff)
+        throw std::runtime_error("The geotiff keys could not be read "
+            "from VLR records");
+}
+
+
+SpatialReference GeotiffSupport::srs() const
+{
+    GTIFDefn sGTIFDefn;
+    SpatialReference srs;
+
+    if (m_gtiff && GTIFGetDefn(m_gtiff, &sGTIFDefn))
+    {
+        char *pszWKT = GTIFGetOGISDefn(m_gtiff, &sGTIFDefn);
+        if (pszWKT)
+            srs.set(pszWKT);
+    }
+    return srs;
+}
+
+
+void GeotiffSupport::rebuildGTIF()
+{
+    // If we already have m_gtiff and m_tiff, that is because we have
+    // already called GetGTIF once before.  VLRs ultimately drive how the
+    // SpatialReference is defined, not the GeoTIFF keys.
+    if (m_tiff != 0)
+    {
+        ST_Destroy(m_tiff);
+        m_tiff = 0;
+    }
+
+    if (m_gtiff != 0)
+    {
+        GTIFFree(m_gtiff);
+        m_gtiff = 0;
+    }
+
+    m_tiff = (StTiff *)ST_Create();
+
+    // (here it used to read in the VLRs)
+
+    m_gtiff = GTIFNewSimpleTags(m_tiff);
+    if (!m_gtiff)
+        throw std::runtime_error("The geotiff keys could not be read from "
+            "VLR records");
+}
+
+
+void GeotiffSupport::setWkt(const std::string& v)
+{
+    if (!m_gtiff)
+        rebuildGTIF();
+
+    if (v.empty())
+        return;
+
+    if (!GTIFSetFromOGISDefn(m_gtiff, v.c_str()))
+        throw pdal_error("Could not set m_gtiff from WKT");
+
+    if (!GTIFWriteKeys(m_gtiff))
+        throw pdal_error("Unable to write SRS as Geotiff keys.");
+}
+
+
+// Utility functor with accompanying to print GeoTIFF directory.
+struct geotiff_dir_printer
+{
+    geotiff_dir_printer() {}
+
+    std::string output() const
+    {
+        return m_oss.str();
+    }
+    std::string::size_type size() const
+    {
+        return m_oss.str().size();
+    }
+
+    void operator()(char* data, void* /*aux*/)
+    {
+        if (0 != data)
+        {
+            m_oss << data;
+        }
+    }
+
+private:
+    std::ostringstream m_oss;
+};
+
+
+static int pdalGeoTIFFPrint(char* data, void* aux)
+{
+    geotiff_dir_printer* printer = reinterpret_cast<geotiff_dir_printer*>(aux);
+    (*printer)(data, 0);
+    return static_cast<int>(printer->size());
+}
+
+
+std::string GeotiffSupport::getText() const
+{
+    if (m_gtiff == NULL)
+        return std::string("");
+
+    geotiff_dir_printer geotiff_printer;
+    GTIFPrint(m_gtiff, pdalGeoTIFFPrint, &geotiff_printer);
+    const std::string s = geotiff_printer.output();
+    return s;
+}
+
+} // namespace pdal
diff --git a/io/GeotiffSupport.hpp b/io/GeotiffSupport.hpp
new file mode 100644
index 0000000..09ff0a7
--- /dev/null
+++ b/io/GeotiffSupport.hpp
@@ -0,0 +1,80 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/SpatialReference.hpp>
+
+// GDAL
+#include <geo_normalize.h>
+#include <ogr_spatialref.h>
+
+// See http://lists.osgeo.org/pipermail/gdal-dev/2013-November/037429.html
+#define CPL_SERV_H_INCLUDED
+
+#include <string>
+#include <stdexcept>
+
+struct StTiff;
+
+namespace pdal
+{
+
+class PDAL_DLL GeotiffSupport
+{
+public:
+    GeotiffSupport() : m_gtiff(0), m_tiff(0)
+    {}
+    ~GeotiffSupport();
+
+    void resetTags();
+    bool setShortKeys(int tag, void *data, int size);
+    bool setDoubleKeys(int tag, void *data, int size);
+    bool setAsciiKeys(int tag, void *data, int size);
+    size_t getKey(int tag, int *count, void **data_ptr) const;
+    void setTags();
+
+    SpatialReference srs() const;
+    void setWkt(const std::string&);
+
+    std::string getText() const;
+
+private:
+    void rebuildGTIF();
+
+    GTIF *m_gtiff;
+    StTiff *m_tiff;
+};
+
+} // namespace pdal
diff --git a/io/las/HeaderVal.hpp b/io/HeaderVal.hpp
similarity index 100%
rename from io/las/HeaderVal.hpp
rename to io/HeaderVal.hpp
diff --git a/io/Ilvis2MetadataReader.cpp b/io/Ilvis2MetadataReader.cpp
new file mode 100644
index 0000000..88a0f87
--- /dev/null
+++ b/io/Ilvis2MetadataReader.cpp
@@ -0,0 +1,688 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "Ilvis2MetadataReader.hpp"
+
+namespace pdal
+{
+
+void Ilvis2MetadataReader::readMetadataFile(std::string filename, MetadataNode* m)
+{
+    xmlDocPtr doc;
+    xmlNodePtr node;
+
+    doc = xmlReadFile(filename.c_str(), NULL, 0);
+    if (doc == NULL)
+    {
+        return;
+    }
+
+    node = xmlDocGetRootElement(doc);
+
+    parseGranuleMetaDataFile(node, m);
+
+    xmlCleanupParser();
+    xmlMemoryDump();
+}
+
+
+void Ilvis2MetadataReader::parseGranuleMetaDataFile(xmlNodePtr node, MetadataNode* m)
+{
+    assertElementIs(node, "GranuleMetaDataFile");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "DTDVersion");
+    m->add<double>("DTDVersion", extractDouble(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "DataCenterId");
+    m->add("DataCenterID", extractString(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "GranuleURMetaData");
+    parseGranuleURMetaData(child, m);
+
+    child = getNextElementNode(child);
+    assertEndOfElements(child);
+}
+
+void Ilvis2MetadataReader::parseGranuleURMetaData(xmlNodePtr node, MetadataNode* m)
+{
+    assertElementIs(node, "GranuleURMetaData");
+
+    xmlNodePtr child;
+
+    child = getFirstChildElementNode(node);
+    assertElementIs(child, "GranuleUR");
+    m->add("GranuleUR", extractString(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "DbID");
+    m->add<long>("DbID", extractLong(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "InsertTime");
+    m->add("InsertTime", extractString(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "LastUpdate");
+    m->add("LastUpdate", extractString(child));
+
+    child = getNextElementNode(child);
+    if (nodeElementIs(child, "CollectionMetaData"))
+    {
+        parseCollectionMetaData(child, m);
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "DataFiles"))
+    {
+        parseDataFiles(child, m);
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "ECSDataGranule"))
+    {
+        parseECSDataGranule(child, m);
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "RangeDateTime"))
+    {
+        parseRangeDateTime(child, m);
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "SpatialDomainContainer"))
+    {
+        parseSpatialDomainContainer(child, m);
+        child = getNextElementNode(child);
+    }
+
+    while (nodeElementIs(child, "Platform"))
+    {
+        MetadataNode plat = m->addList("Platform");
+        parsePlatform(child, &plat);
+        child = getNextElementNode(child);
+    }
+
+    while (nodeElementIs(child, "Campaign"))
+    {
+        parseCampaign(child, m);
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "PSAs"))
+    {
+        parsePSAs(child, m);
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "BrowseProduct"))
+    {
+        parseXXProduct("Browse", child, m);
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "PHProduct"))
+    {
+        parseXXProduct("PH", child, m);
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "QAProduct"))
+    {
+        parseXXProduct("QA", child, m);
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "MPProduct"))
+    {
+        parseXXProduct("MP", child, m);
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseCollectionMetaData(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "CollectionMetaData");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "ShortName");
+    m->add("CollectionShortName", extractString(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "VersionID");
+    m->add("CollectionVersionID", extractInt(child));
+
+    child = getNextElementNode(child);
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseDataFiles(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "DataFiles");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "DataFileContainer");
+
+    while(nodeElementIs(child, "DataFileContainer"))
+    {
+        MetadataNode n = m->addList("DataFile");
+        parseDataFileContainer(child, &n);
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseDataFileContainer(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "DataFileContainer");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "DistributedFileName");
+    m->add("DistributedFileName", extractString(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "FileSize");
+    m->add("FileSize", extractInt(child));
+
+    child = getNextElementNode(child);
+    if (nodeElementIs(child, "ChecksumType"))
+    {
+        m->add("ChecksumType", extractString(child));
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "Checksum"))
+    {
+        m->add("Checksum", extractString(child));
+        child = getNextElementNode(child);
+    }
+
+    if (nodeElementIs(child, "ChecksumOrigin"))
+    {
+        m->add("ChecksumOrigin", extractString(child));
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseECSDataGranule(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "ECSDataGranule");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    if (nodeElementIs(child, "SizeMBECSDataGranule"))
+    {
+        m->add("SizeMBECSDataGranule", extractDouble(child));
+        child = getNextElementNode(child);
+    }
+
+    assertElementIs(child, "LocalGranuleID");
+    m->add("LocalGranuleID", extractString(child));
+
+    child = getNextElementNode(child);
+    if (nodeElementIs(child, "ProductionDateTime"))
+    {
+        m->add("ProductionDateTime", extractString(child));
+        child = getNextElementNode(child);
+    }
+
+    assertElementIs(child, "LocalVersionID");
+    m->add("LocalVersionID", extractString(child));
+
+    child = getNextElementNode(child);
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseRangeDateTime(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "RangeDateTime");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "RangeEndingTime");
+    m->add("RangeEndingTime", extractString(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "RangeEndingDate");
+    m->add("RangeEndingDate", extractString(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "RangeBeginningTime");
+    m->add("RangeBeginningTime", extractString(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "RangeBeginningDate");
+    m->add("RangeBeginningDate", extractString(child));
+
+    child = getNextElementNode(child);
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseSpatialDomainContainer(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "SpatialDomainContainer");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    if (nodeElementIs(child, "HorizontalSpatialDomainContainer"))
+    {
+        xmlNodePtr subChild = getFirstChildElementNode(child);
+        assertElementIs(subChild, "GPolygon");
+        parseGPolygon(subChild, m);
+
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseGPolygon(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "GPolygon");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "Boundary");
+
+    // The number of boundaries is essentially the number of sub-polygons
+    int numBoundaries = countChildElements(node, "Boundary");
+    std::vector<GEOSGeom> poly(numBoundaries); // size shall never change
+    GEOSGeom fullPoly;
+    int polyNum = 0;
+
+    initGEOS(NULL, NULL);
+
+    while (nodeElementIs(child, "Boundary"))
+    {
+        // There must be at least 3 points to be valid per the schema.
+        int numPoints = countChildElements(child, "Point");
+        if (numPoints < 3)
+        {
+            std::ostringstream oss;
+            oss << "Found a polygon boundary with less than 3 points, " <<
+                "invalid for this schema";
+            throw pdal_error(oss.str());
+        }
+
+        GEOSCoordSeq points = GEOSCoordSeq_create(numPoints + 1, 2);
+        xmlNodePtr bdChild = getFirstChildElementNode(child);
+        int ptNum = 0;
+
+        while (nodeElementIs(bdChild, "Point"))
+        {
+            xmlNodePtr ptChild = getFirstChildElementNode(bdChild);
+            assertElementIs(ptChild, "PointLongitude");
+            double ptLon = extractDouble(ptChild);
+
+            ptChild = getNextElementNode(ptChild);
+            assertElementIs(ptChild, "PointLatitude");
+            double ptLat = extractDouble(ptChild);
+
+            ptChild = getNextElementNode(ptChild);
+            assertEndOfElements(ptChild);
+
+            GEOSCoordSeq_setX(points, ptNum, ptLon);
+            GEOSCoordSeq_setY(points, ptNum, ptLat);
+
+            // In the file, the loop is not closed; GEOS requires polygons
+            // to be closed, so we'll do it ourselves.
+            if (ptNum == 0)
+            {
+                GEOSCoordSeq_setX(points, numPoints, ptLon);
+                GEOSCoordSeq_setY(points, numPoints, ptLat);
+            }
+
+            ptNum += 1;
+            bdChild = getNextElementNode(bdChild);
+        }
+
+        GEOSGeom ring = GEOSGeom_createLinearRing(points);
+        poly[polyNum] = GEOSGeom_createPolygon(ring, NULL, 0);
+
+        polyNum += 1;
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+
+    // If only one sub-polygon, just make a POLYGON WKT, else make it a MULTIPOLYGON
+    if (numBoundaries > 1)
+    {
+        fullPoly = GEOSGeom_createCollection(
+                GEOS_MULTIPOLYGON, poly.data(), numBoundaries);
+    }
+    else
+    {
+        fullPoly = poly[0];
+    }
+    GEOSWKTWriter * writer = GEOSWKTWriter_create();
+    GEOSWKTWriter_setRoundingPrecision(writer, 5);
+
+    std::string polyStr = GEOSWKTWriter_write(writer, fullPoly);
+
+    m->add("ConvexHull", polyStr);
+
+    finishGEOS();
+}
+
+
+void Ilvis2MetadataReader::parsePlatform(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "Platform");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "PlatformShortName");
+
+    m->add("PlatformShortName", extractString(child));
+
+    child = getNextElementNode(child);
+    while(nodeElementIs(child, "Instrument"))
+    {
+        MetadataNode inst = m->addList("Instrument");
+        parseInstrument(child, &inst);
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseInstrument(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "Instrument");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "InstrumentShortName");
+    m->add("InstrumentShortName", extractString(child));
+
+    child = getNextElementNode(child);
+
+    while (nodeElementIs(child, "Sensor"))
+    {
+        MetadataNode sens = m->addList("Sensor");
+        parseSensor(child, &sens);
+        child = getNextElementNode(child);
+    }
+
+    while (nodeElementIs(child, "OperationMode"))
+    {
+        m->addList("OperationMode", extractString(child));
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseSensor(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "Sensor");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "SensorShortName");
+    m->add("SensorShortName", extractString(child));
+
+    child = getNextElementNode(child);
+    while (nodeElementIs(child, "SensorCharacteristic"))
+    {
+        MetadataNode n = m->addList("SensorCharacteristic");
+        parseSensorCharacteristic(child, &n);
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseSensorCharacteristic(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "SensorCharacteristic");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "SensorCharacteristicName");
+    m->add("CharacteristicName", extractString(child));
+
+    child = getNextElementNode(child);
+    assertElementIs(child, "SensorCharacteristicValue");
+    m->add("CharacteristicValue", extractString(child));
+
+    child = getNextElementNode(child);
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parseCampaign(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "Campaign");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "CampaignShortName");
+    std::string cName = extractString(child);
+    m->addList("Campaign", cName);
+
+    child = getNextElementNode(child);
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parsePSAs(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "PSAs");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    while (nodeElementIs(child, "PSA"))
+    {
+        MetadataNode n = m->addList("PSA");
+        parsePSA(child, &n);
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+}
+
+
+void Ilvis2MetadataReader::parsePSA(xmlNodePtr node, MetadataNode * m)
+{
+    assertElementIs(node, "PSA");
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    assertElementIs(child, "PSAName");
+    m->add("PSAName", extractString(child));
+
+    child = getNextElementNode(child);
+    while (nodeElementIs(child, "PSAValue"))
+    {
+        m->addList("PSAValue", extractString(child));
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+}
+
+
+// Since the Browse, PH, QA, and MP product nodes have the same structure
+// just differing prefixes, they can share this code.
+void Ilvis2MetadataReader::parseXXProduct(std::string type, xmlNodePtr node, MetadataNode * m)
+{
+    std::string fullBase = type + "Product";
+    std::string fullSub = type + "GranuleId";
+    std::string mdName = type + "ProductGranuleId";
+
+    assertElementIs(node, fullBase);
+
+    xmlNodePtr child = getFirstChildElementNode(node);
+    while (nodeElementIs(child, fullSub))
+    {
+        m->addList(mdName, extractString(child));
+        child = getNextElementNode(child);
+    }
+
+    assertEndOfElements(child);
+}
+
+
+// BEGIN PRIVATE METHODS
+
+
+std::string Ilvis2MetadataReader::extractString(xmlNodePtr node)
+{
+    std::string nodeStr((char*)node->children->content);
+    return nodeStr;
+}
+
+double Ilvis2MetadataReader::extractDouble(xmlNodePtr node)
+{
+    return atof((char*)node->children->content);
+}
+
+int Ilvis2MetadataReader::extractInt(xmlNodePtr node)
+{
+    return atoi((char*)node->children->content);
+}
+
+long Ilvis2MetadataReader::extractLong(xmlNodePtr node)
+{
+    return atol((char*)node->children->content);
+}
+
+
+// private
+
+// Skip all non-element nodes, just get the next element node.
+xmlNodePtr Ilvis2MetadataReader::getNextElementNode(xmlNodePtr node)
+{
+    node = node->next;
+    while (node && node->type != XML_ELEMENT_NODE)
+    {
+        node = node->next;
+    }
+
+    return node;
+}
+
+// Skip all non-element child nodes, get the first element child node
+xmlNodePtr Ilvis2MetadataReader::getFirstChildElementNode(xmlNodePtr node)
+{
+    xmlNodePtr child = node->children;
+    if (!child)
+    {
+        return NULL;
+    }
+    else if (child->type == XML_ELEMENT_NODE)
+    {
+        return child;
+    }
+    else
+    {
+        return getNextElementNode(child);
+    }
+}
+
+// Verifies the name of the node matches what's expected
+bool Ilvis2MetadataReader::nodeElementIs(xmlNodePtr node, std::string expected)
+{
+    if (!node)
+    {
+        return false;
+    }
+
+    return xmlStrcmp(node->name,
+            reinterpret_cast<const xmlChar*>(expected.c_str())) == 0;
+}
+
+// Throws an error if the next element is not what it expects
+void Ilvis2MetadataReader::assertElementIs(xmlNodePtr node, std::string expected)
+{
+    if (!node || !nodeElementIs(node, expected))
+    {
+        errWrongElement(node, expected);
+    }
+}
+
+// Throws an error if the node is not null
+void Ilvis2MetadataReader::assertEndOfElements(xmlNodePtr node)
+{
+    if (node)
+    {
+        errExpectedEnd(node);
+    }
+}
+
+// Counts the number of child element nodes with a given name
+int Ilvis2MetadataReader::countChildElements(xmlNodePtr node, std::string childName)
+{
+    xmlNodePtr child = getFirstChildElementNode(node);
+    int ctr = 0;
+
+    while (child)
+    {
+        if (nodeElementIs(child, childName))
+        {
+            ctr += 1;
+        }
+        child = getNextElementNode(child);
+    }
+
+    return ctr;
+}
+
+
+// Errors used when a file doesn't match the schema.
+void Ilvis2MetadataReader::errWrongElement(xmlNodePtr node, std::string expected)
+{
+    std::ostringstream oss;
+    oss << "Expected element '" << expected << "', found '" << node->name << "'";
+    throw pdal_error(oss.str());
+}
+
+void Ilvis2MetadataReader::errExpectedEnd(xmlNodePtr node)
+{
+    std::ostringstream oss;
+    oss << "Expected to find no more elements, found '" << node->name << "'";
+    throw pdal_error(oss.str());
+}
+
+} // namespace pdal
+
diff --git a/io/Ilvis2MetadataReader.hpp b/io/Ilvis2MetadataReader.hpp
new file mode 100644
index 0000000..d789361
--- /dev/null
+++ b/io/Ilvis2MetadataReader.hpp
@@ -0,0 +1,107 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Metadata.hpp>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <geos_c.h>
+#include <string>
+#include <iostream>
+#include <stdlib.h>
+
+namespace pdal
+{
+
+class PDAL_DLL Ilvis2MetadataReader
+{
+public:
+    void readMetadataFile(std::string filename, MetadataNode* m);
+
+protected:
+    // These methods are written to parse specific nodes.  It doesn't
+    // do full validation, but does check to make sure things are in
+    // the order it expects them to be in.
+
+    void parseGranuleMetaDataFile(xmlNodePtr node, MetadataNode* m);
+    void parseGranuleURMetaData(xmlNodePtr node, MetadataNode* m);
+    void parseCollectionMetaData(xmlNodePtr node, MetadataNode* m);
+    void parseDataFiles(xmlNodePtr node, MetadataNode* m);
+    void parseDataFileContainer(xmlNodePtr node, MetadataNode* m);
+    void parseECSDataGranule(xmlNodePtr node, MetadataNode* m);
+    void parseRangeDateTime(xmlNodePtr node, MetadataNode* m);
+
+    void parsePlatform(xmlNodePtr node, MetadataNode* m);
+    void parseInstrument(xmlNodePtr node, MetadataNode* m);
+    void parseSensor(xmlNodePtr node, MetadataNode* m);
+    void parseSensorCharacteristic(xmlNodePtr node, MetadataNode* m);
+    void parseCampaign(xmlNodePtr node, MetadataNode* m);
+    void parsePSAs(xmlNodePtr node, MetadataNode* m);
+    void parsePSA(xmlNodePtr node, MetadataNode* m);
+    void parseXXProduct(std::string type, xmlNodePtr node, MetadataNode* m);
+
+    void parseSpatialDomainContainer(xmlNodePtr node, MetadataNode* m);
+    void parseGPolygon(xmlNodePtr node, MetadataNode* m);
+    void parseBoundary(xmlNodePtr node, MetadataNode* m);
+    void parsePoint(xmlNodePtr node, MetadataNode* m);
+
+private:
+    // These private methods are mostly helper functions for proessing
+    // the heirarchy and contents of the various XML node objects that
+    // are returned by libxml.
+
+    std::string extractString(xmlNodePtr node);
+    double extractDouble(xmlNodePtr node);
+    int extractInt(xmlNodePtr node);
+    long extractLong(xmlNodePtr node);
+
+    // These two methods are useful to help ignore "empty" text nodes
+    // caused by indentation, etc.  These will simply grab the actual
+    // element nodes directly.
+    // Note that due to the way LIBXML parses things, a child points
+    // to its own siblings; the parent only points to the first child.
+    xmlNodePtr getNextElementNode(xmlNodePtr node);
+    xmlNodePtr getFirstChildElementNode(xmlNodePtr node);
+
+    bool nodeElementIs(xmlNodePtr node, std::string expected);
+    void assertElementIs(xmlNodePtr node, std::string expected);
+    void assertEndOfElements(xmlNodePtr node);
+    int countChildElements(xmlNodePtr node, std::string childName);
+    void errWrongElement(xmlNodePtr node, std::string expected);
+    void errExpectedEnd(xmlNodePtr node);
+};
+
+}
diff --git a/io/Ilvis2Reader.cpp b/io/Ilvis2Reader.cpp
new file mode 100644
index 0000000..bea3db5
--- /dev/null
+++ b/io/Ilvis2Reader.cpp
@@ -0,0 +1,304 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "Ilvis2Reader.hpp"
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <algorithm>
+#include <cmath>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "readers.ilvis2",
+    "ILVIS2 Reader",
+    "http://pdal.io/stages/readers.ilvis2.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, Ilvis2Reader, Reader, s_info)
+
+std::string Ilvis2Reader::getName() const { return s_info.name; }
+
+std::istream& operator >> (std::istream& in, Ilvis2Reader::IlvisMapping& mval)
+{
+    std::string s;
+
+    in >> s;
+    s = Utils::toupper(s);
+    
+    static std::map<std::string, Ilvis2Reader::IlvisMapping> m =
+        { { "INVALID", Ilvis2Reader::IlvisMapping::INVALID }, 
+          { "LOW", Ilvis2Reader::IlvisMapping::LOW }, 
+          { "HIGH", Ilvis2Reader::IlvisMapping::HIGH }, 
+          { "ALL", Ilvis2Reader::IlvisMapping::ALL } };
+
+    mval = m[s];
+    return in;
+}
+
+
+std::ostream& operator<<(std::ostream& out,
+    const Ilvis2Reader::IlvisMapping& mval)
+{
+    switch (mval)
+    {
+    case Ilvis2Reader::IlvisMapping::INVALID:
+        out << "Invalid";
+    case Ilvis2Reader::IlvisMapping::LOW:
+        out << "Low";
+    case Ilvis2Reader::IlvisMapping::HIGH:
+        out << "High";
+    case Ilvis2Reader::IlvisMapping::ALL:
+        out << "All";
+    }
+    return out;
+}
+
+
+void Ilvis2Reader::addArgs(ProgramArgs& args)
+{
+    args.add("mapping", "Mapping for values", m_mapping, IlvisMapping::ALL);
+    args.add("metadata", "Metadata file", m_metadataFile);
+}
+
+
+void Ilvis2Reader::addDimensions(PointLayoutPtr layout)
+{
+    layout->registerDim(Dimension::Id::LvisLfid);
+    layout->registerDim(Dimension::Id::ShotNumber);
+    layout->registerDim(Dimension::Id::GpsTime);
+    layout->registerDim(Dimension::Id::LongitudeCentroid);
+    layout->registerDim(Dimension::Id::LatitudeCentroid);
+    layout->registerDim(Dimension::Id::ElevationCentroid);
+    layout->registerDim(Dimension::Id::LongitudeLow);
+    layout->registerDim(Dimension::Id::LatitudeLow);
+    layout->registerDim(Dimension::Id::ElevationLow);
+    layout->registerDim(Dimension::Id::LongitudeHigh);
+    layout->registerDim(Dimension::Id::LatitudeHigh);
+    layout->registerDim(Dimension::Id::ElevationHigh);
+    layout->registerDim(Dimension::Id::X);
+    layout->registerDim(Dimension::Id::Y);
+    layout->registerDim(Dimension::Id::Z);
+}
+
+
+Dimension::IdList Ilvis2Reader::getDefaultDimensions()
+{
+    using namespace pdal::Dimension;
+    Dimension::IdList ids;
+
+    ids.push_back(Id::GpsTime);
+    ids.push_back(Id::Y);
+    ids.push_back(Id::X);
+    ids.push_back(Id::Z);
+    return ids;
+}
+
+
+void Ilvis2Reader::initialize(PointTableRef)
+{
+    if (!m_metadataFile.empty() && !FileUtils::fileExists(m_metadataFile))
+    {
+        std::ostringstream oss;
+        oss << "Invalid metadata file: '" << m_metadataFile << "'";
+        throw pdal_error(oss.str());
+    }
+
+    // Data are WGS84 (4326) with ITRF2000 datum (6656)
+    // See http://nsidc.org/data/docs/daac/icebridge/ilvis2/index.html for
+    // background
+    SpatialReference ref("EPSG:4326");
+    setSpatialReference(m_metadata, ref);
+}
+
+
+template <typename T>
+T convert(const StringList& s, const std::string& name, size_t fieldno)
+{
+    T output;
+    if (!Utils::fromString(s[fieldno], output))
+    {
+        std::stringstream oss;
+        oss << "Unable to convert " << name << ", " << s[fieldno] <<
+            ", to double";
+        throw pdal_error(oss.str());
+    }
+
+    return output;
+}
+
+
+void Ilvis2Reader::readPoint(PointRef& point, StringList s,
+    std::string pointMap)
+{
+    point.setField(pdal::Dimension::Id::LvisLfid,
+        convert<unsigned>(s, "LVIS_LFID", 0));
+    point.setField(pdal::Dimension::Id::ShotNumber,
+        convert<unsigned>(s, "SHOTNUMBER", 1));
+    point.setField(pdal::Dimension::Id::GpsTime,
+        convert<double>(s, "GPSTIME", 2));
+    point.setField(pdal::Dimension::Id::LongitudeCentroid,
+        Utils::normalizeLongitude(convert<double>(s, "LONGITUDE_CENTROID", 3)));
+    point.setField(pdal::Dimension::Id::LatitudeCentroid,
+        convert<double>(s, "LATITUDE_CENTROID", 4));
+    point.setField(pdal::Dimension::Id::ElevationCentroid,
+        convert<double>(s, "ELEVATION_CENTROID", 5));
+    point.setField(pdal::Dimension::Id::LongitudeLow,
+        Utils::normalizeLongitude(convert<double>(s, "LONGITUDE_LOW", 6)));
+    point.setField(pdal::Dimension::Id::LatitudeLow,
+        convert<double>(s, "LATITUDE_LOW", 7));
+    point.setField(pdal::Dimension::Id::ElevationLow,
+        convert<double>(s, "ELEVATION_LOW", 8));
+    point.setField(pdal::Dimension::Id::LongitudeHigh,
+        Utils::normalizeLongitude(convert<double>(s, "LONGITUDE_HIGH", 9)));
+    point.setField(pdal::Dimension::Id::LatitudeHigh,
+        convert<double>(s, "LATITUDE_HIGH", 10));
+    point.setField(pdal::Dimension::Id::ElevationHigh,
+        convert<double>(s, "ELEVATION_HIGH", 11));
+
+    double x, y, z;
+    pdal::Dimension::Id xd, yd, zd;
+
+    xd = m_layout->findDim("LONGITUDE_" + pointMap);
+    yd = m_layout->findDim("LATITUDE_" + pointMap);
+    zd = m_layout->findDim("ELEVATION_" + pointMap);
+
+    x = point.getFieldAs<double>(xd);
+    y = point.getFieldAs<double>(yd);
+    z = point.getFieldAs<double>(zd);
+
+    point.setField(pdal::Dimension::Id::X, x);
+    point.setField(pdal::Dimension::Id::Y, y);
+    point.setField(pdal::Dimension::Id::Z, z);
+}
+
+
+void Ilvis2Reader::ready(PointTableRef table)
+{
+    if (!m_metadataFile.empty())
+    {
+        m_mdReader.readMetadataFile(m_metadataFile, &m_metadata);
+    }
+
+    static const int HeaderSize = 2;
+    std::string line;
+
+    m_lineNum = 0;
+    m_stream.open(m_filename);
+    m_layout = table.layout();
+    m_resample = false;
+    for (size_t i = 0; i < HeaderSize; ++i)
+    {
+        std::getline(m_stream, line);
+        m_lineNum++;
+    }
+}
+
+
+bool Ilvis2Reader::processOne(PointRef& point)
+{
+    std::string line;
+
+// Format:
+// LVIS_LFID SHOTNUMBER TIME LONGITUDE_CENTROID LATITUDE_CENTROID ELEVATION_CENTROID LONGITUDE_LOW LATITUDE_LOW ELEVATION_LOW LONGITUDE_HIGH LATITUDE_HIGH ELEVATION_HIGH
+
+    // This handles the second time through for this data line when we have
+    // an "ALL" mapping and the high and low elevations are different.
+    if (m_resample)
+    {
+        readPoint(point, m_fields, "HIGH");
+        m_resample = false;
+        return true;
+    }
+
+    if (!std::getline(m_stream, line))
+        return false;
+    m_fields = Utils::split2(line, ' ');
+    if (m_fields.size() != 12)
+    {
+        std::stringstream oss;
+        oss << getName() << ": Invalid format for line " << m_lineNum <<
+            ".  Expected 12 fields, got " << m_fields.size() << ".";
+        throw pdal_error(oss.str());
+    }
+
+    double low_elev = convert<double>(m_fields, "ELEVATION_LOW", 8);
+    double high_elev = convert<double>(m_fields, "ELEVATION_HIGH", 11);
+
+    // write LOW point if specified, or for ALL
+    if (m_mapping == IlvisMapping::LOW || m_mapping == IlvisMapping::ALL)
+    {
+        readPoint(point, m_fields, "LOW");
+        // If we have ALL mapping and the high elevation is different
+        // from that of the low elevation, we'll a second point with the
+        // high elevation.
+        if (m_mapping == IlvisMapping::ALL && (low_elev != high_elev))
+            m_resample = true;
+    }
+    else if (m_mapping == IlvisMapping::HIGH)
+        readPoint(point, m_fields, "HIGH");
+    return true;
+}
+
+
+point_count_t Ilvis2Reader::read(PointViewPtr view, point_count_t count)
+{
+    PointId idx = view->size();
+    point_count_t numRead = 0;
+
+    PointRef point = PointRef(*view, 0);
+    while (numRead < count)
+    {
+        point.setPointId(idx++);
+        if (!processOne(point))
+            break;
+        if (m_cb)
+            m_cb(*view, idx);
+        numRead++;
+    }
+
+    return numRead;
+}
+
+
+void Ilvis2Reader::done(PointTableRef table)
+{
+
+
+}
+
+} // namespace pdal
+
diff --git a/io/ilvis2/Ilvis2Reader.hpp b/io/Ilvis2Reader.hpp
similarity index 100%
rename from io/ilvis2/Ilvis2Reader.hpp
rename to io/Ilvis2Reader.hpp
diff --git a/io/las/LasError.hpp b/io/LasError.hpp
similarity index 100%
rename from io/las/LasError.hpp
rename to io/LasError.hpp
diff --git a/io/LasHeader.cpp b/io/LasHeader.cpp
new file mode 100644
index 0000000..9dfaaf6
--- /dev/null
+++ b/io/LasHeader.cpp
@@ -0,0 +1,486 @@
+/******************************************************************************
+ * Copyright (c) 2008, Mateusz Loskot
+ * Copyright (c) 2008, Phil Vachon
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of the Martin Isenburg or Iowa Department
+ *       of Natural Resources nor the names of its contributors may be
+ *       used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#include "LasHeader.hpp"
+
+#include <pdal/pdal_config.hpp>
+#include <pdal/Scaling.hpp>
+#include <pdal/util/Utils.hpp>
+
+#include "LasSummaryData.hpp"
+
+#include "GeotiffSupport.hpp"
+
+namespace pdal
+{
+
+const std::string LasHeader::FILE_SIGNATURE("LASF");
+#ifndef _WIN32
+const size_t LasHeader::LEGACY_RETURN_COUNT;
+const size_t LasHeader::RETURN_COUNT;
+#endif
+
+std::string GetDefaultSoftwareId()
+{
+    std::string ver(PDAL_VERSION_STRING);
+    std::stringstream oss;
+    std::ostringstream revs;
+    revs << GetSHA1();
+
+
+    oss << "PDAL " << ver << " (" << revs.str().substr(0, 6) <<")";
+    return oss.str();
+}
+
+LasHeader::LasHeader() : m_fileSig(FILE_SIGNATURE), m_sourceId(0),
+    m_globalEncoding(0), m_versionMinor(2), m_systemId(getSystemIdentifier()),
+    m_createDOY(0), m_createYear(0), m_vlrOffset(0), m_pointOffset(0),
+    m_vlrCount(0), m_pointFormat(0), m_pointLen(0), m_pointCount(0),
+    m_isCompressed(false), m_eVlrOffset(0), m_eVlrCount(0)
+{
+    std::time_t now;
+    std::time(&now);
+    std::tm* ptm = std::gmtime(&now);
+    if (ptm)
+    {
+        m_createDOY = static_cast<uint16_t>(ptm->tm_yday);
+        m_createYear = static_cast<uint16_t>(ptm->tm_year + 1900);
+    }
+
+    m_pointLen = basePointLen(m_pointFormat);
+    m_pointCountByReturn.fill(0);
+    m_scales.fill(1.0);
+    m_offsets.fill(0.0);
+}
+
+
+void LasHeader::setSummary(const LasSummaryData& summary)
+{
+    m_pointCount = summary.getTotalNumPoints();
+    for (size_t num = 0; num < RETURN_COUNT; ++num)
+        m_pointCountByReturn[num] = (int)summary.getReturnCount(num);
+    m_bounds = summary.getBounds();
+}
+
+
+void LasHeader::setScaling(const Scaling& scaling)
+{
+    const double& xs = scaling.m_xXform.m_scale.m_val;
+    const double& ys = scaling.m_yXform.m_scale.m_val;
+    const double& zs = scaling.m_zXform.m_scale.m_val;
+    if (xs == 0)
+        throw std::invalid_argument("X scale of 0.0 is invalid!");
+
+    if (ys == 0)
+        throw std::invalid_argument("Y scale of 0.0 is invalid!");
+
+    if (zs == 0)
+        throw std::invalid_argument("Z scale of 0.0 is invalid!");
+
+    m_scales[0] = xs;
+    m_scales[1] = ys;
+    m_scales[2] = zs;
+
+    m_offsets[0] = scaling.m_xXform.m_offset.m_val;
+    m_offsets[1] = scaling.m_yXform.m_offset.m_val;
+    m_offsets[2] = scaling.m_zXform.m_offset.m_val;
+}
+
+
+uint16_t LasHeader::basePointLen(uint8_t type)
+{
+    switch (type)
+    {
+    case 0:
+        return 20;
+    case 1:
+        return 28;
+    case 2:
+        return 26;
+    case 3:
+        return 34;
+    case 6:
+        return 30;
+    case 7:
+        return 36;
+    case 8:
+        return 38;
+    }
+    return 0;
+}
+
+
+bool LasHeader::valid() const
+{
+    if (m_fileSig != FILE_SIGNATURE)
+        return false;
+    if (m_versionMinor > 10)
+        return false;
+    if (m_createDOY > 366)
+        return false;
+    if (m_createYear < 1970 || m_createYear > 2100)
+       return false;
+    return true;
+}
+
+
+void LasHeader::get(ILeStream& in, Uuid& uuid)
+{
+    char buf[uuid.size];
+
+    in.get(buf, uuid.size);
+    uuid.unpack(buf);
+}
+
+
+void LasHeader::put(OLeStream& out, Uuid uuid)
+{
+    char buf[uuid.size];
+
+    uuid.pack(buf);
+    out.put(buf, uuid.size);
+}
+
+
+Dimension::IdList LasHeader::usedDims() const
+{
+    using namespace Dimension;
+
+    Dimension::Id dims[] = { Id::ReturnNumber, Id::NumberOfReturns,
+        Id::X, Id::Y, Id::Z, Id::Intensity, Id::ScanChannel,
+        Id::ScanDirectionFlag, Id::EdgeOfFlightLine, Id::Classification,
+        Id::UserData, Id::ScanAngleRank, Id::PointSourceId };
+
+    Dimension::IdList ids(std::begin(dims), std::end(dims));
+
+    if (hasTime())
+        ids.push_back(Id::GpsTime);
+    if (hasColor())
+    {
+        ids.push_back(Id::Red);
+        ids.push_back(Id::Green);
+        ids.push_back(Id::Blue);
+    }
+    if (hasInfrared())
+        ids.push_back(Id::Infrared);
+
+    return ids;
+}
+
+void LasHeader::setSrs()
+{
+    bool useWkt = false;
+
+    if (incompatibleSrs())
+    {
+        m_log->get(LogLevel::Error) << "Invalid SRS specification.  "
+            "GeoTiff not allowed with point formats 6 - 10." << std::endl;
+    }
+    else if (findVlr(TRANSFORM_USER_ID, WKT_RECORD_ID) &&
+        findVlr(TRANSFORM_USER_ID, GEOTIFF_DIRECTORY_RECORD_ID))
+    {
+        m_log->get(LogLevel::Debug) << "File contains both "
+            "WKT and GeoTiff VLRs which is disallowed." << std::endl;
+    }
+    else
+        useWkt = (m_versionMinor >= 4);
+
+    try
+    {
+        if (useWkt)
+            setSrsFromWkt();
+        else
+            setSrsFromGeotiff();
+    }
+    catch (...)
+    {
+        m_log->get(LogLevel::Error) << "Could not create an SRS" << std::endl;
+    }
+}
+
+
+LasVLR *LasHeader::findVlr(const std::string& userId,
+    uint16_t recordId)
+{
+    for (auto vi = m_vlrs.begin(); vi != m_vlrs.end(); ++vi)
+    {
+        LasVLR& vlr = *vi;
+        if (vlr.matches(userId, recordId))
+            return &vlr;
+    }
+    return NULL;
+}
+
+
+void LasHeader::setSrsFromWkt()
+{
+    LasVLR *vlr = findVlr(TRANSFORM_USER_ID, WKT_RECORD_ID);
+    if (!vlr)
+        vlr = findVlr(LIBLAS_USER_ID, WKT_RECORD_ID);
+    if (!vlr || vlr->dataLen() == 0)
+        return;
+
+    // There is supposed to be a NULL byte at the end of the data,
+    // but sometimes there isn't because some people don't follow the
+    // rules.  If there is a NULL byte, don't stick it in the
+    // wkt string.
+    size_t len = vlr->dataLen();
+    const char *c = vlr->data() + len - 1;
+    if (*c == 0)
+        len--;
+    m_srs.set(std::string(vlr->data(), len));
+}
+
+
+void LasHeader::setSrsFromGeotiff()
+{
+// These are defined in geo_simpletags.h
+// We're not including that file because it includes
+// geotiff.h, which includes a ton of other stuff
+// that might conflict with the messy libgeotiff/GDAL
+// symbol mess
+
+#define STT_SHORT   1
+#define STT_DOUBLE  2
+#define STT_ASCII   3
+
+    GeotiffSupport geotiff;
+    geotiff.resetTags();
+
+    LasVLR *vlr;
+
+    vlr = findVlr(TRANSFORM_USER_ID, GEOTIFF_DIRECTORY_RECORD_ID);
+    // We must have a directory entry.
+    if (!vlr)
+        return;
+    if (!geotiff.setShortKeys(vlr->recordId(), (void *)vlr->data(),
+        (int)vlr->dataLen()))
+    {
+        std::ostringstream oss;
+
+        oss << "Invalid GeoTIFF directory record.  Can't "
+            "interpret spatial reference.";
+        throw pdal_error(oss.str());
+    }
+
+    vlr = findVlr(TRANSFORM_USER_ID, GEOTIFF_DOUBLES_RECORD_ID);
+    if (vlr && !vlr->isEmpty())
+        geotiff.setDoubleKeys(vlr->recordId(), (void *)vlr->data(),
+            (int)vlr->dataLen());
+    vlr = findVlr(TRANSFORM_USER_ID, GEOTIFF_ASCII_RECORD_ID);
+    if (vlr && !vlr->isEmpty())
+        geotiff.setAsciiKeys(vlr->recordId(), (void *)vlr->data(),
+            (int)vlr->dataLen());
+
+    geotiff.setTags();
+    SpatialReference gtiffSrs = geotiff.srs();
+    if (!gtiffSrs.empty())
+        m_srs = gtiffSrs;
+
+    m_log->get(LogLevel::Debug5) << "GeoTIFF keys: " << geotiff.getText() <<
+        std::endl;
+}
+
+
+ILeStream& operator>>(ILeStream& in, LasHeader& h)
+{
+    uint8_t versionMajor;
+    uint32_t legacyPointCount;
+    uint32_t legacyReturnCount;
+
+    in.get(h.m_fileSig, 4);
+    if (!Utils::iequals(h.m_fileSig, "LASF"))
+    {
+        throw pdal::pdal_error("File signature is not 'LASF', "
+            "is this an LAS/LAZ file?");
+    }
+    in >> h.m_sourceId >> h.m_globalEncoding;
+    LasHeader::get(in, h.m_projectUuid);
+    in >> versionMajor >> h.m_versionMinor;
+    in.get(h.m_systemId, 32);
+
+    in.get(h.m_softwareId, 32);
+    in >> h.m_createDOY >> h.m_createYear >> h.m_vlrOffset >>
+        h.m_pointOffset >> h.m_vlrCount >> h.m_pointFormat >>
+        h.m_pointLen >> legacyPointCount;
+    h.m_pointCount = legacyPointCount;
+
+    // Although it isn't part of the LAS spec, the two high bits have been used
+    // to indicate compression, though only the high bit is currently used.
+    if (h.m_pointFormat & 0x80)
+        h.setCompressed(true);
+    h.m_pointFormat &= ~0xC0;
+
+    for (size_t i = 0; i < LasHeader::LEGACY_RETURN_COUNT; ++i)
+    {
+        in >> legacyReturnCount;
+        h.m_pointCountByReturn[i] = legacyReturnCount;
+    }
+
+    in >> h.m_scales[0] >> h.m_scales[1] >> h.m_scales[2];
+    in >> h.m_offsets[0] >> h.m_offsets[1] >> h.m_offsets[2];
+
+    double maxX, minX;
+    double maxY, minY;
+    double maxZ, minZ;
+    in >> maxX >> minX >> maxY >> minY >> maxZ >> minZ;
+    h.m_bounds = BOX3D(minX, minY, minZ, maxX, maxY, maxZ);
+
+    if (h.versionAtLeast(1, 3))
+    {
+        uint64_t waveformOffset;
+        in >> waveformOffset;
+    }
+    if (h.versionAtLeast(1, 4))
+    {
+        in >> h.m_eVlrOffset >> h.m_eVlrCount >> h.m_pointCount;
+        for (size_t i = 0; i < LasHeader::RETURN_COUNT; ++i)
+            in >> h.m_pointCountByReturn[i];
+    }
+
+    // Read regular VLRs.
+    in.seek(h.m_vlrOffset);
+    for (size_t i = 0; i < h.m_vlrCount; ++i)
+    {
+        LasVLR r;
+        in >> r;
+        h.m_vlrs.push_back(std::move(r));
+    }
+
+    // Read extended VLRs.
+    if (h.versionAtLeast(1, 4))
+    {
+        in.seek(h.m_eVlrOffset);
+        for (size_t i = 0; i < h.m_eVlrCount; ++i)
+        {
+            ExtLasVLR r;
+            in >> r;
+            h.m_vlrs.push_back(std::move(r));
+        }
+    }
+    h.setSrs();
+
+    return in;
+}
+
+
+OLeStream& operator<<(OLeStream& out, const LasHeader& h)
+{
+    uint32_t legacyPointCount = 0;
+    if (h.m_pointCount <= (std::numeric_limits<uint32_t>::max)())
+        legacyPointCount = (uint32_t)h.m_pointCount;
+
+    out.put(h.m_fileSig, 4);
+    if (h.versionEquals(1, 0))
+        out << (uint32_t)0;
+    else if (h.versionEquals(1, 1))
+        out << h.m_sourceId << (uint16_t)0;
+    else
+        out << h.m_sourceId << h.m_globalEncoding;
+    LasHeader::put(out, h.m_projectUuid);
+    out << (uint8_t)1 << h.m_versionMinor;
+    out.put(h.m_systemId, 32);
+    out.put(h.m_softwareId, 32);
+
+    uint8_t pointFormat = h.m_pointFormat;
+    if (h.compressed())
+        pointFormat |= 0x80;
+
+    out << h.m_createDOY << h.m_createYear << h.m_vlrOffset <<
+        h.m_pointOffset << h.m_vlrCount << pointFormat <<
+        h.m_pointLen << legacyPointCount;
+
+    for (size_t i = 0; i < LasHeader::LEGACY_RETURN_COUNT; ++i)
+    {
+        uint32_t legacyReturnCount = std::min(h.m_pointCountByReturn[i],
+            (uint64_t)(std::numeric_limits<uint32_t>::max)());
+        out << legacyReturnCount;
+    }
+
+    out << h.m_scales[0] << h.m_scales[1] << h.m_scales[2];
+    out << h.m_offsets[0] << h.m_offsets[1] << h.m_offsets[2];
+
+    out << h.maxX() << h.minX() << h.maxY() << h.minY() << h.maxZ() << h.minZ();
+
+    if (h.versionAtLeast(1, 3))
+        out << (uint64_t)0;
+    if (h.versionAtLeast(1, 4))
+    {
+        out << h.m_eVlrOffset << h.m_eVlrCount << h.m_pointCount;
+        for (size_t i = 0; i < LasHeader::RETURN_COUNT; ++i)
+            out << h.m_pointCountByReturn[i];
+    }
+
+    return out;
+}
+
+
+std::ostream& operator<<(std::ostream& out, const LasHeader& h)
+{
+    out << "File version = " << "1." << (int)h.m_versionMinor << "\n";
+    out << "File signature: " << h.m_fileSig << "\n";
+    out << "File source ID: " << h.m_sourceId << "\n";
+    out << "Global encoding: " << h.m_globalEncoding << "\n";
+    out << "Project UUID: " << h.m_projectUuid << "\n";
+    out << "System ID: " << h.m_systemId << "\n";
+    out << "Software ID: " << h.m_softwareId << "\n";
+    out << "Creation DOY: " << h.m_createDOY << "\n";
+    out << "Creation Year: " << h.m_createYear << "\n";
+    out << "VLR offset (header size): " << h.m_vlrOffset << "\n";
+    out << "VLR Count: " << h.m_vlrCount << "\n";
+    out << "Point format: " << (int)h.m_pointFormat << "\n";
+    out << "Point offset: " << h.m_pointOffset << "\n";
+    out << "Point count: " << h.m_pointCount << "\n";
+    for (size_t i = 0; i < LasHeader::RETURN_COUNT; ++i)
+        out << "Point count by return[" << i << "]: " <<
+            h.m_pointCountByReturn[i] << "\n";
+    out << "Scales X/Y/Z: " << h.m_scales[0] << "/" <<
+        h.m_scales[1] << "/" << h.m_scales[2] << "\n";
+    out << "Offsets X/Y/Z: " << h.m_offsets[0] << "/" <<
+        h.m_offsets[1] << "/" << h.m_offsets[2] << "\n";
+    out << "Max X/Y/Z: " << h.maxX() << "/" <<
+        h.maxY() << "/" << h.maxZ() << "\n";
+    out << "Min X/Y/Z: " << h.minX() << "/" <<
+        h.minY() << "/" << h.minZ() << "\n";
+    if (h.versionAtLeast(1, 4))
+    {
+        out << "Ext. VLR offset: " << h.m_eVlrOffset << "\n";
+        out << "Ext. VLR count: " << h.m_eVlrCount << "\n";
+    }
+    out << "Compressed: " << (h.m_isCompressed ? "true" : "false") << "\n";
+    return out;
+}
+
+} // namespace pdal
diff --git a/io/LasHeader.hpp b/io/LasHeader.hpp
new file mode 100644
index 0000000..17e4b30
--- /dev/null
+++ b/io/LasHeader.hpp
@@ -0,0 +1,425 @@
+/******************************************************************************
+ * Copyright (c) 2008, Mateusz Loskot
+ * Copyright (c) 2008, Phil Vachon
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of the Martin Isenburg or Iowa Department
+ *       of Natural Resources nor the names of its contributors may be
+ *       used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include <pdal/Dimension.hpp>
+#include <pdal/Log.hpp>
+#include <pdal/util/Bounds.hpp>
+#include <pdal/util/Uuid.hpp>
+#include <pdal/pdal_config.hpp>
+#include <pdal/gitsha.h>
+
+#include "LasVLR.hpp"
+
+namespace pdal
+{
+class OLeStream;
+class ILeStream;
+
+typedef uint8_t PointFormat;
+std::string GetDefaultSoftwareId();
+class LasSummaryData;
+class Scaling;
+
+class PDAL_DLL LasHeader
+{
+public:
+    static const size_t LEGACY_RETURN_COUNT = 5;
+    static const size_t RETURN_COUNT = 15;
+    static const std::string FILE_SIGNATURE;
+    inline std::string getSystemIdentifier() const { return "PDAL"; }
+
+    LasHeader();
+
+    /// Get ASPRS LAS file signature.
+    /// \return 4-characters long string - \b "LASF".
+    std::string fileSignature() const
+        { return m_fileSig; }
+
+    /// Set ASPRS LAS file signature.
+    /// The only value allowed as file signature is \b "LASF",
+    /// defined as FileSignature constant.
+    /// \exception std::invalid_argument - if invalid signature given.
+    /// \param v - string contains file signature, at least 4-bytes long
+    /// with "LASF" as first four bytes.
+    void SetFileSignature(std::string const& v);
+
+    /// Get file source identifier.
+    /// \exception No throw
+    uint16_t fileSourceId() const
+        { return m_sourceId; }
+
+    /// Set file source identifier.
+    /// \param v - should be set to a value between 1 and 65535.
+    /// \exception No throw
+    void setFileSourceId(uint16_t v)
+        { m_sourceId = v; }
+
+    uint16_t globalEncoding() const
+        { return m_globalEncoding; }
+    void setGlobalEncoding(uint16_t globalEncoding)
+        { m_globalEncoding = globalEncoding; }
+
+    /// Get project identifier.
+    /// \return UUID
+    Uuid projectId() const
+        { return m_projectUuid; }
+
+    /// Set project identifier.
+    void setProjectId(const Uuid& v)
+        { m_projectUuid = v; }
+
+    /// Get the LAS major version.
+    /// \return  LAS major version
+    uint8_t versionMajor() const
+        { return (uint8_t)1; }
+
+    /// Get minor component of version of LAS format.
+    /// \return Valid values are 0, 1, 2, 3.
+    uint8_t versionMinor() const
+        { return m_versionMinor; }
+
+    /// Set minor component of version of LAS format.
+    /// \exception std::out_of_range - invalid value given.
+    /// \param v - value between eVersionMinorMin and eVersionMinorMax.
+    void setVersionMinor(uint8_t v)
+    {
+        assert(v <= 4);
+        m_versionMinor = v;
+    }
+
+    /// Determine if the header is for a LAS file version of at least
+    ///   a certain level.
+    /// \param major - Major version.
+    /// \param minor - Minor version.
+    /// \return  Whether the version meets the criteria.
+    bool versionAtLeast(uint8_t major, uint8_t minor) const
+        { return (1 >= major && m_versionMinor >= minor); }
+
+    /// Determine if the header is for a particular LAS file version.
+    /// \param major - Major version.
+    /// \param minor - Minor version.
+    /// \return  Whether the version meets the criteria.
+    bool versionEquals(uint8_t major, uint8_t minor) const
+        { return (major == 1 && minor == m_versionMinor); }
+
+    /// Get system identifier.
+    /// Default value is \b "libLAS" specified as the SystemIdentifier constant.
+    /// \param pad - if true the returned string is padded right with spaces and
+    /// its length is 32 bytes, if false (default) no padding occurs and
+    /// length of the returned string is <= 32 bytes.
+    /// \return value of system identifier field.
+    std::string systemId() const
+        { return m_systemId; }
+
+    /// Set system identifier.
+    /// \param v - system identifiers string.
+    void setSystemId(std::string const& v)
+        { m_systemId = v; }
+
+    /// Get software identifier.
+    /// Default value is \b "libLAS 1.0", specified as the SoftwareIdentifier
+    /// constant.
+    std::string softwareId() const
+        { return m_softwareId; }
+
+    /// Set software identifier.
+    /// \param v - software identifiers string.
+    void setSoftwareId(std::string const& v)
+        { m_softwareId = v; }
+
+    /// Get day of year of file creation date.
+    uint16_t creationDOY() const
+        { return m_createDOY; }
+
+    /// Set day of year of file creation date.
+    /// \exception std::out_of_range - given value is higher than number 366.
+    void setCreationDOY(uint16_t v)
+        { m_createDOY = v; }
+
+    /// Set year of file creation date.
+    uint16_t creationYear() const
+        { return m_createYear; }
+
+    /// Get year of file creation date.
+    /// \exception std::out_of_range - given value is higher than number 9999.
+    void setCreationYear(uint16_t v)
+        { m_createYear = v; }
+
+    /// Get number of bytes of generic verion of public header block storage.
+    /// Standard version of the public header block is 227 bytes long.
+    uint16_t vlrOffset() const
+        { return m_vlrOffset; }
+
+    void setVlrOffset(uint16_t offset)
+        { m_vlrOffset = offset; }
+
+    /// Get number of bytes from the beginning to the first point record.
+    uint32_t pointOffset() const
+        { return m_pointOffset; }
+
+    /// Set number of bytes from the beginning to the first point record.
+    /// \param  offset - Offset to start of point data.
+    void setPointOffset(uint32_t offset)
+          { m_pointOffset = offset; }
+
+    /// Set the point format.
+    /// \param format  Point format
+    void setPointFormat(uint8_t format)
+        { m_pointFormat = format; }
+
+    /// Get identifier of point data (record) format.
+    uint8_t pointFormat() const
+        { return m_pointFormat; }
+    bool pointFormatSupported() const
+    {
+        if (versionAtLeast(1, 4))
+            return m_pointFormat <= 10 && !hasWave();
+        else
+            return m_pointFormat <= 5 && !hasWave();
+    }
+
+    /// The length in bytes of each point.  All points in the file are
+    /// considered to be fixed in size, and the PointFormatName is used
+    /// to determine the fixed portion of the dimensions in the point.
+    uint16_t pointLen() const
+        { return m_pointLen; }
+	void setPointLen(uint16_t v)
+        { m_pointLen = v; }
+    uint16_t basePointLen()
+        { return basePointLen(m_pointFormat); }
+    uint16_t basePointLen(uint8_t format);
+
+    /// Set the number of points.
+    /// \param pointCount  Number of points in the file.
+    void setPointCount(uint64_t pointCount)
+        { m_pointCount = pointCount; }
+    /// Get total number of point records stored in the LAS file.
+    uint64_t pointCount() const
+        { return m_pointCount; }
+    //
+    /// Set values point count by return number.
+    /// \param index - Return number.
+    /// \param v - Point count for return number.
+    void setPointCountByReturn(std::size_t index, uint64_t v)
+        { m_pointCountByReturn[index] = v; }
+
+    /// Get the point count by return number.
+    /// \param index - Return number.
+    /// \return - Point count.
+    uint64_t pointCountByReturn(std::size_t index)
+        { return m_pointCountByReturn[index]; }
+
+    size_t maxReturnCount() const
+        { return (versionAtLeast(1, 4) ? RETURN_COUNT : LEGACY_RETURN_COUNT); }
+
+    /// Get scale factor for X coordinate.
+    double scaleX() const
+        { return m_scales[0]; }
+
+    /// Get scale factor for Y coordinate.
+    double scaleY() const
+        { return m_scales[1]; }
+
+    /// Get scale factor for Z coordinate.
+    double scaleZ() const
+        { return m_scales[2]; }
+
+    /// Set values of scale/offset factor for X, Y and Z coordinates.
+    void setScaling(const Scaling& scaling);
+
+    /// Get X coordinate offset.
+    double offsetX() const
+        { return m_offsets[0]; }
+
+    /// Get Y coordinate offset.
+    double offsetY() const
+        { return m_offsets[1]; }
+
+    /// Get Z coordinate offset.
+    double offsetZ() const
+        { return m_offsets[2]; }
+
+    /// Set values of X, Y and Z coordinates offset.
+    void setOffset(double x, double y, double z);
+
+    /// Get minimum value of extent of X coordinate.
+    double maxX() const
+        { return m_bounds.maxx; }
+
+    /// Get maximum value of extent of X coordinate.
+    double minX() const
+        { return m_bounds.minx; }
+
+    /// Get minimum value of extent of Y coordinate.
+    double maxY() const
+        { return m_bounds.maxy; }
+
+    /// Get maximum value of extent of Y coordinate.
+    double minY() const
+        { return m_bounds.miny; }
+
+    /// Get minimum value of extent of Z coordinate.
+    double maxZ() const
+        { return m_bounds.maxz; }
+
+    /// Get maximum value of extent of Z coordinate.
+    double minZ() const
+       { return m_bounds.minz; }
+
+    const BOX3D& getBounds() const
+        { return m_bounds; }
+    void setBounds(const BOX3D& bounds)
+        { m_bounds = bounds; }
+
+    bool hasTime() const
+    {
+        PointFormat f = pointFormat();
+        return f == 1 || f >= 3;
+    }
+
+    bool hasColor() const
+    {
+        PointFormat f = pointFormat();
+        return f == 2 || f == 3 || f == 5 || f == 7 || f == 8 || f == 10;
+    }
+
+    bool hasWave() const
+    {
+        PointFormat f = pointFormat();
+        return f == 4 || f == 5 || f == 9 || f == 10;
+    }
+
+    bool hasInfrared() const
+    {
+        PointFormat f = pointFormat();
+        return f == 8;
+    }
+
+    bool has14Format() const
+    {
+        PointFormat f = pointFormat();
+        return f > 5;
+    }
+
+    bool useWkt() const
+        { return (bool)((m_globalEncoding >> 4) & 1); }
+
+    bool incompatibleSrs() const
+        { return !useWkt() && has14Format(); }
+
+    /// Returns true iff the file is compressed (laszip),
+    /// as determined by the high bit in the point type
+    bool compressed() const
+        { return m_isCompressed; }
+
+    /// Sets whether or not the points are compressed.
+    void setCompressed(bool b)
+        { m_isCompressed = b; }
+
+    void setVlrCount(uint32_t vlrCount)
+        { m_vlrCount = vlrCount; }
+    uint32_t vlrCount() const
+        { return m_vlrCount; }
+    void setEVlrOffset(uint64_t offset)
+        { m_eVlrOffset = offset; }
+    uint64_t eVlrOffset() const
+        { return m_eVlrOffset; }
+    void setEVlrCount(uint32_t count)
+        { m_eVlrCount = count; }
+    uint32_t eVlrCount() const
+        { return m_eVlrCount; }
+    std::string const& compressionInfo() const
+        { return m_compressionInfo; }
+    void setCompressionInfo(std::string const& info)
+        { m_compressionInfo = info; }
+    SpatialReference srs() const
+        { return m_srs; }
+
+    void setSummary(const LasSummaryData& summary);
+    bool valid() const;
+    Dimension::IdList usedDims() const;
+    LasVLR *findVlr(const std::string& userId, uint16_t recordId);
+    void setLog(LogPtr log)
+        { m_log = log; }
+    const VlrList& vlrs() const
+        { return m_vlrs; }
+
+    PDAL_DLL friend ILeStream& operator>>(ILeStream&, LasHeader& h);
+    friend OLeStream& operator<<(OLeStream&, const LasHeader& h);
+    friend std::ostream& operator<<(std::ostream& ostr, const LasHeader& h);
+
+private:
+    std::string m_fileSig;
+    uint16_t m_sourceId;
+    uint16_t m_globalEncoding;
+    Uuid m_projectUuid;
+    uint8_t m_versionMinor;
+    std::string m_systemId;
+    std::string m_softwareId;
+    uint16_t m_createDOY;
+    uint16_t m_createYear;
+    uint16_t m_vlrOffset;  // Same as header size.
+    uint32_t m_pointOffset;
+    uint32_t m_vlrCount;
+    uint8_t m_pointFormat;
+    uint16_t m_pointLen;
+    uint64_t m_pointCount;
+    std::array<uint64_t, RETURN_COUNT> m_pointCountByReturn;
+    std::array<double, 3> m_scales;
+    std::array<double, 3> m_offsets;
+    bool m_isCompressed;
+    uint64_t m_eVlrOffset;
+    uint32_t m_eVlrCount;
+    BOX3D m_bounds;
+    std::string m_compressionInfo;
+    LogPtr m_log;
+    SpatialReference m_srs;
+    VlrList m_vlrs;
+    VlrList m_eVlrs;
+
+    void setSrs();
+    void setSrsFromWkt();
+    void setSrsFromGeotiff();
+
+    static void get(ILeStream& in, Uuid& uuid);
+    static void put(OLeStream& in, Uuid uuid);
+};
+
+} // namespace pdal
diff --git a/io/LasReader.cpp b/io/LasReader.cpp
new file mode 100644
index 0000000..efccc40
--- /dev/null
+++ b/io/LasReader.cpp
@@ -0,0 +1,808 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "LasReader.hpp"
+
+#include <sstream>
+#include <string.h>
+
+#include <pdal/Metadata.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/QuickInfo.hpp>
+#include <pdal/util/Extractor.hpp>
+#include <pdal/util/IStream.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include "GeotiffSupport.hpp"
+#include "LasHeader.hpp"
+#include "LasVLR.hpp"
+#include "LasZipPoint.hpp"
+
+namespace pdal
+{
+
+namespace
+{
+
+class invalid_stream : public pdal_error
+{
+public:
+    invalid_stream(const std::string& msg) : pdal_error(msg)
+        {}
+};
+
+} // unnamed namespace
+
+void LasReader::addArgs(ProgramArgs& args)
+{
+    addSpatialReferenceArg(args);
+    args.add("extra_dims", "Dimensions to assign to extra byte data",
+        m_extraDimSpec);
+    args.add("compression", "Decompressor to use", m_compression, "EITHER");
+}
+
+
+static PluginInfo const s_info = PluginInfo(
+    "readers.las",
+    "ASPRS LAS 1.0 - 1.4 read support. LASzip support is also \n" \
+        "enabled through this driver if LASzip was found during \n" \
+        "compilation.",
+    "http://pdal.io/stages/readers.las.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, LasReader, Reader, s_info)
+
+std::string LasReader::getName() const { return s_info.name; }
+
+QuickInfo LasReader::inspect()
+{
+    QuickInfo qi;
+    std::unique_ptr<PointLayout> layout(new PointLayout());
+
+    PointTable table;
+    initialize(table);
+    addDimensions(layout.get());
+
+    Dimension::IdList dims = layout->dims();
+    for (auto di = dims.begin(); di != dims.end(); ++di)
+        qi.m_dimNames.push_back(layout->dimName(*di));
+    if (!Utils::numericCast(m_header.pointCount(), qi.m_pointCount))
+        qi.m_pointCount = std::numeric_limits<point_count_t>::max();
+    qi.m_bounds = m_header.getBounds();
+    qi.m_srs = getSpatialReference();
+    qi.m_valid = true;
+
+    done(table);
+
+    return qi;
+}
+
+
+void LasReader::initializeLocal(PointTableRef table, MetadataNode& m)
+{
+    m_extraDims = LasUtils::parse(m_extraDimSpec);
+
+    std::string compression = Utils::toupper(m_compression);
+#if defined(PDAL_HAVE_LAZPERF) && defined(PDAL_HAVE_LASZIP)
+    if (compression == "EITHER")
+        compression = "LASZIP";
+#endif
+#if !defined(PDAL_HAVE_LAZPERF) && defined(PDAL_HAVE_LASZIP)
+    if (compression == "EITHER")
+        compression = "LASZIP";
+    if (compression == "LAZPERF")
+        throw pdal_error("Can't decompress with LAZperf.  PDAL not built "
+            "with LAZperf.");
+#endif
+#if defined(PDAL_HAVE_LAZPERF) && !defined(PDAL_HAVE_LASZIP)
+    if (compression == "EITHER")
+        compression = "LAZPERF";
+    if (compression == "LASZIP")
+        throw pdal_error("Can't decompress with LASzip.  PDAL not built "
+            "with LASzip.");
+#endif
+
+#if defined(PDAL_HAVE_LAZPERF) || defined(PDAL_HAVE_LASZIP)
+    if (compression != "LAZPERF" && compression != "LASZIP")
+    {
+        std::ostringstream oss;
+
+        oss << "Invalid value for option for compression: '" <<
+            m_compression << "'.  Value values are 'lazperf' and 'laszip'.";
+        throw pdal_error(oss.str());
+    }
+#endif
+
+    // Set case-corrected value.
+    m_compression = compression;
+    m_error.setFilename(m_filename);
+
+    m_error.setLog(log());
+    m_header.setLog(log());
+    createStream();
+
+    std::istream *stream(m_streamIf->m_istream);
+
+    stream->seekg(0);
+    ILeStream in(stream);
+    try
+    {
+        in >> m_header;
+    }
+    catch (pdal_error e)
+    {
+        std::ostringstream oss;
+
+        oss << getName() << e.what();
+        throw pdal_error(oss.str());
+    }
+
+    if (!m_header.pointFormatSupported())
+    {
+        std::ostringstream oss;
+        oss << "Unsupported LAS input point format: " <<
+            (int)m_header.pointFormat() << ".";
+       throw pdal_error(oss.str());
+    }
+
+    if (m_header.versionAtLeast(1, 4))
+        readExtraBytesVlr();
+    setSrs(m);
+    MetadataNode forward = table.privateMetadata("lasforward");
+    extractHeaderMetadata(forward, m);
+    extractVlrMetadata(forward, m);
+
+    m_streamIf.reset();
+}
+
+
+void LasReader::ready(PointTableRef table)
+{
+    createStream();
+    std::istream *stream(m_streamIf->m_istream);
+
+    m_index = 0;
+    if (m_header.compressed())
+    {
+#ifdef PDAL_HAVE_LASZIP
+        if (m_compression == "LASZIP")
+        {
+            LasVLR *vlr = m_header.findVlr(LASZIP_USER_ID,
+                LASZIP_RECORD_ID);
+            m_zipPoint.reset(new LasZipPoint(vlr));
+
+            if (!m_unzipper)
+            {
+                m_unzipper.reset(new LASunzipper());
+
+                stream->seekg(m_header.pointOffset(), std::ios::beg);
+
+                // Once we open the zipper, don't touch the stream until the
+                // zipper is closed or bad things happen.
+                if (!m_unzipper->open(*stream, m_zipPoint->GetZipper()))
+                {
+                    std::ostringstream oss;
+                    const char* err = m_unzipper->get_error();
+                    if (err == NULL)
+                        err = "(unknown error)";
+                    oss << "Failed to open LASzip stream: " << std::string(err);
+                    throw pdal_error(oss.str());
+                }
+            }
+        }
+#endif
+
+#ifdef PDAL_HAVE_LAZPERF
+        if (m_compression == "LAZPERF")
+        {
+            LasVLR *vlr = m_header.findVlr(LASZIP_USER_ID,
+                LASZIP_RECORD_ID);
+            m_decompressor.reset(new LazPerfVlrDecompressor(*stream,
+                vlr->data(), m_header.pointOffset()));
+            m_decompressorBuf.resize(m_decompressor->pointSize());
+        }
+#endif
+
+#if !defined(PDAL_HAVE_LAZPERF) && !defined(PDAL_HAVE_LASZIP)
+        throw pdal_error("Can't read compressed file without LASzip or "
+            "LAZperf decompression library.");
+#endif
+    }
+    else
+        stream->seekg(m_header.pointOffset());
+}
+
+
+// Store data in the normal metadata place.  Also store it in the private
+// lasforward metadata node.
+template <typename T>
+void addForwardMetadata(MetadataNode& forward, MetadataNode& m,
+    const std::string& name, T val, const std::string description = "")
+{
+    MetadataNode n = m.add(name, val, description);
+
+    // If the entry doesn't already exist, just add it.
+    MetadataNode f = forward.findChild(name);
+    if (!f.valid())
+    {
+        forward.add(n);
+        return;
+    }
+
+    // If the old value and new values aren't the same, set an invalid flag.
+    MetadataNode temp = f.addOrUpdate("temp", val);
+    if (f.value<std::string>() != temp.value<std::string>())
+        forward.addOrUpdate(name + "INVALID", "");
+}
+
+
+void LasReader::extractHeaderMetadata(MetadataNode& forward, MetadataNode& m)
+{
+    m.add<bool>("compressed", m_header.compressed(),
+        "true if this LAS file is compressed");
+
+    addForwardMetadata(forward, m, "major_version", m_header.versionMajor(),
+        "The major LAS version for the file, always 1 for now");
+    addForwardMetadata(forward, m, "minor_version", m_header.versionMinor(),
+        "The minor LAS version for the file");
+    addForwardMetadata(forward, m, "dataformat_id", m_header.pointFormat(),
+        "LAS Point Data Format");
+    if (m_header.versionAtLeast(1, 1))
+        addForwardMetadata(forward, m, "filesource_id",
+            m_header.fileSourceId(), "File Source ID (Flight Line Number "
+            "if this file was derived from an original flight line).");
+    if (m_header.versionAtLeast(1, 2))
+    {
+        // For some reason we've written global encoding as a base 64
+        // encoded value in the past.  In an effort to standardize things,
+        // I'm writing this as a special value, and will also write
+        // global_encoding like we write all other header metadata.
+        uint16_t globalEncoding = m_header.globalEncoding();
+        m.addEncoded("global_encoding_base64", (uint8_t *)&globalEncoding,
+            sizeof(globalEncoding),
+            "Global Encoding: general property bit field.");
+
+        addForwardMetadata(forward, m, "global_encoding",
+            m_header.globalEncoding(),
+            "Global Encoding: general property bit field.");
+    }
+
+    addForwardMetadata(forward, m, "project_id", m_header.projectId(),
+        "Project ID.");
+    addForwardMetadata(forward, m, "system_id", m_header.systemId());
+    addForwardMetadata(forward, m, "software_id", m_header.softwareId(),
+        "Generating software description.");
+    addForwardMetadata(forward, m, "creation_doy", m_header.creationDOY(),
+        "Day, expressed as an unsigned short, on which this file was created. "
+        "Day is computed as the Greenwich Mean Time (GMT) day. January 1 is "
+        "considered day 1.");
+    addForwardMetadata(forward, m, "creation_year", m_header.creationYear(),
+        "The year, expressed as a four digit number, in which the file was "
+        "created.");
+    addForwardMetadata(forward, m, "scale_x", m_header.scaleX(),
+        "The scale factor for X values.");
+    addForwardMetadata(forward, m, "scale_y", m_header.scaleY(),
+        "The scale factor for Y values.");
+    addForwardMetadata(forward, m, "scale_z", m_header.scaleZ(),
+        "The scale factor for Z values.");
+    addForwardMetadata(forward, m, "offset_x", m_header.offsetX(),
+        "The offset for X values.");
+    addForwardMetadata(forward, m, "offset_y", m_header.offsetY(),
+        "The offset for Y values.");
+    addForwardMetadata(forward, m, "offset_z", m_header.offsetZ(),
+        "The offset for Z values.");
+
+    m.add("header_size", m_header.vlrOffset(),
+        "The size, in bytes, of the header block, including any extension "
+        "by specific software.");
+    m.add("dataoffset", m_header.pointOffset(),
+        "The actual number of bytes from the beginning of the file to the "
+        "first field of the first point record data field. This data offset "
+        "must be updated if any software adds data from the Public Header "
+        "Block or adds/removes data to/from the Variable Length Records.");
+    m.add<double>("minx", m_header.minX(),
+        "The max and min data fields are the actual unscaled extents of the "
+        "LAS point file data, specified in the coordinate system of the LAS "
+        "data.");
+    m.add<double>("miny", m_header.minY(),
+        "The max and min data fields are the actual unscaled extents of the "
+        "LAS point file data, specified in the coordinate system of the LAS "
+        "data.");
+    m.add<double>("minz", m_header.minZ(),
+        "The max and min data fields are the actual unscaled extents of the "
+        "LAS point file data, specified in the coordinate system of the LAS "
+        "data.");
+    m.add<double>("maxx", m_header.maxX(),
+        "The max and min data fields are the actual unscaled extents of the "
+        "LAS point file data, specified in the coordinate system of the LAS "
+        "data.");
+    m.add<double>("maxy", m_header.maxY(),
+        "The max and min data fields are the actual unscaled extents of the "
+        "LAS point file data, specified in the coordinate system of the LAS "
+        "data.");
+    m.add<double>("maxz", m_header.maxZ(),
+        "The max and min data fields are the actual unscaled extents of the "
+        "LAS point file data, specified in the coordinate system of the LAS "
+        "data.");
+    m.add<uint32_t>("count",
+        m_header.pointCount(), "This field contains the total "
+        "number of point records within the file.");
+}
+
+
+void LasReader::readExtraBytesVlr()
+{
+    LasVLR *vlr = m_header.findVlr(SPEC_USER_ID,
+        EXTRA_BYTES_RECORD_ID);
+    if (!vlr)
+        return;
+    const char *pos = vlr->data();
+    size_t size = vlr->dataLen();
+    if (size % sizeof(ExtraBytesSpec) != 0)
+    {
+        log()->get(LogLevel::Warning) << "Bad size for extra bytes VLR.  "
+            "Ignoring.";
+        return;
+    }
+    size /= sizeof(ExtraBytesSpec);
+    std::vector<ExtraBytesIf> ebList;
+    while (size--)
+    {
+        ExtraBytesIf eb;
+        eb.readFrom(pos);
+        ebList.push_back(eb);
+        pos += sizeof(ExtraBytesSpec);
+    }
+
+    std::vector<ExtraDim> extraDims;
+    for (ExtraBytesIf& eb : ebList)
+    {
+       std::vector<ExtraDim> eds = eb.toExtraDims();
+       for (auto& ed : eds)
+           extraDims.push_back(std::move(ed));
+    }
+    if (m_extraDims.size() && m_extraDims != extraDims)
+        log()->get(LogLevel::Warning) << "Extra byte dimensions specified "
+            "in pineline and VLR don't match.  Ignoring pipeline-specified "
+            "dimensions";
+    m_extraDims = extraDims;
+}
+
+
+void LasReader::setSrs(MetadataNode& m)
+{
+    // If the user is already overriding this by setting it on the stage, we'll
+    // take their overridden value
+    SpatialReference srs = getSpatialReference();
+
+    if (srs.getWKT().empty())
+        srs = m_header.srs();
+    setSpatialReference(m, srs);
+}
+
+
+void LasReader::extractVlrMetadata(MetadataNode& forward, MetadataNode& m)
+{
+    static const size_t DATA_LEN_MAX = 1000000;
+
+    int i = 0;
+    for (auto vlr : m_header.vlrs())
+    {
+        if (vlr.dataLen() > DATA_LEN_MAX)
+            continue;
+
+        std::ostringstream name;
+        name << "vlr_" << i++;
+        MetadataNode vlrNode = m.addEncoded(name.str(),
+            (const uint8_t *)vlr.data(), vlr.dataLen(), vlr.description());
+
+        vlrNode.add("user_id", vlr.userId(),
+            "User ID of the record or pre-defined value from the "
+            "specification.");
+        vlrNode.add("record_id", vlr.recordId(),
+            "Record ID specified by the user.");
+        vlrNode.add("description", vlr.description());
+
+        if ((vlr.userId() != TRANSFORM_USER_ID) &&
+            (vlr.userId() != SPEC_USER_ID) &&
+            (vlr.userId() != LASZIP_USER_ID) &&
+            (vlr.userId() != LIBLAS_USER_ID))
+            forward.add(vlrNode);
+    }
+}
+
+
+void LasReader::addDimensions(PointLayoutPtr layout)
+{
+    using namespace Dimension;
+
+    layout->registerDim(Id::X, Type::Double);
+    layout->registerDim(Id::Y, Type::Double);
+    layout->registerDim(Id::Z, Type::Double);
+    layout->registerDim(Id::Intensity, Type::Unsigned16);
+    layout->registerDim(Id::ReturnNumber, Type::Unsigned8);
+    layout->registerDim(Id::NumberOfReturns, Type::Unsigned8);
+    layout->registerDim(Id::ScanDirectionFlag, Type::Unsigned8);
+    layout->registerDim(Id::EdgeOfFlightLine, Type::Unsigned8);
+    layout->registerDim(Id::Classification, Type::Unsigned8);
+    layout->registerDim(Id::ScanAngleRank, Type::Float);
+    layout->registerDim(Id::UserData, Type::Unsigned8);
+    layout->registerDim(Id::PointSourceId, Type::Unsigned16);
+
+    if (m_header.hasTime())
+        layout->registerDim(Id::GpsTime, Type::Double);
+    if (m_header.hasColor())
+    {
+        layout->registerDim(Id::Red, Type::Unsigned16);
+        layout->registerDim(Id::Green, Type::Unsigned16);
+        layout->registerDim(Id::Blue, Type::Unsigned16);
+    }
+    if (m_header.hasInfrared())
+        layout->registerDim(Id::Infrared);
+    if (m_header.versionAtLeast(1, 4))
+    {
+        layout->registerDim(Id::ScanChannel);
+        layout->registerDim(Id::ClassFlags);
+    }
+
+    for (auto& dim : m_extraDims)
+    {
+        Dimension::Type type = dim.m_dimType.m_type;
+        if (type == Dimension::Type::None)
+            continue;
+        if (dim.m_dimType.m_xform.nonstandard())
+            type = Dimension::Type::Double;
+        dim.m_dimType.m_id = layout->assignDim(dim.m_name, type);
+    }
+}
+
+
+bool LasReader::processOne(PointRef& point)
+{
+    if (m_index >= getNumPoints())
+        return false;
+
+    size_t pointLen = m_header.pointLen();
+
+    if (m_header.compressed())
+    {
+#ifdef PDAL_HAVE_LASZIP
+        if (m_compression == "LASZIP")
+        {
+            if (!m_unzipper->read(m_zipPoint->m_lz_point))
+            {
+                std::string error = "Error reading compressed point data: ";
+                const char* err = m_unzipper->get_error();
+                if (!err)
+                    err = "(unknown error)";
+                error += err;
+                throw pdal_error(error);
+            }
+            loadPoint(point, (char *)m_zipPoint->m_lz_point_data.data(),
+                pointLen);
+        }
+#endif
+
+#ifdef PDAL_HAVE_LAZPERF
+        if (m_compression == "LAZPERF")
+        {
+            m_decompressor->decompress(m_decompressorBuf.data());
+            loadPoint(point, m_decompressorBuf.data(), pointLen);
+        }
+#endif
+#if !defined(PDAL_HAVE_LAZPERF) && !defined(PDAL_HAVE_LASZIP)
+        throw pdal_error("Can't read compressed file without LASzip or "
+            "LAZperf decompression library.");
+#endif
+    } // compression
+    else
+    {
+        std::vector<char> buf(m_header.pointLen());
+
+        m_streamIf->m_istream->read(buf.data(), pointLen);
+        loadPoint(point, buf.data(), pointLen);
+    }
+    m_index++;
+    return true;
+}
+
+
+point_count_t LasReader::read(PointViewPtr view, point_count_t count)
+{
+    size_t pointLen = m_header.pointLen();
+    count = std::min(count, getNumPoints() - m_index);
+
+    PointId i = 0;
+    if (m_header.compressed())
+    {
+#if defined(PDAL_HAVE_LAZPERF) || defined(PDAL_HAVE_LASZIP)
+        if (m_compression == "LASZIP" || m_compression == "LAZPERF")
+        {
+            for (i = 0; i < count; i++)
+            {
+                PointRef point = view->point(i);
+                PointId id = view->size();
+                processOne(point);
+                if (m_cb)
+                    m_cb(*view, id);
+            }
+        }
+#else
+        throw pdal_error("Can't read compressed file without LASzip or "
+            "LAZperf decompression library.");
+#endif
+    }
+    else
+    {
+        point_count_t remaining = count;
+
+        // Make a buffer at most a meg.
+        size_t bufsize = std::min<size_t>((point_count_t)1000000,
+            count * pointLen);
+        std::vector<char> buf(bufsize);
+        try
+        {
+            do
+            {
+                point_count_t blockPoints = readFileBlock(buf, remaining);
+                remaining -= blockPoints;
+                char *pos = buf.data();
+                while (blockPoints--)
+                {
+                    PointId id = view->size();
+                    PointRef point = view->point(id);
+                    loadPoint(point, pos, pointLen);
+                    if (m_cb)
+                        m_cb(*view, id);
+                    pos += pointLen;
+                    i++;
+                }
+            } while (remaining);
+        }
+        catch (std::out_of_range&)
+        {}
+        catch (invalid_stream&)
+        {}
+    }
+    m_index += i;
+    return (point_count_t)i;
+}
+
+
+point_count_t LasReader::readFileBlock(std::vector<char>& buf,
+    point_count_t maxpoints)
+{
+    std::istream *stream(m_streamIf->m_istream);
+
+    size_t ptLen = m_header.pointLen();
+    point_count_t blockpoints = buf.size() / ptLen;
+
+    blockpoints = std::min(maxpoints, blockpoints);
+    if (stream->eof())
+        throw invalid_stream("stream is done");
+
+    stream->read(buf.data(), blockpoints * ptLen);
+    if (stream->gcount() != (std::streamsize)(blockpoints * ptLen))
+    {
+        // we read fewer bytes than we asked for
+        // because the file was either truncated
+        // or the header is bunk.
+        blockpoints = stream->gcount() / ptLen;
+    }
+    return blockpoints;
+}
+
+
+void LasReader::loadPoint(PointRef& point, char *buf, size_t bufsize)
+{
+    if (m_header.has14Format())
+        loadPointV14(point, buf, bufsize);
+    else
+        loadPointV10(point, buf, bufsize);
+}
+
+
+void LasReader::loadPointV10(PointRef& point, char *buf, size_t bufsize)
+{
+    LeExtractor istream(buf, bufsize);
+
+    int32_t xi, yi, zi;
+    istream >> xi >> yi >> zi;
+
+    const LasHeader& h = m_header;
+
+    double x = xi * h.scaleX() + h.offsetX();
+    double y = yi * h.scaleY() + h.offsetY();
+    double z = zi * h.scaleZ() + h.offsetZ();
+
+    uint16_t intensity;
+    uint8_t flags;
+    uint8_t classification;
+    int8_t scanAngleRank;
+    uint8_t user;
+    uint16_t pointSourceId;
+
+    istream >> intensity >> flags >> classification >> scanAngleRank >>
+        user >> pointSourceId;
+
+    uint8_t returnNum = flags & 0x07;
+    uint8_t numReturns = (flags >> 3) & 0x07;
+    uint8_t scanDirFlag = (flags >> 6) & 0x01;
+    uint8_t flight = (flags >> 7) & 0x01;
+
+    if (returnNum == 0 || returnNum > 5)
+        m_error.returnNumWarning(returnNum);
+
+    if (numReturns == 0 || numReturns > 5)
+        m_error.numReturnsWarning(numReturns);
+
+    point.setField(Dimension::Id::X, x);
+    point.setField(Dimension::Id::Y, y);
+    point.setField(Dimension::Id::Z, z);
+    point.setField(Dimension::Id::Intensity, intensity);
+    point.setField(Dimension::Id::ReturnNumber, returnNum);
+    point.setField(Dimension::Id::NumberOfReturns, numReturns);
+    point.setField(Dimension::Id::ScanDirectionFlag, scanDirFlag);
+    point.setField(Dimension::Id::EdgeOfFlightLine, flight);
+    point.setField(Dimension::Id::Classification, classification);
+    point.setField(Dimension::Id::ScanAngleRank, scanAngleRank);
+    point.setField(Dimension::Id::UserData, user);
+    point.setField(Dimension::Id::PointSourceId, pointSourceId);
+
+    if (h.hasTime())
+    {
+        double time;
+        istream >> time;
+        point.setField(Dimension::Id::GpsTime, time);
+    }
+
+    if (h.hasColor())
+    {
+        uint16_t red, green, blue;
+        istream >> red >> green >> blue;
+        point.setField(Dimension::Id::Red, red);
+        point.setField(Dimension::Id::Green, green);
+        point.setField(Dimension::Id::Blue, blue);
+    }
+
+    if (m_extraDims.size())
+        loadExtraDims(istream, point);
+
+}
+
+void LasReader::loadPointV14(PointRef& point, char *buf, size_t bufsize)
+{
+    LeExtractor istream(buf, bufsize);
+
+    int32_t xi, yi, zi;
+    istream >> xi >> yi >> zi;
+
+    const LasHeader& h = m_header;
+
+    double x = xi * h.scaleX() + h.offsetX();
+    double y = yi * h.scaleY() + h.offsetY();
+    double z = zi * h.scaleZ() + h.offsetZ();
+
+    uint16_t intensity;
+    uint8_t returnInfo;
+    uint8_t flags;
+    uint8_t classification;
+    uint8_t user;
+    int16_t scanAngle;
+    uint16_t pointSourceId;
+    double gpsTime;
+
+    istream >> intensity >> returnInfo >> flags >> classification >> user >>
+        scanAngle >> pointSourceId >> gpsTime;
+
+    uint8_t returnNum = returnInfo & 0x0F;
+    uint8_t numReturns = (returnInfo >> 4) & 0x0F;
+    uint8_t classFlags = flags & 0x0F;
+    uint8_t scanChannel = (flags >> 4) & 0x03;
+    uint8_t scanDirFlag = (flags >> 6) & 0x01;
+    uint8_t flight = (flags >> 7) & 0x01;
+
+    point.setField(Dimension::Id::X, x);
+    point.setField(Dimension::Id::Y, y);
+    point.setField(Dimension::Id::Z, z);
+    point.setField(Dimension::Id::Intensity, intensity);
+    point.setField(Dimension::Id::ReturnNumber, returnNum);
+    point.setField(Dimension::Id::NumberOfReturns, numReturns);
+    point.setField(Dimension::Id::ClassFlags, classFlags);
+    point.setField(Dimension::Id::ScanChannel, scanChannel);
+    point.setField(Dimension::Id::ScanDirectionFlag, scanDirFlag);
+    point.setField(Dimension::Id::EdgeOfFlightLine, flight);
+    point.setField(Dimension::Id::Classification, classification);
+    point.setField(Dimension::Id::ScanAngleRank, scanAngle * .006);
+    point.setField(Dimension::Id::UserData, user);
+    point.setField(Dimension::Id::PointSourceId, pointSourceId);
+    point.setField(Dimension::Id::GpsTime, gpsTime);
+
+    if (h.hasColor())
+    {
+        uint16_t red, green, blue;
+        istream >> red >> green >> blue;
+        point.setField(Dimension::Id::Red, red);
+        point.setField(Dimension::Id::Green, green);
+        point.setField(Dimension::Id::Blue, blue);
+    }
+
+    if (h.hasInfrared())
+    {
+        uint16_t nearInfraRed;
+
+        istream >> nearInfraRed;
+        point.setField(Dimension::Id::Infrared, nearInfraRed);
+    }
+
+    if (m_extraDims.size())
+        loadExtraDims(istream, point);
+}
+
+
+void LasReader::loadExtraDims(LeExtractor& istream, PointRef& point)
+{
+    for (auto& dim : m_extraDims)
+    {
+        // Dimension type of None is undefined and unprocessed
+        if (dim.m_dimType.m_type == Dimension::Type::None)
+        {
+            istream.skip(dim.m_size);
+            continue;
+        }
+
+        Everything e = Utils::extractDim(istream, dim.m_dimType.m_type);
+        if (dim.m_dimType.m_xform.nonstandard())
+        {
+            double d = Utils::toDouble(e, dim.m_dimType.m_type);
+            d = d * dim.m_dimType.m_xform.m_scale.m_val +
+                dim.m_dimType.m_xform.m_offset.m_val;
+            point.setField(dim.m_dimType.m_id, d);
+        }
+        else
+            point.setField(dim.m_dimType.m_id, dim.m_dimType.m_type, &e);
+    }
+}
+
+
+void LasReader::done(PointTableRef)
+{
+#ifdef PDAL_HAVE_LASZIP
+    m_zipPoint.reset();
+    m_unzipper.reset();
+#endif
+    m_streamIf.reset();
+}
+
+} // namespace pdal
diff --git a/io/LasReader.hpp b/io/LasReader.hpp
new file mode 100644
index 0000000..47eeb9d
--- /dev/null
+++ b/io/LasReader.hpp
@@ -0,0 +1,152 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/pdal_export.hpp>
+#include <pdal/plugin.hpp>
+#include <pdal/Compression.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/Reader.hpp>
+
+#include "LasError.hpp"
+#include "LasHeader.hpp"
+#include "LasUtils.hpp"
+#include "LasZipPoint.hpp"
+
+extern "C" int32_t LasReader_ExitFunc();
+extern "C" PF_ExitFunc LasReader_InitPlugin();
+
+namespace pdal
+{
+
+class NitfReader;
+class LasHeader;
+class LeExtractor;
+class PointDimensions;
+
+class PDAL_DLL LasReader : public pdal::Reader
+{
+protected:
+    class LasStreamIf
+    {
+    protected:
+        LasStreamIf()
+        {}
+
+    public:
+        LasStreamIf(const std::string& filename)
+            { m_istream = Utils::openFile(filename); }
+
+        ~LasStreamIf()
+        {
+            if (m_istream)
+                Utils::closeFile(m_istream);
+        }
+
+        std::istream *m_istream;
+    };
+
+    friend class NitfReader;
+public:
+    LasReader() : pdal::Reader(), m_index(0)
+        {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    const LasHeader& header() const
+        { return m_header; }
+    point_count_t getNumPoints() const
+        { return m_header.pointCount(); }
+
+protected:
+    virtual void createStream()
+    {
+        if (m_streamIf)
+            std::cerr << "Attempt to create stream twice!\n";
+        m_streamIf.reset(new LasStreamIf(m_filename));
+        if (!m_streamIf->m_istream)
+        {
+            std::ostringstream oss;
+            oss << "Unable to open stream for '"
+                << m_filename <<"' with error '" << strerror(errno) <<"'";
+            throw pdal_error(oss.str());
+        }
+    }
+
+    std::unique_ptr<LasStreamIf> m_streamIf;
+
+private:
+    LasError m_error;
+    LasHeader m_header;
+    std::unique_ptr<LasZipPoint> m_zipPoint;
+    std::unique_ptr<LASunzipper> m_unzipper;
+    std::unique_ptr<LazPerfVlrDecompressor> m_decompressor;
+    std::vector<char> m_decompressorBuf;
+    point_count_t m_index;
+    StringList m_extraDimSpec;
+    std::vector<ExtraDim> m_extraDims;
+    std::string m_compression;
+
+    virtual void addArgs(ProgramArgs& args);
+    virtual void initialize(PointTableRef table)
+        { initializeLocal(table, m_metadata); }
+    virtual void initializeLocal(PointTableRef table, MetadataNode& m);
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual QuickInfo inspect();
+    virtual void ready(PointTableRef table);
+    virtual point_count_t read(PointViewPtr view, point_count_t count);
+    virtual bool processOne(PointRef& point);
+    virtual void done(PointTableRef table);
+    virtual bool eof()
+        { return m_index >= getNumPoints(); }
+
+    void setSrs(MetadataNode& m);
+    void readExtraBytesVlr();
+    void extractHeaderMetadata(MetadataNode& forward, MetadataNode& m);
+    void extractVlrMetadata(MetadataNode& forward, MetadataNode& m);
+    void loadPoint(PointRef& point, char *buf, size_t bufsize);
+    void loadPointV10(PointRef& point, char *buf, size_t bufsize);
+    void loadPointV14(PointRef& point, char *buf, size_t bufsize);
+    void loadExtraDims(LeExtractor& istream, PointRef& data);
+    point_count_t readFileBlock(std::vector<char>& buf,
+        point_count_t maxPoints);
+
+    LasReader& operator=(const LasReader&); // not implemented
+    LasReader(const LasReader&); // not implemented
+};
+
+} // namespace pdal
diff --git a/io/LasSummaryData.cpp b/io/LasSummaryData.cpp
new file mode 100644
index 0000000..de2bcf8
--- /dev/null
+++ b/io/LasSummaryData.cpp
@@ -0,0 +1,109 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "LasSummaryData.hpp"
+
+namespace pdal
+{
+
+LasSummaryData::LasSummaryData() :
+    m_minX((std::numeric_limits<double>::max)()),
+    m_minY((std::numeric_limits<double>::max)()),
+    m_minZ((std::numeric_limits<double>::max)()),
+    m_maxX(std::numeric_limits<double>::lowest()),
+    m_maxY(std::numeric_limits<double>::lowest()),
+    m_maxZ(std::numeric_limits<double>::lowest()),
+    m_totalNumPoints(0)
+{
+    m_returnCounts.fill(0);
+}
+
+
+void LasSummaryData::addPoint(double x, double y, double z, int returnNumber)
+{
+    ++m_totalNumPoints;
+    m_minX = (std::min)(m_minX, x);
+    m_minY = (std::min)(m_minY, y);
+    m_minZ = (std::min)(m_minZ, z);
+    m_maxX = (std::max)(m_maxX, x);
+    m_maxY = (std::max)(m_maxY, y);
+    m_maxZ = (std::max)(m_maxZ, z);
+
+    // Returns numbers are indexed from one, but the array indexes from 0.
+    returnNumber--;
+    if (returnNumber >= 0 && (size_t)returnNumber < m_returnCounts.size())
+        m_returnCounts[returnNumber]++;
+}
+
+
+BOX3D LasSummaryData::getBounds() const
+{
+    BOX3D output(m_minX, m_minY, m_minZ, m_maxX, m_maxY, m_maxZ);
+    return output;
+}
+
+
+point_count_t LasSummaryData::getReturnCount(int returnNumber) const
+{
+    if (returnNumber < 0 || (size_t)returnNumber >= m_returnCounts.size())
+        throw pdal_error("getReturnCount: point returnNumber is out of range");
+    return m_returnCounts[returnNumber];
+}
+
+
+void LasSummaryData::dump(std::ostream& str) const
+{
+    str << "MinX: " << m_minX << "\n";
+    str << "MinY: " << m_minY << "\n";
+    str << "MinZ: " << m_minZ << "\n";
+    str << "MaxX: " << m_maxX << "\n";
+    str << "MaxY: " << m_maxY << "\n";
+    str << "MaxZ: " << m_maxZ << "\n";
+
+    str << "Number of returns:";
+    for (size_t i = 0; i < m_returnCounts.size(); ++i)
+        str << " " << m_returnCounts[i];
+    str << "\n";
+
+    str << "Total number of points: " << m_totalNumPoints << "\n";
+}
+
+
+std::ostream& operator<<(std::ostream& ostr, const LasSummaryData& data)
+{
+    data.dump(ostr);
+    return ostr;
+}
+
+} // namespace pdal
diff --git a/io/LasSummaryData.hpp b/io/LasSummaryData.hpp
new file mode 100644
index 0000000..16e8fa8
--- /dev/null
+++ b/io/LasSummaryData.hpp
@@ -0,0 +1,76 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <ostream>
+#include <array>
+
+#include <pdal/pdal_internal.hpp>
+
+#include "LasHeader.hpp"
+
+namespace pdal
+{
+
+class PDAL_DLL LasSummaryData
+{
+public:
+    LasSummaryData();
+
+    void addPoint(double x, double y, double z, int returnNumber);
+    point_count_t getTotalNumPoints() const
+        { return m_totalNumPoints; }
+    BOX3D getBounds() const;
+    point_count_t getReturnCount(int returnNumber) const;
+
+    void dump(std::ostream&) const;
+
+private:
+    double m_minX;
+    double m_minY;
+    double m_minZ;
+    double m_maxX;
+    double m_maxY;
+    double m_maxZ;
+    std::array<point_count_t, LasHeader::RETURN_COUNT> m_returnCounts;
+    point_count_t m_totalNumPoints;
+
+    LasSummaryData& operator=(const LasSummaryData&); // not implemented
+    LasSummaryData(const LasSummaryData&); // not implemented
+};
+
+PDAL_DLL std::ostream& operator<<(std::ostream& ostr, const LasSummaryData&);
+
+} // namespace pdal
diff --git a/io/las/LasUtils.cpp b/io/LasUtils.cpp
similarity index 100%
rename from io/las/LasUtils.cpp
rename to io/LasUtils.cpp
diff --git a/io/las/LasUtils.hpp b/io/LasUtils.hpp
similarity index 100%
rename from io/las/LasUtils.hpp
rename to io/LasUtils.hpp
diff --git a/io/LasVLR.cpp b/io/LasVLR.cpp
new file mode 100644
index 0000000..b9fda57
--- /dev/null
+++ b/io/LasVLR.cpp
@@ -0,0 +1,107 @@
+/******************************************************************************
+* Copyright (c) 2014, Hobu Inc. (hobu at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "LasVLR.hpp"
+
+namespace pdal
+{
+
+const uint16_t LasVLR::MAX_DATA_SIZE =
+    (std::numeric_limits<uint16_t>::max)();
+
+ILeStream& operator>>(ILeStream& in, LasVLR& v)
+{
+    uint16_t reserved;
+    uint16_t dataLen;
+
+    in >> reserved;
+    in.get(v.m_userId, 16);
+    in >> v.m_recordId >> dataLen;
+    in.get(v.m_description, 32);
+    v.m_data.resize(dataLen);
+    if (v.m_data.size() > 0) {
+        in.get(v.m_data);
+    }
+
+    return in;
+}
+
+
+OLeStream& operator<<(OLeStream& out, const LasVLR& v)
+{
+    out << v.m_recordSig;
+    out.put(v.m_userId, 16);
+    out << v.m_recordId << (uint16_t)v.dataLen();
+    out.put(v.m_description, 32);
+    out.put(v.data(), v.dataLen());
+
+    return out;
+}
+
+
+ILeStream& operator>>(ILeStream& in, ExtLasVLR& v)
+{
+    uint64_t dataLen;
+
+    in >> v.m_recordSig;
+    in.get(v.m_userId, 16);
+    in >> v.m_recordId >> dataLen;
+    in.get(v.m_description, 32);
+    v.m_data.resize(dataLen);
+    if (v.m_data.size() > 0) {
+        in.get(v.m_data);
+    }
+
+    return in;
+}
+
+
+OLeStream& operator<<(OLeStream& out, const ExtLasVLR& v)
+{
+    out << (uint16_t)0;
+    out.put(v.userId(), 16);
+    out << v.recordId() << v.dataLen();
+    out.put(v.description(), 32);
+    out.put(v.data(), v.dataLen());
+
+    return out;
+}
+
+    void LasVLR::write(OLeStream& out, uint16_t recordSig)
+    {
+        m_recordSig = recordSig;
+        out << *this;
+    }
+
+} // namespace pdal
diff --git a/io/LasVLR.hpp b/io/LasVLR.hpp
new file mode 100644
index 0000000..98daa50
--- /dev/null
+++ b/io/LasVLR.hpp
@@ -0,0 +1,126 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <limits>
+#include <string>
+#include <vector>
+
+#include <pdal/SpatialReference.hpp>
+#include <pdal/util/IStream.hpp>
+#include <pdal/util/OStream.hpp>
+
+namespace pdal
+{
+
+static const int WKT_RECORD_ID = 2112;
+static const uint16_t GEOTIFF_DIRECTORY_RECORD_ID = 34735;
+static const uint16_t GEOTIFF_DOUBLES_RECORD_ID = 34736;
+static const uint16_t GEOTIFF_ASCII_RECORD_ID = 34737;
+static const uint16_t LASZIP_RECORD_ID = 22204;
+static const uint16_t EXTRA_BYTES_RECORD_ID = 4;
+
+static const char TRANSFORM_USER_ID[] = "LASF_Projection";
+static const char SPEC_USER_ID[] = "LASF_Spec";
+static const char LIBLAS_USER_ID[] = "liblas";
+static const char LASZIP_USER_ID[] = "laszip encoded";
+
+class LasVLR;
+typedef std::vector<LasVLR> VlrList;
+
+class PDAL_DLL LasVLR
+{
+public:
+    static const uint16_t MAX_DATA_SIZE;
+
+    LasVLR(const std::string& userId, uint16_t recordId,
+            const std::string& description, std::vector<uint8_t>& data) :
+        m_userId(userId), m_recordId(recordId), m_description(description),
+        m_data(std::move(data)), m_recordSig(0)
+    {}
+    LasVLR() : m_recordId(0), m_recordSig(0)
+    {}
+
+    std::string userId() const
+        { return m_userId;}
+    uint16_t recordId() const
+        { return m_recordId; }
+    std::string description() const
+        { return m_description; }
+    
+    bool matches(const std::string& userId) const
+        { return userId == m_userId; }
+    bool matches(const std::string& userId, uint16_t recordId) const
+        { return matches(userId) && (recordId == m_recordId); }
+
+    const char* data() const
+        { return (const char *)m_data.data(); }
+    char* data()
+        { return (char *)m_data.data(); }
+    bool isEmpty() const
+        { return m_data.size() == 0; }
+    uint64_t dataLen() const
+        { return m_data.size(); }
+    void setDataLen(uint64_t size)
+        { m_data.resize((size_t)size); }
+    void write(OLeStream& out, uint16_t recordSig);
+
+    friend ILeStream& operator>>(ILeStream& in, LasVLR& v);
+    friend OLeStream& operator<<(OLeStream& out, const LasVLR& v);
+
+protected:
+    std::string m_userId;
+    uint16_t m_recordId;
+    std::string m_description;
+    std::vector<uint8_t> m_data;
+    uint16_t m_recordSig;
+};
+
+class ExtLasVLR : public LasVLR
+{
+public:
+    ExtLasVLR(const std::string& userId, uint16_t recordId,
+            const std::string& description, std::vector<uint8_t>& data) :
+        LasVLR(userId, recordId, description, data)
+    {}
+    ExtLasVLR()
+    {}
+
+    friend ILeStream& operator>>(ILeStream& in, ExtLasVLR& v);
+    friend OLeStream& operator<<(OLeStream& out,
+        const ExtLasVLR& v);
+};
+
+} // namespace pdal
diff --git a/io/LasWriter.cpp b/io/LasWriter.cpp
new file mode 100644
index 0000000..732bb98
--- /dev/null
+++ b/io/LasWriter.cpp
@@ -0,0 +1,926 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "LasWriter.hpp"
+
+#include <iostream>
+
+#include <pdal/Compression.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/util/Algorithm.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/util/Inserter.hpp>
+#include <pdal/util/OStream.hpp>
+#include <pdal/util/Utils.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include "GeotiffSupport.hpp"
+#include "LasZipPoint.hpp"
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "writers.las",
+    "ASPRS LAS 1.0 - 1.4 writer. LASzip support is also \n" \
+        "available if enabled at compile-time. Note that LAZ \n" \
+        "does not provide LAS 1.4 support at this time.",
+    "http://pdal.io/stages/writers.las.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, LasWriter, Writer, s_info)
+
+std::string LasWriter::getName() const { return s_info.name; }
+
+LasWriter::LasWriter() : m_ostream(NULL), m_compression(LasCompression::None)
+{}
+
+
+void LasWriter::addArgs(ProgramArgs& args)
+{
+    std::time_t now;
+    std::time(&now);
+    std::tm* ptm = std::gmtime(&now);
+    uint16_t year = ptm->tm_year + 1900;
+    uint16_t doy = ptm->tm_yday;
+
+    args.add("filename", "Output filename", m_filename).setPositional();
+    args.add("a_srs", "Spatial reference to use to write output", m_aSrs);
+    args.add("compression", "Compression to use for output ('LASZIP' or "
+        "'LAZPERF')", m_compression, LasCompression::None);
+    args.add("discard_high_return_numbers", "Discard points with out-of-spec "
+        "return numbers.", m_discardHighReturnNumbers);
+    args.add("extra_dims", "Dimensions to write above those in point format",
+        m_extraDimSpec);
+    args.add("forward", "Dimensions to forward from LAS reader", m_forwardSpec);
+
+    args.add("major_version", "LAS major version", m_majorVersion,
+        decltype(m_majorVersion)(1));
+    args.add("minor_version", "LAS minor version", m_minorVersion,
+        decltype(m_minorVersion)(2));
+    args.add("dataformat_id", "Point format", m_dataformatId,
+        decltype(m_dataformatId)(3));
+    args.add("format", "Point format", m_dataformatId,
+        decltype(m_dataformatId)(3));
+    args.add("global_encoding", "Global encoding byte", m_globalEncoding);
+    args.add("project_id", "Project ID", m_projectId);
+    args.add("system_id", "System ID", m_systemId,
+        decltype(m_systemId)(m_lasHeader.getSystemIdentifier()));
+    args.add("software_id", "Software ID", m_softwareId,
+        decltype(m_softwareId)(GetDefaultSoftwareId()));
+    args.add("creation_doy", "Creation day of year", m_creationDoy,
+        decltype(m_creationDoy)(doy));
+    args.add("creation_year", "Creation year", m_creationYear,
+        decltype(m_creationYear)(year));
+    args.add("scale_x", "X scale factor", m_scaleX, decltype(m_scaleX)(".01"));
+    args.add("scale_y", "Y scale factor", m_scaleY, decltype(m_scaleY)(".01"));
+    args.add("scale_z", "Z scale factor", m_scaleZ, decltype(m_scaleZ)(".01"));
+    args.add("offset_x", "X offset", m_offsetX);
+    args.add("offset_y", "Y offset", m_offsetY);
+    args.add("offset_z", "Z offset", m_offsetZ);
+}
+
+void LasWriter::initialize()
+{
+    std::string ext = FileUtils::extension(m_filename);
+    ext = Utils::tolower(ext);
+    if ((ext == ".laz") && (m_compression == LasCompression::None))
+        m_compression = LasCompression::LasZip;
+
+    if (!m_aSrs.empty())
+        setSpatialReference(m_aSrs);
+    if (m_compression != LasCompression::None)
+        m_lasHeader.setCompressed(true);
+#if !defined(PDAL_HAVE_LASZIP) && !defined(PDAL_HAVE_LAZPERF)
+    if (m_compression != LasCompression::None)
+        throw pdal_error("Can't write LAZ output.  "
+            "PDAL not built with LASzip or LAZperf.");
+#endif
+    m_extraDims = LasUtils::parse(m_extraDimSpec);
+    fillForwardList();
+}
+
+
+void LasWriter::prepared(PointTableRef table)
+{
+    FlexWriter::validateFilename(table);
+
+    PointLayoutPtr layout = table.layout();
+
+    // If we've asked for all dimensions, add to extraDims all dimensions
+    // in the layout that aren't already destined for LAS output.
+    if (m_extraDims.size() == 1 && m_extraDims[0].m_name == "all")
+    {
+        m_extraDims.clear();
+        Dimension::IdList ids = m_lasHeader.usedDims();
+        DimTypeList dimTypes = layout->dimTypes();
+        for (auto& dt : dimTypes)
+        {
+            if (!Utils::contains(ids, dt.m_id))
+                m_extraDims.push_back(
+                    ExtraDim(layout->dimName(dt.m_id), dt.m_type));
+        }
+    }
+
+    m_extraByteLen = 0;
+    for (auto& dim : m_extraDims)
+    {
+        dim.m_dimType.m_id = table.layout()->findDim(dim.m_name);
+        if (dim.m_dimType.m_id == Dimension::Id::Unknown)
+        {
+            std::ostringstream oss;
+            oss << "Dimension '" << dim.m_name << "' specified in "
+                "'extra_dim' option not found.";
+            throw pdal_error(oss.str());
+        }
+        m_extraByteLen += Dimension::size(dim.m_dimType.m_type);
+    }
+}
+
+
+// Get header info from options and store in map for processing with
+// metadata.
+void LasWriter::fillForwardList()
+{
+    static const StringList header = {
+        "dataformat_id", "major_version", "minor_version", "filesource_id",
+        "global_encoding", "project_id", "system_id", "software_id",
+        "creation_doy", "creation_year"
+    };
+
+    static const StringList scale = { "scale_x", "scale_y", "scale_z" };
+
+    static const StringList offset = { "offset_x", "offset_y", "offset_z" };
+
+    static StringList all;
+    all.insert(all.begin(), header.begin(), header.end());
+    all.insert(all.begin(), scale.begin(), scale.end());
+    all.insert(all.begin(), offset.begin(), offset.end());
+
+    // Build the forward list, replacing special keywords with the proper
+    // field names.
+    for (auto& name : m_forwardSpec)
+    {
+        if (name == "all")
+        {
+            m_forwards.insert(all.begin(), all.end());
+            m_forwardVlrs = true;
+        }
+        else if (name == "header")
+            m_forwards.insert(header.begin(), header.end());
+        else if (name == "scale")
+            m_forwards.insert(scale.begin(), scale.end());
+        else if (name == "offset")
+            m_forwards.insert(offset.begin(), offset.end());
+        else if (name == "format")
+            m_forwards.insert("dataformat_id");
+        else if (name == "vlr")
+            m_forwardVlrs = true;
+        else if (Utils::contains(all, name))
+            m_forwards.insert(name);
+        else
+        {
+            std::ostringstream oss;
+
+            oss << "Error in 'forward' option.  Unknown field for "
+                "forwarding: '" << name << "'.";
+            throw pdal_error(oss.str());
+        }
+    }
+}
+
+
+void LasWriter::readyTable(PointTableRef table)
+{
+    m_forwardMetadata = table.privateMetadata("lasforward");
+    setExtraBytesVlr();
+}
+
+
+void LasWriter::readyFile(const std::string& filename,
+    const SpatialReference& srs)
+{
+    std::ostream *out = Utils::createFile(filename, true);
+    if (!out)
+    {
+        std::stringstream out;
+
+        out << "writers.las couldn't open file '" << filename <<
+            "' for output.";
+        throw pdal_error(out.str());
+    }
+    m_curFilename = filename;
+    m_error.setFilename(filename);
+    Utils::writeProgress(m_progressFd, "READYFILE", filename);
+    prepOutput(out, srs);
+}
+
+
+void LasWriter::prepOutput(std::ostream *outStream, const SpatialReference& srs)
+{
+    // Use stage SRS if provided.
+    m_srs = getSpatialReference().empty() ? srs : getSpatialReference();
+
+    handleHeaderForwards(m_forwardMetadata);
+
+    // Filling the header here gives the VLR functions below easy access to
+    // the version information and so on.
+    fillHeader();
+
+    // Spatial reference can potentially change for multiple output files.
+    setVlrsFromSpatialRef();
+    setVlrsFromMetadata(m_forwardMetadata);
+
+    m_summaryData.reset(new LasSummaryData());
+    m_ostream = outStream;
+    if (m_lasHeader.compressed())
+        readyCompression();
+
+    // Compression should cause the last of the VLRs to get filled.  We now
+    // have a valid count, so fill the header again.
+    fillHeader();
+
+    // Write the header.
+    OLeStream out(m_ostream);
+    out << m_lasHeader;
+
+    m_lasHeader.setVlrOffset((uint32_t)m_ostream->tellp());
+
+    for (auto vi = m_vlrs.begin(); vi != m_vlrs.end(); ++vi)
+    {
+        LasVLR& vlr = *vi;
+        vlr.write(out, m_lasHeader.versionEquals(1, 0) ? 0xAABB : 0);
+    }
+
+    // Write the point data start signature for version 1.0.
+    if (m_lasHeader.versionEquals(1, 0))
+        out << (uint16_t)0xCCDD;
+    m_lasHeader.setPointOffset((uint32_t)m_ostream->tellp());
+    if (m_compression == LasCompression::LasZip)
+        openCompression();
+
+    // Set the point buffer size here in case we're using the streaming
+    // interface.
+    m_pointBuf.resize(m_lasHeader.pointLen());
+
+    m_error.setLog(log());
+}
+
+
+/// Search for metadata associated with the provided recordId and userId.
+/// \param  node - Top-level node to use for metadata search.
+/// \param  recordId - Record ID to match.
+/// \param  userId - User ID to match.
+MetadataNode LasWriter::findVlrMetadata(MetadataNode node,
+    uint16_t recordId, const std::string& userId)
+{
+    std::string sRecordId = std::to_string(recordId);
+
+    // Find a node whose name starts with vlr and that has child nodes
+    // with the name and recordId we're looking for.
+    auto pred = [sRecordId,userId](MetadataNode n)
+    {
+        auto recPred = [sRecordId](MetadataNode n)
+        {
+            return n.name() == "record_id" &&
+                n.value() == sRecordId;
+        };
+        auto userPred = [userId](MetadataNode n)
+        {
+            return n.name() == "user_id" &&
+                n.value() == userId;
+        };
+        return (Utils::startsWith(n.name(), "vlr") &&
+            !n.findChild(recPred).empty() &&
+            !n.findChild(userPred).empty());
+    };
+    return node.find(pred);
+}
+
+
+/// Set VLRs from metadata for forwarded info.
+void LasWriter::setVlrsFromMetadata(MetadataNode& forward)
+{
+    std::vector<uint8_t> data;
+
+    if (!m_forwardVlrs)
+        return;
+
+    auto pred = [](MetadataNode n)
+        { return Utils::startsWith(n.name(), "vlr_"); };
+
+    MetadataNodeList nodes = forward.findChildren(pred);
+    for (auto& n : nodes)
+    {
+        const MetadataNode& userIdNode = n.findChild("user_id");
+        const MetadataNode& recordIdNode = n.findChild("record_id");
+        if (recordIdNode.valid() && userIdNode.valid())
+        {
+            data = Utils::base64_decode(n.value());
+            uint16_t recordId = (uint16_t)std::stoi(recordIdNode.value());
+            addVlr(userIdNode.value(), recordId, n.description(), data);
+        }
+    }
+}
+
+
+/// Set VLRs from the active spatial reference.
+/// \param  srs - Active spatial reference.
+void LasWriter::setVlrsFromSpatialRef()
+{
+    // Delete any existing spatial ref VLRs.  This can be an issue if we're
+    // using the reader to write multiple output files via a filename template.
+    deleteVlr(TRANSFORM_USER_ID, GEOTIFF_DIRECTORY_RECORD_ID);
+    deleteVlr(TRANSFORM_USER_ID, GEOTIFF_DOUBLES_RECORD_ID);
+    deleteVlr(TRANSFORM_USER_ID, GEOTIFF_ASCII_RECORD_ID);
+    deleteVlr(TRANSFORM_USER_ID, WKT_RECORD_ID);
+    deleteVlr(LIBLAS_USER_ID, WKT_RECORD_ID);
+
+    if (m_lasHeader.versionAtLeast(1, 4))
+        addWktVlr();
+    else
+        addGeotiffVlrs();
+}
+
+void LasWriter::addGeotiffVlrs()
+{
+    GeotiffSupport geotiff;
+    geotiff.resetTags();
+
+    geotiff.setWkt(m_srs.getWKT());
+
+    addGeotiffVlr(geotiff, GEOTIFF_DIRECTORY_RECORD_ID,
+        "GeoTiff GeoKeyDirectoryTag");
+    addGeotiffVlr(geotiff, GEOTIFF_DOUBLES_RECORD_ID,
+        "GeoTiff GeoDoubleParamsTag");
+    addGeotiffVlr(geotiff, GEOTIFF_ASCII_RECORD_ID,
+        "GeoTiff GeoAsciiParamsTag");
+}
+
+
+/// Add a geotiff VLR from the information associated with the record ID.
+/// \param  geotiff - Geotiff support structure reference.
+/// \param  recordId - Record ID associated with the VLR/Geotiff ref.
+/// \param  description - Description to use with the VLR
+/// \return  Whether the VLR was added.
+void LasWriter::addGeotiffVlr(GeotiffSupport& geotiff, uint16_t recordId,
+    const std::string& description)
+{
+    void *data;
+    int count;
+
+    size_t size = geotiff.getKey(recordId, &count, &data);
+    if (size == 0)
+    {
+        log()->get(LogLevel::Warning) << getName() << ": Invalid spatial "
+            "reference for writing GeoTiff VLR." << std::endl;
+        return;
+    }
+
+    std::vector<uint8_t> buf(size);
+    memcpy(buf.data(), data, size);
+    addVlr(TRANSFORM_USER_ID, recordId, description, buf);
+}
+
+
+/// Add a Well-known Text VLR associated with the spatial reference.
+/// \return  Whether the VLR was added.
+bool LasWriter::addWktVlr()
+{
+    std::string wkt = m_srs.getWKT();
+    if (wkt.empty())
+        return false;
+
+    std::vector<uint8_t> wktBytes(wkt.begin(), wkt.end());
+    // This tacks a NULL to the end of the data, which is required by the spec.
+    wktBytes.resize(wktBytes.size() + 1, 0);
+    addVlr(TRANSFORM_USER_ID, WKT_RECORD_ID, "OGC Transformation Record",
+        wktBytes);
+
+    // The data in the vector gets moved to the VLR, so we have to recreate it.
+    std::vector<uint8_t> wktBytes2(wkt.begin(), wkt.end());
+    wktBytes2.resize(wktBytes2.size() + 1, 0);
+    addVlr(LIBLAS_USER_ID, WKT_RECORD_ID,
+        "OGR variant of OpenGIS WKT SRS", wktBytes2);
+    return true;
+}
+
+
+void LasWriter::setExtraBytesVlr()
+{
+    if (m_extraDims.empty())
+        return;
+
+    std::vector<uint8_t> ebBytes;
+    for (auto& dim : m_extraDims)
+    {
+        ExtraBytesIf eb(dim.m_name, dim.m_dimType.m_type,
+            Dimension::description(dim.m_dimType.m_id));
+        eb.appendTo(ebBytes);
+    }
+
+    addVlr(SPEC_USER_ID, EXTRA_BYTES_RECORD_ID, "Extra Bytes Record", ebBytes);
+}
+
+
+/// Add a standard or variable-length VLR depending on the data size.
+/// \param  userId - VLR user ID
+/// \param  recordId - VLR record ID
+/// \param  description - VLR description
+/// \param  data - Raw VLR data
+void LasWriter::addVlr(const std::string& userId, uint16_t recordId,
+   const std::string& description, std::vector<uint8_t>& data)
+{
+    if (data.size() > LasVLR::MAX_DATA_SIZE)
+    {
+        ExtLasVLR evlr(userId, recordId, description, data);
+        m_eVlrs.push_back(std::move(evlr));
+    }
+    else
+    {
+        LasVLR vlr(userId, recordId, description, data);
+        m_vlrs.push_back(std::move(vlr));
+    }
+}
+
+/// Delete a VLR from the vlr list.
+///
+void LasWriter::deleteVlr(const std::string& userId, uint16_t recordId)
+{
+    auto matches = [&userId, recordId](const LasVLR& vlr)
+    {
+        return vlr.matches(userId, recordId);
+    };
+
+    Utils::remove_if(m_vlrs, matches);
+    Utils::remove_if(m_eVlrs, matches);
+}
+
+
+template <typename T>
+void LasWriter::handleHeaderForward(const std::string& s, T& headerVal,
+    const MetadataNode& base)
+{
+    if (Utils::contains(m_forwards, s) && !headerVal.valSet())
+    {
+        MetadataNode invalid = base.findChild(s + "INVALID");
+        MetadataNode m = base.findChild(s);
+        if (!invalid.valid() && m.valid())
+            headerVal.setVal(m.value<typename T::type>());
+    }
+}
+
+
+void LasWriter::handleHeaderForwards(MetadataNode& forward)
+{
+    handleHeaderForward("major_version", m_majorVersion, forward);
+    handleHeaderForward("minor_version", m_minorVersion, forward);
+    handleHeaderForward("dataformat_id", m_dataformatId, forward);
+    handleHeaderForward("filesource_id", m_filesourceId, forward);
+    handleHeaderForward("global_encoding", m_globalEncoding, forward);
+    handleHeaderForward("project_id", m_projectId, forward);
+    handleHeaderForward("system_id", m_systemId, forward);
+    handleHeaderForward("software_id", m_softwareId, forward);
+    handleHeaderForward("creation_doy", m_creationDoy, forward);
+    handleHeaderForward("creation_year", m_creationYear, forward);
+
+    handleHeaderForward("scale_x", m_scaleX, forward);
+    handleHeaderForward("scale_y", m_scaleY, forward);
+    handleHeaderForward("scale_z", m_scaleZ, forward);
+    handleHeaderForward("offset_x", m_offsetX, forward);
+    handleHeaderForward("offset_y", m_offsetY, forward);
+    handleHeaderForward("offset_z", m_offsetZ, forward);
+
+    m_scaling.m_xXform.m_scale.set(m_scaleX.val());
+    m_scaling.m_yXform.m_scale.set(m_scaleY.val());
+    m_scaling.m_zXform.m_scale.set(m_scaleZ.val());
+    m_scaling.m_xXform.m_offset.set(m_offsetX.val());
+    m_scaling.m_yXform.m_offset.set(m_offsetY.val());
+    m_scaling.m_zXform.m_offset.set(m_offsetZ.val());
+}
+
+/// Fill the LAS header with values as provided in options or forwarded
+/// metadata.
+void LasWriter::fillHeader()
+{
+    const uint16_t WKT_MASK = (1 << 4);
+
+    m_lasHeader.setScaling(m_scaling);
+    m_lasHeader.setVlrCount(m_vlrs.size());
+    m_lasHeader.setEVlrCount(m_eVlrs.size());
+
+    m_lasHeader.setPointFormat(m_dataformatId.val());
+    m_lasHeader.setPointLen(m_lasHeader.basePointLen() + m_extraByteLen);
+    m_lasHeader.setVersionMinor(m_minorVersion.val());
+    m_lasHeader.setCreationYear(m_creationYear.val());
+    m_lasHeader.setCreationDOY(m_creationDoy.val());
+    m_lasHeader.setSoftwareId(m_softwareId.val());
+    m_lasHeader.setSystemId(m_systemId.val());
+    m_lasHeader.setProjectId(m_projectId.val());
+    m_lasHeader.setFileSourceId(m_filesourceId.val());
+
+    // We always write a WKT VLR for version 1.4 and later.
+    uint16_t globalEncoding = m_globalEncoding.val();
+    if (m_lasHeader.versionAtLeast(1, 4))
+        globalEncoding |= WKT_MASK;
+    m_lasHeader.setGlobalEncoding(globalEncoding);
+
+    if (!m_lasHeader.pointFormatSupported())
+    {
+        std::ostringstream oss;
+        oss << "Unsupported LAS output point format: " <<
+            (int)m_lasHeader.pointFormat() << ".";
+        throw pdal_error(oss.str());
+    }
+}
+
+
+void LasWriter::readyCompression()
+{
+    if (m_compression == LasCompression::LasZip)
+        readyLasZipCompression();
+    else if (m_compression == LasCompression::LazPerf)
+        readyLazPerfCompression();
+}
+
+
+void LasWriter::readyLasZipCompression()
+{
+#ifdef PDAL_HAVE_LASZIP
+    m_zipPoint.reset(new LasZipPoint(m_lasHeader.pointFormat(),
+        m_lasHeader.pointLen()));
+    m_zipper.reset(new LASzipper());
+    // Note: this will make the VLR count in the header incorrect, but we
+    // rewrite that bit in finishOutput() to fix it up.
+    std::vector<uint8_t> data = m_zipPoint->vlrData();
+    addVlr(LASZIP_USER_ID, LASZIP_RECORD_ID, "http://laszip.org", data);
+#endif
+}
+
+
+void LasWriter::readyLazPerfCompression()
+{
+#ifdef PDAL_HAVE_LAZPERF
+    if (m_lasHeader.versionAtLeast(1, 4))
+        throw pdal_error("Can't write version 1.4 output with LAZperf.");
+
+    laszip::factory::record_schema schema;
+    schema.push(laszip::factory::record_item::POINT10);
+    if (m_lasHeader.hasTime())
+        schema.push(laszip::factory::record_item::GPSTIME);
+    if (m_lasHeader.hasColor())
+        schema.push(laszip::factory::record_item::RGB12);
+    laszip::io::laz_vlr zipvlr = laszip::io::laz_vlr::from_schema(schema);
+    std::vector<uint8_t> data(zipvlr.size());
+    zipvlr.extract((char *)data.data());
+    addVlr(LASZIP_USER_ID, LASZIP_RECORD_ID, "http://laszip.org", data);
+
+    m_compressor.reset(new LazPerfVlrCompressor(*m_ostream, schema,
+        zipvlr.chunk_size));
+#endif
+}
+
+
+/// Prepare the compressor to write points.
+/// \param  pointFormat - Formt of points we're writing.
+void LasWriter::openCompression()
+{
+#ifdef PDAL_HAVE_LASZIP
+    if (!m_zipper->open(*m_ostream, m_zipPoint->GetZipper()))
+    {
+        std::ostringstream oss;
+        const char* err = m_zipper->get_error();
+        if (err == NULL)
+            err = "(unknown error)";
+        oss << "Error opening LASzipper: " << std::string(err);
+        throw pdal_error(oss.str());
+    }
+#endif
+}
+
+
+bool LasWriter::processOne(PointRef& point)
+{
+    //ABELL - Need to do something about auto offset.
+    LeInserter ostream(m_pointBuf.data(), m_pointBuf.size());
+
+    if (!fillPointBuf(point, ostream))
+        return false;
+
+    if (m_compression == LasCompression::LasZip)
+        writeLasZipBuf(m_pointBuf.data(), m_lasHeader.pointLen(), 1);
+    else if (m_compression == LasCompression::LazPerf)
+        writeLazPerfBuf(m_pointBuf.data(), m_lasHeader.pointLen(), 1);
+    else
+        m_ostream->write(m_pointBuf.data(), m_lasHeader.pointLen());
+    return true;
+}
+
+
+void LasWriter::writeView(const PointViewPtr view)
+{
+    Utils::writeProgress(m_progressFd, "READYVIEW",
+        std::to_string(view->size()));
+    m_scaling.setAutoXForm(view);
+
+    point_count_t pointLen = m_lasHeader.pointLen();
+
+    // Make a buffer of at most a meg.
+    m_pointBuf.resize(std::min((point_count_t)1000000, pointLen * view->size()));
+
+    const PointView& viewRef(*view.get());
+
+    point_count_t remaining = view->size();
+    PointId idx = 0;
+    while (remaining)
+    {
+        point_count_t filled = fillWriteBuf(viewRef, idx, m_pointBuf);
+        idx += filled;
+        remaining -= filled;
+
+        if (m_compression == LasCompression::LasZip)
+            writeLasZipBuf(m_pointBuf.data(), pointLen, filled);
+        else if (m_compression == LasCompression::LazPerf)
+            writeLazPerfBuf(m_pointBuf.data(), pointLen, filled);
+        else
+            m_ostream->write(m_pointBuf.data(), filled * pointLen);
+    }
+    Utils::writeProgress(m_progressFd, "DONEVIEW",
+        std::to_string(view->size()));
+}
+
+
+void LasWriter::writeLasZipBuf(char *pos, size_t pointLen, point_count_t numPts)
+{
+#ifdef PDAL_HAVE_LASZIP
+    for (point_count_t i = 0; i < numPts; i++)
+    {
+        memcpy(m_zipPoint->m_lz_point_data.data(), pos, pointLen);
+        if (!m_zipper->write(m_zipPoint->m_lz_point))
+        {
+            std::ostringstream oss;
+            const char* err = m_zipper->get_error();
+            if (err == NULL)
+                err = "(unknown error)";
+            oss << "Error writing point: " << std::string(err);
+            throw pdal_error(oss.str());
+        }
+        pos += pointLen;
+    }
+#endif
+}
+
+
+void LasWriter::writeLazPerfBuf(char *pos, size_t pointLen,
+    point_count_t numPts)
+{
+#ifdef PDAL_HAVE_LAZPERF
+    for (point_count_t i = 0; i < numPts; i++)
+    {
+        m_compressor->compress(pos);
+        pos += pointLen;
+    }
+#endif
+}
+
+
+bool LasWriter::fillPointBuf(PointRef& point, LeInserter& ostream)
+{
+    bool has14Format = m_lasHeader.has14Format();
+    bool hasColor = m_lasHeader.hasColor();
+    bool hasTime = m_lasHeader.hasTime();
+    bool hasInfrared = m_lasHeader.hasInfrared();
+    static const size_t maxReturnCount = m_lasHeader.maxReturnCount();
+
+    // we always write the base fields
+    using namespace Dimension;
+
+    uint8_t returnNumber(1);
+    uint8_t numberOfReturns(1);
+    if (point.hasDim(Id::ReturnNumber))
+    {
+        returnNumber = point.getFieldAs<uint8_t>(Id::ReturnNumber);
+        if (returnNumber < 1 || returnNumber > maxReturnCount)
+            m_error.returnNumWarning(returnNumber);
+    }
+    if (point.hasDim(Id::NumberOfReturns))
+        numberOfReturns = point.getFieldAs<uint8_t>(Id::NumberOfReturns);
+    if (numberOfReturns == 0)
+        m_error.numReturnsWarning(0);
+    if (numberOfReturns > maxReturnCount)
+    {
+        if (m_discardHighReturnNumbers)
+        {
+            // If this return number is too high, pitch the point.
+            if (returnNumber > maxReturnCount)
+                return false;
+            numberOfReturns = maxReturnCount;
+        }
+        else
+            m_error.numReturnsWarning(numberOfReturns);
+    }
+
+    auto converter = [this](double d, Dimension::Id dim) -> int32_t
+    {
+        int32_t i;
+
+        if (!Utils::numericCast(d, i))
+        {
+            std::ostringstream oss;
+            oss << "Unable to convert scaled value (" << d << ") to "
+                "int32 for dimension '" << Dimension::name(dim) <<
+                "' when writing LAS/LAZ file " << m_curFilename << ".";
+            throw pdal_error(oss.str());
+        }
+        return i;
+    };
+
+    double xOrig = point.getFieldAs<double>(Id::X);
+    double yOrig = point.getFieldAs<double>(Id::Y);
+    double zOrig = point.getFieldAs<double>(Id::Z);
+    double x = m_scaling.m_xXform.toScaled(xOrig);
+    double y = m_scaling.m_yXform.toScaled(yOrig);
+    double z = m_scaling.m_zXform.toScaled(zOrig);
+
+    ostream << converter(x, Id::X);
+    ostream << converter(y, Id::Y);
+    ostream << converter(z, Id::Z);
+
+    ostream << point.getFieldAs<uint16_t>(Id::Intensity);
+
+    uint8_t scanChannel = point.getFieldAs<uint8_t>(Id::ScanChannel);
+    uint8_t scanDirectionFlag =
+        point.getFieldAs<uint8_t>(Id::ScanDirectionFlag);
+    uint8_t edgeOfFlightLine =
+        point.getFieldAs<uint8_t>(Id::EdgeOfFlightLine);
+
+    if (has14Format)
+    {
+        uint8_t bits = returnNumber | (numberOfReturns << 4);
+        ostream << bits;
+
+        uint8_t classFlags = point.getFieldAs<uint8_t>(Id::ClassFlags);
+        bits = (classFlags & 0x0F) |
+            ((scanChannel & 0x03) << 4) |
+            ((scanDirectionFlag & 0x01) << 6) |
+            ((edgeOfFlightLine & 0x01) << 7);
+        ostream << bits;
+    }
+    else
+    {
+        uint8_t bits = returnNumber | (numberOfReturns << 3) |
+            (scanDirectionFlag << 6) | (edgeOfFlightLine << 7);
+        ostream << bits;
+    }
+
+    ostream << point.getFieldAs<uint8_t>(Id::Classification);
+
+    uint8_t userData = point.getFieldAs<uint8_t>(Id::UserData);
+    if (has14Format)
+    {
+         int16_t scanAngleRank =
+             point.getFieldAs<float>(Id::ScanAngleRank) / .006;
+         ostream << userData << scanAngleRank;
+    }
+    else
+    {
+        int8_t scanAngleRank = point.getFieldAs<int8_t>(Id::ScanAngleRank);
+        ostream << scanAngleRank << userData;
+    }
+
+    ostream << point.getFieldAs<uint16_t>(Id::PointSourceId);
+
+    if (hasTime)
+        ostream << point.getFieldAs<double>(Id::GpsTime);
+
+    if (hasColor)
+    {
+        ostream << point.getFieldAs<uint16_t>(Id::Red);
+        ostream << point.getFieldAs<uint16_t>(Id::Green);
+        ostream << point.getFieldAs<uint16_t>(Id::Blue);
+    }
+
+    if (hasInfrared)
+        ostream << point.getFieldAs<uint16_t>(Id::Infrared);
+
+    Everything e;
+    for (auto& dim : m_extraDims)
+    {
+        point.getField((char *)&e, dim.m_dimType.m_id, dim.m_dimType.m_type);
+        Utils::insertDim(ostream, dim.m_dimType.m_type, e);
+    }
+
+    m_summaryData->addPoint(xOrig, yOrig, zOrig, returnNumber);
+    return true;
+}
+
+
+point_count_t LasWriter::fillWriteBuf(const PointView& view,
+    PointId startId, std::vector<char>& buf)
+{
+    point_count_t blocksize = buf.size() / m_lasHeader.pointLen();
+    blocksize = std::min(blocksize, view.size() - startId);
+    PointId lastId = startId + blocksize;
+
+    LeInserter ostream(buf.data(), buf.size());
+    PointRef point = (const_cast<PointView&>(view)).point(0);
+    for (PointId idx = startId; idx < lastId; idx++)
+    {
+        point.setPointId(idx);
+        fillPointBuf(point, ostream);
+    }
+    return blocksize;
+}
+
+
+void LasWriter::doneFile()
+{
+    finishOutput();
+    Utils::writeProgress(m_progressFd, "DONEFILE", m_curFilename);
+    getMetadata().addList("filename", m_curFilename);
+    delete m_ostream;
+    m_ostream = NULL;
+}
+
+
+void LasWriter::finishOutput()
+{
+    if (m_compression == LasCompression::LasZip)
+        finishLasZipOutput();
+    else if (m_compression == LasCompression::LazPerf)
+        finishLazPerfOutput();
+
+    log()->get(LogLevel::Debug) << "Wrote " <<
+        m_summaryData->getTotalNumPoints() <<
+        " points to the LAS file" << std::endl;
+
+    OLeStream out(m_ostream);
+
+    for (auto vi = m_eVlrs.begin(); vi != m_eVlrs.end(); ++vi)
+    {
+        ExtLasVLR evlr = *vi;
+        out << evlr;
+    }
+
+    // Reset the offset/scale since it may have been auto-computed
+    m_lasHeader.setScaling(m_scaling);
+
+    // The summary is calculated as points are written.
+    m_lasHeader.setSummary(*m_summaryData);
+
+    out.seek(0);
+    out << m_lasHeader;
+    out.seek(m_lasHeader.pointOffset());
+
+    m_ostream->flush();
+}
+
+
+void LasWriter::finishLasZipOutput()
+{
+#ifdef PDAL_HAVE_LASZIP
+    m_zipper->close();
+#endif
+}
+
+
+void LasWriter::finishLazPerfOutput()
+{
+#ifdef PDAL_HAVE_LAZPERF
+    m_compressor->done();
+#endif
+}
+
+} // namespace pdal
diff --git a/io/LasWriter.hpp b/io/LasWriter.hpp
new file mode 100644
index 0000000..0ac8103
--- /dev/null
+++ b/io/LasWriter.hpp
@@ -0,0 +1,173 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Compression.hpp>
+#include <pdal/FlexWriter.hpp>
+#include <pdal/plugin.hpp>
+
+#include "HeaderVal.hpp"
+#include "LasError.hpp"
+#include "LasHeader.hpp"
+#include "LasUtils.hpp"
+#include "LasSummaryData.hpp"
+#include "LasZipPoint.hpp"
+
+extern "C" int32_t LasWriter_ExitFunc();
+extern "C" PF_ExitFunc LasWriter_InitPlugin();
+
+namespace pdal
+{
+class LeInserter;
+class LasTester;
+class NitfWriter;
+class GeotiffSupport;
+
+struct VlrOptionInfo
+{
+    std::string m_name;
+    std::string m_value;
+    std::string m_userId;
+    uint16_t m_recordId;
+    std::string m_description;
+};
+
+class PDAL_DLL LasWriter : public FlexWriter
+{
+    friend class LasTester;
+    friend class NitfWriter;
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    LasWriter();
+
+protected:
+    void prepOutput(std::ostream *out, const SpatialReference& srs);
+    void finishOutput();
+
+private:
+    LasError m_error;
+    LasHeader m_lasHeader;
+    std::unique_ptr<LasSummaryData> m_summaryData;
+    std::unique_ptr<LASzipper> m_zipper;
+    std::unique_ptr<LasZipPoint> m_zipPoint;
+    std::unique_ptr<LazPerfVlrCompressor> m_compressor;
+    bool m_discardHighReturnNumbers;
+    std::map<std::string, std::string> m_headerVals;
+    std::vector<VlrOptionInfo> m_optionInfos;
+    std::ostream *m_ostream;
+    std::vector<LasVLR> m_vlrs;
+    std::vector<ExtLasVLR> m_eVlrs;
+    StringList m_extraDimSpec;
+    std::vector<ExtraDim> m_extraDims;
+    uint16_t m_extraByteLen;
+    SpatialReference m_srs;
+    std::string m_curFilename;
+    StringList m_forwardSpec;
+    std::set<std::string> m_forwards;
+    bool m_forwardVlrs;
+    LasCompression m_compression;
+    std::vector<char> m_pointBuf;
+    SpatialReference m_aSrs;
+
+    NumHeaderVal<uint8_t, 1, 1> m_majorVersion;
+    NumHeaderVal<uint8_t, 1, 4> m_minorVersion;
+    NumHeaderVal<uint8_t, 0, 10> m_dataformatId;
+    // MSVC doesn't see numeric_limits::max() as constexpr do doesn't allow
+    // it as defaults for templates.  Remove when possible.
+    NumHeaderVal<uint16_t, 0, 65535> m_filesourceId;
+    NumHeaderVal<uint16_t, 0, 31> m_globalEncoding;
+    UuidHeaderVal m_projectId;
+    StringHeaderVal<32> m_systemId;
+    StringHeaderVal<32> m_softwareId;
+    NumHeaderVal<uint16_t, 0, 366> m_creationDoy;
+    // MSVC doesn't see numeric_limits::max() as constexpr so doesn't allow
+    // them as defaults for templates.  Remove when possible.
+    NumHeaderVal<uint16_t, 0, 65535> m_creationYear;
+    StringHeaderVal<20> m_scaleX;
+    StringHeaderVal<20> m_scaleY;
+    StringHeaderVal<20> m_scaleZ;
+    StringHeaderVal<20> m_offsetX;
+    StringHeaderVal<20> m_offsetY;
+    StringHeaderVal<20> m_offsetZ;
+    MetadataNode m_forwardMetadata;
+
+    virtual void addArgs(ProgramArgs& args);
+    virtual void initialize();
+    virtual void prepared(PointTableRef table);
+    virtual void readyTable(PointTableRef table);
+    virtual void readyFile(const std::string& filename,
+        const SpatialReference& srs);
+    virtual void writeView(const PointViewPtr view);
+    virtual bool processOne(PointRef& point);
+    virtual void doneFile();
+
+    void fillForwardList();
+    template <typename T>
+    void handleHeaderForward(const std::string& s, T& headerVal,
+        const MetadataNode& base);
+    void handleHeaderForwards(MetadataNode& forward);
+    void fillHeader();
+    bool fillPointBuf(PointRef& point, LeInserter& ostream);
+    point_count_t fillWriteBuf(const PointView& view, PointId startId,
+        std::vector<char>& buf);
+    void writeLasZipBuf(char *data, size_t pointLen, point_count_t numPts);
+    void writeLazPerfBuf(char *data, size_t pointLen, point_count_t numPts);
+    void setVlrsFromMetadata(MetadataNode& forward);
+    MetadataNode findVlrMetadata(MetadataNode node, uint16_t recordId,
+        const std::string& userId);
+    void setExtraBytesVlr();
+    void setVlrsFromSpatialRef();
+    void readyCompression();
+    void readyLasZipCompression();
+    void readyLazPerfCompression();
+    void openCompression();
+    void addVlr(const std::string& userId, uint16_t recordId,
+        const std::string& description, std::vector<uint8_t>& data);
+    void deleteVlr(const std::string& userId, uint16_t recordId);
+    void addGeotiffVlrs();
+    void addGeotiffVlr(GeotiffSupport& geotiff, uint16_t recordId,
+        const std::string& description);
+    bool addWktVlr();
+    void finishLasZipOutput();
+    void finishLazPerfOutput();
+
+    LasWriter& operator=(const LasWriter&); // not implemented
+    LasWriter(const LasWriter&); // not implemented
+};
+
+} // namespace pdal
diff --git a/io/LasZipPoint.cpp b/io/LasZipPoint.cpp
new file mode 100644
index 0000000..8018133
--- /dev/null
+++ b/io/LasZipPoint.cpp
@@ -0,0 +1,124 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <sstream>
+#include <string.h>
+
+#include <pdal/pdal_internal.hpp>
+
+#ifdef PDAL_HAVE_LASZIP
+
+#include "LasVLR.hpp"
+#include "LasZipPoint.hpp"
+
+namespace pdal
+{
+
+// Read-mode ctor.
+LasZipPoint::LasZipPoint(LasVLR *vlr) :
+    m_zip(new LASzip()), m_lz_point(NULL), m_lz_point_size(0)
+{
+    if (!vlr || !m_zip->unpack((unsigned char *)vlr->data(),
+        (int)vlr->dataLen()))
+    {
+        std::ostringstream oss;
+        const char* err = m_zip->get_error();
+        if (err == NULL) 
+            err = "(unknown error)";
+        oss << "Error unpacking zip VLR data: " << std::string(err);
+        throw pdal_error(oss.str());
+    }
+    ConstructItems();
+}
+
+
+// Write-mode ctor.
+LasZipPoint::LasZipPoint(uint8_t format, uint16_t pointLen) :
+    m_zip(new LASzip()), m_lz_point(NULL), m_lz_point_size(0)
+{
+    if (!m_zip->setup(format, pointLen))
+    {
+        std::ostringstream oss;
+        const char* err = m_zip->get_error();
+        if (err == NULL)
+            err = "(unknown error)";
+        oss << "Error setting up LASzip for format " << format << ": " <<
+            err;
+        throw pdal_error(oss.str());
+    }
+    ConstructItems();
+}
+
+
+LasZipPoint::~LasZipPoint()
+{
+    delete[] m_lz_point;
+}
+
+
+void LasZipPoint::ConstructItems()
+{
+    // construct the object that will hold a laszip point
+
+    // compute the point size
+    m_lz_point_size = 0;
+    for (unsigned int i = 0; i < m_zip->num_items; i++)
+        m_lz_point_size += m_zip->items[i].size;
+
+    // create the point data
+    unsigned int point_offset = 0;
+    m_lz_point = new unsigned char*[m_zip->num_items];
+
+    m_lz_point_data.resize(m_lz_point_size);
+    for (unsigned i = 0; i < m_zip->num_items; i++)
+    {
+        m_lz_point[i] = &(m_lz_point_data[point_offset]);
+        point_offset += m_zip->items[i].size;
+    }
+}
+
+
+std::vector<uint8_t> LasZipPoint::vlrData() const
+{
+    // This puts a bunch of data into an array of data pointed to by 'data',
+    // suitable for storage in a VLR.
+    uint8_t* data;
+    int num;
+    m_zip->pack(data, num);
+    return std::vector<uint8_t>(data, data + num);
+}
+
+} // namespace pdal
+
+#endif // PDAL_HAVE_LASZIP
diff --git a/io/LasZipPoint.hpp b/io/LasZipPoint.hpp
new file mode 100644
index 0000000..c887d6b
--- /dev/null
+++ b/io/LasZipPoint.hpp
@@ -0,0 +1,83 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <vector>
+
+#ifdef PDAL_HAVE_LASZIP
+#include <laszip/laszip.hpp>
+#include <laszip/lasunzipper.hpp>
+#include <laszip/laszipper.hpp>
+#endif
+
+namespace pdal
+{
+
+#ifdef PDAL_HAVE_LASZIP
+
+class LasVLR;
+
+class PDAL_DLL LasZipPoint
+{
+public:
+    LasZipPoint(LasVLR *lasHeader);
+    LasZipPoint(uint8_t format, uint16_t pointLen);
+    ~LasZipPoint();
+
+    std::vector<uint8_t> vlrData() const;
+    LASzip* GetZipper() const
+        { return m_zip.get(); }
+    
+private:
+    std::unique_ptr<LASzip> m_zip;
+
+//ABELL - This block should be made private.
+public:
+    unsigned char** m_lz_point;
+    unsigned int m_lz_point_size;
+    std::vector<uint8_t> m_lz_point_data;
+
+private:
+    void ConstructItems();
+};
+#else // PDAL_HAVE_LASZIP
+// The types here just need to be something suitable for a smart pointer.
+// They aren't ever used beyond testing for NULL.
+typedef char LASzipper;
+typedef char LASunzipper;
+typedef char LasZipPoint;
+#endif
+
+} // namespace pdal
diff --git a/io/null/NullWriter.cpp b/io/NullWriter.cpp
similarity index 100%
rename from io/null/NullWriter.cpp
rename to io/NullWriter.cpp
diff --git a/io/null/NullWriter.hpp b/io/NullWriter.hpp
similarity index 100%
rename from io/null/NullWriter.hpp
rename to io/NullWriter.hpp
diff --git a/io/optech/OptechCommon.hpp b/io/OptechCommon.hpp
similarity index 100%
rename from io/optech/OptechCommon.hpp
rename to io/OptechCommon.hpp
diff --git a/io/OptechReader.cpp b/io/OptechReader.cpp
new file mode 100644
index 0000000..f8ce0ed
--- /dev/null
+++ b/io/OptechReader.cpp
@@ -0,0 +1,283 @@
+/******************************************************************************
+* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#define _USE_MATH_DEFINES
+#include "OptechReader.hpp"
+
+#include <cmath>
+#include <cstring>
+#include <sstream>
+
+#include <pdal/PDALUtils.hpp>
+#include <pdal/SpatialReference.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "readers.optech",
+    "Optech reader support.",
+    "http://pdal.io/stages/readers.optech.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, OptechReader, Reader, s_info);
+
+std::string OptechReader::getName() const
+{
+    return s_info.name;
+}
+
+#ifndef _WIN32
+const size_t OptechReader::MaximumNumberOfReturns;
+const size_t OptechReader::MaxNumRecordsInBuffer;
+const size_t OptechReader::NumBytesInRecord;
+#endif
+
+OptechReader::OptechReader()
+    : Reader()
+    , m_header()
+    , m_boresightMatrix(georeference::createIdentityMatrix())
+    , m_istream()
+    , m_buffer()
+    , m_extractor(m_buffer.data(), 0)
+    , m_recordIndex(0)
+    , m_returnIndex(0)
+    , m_pulse()
+{
+    // The Optech docs say that their lat/longs are referenced
+    // to the WGS84 reference frame.
+    setSpatialReference(SpatialReference("EPSG:4326"));
+}
+
+
+Dimension::IdList OptechReader::getDefaultDimensions()
+{
+    Dimension::IdList dims;
+    dims.push_back(Dimension::Id::X);
+    dims.push_back(Dimension::Id::Y);
+    dims.push_back(Dimension::Id::Z);
+    dims.push_back(Dimension::Id::GpsTime);
+    dims.push_back(Dimension::Id::ReturnNumber);
+    dims.push_back(Dimension::Id::NumberOfReturns);
+    dims.push_back(Dimension::Id::EchoRange);
+    dims.push_back(Dimension::Id::Intensity);
+    dims.push_back(Dimension::Id::ScanAngleRank);
+    return dims;
+}
+
+
+const CsdHeader& OptechReader::getHeader() const { return m_header; }
+
+
+void OptechReader::initialize()
+{
+    ILeStream stream(Utils::openFile(m_filename));
+    if (!stream)
+    {
+        std::stringstream ss;
+        ss << "Unable to open " << m_filename << " for reading.";
+        throw pdal_error(ss.str());
+    }
+
+    stream.get(m_header.signature, 4);
+    if (strcmp(m_header.signature, "CSD") != 0)
+    {
+        std::stringstream ss;
+        ss << "Invalid header signature when reading CSD file: '"
+           << m_header.signature << "'";
+        throw optech_error(ss.str());
+    }
+    stream.get(m_header.vendorId, 64);
+    stream.get(m_header.softwareVersion, 32);
+    stream >> m_header.formatVersion >> m_header.headerSize >>
+        m_header.gpsWeek >> m_header.minTime >> m_header.maxTime >>
+        m_header.numRecords >> m_header.numStrips;
+    for (size_t i = 0; i < 256; ++i)
+    {
+        stream >> m_header.stripPointers[i];
+    }
+    stream >> m_header.misalignmentAngles[0] >>
+        m_header.misalignmentAngles[1] >> m_header.misalignmentAngles[2] >>
+        m_header.imuOffsets[0] >> m_header.imuOffsets[1] >>
+        m_header.imuOffsets[2] >> m_header.temperature >> m_header.pressure;
+    stream.get(m_header.freeSpace, 830);
+
+    m_boresightMatrix = createOptechRotationMatrix(
+        m_header.misalignmentAngles[0] + m_header.imuOffsets[0],
+        m_header.misalignmentAngles[1] + m_header.imuOffsets[1],
+        m_header.misalignmentAngles[2] + m_header.imuOffsets[2]);
+}
+
+
+void OptechReader::addDimensions(PointLayoutPtr layout)
+{
+    for (auto it : getDefaultDimensions())
+    {
+        layout->registerDim(it);
+    }
+}
+
+
+void OptechReader::ready(PointTableRef)
+{
+    m_istream.reset(new IStream(m_filename));
+    if (!*m_istream)
+    {
+        std::stringstream ss;
+        ss << "Unable to open " << m_filename << " for reading.";
+        throw pdal_error(ss.str());
+    }
+
+    m_istream->seek(m_header.headerSize);
+    m_recordIndex = 0;
+    m_returnIndex = 0;
+    m_pulse = CsdPulse();
+}
+
+
+point_count_t OptechReader::read(PointViewPtr data,
+                                 point_count_t countRequested)
+{
+    point_count_t numRead = 0;
+    point_count_t dataIndex = data->size();
+
+    while (numRead < countRequested)
+    {
+        if (m_returnIndex == 0)
+        {
+            if (!m_extractor.good())
+            {
+                if (m_recordIndex >= m_header.numRecords)
+                {
+                    break;
+                }
+                m_recordIndex += fillBuffer();
+            }
+
+            m_extractor >> m_pulse.gpsTime >> m_pulse.returnCount >>
+                m_pulse.range[0] >> m_pulse.range[1] >> m_pulse.range[2] >>
+                m_pulse.range[3] >> m_pulse.intensity[0] >>
+                m_pulse.intensity[1] >> m_pulse.intensity[2] >>
+                m_pulse.intensity[3] >> m_pulse.scanAngle >> m_pulse.roll >>
+                m_pulse.pitch >> m_pulse.heading >> m_pulse.latitude >>
+                m_pulse.longitude >> m_pulse.elevation;
+
+            if (m_pulse.returnCount == 0)
+            {
+                m_returnIndex = 0;
+                continue;
+            }
+
+            // In all the csd files that we've tested, the longitude
+            // values have been less than -2pi.
+            if (m_pulse.longitude < -M_PI * 2)
+            {
+                m_pulse.longitude = m_pulse.longitude + M_PI * 2;
+            }
+            else if (m_pulse.longitude > M_PI * 2)
+            {
+                m_pulse.longitude = m_pulse.longitude - M_PI * 2;
+            }
+        }
+
+        georeference::Xyz gpsPoint = georeference::Xyz(
+            m_pulse.longitude, m_pulse.latitude, m_pulse.elevation);
+        georeference::RotationMatrix rotationMatrix =
+            createOptechRotationMatrix(m_pulse.roll, m_pulse.pitch,
+                                       m_pulse.heading);
+        georeference::Xyz point = pdal::georeference::georeferenceWgs84(
+            m_pulse.range[m_returnIndex], m_pulse.scanAngle,
+            m_boresightMatrix, rotationMatrix, gpsPoint);
+
+        data->setField(Dimension::Id::X, dataIndex, point.X * 180 / M_PI);
+        data->setField(Dimension::Id::Y, dataIndex, point.Y * 180 / M_PI);
+        data->setField(Dimension::Id::Z, dataIndex, point.Z);
+        data->setField(Dimension::Id::GpsTime, dataIndex, m_pulse.gpsTime);
+        if (m_returnIndex == MaximumNumberOfReturns - 1)
+        {
+            data->setField(Dimension::Id::ReturnNumber, dataIndex,
+                          m_pulse.returnCount);
+        }
+        else
+        {
+            data->setField(Dimension::Id::ReturnNumber, dataIndex,
+                          m_returnIndex + 1);
+        }
+        data->setField(Dimension::Id::NumberOfReturns, dataIndex,
+                      m_pulse.returnCount);
+        data->setField(Dimension::Id::EchoRange, dataIndex,
+                      m_pulse.range[m_returnIndex]);
+        data->setField(Dimension::Id::Intensity, dataIndex,
+                      m_pulse.intensity[m_returnIndex]);
+        data->setField(Dimension::Id::ScanAngleRank, dataIndex,
+                      m_pulse.scanAngle * 180 / M_PI);
+
+        if (m_cb)
+            m_cb(*data, dataIndex);
+
+        ++dataIndex;
+        ++numRead;
+        ++m_returnIndex;
+
+        if (m_returnIndex >= m_pulse.returnCount ||
+            m_returnIndex >= MaximumNumberOfReturns)
+        {
+            m_returnIndex = 0;
+        }
+    }
+    return numRead;
+}
+
+
+size_t OptechReader::fillBuffer()
+{
+    size_t numRecords = std::min<size_t>(m_header.numRecords - m_recordIndex,
+                                         MaxNumRecordsInBuffer);
+
+    buffer_size_t bufferSize = NumBytesInRecord * numRecords;
+    m_buffer.resize(bufferSize);
+    m_istream->get(m_buffer);
+    m_extractor = LeExtractor(m_buffer.data(), m_buffer.size());
+    return numRecords;
+}
+
+
+void OptechReader::done(PointTableRef)
+{
+    m_istream.reset();
+}
+
+} // namespace pdal
+
diff --git a/io/optech/OptechReader.hpp b/io/OptechReader.hpp
similarity index 100%
rename from io/optech/OptechReader.hpp
rename to io/OptechReader.hpp
diff --git a/io/optech/OptechRotationMatrix.hpp b/io/OptechRotationMatrix.hpp
similarity index 100%
rename from io/optech/OptechRotationMatrix.hpp
rename to io/OptechRotationMatrix.hpp
diff --git a/io/ply/PlyReader.cpp b/io/PlyReader.cpp
similarity index 100%
rename from io/ply/PlyReader.cpp
rename to io/PlyReader.cpp
diff --git a/io/PlyReader.hpp b/io/PlyReader.hpp
new file mode 100644
index 0000000..c7e0642
--- /dev/null
+++ b/io/PlyReader.hpp
@@ -0,0 +1,78 @@
+/******************************************************************************
+* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <string>
+
+#include <rply/rply.h>
+
+#include <pdal/Dimension.hpp>
+#include <pdal/Reader.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/plugin.hpp>
+
+extern "C" int32_t PlyReader_ExitFunc();
+extern "C" PF_ExitFunc PlyReader_InitPlugin();
+
+namespace pdal
+{
+
+class PDAL_DLL PlyReader : public Reader
+{
+public:
+    static void *create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    typedef std::map<std::string, Dimension::Id> DimensionMap;
+
+    PlyReader();
+
+    static Dimension::IdList getDefaultDimensions();
+
+private:
+    virtual void initialize();
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual void ready(PointTableRef table);
+    virtual point_count_t read(PointViewPtr view, point_count_t num);
+    virtual void done(PointTableRef table);
+
+    p_ply m_ply;
+
+    DimensionMap m_vertexDimensions;
+    std::map<std::string, Dimension::Type> m_vertexTypes;
+};
+}
+
diff --git a/io/PlyWriter.cpp b/io/PlyWriter.cpp
new file mode 100644
index 0000000..a3cb20b
--- /dev/null
+++ b/io/PlyWriter.cpp
@@ -0,0 +1,205 @@
+/******************************************************************************
+* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "PlyWriter.hpp"
+
+#include <sstream>
+
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+namespace pdal
+{
+namespace
+{
+
+void createErrorCallback(p_ply ply, const char* message)
+{
+    std::stringstream ss;
+    ss << "Error when creating ply file: " << message;
+    throw pdal_error(ss.str());
+}
+
+
+e_ply_type getPlyType(Dimension::Type type)
+{
+    static std::map<Dimension::Type, e_ply_type> types =
+    {
+        { Dimension::Type::Unsigned8, PLY_UINT8 },
+        { Dimension::Type::Signed8, PLY_INT8 },
+        { Dimension::Type::Unsigned16, PLY_UINT16 },
+        { Dimension::Type::Signed16, PLY_INT16 },
+        { Dimension::Type::Unsigned32, PLY_UINT32 },
+        { Dimension::Type::Signed32, PLY_INT32 },
+        { Dimension::Type::Float, PLY_FLOAT32 },
+        { Dimension::Type::Double, PLY_FLOAT64 }
+    };
+
+    return types[type];
+}
+
+} // unnamed namespace
+
+
+static PluginInfo const s_info = PluginInfo(
+        "writers.ply",
+        "ply writer",
+        "http://pdal.io/stages/writers.ply.html"
+        );
+
+CREATE_STATIC_PLUGIN(1, 0, PlyWriter, Writer, s_info)
+
+std::string PlyWriter::getName() const { return s_info.name; }
+
+
+PlyWriter::PlyWriter()
+    : m_ply(nullptr)
+    , m_pointCollector(nullptr)
+    , m_storageMode(PLY_DEFAULT)
+{}
+
+
+void PlyWriter::addArgs(ProgramArgs& args)
+{
+    args.add("filename", "Output filename", m_filename).setPositional();
+    args.add("storage_mode", "PLY Storage mode", m_storageModeSpec, "default");
+}
+
+
+void PlyWriter::initialize()
+{
+    std::string storageMode(m_storageModeSpec);
+    storageMode = Utils::tolower(storageMode);
+
+    if (storageMode == "ascii")
+    {
+        m_storageMode = PLY_ASCII;
+    }
+    else if (storageMode == "little endian")
+    {
+        m_storageMode = PLY_LITTLE_ENDIAN;
+    }
+    else if (storageMode == "big endian")
+    {
+        m_storageMode = PLY_BIG_ENDIAN;
+    }
+    else if (storageMode == "default")
+    {
+        m_storageMode = PLY_DEFAULT;
+    }
+    else
+    {
+        std::stringstream ss;
+        ss << "Unknown storage mode '" << m_storageModeSpec <<
+            "'. Known storage modes are: 'ascii', 'little endian', "
+            "'big endian', and 'default'";
+        throw pdal_error(ss.str());
+    }
+}
+
+
+void PlyWriter::ready(PointTableRef table)
+{
+    m_ply = ply_create(m_filename.c_str(), m_storageMode, createErrorCallback,
+        0, nullptr);
+    if (!m_ply)
+    {
+        std::stringstream ss;
+        ss << "Could not open file for writing: " << m_filename;
+        throw pdal_error(ss.str());
+    }
+    m_pointCollector.reset(new PointView(table));
+}
+
+
+void PlyWriter::write(const PointViewPtr data)
+{
+    m_pointCollector->append(*data);
+}
+
+
+void PlyWriter::done(PointTableRef table)
+{
+    if (!ply_add_element(m_ply, "vertex", m_pointCollector->size()))
+    {
+        std::stringstream ss;
+        ss << "Could not add vertex element";
+        throw pdal_error(ss.str());
+    }
+    auto dimensions = table.layout()->dims();
+    for (auto dim : dimensions) {
+        std::string name = Dimension::name(dim);
+        e_ply_type plyType = getPlyType(Dimension::defaultType(dim));
+        if (!ply_add_scalar_property(m_ply, name.c_str(), plyType))
+        {
+            std::stringstream ss;
+            ss << "Could not add scalar property '" << name << "'";
+            throw pdal_error(ss.str());
+        }
+    }
+    if (!ply_add_comment(m_ply, "Generated by PDAL"))
+    {
+        std::stringstream ss;
+        ss << "Could not add comment";
+        throw pdal_error(ss.str());
+    }
+    if (!ply_write_header(m_ply))
+    {
+        std::stringstream ss;
+        ss << "Could not write ply header";
+        throw pdal_error(ss.str());
+    }
+
+    for (PointId index = 0; index < m_pointCollector->size(); ++index)
+    {
+        for (auto dim : dimensions)
+        {
+            double value = m_pointCollector->getFieldAs<double>(dim, index);
+            if (!ply_write(m_ply, value))
+            {
+                std::stringstream ss;
+                ss << "Error writing dimension '" << Dimension::name(dim) <<
+                    "' of point number " << index;
+                throw pdal_error(ss.str());
+            }
+        }
+    }
+
+    if (!ply_close(m_ply))
+        throw pdal_error("Error closing ply file");
+
+    getMetadata().addList("filename", m_filename);
+}
+
+}
diff --git a/io/PlyWriter.hpp b/io/PlyWriter.hpp
new file mode 100644
index 0000000..f492510
--- /dev/null
+++ b/io/PlyWriter.hpp
@@ -0,0 +1,71 @@
+/******************************************************************************
+* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <rply/rply.h>
+
+#include <pdal/PointView.hpp>
+#include <pdal/Writer.hpp>
+#include <pdal/plugin.hpp>
+
+extern "C" int32_t PlyWriter_ExitFunc();
+extern "C" PF_ExitFunc PlyWriter_InitPlugin();
+
+namespace pdal
+{
+
+class PDAL_DLL PlyWriter : public Writer
+{
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    PlyWriter();
+
+private:
+    virtual void addArgs(ProgramArgs& args);
+    virtual void initialize();
+    virtual void ready(PointTableRef table);
+    virtual void write(const PointViewPtr data);
+    virtual void done(PointTableRef table);
+
+    std::string m_filename;
+    p_ply m_ply;
+    PointViewPtr m_pointCollector;
+    std::string m_storageModeSpec;
+    e_ply_storage_mode m_storageMode;
+
+};
+
+}
diff --git a/io/pts/PtsReader.cpp b/io/PtsReader.cpp
similarity index 100%
rename from io/pts/PtsReader.cpp
rename to io/PtsReader.cpp
diff --git a/io/pts/PtsReader.hpp b/io/PtsReader.hpp
similarity index 100%
rename from io/pts/PtsReader.hpp
rename to io/PtsReader.hpp
diff --git a/io/qfit/QfitReader.cpp b/io/QfitReader.cpp
similarity index 100%
rename from io/qfit/QfitReader.cpp
rename to io/QfitReader.cpp
diff --git a/io/qfit/QfitReader.hpp b/io/QfitReader.hpp
similarity index 100%
rename from io/qfit/QfitReader.hpp
rename to io/QfitReader.hpp
diff --git a/io/sbet/SbetCommon.cpp b/io/SbetCommon.cpp
similarity index 100%
rename from io/sbet/SbetCommon.cpp
rename to io/SbetCommon.cpp
diff --git a/io/sbet/SbetCommon.hpp b/io/SbetCommon.hpp
similarity index 100%
rename from io/sbet/SbetCommon.hpp
rename to io/SbetCommon.hpp
diff --git a/io/SbetReader.cpp b/io/SbetReader.cpp
new file mode 100644
index 0000000..371ccdd
--- /dev/null
+++ b/io/SbetReader.cpp
@@ -0,0 +1,120 @@
+/******************************************************************************
+* Copyright (c) 2014, Peter J. Gadomski (pete.gadomski at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/PointRef.hpp>
+
+#include "SbetReader.hpp"
+
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/FileUtils.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "readers.sbet",
+    "SBET Reader",
+    "http://pdal.io/stages/readers.sbet.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, SbetReader, Reader, s_info)
+
+std::string SbetReader::getName() const { return s_info.name; }
+
+void SbetReader::addDimensions(PointLayoutPtr layout)
+{
+    layout->registerDims(getDefaultDimensions());
+}
+
+
+void SbetReader::ready(PointTableRef)
+{
+    size_t fileSize = FileUtils::fileSize(m_filename);
+    size_t pointSize = getDefaultDimensions().size() * sizeof(double);
+    if (fileSize % pointSize != 0)
+        throw pdal_error("invalid sbet file size");
+    m_numPts = fileSize / pointSize;
+    m_index = 0;
+    m_stream.reset(new ILeStream(m_filename));
+    m_dims = fileDimensions();
+    seek(m_index);
+}
+
+
+bool SbetReader::processOne(PointRef& point)
+{
+    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
+    {
+        double d;
+        *m_stream >> d;
+        Dimension::Id dim = *di;
+        point.setField(dim, d);
+    }
+    return (m_stream->good());
+}
+
+
+point_count_t SbetReader::read(PointViewPtr view, point_count_t count)
+{
+    PointId nextId = view->size();
+    PointId idx = m_index;
+    point_count_t numRead = 0;
+    seek(idx);
+    while (numRead < count && idx < m_numPts)
+    {
+        PointRef point = view->point(nextId);
+        processOne(point);
+        if (m_cb)
+            m_cb(*view, nextId);
+
+        idx++;
+        nextId++;
+        numRead++;
+    }
+    m_index = idx;
+    return numRead;
+}
+
+
+bool SbetReader::eof()
+{
+    return m_index >= m_numPts;
+}
+
+
+void SbetReader::seek(PointId idx)
+{
+    m_stream->seek(idx * sizeof(double) * getDefaultDimensions().size());
+}
+
+} // namespace pdal
diff --git a/io/sbet/SbetReader.hpp b/io/SbetReader.hpp
similarity index 100%
rename from io/sbet/SbetReader.hpp
rename to io/SbetReader.hpp
diff --git a/io/SbetWriter.cpp b/io/SbetWriter.cpp
new file mode 100644
index 0000000..81f84a4
--- /dev/null
+++ b/io/SbetWriter.cpp
@@ -0,0 +1,86 @@
+/******************************************************************************
+* Copyright (c) 2014, Peter J. Gadomski (pete.gadomski at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "SbetWriter.hpp"
+
+#include <pdal/PointView.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "writers.sbet",
+    "SBET Writer",
+    "http://pdal.io/stages/writers.sbet.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, SbetWriter, Writer, s_info)
+
+std::string SbetWriter::getName() const { return s_info.name; }
+
+void SbetWriter::addArgs(ProgramArgs& args)
+{
+    args.add("filename", "Output filename", m_filename).setPositional();
+}
+
+
+void SbetWriter::ready(PointTableRef)
+{
+    m_stream.reset(new OLeStream(m_filename));
+}
+
+
+void SbetWriter::write(const PointViewPtr view)
+{
+    Dimension::IdList dims = getDefaultDimensions();
+    for (PointId idx = 0; idx < view->size(); ++idx)
+    {
+        for (auto di = dims.begin(); di != dims.end(); ++di)
+        {
+            // If a dimension doesn't exist, write 0.
+            Dimension::Id dim = *di;
+            *m_stream << (view->hasDim(dim) ?
+                view->getFieldAs<double>(dim, idx) : 0.0);
+        }
+    }
+}
+
+
+void SbetWriter::done(PointTableRef table)
+{
+    getMetadata().addList("filename", m_filename);
+}
+
+} // namespace pdal
diff --git a/io/SbetWriter.hpp b/io/SbetWriter.hpp
new file mode 100644
index 0000000..262209a
--- /dev/null
+++ b/io/SbetWriter.hpp
@@ -0,0 +1,69 @@
+/******************************************************************************
+* Copyright (c) 2014, Peter J. Gadomski (pete.gadomski at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/plugin.hpp>
+#include <pdal/util/OStream.hpp>
+#include <pdal/Writer.hpp>
+
+#include "SbetCommon.hpp"
+
+extern "C" int32_t SbetWriter_ExitFunc();
+extern "C" PF_ExitFunc SbetWriter_InitPlugin();
+
+namespace pdal
+{
+
+class PDAL_DLL SbetWriter : public Writer
+{
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    static Dimension::IdList getDefaultDimensions()
+        { return fileDimensions(); }
+
+private:
+    std::unique_ptr<OLeStream> m_stream;
+    std::string m_filename;
+
+    virtual void addArgs(ProgramArgs& args);
+    virtual void ready(PointTableRef table);
+    virtual void write(const PointViewPtr view);
+    virtual void done(PointTableRef table);
+};
+
+} // namespace pdal
diff --git a/io/tindex/TIndexReader.cpp b/io/TIndexReader.cpp
similarity index 100%
rename from io/tindex/TIndexReader.cpp
rename to io/TIndexReader.cpp
diff --git a/io/TIndexReader.hpp b/io/TIndexReader.hpp
new file mode 100644
index 0000000..09d88ec
--- /dev/null
+++ b/io/TIndexReader.hpp
@@ -0,0 +1,110 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/PointView.hpp>
+#include <pdal/Reader.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/GDALUtils.hpp>
+#include <pdal/plugin.hpp>
+#include <filters/MergeFilter.hpp>
+
+extern "C" int32_t TIndexReader_ExitFunc();
+extern "C" PF_ExitFunc TIndexReader_InitPlugin();
+
+namespace pdal
+{
+class PDAL_DLL TIndexReader : public pdal::Reader
+{
+    struct FileInfo
+    {
+        std::string m_filename;
+        std::string m_srs;
+        std::string m_boundary;
+        struct tm m_ctime;
+        struct tm m_mtime;
+    };
+
+    struct FieldIndexes
+    {
+        int m_filename;
+        int m_srs;
+        int m_ctime;
+        int m_mtime;
+    };
+
+public:
+    TIndexReader() : m_dataset(NULL) , m_layer(NULL)
+        {}
+
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+
+    static Dimension::IdList getDefaultDimensions();
+
+private:
+    virtual void addDimensions(PointLayoutPtr layout);
+    virtual void addArgs(ProgramArgs& args);
+    virtual void initialize();
+    virtual void ready(PointTableRef table);
+    virtual PointViewSet run(PointViewPtr view);
+
+    std::string m_layerName;
+    std::string m_driverName;
+    std::string m_tileIndexColumnName;
+    std::string m_srsColumnName;
+    std::string m_wkt;
+    std::string m_tgtSrsString;
+    std::string m_filterSRS;
+    std::string m_attributeFilter;
+    std::string m_dialect;
+    BOX2D m_bounds;
+    std::string m_sql;
+
+    std::unique_ptr<gdal::SpatialRef> m_out_ref;
+    void *m_dataset;
+    void *m_layer;
+
+    StageFactory m_factory;
+    MergeFilter m_merge;
+    PointViewSet m_pvSet;
+
+    std::vector<FileInfo> getFiles();
+    FieldIndexes getFields();
+};
+
+
+} // namespace pdal
diff --git a/io/terrasolid/TerrasolidReader.cpp b/io/TerrasolidReader.cpp
similarity index 100%
rename from io/terrasolid/TerrasolidReader.cpp
rename to io/TerrasolidReader.cpp
diff --git a/io/terrasolid/TerrasolidReader.hpp b/io/TerrasolidReader.hpp
similarity index 100%
rename from io/terrasolid/TerrasolidReader.hpp
rename to io/TerrasolidReader.hpp
diff --git a/io/TextReader.cpp b/io/TextReader.cpp
new file mode 100644
index 0000000..5f4a770
--- /dev/null
+++ b/io/TextReader.cpp
@@ -0,0 +1,185 @@
+/******************************************************************************
+* Copyright (c) 2016, Hobu Inc., info at hobu.co
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/PDALUtils.hpp>
+#include <pdal/util/Algorithm.hpp>
+
+#include "TextReader.hpp"
+
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/Algorithm.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "readers.text",
+    "Text Reader",
+    "http://pdal.io/stages/readers.text.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, TextReader, Reader, s_info)
+
+std::string TextReader::getName() const { return s_info.name; }
+
+void TextReader::initialize(PointTableRef table)
+{
+    m_istream = Utils::openFile(m_filename);
+    if (!m_istream)
+    {
+        std::ostringstream oss;
+        oss << getName() << ": Unable to open text file '" <<
+            m_filename << "'.";
+        throw pdal_error(oss.str());
+    }
+
+    std::string buf;
+    std::getline(*m_istream, buf);
+
+    auto isspecial = [](char c)
+        { return (!std::isalnum(c) && c != ' '); };
+
+    // Scan string for some character not a number, space or letter.
+    for (size_t i = 0; i < buf.size(); ++i)
+        if (isspecial(buf[i]))
+        {
+            m_separator = buf[i];
+            break;
+        }
+
+    if (m_separator != ' ')
+    {
+        Utils::remove(buf, ' ');
+        m_dimNames = Utils::split(buf, m_separator);
+    }
+    else
+        m_dimNames = Utils::split2(buf, m_separator);
+    Utils::closeFile(m_istream);
+}
+
+
+void TextReader::addDimensions(PointLayoutPtr layout)
+{
+    for (auto name : m_dimNames)
+    {
+        Dimension::Id id = layout->registerOrAssignDim(name,
+            Dimension::Type::Double);
+        if (Utils::contains(m_dims, id))
+        {
+            std::ostringstream oss;
+
+            oss << getName() << ": Duplicate dimension '" << name <<
+                "' detected in input file '" << m_filename << "'.";
+            throw pdal_error(oss.str());
+        }
+        m_dims.push_back(id);
+    }
+}
+
+
+void TextReader::ready(PointTableRef table)
+{
+    m_istream = Utils::openFile(m_filename);
+    if (!m_istream)
+    {
+        std::ostringstream oss;
+        oss << getName() << ": Unable to open text file '" <<
+            m_filename << "'.";
+        throw pdal_error(oss.str());
+    }
+
+    // Skip header line.
+    std::string buf;
+    std::getline(*m_istream, buf);
+}
+
+
+point_count_t TextReader::read(PointViewPtr view, point_count_t numPts)
+{
+    PointId idx = view->size();
+
+    point_count_t cnt = 0;
+    size_t line = 1;
+    while (m_istream->good() && cnt < numPts)
+    {
+        std::string buf;
+        StringList fields;
+
+        std::getline(*m_istream, buf);
+        line++;
+        if (buf.empty())
+            continue;
+        if (m_separator != ' ')
+        {
+            Utils::remove(buf, ' ');
+            fields = Utils::split(buf, m_separator);
+        }
+        else
+            fields = Utils::split2(buf, m_separator);
+        if (fields.size() != m_dims.size())
+        {
+            log()->get(LogLevel::Error) << "Line " << line <<
+               " in '" << m_filename << "' contains " << fields.size() <<
+               " fields when " << m_dims.size() << " were expected.  "
+               "Ignoring." << std::endl;
+            continue;
+        }
+
+        double d;
+        for (size_t i = 0; i < fields.size(); ++i)
+        {
+            if (!Utils::fromString(fields[i], d))
+            {
+                log()->get(LogLevel::Error) << "Can't convert "
+                    "field '" << fields[i] << "' to numeric value on line " <<
+                    line << " in '" << m_filename << "'.  Setting to 0." <<
+                    std::endl;
+                d = 0;
+            }
+            view->setField(m_dims[i], idx, d);
+        }
+        cnt++;
+        idx++;
+    }
+    return cnt;
+}
+
+
+void TextReader::done(PointTableRef table)
+{
+    Utils::closeFile(m_istream);
+}
+
+
+} // namespace pdal
+
diff --git a/io/text/TextReader.hpp b/io/TextReader.hpp
similarity index 100%
rename from io/text/TextReader.hpp
rename to io/TextReader.hpp
diff --git a/io/TextWriter.cpp b/io/TextWriter.cpp
new file mode 100644
index 0000000..7be7d5e
--- /dev/null
+++ b/io/TextWriter.cpp
@@ -0,0 +1,251 @@
+/******************************************************************************
+* Copyright (c) 2011, Howard Butler, hobu.inc at gmail.com
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "TextWriter.hpp"
+
+#include <pdal/pdal_export.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/util/Algorithm.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <iostream>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo(
+    "writers.text",
+    "Text Writer",
+    "http://pdal.io/stages/writers.text.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, TextWriter, Writer, s_info)
+
+std::string TextWriter::getName() const { return s_info.name; }
+
+struct FileStreamDeleter
+{
+
+    template <typename T>
+    void operator()(T* ptr)
+    {
+        if (ptr)
+        {
+            ptr->flush();
+            Utils::closeFile(ptr);
+        }
+    }
+};
+
+
+void TextWriter::addArgs(ProgramArgs& args)
+{
+    args.add("filename", "Output filename", m_filename);
+    args.add("format", "Output format", m_outputType, "csv");
+    args.add("jscallback", "", m_callback);
+    args.add("keep_unspecified", "Write all dimensions", m_writeAllDims, true);
+    args.add("order", "Dimension order", m_dimOrder);
+    args.add("write_header", "Whether a header should be written",
+        m_writeHeader, true);
+    args.add("newline", "String to use as newline", m_newline, "\n");
+    args.add("delimiter", "Dimension delimiter", m_delimiter, ",");
+    args.add("quote_header", "Whether a header should be quoted",
+        m_quoteHeader, true);
+    args.add("precision", "Output precision", m_precision, 3);
+}
+
+
+void TextWriter::initialize(PointTableRef table)
+{
+    m_stream = FileStreamPtr(Utils::createFile(m_filename, true),
+        FileStreamDeleter());
+    if (!m_stream)
+    {
+        std::stringstream out;
+        out << "writers.text couldn't open '" << m_filename <<
+            "' for output.";
+        throw pdal_error(out.str());
+    }
+    m_outputType = Utils::toupper(m_outputType);
+}
+
+
+void TextWriter::ready(PointTableRef table)
+{
+    m_stream->precision(m_precision);
+    *m_stream << std::fixed;
+
+    // Find the dimensions listed and put them on the id list.
+    StringList dimNames = Utils::split2(m_dimOrder, ',');
+    for (std::string dim : dimNames)
+    {
+        Utils::trim(dim);
+        Dimension::Id d = table.layout()->findDim(dim);
+        if (d == Dimension::Id::Unknown)
+        {
+            std::ostringstream oss;
+            oss << getName() << ": Dimension not found with name '" <<
+                dim << "'.";
+            throw pdal_error(oss.str());
+        }
+        m_dims.push_back(d);
+    }
+
+    // Add the rest of the dimensions to the list if we're doing that.
+    // Yes, this isn't efficient when, but it's simple.
+    if (m_dimOrder.empty() || m_writeAllDims)
+    {
+        Dimension::IdList all = table.layout()->dims();
+        for (auto di = all.begin(); di != all.end(); ++di)
+            if (!Utils::contains(m_dims, *di))
+                m_dims.push_back(*di);
+    }
+
+    if (!m_writeHeader)
+        log()->get(LogLevel::Debug) << "Not writing header" << std::endl;
+    else
+        writeHeader(table);
+}
+
+
+void TextWriter::writeHeader(PointTableRef table)
+{
+    log()->get(LogLevel::Debug) << "Writing header to filename: " <<
+        m_filename << std::endl;
+    if (m_outputType == "GEOJSON")
+        writeGeoJSONHeader();
+    else if (m_outputType == "CSV")
+        writeCSVHeader(table);
+}
+
+
+void TextWriter::writeFooter()
+{
+    if (m_outputType == "GEOJSON")
+    {
+        *m_stream << "]}";
+        if (m_callback.size())
+            *m_stream  <<")";
+    }
+    m_stream.reset();
+}
+
+
+void TextWriter::writeGeoJSONHeader()
+{
+    if (m_callback.size())
+        *m_stream << m_callback <<"(";
+    *m_stream << "{ \"type\": \"FeatureCollection\", \"features\": [";
+}
+
+
+void TextWriter::writeCSVHeader(PointTableRef table)
+{
+    const PointLayoutPtr layout(table.layout());
+    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
+    {
+        if (di != m_dims.begin())
+            *m_stream << m_delimiter;
+
+        if (m_quoteHeader)
+            *m_stream << "\"" << layout->dimName(*di) << "\"";
+        else
+            *m_stream << layout->dimName(*di);
+    }
+    *m_stream << m_newline;
+}
+
+void TextWriter::writeCSVBuffer(const PointViewPtr view)
+{
+    for (PointId idx = 0; idx < view->size(); ++idx)
+    {
+        for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
+        {
+            if (di != m_dims.begin())
+                *m_stream << m_delimiter;
+            *m_stream << view->getFieldAs<double>(*di, idx);
+        }
+        *m_stream << m_newline;
+    }
+}
+
+void TextWriter::writeGeoJSONBuffer(const PointViewPtr view)
+{
+    using namespace Dimension;
+
+    for (PointId idx = 0; idx < view->size(); ++idx)
+    {
+        if (idx)
+            *m_stream << ",";
+
+        *m_stream << "{ \"type\":\"Feature\",\"geometry\": "
+            "{ \"type\": \"Point\", \"coordinates\": [";
+        *m_stream << view->getFieldAs<double>(Id::X, idx) << ",";
+        *m_stream << view->getFieldAs<double>(Id::Y, idx) << ",";
+        *m_stream << view->getFieldAs<double>(Id::Z, idx) << "]},";
+
+        *m_stream << "\"properties\": {";
+
+        for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
+        {
+            if (di != m_dims.begin())
+                *m_stream << ",";
+
+            *m_stream << "\"" << view->dimName(*di) << "\":";
+            *m_stream << "\"";
+            *m_stream << view->getFieldAs<double>(*di, idx);
+            *m_stream <<"\"";
+        }
+        *m_stream << "}"; // end properties
+        *m_stream << "}"; // end feature
+    }
+}
+
+void TextWriter::write(const PointViewPtr view)
+{
+    if (m_outputType == "CSV")
+        writeCSVBuffer(view);
+    else if (m_outputType == "GEOJSON")
+        writeGeoJSONBuffer(view);
+}
+
+
+void TextWriter::done(PointTableRef /*table*/)
+{
+    writeFooter();
+    getMetadata().addList("filename", m_filename);
+}
+
+} // namespace pdal
diff --git a/io/text/TextWriter.hpp b/io/TextWriter.hpp
similarity index 100%
rename from io/text/TextWriter.hpp
rename to io/TextWriter.hpp
diff --git a/io/bpf/BpfHeader.hpp b/io/bpf/BpfHeader.hpp
deleted file mode 100644
index 6f7b2be..0000000
--- a/io/bpf/BpfHeader.hpp
+++ /dev/null
@@ -1,261 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Howard Butler, hobu.inc at gmail.com
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <stdint.h>
-#include <string>
-#include <vector>
-
-#include <pdal/Dimension.hpp>
-#include <pdal/Log.hpp>
-#include <pdal/Metadata.hpp>
-
-namespace pdal
-{
-
-class ILeStream;
-class OLeStream;
-
-struct BpfMuellerMatrix
-{
-    BpfMuellerMatrix()
-    {
-        static const double vals[] = {1.0, 0.0, 0.0, 0.0,
-                         0.0, 1.0, 0.0, 0.0,
-                         0.0, 0.0, 1.0, 0.0,
-                         0.0, 0.0, 0.0, 1.0};
-        memcpy(m_vals, vals, sizeof(vals));
-    }
-
-    double m_vals[16];
-
-    void dump()
-    {
-        for (size_t i = 0; i < 4; ++i)
-            std::cerr << m_vals[i] << '\t';
-        std::cerr << "\n";
-        for (size_t i = 4; i < 8; ++i)
-            std::cerr << m_vals[i] << '\t';
-        std::cerr << "\n";
-        for (size_t i = 8; i < 12; ++i)
-            std::cerr << m_vals[i] << '\t';
-        std::cerr << "\n";
-        for (size_t i = 12; i < 16; ++i)
-            std::cerr << m_vals[i] << '\t';
-        std::cerr << "\n\n";
-
-    }
-
-    void apply(double& x, double& y, double& z)
-    {
-        double w = x * m_vals[12] + y * m_vals[13] + z * m_vals[14] +
-            m_vals[15];
-
-        x = (x * m_vals[0] + y * m_vals[1] + z * m_vals[2] + m_vals[3]) / w;
-        y = (x * m_vals[4] + y * m_vals[5] + z * m_vals[6] + m_vals[7]) / w;
-        z = (x * m_vals[8] + y * m_vals[9] + z * m_vals[10] + m_vals[11]) / w;
-    }
-};
-ILeStream& operator >> (ILeStream& stream, BpfMuellerMatrix& m);
-OLeStream& operator << (OLeStream& stream, BpfMuellerMatrix& m);
-
-enum class BpfFormat
-{
-    DimMajor,
-    PointMajor,
-    ByteMajor
-};
-
-std::istream& operator >> (std::istream& in, BpfFormat& format);
-std::ostream& operator << (std::ostream& in, const BpfFormat& format);
-
-enum class BpfCoordType
-{
-    None,
-    UTM,
-    TCR,
-    ENU
-};
-
-enum class BpfCompression
-{
-    None,
-    QuickLZ,
-    FastLZ,
-    Zlib
-};
-
-struct BpfDimension
-{
-    BpfDimension() : m_offset(0.0),
-        m_min((std::numeric_limits<double>::max)()),
-        m_max(std::numeric_limits<double>::lowest()),
-        m_id(Dimension::Id::Unknown)
-    {}
-
-    double m_offset;
-    double m_min;
-    double m_max;
-    std::string m_label;
-    Dimension::Id m_id;
-
-    static bool read(ILeStream& stream, std::vector<BpfDimension>& dims,
-        size_t start);
-    static bool write(OLeStream& stream, std::vector<BpfDimension>& dims);
-};
-typedef std::vector<BpfDimension> BpfDimensionList;
-
-struct BpfHeader
-{
-    BpfHeader() : m_version(0), m_len(176), m_numDim(0),
-        m_compression(Utils::toNative(BpfCompression::None)), m_numPts(0),
-        m_coordType(Utils::toNative(BpfCoordType::None)), m_coordId(0),
-        m_spacing(0.0), m_startTime(0.0), m_endTime(0.0)
-    {}
-
-    int32_t m_version;
-    std::string m_ver;
-    int32_t m_len;
-    int32_t m_numDim;
-    BpfFormat m_pointFormat;
-    uint8_t m_compression;
-    int32_t m_numPts;
-    int32_t m_coordType;
-    int32_t m_coordId;
-    float m_spacing;
-    BpfMuellerMatrix m_xform;
-    double m_startTime;
-    double m_endTime;
-    std::vector<BpfDimension> m_staticDims;
-    LogPtr m_log;
-
-    PDAL_DLL void setLog(const LogPtr& log)
-         { m_log = log; }
-    PDAL_DLL bool read(ILeStream& stream);
-    bool write(OLeStream& stream);
-    bool readV3(ILeStream& stream);
-    bool readV1(ILeStream& stream);
-    PDAL_DLL bool readDimensions(ILeStream& stream,
-        std::vector<BpfDimension>& dims);
-    void writeDimensions(OLeStream& stream, std::vector<BpfDimension>& dims);
-    void dump();
-};
-
-struct BpfUlemHeader
-{
-    uint32_t m_numFrames;
-    uint16_t m_year;
-    uint8_t m_month;
-    uint8_t m_day;
-    uint16_t m_lidarMode;
-    uint16_t m_wavelen;  // In nm.
-    uint16_t m_pulseFreq;  // In Hz.
-    uint16_t m_focalWidth;
-    uint16_t m_focalHeight;
-    float m_pixelPitchWidth;
-    float m_pixelPitchHeight;
-    std::string m_classCode;
-
-    bool read(ILeStream& stream);
-};
-
-struct BpfUlemFrame
-{
-    int32_t m_num;
-    double m_roll; //x
-    double m_pitch; //y
-    double m_heading; //z
-    BpfMuellerMatrix m_xform;
-    int16_t m_shortEncoder;
-    int16_t m_longEncoder;
-
-    bool read(ILeStream& stream);
-};
-
-struct BpfUlemFile
-{
-    uint32_t m_len;
-    std::string m_filename;
-    std::vector<char> m_buf;
-    std::string m_filespec;
-
-    BpfUlemFile() : m_len(0)
-    {}
-
-    BpfUlemFile(uint32_t len, const std::string& filename,
-            const std::string& filespec) :
-        m_len(len), m_filename(filename), m_filespec(filespec)
-    {}
-
-    bool read(ILeStream& stream);
-    bool write(OLeStream& stream);
-};
-
-struct BpfPolarStokesParam
-{
-    float m_x;
-    float m_y;
-    float m_z;
-    float m_a;
-
-    bool read(ILeStream& stream);
-};
-
-struct BpfPolarHeader
-{
-    uint32_t m_numFrames;
-    uint16_t m_fpaId;
-    uint32_t m_numXmit;
-    uint32_t m_numRcv;
-    std::vector<BpfPolarStokesParam> m_xmitStates;
-    std::vector<BpfMuellerMatrix> m_psaSettings;
-
-    bool read(ILeStream& stream);
-};
-
-struct BpfPolarFrame
-{
-public:
-    uint32_t m_num;
-    int16_t m_stokesIdx;
-    float m_stokesParam[4];
-    float m_stokesOutParam[4];
-    BpfMuellerMatrix m_xform;
-    int16_t m_truncation;
-
-    bool read(ILeStream& stream);
-};
-
-} //namespace pdal
diff --git a/io/bpf/BpfReader.cpp b/io/bpf/BpfReader.cpp
deleted file mode 100644
index 588693a..0000000
--- a/io/bpf/BpfReader.cpp
+++ /dev/null
@@ -1,602 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Andrew Bell
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "BpfReader.hpp"
-
-#include <climits>
-
-#include <zlib.h>
-
-#include <pdal/Options.hpp>
-#include <pdal/pdal_export.hpp>
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "readers.bpf",
-    "\"Binary Point Format\" (BPF) reader support. BPF is a simple \n" \
-        "DoD and research format that is used by some sensor and \n" \
-        "processing chains.",
-    "http://pdal.io/stages/readers.bpf.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, BpfReader, Reader, s_info)
-
-std::string BpfReader::getName() const { return s_info.name; }
-
-QuickInfo BpfReader::inspect()
-{
-    QuickInfo qi;
-
-    initialize();
-    qi.m_valid = true;
-    qi.m_pointCount = m_header.m_numPts;
-    qi.m_srs = getSpatialReference();
-    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
-    {
-        BpfDimension& dim = *di;
-        qi.m_dimNames.push_back(dim.m_label);
-        if (dim.m_label == "X")
-        {
-            qi.m_bounds.minx = dim.m_min;
-            qi.m_bounds.maxx = dim.m_max;
-        }
-        if (dim.m_label == "Y")
-        {
-            qi.m_bounds.miny = dim.m_min;
-            qi.m_bounds.maxy = dim.m_max;
-        }
-        if (dim.m_label == "Z")
-        {
-            qi.m_bounds.minz = dim.m_min;
-            qi.m_bounds.maxz = dim.m_max;
-        }
-    }
-    return qi;
-}
-
-
-// When the stage is intialized, the schema needs to be populated with the
-// dimensions in order to allow subsequent stages to be aware of or append to
-// the dimensions in the PointView.
-void BpfReader::initialize()
-{
-    if (m_filename.empty())
-        throw pdal_error("Can't read BPF file without filename.");
-
-    // Logfile doesn't get set until options are processed.
-    m_header.setLog(log());
-
-    m_stream.open(m_filename);
-
-    // Resets the stream position in case it was already open.
-    m_stream.seek(0);
-    // In order to know the dimensions we must read the file header.
-    if (!m_header.read(m_stream))
-        return;
-
-    if (!m_header.readDimensions(m_stream, m_dims))
-        return;
-
-    uint32_t zone(abs(m_header.m_coordId));
-    std::string code;
-    if (m_header.m_coordId > 0)
-        code = "EPSG:326" + Utils::toString(zone);
-    else
-        code = "EPSG:327" + Utils::toString(zone);
-    SpatialReference srs(code);
-    setSpatialReference(srs);
-
-    if (m_header.m_version >= 3)
-    {
-        readUlemData();
-        if (!m_stream)
-            return;
-        readUlemFiles();
-        if (!m_stream)
-            return;
-        readPolarData();
-    }
-
-    // Read thing after the standard header as metadata->
-    readHeaderExtraData();
-
-    // Fast forward file to end of header as reported by base header.
-    std::streampos pos = m_stream.position();
-    if (pos > m_header.m_len)
-    {
-        std::ostringstream oss;
-        oss << getName() << ": BPF Header length exceeded that reported by "
-            "file.";
-        throw pdal_error(oss.str());
-    }
-    m_stream.close();
-}
-
-
-void BpfReader::addDimensions(PointLayoutPtr layout)
-{
-    for (size_t i = 0; i < m_dims.size(); ++i)
-    {
-        Dimension::Type type = Dimension::Type::Float;
-
-        BpfDimension& dim = m_dims[i];
-        if (dim.m_label == "X" ||
-            dim.m_label == "Y" ||
-            dim.m_label == "Z")
-            type = Dimension::Type::Double;
-        dim.m_id = layout->registerOrAssignDim(dim.m_label, type);
-    }
-}
-
-
-bool BpfReader::readUlemData()
-{
-    if (!m_ulemHeader.read(m_stream))
-        return false;
-
-    for (size_t i = 0; i < m_ulemHeader.m_numFrames; i++)
-    {
-        BpfUlemFrame frame;
-        if (!frame.read(m_stream))
-            return false;
-        m_ulemFrames.push_back(frame);
-    }
-    return (bool)m_stream;
-}
-
-
-bool BpfReader::readUlemFiles()
-{
-    BpfUlemFile file;
-    while (file.read(m_stream))
-    {
-        MetadataNode m = m_metadata.add("bundled_file");
-        m.addEncoded(file.m_filename,
-            (const unsigned char *)file.m_buf.data(), file.m_len);
-    }
-    return (bool)m_stream;
-}
-
-
-/// Encode all data that follows the headers as metadata->
-/// \return  Whether the stream is still valid.
-bool BpfReader::readHeaderExtraData()
-{
-    if (m_stream.position() < m_header.m_len)
-    {
-        std::streampos size = m_header.m_len - m_stream.position();
-        std::vector<uint8_t> buf(size);
-        m_stream.get(buf);
-        m_metadata.addEncoded("header_data", buf.data(), buf.size());
-    }
-    return (bool)m_stream;
-}
-
-
-bool BpfReader::readPolarData()
-{
-    if (!m_polarHeader.read(m_stream))
-        return false;
-    for (size_t i = 0; i < m_polarHeader.m_numFrames; ++i)
-    {
-        BpfPolarFrame frame;
-        if (!frame.read(m_stream))
-            return false;
-        m_polarFrames.push_back(frame);
-    }
-    return (bool)m_stream;
-}
-
-
-void BpfReader::ready(PointTableRef)
-{
-    m_stream.open(m_filename);
-    m_stream.seek(m_header.m_len);
-    m_index = 0;
-    m_start = m_stream.position();
-    if (m_header.m_compression)
-    {
-        m_deflateBuf.resize(numPoints() * m_dims.size() * sizeof(float));
-        size_t index = 0;
-        size_t bytesRead = 0;
-        do
-        {
-            bytesRead = readBlock(m_deflateBuf, index);
-            index += bytesRead;
-        } while (bytesRead > 0 && index < m_deflateBuf.size());
-        m_charbuf.initialize(m_deflateBuf.data(), m_deflateBuf.size(), m_start);
-        m_stream.pushStream(new std::istream(&m_charbuf));
-    }
-}
-
-
-void BpfReader::done(PointTableRef)
-{
-     delete m_stream.popStream();
-     m_stream.close();
-}
-
-
-bool BpfReader::processOne(PointRef& point)
-{
-    switch (m_header.m_pointFormat)
-    {
-    case BpfFormat::PointMajor:
-        readPointMajor(point);
-        break;
-    case BpfFormat::DimMajor:
-        readDimMajor(point);
-        break;
-    case BpfFormat::ByteMajor:
-        readByteMajor(point);
-        break;
-    }
-    return !eof() && (m_index < m_count);
-}
-
-
-point_count_t BpfReader::read(PointViewPtr data, point_count_t count)
-{
-    switch (m_header.m_pointFormat)
-    {
-    case BpfFormat::PointMajor:
-        return readPointMajor(data, count);
-    case BpfFormat::DimMajor:
-        return readDimMajor(data, count);
-    case BpfFormat::ByteMajor:
-        return readByteMajor(data, count);
-    }
-    return 0;
-}
-
-
-size_t BpfReader::readBlock(std::vector<char>& outBuf, size_t index)
-{
-    uint32_t finalBytes;
-    uint32_t compressBytes;
-
-    m_stream >> finalBytes;
-    m_stream >> compressBytes;
-
-    std::vector<char> in(compressBytes);
-
-    // Fill the input bytes from the stream.
-    m_stream.get(in);
-    int ret = inflate(in.data(), compressBytes,
-        outBuf.data() + index, finalBytes);
-    return (ret ? 0 : finalBytes);
-}
-
-
-bool BpfReader::eof()
-{
-    return m_index >= numPoints();
-}
-
-
-void BpfReader::readPointMajor(PointRef& point)
-{
-    double x(0), y(0), z(0);
-
-    seekPointMajor(m_index);
-    for (size_t dim = 0; dim < m_dims.size(); ++dim)
-    {
-        float f;
-
-        m_stream >> f;
-        double d = f + m_dims[dim].m_offset;
-        if (m_dims[dim].m_id == Dimension::Id::X)
-            x = d;
-        else if (m_dims[dim].m_id == Dimension::Id::Y)
-            y = d;
-        else if (m_dims[dim].m_id == Dimension::Id::Z)
-            z = d;
-        else
-            point.setField(m_dims[dim].m_id, d);
-    }
-
-    m_header.m_xform.apply(x, y, z);
-    point.setField(Dimension::Id::X, x);
-    point.setField(Dimension::Id::Y, y);
-    point.setField(Dimension::Id::Z, z);
-    m_index++;
-}
-
-
-point_count_t BpfReader::readPointMajor(PointViewPtr view, point_count_t count)
-{
-    PointId nextId = view->size();
-    PointId idx = m_index;
-    point_count_t numRead = 0;
-    seekPointMajor(idx);
-    while (numRead < count && idx < numPoints())
-    {
-        for (size_t d = 0; d < m_dims.size(); ++d)
-        {
-            float f;
-
-            m_stream >> f;
-            view->setField(m_dims[d].m_id, nextId, f + m_dims[d].m_offset);
-        }
-
-        // Transformation only applies to X, Y and Z
-        double x = view->getFieldAs<double>(Dimension::Id::X, nextId);
-        double y = view->getFieldAs<double>(Dimension::Id::Y, nextId);
-        double z = view->getFieldAs<double>(Dimension::Id::Z, nextId);
-        m_header.m_xform.apply(x, y, z);
-        view->setField(Dimension::Id::X, nextId, x);
-        view->setField(Dimension::Id::Y, nextId, y);
-        view->setField(Dimension::Id::Z, nextId, z);
-        if (m_cb)
-            m_cb(*view, nextId);
-
-        idx++;
-        numRead++;
-        nextId++;
-    }
-    m_index = idx;
-    return numRead;
-}
-
-
-// This isn't lovely as we have to seek for each dimension access.
-void BpfReader::readDimMajor(PointRef& point)
-{
-    double x(0), y(0), z(0);
-
-    for (size_t dim = 0; dim < m_dims.size(); ++dim)
-    {
-        seekDimMajor(dim, m_index);
-
-        float f;
-        m_stream >> f;
-        double d = f + m_dims[dim].m_offset;
-        if (m_dims[dim].m_id == Dimension::Id::X)
-            x = d;
-        else if (m_dims[dim].m_id == Dimension::Id::Y)
-            y = d;
-        else if (m_dims[dim].m_id == Dimension::Id::Z)
-            z = d;
-        else
-            point.setField(m_dims[dim].m_id, d);
-    }
-
-    // Transformation only applies to X, Y and Z
-    m_header.m_xform.apply(x, y, z);
-    point.setField(Dimension::Id::X, x);
-    point.setField(Dimension::Id::Y, y);
-    point.setField(Dimension::Id::Z, z);
-    m_index++;
-}
-
-
-point_count_t BpfReader::readDimMajor(PointViewPtr data, point_count_t count)
-{
-    PointId idx(0);
-    PointId startId = data->size();
-    point_count_t numRead = 0;
-    for (size_t d = 0; d < m_dims.size(); ++d)
-    {
-        idx = m_index;
-        PointId nextId = startId;
-        numRead = 0;
-        seekDimMajor(d, idx);
-        for (; numRead < count && idx < numPoints(); idx++, numRead++, nextId++)
-        {
-            float f;
-
-            m_stream >> f;
-            data->setField(m_dims[d].m_id, nextId, f + m_dims[d].m_offset);
-        }
-    }
-    m_index = idx;
-
-    // Transformation only applies to X, Y and Z
-    for (PointId idx = startId; idx < data->size(); idx++)
-    {
-        double x = data->getFieldAs<double>(Dimension::Id::X, idx);
-        double y = data->getFieldAs<double>(Dimension::Id::Y, idx);
-        double z = data->getFieldAs<double>(Dimension::Id::Z, idx);
-        m_header.m_xform.apply(x, y, z);
-        data->setField(Dimension::Id::X, idx, x);
-        data->setField(Dimension::Id::Y, idx, y);
-        data->setField(Dimension::Id::Z, idx, z);
-
-        if (m_cb)
-            m_cb(*data, idx);
-    }
-
-    return numRead;
-}
-
-
-void BpfReader::readByteMajor(PointRef& point)
-{
-    // We need a temp buffer for the point data
-    union uu
-    {
-        float f;
-        uint32_t u32;
-    } u;
-    double x(0), y(0), z(0);
-    uint8_t u8;
-
-    for (size_t dim = 0; dim < m_dims.size(); ++dim)
-    {
-        u.u32 = 0;
-        for (size_t b = 0; b < sizeof(float); ++b)
-        {
-            seekByteMajor(dim, b, m_index);
-
-            m_stream >> u8;
-            u.u32 |= ((uint32_t)u8 << (b * CHAR_BIT));
-        }
-        double d = u.f + m_dims[dim].m_offset;
-        if (m_dims[dim].m_id == Dimension::Id::X)
-            x = d;
-        else if (m_dims[dim].m_id == Dimension::Id::Y)
-            y = d;
-        else if (m_dims[dim].m_id == Dimension::Id::Z)
-            z = d;
-        else
-            point.setField(m_dims[dim].m_id, d);
-    }
-
-    m_header.m_xform.apply(x, y, z);
-    point.setField(Dimension::Id::X, x);
-    point.setField(Dimension::Id::Y, y);
-    point.setField(Dimension::Id::Z, z);
-    m_index++;
-}
-
-
-point_count_t BpfReader::readByteMajor(PointViewPtr data, point_count_t count)
-{
-    PointId idx(0);
-    PointId startId = data->size();
-    point_count_t numRead = 0;
-
-    // We need a temp buffer for the point data
-    union uu
-    {
-        float f;
-        uint32_t u32;
-    };
-    std::unique_ptr<union uu> uArr(
-        new uu[std::min(count, numPoints() - m_index)]);
-
-    for (size_t d = 0; d < m_dims.size(); ++d)
-    {
-        for (size_t b = 0; b < sizeof(float); ++b)
-        {
-            idx = m_index;
-            numRead = 0;
-            PointId nextId = startId;
-            seekByteMajor(d, b, idx);
-
-            for (;numRead < count && idx < numPoints();
-                idx++, numRead++, nextId++)
-            {
-                union uu& u = *(uArr.get() + numRead);
-
-                if (b == 0)
-                    u.u32 = 0;
-                uint8_t u8;
-                m_stream >> u8;
-                u.u32 |= ((uint32_t)u8 << (b * CHAR_BIT));
-                if (b == 3)
-                {
-                    u.f += m_dims[d].m_offset;
-                    data->setField(m_dims[d].m_id, nextId, u.f);
-                }
-            }
-        }
-    }
-    m_index = idx;
-
-    // Transformation only applies to X, Y and Z
-    for (PointId idx = startId; idx < data->size(); idx++)
-    {
-        double x = data->getFieldAs<double>(Dimension::Id::X, idx);
-        double y = data->getFieldAs<double>(Dimension::Id::Y, idx);
-        double z = data->getFieldAs<double>(Dimension::Id::Z, idx);
-        m_header.m_xform.apply(x, y, z);
-        data->setField(Dimension::Id::X, idx, x);
-        data->setField(Dimension::Id::Y, idx, y);
-        data->setField(Dimension::Id::Z, idx, z);
-
-        if (m_cb)
-            m_cb(*data, idx);
-    }
-
-    return numRead;
-}
-
-
-void BpfReader::seekPointMajor(PointId ptIdx)
-{
-    std::streamoff offset = ptIdx * sizeof(float) * m_dims.size();
-    m_stream.seek(m_start + offset);
-}
-
-
-void BpfReader::seekDimMajor(size_t dimIdx, PointId ptIdx)
-{
-    std::streamoff offset = ((sizeof(float) * dimIdx * numPoints()) +
-        (sizeof(float) * ptIdx));
-    m_stream.seek(m_start + offset);
-}
-
-
-void BpfReader::seekByteMajor(size_t dimIdx, size_t byteIdx, PointId ptIdx)
-{
-    std::streamoff offset =
-        (dimIdx * numPoints() * sizeof(float)) +
-        (byteIdx * numPoints()) +
-        ptIdx;
-    m_stream.seek(m_start + offset);
-}
-
-
-int BpfReader::inflate(char *buf, uint32_t insize,
-    char *outbuf, uint32_t outsize)
-{
-   if (insize == 0)
-        return 0;
-
-    int ret;
-    z_stream strm;
-
-    /* allocate inflate state */
-    strm.zalloc = Z_NULL;
-    strm.zfree = Z_NULL;
-    strm.opaque = Z_NULL;
-    strm.avail_in = 0;
-    strm.next_in = Z_NULL;
-    if (inflateInit(&strm) != Z_OK)
-        return -2;
-
-    strm.avail_in = insize;
-    strm.next_in = (unsigned char *)buf;
-    strm.avail_out = outsize;
-    strm.next_out = (unsigned char *)outbuf;
-
-    ret = ::inflate(&strm, Z_NO_FLUSH);
-    (void)inflateEnd(&strm);
-    return ret == Z_STREAM_END ? 0 : -1;
-}
-
-} //namespace pdal
diff --git a/io/bpf/BpfReader.hpp b/io/bpf/BpfReader.hpp
deleted file mode 100644
index a27cfbc..0000000
--- a/io/bpf/BpfReader.hpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Andrew Bell
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-// BPF is an NGA specification for point cloud data. The specification can be
-// found at https://nsgreg.nga.mil/doc/view?i=4202
-
-#pragma once
-
-#include <vector>
-
-#include <pdal/Reader.hpp>
-#include <pdal/util/Charbuf.hpp>
-#include <pdal/util/IStream.hpp>
-#include <pdal/pdal_export.hpp>
-#include <pdal/plugin.hpp>
-
-#include "BpfHeader.hpp"
-
-#include <vector>
-
-extern "C" int32_t BpfReader_ExitFunc();
-extern "C" PF_ExitFunc BpfReader_InitPlugin();
-
-namespace pdal
-{
-
-class PDAL_DLL BpfReader : public Reader
-{
-public:
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-    virtual point_count_t numPoints() const
-        {  return (point_count_t)m_header.m_numPts; }
-private:
-    ILeStream m_stream;
-    BpfHeader m_header;
-    BpfDimensionList m_dims;
-    Dimension::IdList m_schemaDims;
-    BpfUlemHeader m_ulemHeader;
-    std::vector<BpfUlemFrame> m_ulemFrames;
-    BpfPolarHeader m_polarHeader;
-    std::vector<BpfPolarFrame> m_polarFrames;
-    /// Stream position at the beginning of point records.
-    std::streampos m_start;
-    /// Index of the next point to read.
-    point_count_t m_index;
-    /// Buffer for deflated data.
-    std::vector<char> m_deflateBuf;
-    /// Streambuf for deflated data.
-    Charbuf m_charbuf;
-
-    virtual QuickInfo inspect();
-    virtual void initialize();
-    virtual void addDimensions(PointLayoutPtr Layout);
-    virtual void ready(PointTableRef table);
-    virtual bool processOne(PointRef& point);
-    virtual point_count_t read(PointViewPtr data, point_count_t num);
-    virtual void done(PointTableRef table);
-
-    bool readUlemData();
-    bool readUlemFiles();
-    bool readHeaderExtraData();
-    bool readPolarData();
-    void readPointMajor(PointRef& point);
-    point_count_t readPointMajor(PointViewPtr data, point_count_t count);
-    void readDimMajor(PointRef& point);
-    point_count_t readDimMajor(PointViewPtr data, point_count_t count);
-    void readByteMajor(PointRef& point);
-    point_count_t readByteMajor(PointViewPtr data, point_count_t count);
-    size_t readBlock(std::vector<char>& outBuf, size_t index);
-    bool eof();
-    int inflate(char *inbuf, uint32_t insize, char *outbuf, uint32_t outsize);
-
-    void seekPointMajor(PointId ptIdx);
-    void seekDimMajor(size_t dimIdx, PointId ptIdx);
-    void seekByteMajor(size_t dimIdx, size_t byteIdx, PointId ptIdx);
-};
-
-} // namespace pdal
diff --git a/io/bpf/BpfWriter.cpp b/io/bpf/BpfWriter.cpp
deleted file mode 100644
index f7e4506..0000000
--- a/io/bpf/BpfWriter.cpp
+++ /dev/null
@@ -1,356 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Hobu Inc., hobu at hobu.co
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "BpfWriter.hpp"
-
-#include <climits>
-
-#include <pdal/Options.hpp>
-#include <pdal/pdal_export.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <zlib.h>
-
-#include "BpfCompressor.hpp"
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/Utils.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "writers.bpf",
-    "\"Binary Point Format\" (BPF) writer support. BPF is a simple \n" \
-        "DoD and research format that is used by some sensor and \n" \
-        "processing chains.",
-    "http://pdal.io/stages/writers.bpf.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, BpfWriter, Writer, s_info)
-
-std::string BpfWriter::getName() const { return s_info.name; }
-
-void BpfWriter::addArgs(ProgramArgs& args)
-{
-    args.add("filename", "Output filename", m_filename).setPositional();
-    args.add("compression", "Output compression", m_compression);
-    args.add("header_data", "Base64-encoded header data", m_extraDataSpec);
-    args.add("format", "Output format", m_header.m_pointFormat,
-        BpfFormat::DimMajor);
-    args.add("coord_id", "UTM coordinate ID", m_header.m_coordId, -9999);
-    args.add("bundledfile", "List of files to bundle in output",
-        m_bundledFilesSpec);
-    args.add("output_dims", "Output dimensions", m_outputDims);
-    m_scaling.addArgs(args);
-}
-
-
-void BpfWriter::initialize()
-{
-    m_header.m_compression = Utils::toNative(
-            m_compression ? BpfCompression::Zlib : BpfCompression::None);
-    m_extraData = Utils::base64_decode(m_extraDataSpec);
-    if (m_header.m_coordId == -9999)
-    {
-        m_header.m_coordId = 0;
-        m_header.m_coordType = Utils::toNative(BpfCoordType::None);
-    }
-    else
-    {
-        m_header.m_coordType = Utils::toNative(BpfCoordType::UTM);
-    }
-
-    for (auto file : m_bundledFilesSpec)
-    {
-        if (!FileUtils::fileExists(file))
-        {
-            std::ostringstream oss;
-
-            oss << getName() << ": bundledfile '" << file << "' doesn't exist.";
-            throw pdal_error(oss.str());
-        }
-
-        size_t size = FileUtils::fileSize(file);
-        if (size > (std::numeric_limits<uint32_t>::max)())
-        {
-            std::ostringstream oss;
-            oss << getName() << ": bundledfile '" << file << "' too large.";
-            throw pdal_error(oss.str());
-        }
-
-        BpfUlemFile ulemFile(size, FileUtils::getFilename(file), file);
-        if (ulemFile.m_filename.length() > 32)
-        {
-            std::ostringstream oss;
-            oss << getName() << ": bundledfile '" << file << "' name "
-                "exceeds maximum length of 32.";
-            throw pdal_error(oss.str());
-        }
-        m_bundledFiles.push_back(ulemFile);
-    }
-
-    // BPF coordinates are always in UTM meters, which can be quite large.
-    // Allowing the writer to proceed with the default offset of 0 can lead to
-    // unexpected quantization of the coordinates. Instead, we force use of
-    // auto offset to subtract the minimum value in XYZ, unless of course, the
-    // user chooses to override with their own offset.
-    if (!m_scaling.m_xOffArg->set())
-        m_scaling.m_xXform.m_offset.m_auto = true;
-    if (!m_scaling.m_yOffArg->set())
-        m_scaling.m_yXform.m_offset.m_auto = true;
-    if (!m_scaling.m_zOffArg->set())
-        m_scaling.m_zXform.m_offset.m_auto = true;
-}
-
-
-void BpfWriter::prepared(PointTableRef table)
-{
-    loadBpfDimensions(table.layout());
-}
-
-
-void BpfWriter::readyFile(const std::string& filename, const SpatialReference&)
-{
-    m_stream.open(filename);
-    m_header.m_version = 3;
-    m_header.m_numDim = m_dims.size();
-    m_header.m_numPts = 0;
-    m_header.setLog(log());
-
-    // We will re-write the header and dimensions to account for the point
-    // count and dimension min/max.
-    m_header.write(m_stream);
-    m_header.writeDimensions(m_stream, m_dims);
-    for (auto& file : m_bundledFiles)
-        file.write(m_stream);
-    m_stream.put((const char *)m_extraData.data(), m_extraData.size());
-
-    m_header.m_len = m_stream.position();
-
-    m_header.m_xform.m_vals[0] = m_scaling.m_xXform.m_scale.m_val;
-    m_header.m_xform.m_vals[5] = m_scaling.m_yXform.m_scale.m_val;
-    m_header.m_xform.m_vals[10] = m_scaling.m_zXform.m_scale.m_val;
-}
-
-
-void BpfWriter::loadBpfDimensions(PointLayoutPtr layout)
-{
-    Dimension::IdList dims;
-
-    if (m_outputDims.size())
-    {
-       for (std::string& s : m_outputDims)
-       {
-           Dimension::Id id = layout->findDim(s);
-           if (id == Dimension::Id::Unknown)
-           {
-               std::ostringstream oss;
-               oss << "Invalid dimension '" << s << "' specified for "
-                   "'output_dims' option.";
-               throw pdal_error(oss.str());
-            }
-            dims.push_back(id);
-       }
-    }
-    else
-        dims = layout->dims();
-
-    // Verify that we have X, Y and Z and that they're the first three
-    // dimensions.
-    std::sort(dims.begin(), dims.end());
-    if (dims.size() < 3 || dims[0] != Dimension::Id::X ||
-        dims[1] != Dimension::Id::Y || dims[2] != Dimension::Id::Z)
-    {
-        throw pdal_error("Missing one of dimensions X, Y or Z.  "
-            "Can't write BPF.");
-    }
-
-    for (auto id : dims)
-    {
-        BpfDimension dim;
-        dim.m_id = id;
-        dim.m_label = layout->dimName(id);
-        m_dims.push_back(dim);
-    }
-}
-
-
-void BpfWriter::writeView(const PointViewPtr dataShared)
-{
-    m_scaling.setAutoXForm(dataShared);
-
-    // Avoid reference count overhead internally.
-    const PointView* data(dataShared.get());
-
-    // We know that X, Y and Z are dimensions 0, 1 and 2.
-    m_dims[0].m_offset = m_scaling.m_xXform.m_offset.m_val;
-    m_dims[1].m_offset = m_scaling.m_yXform.m_offset.m_val;
-    m_dims[2].m_offset = m_scaling.m_zXform.m_offset.m_val;
-
-    switch (m_header.m_pointFormat)
-    {
-    case BpfFormat::PointMajor:
-        writePointMajor(data);
-        break;
-    case BpfFormat::DimMajor:
-        writeDimMajor(data);
-        break;
-    case BpfFormat::ByteMajor:
-        writeByteMajor(data);
-        break;
-    }
-    m_header.m_numPts += data->size();
-}
-
-
-void BpfWriter::writePointMajor(const PointView* data)
-{
-    // Blocks of 10,000 points will ensure that we're under 16MB, even
-    // for 255 dimensions.
-    size_t blockpoints = std::min<point_count_t>(10000UL, data->size());
-
-    // For compression we're going to write to a buffer so that it can be
-    // compressed before it's written to the file stream.
-    BpfCompressor compressor(m_stream,
-        blockpoints * sizeof(float) * m_dims.size());
-    PointId idx = 0;
-    while (idx < data->size())
-    {
-        if (m_header.m_compression)
-            compressor.startBlock();
-        size_t blockId;
-        for (blockId = 0; idx < data->size() && blockId < blockpoints;
-            ++idx, ++blockId)
-        {
-            for (auto & bpfDim : m_dims)
-            {
-                double d = getAdjustedValue(data, bpfDim, idx);
-                m_stream << (float)d;
-            }
-        }
-        if (m_header.m_compression)
-        {
-            compressor.compress();
-            compressor.finish();
-        }
-    }
-}
-
-
-void BpfWriter::writeDimMajor(const PointView* data)
-{
-    // We're going to pretend for now that we only even have one point buffer.
-    BpfCompressor compressor(m_stream, data->size() * sizeof(float));
-
-    for (auto & bpfDim : m_dims)
-    {
-
-        if (m_header.m_compression)
-            compressor.startBlock();
-        for (PointId idx = 0; idx < data->size(); ++idx)
-        {
-            double d = getAdjustedValue(data, bpfDim, idx);
-            m_stream << (float)d;
-        }
-        if (m_header.m_compression)
-        {
-            compressor.compress();
-            compressor.finish();
-        }
-    }
-}
-
-
-void BpfWriter::writeByteMajor(const PointView* data)
-{
-    union
-    {
-        float f;
-        uint32_t u32;
-    } uu;
-
-    // We're going to pretend for now that we only ever have one point buffer.
-    BpfCompressor compressor(m_stream,
-        data->size() * sizeof(float) * m_dims.size());
-
-    if (m_header.m_compression)
-        compressor.startBlock();
-    for (auto & bpfDim : m_dims)
-    {
-        for (size_t b = 0; b < sizeof(float); b++)
-        {
-            for (PointId idx = 0; idx < data->size(); ++idx)
-            {
-                uu.f = (float)getAdjustedValue(data, bpfDim, idx);
-                uint8_t u8 = (uint8_t)(uu.u32 >> (b * CHAR_BIT));
-                m_stream << u8;
-            }
-        }
-    }
-    if (m_header.m_compression)
-    {
-        compressor.compress();
-        compressor.finish();
-    }
-}
-
-
-double BpfWriter::getAdjustedValue(const PointView* data,
-    BpfDimension& bpfDim, PointId idx)
-{
-    double d = data->getFieldAs<double>(bpfDim.m_id, idx);
-    bpfDim.m_min = std::min(bpfDim.m_min, d);
-    bpfDim.m_max = std::max(bpfDim.m_max, d);
-
-    if (bpfDim.m_id == Dimension::Id::X)
-        d /= m_scaling.m_xXform.m_scale.m_val;
-    else if (bpfDim.m_id == Dimension::Id::Y)
-        d /= m_scaling.m_yXform.m_scale.m_val;
-    else if (bpfDim.m_id == Dimension::Id::Z)
-        d /= m_scaling.m_zXform.m_scale.m_val;
-    return (d - bpfDim.m_offset);
-}
-
-
-void BpfWriter::doneFile()
-{
-    // Rewrite the header to update the the correct number of points and
-    // statistics.
-    m_stream.seek(0);
-    m_header.write(m_stream);
-    m_header.writeDimensions(m_stream, m_dims);
-    m_stream.close();
-}
-
-} //namespace pdal
diff --git a/io/bpf/BpfWriter.hpp b/io/bpf/BpfWriter.hpp
deleted file mode 100644
index fb8c6fe..0000000
--- a/io/bpf/BpfWriter.hpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Hobu Inc., hobu at hobu.co
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-// BPF is an NGA specification for point cloud data. The specification can be
-// found at https://nsgreg.nga.mil/doc/view?i=4202
-
-#pragma once
-
-#include "BpfHeader.hpp"
-
-#include <pdal/pdal_export.hpp>
-#include <pdal/FlexWriter.hpp>
-#include <pdal/util/OStream.hpp>
-#include <pdal/plugin.hpp>
-
-#include <vector>
-
-extern "C" int32_t BpfWriter_ExitFunc();
-extern "C" PF_ExitFunc BpfWriter_InitPlugin();
-
-namespace pdal
-{
-
-class PDAL_DLL BpfWriter : public FlexWriter
-{
-public:
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    StringList m_outputDims; ///< List of dimensions to write
-    OLeStream m_stream;
-    BpfHeader m_header;
-    BpfDimensionList m_dims;
-    std::vector<uint8_t> m_extraData;
-    std::vector<BpfUlemFile> m_bundledFiles;
-    bool m_compression;
-    std::string m_extraDataSpec;
-    StringList m_bundledFilesSpec;
-
-    virtual void addArgs(ProgramArgs& args);
-    virtual void initialize();
-    virtual void prepared(PointTableRef table);
-    virtual void readyFile(const std::string& filename,
-        const SpatialReference& srs);
-    virtual void writeView(const PointViewPtr data);
-    virtual void doneFile();
-
-    double getAdjustedValue(const PointView* data, BpfDimension& bpfDim,
-        PointId idx);
-    void loadBpfDimensions(PointLayoutPtr layout);
-    void writePointMajor(const PointView* data);
-    void writeDimMajor(const PointView* data);
-    void writeByteMajor(const PointView* data);
-    void writeCompressedBlock(char *buf, size_t size);
-};
-
-} // namespace pdal
diff --git a/io/bpf/CMakeLists.txt b/io/bpf/CMakeLists.txt
deleted file mode 100644
index 09623a3..0000000
--- a/io/bpf/CMakeLists.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# BPF driver CMake configuration
-#
-
-#
-# BPF Reader
-#
-set(srcs
-    BpfCompressor.cpp
-    BpfHeader.cpp
-    BpfReader.cpp
-    BpfWriter.cpp
-)
-
-set(incs
-    BpfCompressor.hpp
-    BpfHeader.hpp
-    BpfReader.hpp
-    BpfWriter.hpp
-)
-
-PDAL_ADD_DRIVER(reader bpf "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/io/buffer/CMakeLists.txt b/io/buffer/CMakeLists.txt
deleted file mode 100644
index d9158fc..0000000
--- a/io/buffer/CMakeLists.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Buffer driver CMake configuration
-#
-
-#
-# Buffer Reader
-#
-
-set(incs
-    BufferReader.hpp
-)
-
-install(FILES ${incs} DESTINATION "${PDAL_INCLUDE_INSTALL_DIR}")
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
-
diff --git a/io/derivative/CMakeLists.txt b/io/derivative/CMakeLists.txt
deleted file mode 100644
index ddae4ef..0000000
--- a/io/derivative/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Derivative driver CMake configuration
-#
-
-#
-# Derivative Writer
-#
-set(srcs
-    DerivativeWriter.cpp
-)
-
-set(incs
-    DerivativeWriter.hpp
-)
-
-PDAL_ADD_DRIVER(writer derivative "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/io/derivative/DerivativeWriter.cpp b/io/derivative/DerivativeWriter.cpp
deleted file mode 100644
index 31ae3f4..0000000
--- a/io/derivative/DerivativeWriter.cpp
+++ /dev/null
@@ -1,1708 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers, brad.chambers at gmail.com
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "DerivativeWriter.hpp"
-
-#include <pdal/PointView.hpp>
-#include <pdal/util/FileUtils.hpp>
-#include <pdal/util/Utils.hpp>
-#include <pdal/pdal_macros.hpp>
-
-#include <algorithm>
-#include <cfloat>
-#include <cmath>
-#include <iostream>
-#include <limits>
-
-#include "gdal_priv.h" // For File I/O
-#include "gdal_version.h" // For version info
-#include "ogr_spatialref.h"  //For Geographic Information/Transformations
-
-namespace pdal
-{
-static PluginInfo const s_info = PluginInfo(
-                                     "writers.derivative",
-                                     "Derivative writer",
-                                     "http://pdal.io/stages/writers.derivative.html");
-
-CREATE_STATIC_PLUGIN(1, 0, DerivativeWriter, Writer, s_info)
-
-std::string DerivativeWriter::getName() const
-{
-    return s_info.name;
-}
-
-const double c_pi = 3.14159265358979323846; /*!< PI value */
-const float c_background = FLT_MIN;
-
-DerivativeWriter::DerivativeWriter()
-{
-    GDALAllRegister();
-}
-
-
-void DerivativeWriter::addArgs(ProgramArgs& args)
-{
-    args.add("grid_dist_x", "X grid distance", m_GRID_DIST_X, 15.0);
-    args.add("grid_dist_y", "Y grid distance", m_GRID_DIST_Y, 15.0);
-    args.add("primitive_type", "Primitive type", m_primTypesSpec);
-}
-
-
-void DerivativeWriter::initialize()
-{
-    static std::map<std::string, PrimitiveType> primtypes =
-        { {"slope_d8", SLOPE_D8},
-          {"slope_fd", SLOPE_FD},
-          {"aspect_d8", ASPECT_D8},
-          {"aspect_fd", ASPECT_FD},
-          {"hillshade", HILLSHADE},
-          {"contour_curvature", CONTOUR_CURVATURE},
-          {"profile_curvature", PROFILE_CURVATURE},
-          {"tangential_curvature", TANGENTIAL_CURVATURE},
-          {"total_curvature", TOTAL_CURVATURE},
-          {"catchment_area", CATCHMENT_AREA}
-        };
-/**
-    primtypes["slope_d8"] = SLOPE_D8;
-    primtypes["slope_fd"] = SLOPE_FD;
-    primtypes["aspect_d8"] = ASPECT_D8;
-    primtypes["aspect_fd"] = ASPECT_FD;
-    primtypes["hillshade"] = HILLSHADE;
-    primtypes["contour_curvature"] = CONTOUR_CURVATURE;
-    primtypes["profile_curvature"] = PROFILE_CURVATURE;
-    primtypes["tangential_curvature"] = TANGENTIAL_CURVATURE;
-    primtypes["total_curvature"] = TOTAL_CURVATURE;
-    primtypes["catchment_area"] = CATCHMENT_AREA;
-**/
-
-    if (m_primTypesSpec.empty())
-        m_primTypesSpec.push_back("slope_d8");
-
-    handleFilenameTemplate();
-    if (m_hashPos == std::string::npos && m_primTypesSpec.size() > 1)
-    {
-        std::ostringstream oss;
-
-        oss << getName() << ": No template placeholder ('#') found in "
-            "filename '" << m_filename << "' when one is required with "
-            "multiple primitive types.";
-        throw pdal_error(oss.str());
-    }
-
-    for (std::string os : m_primTypesSpec)
-    {
-        std::string s = Utils::tolower(os);
-        auto pi = primtypes.find(s);
-        if (pi == primtypes.end())
-        {
-            std::ostringstream oss;
-            oss << getName() << ": Unrecognized primitive type '" << os <<
-                "'.";
-            throw pdal_error(oss.str());
-        }
-        TypeOutput to;
-        to.m_type = pi->second;
-        to.m_filename = generateFilename(pi->first);
-        m_primitiveTypes.push_back(to);
-    }
-
-    setBounds(BOX2D());
-}
-
-
-std::string
-DerivativeWriter::generateFilename(const std::string& primName) const
-{
-    // We've already checked during argument parsing that we have a valid
-    // template placeholder (#) if necessary.
-    std::string filename = m_filename;
-    if (m_hashPos != std::string::npos)
-        filename.replace(m_hashPos, 1, primName);
-    return filename;
-}
-
-
-double DerivativeWriter::GetNeighbor(Eigen::MatrixXd* data, int row, int col,
-    Direction d)
-{
-    double val;
-    switch (d)
-    {
-        case NORTH:
-            val = ((*data)(row-1, col));
-            break;
-        case SOUTH:
-            val = ((*data)(row+1, col));
-            break;
-        case EAST:
-            val = ((*data)(row, col+1));
-            break;
-        case WEST:
-            val = ((*data)(row, col-1));
-            break;
-        case NORTHEAST:
-            val = ((*data)(row-1, col+1));
-            break;
-        case NORTHWEST:
-            val = ((*data)(row-1, col-1));
-            break;
-        case SOUTHEAST:
-            val = ((*data)(row+1, col+1));
-            break;
-        case SOUTHWEST:
-            val = ((*data)(row+1, col-1));
-            break;
-        default:
-            val = ((*data)(row, col));
-            break;
-    }
-    return val;
-}
-
-
-double DerivativeWriter::determineSlopeFD(Eigen::MatrixXd* data, int row,
-        int col, double postSpacing, double valueToIgnore)
-{
-    double tSlopeVal = valueToIgnore;
-    double tSlopeValDegree = valueToIgnore;
-
-    double mean = 0.0;
-    unsigned int nvals = 0;
-
-    double val = static_cast<double>((*data)(row, col));
-    double north = GetNeighbor(data, row, col, NORTH);
-    double south = GetNeighbor(data, row, col, SOUTH);
-    double east = GetNeighbor(data, row, col, EAST);
-    double west = GetNeighbor(data, row, col, WEST);
-
-    auto accumulate = [&nvals, &mean, valueToIgnore](double val)
-    {
-        if (val != valueToIgnore)
-        {
-            mean += val;
-            nvals++;
-        }
-    };
-
-    accumulate(val);
-    accumulate(north);
-    accumulate(south);
-    accumulate(east);
-    accumulate(west);
-
-    mean /= nvals;
-
-    if (north == valueToIgnore) north = mean;
-    if (south == valueToIgnore) south = mean;
-    if (east == valueToIgnore) east = mean;
-    if (west == valueToIgnore) west = mean;
-
-    double zX = (east - west) / (2 * postSpacing);
-    double zY = (north - south) / (2 * postSpacing);
-    double p = (zX * zX) + (zY * zY);
-
-    tSlopeVal = std::sqrt(p);
-
-    if (tSlopeVal != valueToIgnore)
-        tSlopeValDegree = atan(tSlopeVal) * (180.0f / c_pi);
-
-    return tSlopeValDegree;
-}
-
-
-double DerivativeWriter::determineSlopeD8(Eigen::MatrixXd* data, int row,
-        int col, double postSpacing, double valueToIgnore)
-{
-    double tPhi1 = 1.0f;
-    double tPhi2 = sqrt(2.0f);
-    double tSlopeVal = valueToIgnore;
-    double tSlopeValDegree = valueToIgnore;
-
-    double val = static_cast<double>((*data)(row, col));
-    if (val == valueToIgnore)
-        return val;
-
-    double north = GetNeighbor(data, row, col, NORTH);
-    double south = GetNeighbor(data, row, col, SOUTH);
-    double east = GetNeighbor(data, row, col, EAST);
-    double west = GetNeighbor(data, row, col, WEST);
-    double northeast = GetNeighbor(data, row, col, NORTHEAST);
-    double northwest = GetNeighbor(data, row, col, NORTHWEST);
-    double southeast = GetNeighbor(data, row, col, SOUTHEAST);
-    double southwest = GetNeighbor(data, row, col, SOUTHWEST);
-
-    auto checkVal = [val, &tSlopeVal, valueToIgnore, postSpacing]
-        (double neighbor, double phi)
-    {
-        if (neighbor != valueToIgnore)
-        {
-            neighbor = (val - neighbor) / (postSpacing * phi);
-            if ((std::fabs(neighbor) > std::fabs(tSlopeVal)) ||
-                (tSlopeVal == valueToIgnore))
-                tSlopeVal = neighbor;
-        }
-    };
-
-    checkVal(north, tPhi1);
-    checkVal(south, tPhi1);
-    checkVal(east, tPhi1);
-    checkVal(west, tPhi1);
-    checkVal(northeast, tPhi2);
-    checkVal(northwest, tPhi2);
-    checkVal(southeast, tPhi2);
-    checkVal(southwest, tPhi2);
-
-    if (tSlopeVal != valueToIgnore)
-        tSlopeValDegree = atan(tSlopeVal) * (180.0f / c_pi);
-
-    return tSlopeValDegree;
-}
-
-
-double DerivativeWriter::determineAspectFD(Eigen::MatrixXd* data, int row,
-        int col, double postSpacing, double valueToIgnore)
-{
-    double mean = 0.0;
-    unsigned int nvals = 0;
-
-    double val = static_cast<double>((*data)(row, col));
-    double north = GetNeighbor(data, row, col, NORTH);
-    double south = GetNeighbor(data, row, col, SOUTH);
-    double east = GetNeighbor(data, row, col, EAST);
-    double west = GetNeighbor(data, row, col, WEST);
-
-    auto accumulate = [&nvals, &mean, valueToIgnore](double val)
-    {
-        if (val != valueToIgnore)
-        {
-            mean += val;
-            nvals++;
-        }
-    };
-
-    accumulate(val);
-    accumulate(north);
-    accumulate(south);
-    accumulate(east);
-    accumulate(west);
-
-    mean /= nvals;
-
-    if (north == valueToIgnore) north = mean;
-    if (south == valueToIgnore) south = mean;
-    if (east == valueToIgnore) east = mean;
-    if (west == valueToIgnore) west = mean;
-
-    double zX = (east - west) / (2 * postSpacing);
-    double zY = (north - south) / (2 * postSpacing);
-    double p = (zX * zX) + (zY * zY);
-
-    return 180.0 - std::atan(zY/zX) + 90.0 * (zX / std::fabs(zX));
-}
-
-
-double DerivativeWriter::determineAspectD8(Eigen::MatrixXd* data, int row,
-        int col, double postSpacing)
-{
-    double tPhi1 = 1.0f;
-    double tPhi2 = sqrt(2.0f);
-    double tH = postSpacing;
-    double tVal, tN, tS, tE, tW, tNW, tNE, tSW, tSE, nextTVal;
-    double tSlopeVal = std::numeric_limits<double>::max(), tSlopeValDegree;
-//     int tNextY, tNextX;
-    unsigned int j = 0;
-
-    tVal = (*data)(row, col);
-    if (tVal == std::numeric_limits<double>::max())
-        return tVal;
-
-    //North
-    nextTVal = GetNeighbor(data, row, col, NORTH);
-    if (nextTVal < std::numeric_limits<double>::max())
-    {
-        tN = (tVal - nextTVal) / (tH * tPhi1);
-        if (tN > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        {
-            tSlopeVal = tN;
-            j = 8;
-        }
-    }
-    //South
-    nextTVal = GetNeighbor(data, row, col, SOUTH);
-    if (nextTVal < std::numeric_limits<double>::max())
-    {
-        tS = (tVal - nextTVal) / (tH * tPhi1);
-        if (tS > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        {
-            tSlopeVal = tS;
-            j = 4;
-        }
-    }
-    //East
-    nextTVal = GetNeighbor(data, row, col, EAST);
-    if (nextTVal < std::numeric_limits<double>::max())
-    {
-        tE = (tVal - nextTVal) / (tH * tPhi1);
-        if (tE > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        {
-            tSlopeVal = tE;
-            j = 2;
-        }
-    }
-    //West
-    nextTVal = GetNeighbor(data, row, col, WEST);
-    if (nextTVal < std::numeric_limits<double>::max())
-    {
-        tW = (tVal - nextTVal) / (tH * tPhi1);
-        if (tW > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        {
-            tSlopeVal = tW;
-            j = 6;
-        }
-    }
-    //NorthEast
-    nextTVal = GetNeighbor(data, row, col, NORTHEAST);
-    if (nextTVal < std::numeric_limits<double>::max())
-    {
-        tNE = (tVal - nextTVal) / (tH * tPhi2);
-        if (tNE > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        {
-            tSlopeVal = tNE;
-            j = 1;
-        }
-    }
-    //NorthWest
-    nextTVal = GetNeighbor(data, row, col, NORTHWEST);
-    if (nextTVal < std::numeric_limits<double>::max())
-    {
-        tNW = (tVal - nextTVal) / (tH * tPhi2);
-        if (tNW > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        {
-            tSlopeVal = tNW;
-            j = 7;
-        }
-    }
-    //SouthEast
-    nextTVal = GetNeighbor(data, row, col, SOUTHEAST);
-    if (nextTVal < std::numeric_limits<double>::max())
-    {
-        tSE = (tVal - nextTVal) / (tH * tPhi2);
-        if (tSE > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        {
-            tSlopeVal = tSE;
-            j = 3;
-        }
-    }
-    //SouthWest
-    nextTVal = GetNeighbor(data, row, col, SOUTHWEST);
-    if (nextTVal < std::numeric_limits<double>::max())
-    {
-        tSW = (tVal - nextTVal) / (tH * tPhi2);
-        if (tSW > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        {
-            tSlopeVal = tSW;
-            j = 5;
-        }
-    }
-
-    //tSlopeValDegree = 45 * j;
-    tSlopeValDegree = std::pow(2.0,j-1);
-
-    return tSlopeValDegree;
-}
-
-int DerivativeWriter::determineCatchmentAreaD8(Eigen::MatrixXd* data,
-        Eigen::MatrixXd* area, int row, int col, double postSpacing)
-{
-    if ((*area)(row, col) > 0)
-    {
-        return (*area)(row, col);
-    }
-    else
-    {
-        (*area)(row, col) = 1;
-
-        for (int i = 1; i < 9; ++i)
-        {
-            int j, k;
-            switch (i)
-            {
-                case 1:
-                    j = row - 1;
-                    k = col + 1;
-                    break;
-
-                case 2:
-                    j = row;
-                    k = col + 1;
-                    break;
-
-                case 3:
-                    j = row + 1;
-                    k = col + 1;
-                    break;
-
-                case 4:
-                    j = row + 1;
-                    k = col;
-                    break;
-
-                case 5:
-                    j = row + 1;
-                    k = col - 1;
-                    break;
-
-                case 6:
-                    j = row;
-                    k = col - 1;
-                    break;
-
-                case 7:
-                    j = row - 1;
-                    k = col - 1;
-                    break;
-
-                case 8:
-                    j = row - 1;
-                    k = col;
-                    break;
-            }
-
-            if ((*area)(j, k) > 0)
-                (*area)(row, col) += determineCatchmentAreaD8(data, area, j, k,
-                                     postSpacing);
-
-            // not quite complete here...
-        }
-
-        //double tPhi1 = 1.0f;
-        //double tPhi2 = sqrt(2.0f);
-        //double tH = postSpacing;
-        //double tVal, tN, tS, tE, tW, tNW, tNE, tSW, tSE, nextTVal;
-        //double tSlopeVal = std::numeric_limits<double>::max(), tSlopeValDegree;
-        //int tNextY, tNextX;
-        //unsigned int j = 0;
-
-        //tVal = (*data)(row, col);
-        //if (tVal == std::numeric_limits<double>::max())
-        //  return tVal;
-
-        ////North
-        //tNextY = row - 1;
-        //tNextX = col;
-        //nextTVal = (*data)(tNextY, tNextX);
-        //if (nextTVal < std::numeric_limits<double>::max())
-        //{
-        //  tN = (tVal - nextTVal) / (tH * tPhi1);
-        //  if (tN > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        //  {
-        //    tSlopeVal = tN;
-        //    j = 8;
-        //  }
-        //}
-        ////South
-        //tNextY = row + 1;
-        //tNextX = col;
-        //nextTVal = (*data)(tNextY, tNextX);
-        //if (nextTVal < std::numeric_limits<double>::max())
-        //{
-        //  tS = (tVal - nextTVal) / (tH * tPhi1);
-        //  if (tS > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        //  {
-        //    tSlopeVal = tS;
-        //    j = 4;
-        //  }
-        //}
-        ////East
-        //tNextY = row;
-        //tNextX = col + 1;
-        //nextTVal = (*data)(tNextY, tNextX);
-        //if (nextTVal < std::numeric_limits<double>::max())
-        //{
-        //  tE = (tVal - nextTVal) / (tH * tPhi1);
-        //  if (tE > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        //  {
-        //    tSlopeVal = tE;
-        //    j = 2;
-        //  }
-        //}
-        ////West
-        //tNextY = row;
-        //tNextX = col - 1;
-        //nextTVal = (*data)(tNextY, tNextX);
-        //if (nextTVal < std::numeric_limits<double>::max())
-        //{
-        //  tW = (tVal - nextTVal) / (tH * tPhi1);
-        //  if (tW > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        //  {
-        //    tSlopeVal = tW;
-        //    j = 6;
-        //  }
-        //}
-        ////NorthEast
-        //tNextY = row - 1;
-        //tNextX = col + 1;
-        //nextTVal = (*data)(tNextY, tNextX);
-        //if (nextTVal < std::numeric_limits<double>::max())
-        //{
-        //  tNE = (tVal - nextTVal) / (tH * tPhi2);
-        //  if (tNE > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        //  {
-        //    tSlopeVal = tNE;
-        //    j = 1;
-        //  }
-        //}
-        ////NorthWest
-        //tNextY = row - 1;
-        //tNextX = col - 1;
-        //nextTVal = (*data)(tNextY, tNextX);
-        //if (nextTVal < std::numeric_limits<double>::max())
-        //{
-        //  tNW = (tVal - nextTVal) / (tH * tPhi2);
-        //  if (tNW > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        //  {
-        //    tSlopeVal = tNW;
-        //    j = 7;
-        //  }
-        //}
-        ////SouthEast
-        //tNextY = row + 1;
-        //tNextX = col + 1;
-        //nextTVal = (*data)(tNextY, tNextX);
-        //if (nextTVal < std::numeric_limits<double>::max())
-        //{
-        //  tSE = (tVal - nextTVal) / (tH * tPhi2);
-        //  if (tSE > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        //  {
-        //    tSlopeVal = tSE;
-        //    j = 3;
-        //  }
-        //}
-        ////SouthWest
-        //tNextY = row + 1;
-        //tNextX = col - 1;
-        //nextTVal = (*data)(tNextY, tNextX);
-        //if (nextTVal < std::numeric_limits<double>::max())
-        //{
-        //  tSW = (tVal - nextTVal) / (tH * tPhi2);
-        //  if (tSW > tSlopeVal || tSlopeVal == std::numeric_limits<double>::max())
-        //  {
-        //    tSlopeVal = tSW;
-        //    j = 5;
-        //  }
-        //}
-
-        //switch (j)
-        //{
-        //case 1:
-        //  tNextY = row - 1;
-        //  tNextX = col + 1;
-        //  break;
-
-        //case 2:
-        //  tNextY = row;
-        //  tNextX = col + 1;
-        //  break;
-
-        //case 3:
-        //  tNextY = row + 1;
-        //  tNextX = col + 1;
-        //  break;
-
-        //case 4:
-        //  tNextY = row + 1;
-        //  tNextX = col;
-        //  break;
-
-        //case 5:
-        //  tNextY = row + 1;
-        //  tNextX = col - 1;
-        //  break;
-
-        //case 6:
-        //  tNextY = row;
-        //  tNextX = col - 1;
-        //  break;
-
-        //case 7:
-        //  tNextY = row - 1;
-        //  tNextX = col - 1;
-        //  break;
-
-        //case 8:
-        //  tNextY = row - 1;
-        //  tNextX = col;
-        //  break;
-        //}
-        //(*area)(row, col) = determineCatchmentAreaD8(data, area, tNextY, tNextX, postSpacing);
-    }
-    return 0;
-}
-
-double DerivativeWriter::determineHillshade(Eigen::MatrixXd* data, int row,
-        int col, double zenithRad, double azimuthRad, double postSpacing)
-{
-    //ABELL - tEVar not currently used.
-    //double tAVar, tBVar, tCVar, tDVar, tEVar, tFVar, tGVar, tHVar, tIVar;
-    double tAVar, tBVar, tCVar, tDVar, tFVar, tGVar, tHVar, tIVar;
-    double tDZDX, tDZDY, tSlopeRad, tAspectRad = 0.0;
-    double tHillShade;
-
-    tAVar = GetNeighbor(data, row, col, NORTHWEST);
-    tBVar = GetNeighbor(data, row, col, NORTH);
-    tCVar = GetNeighbor(data, row, col, NORTHWEST);
-    tDVar = GetNeighbor(data, row, col, WEST);
-    //tEVar = (double)(*data)(row, col);
-    tFVar = GetNeighbor(data, row, col, EAST);
-    tGVar = GetNeighbor(data, row, col, SOUTHWEST);
-    tHVar = GetNeighbor(data, row, col, SOUTH);
-    tIVar = GetNeighbor(data, row, col, SOUTHEAST);
-
-    tDZDX = ((tCVar + 2 * tFVar + tIVar) - (tAVar + 2 * tDVar + tGVar)) /
-            (8 * postSpacing);
-    tDZDY = ((tGVar + 2* tHVar + tIVar) - (tAVar + 2 * tBVar + tCVar))  /
-            (8 * postSpacing);
-    tSlopeRad = atan(sqrt(pow(tDZDX, 2) + pow(tDZDY, 2)));
-
-    if (tDZDX == 0)
-    {
-        if (tDZDY > 0)
-        {
-            tAspectRad = c_pi / 2;
-        }
-        else if (tDZDY < 0)
-        {
-            tAspectRad = (2 * c_pi) - (c_pi / 2);
-        }
-        else
-        {
-            //ABELL - This looks wrong.  At least needs a comment.
-            // tAspectRad = tAspectRad;
-            ;
-        }
-    }
-    else
-    {
-        tAspectRad = atan2(tDZDY, -1 * tDZDX);
-        if (tAspectRad < 0)
-        {
-            tAspectRad = 2 * c_pi + tAspectRad;
-        }
-    }
-
-    tHillShade = (((cos(zenithRad) * cos(tSlopeRad)) + (sin(zenithRad) *
-                   sin(tSlopeRad) * cos(azimuthRad - tAspectRad))));
-
-    return tHillShade;
-}
-
-
-double DerivativeWriter::determineContourCurvature(Eigen::MatrixXd* data,
-        int row, int col, double postSpacing, double valueToIgnore)
-{
-    double mean = 0.0;
-    unsigned int nvals = 0;
-
-    double value = static_cast<double>((*data)(row, col));
-    double north = GetNeighbor(data, row, col, NORTH);
-    double south = GetNeighbor(data, row, col, SOUTH);
-    double east = GetNeighbor(data, row, col, EAST);
-    double west = GetNeighbor(data, row, col, WEST);
-    double northeast = GetNeighbor(data, row, col, NORTHEAST);
-    double northwest = GetNeighbor(data, row, col, NORTHWEST);
-    double southeast = GetNeighbor(data, row, col, SOUTHEAST);
-    double southwest = GetNeighbor(data, row, col, SOUTHWEST);
-
-    auto accumulate = [&nvals, &mean, valueToIgnore](double val)
-    {
-        if (val != valueToIgnore)
-        {
-            mean += val;
-            nvals++;
-        }
-    };
-
-    accumulate(value);
-    accumulate(north);
-    accumulate(south);
-    accumulate(east);
-    accumulate(west);
-    accumulate(northeast);
-    accumulate(northwest);
-    accumulate(southeast);
-    accumulate(southwest);
-
-    mean /= nvals;
-
-    if (value == valueToIgnore) value = mean;
-    if (north == valueToIgnore) north = mean;
-    if (south == valueToIgnore) south = mean;
-    if (east == valueToIgnore) east = mean;
-    if (west == valueToIgnore) west = mean;
-    if (northeast == valueToIgnore) northeast = mean;
-    if (northwest == valueToIgnore) northwest = mean;
-    if (southeast == valueToIgnore) southeast = mean;
-    if (southwest == valueToIgnore) southwest = mean;
-
-    double zXX = (east - 2.0 * value + west) / (postSpacing * postSpacing);
-    double zYY = (north - 2.0 * value + south) / (postSpacing * postSpacing);
-    double zXY = ((-1.0 * northwest) + northeast + southwest - southeast) / (4.0 * postSpacing * postSpacing);
-    double zX = (east - west) / (2 * postSpacing);
-    double zY = (north - south) / (2 * postSpacing);
-    double p = (zX * zX) + (zY * zY);
-    double q = p + 1;
-
-    return static_cast<float>(((zXX*zX*zX)-(2*zXY*zX*zY)+(zYY*zY*zY))/(p*std::sqrt(q*q*q)));
-}
-
-
-double DerivativeWriter::determineProfileCurvature(Eigen::MatrixXd* data,
-        int row, int col, double postSpacing, double valueToIgnore)
-{
-    double mean = 0.0;
-    unsigned int nvals = 0;
-
-    double value = static_cast<double>((*data)(row, col));
-    double north = GetNeighbor(data, row, col, NORTH);
-    double south = GetNeighbor(data, row, col, SOUTH);
-    double east = GetNeighbor(data, row, col, EAST);
-    double west = GetNeighbor(data, row, col, WEST);
-    double northeast = GetNeighbor(data, row, col, NORTHEAST);
-    double northwest = GetNeighbor(data, row, col, NORTHWEST);
-    double southeast = GetNeighbor(data, row, col, SOUTHEAST);
-    double southwest = GetNeighbor(data, row, col, SOUTHWEST);
-
-    auto accumulate = [&nvals, &mean, valueToIgnore](double val)
-    {
-        if (val != valueToIgnore)
-        {
-            mean += val;
-            nvals++;
-        }
-    };
-
-    accumulate(value);
-    accumulate(north);
-    accumulate(south);
-    accumulate(east);
-    accumulate(west);
-    accumulate(northeast);
-    accumulate(northwest);
-    accumulate(southeast);
-    accumulate(southwest);
-
-    mean /= nvals;
-
-    if (value == valueToIgnore) value = mean;
-    if (north == valueToIgnore) north = mean;
-    if (south == valueToIgnore) south = mean;
-    if (east == valueToIgnore) east = mean;
-    if (west == valueToIgnore) west = mean;
-    if (northeast == valueToIgnore) northeast = mean;
-    if (northwest == valueToIgnore) northwest = mean;
-    if (southeast == valueToIgnore) southeast = mean;
-    if (southwest == valueToIgnore) southwest = mean;
-
-    double zXX = (east - 2.0 * value + west) / (postSpacing * postSpacing);
-    double zYY = (north - 2.0 * value + south) / (postSpacing * postSpacing);
-    double zXY = ((-1.0 * northwest) + northeast + southwest - southeast) / (4.0 * postSpacing * postSpacing);
-    double zX = (east - west) / (2 * postSpacing);
-    double zY = (north - south) / (2 * postSpacing);
-    double p = (zX * zX) + (zY * zY);
-    double q = p + 1;
-
-    return static_cast<float>(((zXX*zX*zX)+(2*zXY*zX*zY)+(zYY*zY*zY))/(p*std::sqrt(q*q*q)));
-}
-
-
-double DerivativeWriter::determineTangentialCurvature(Eigen::MatrixXd* data,
-        int row, int col, double postSpacing, double valueToIgnore)
-{
-    double mean = 0.0;
-    unsigned int nvals = 0;
-
-    double value = static_cast<double>((*data)(row, col));
-    double north = GetNeighbor(data, row, col, NORTH);
-    double south = GetNeighbor(data, row, col, SOUTH);
-    double east = GetNeighbor(data, row, col, EAST);
-    double west = GetNeighbor(data, row, col, WEST);
-    double northeast = GetNeighbor(data, row, col, NORTHEAST);
-    double northwest = GetNeighbor(data, row, col, NORTHWEST);
-    double southeast = GetNeighbor(data, row, col, SOUTHEAST);
-    double southwest = GetNeighbor(data, row, col, SOUTHWEST);
-
-    auto accumulate = [&nvals, &mean, valueToIgnore](double val)
-    {
-        if (val != valueToIgnore)
-        {
-            mean += val;
-            nvals++;
-        }
-    };
-
-    accumulate(value);
-    accumulate(north);
-    accumulate(south);
-    accumulate(east);
-    accumulate(west);
-    accumulate(northeast);
-    accumulate(northwest);
-    accumulate(southeast);
-    accumulate(southwest);
-
-    mean /= nvals;
-
-    if (value == valueToIgnore) value = mean;
-    if (north == valueToIgnore) north = mean;
-    if (south == valueToIgnore) south = mean;
-    if (east == valueToIgnore) east = mean;
-    if (west == valueToIgnore) west = mean;
-    if (northeast == valueToIgnore) northeast = mean;
-    if (northwest == valueToIgnore) northwest = mean;
-    if (southeast == valueToIgnore) southeast = mean;
-    if (southwest == valueToIgnore) southwest = mean;
-
-    double zXX = (east - 2.0 * value + west) / (postSpacing * postSpacing);
-    double zYY = (north - 2.0 * value + south) / (postSpacing * postSpacing);
-    double zXY = ((-1.0 * northwest) + northeast + southwest - southeast) / (4.0 * postSpacing * postSpacing);
-    double zX = (east - west) / (2 * postSpacing);
-    double zY = (north - south) / (2 * postSpacing);
-    double p = (zX * zX) + (zY * zY);
-    double q = p + 1;
-
-    return static_cast<float>(((zXX*zY*zY)-(2*zXY*zX*zY)+(zYY*zX*zX))/(p*std::sqrt(q)));
-}
-
-
-double DerivativeWriter::determineTotalCurvature(Eigen::MatrixXd* data, int row,
-        int col, double postSpacing, double valueToIgnore)
-{
-    double mean = 0.0;
-    unsigned int nvals = 0;
-
-    double value = static_cast<double>((*data)(row, col));
-    double north = GetNeighbor(data, row, col, NORTH);
-    double south = GetNeighbor(data, row, col, SOUTH);
-    double east = GetNeighbor(data, row, col, EAST);
-    double west = GetNeighbor(data, row, col, WEST);
-    double northeast = GetNeighbor(data, row, col, NORTHEAST);
-    double northwest = GetNeighbor(data, row, col, NORTHWEST);
-    double southeast = GetNeighbor(data, row, col, SOUTHEAST);
-    double southwest = GetNeighbor(data, row, col, SOUTHWEST);
-
-    auto accumulate = [&nvals, &mean, valueToIgnore](double val)
-    {
-        if (val != valueToIgnore)
-        {
-            mean += val;
-            nvals++;
-        }
-    };
-
-    accumulate(value);
-    accumulate(north);
-    accumulate(south);
-    accumulate(east);
-    accumulate(west);
-    accumulate(northeast);
-    accumulate(northwest);
-    accumulate(southeast);
-    accumulate(southwest);
-
-    mean /= nvals;
-
-    if (value == valueToIgnore) value = mean;
-    if (north == valueToIgnore) north = mean;
-    if (south == valueToIgnore) south = mean;
-    if (east == valueToIgnore) east = mean;
-    if (west == valueToIgnore) west = mean;
-    if (northeast == valueToIgnore) northeast = mean;
-    if (northwest == valueToIgnore) northwest = mean;
-    if (southeast == valueToIgnore) southeast = mean;
-    if (southwest == valueToIgnore) southwest = mean;
-
-    double zXX = (east - 2.0 * value + west) / (postSpacing * postSpacing);
-    double zYY = (north - 2.0 * value + south) / (postSpacing * postSpacing);
-    double zXY = ((-1.0 * northwest) + northeast + southwest - southeast) / (4.0 * postSpacing * postSpacing);
-
-    return static_cast<float>((zXX * zXX) + (2.0 * zXY * zXY) + (zYY * zYY));
-}
-
-
-GDALDataset* DerivativeWriter::createFloat32GTIFF(std::string filename,
-        int cols, int rows)
-{
-    char **papszMetadata;
-
-    // parse the format driver, hardcoded for the time being
-    std::string tFormat("GTIFF");
-    const char *pszFormat = tFormat.c_str();
-    GDALDriver* tpDriver = GetGDALDriverManager()->GetDriverByName(pszFormat);
-
-    // try to create a file of the requested format
-    if (tpDriver != NULL)
-    {
-        papszMetadata = tpDriver->GetMetadata();
-        if (CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATE, FALSE))
-        {
-            char **papszOptions = NULL;
-
-            std::string path = FileUtils::stem(filename) + ".tif";
-            GDALDataset *dataset;
-            dataset = tpDriver->Create(path.c_str(), cols, rows, 1,
-                GDT_Float32, papszOptions);
-
-            BOX2D& extent = getBounds();
-
-            // set the geo transformation
-            double adfGeoTransform[6];
-            adfGeoTransform[0] = extent.minx; // - 0.5*m_GRID_DIST_X;
-            adfGeoTransform[1] = m_GRID_DIST_X;
-            adfGeoTransform[2] = 0.0;
-            adfGeoTransform[3] = extent.maxy; // + 0.5*m_GRID_DIST_Y;
-            adfGeoTransform[4] = 0.0;
-            adfGeoTransform[5] = -1 * m_GRID_DIST_Y;
-            dataset->SetGeoTransform(adfGeoTransform);
-
-            // set the projection
-            log()->get(LogLevel::Debug5) << m_inSRS.getWKT() << std::endl;
-            dataset->SetProjection(m_inSRS.getWKT().c_str());
-
-            if (dataset)
-                return dataset;
-        }
-    }
-    return NULL;
-}
-
-
-void DerivativeWriter::writeSlope(Eigen::MatrixXd* tDemData,
-        const PointViewPtr data, PrimitiveType method,
-        const std::string& filename)
-{
-    BOX2D& extent = getBounds();
-
-    // use the max grid size as the post spacing
-    double tPostSpacing = std::max(m_GRID_DIST_X, m_GRID_DIST_Y);
-
-    GDALDataset *mpDstDS;
-    mpDstDS = createFloat32GTIFF(filename, m_GRID_SIZE_X, m_GRID_SIZE_Y);
-
-    // if we have a valid file
-    if (mpDstDS)
-    {
-        // loop over the raster and determine max slope at each location
-        int tXStart = 1, tXEnd = m_GRID_SIZE_X - 1;
-        int tYStart = 1, tYEnd = m_GRID_SIZE_Y - 1;
-        float *poRasterData = new float[m_GRID_SIZE_X*m_GRID_SIZE_Y];
-        for (uint32_t i=0; i<m_GRID_SIZE_X*m_GRID_SIZE_Y; i++)
-        {
-            poRasterData[i] = c_background;
-        }
-
-        #pragma omp parallel for
-        for (int tXOut = tXStart; tXOut < tXEnd; tXOut++)
-        {
-            int tXIn = tXOut;
-            for (int tYOut = tYStart; tYOut < tYEnd; tYOut++)
-            {
-                int tYIn = tYOut;
-
-                float tSlopeValDegree(0);
-
-                //Compute Slope Value
-                switch (method)
-                {
-                    case SLOPE_D8:
-                        tSlopeValDegree = (float)determineSlopeD8(tDemData,
-                                          tYOut, tXOut, tPostSpacing,
-                                          c_background);
-                        break;
-
-                    case SLOPE_FD:
-                        tSlopeValDegree = (double)determineSlopeFD(tDemData,
-                                          tYOut, tXOut, tPostSpacing,
-                                          c_background);
-                        break;
-                    default:
-                        assert(false);
-                        return;
-                }
-
-                poRasterData[(tYIn * m_GRID_SIZE_X) + tXIn] =
-                    std::tan(tSlopeValDegree*c_pi/180.0)*100.0;
-            }
-        }
-
-        // write the data
-        if (poRasterData)
-        {
-            GDALRasterBand *tBand = mpDstDS->GetRasterBand(1);
-
-            tBand->SetNoDataValue((double)c_background);
-
-            if (m_GRID_SIZE_X > 0 && m_GRID_SIZE_Y > 0)
-// #define STR_HELPER(x) #x
-// #define STR(x) STR_HELPER(x)
-//
-// #pragma message "content of GDAL_VERSION_MAJOR:" STR(GDAL_VERSION_MAJOR)
-#if GDAL_VERSION_MAJOR <= 1
-                tBand->RasterIO(GF_Write, 0, 0, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                poRasterData, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                GDT_Float32, 0, 0);
-#else
-
-                int ret = tBand->RasterIO(GF_Write, 0, 0, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                          poRasterData, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                          GDT_Float32, 0, 0, 0);
-#endif
-        }
-
-        GDALClose((GDALDatasetH) mpDstDS);
-
-        delete [] poRasterData;
-    }
-}
-
-
-void DerivativeWriter::writeAspect(Eigen::MatrixXd* tDemData,
-    const PointViewPtr data, PrimitiveType method, const std::string& filename)
-{
-    BOX2D& extent = getBounds();
-
-    // use the max grid size as the post spacing
-    double tPostSpacing = std::max(m_GRID_DIST_X, m_GRID_DIST_Y);
-
-    GDALDataset *mpDstDS;
-    mpDstDS = createFloat32GTIFF(filename, m_GRID_SIZE_X, m_GRID_SIZE_Y);
-
-    // if we have a valid file
-    if (mpDstDS)
-    {
-        // loop over the raster and determine max slope at each location
-        int tXStart = 1, tXEnd = m_GRID_SIZE_X - 1;
-        int tYStart = 1, tYEnd = m_GRID_SIZE_Y - 1;
-        float *poRasterData = new float[m_GRID_SIZE_X*m_GRID_SIZE_Y];
-        for (uint32_t i=0; i<m_GRID_SIZE_X*m_GRID_SIZE_Y; i++)
-        {
-            poRasterData[i] = 0;    // Initialize all elements to zero.
-        }
-
-        #pragma omp parallel for
-        for (int tXOut = tXStart; tXOut < tXEnd; tXOut++)
-        {
-            int tXIn = tXOut;
-            for (int tYOut = tYStart; tYOut < tYEnd; tYOut++)
-            {
-                int tYIn = tYOut;
-
-                float tSlopeValDegree(0);
-
-                //Compute Aspect Value
-                switch (method)
-                {
-                    case ASPECT_D8:
-                        tSlopeValDegree = (float)determineAspectD8(tDemData,
-                                          tYOut, tXOut, tPostSpacing);
-                        break;
-                    case ASPECT_FD:
-                        tSlopeValDegree = (float)determineAspectFD(tDemData,
-                                tYOut, tXOut, tPostSpacing, c_background);
-                        break;
-                    default:
-                        assert(false);
-                        return;
-                }
-
-                if (tSlopeValDegree == std::numeric_limits<double>::max())
-                    poRasterData[(tYIn * m_GRID_SIZE_X) + tXIn] = c_background;
-                else
-                    poRasterData[(tYIn * m_GRID_SIZE_X) + tXIn] =
-                        tSlopeValDegree;
-            }
-        }
-
-        // write the data
-        if (poRasterData)
-        {
-            GDALRasterBand *tBand = mpDstDS->GetRasterBand(1);
-
-            tBand->SetNoDataValue((double)c_background);
-
-            if (m_GRID_SIZE_X > 0 && m_GRID_SIZE_Y > 0)
-#if GDAL_VERSION_MAJOR <= 1
-                tBand->RasterIO(GF_Write, 0, 0, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                poRasterData, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                GDT_Float32, 0, 0);
-#else
-                int ret = tBand->RasterIO(GF_Write, 0, 0, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                          poRasterData, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                          GDT_Float32, 0, 0, 0);
-#endif
-        }
-
-        GDALClose((GDALDatasetH) mpDstDS);
-
-        delete [] poRasterData;
-    }
-}
-
-
-void DerivativeWriter::writeCatchmentArea(Eigen::MatrixXd* tDemData,
-        const PointViewPtr data, const std::string& filename)
-{
-    Eigen::MatrixXd area(m_GRID_SIZE_Y, m_GRID_SIZE_X);
-    area.setZero();
-
-    BOX2D& extent = getBounds();
-
-    // use the max grid size as the post spacing
-    double tPostSpacing = std::max(m_GRID_DIST_X, m_GRID_DIST_Y);
-
-    GDALDataset *mpDstDS;
-    mpDstDS = createFloat32GTIFF(filename, m_GRID_SIZE_X, m_GRID_SIZE_Y);
-
-    // if we have a valid file
-    if (mpDstDS)
-    {
-        // loop over the raster and determine max slope at each location
-        int tXStart = 1, tXEnd = m_GRID_SIZE_X - 1;
-        int tYStart = 1, tYEnd = m_GRID_SIZE_Y - 1;
-        float *poRasterData = new float[m_GRID_SIZE_X*m_GRID_SIZE_Y];
-        for (uint32_t i=0; i<m_GRID_SIZE_X*m_GRID_SIZE_Y; i++)
-        {
-            poRasterData[i] = c_background;    // Initialize all elements to zero.
-        }
-
-
-        int tXOut = tXStart;
-        int tYOut = tYStart;
-        //for (int tXOut = tXStart; tXOut < tXEnd; tXOut++)
-        //{
-        //    for (int tYOut = tYStart; tYOut < tYEnd; tYOut++)
-        //    {
-        //Compute Aspect Value
-        //switch (method)
-        //{
-        //case AD8:
-        //tSlopeValDegree = (float)determineAspectD8(tDemData, tYOut, tXOut, tPostSpacing);
-        //break;
-        //
-        //case SFD:
-        //  tSlopeValDegree = (float)determineAspectFD(tDemData, tYOut, tXOut, tPostSpacing, c_background);
-        //break;
-        //}
-        area(tYOut, tXOut) = determineCatchmentAreaD8(tDemData, &area, tYOut,
-                             tXOut, tPostSpacing);
-        //    }
-        // }
-
-        #pragma omp parallel for
-        for (int tXOut = tXStart; tXOut < tXEnd; tXOut++)
-        {
-            for (int tYOut = tYStart; tYOut < tYEnd; tYOut++)
-            {
-                poRasterData[(tYOut * m_GRID_SIZE_X) + tXOut] = area(tYOut, tXOut);
-            }
-        }
-
-        //stretchData(poRasterData);
-
-        // write the data
-        if (poRasterData)
-        {
-            GDALRasterBand *tBand = mpDstDS->GetRasterBand(1);
-
-            tBand->SetNoDataValue((double)c_background);
-
-            if (m_GRID_SIZE_X > 0 && m_GRID_SIZE_Y > 0)
-#if GDAL_VERSION_MAJOR <= 1
-                tBand->RasterIO(GF_Write, 0, 0, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                poRasterData, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                GDT_Float32, 0, 0);
-#else
-                int ret = tBand->RasterIO(GF_Write, 0, 0, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                          poRasterData, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                          GDT_Float32, 0, 0, 0);
-#endif
-        }
-
-        GDALClose((GDALDatasetH) mpDstDS);
-
-        delete [] poRasterData;
-    }
-}
-
-// void DerivativeWriter::stretchData(float *data)
-// {
-//     unsigned int nvals = 0;
-//
-//     int tXStart = 1, tXEnd = m_GRID_SIZE_X - 1;
-//     int tYStart = 1, tYEnd = m_GRID_SIZE_Y - 1;
-//
-//     // pass #1: compute mean
-//     double mean = 0.0;
-//     for (int tXOut = tXStart; tXOut < tXEnd; tXOut++)
-//     {
-//         for (int tYOut = tYStart; tYOut < tYEnd; tYOut++)
-//         {
-//             float val = data[(tYOut * m_GRID_SIZE_X) + tXOut];
-//             if (val != c_background && !_isnanf(val))
-//             {
-//                 mean += val;
-//                 nvals++;
-//             }
-//         }
-//     }
-//     mean /= nvals;
-//
-//     // pass #2: compute standard deviation
-//     double stdev = 0.0;
-//     for (int tXOut = tXStart; tXOut < tXEnd; tXOut++)
-//     {
-//         for (int tYOut = tYStart; tYOut < tYEnd; tYOut++)
-//         {
-//             float val = data[(tYOut * m_GRID_SIZE_X) + tXOut];
-//             if (val != c_background && !_isnanf(val))
-//             {
-//                 stdev += std::pow(val - mean, 2);
-//                 nvals++;
-//             }
-//         }
-//     }
-//     stdev /= (nvals - 1);
-//     stdev = std::sqrt(stdev);
-//
-//     // pass #3: scale to +/- 2x standard deviations from mean
-//     double min_val = mean - 2*stdev;
-//     double max_val = mean + 2*stdev;
-//     double range = max_val - min_val;
-//     double scale = 256.0 / range;
-//
-//     #pragma omp parallel for
-//
-//     for (int tXOut = tXStart; tXOut < tXEnd; tXOut++)
-//     {
-//         for (int tYOut = tYStart; tYOut < tYEnd; tYOut++)
-//         {
-//             data[(tYOut * m_GRID_SIZE_X) + tXOut] =
-//                 (data[(tYOut * m_GRID_SIZE_X) + tXOut] - min_val) * scale;
-//         }
-//     }
-// }
-
-
-void DerivativeWriter::writeHillshade(Eigen::MatrixXd* tDemData,
-    const PointViewPtr data, const std::string& filename)
-{
-    BOX2D& extent = getBounds();
-
-    // use the max grid size as the post spacing
-    double tPostSpacing = std::max(m_GRID_DIST_X, m_GRID_DIST_Y);
-
-    GDALDataset *mpDstDS;
-    mpDstDS = createFloat32GTIFF(filename, m_GRID_SIZE_X, m_GRID_SIZE_Y);
-
-    // if we have a valid file
-    if (mpDstDS)
-    {
-        // loop over the raster and determine max slope at each location
-        int tXStart = 1, tXEnd = m_GRID_SIZE_X - 1;
-        int tYStart = 1, tYEnd = m_GRID_SIZE_Y - 1;
-        float *poRasterData = new float[m_GRID_SIZE_X*m_GRID_SIZE_Y];
-        for (uint32_t i=0; i<m_GRID_SIZE_X*m_GRID_SIZE_Y; i++)
-        {
-            poRasterData[i] = 0;    // Initialize all elements to zero.
-        }
-
-        // Parameters for hill shade
-        double illumAltitudeDegree = 45.0;
-        double illumAzimuthDegree = 315.0;
-        double tZenithRad = (90 - illumAltitudeDegree) * (c_pi / 180.0);
-        double tAzimuthMath = 360.0 - illumAzimuthDegree + 90;
-
-        if (tAzimuthMath >= 360.0)
-        {
-            tAzimuthMath = tAzimuthMath - 360.0;
-        }
-
-        double tAzimuthRad = tAzimuthMath * (c_pi / 180.0);
-
-        double min_val = std::numeric_limits<double>::max();
-        double max_val = -std::numeric_limits<double>::max();
-
-        #pragma omp parallel for
-        for (int tXOut = tXStart; tXOut < tXEnd; tXOut++)
-        {
-            for (int tYOut = tYStart; tYOut < tYEnd; tYOut++)
-            {
-                //Compute Slope Value
-                float tSlopeValDegree = (float)determineHillshade(tDemData,
-                                        tYOut, tXOut, tZenithRad, tAzimuthRad,
-                                        tPostSpacing);
-
-                if (tSlopeValDegree == std::numeric_limits<double>::max())
-                    poRasterData[(tYOut * m_GRID_SIZE_X) + tXOut] = c_background;
-                else
-                    poRasterData[(tYOut * m_GRID_SIZE_X) + tXOut] = tSlopeValDegree;
-
-                if (tSlopeValDegree < min_val) min_val = tSlopeValDegree;
-                if (tSlopeValDegree > max_val) max_val = tSlopeValDegree;
-            }
-        }
-
-        // stretchData(poRasterData);
-
-        // write the data
-        if (poRasterData)
-        {
-            GDALRasterBand *tBand = mpDstDS->GetRasterBand(1);
-
-            tBand->SetNoDataValue((double)c_background);
-
-            if (m_GRID_SIZE_X > 0 && m_GRID_SIZE_Y > 0)
-#if GDAL_VERSION_MAJOR <= 1
-                tBand->RasterIO(GF_Write, 0, 0, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                poRasterData, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                GDT_Float32, 0, 0);
-#else
-
-                int ret = tBand->RasterIO(GF_Write, 0, 0, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                          poRasterData, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                                          GDT_Float32, 0, 0, 0);
-#endif
-        }
-
-        GDALClose((GDALDatasetH) mpDstDS);
-
-        delete [] poRasterData;
-    }
-}
-
-
-void DerivativeWriter::writeCurvature(Eigen::MatrixXd* tDemData,
-    const PointViewPtr data, PrimitiveType curveType, double valueToIgnore,
-    const std::string& filename)
-{
-    BOX2D& extent = getBounds();
-
-    // use the max grid size as the post spacing
-    double tPostSpacing = std::max(m_GRID_DIST_X, m_GRID_DIST_Y);
-
-    GDALDataset *mpDstDS;
-    mpDstDS = createFloat32GTIFF(filename, m_GRID_SIZE_X, m_GRID_SIZE_Y);
-
-    // if we have a valid file
-    if (!mpDstDS)
-        return;
-
-    // loop over the raster and determine max slope at each location
-    int tXStart = 1, tXEnd = m_GRID_SIZE_X - 1;
-    int tYStart = 1, tYEnd = m_GRID_SIZE_Y - 1;
-    std::vector<float> poRasterData(m_GRID_SIZE_X * m_GRID_SIZE_Y);
-    for (uint32_t i=0; i<m_GRID_SIZE_X*m_GRID_SIZE_Y; i++)
-    {
-        poRasterData[i] = c_background;
-    }
-
-#pragma omp parallel for
-    for (int tXOut = tXStart; tXOut < tXEnd; tXOut++)
-    {
-        int tXIn = tXOut;
-        for (int tYOut = tYStart; tYOut < tYEnd; tYOut++)
-        {
-            int tYIn = tYOut;
-
-            double curve(0);
-
-            //Compute Slope Value
-            switch (curveType)
-            {
-                case CONTOUR_CURVATURE:
-                    curve = determineContourCurvature(tDemData,
-                            tYOut, tXOut,
-                            tPostSpacing,
-                            c_background);
-                    break;
-
-                case PROFILE_CURVATURE:
-                    curve = determineProfileCurvature(tDemData,
-                            tYOut, tXOut,
-                            tPostSpacing,
-                            c_background);
-                    break;
-
-                case TANGENTIAL_CURVATURE:
-                    curve = determineTangentialCurvature(tDemData,
-                            tYOut, tXOut,
-                            tPostSpacing,
-                            c_background);
-                    break;
-
-                case TOTAL_CURVATURE:
-                    curve = determineTotalCurvature(tDemData,
-                            tYOut, tXOut,
-                            tPostSpacing,
-                            c_background);
-                    break;
-                default:
-                    assert(false);
-                    return;
-            }
-
-            poRasterData[(tYOut * m_GRID_SIZE_X) + tXOut] =
-                static_cast<float>(curve);
-        }
-    }
-
-    //stretchData(poRasterData);
-
-    // write the data
-    GDALRasterBand *tBand = mpDstDS->GetRasterBand(1);
-
-    tBand->SetNoDataValue((double)c_background);
-
-    if (m_GRID_SIZE_X > 0 && m_GRID_SIZE_Y > 0)
-    {
-        int ret;
-#if GDAL_VERSION_MAJOR <= 1
-        ret = tBand->RasterIO(GF_Write, 0, 0, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                poRasterData.data(), m_GRID_SIZE_X, m_GRID_SIZE_Y,
-                GDT_Float32, 0, 0);
-#else
-        ret = tBand->RasterIO(GF_Write, 0, 0, m_GRID_SIZE_X, m_GRID_SIZE_Y,
-            poRasterData.data(), m_GRID_SIZE_X, m_GRID_SIZE_Y,
-            GDT_Float32, 0, 0, 0);
-#endif
-        if (ret != CE_None)
-        {
-            std::ostringstream oss;
-
-            oss << getName() << ": Error reading raster IO.";
-            throw pdal_error(oss.str());
-        }
-    }
-    GDALClose((GDALDatasetH) mpDstDS);
-}
-
-
-void DerivativeWriter::write(const PointViewPtr data)
-{
-    m_inSRS = data->spatialReference();
-    data->calculateBounds(m_bounds);
-
-    // calculate grid based off bounds and post spacing
-    calculateGridSizes();
-    log()->get(LogLevel::Debug2) << "X grid size: " <<
-        m_GRID_SIZE_X << std::endl;
-    log()->get(LogLevel::Debug2) << "Y grid size: " <<
-        m_GRID_SIZE_Y << std::endl;
-
-    log()->floatPrecision(6);
-    log()->get(LogLevel::Debug2) << "X grid distance: " <<
-        m_GRID_DIST_X << std::endl;
-    log()->get(LogLevel::Debug2) << "Y grid distance: " <<
-        m_GRID_DIST_Y << std::endl;
-    log()->clearFloat();
-
-    BOX2D& extent = getBounds();
-    double yMax = extent.miny + m_GRID_SIZE_Y * m_GRID_DIST_Y;
-    log()->get(LogLevel::Debug4) << yMax << ", " << extent.maxy << std::endl;
-
-    // need to create the min DEM
-    Eigen::MatrixXd tDemData(m_GRID_SIZE_Y, m_GRID_SIZE_X);
-    tDemData.setConstant(c_background);
-    for (PointId idx = 0; idx < data->size(); ++idx)
-    {
-        double x = data->getFieldAs<double>(Dimension::Id::X, idx);
-        double y = data->getFieldAs<double>(Dimension::Id::Y, idx);
-        double z = data->getFieldAs<double>(Dimension::Id::Z, idx);
-
-        auto clamp = [](double t, double min, double max)
-        {
-            return ((t < min) ? min : ((t > max) ? max : t));
-        };
-
-        int xIndex = clamp(static_cast<int>(floor((x - extent.minx) / m_GRID_DIST_X)), 0, m_GRID_SIZE_X-1);
-        int yIndex = clamp(static_cast<int>(floor((yMax - y) / m_GRID_DIST_Y)), 0, m_GRID_SIZE_Y-1);
-
-        double tDemValue = tDemData(yIndex, xIndex);
-
-        if (tDemValue == c_background)
-        {
-            tDemData(yIndex, xIndex) = z;
-        }
-        else
-        {
-            if (z > tDemValue)
-                tDemData(yIndex, xIndex) = z;
-        }
-    }
-
-    auto CleanRasterScanLine = [](Eigen::MatrixXd data, Eigen::VectorXd datarow,
-                                  int mDim, int row, bool* prevSetCols,
-                                  bool* curSetCols)
-    {
-
-        auto InterpolateRasterPixelScanLine = [](Eigen::MatrixXd data, int mDim,
-                                              int x, int y, bool* prevSetCols)
-        {
-            int yMinus, yPlus, xMinus, xPlus;
-            float tInterpValue;
-            bool tPrevInterp;
-
-            yMinus = y - 1;
-            yPlus = y + 1;
-            xMinus = x - 1;
-            xPlus = x + 1;
-
-            //North
-            tInterpValue = data(yMinus, x);
-            tPrevInterp = prevSetCols[x];
-            if (tInterpValue != c_background && tPrevInterp != true)
-                return tInterpValue;
-
-            //South
-            tInterpValue = data(yPlus, x);
-            if (tInterpValue != c_background)
-                return tInterpValue;
-
-            //East
-            tInterpValue = data(y, xPlus);
-            if (tInterpValue != c_background)
-                return tInterpValue;
-
-            //West
-            tInterpValue = data(y, xMinus);
-            if (tInterpValue != c_background)
-                return tInterpValue;
-
-            //NorthWest
-            tInterpValue = data(yMinus, xMinus);
-            tPrevInterp = prevSetCols[xMinus];
-            if (tInterpValue != c_background && tPrevInterp != true)
-                return tInterpValue;
-
-            //NorthWest
-            tInterpValue = data(yMinus, xPlus);
-            tPrevInterp = prevSetCols[xPlus];
-            if (tInterpValue != c_background && tPrevInterp != true)
-                return tInterpValue;
-
-            //SouthWest
-            tInterpValue = data(yPlus, xMinus);
-            if (tInterpValue != c_background)
-                return tInterpValue;
-
-            //SouthEast
-            tInterpValue = data(yPlus, xPlus);
-            if (tInterpValue != c_background)
-                return tInterpValue;
-
-            return 0.0f;
-        };
-
-        float tInterpValue;
-        float tValue;
-
-        int y = row;
-        for (int x = 1; x < mDim-1; ++x)
-        {
-            tValue = datarow(x);
-
-            if (tValue == c_background)
-            {
-                tInterpValue = InterpolateRasterPixelScanLine(data, mDim, x, y,
-                               prevSetCols);
-                if (tInterpValue != c_background)
-                {
-                    curSetCols[x] = true;
-                    datarow(x) = tInterpValue;
-                }
-            }
-        }
-    };
-
-    {
-        std::unique_ptr<bool> prevSetCols(new bool[m_GRID_SIZE_X]);
-        std::unique_ptr<bool> curSetCols(new bool[m_GRID_SIZE_X]);
-
-        for (uint32_t y = 1; y < m_GRID_SIZE_Y-1; ++y)
-        {
-            CleanRasterScanLine(tDemData, tDemData.row(1), m_GRID_SIZE_X, y,
-                prevSetCols.get(), curSetCols.get());
-            memcpy(prevSetCols.get(), curSetCols.get(), m_GRID_SIZE_X);
-            memset(curSetCols.get(), 0, m_GRID_SIZE_X);
-        }
-    }
-
-    for (TypeOutput& to : m_primitiveTypes)
-    {
-        switch (to.m_type)
-        {
-        case SLOPE_D8:
-        case SLOPE_FD:
-            writeSlope(&tDemData, data, to.m_type, to.m_filename);
-            break;
-        case ASPECT_D8:
-        case ASPECT_FD:
-            writeAspect(&tDemData, data, to.m_type, to.m_filename);
-            break;
-        case HILLSHADE:
-            writeHillshade(&tDemData, data, to.m_filename);
-            break;
-        case CONTOUR_CURVATURE:
-        case PROFILE_CURVATURE:
-        case TANGENTIAL_CURVATURE:
-        case TOTAL_CURVATURE:
-            writeCurvature(&tDemData, data, to.m_type, c_background,
-                to.m_filename);
-            break;
-        case CATCHMENT_AREA:
-            writeCatchmentArea(&tDemData, data, to.m_filename);
-            break;
-        }
-    }
-}
-
-void DerivativeWriter::calculateGridSizes()
-{
-    BOX2D& extent = getBounds();
-
-    m_GRID_SIZE_X = (int)(ceil((extent.maxx - extent.minx)/m_GRID_DIST_X)) + 1;
-    m_GRID_SIZE_Y = (int)(ceil((extent.maxy - extent.miny)/m_GRID_DIST_Y)) + 1;
-}
-
-} // namespace pdal
diff --git a/io/derivative/DerivativeWriter.hpp b/io/derivative/DerivativeWriter.hpp
deleted file mode 100644
index 4ad8f99..0000000
--- a/io/derivative/DerivativeWriter.hpp
+++ /dev/null
@@ -1,163 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers, brad.chambers at gmail.com
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Writer.hpp>
-#include <pdal/plugin.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <Eigen/Core>
-
-#include <string>
-
-#include "gdal_priv.h" // For File I/O
-
-extern "C" int32_t DerivativeWriter_ExitFunc();
-extern "C" PF_ExitFunc DerivativeWriter_InitPlugin();
-
-namespace pdal
-{
-
-class BOX2D;
-
-class PDAL_DLL DerivativeWriter : public Writer
-{
-    enum PrimitiveType
-    {
-        SLOPE_D8,
-        SLOPE_FD,
-        ASPECT_D8,
-        ASPECT_FD,
-        HILLSHADE,
-        CONTOUR_CURVATURE,
-        PROFILE_CURVATURE,
-        TANGENTIAL_CURVATURE,
-        TOTAL_CURVATURE,
-        CATCHMENT_AREA
-    };
-
-    enum Direction
-    {
-        NORTH,
-        SOUTH,
-        EAST,
-        WEST,
-        NORTHEAST,
-        NORTHWEST,
-        SOUTHEAST,
-        SOUTHWEST
-    };
-
-    struct TypeOutput
-    {
-        PrimitiveType m_type;
-        std::string m_filename;
-    };
-
-public:
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-    DerivativeWriter();
-
-private:
-    virtual void addArgs(ProgramArgs& args);
-    virtual void initialize();
-    virtual void write(const PointViewPtr view);
-
-    void setBounds(const BOX2D& v)
-    {
-        m_bounds = v;
-    }
-
-    BOX2D& getBounds()
-    {
-        return m_bounds;
-    }
-
-    std::string generateFilename(const std::string& primName) const;
-    void calculateGridSizes();
-    double determineSlopeFD(Eigen::MatrixXd* data, int row, int col,
-                            double postSpacing, double valueToIgnore);
-    double determineSlopeD8(Eigen::MatrixXd* data, int row, int col,
-                            double postSpacing, double valueToIgnore);
-    double determineAspectFD(Eigen::MatrixXd* data, int row, int col,
-                             double postSpacing, double valueToIgnore);
-    double determineAspectD8(Eigen::MatrixXd* data, int row, int col,
-                             double postSpacing);
-    int determineCatchmentAreaD8(Eigen::MatrixXd* data, Eigen::MatrixXd* area,
-                                 int row, int col, double postSpacing);
-    double determineContourCurvature(Eigen::MatrixXd* data, int row, int col,
-                                     double postSpacing, double valueToIgnore);
-    double determineProfileCurvature(Eigen::MatrixXd* data, int row, int col,
-                                     double postSpacing, double valueToIgnore);
-    double determineTangentialCurvature(Eigen::MatrixXd* data, int row, int col,
-                                        double postSpacing, double valueToIgnore);
-    double determineTotalCurvature(Eigen::MatrixXd* data, int row, int col,
-                                   double postSpacing, double valueToIgnore);
-    double determineHillshade(Eigen::MatrixXd* data, int row, int col,
-                              double zenithRad, double azimuthRad,
-                              double postSpacing);
-    double GetNeighbor(Eigen::MatrixXd* data, int row, int col, Direction d);
-    void writeSlope(Eigen::MatrixXd* dem, const PointViewPtr cloud,
-        PrimitiveType method, const std::string& filename);
-    void writeAspect(Eigen::MatrixXd* dem, const PointViewPtr cloud,
-        PrimitiveType method, const std::string& filename);
-    void writeCatchmentArea(Eigen::MatrixXd* dem, const PointViewPtr cloud,
-        const std::string& filename);
-    void writeHillshade(Eigen::MatrixXd* dem, const PointViewPtr cloud,
-        const std::string& filename);
-    void writeCurvature(Eigen::MatrixXd* dem, const PointViewPtr cloud,
-        PrimitiveType curveType, double valueToIgnore,
-        const std::string& filename);
-    GDALDataset* createFloat32GTIFF(std::string filename, int cols, int rows);
-    void stretchData(float *data);
-
-    uint64_t m_pointCount;
-    uint32_t m_GRID_SIZE_X;
-    uint32_t m_GRID_SIZE_Y;
-    double m_GRID_DIST_X;
-    double m_GRID_DIST_Y;
-    StringList m_primTypesSpec;
-    std::vector<TypeOutput> m_primitiveTypes;
-    BOX2D m_bounds;
-    SpatialReference m_inSRS;
-
-    DerivativeWriter& operator=(const DerivativeWriter&); // not implemented
-    DerivativeWriter(const DerivativeWriter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/io/faux/CMakeLists.txt b/io/faux/CMakeLists.txt
deleted file mode 100644
index 61b4a95..0000000
--- a/io/faux/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Faux driver CMake configuration
-#
-
-#
-# Faux Reader
-#
-set(srcs
-    FauxReader.cpp
-)
-
-set(incs
-    FauxReader.hpp
-)
-
-PDAL_ADD_DRIVER(reader faux "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/io/gdal/CMakeLists.txt b/io/gdal/CMakeLists.txt
deleted file mode 100644
index 546f0c0..0000000
--- a/io/gdal/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-set(src
-    GDALReader.cpp
-)
-
-set(inc
-    GDALReader.hpp
-)
-
-PDAL_ADD_DRIVER(reader gdal "${src}" "${inc}" objs)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objs} PARENT_SCOPE)
diff --git a/io/gdal/GDALReader.cpp b/io/gdal/GDALReader.cpp
deleted file mode 100644
index ebe915c..0000000
--- a/io/gdal/GDALReader.cpp
+++ /dev/null
@@ -1,162 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler <howard at hobu.co>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "GDALReader.hpp"
-
-#include <sstream>
-
-#include <pdal/GDALUtils.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-        "readers.gdal",
-        "Read GDAL rasters as point clouds.",
-        "http://pdal.io/stages/reader.gdal.html");
-
-
-CREATE_STATIC_PLUGIN(1, 0, GDALReader, Reader, s_info);
-
-
-std::string GDALReader::getName() const
-{
-    return s_info.name;
-}
-
-
-GDALReader::GDALReader()
-    : m_index(0)
-{}
-
-
-void GDALReader::initialize()
-{
-    gdal::registerDrivers();
-    m_raster.reset(new gdal::Raster(m_filename));
-
-    m_raster->open();
-    setSpatialReference(m_raster->getSpatialRef());
-    m_count = m_raster->m_raster_x_size * m_raster->m_raster_y_size;
-    m_raster->close();
-}
-
-
-QuickInfo GDALReader::inspect()
-{
-    QuickInfo qi;
-    std::unique_ptr<PointLayout> layout(new PointLayout());
-
-    addDimensions(layout.get());
-    initialize();
-
-    m_raster = std::unique_ptr<gdal::Raster>(new gdal::Raster(m_filename));
-    m_raster->open();
-
-    qi.m_pointCount = m_raster->m_raster_x_size * m_raster->m_raster_y_size;
-    // qi.m_bounds = ???;
-    qi.m_srs = m_raster->getSpatialRef();
-    qi.m_valid = true;
-
-    return qi;
-}
-
-
-void GDALReader::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(pdal::Dimension::Id::X);
-    layout->registerDim(pdal::Dimension::Id::Y);
-    for (int i = 0; i < m_raster->m_band_count; ++i)
-    {
-        std::ostringstream oss;
-        oss << "band-" << (i + 1);
-        layout->registerOrAssignDim(oss.str(), Dimension::Type::Double);
-    }
-}
-
-
-void GDALReader::ready(PointTableRef table)
-{
-    m_index = 0;
-    m_raster->open();
-}
-
-
-point_count_t GDALReader::read(PointViewPtr view, point_count_t num)
-{
-    point_count_t count = std::min(num, m_count - m_index);
-    PointId nextId = view->size();
-
-    std::array<double, 2> coords;
-    for (int row = 0; row < m_raster->m_raster_y_size; ++row)
-    {
-        for (int col = 0; col < m_raster->m_raster_x_size; ++col)
-        {
-            m_raster->pixelToCoord(col, row, coords);
-            view->setField(Dimension::Id::X, nextId, coords[0]);
-            view->setField(Dimension::Id::Y, nextId, coords[1]);
-            nextId++;
-        }
-    }
-
-    std::vector<uint8_t> band;
-    std::vector<Dimension::Type> band_types =
-        m_raster->getPDALDimensionTypes();
-
-    for (int b = 0; b < m_raster->m_band_count; ++b)
-    {
-        // Bands count from 1
-        m_raster->readBand(band, b + 1);
-        std::stringstream oss;
-        oss << "band-" << (b + 1);
-        log()->get(LogLevel::Info) << "Read band '" << oss.str() << "'" <<
-            std::endl;
-
-        Dimension::Id d = view->layout()->findDim(oss.str());
-        size_t dimSize = Dimension::size(band_types[b]);
-        uint8_t* p = band.data();
-        for (point_count_t i = 0; i < count; ++i)
-        {
-            view->setField(d, band_types[b], i, p);
-            p = p + dimSize;
-        }
-    }
-
-    return view->size();
-}
-
-} // namespace pdal
-
diff --git a/io/gdal/GDALReader.hpp b/io/gdal/GDALReader.hpp
deleted file mode 100644
index 639fa00..0000000
--- a/io/gdal/GDALReader.hpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler <howard at hobu.co>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <string>
-
-
-#include <pdal/Dimension.hpp>
-#include <pdal/Reader.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/GDALUtils.hpp>
-#include <pdal/plugin.hpp>
-
-extern "C" int32_t GDALReader_ExitFunc();
-extern "C" PF_ExitFunc GDALReader_InitPlugin();
-
-
-namespace pdal
-{
-
-
-typedef std::map<std::string, Dimension::Id> DimensionMap;
-
-
-
-class PDAL_DLL GDALReader : public Reader
-{
-public:
-    static void *create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-    GDALReader();
-
-    static Dimension::IdList getDefaultDimensions();
-
-private:
-    virtual void initialize();
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual void ready(PointTableRef table);
-    virtual point_count_t read(PointViewPtr view, point_count_t num);
-    virtual void done(PointTableRef table)
-        { m_raster->close(); }
-    virtual QuickInfo inspect();
-
-    std::unique_ptr<gdal::Raster> m_raster;
-    point_count_t m_index;
-
-};
-}
-
diff --git a/io/ilvis2/CMakeLists.txt b/io/ilvis2/CMakeLists.txt
deleted file mode 100644
index f343808..0000000
--- a/io/ilvis2/CMakeLists.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# ILVIS2 driver CMake configuration
-#
-
-set(objs "")
-
-#
-# ILVIS2 Reader
-#
-set(srcs
-    Ilvis2Reader.cpp
-)
-
-set(incs
-    Ilvis2Reader.hpp
-)
-
-if (PDAL_HAVE_LIBXML2)
-    list(APPEND srcs Ilvis2MetadataReader.cpp)
-    list(APPEND incs Ilvis2MetadataReader.hpp)
-endif()
-
-PDAL_ADD_DRIVER(reader ilvis2 "${srcs}" "${incs}" reader_objs)
-set(objs ${objs} ${reader_objs})
-
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objs} PARENT_SCOPE)
diff --git a/io/ilvis2/Ilvis2MetadataReader.cpp b/io/ilvis2/Ilvis2MetadataReader.cpp
deleted file mode 100644
index e4fe7b7..0000000
--- a/io/ilvis2/Ilvis2MetadataReader.cpp
+++ /dev/null
@@ -1,688 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "Ilvis2MetadataReader.hpp"
-
-namespace pdal
-{
-
-void Ilvis2MetadataReader::readMetadataFile(std::string filename, MetadataNode* m)
-{
-    xmlDocPtr doc;
-    xmlNodePtr node;
-
-    doc = xmlReadFile(filename.c_str(), NULL, 0);
-    if (doc == NULL)
-    {
-        return;
-    }
-
-    node = xmlDocGetRootElement(doc);
-
-    parseGranuleMetaDataFile(node, m);
-
-    xmlCleanupParser();
-    xmlMemoryDump();
-}
-
-
-void Ilvis2MetadataReader::parseGranuleMetaDataFile(xmlNodePtr node, MetadataNode* m)
-{
-    assertElementIs(node, "GranuleMetaDataFile");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "DTDVersion");
-    m->add<float>("DTDVersion", extractDouble(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "DataCenterId");
-    m->add("DataCenterID", extractString(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "GranuleURMetaData");
-    parseGranuleURMetaData(child, m);
-
-    child = getNextElementNode(child);
-    assertEndOfElements(child);
-}
-
-void Ilvis2MetadataReader::parseGranuleURMetaData(xmlNodePtr node, MetadataNode* m)
-{
-    assertElementIs(node, "GranuleURMetaData");
-
-    xmlNodePtr child, subchild;
-
-    child = getFirstChildElementNode(node);
-    assertElementIs(child, "GranuleUR");
-    m->add("GranuleUR", extractString(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "DbID");
-    m->add<long>("DbID", extractLong(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "InsertTime");
-    m->add("InsertTime", extractString(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "LastUpdate");
-    m->add("LastUpdate", extractString(child));
-
-    child = getNextElementNode(child);
-    if (nodeElementIs(child, "CollectionMetaData"))
-    {
-        parseCollectionMetaData(child, m);
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "DataFiles"))
-    {
-        parseDataFiles(child, m);
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "ECSDataGranule"))
-    {
-        parseECSDataGranule(child, m);
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "RangeDateTime"))
-    {
-        parseRangeDateTime(child, m);
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "SpatialDomainContainer"))
-    {
-        parseSpatialDomainContainer(child, m);
-        child = getNextElementNode(child);
-    }
-
-    while (nodeElementIs(child, "Platform"))
-    {
-        MetadataNode plat = m->addList("Platform");
-        parsePlatform(child, &plat);
-        child = getNextElementNode(child);
-    }
-
-    while (nodeElementIs(child, "Campaign"))
-    {
-        parseCampaign(child, m);
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "PSAs"))
-    {
-        parsePSAs(child, m);
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "BrowseProduct"))
-    {
-        parseXXProduct("Browse", child, m);
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "PHProduct"))
-    {
-        parseXXProduct("PH", child, m);
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "QAProduct"))
-    {
-        parseXXProduct("QA", child, m);
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "MPProduct"))
-    {
-        parseXXProduct("MP", child, m);
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseCollectionMetaData(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "CollectionMetaData");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "ShortName");
-    m->add("CollectionShortName", extractString(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "VersionID");
-    m->add("CollectionVersionID", extractInt(child));
-
-    child = getNextElementNode(child);
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseDataFiles(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "DataFiles");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "DataFileContainer");
-
-    while(nodeElementIs(child, "DataFileContainer"))
-    {
-        MetadataNode n = m->addList("DataFile");
-        parseDataFileContainer(child, &n);
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseDataFileContainer(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "DataFileContainer");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "DistributedFileName");
-    m->add("DistributedFileName", extractString(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "FileSize");
-    m->add("FileSize", extractInt(child));
-
-    child = getNextElementNode(child);
-    if (nodeElementIs(child, "ChecksumType"))
-    {
-        m->add("ChecksumType", extractString(child));
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "Checksum"))
-    {
-        m->add("Checksum", extractString(child));
-        child = getNextElementNode(child);
-    }
-
-    if (nodeElementIs(child, "ChecksumOrigin"))
-    {
-        m->add("ChecksumOrigin", extractString(child));
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseECSDataGranule(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "ECSDataGranule");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    if (nodeElementIs(child, "SizeMBECSDataGranule"))
-    {
-        m->add("SizeMBECSDataGranule", extractDouble(child));
-        child = getNextElementNode(child);
-    }
-
-    assertElementIs(child, "LocalGranuleID");
-    m->add("LocalGranuleID", extractString(child));
-
-    child = getNextElementNode(child);
-    if (nodeElementIs(child, "ProductionDateTime"))
-    {
-        m->add("ProductionDateTime", extractString(child));
-        child = getNextElementNode(child);
-    }
-
-    assertElementIs(child, "LocalVersionID");
-    m->add("LocalVersionID", extractString(child));
-
-    child = getNextElementNode(child);
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseRangeDateTime(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "RangeDateTime");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "RangeEndingTime");
-    m->add("RangeEndingTime", extractString(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "RangeEndingDate");
-    m->add("RangeEndingDate", extractString(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "RangeBeginningTime");
-    m->add("RangeBeginningTime", extractString(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "RangeBeginningDate");
-    m->add("RangeBeginningDate", extractString(child));
-
-    child = getNextElementNode(child);
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseSpatialDomainContainer(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "SpatialDomainContainer");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    if (nodeElementIs(child, "HorizontalSpatialDomainContainer"))
-    {
-        xmlNodePtr subChild = getFirstChildElementNode(child);
-        assertElementIs(subChild, "GPolygon");
-        parseGPolygon(subChild, m);
-
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseGPolygon(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "GPolygon");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "Boundary");
-
-    // The number of boundaries is essentially the number of sub-polygons
-    int numBoundaries = countChildElements(node, "Boundary");
-    std::vector<GEOSGeom> poly(numBoundaries); // size shall never change
-    GEOSGeom fullPoly;
-    int polyNum = 0;
-
-    initGEOS(NULL, NULL);
-
-    while (nodeElementIs(child, "Boundary"))
-    {
-        // There must be at least 3 points to be valid per the schema.
-        int numPoints = countChildElements(child, "Point");
-        if (numPoints < 3)
-        {
-            std::ostringstream oss;
-            oss << "Found a polygon boundary with less than 3 points, " <<
-                "invalid for this schema";
-            throw pdal_error(oss.str());
-        }
-
-        GEOSCoordSeq points = GEOSCoordSeq_create(numPoints + 1, 2);
-        xmlNodePtr bdChild = getFirstChildElementNode(child);
-        int ptNum = 0;
-
-        while (nodeElementIs(bdChild, "Point"))
-        {
-            xmlNodePtr ptChild = getFirstChildElementNode(bdChild);
-            assertElementIs(ptChild, "PointLongitude");
-            double ptLon = extractDouble(ptChild);
-
-            ptChild = getNextElementNode(ptChild);
-            assertElementIs(ptChild, "PointLatitude");
-            double ptLat = extractDouble(ptChild);
-
-            ptChild = getNextElementNode(ptChild);
-            assertEndOfElements(ptChild);
-
-            GEOSCoordSeq_setX(points, ptNum, ptLon);
-            GEOSCoordSeq_setY(points, ptNum, ptLat);
-
-            // In the file, the loop is not closed; GEOS requires polygons
-            // to be closed, so we'll do it ourselves.
-            if (ptNum == 0)
-            {
-                GEOSCoordSeq_setX(points, numPoints, ptLon);
-                GEOSCoordSeq_setY(points, numPoints, ptLat);
-            }
-
-            ptNum += 1;
-            bdChild = getNextElementNode(bdChild);
-        }
-
-        GEOSGeom ring = GEOSGeom_createLinearRing(points);
-        poly[polyNum] = GEOSGeom_createPolygon(ring, NULL, 0);
-
-        polyNum += 1;
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-
-    // If only one sub-polygon, just make a POLYGON WKT, else make it a MULTIPOLYGON
-    if (numBoundaries > 1)
-    {
-        fullPoly = GEOSGeom_createCollection(
-                GEOS_MULTIPOLYGON, poly.data(), numBoundaries);
-    }
-    else
-    {
-        fullPoly = poly[0];
-    }
-    GEOSWKTWriter * writer = GEOSWKTWriter_create();
-    GEOSWKTWriter_setRoundingPrecision(writer, 5);
-
-    std::string polyStr = GEOSWKTWriter_write(writer, fullPoly);
-
-    m->add("ConvexHull", polyStr);
-
-    finishGEOS();
-}
-
-
-void Ilvis2MetadataReader::parsePlatform(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "Platform");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "PlatformShortName");
-
-    m->add("PlatformShortName", extractString(child));
-
-    child = getNextElementNode(child);
-    while(nodeElementIs(child, "Instrument"))
-    {
-        MetadataNode inst = m->addList("Instrument");
-        parseInstrument(child, &inst);
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseInstrument(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "Instrument");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "InstrumentShortName");
-    m->add("InstrumentShortName", extractString(child));
-
-    child = getNextElementNode(child);
-
-    while (nodeElementIs(child, "Sensor"))
-    {
-        MetadataNode sens = m->addList("Sensor");
-        parseSensor(child, &sens);
-        child = getNextElementNode(child);
-    }
-
-    while (nodeElementIs(child, "OperationMode"))
-    {
-        m->addList("OperationMode", extractString(child));
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseSensor(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "Sensor");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "SensorShortName");
-    m->add("SensorShortName", extractString(child));
-
-    child = getNextElementNode(child);
-    while (nodeElementIs(child, "SensorCharacteristic"))
-    {
-        MetadataNode n = m->addList("SensorCharacteristic");
-        parseSensorCharacteristic(child, &n);
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseSensorCharacteristic(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "SensorCharacteristic");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "SensorCharacteristicName");
-    m->add("CharacteristicName", extractString(child));
-
-    child = getNextElementNode(child);
-    assertElementIs(child, "SensorCharacteristicValue");
-    m->add("CharacteristicValue", extractString(child));
-
-    child = getNextElementNode(child);
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parseCampaign(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "Campaign");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "CampaignShortName");
-    std::string cName = extractString(child);
-    m->addList("Campaign", cName);
-
-    child = getNextElementNode(child);
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parsePSAs(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "PSAs");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    while (nodeElementIs(child, "PSA"))
-    {
-        MetadataNode n = m->addList("PSA");
-        parsePSA(child, &n);
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-}
-
-
-void Ilvis2MetadataReader::parsePSA(xmlNodePtr node, MetadataNode * m)
-{
-    assertElementIs(node, "PSA");
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    assertElementIs(child, "PSAName");
-    m->add("PSAName", extractString(child));
-
-    child = getNextElementNode(child);
-    while (nodeElementIs(child, "PSAValue"))
-    {
-        m->addList("PSAValue", extractString(child));
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-}
-
-
-// Since the Browse, PH, QA, and MP product nodes have the same structure
-// just differing prefixes, they can share this code.
-void Ilvis2MetadataReader::parseXXProduct(std::string type, xmlNodePtr node, MetadataNode * m)
-{
-    std::string fullBase = type + "Product";
-    std::string fullSub = type + "GranuleId";
-    std::string mdName = type + "ProductGranuleId";
-
-    assertElementIs(node, fullBase);
-
-    xmlNodePtr child = getFirstChildElementNode(node);
-    while (nodeElementIs(child, fullSub))
-    {
-        m->addList(mdName, extractString(child));
-        child = getNextElementNode(child);
-    }
-
-    assertEndOfElements(child);
-}
-
-
-// BEGIN PRIVATE METHODS
-
-
-std::string Ilvis2MetadataReader::extractString(xmlNodePtr node)
-{
-    std::string nodeStr((char*)node->children->content);
-    return nodeStr;
-}
-
-double Ilvis2MetadataReader::extractDouble(xmlNodePtr node)
-{
-    return atof((char*)node->children->content);
-}
-
-int Ilvis2MetadataReader::extractInt(xmlNodePtr node)
-{
-    return atoi((char*)node->children->content);
-}
-
-long Ilvis2MetadataReader::extractLong(xmlNodePtr node)
-{
-    return atol((char*)node->children->content);
-}
-
-
-// private
-
-// Skip all non-element nodes, just get the next element node.
-xmlNodePtr Ilvis2MetadataReader::getNextElementNode(xmlNodePtr node)
-{
-    node = node->next;
-    while (node && node->type != XML_ELEMENT_NODE)
-    {
-        node = node->next;
-    }
-
-    return node;
-}
-
-// Skip all non-element child nodes, get the first element child node
-xmlNodePtr Ilvis2MetadataReader::getFirstChildElementNode(xmlNodePtr node)
-{
-    xmlNodePtr child = node->children;
-    if (!child)
-    {
-        return NULL;
-    }
-    else if (child->type == XML_ELEMENT_NODE)
-    {
-        return child;
-    }
-    else
-    {
-        return getNextElementNode(child);
-    }
-}
-
-// Verifies the name of the node matches what's expected
-bool Ilvis2MetadataReader::nodeElementIs(xmlNodePtr node, std::string expected)
-{
-    if (!node)
-    {
-        return false;
-    }
-
-    return xmlStrcmp(node->name,
-            reinterpret_cast<const xmlChar*>(expected.c_str())) == 0;
-}
-
-// Throws an error if the next element is not what it expects
-void Ilvis2MetadataReader::assertElementIs(xmlNodePtr node, std::string expected)
-{
-    if (!node || !nodeElementIs(node, expected))
-    {
-        errWrongElement(node, expected);
-    }
-}
-
-// Throws an error if the node is not null
-void Ilvis2MetadataReader::assertEndOfElements(xmlNodePtr node)
-{
-    if (node)
-    {
-        errExpectedEnd(node);
-    }
-}
-
-// Counts the number of child element nodes with a given name
-int Ilvis2MetadataReader::countChildElements(xmlNodePtr node, std::string childName)
-{
-    xmlNodePtr child = getFirstChildElementNode(node);
-    int ctr = 0;
-
-    while (child)
-    {
-        if (nodeElementIs(child, childName))
-        {
-            ctr += 1;
-        }
-        child = getNextElementNode(child);
-    }
-
-    return ctr;
-}
-
-
-// Errors used when a file doesn't match the schema.
-void Ilvis2MetadataReader::errWrongElement(xmlNodePtr node, std::string expected)
-{
-    std::ostringstream oss;
-    oss << "Expected element '" << expected << "', found '" << node->name << "'";
-    throw pdal_error(oss.str());
-}
-
-void Ilvis2MetadataReader::errExpectedEnd(xmlNodePtr node)
-{
-    std::ostringstream oss;
-    oss << "Expected to find no more elements, found '" << node->name << "'";
-    throw pdal_error(oss.str());
-}
-
-} // namespace pdal
-
diff --git a/io/ilvis2/Ilvis2MetadataReader.hpp b/io/ilvis2/Ilvis2MetadataReader.hpp
deleted file mode 100644
index 928cbaf..0000000
--- a/io/ilvis2/Ilvis2MetadataReader.hpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Metadata.hpp>
-
-#include <libxml/parser.h>
-#include <libxml/tree.h>
-#include <geos_c.h>
-#include <string>
-#include <iostream>
-#include <stdlib.h>
-
-namespace pdal
-{
-
-class PDAL_DLL Ilvis2MetadataReader
-{
-public:
-    void readMetadataFile(std::string filename, MetadataNode* m);
-
-protected:
-    // These methods are written to parse specific nodes.  It doesn't
-    // do full validation, but does check to make sure things are in
-    // the order it expects them to be in.
-
-    void parseGranuleMetaDataFile(xmlNodePtr node, MetadataNode* m);
-    void parseGranuleURMetaData(xmlNodePtr node, MetadataNode* m);
-    void parseCollectionMetaData(xmlNodePtr node, MetadataNode* m);
-    void parseDataFiles(xmlNodePtr node, MetadataNode* m);
-    void parseDataFileContainer(xmlNodePtr node, MetadataNode* m);
-    void parseECSDataGranule(xmlNodePtr node, MetadataNode* m);
-    void parseRangeDateTime(xmlNodePtr node, MetadataNode* m);
-
-    void parsePlatform(xmlNodePtr node, MetadataNode* m);
-    void parseInstrument(xmlNodePtr node, MetadataNode* m);
-    void parseSensor(xmlNodePtr node, MetadataNode* m);
-    void parseSensorCharacteristic(xmlNodePtr node, MetadataNode* m);
-    void parseCampaign(xmlNodePtr node, MetadataNode* m);
-    void parsePSAs(xmlNodePtr node, MetadataNode* m);
-    void parsePSA(xmlNodePtr node, MetadataNode* m);
-    void parseXXProduct(std::string type, xmlNodePtr node, MetadataNode* m);
-
-    void parseSpatialDomainContainer(xmlNodePtr node, MetadataNode* m);
-    void parseGPolygon(xmlNodePtr node, MetadataNode* m);
-    void parseBoundary(xmlNodePtr node, MetadataNode* m);
-    void parsePoint(xmlNodePtr node, MetadataNode* m);
-
-private:
-    // These private methods are mostly helper functions for proessing
-    // the heirarchy and contents of the various XML node objects that
-    // are returned by libxml.
-
-    std::string extractString(xmlNodePtr node);
-    double extractDouble(xmlNodePtr node);
-    int extractInt(xmlNodePtr node);
-    long extractLong(xmlNodePtr node);
-
-    // These two methods are useful to help ignore "empty" text nodes
-    // caused by indentation, etc.  These will simply grab the actual
-    // element nodes directly.
-    // Note that due to the way LIBXML parses things, a child points
-    // to its own siblings; the parent only points to the first child.
-    xmlNodePtr getNextElementNode(xmlNodePtr node);
-    xmlNodePtr getFirstChildElementNode(xmlNodePtr node);
-
-    bool nodeElementIs(xmlNodePtr node, std::string expected);
-    void assertElementIs(xmlNodePtr node, std::string expected);
-    void assertEndOfElements(xmlNodePtr node);
-    int countChildElements(xmlNodePtr node, std::string childName);
-    void errWrongElement(xmlNodePtr node, std::string expected);
-    void errExpectedEnd(xmlNodePtr node);
-};
-
-}
\ No newline at end of file
diff --git a/io/ilvis2/Ilvis2Reader.cpp b/io/ilvis2/Ilvis2Reader.cpp
deleted file mode 100644
index 706a515..0000000
--- a/io/ilvis2/Ilvis2Reader.cpp
+++ /dev/null
@@ -1,302 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "Ilvis2Reader.hpp"
-#include <pdal/util/FileUtils.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <algorithm>
-#include <cmath>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "readers.ilvis2",
-    "ILVIS2 Reader",
-    "http://pdal.io/stages/readers.ilvis2.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, Ilvis2Reader, Reader, s_info)
-
-std::string Ilvis2Reader::getName() const { return s_info.name; }
-
-std::istream& operator >> (std::istream& in, Ilvis2Reader::IlvisMapping& mval)
-{
-    std::string s;
-
-    in >> s;
-    s = Utils::toupper(s);
-    
-    static std::map<std::string, Ilvis2Reader::IlvisMapping> m =
-        { { "INVALID", Ilvis2Reader::IlvisMapping::INVALID }, 
-          { "LOW", Ilvis2Reader::IlvisMapping::LOW }, 
-          { "HIGH", Ilvis2Reader::IlvisMapping::HIGH }, 
-          { "ALL", Ilvis2Reader::IlvisMapping::ALL } };
-
-    mval = m[s];
-    return in;
-}
-
-
-std::ostream& operator<<(std::ostream& out,
-    const Ilvis2Reader::IlvisMapping& mval)
-{
-    switch (mval)
-    {
-    case Ilvis2Reader::IlvisMapping::INVALID:
-        out << "Invalid";
-    case Ilvis2Reader::IlvisMapping::LOW:
-        out << "Low";
-    case Ilvis2Reader::IlvisMapping::HIGH:
-        out << "High";
-    case Ilvis2Reader::IlvisMapping::ALL:
-        out << "All";
-    }
-    return out;
-}
-
-
-void Ilvis2Reader::addArgs(ProgramArgs& args)
-{
-    args.add("mapping", "Mapping for values", m_mapping, IlvisMapping::ALL);
-    args.add("metadata", "Metadata file", m_metadataFile);
-}
-
-
-void Ilvis2Reader::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(Dimension::Id::LvisLfid);
-    layout->registerDim(Dimension::Id::ShotNumber);
-    layout->registerDim(Dimension::Id::GpsTime);
-    layout->registerDim(Dimension::Id::LongitudeCentroid);
-    layout->registerDim(Dimension::Id::LatitudeCentroid);
-    layout->registerDim(Dimension::Id::ElevationCentroid);
-    layout->registerDim(Dimension::Id::LongitudeLow);
-    layout->registerDim(Dimension::Id::LatitudeLow);
-    layout->registerDim(Dimension::Id::ElevationLow);
-    layout->registerDim(Dimension::Id::LongitudeHigh);
-    layout->registerDim(Dimension::Id::LatitudeHigh);
-    layout->registerDim(Dimension::Id::ElevationHigh);
-    layout->registerDim(Dimension::Id::X);
-    layout->registerDim(Dimension::Id::Y);
-    layout->registerDim(Dimension::Id::Z);
-}
-
-
-Dimension::IdList Ilvis2Reader::getDefaultDimensions()
-{
-    using namespace pdal::Dimension;
-    Dimension::IdList ids;
-
-    ids.push_back(Id::GpsTime);
-    ids.push_back(Id::Y);
-    ids.push_back(Id::X);
-    ids.push_back(Id::Z);
-    return ids;
-}
-
-
-void Ilvis2Reader::initialize(PointTableRef)
-{
-    if (!m_metadataFile.empty() && !FileUtils::fileExists(m_metadataFile))
-    {
-        std::ostringstream oss;
-        oss << "Invalid metadata file: '" << m_metadataFile << "'";
-        throw pdal_error(oss.str());
-    }
-
-    // Data are WGS84 (4326) with ITRF2000 datum (6656)
-    // See http://nsidc.org/data/docs/daac/icebridge/ilvis2/index.html for
-    // background
-    SpatialReference ref("EPSG:4326");
-    setSpatialReference(m_metadata, ref);
-}
-
-
-template <typename T>
-T convert(const StringList& s, const std::string& name, size_t fieldno)
-{
-    T output;
-    if (!Utils::fromString(s[fieldno], output))
-    {
-        std::stringstream oss;
-        oss << "Unable to convert " << name << ", " << s[fieldno] <<
-            ", to double";
-        throw pdal_error(oss.str());
-    }
-
-    return output;
-}
-
-
-void Ilvis2Reader::readPoint(PointRef& point, StringList s,
-    std::string pointMap)
-{
-    point.setField(pdal::Dimension::Id::LvisLfid,
-        convert<unsigned>(s, "LVIS_LFID", 0));
-    point.setField(pdal::Dimension::Id::ShotNumber,
-        convert<unsigned>(s, "SHOTNUMBER", 1));
-    point.setField(pdal::Dimension::Id::GpsTime,
-        convert<double>(s, "GPSTIME", 2));
-    point.setField(pdal::Dimension::Id::LongitudeCentroid,
-        Utils::normalizeLongitude(convert<double>(s, "LONGITUDE_CENTROID", 3)));
-    point.setField(pdal::Dimension::Id::LatitudeCentroid,
-        convert<double>(s, "LATITUDE_CENTROID", 4));
-    point.setField(pdal::Dimension::Id::ElevationCentroid,
-        convert<double>(s, "ELEVATION_CENTROID", 5));
-    point.setField(pdal::Dimension::Id::LongitudeLow,
-        Utils::normalizeLongitude(convert<double>(s, "LONGITUDE_LOW", 6)));
-    point.setField(pdal::Dimension::Id::LatitudeLow,
-        convert<double>(s, "LATITUDE_LOW", 7));
-    point.setField(pdal::Dimension::Id::ElevationLow,
-        convert<double>(s, "ELEVATION_LOW", 8));
-    point.setField(pdal::Dimension::Id::LongitudeHigh,
-        Utils::normalizeLongitude(convert<double>(s, "LONGITUDE_HIGH", 9)));
-    point.setField(pdal::Dimension::Id::LatitudeHigh,
-        convert<double>(s, "LATITUDE_HIGH", 10));
-    point.setField(pdal::Dimension::Id::ElevationHigh,
-        convert<double>(s, "ELEVATION_HIGH", 11));
-
-    double x, y, z;
-    pdal::Dimension::Id xd, yd, zd;
-
-    xd = m_layout->findDim("LONGITUDE_" + pointMap);
-    yd = m_layout->findDim("LATITUDE_" + pointMap);
-    zd = m_layout->findDim("ELEVATION_" + pointMap);
-
-    x = point.getFieldAs<double>(xd);
-    y = point.getFieldAs<double>(yd);
-    z = point.getFieldAs<double>(zd);
-
-    point.setField(pdal::Dimension::Id::X, x);
-    point.setField(pdal::Dimension::Id::Y, y);
-    point.setField(pdal::Dimension::Id::Z, z);
-}
-
-
-void Ilvis2Reader::ready(PointTableRef table)
-{
-    static const int HeaderSize = 2;
-    std::string line;
-
-    m_lineNum = 0;
-    m_stream.open(m_filename);
-    m_layout = table.layout();
-    m_resample = false;
-    for (size_t i = 0; i < HeaderSize; ++i)
-    {
-        std::getline(m_stream, line);
-        m_lineNum++;
-    }
-}
-
-
-bool Ilvis2Reader::processOne(PointRef& point)
-{
-    std::string line;
-
-// Format:
-// LVIS_LFID SHOTNUMBER TIME LONGITUDE_CENTROID LATITUDE_CENTROID ELEVATION_CENTROID LONGITUDE_LOW LATITUDE_LOW ELEVATION_LOW LONGITUDE_HIGH LATITUDE_HIGH ELEVATION_HIGH
-
-    // This handles the second time through for this data line when we have
-    // an "ALL" mapping and the high and low elevations are different.
-    if (m_resample)
-    {
-        readPoint(point, m_fields, "HIGH");
-        m_resample = false;
-        return true;
-    }
-
-    if (!std::getline(m_stream, line))
-        return false;
-    m_fields = Utils::split2(line, ' ');
-    if (m_fields.size() != 12)
-    {
-        std::stringstream oss;
-        oss << getName() << ": Invalid format for line " << m_lineNum <<
-            ".  Expected 12 fields, got " << m_fields.size() << ".";
-        throw pdal_error(oss.str());
-    }
-
-    double low_elev = convert<double>(m_fields, "ELEVATION_LOW", 8);
-    double high_elev = convert<double>(m_fields, "ELEVATION_HIGH", 11);
-
-    // write LOW point if specified, or for ALL
-    if (m_mapping == IlvisMapping::LOW || m_mapping == IlvisMapping::ALL)
-    {
-        readPoint(point, m_fields, "LOW");
-        // If we have ALL mapping and the high elevation is different
-        // from that of the low elevation, we'll a second point with the
-        // high elevation.
-        if (m_mapping == IlvisMapping::ALL && (low_elev != high_elev))
-            m_resample = true;
-    }
-    else if (m_mapping == IlvisMapping::HIGH)
-        readPoint(point, m_fields, "HIGH");
-    return true;
-}
-
-
-point_count_t Ilvis2Reader::read(PointViewPtr view, point_count_t count)
-{
-    PointId idx = view->size();
-    point_count_t numRead = 0;
-
-    PointRef point = PointRef(*view, 0);
-    while (numRead < count)
-    {
-        point.setPointId(idx++);
-        if (!processOne(point))
-            break;
-        if (m_cb)
-            m_cb(*view, idx);
-        numRead++;
-    }
-
-    return numRead;
-}
-
-
-void Ilvis2Reader::done(PointTableRef table)
-{
-    if (!m_metadataFile.empty())
-    {
-        m_mdReader.readMetadataFile(m_metadataFile, &m_metadata);
-    }
-
-}
-
-} // namespace pdal
-
diff --git a/io/las/CMakeLists.txt b/io/las/CMakeLists.txt
deleted file mode 100644
index 1bd7c8b..0000000
--- a/io/las/CMakeLists.txt
+++ /dev/null
@@ -1,67 +0,0 @@
-#
-# LAS driver CMake configuration
-#
-
-if ((GEOTIFF_FOUND) AND (GDAL_FOUND))
-    set (PDAL_DRIVERS_LAS_GTIFF GeotiffSupport.cpp)
-endif()
-
-if (LASZIP_FOUND)
-    set(PDAL_DRIVERS_LAS_LASZIP ZipPoint.cpp)
-endif()
-
-set (srcs
-  ${PDAL_DRIVERS_LAS_GTIFF}
-  ${PDAL_DRIVERS_LAS_LASZIP}
-  LasHeader.cpp
-  LasUtils.cpp
-  SummaryData.cpp
-  VariableLengthRecord.cpp
-)
-
-set(incs
-  GeotiffSupport.hpp
-  HeaderVal.hpp
-  LasError.hpp
-  LasHeader.hpp
-  LasUtils.hpp
-  SummaryData.hpp
-  VariableLengthRecord.hpp
-  ZipPoint.hpp
-)
-
-set(objs "")
-
-add_library(lascommon OBJECT ${srcs} ${incs})
-add_dependencies(lascommon generate_dimension_hpp)
-set(objs ${objs} $<TARGET_OBJECTS:lascommon>)
-
-#
-# LAS Reader
-#
-set(srcs
-    LasReader.cpp
-)
-
-set(incs
-    LasReader.hpp
-)
-
-PDAL_ADD_DRIVER(reader las "${srcs}" "${incs}" reader_objs)
-set(objs ${objs} ${reader_objs})
-
-#
-# LAS Writer
-#
-set(srcs
-    LasWriter.cpp
-)
-
-set(incs
-    LasWriter.hpp
-)
-
-PDAL_ADD_DRIVER(writer las "${srcs}" "${incs}" writer_objs)
-set(objs ${objs} ${writer_objs})
-
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objs} PARENT_SCOPE)
diff --git a/io/las/GeotiffSupport.cpp b/io/las/GeotiffSupport.cpp
deleted file mode 100644
index c1904e0..0000000
--- a/io/las/GeotiffSupport.cpp
+++ /dev/null
@@ -1,338 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "GeotiffSupport.hpp"
-
-#include <sstream>
-
-// GDAL
-#include <geo_normalize.h>
-#include <ogr_spatialref.h>
-
-// See http://lists.osgeo.org/pipermail/gdal-dev/2013-November/037429.html
-#define CPL_SERV_H_INCLUDED
-
-#include <geo_simpletags.h>
-#include <cpl_conv.h>
-
-PDAL_C_START
-
-// These functions are available from GDAL, but they
-// aren't exported.
-char CPL_DLL * GTIFGetOGISDefn(GTIF*, GTIFDefn*);
-int CPL_DLL GTIFSetFromOGISDefn(GTIF*, const char*);
-
-PDAL_C_END
-
-#include <pdal/GDALUtils.hpp>
-
-struct StTiff : public ST_TIFF
-{};
-
-namespace pdal
-{
-
-GeotiffSupport::~GeotiffSupport()
-{
-#ifdef PDAL_HAVE_LIBGEOTIFF
-    if (m_gtiff != 0)
-    {
-        GTIFFree(m_gtiff);
-        m_gtiff = 0;
-    }
-    if (m_tiff != 0)
-    {
-        ST_Destroy(m_tiff);
-        m_tiff = 0;
-    }
-#endif
-}
-
-
-void GeotiffSupport::resetTags()
-{
-    // If we already have m_gtiff and m_tiff, that is because we have
-    // already called GetGTIF once before.  VLRs ultimately drive how the
-    // SpatialReference is defined, not the GeoTIFF keys.
-    if (m_tiff != 0)
-    {
-        ST_Destroy(m_tiff);
-        m_tiff = 0;
-    }
-
-    if (m_gtiff != 0)
-    {
-        GTIFFree(m_gtiff);
-        m_gtiff = 0;
-    }
-
-    m_tiff = (StTiff *)ST_Create();
-
-    return;
-}
-
-
-bool GeotiffSupport::setShortKeys(int tag, void *data, int size)
-{
-    // Make sure struct is 16 bytes.
-#pragma pack(push)
-#pragma pack(1)
-    struct ShortKeyHeader
-    {
-        uint16_t dirVersion;
-        uint16_t keyRev;
-        uint16_t minorRev;
-        uint16_t numKeys;
-    };
-#pragma pack(pop)
-
-    ShortKeyHeader *header = (ShortKeyHeader *)data;
-    int declaredSize = (header->numKeys + 1) * 4;
-    if (size < declaredSize)
-        return false;
-    ST_SetKey(m_tiff, tag, (1 + header->numKeys) * 4, STT_SHORT, data);
-    return true;
-}
-
-
-bool GeotiffSupport::setDoubleKeys(int tag, void *data, int size)
-{
-    ST_SetKey(m_tiff, tag, size / sizeof(double), STT_DOUBLE, data);
-    return true;
-}
-
-
-bool GeotiffSupport::setAsciiKeys(int tag, void *data, int size)
-{
-    ST_SetKey(m_tiff, tag, size, STT_ASCII, data);
-    return true;
-}
-
-
-/// Get the geotiff data associated with a tag.
-/// \param tag - geotiff tag.
-/// \param count - Number of items fetched.
-/// \param data_ptr - Pointer to fill with address of filled data.
-/// \return  Size of data referred to by \c data_ptr
-size_t GeotiffSupport::getKey(int tag, int *count, void **data_ptr) const
-{
-    int st_type;
-
-    if (m_tiff == 0)
-        return 0;
-
-    if (!ST_GetKey(m_tiff, tag, count, &st_type, data_ptr))
-        return 0;
-
-    if (st_type == STT_ASCII)
-        return *count;
-    else if (st_type == STT_SHORT)
-        return 2 * *count;
-    else if (st_type == STT_DOUBLE)
-        return 8 * *count;
-    return 8 * *count;
-}
-
-
-void GeotiffSupport::setTags()
-{
-    m_gtiff = GTIFNewSimpleTags(m_tiff);
-    if (!m_gtiff)
-        throw std::runtime_error("The geotiff keys could not be read "
-            "from VLR records");
-}
-
-
-std::string GeotiffSupport::getWkt(bool horizOnly, bool pretty) const
-{
-    GTIFDefn sGTIFDefn;
-    char* pszWKT = 0;
-
-    if (!m_gtiff)
-        return std::string();
-
-    if (!GTIFGetDefn(m_gtiff, &sGTIFDefn))
-        return std::string();
-
-    pszWKT = GTIFGetOGISDefn(m_gtiff, &sGTIFDefn);
-
-    if (pretty)
-    {
-        OGRSpatialReference* poSRS =
-            (OGRSpatialReference*) OSRNewSpatialReference(NULL);
-        char *pszOrigWKT = pszWKT;
-        poSRS->importFromWkt(&pszOrigWKT);
-
-        CPLFree(pszWKT);
-        pszWKT = NULL;
-        poSRS->exportToPrettyWkt(&pszWKT, false);
-        OSRDestroySpatialReference(poSRS);
-
-    }
-
-    if (pszWKT
-            && horizOnly
-            && strstr(pszWKT,"COMPD_CS") != NULL)
-    {
-        OGRSpatialReference* poSRS =
-            (OGRSpatialReference*) OSRNewSpatialReference(NULL);
-        char *pszOrigWKT = pszWKT;
-        poSRS->importFromWkt(&pszOrigWKT);
-
-        CPLFree(pszWKT);
-        pszWKT = NULL;
-
-        poSRS->StripVertical();
-        if (pretty)
-            poSRS->exportToPrettyWkt(&pszWKT, false);
-        else
-            poSRS->exportToWkt(&pszWKT);
-
-        OSRDestroySpatialReference(poSRS);
-    }
-
-    if (pszWKT)
-    {
-        std::string tmp(pszWKT);
-        CPLFree(pszWKT);
-        return tmp;
-    }
-
-    return std::string();
-}
-
-
-void GeotiffSupport::rebuildGTIF()
-{
-    // If we already have m_gtiff and m_tiff, that is because we have
-    // already called GetGTIF once before.  VLRs ultimately drive how the
-    // SpatialReference is defined, not the GeoTIFF keys.
-    if (m_tiff != 0)
-    {
-        ST_Destroy(m_tiff);
-        m_tiff = 0;
-    }
-
-    if (m_gtiff != 0)
-    {
-        GTIFFree(m_gtiff);
-        m_gtiff = 0;
-    }
-
-    m_tiff = (StTiff *)ST_Create();
-
-    // (here it used to read in the VLRs)
-
-    m_gtiff = GTIFNewSimpleTags(m_tiff);
-    if (!m_gtiff)
-        throw std::runtime_error("The geotiff keys could not be read from "
-            "VLR records");
-}
-
-
-void GeotiffSupport::setWkt(const std::string& v)
-{
-    if (!m_gtiff)
-        rebuildGTIF();
-
-    if (v == "")
-        return;
-
-    if (!GTIFSetFromOGISDefn(m_gtiff, v.c_str()))
-        throw std::invalid_argument("could not set m_gtiff from WKT");
-
-    int ret = 0;
-    ret = GTIFSetFromOGISDefn(m_gtiff, v.c_str());
-    if (!ret)
-    {
-        throw std::invalid_argument("could not set m_gtiff from WKT");
-    }
-
-    ret = GTIFWriteKeys(m_gtiff);
-    if (!ret)
-    {
-        throw std::runtime_error("The geotiff keys could not be written");
-    }
-
-    return;
-}
-
-
-// Utility functor with accompanying to print GeoTIFF directory.
-struct geotiff_dir_printer
-{
-    geotiff_dir_printer() {}
-
-    std::string output() const
-    {
-        return m_oss.str();
-    }
-    std::string::size_type size() const
-    {
-        return m_oss.str().size();
-    }
-
-    void operator()(char* data, void* /*aux*/)
-    {
-        if (0 != data)
-        {
-            m_oss << data;
-        }
-    }
-
-private:
-    std::ostringstream m_oss;
-};
-
-
-static int pdalGeoTIFFPrint(char* data, void* aux)
-{
-    geotiff_dir_printer* printer = reinterpret_cast<geotiff_dir_printer*>(aux);
-    (*printer)(data, 0);
-    return static_cast<int>(printer->size());
-}
-
-
-std::string GeotiffSupport::getText() const
-{
-    if (m_gtiff == NULL)
-        return std::string("");
-
-    geotiff_dir_printer geotiff_printer;
-    GTIFPrint(m_gtiff, pdalGeoTIFFPrint, &geotiff_printer);
-    const std::string s = geotiff_printer.output();
-    return s;
-}
-
-} // namespace pdal
diff --git a/io/las/GeotiffSupport.hpp b/io/las/GeotiffSupport.hpp
deleted file mode 100644
index e27092d..0000000
--- a/io/las/GeotiffSupport.hpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_internal.hpp>
-
-// GDAL
-#include <geo_normalize.h>
-#include <ogr_spatialref.h>
-
-// See http://lists.osgeo.org/pipermail/gdal-dev/2013-November/037429.html
-#define CPL_SERV_H_INCLUDED
-
-#include <string>
-#include <stdexcept>
-
-struct GTIFS;
-struct StTiff;
-
-namespace pdal
-{
-
-class PDAL_DLL GeotiffSupport
-{
-public:
-    GeotiffSupport() : m_gtiff(0), m_tiff(0)
-    {}
-    ~GeotiffSupport();
-
-    void resetTags();
-    bool setShortKeys(int tag, void *data, int size);
-    bool setDoubleKeys(int tag, void *data, int size);
-    bool setAsciiKeys(int tag, void *data, int size);
-    size_t getKey(int tag, int *count, void **data_ptr) const;
-    void setTags();
-
-    std::string getWkt(bool horizOnly, bool pretty) const;
-    void setWkt(const std::string&);
-
-    std::string getText() const;
-
-private:
-    void rebuildGTIF();
-
-    GTIF *m_gtiff;
-    StTiff *m_tiff;
-};
-
-} // namespace pdal
diff --git a/io/las/LasHeader.cpp b/io/las/LasHeader.cpp
deleted file mode 100644
index bfe154a..0000000
--- a/io/las/LasHeader.cpp
+++ /dev/null
@@ -1,485 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2008, Mateusz Loskot
- * Copyright (c) 2008, Phil Vachon
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of the Martin Isenburg or Iowa Department
- *       of Natural Resources nor the names of its contributors may be
- *       used to endorse or promote products derived from this software
- *       without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#include "LasHeader.hpp"
-
-#include <pdal/pdal_config.hpp>
-#include <pdal/Scaling.hpp>
-#include <pdal/util/Utils.hpp>
-
-#include "SummaryData.hpp"
-
-#ifdef PDAL_HAVE_LIBGEOTIFF
-#include "GeotiffSupport.hpp"
-#endif
-
-namespace pdal
-{
-
-const std::string LasHeader::FILE_SIGNATURE("LASF");
-#ifndef _WIN32
-const size_t LasHeader::LEGACY_RETURN_COUNT;
-const size_t LasHeader::RETURN_COUNT;
-#endif
-
-std::string GetDefaultSoftwareId()
-{
-    std::string ver(PDAL_VERSION_STRING);
-    std::stringstream oss;
-    std::ostringstream revs;
-    revs << GetSHA1();
-
-
-    oss << "PDAL " << ver << " (" << revs.str().substr(0, 6) <<")";
-    return oss.str();
-}
-
-LasHeader::LasHeader() : m_fileSig(FILE_SIGNATURE), m_sourceId(0),
-    m_globalEncoding(0), m_versionMinor(2), m_systemId(getSystemIdentifier()),
-    m_createDOY(0), m_createYear(0), m_vlrOffset(0), m_pointOffset(0),
-    m_vlrCount(0), m_pointFormat(0), m_pointLen(0), m_pointCount(0),
-    m_isCompressed(false), m_eVlrOffset(0), m_eVlrCount(0)
-{
-    std::time_t now;
-    std::time(&now);
-    std::tm* ptm = std::gmtime(&now);
-    if (ptm)
-    {
-        m_createDOY = static_cast<uint16_t>(ptm->tm_yday);
-        m_createYear = static_cast<uint16_t>(ptm->tm_year + 1900);
-    }
-
-    m_pointLen = basePointLen(m_pointFormat);
-    m_pointCountByReturn.fill(0);
-    m_scales.fill(1.0);
-    m_offsets.fill(0.0);
-}
-
-
-void LasHeader::setSummary(const SummaryData& summary)
-{
-    m_pointCount = summary.getTotalNumPoints();
-    for (size_t num = 0; num < RETURN_COUNT; ++num)
-        m_pointCountByReturn[num] = (int)summary.getReturnCount(num);
-    m_bounds = summary.getBounds();
-}
-
-
-void LasHeader::setScaling(const Scaling& scaling)
-{
-    const double& xs = scaling.m_xXform.m_scale.m_val;
-    const double& ys = scaling.m_yXform.m_scale.m_val;
-    const double& zs = scaling.m_zXform.m_scale.m_val;
-    if (xs == 0)
-        throw std::invalid_argument("X scale of 0.0 is invalid!");
-
-    if (ys == 0)
-        throw std::invalid_argument("Y scale of 0.0 is invalid!");
-
-    if (zs == 0)
-        throw std::invalid_argument("Z scale of 0.0 is invalid!");
-
-    m_scales[0] = xs;
-    m_scales[1] = ys;
-    m_scales[2] = zs;
-
-    m_offsets[0] = scaling.m_xXform.m_offset.m_val;
-    m_offsets[1] = scaling.m_yXform.m_offset.m_val;
-    m_offsets[2] = scaling.m_zXform.m_offset.m_val;
-}
-
-
-uint16_t LasHeader::basePointLen(uint8_t type)
-{
-    switch (type)
-    {
-    case 0:
-        return 20;
-    case 1:
-        return 28;
-    case 2:
-        return 26;
-    case 3:
-        return 34;
-    case 6:
-        return 30;
-    case 7:
-        return 36;
-    case 8:
-        return 38;
-    }
-    return 0;
-}
-
-
-bool LasHeader::valid() const
-{
-    if (m_fileSig != FILE_SIGNATURE)
-        return false;
-    if (m_versionMinor > 10)
-        return false;
-    if (m_createDOY > 366)
-        return false;
-    if (m_createYear < 1970 || m_createYear > 2100)
-       return false;
-    return true;
-}
-
-
-void LasHeader::get(ILeStream& in, Uuid& uuid)
-{
-    char buf[uuid.size];
-
-    in.get(buf, uuid.size);
-    uuid.unpack(buf);
-}
-
-
-void LasHeader::put(OLeStream& out, Uuid uuid)
-{
-    char buf[uuid.size];
-
-    uuid.pack(buf);
-    out.put(buf, uuid.size);
-}
-
-
-Dimension::IdList LasHeader::usedDims() const
-{
-    using namespace Dimension;
-
-    Dimension::Id dims[] = { Id::ReturnNumber, Id::NumberOfReturns,
-        Id::X, Id::Y, Id::Z, Id::Intensity, Id::ScanChannel,
-        Id::ScanDirectionFlag, Id::EdgeOfFlightLine, Id::Classification,
-        Id::UserData, Id::ScanAngleRank, Id::PointSourceId };
-
-    Dimension::IdList ids(std::begin(dims), std::end(dims));
-
-    if (hasTime())
-        ids.push_back(Id::GpsTime);
-    if (hasColor())
-    {
-        ids.push_back(Id::Red);
-        ids.push_back(Id::Green);
-        ids.push_back(Id::Blue);
-    }
-    if (hasInfrared())
-        ids.push_back(Id::Infrared);
-
-    return ids;
-}
-
-void LasHeader::setSrs()
-{
-    bool useWkt = false;
-
-    if (incompatibleSrs())
-    {
-        m_log->get(LogLevel::Error) << "Invalid SRS specification.  "
-            "GeoTiff not allowed with point formats 6 - 10." << std::endl;
-    }
-    else if (findVlr(TRANSFORM_USER_ID, WKT_RECORD_ID) &&
-        findVlr(TRANSFORM_USER_ID, GEOTIFF_DIRECTORY_RECORD_ID))
-    {
-        m_log->get(LogLevel::Debug) << "File contains both "
-            "WKT and GeoTiff VLRs which is disallowed." << std::endl;
-    }
-    else
-        useWkt = (m_versionMinor >= 4);
-
-    return useWkt ? setSrsFromWkt() : setSrsFromGeotiff();
-}
-
-
-VariableLengthRecord *LasHeader::findVlr(const std::string& userId,
-    uint16_t recordId)
-{
-    for (auto vi = m_vlrs.begin(); vi != m_vlrs.end(); ++vi)
-    {
-        VariableLengthRecord& vlr = *vi;
-        if (vlr.matches(userId, recordId))
-            return &vlr;
-    }
-    return NULL;
-}
-
-
-void LasHeader::setSrsFromWkt()
-{
-    VariableLengthRecord *vlr = findVlr(TRANSFORM_USER_ID, WKT_RECORD_ID);
-    if (!vlr)
-        vlr = findVlr(LIBLAS_USER_ID, WKT_RECORD_ID);
-    if (!vlr || vlr->dataLen() == 0)
-        return;
-
-    // There is supposed to be a NULL byte at the end of the data,
-    // but sometimes there isn't because some people don't follow the
-    // rules.  If there is a NULL byte, don't stick it in the
-    // wkt string.
-    size_t len = vlr->dataLen();
-    const char *c = vlr->data() + len - 1;
-    if (*c == 0)
-        len--;
-    m_srs.setWKT(std::string(vlr->data(), len));
-}
-
-
-void LasHeader::setSrsFromGeotiff()
-{
-#ifdef PDAL_HAVE_LIBGEOTIFF
-
-// These are defined in geo_simpletags.h
-// We're not including that file because it includes
-// geotiff.h, which includes a ton of other stuff
-// that might conflict with the messy libgeotiff/GDAL
-// symbol mess
-
-#define STT_SHORT   1
-#define STT_DOUBLE  2
-#define STT_ASCII   3
-
-    GeotiffSupport geotiff;
-    geotiff.resetTags();
-
-    VariableLengthRecord *vlr;
-
-    vlr = findVlr(TRANSFORM_USER_ID, GEOTIFF_DIRECTORY_RECORD_ID);
-    // We must have a directory entry.
-    if (!vlr)
-        return;
-    if (!geotiff.setShortKeys(vlr->recordId(), (void *)vlr->data(),
-        (int)vlr->dataLen()))
-    {
-        std::ostringstream oss;
-
-        oss << "Invalid GeoTIFF directory record.  Can't "
-            "interpret spatial reference.";
-        throw pdal_error(oss.str());
-    }
-
-    vlr = findVlr(TRANSFORM_USER_ID, GEOTIFF_DOUBLES_RECORD_ID);
-    if (vlr)
-        geotiff.setDoubleKeys(vlr->recordId(), (void *)vlr->data(),
-            (int)vlr->dataLen());
-    vlr = findVlr(TRANSFORM_USER_ID, GEOTIFF_ASCII_RECORD_ID);
-    if (vlr)
-        geotiff.setAsciiKeys(vlr->recordId(), (void *)vlr->data(),
-            (int)vlr->dataLen());
-
-    geotiff.setTags();
-    std::string wkt(geotiff.getWkt(false, false));
-    if (wkt.size())
-        m_srs.setFromUserInput(geotiff.getWkt(false, false));
-
-    m_log->get(LogLevel::Debug5) << "GeoTIFF keys: " << geotiff.getText() <<
-        std::endl;
-#else
-    if (findVlr(TRANSFORM_USER_ID, GEOTIFF_DIRECTORY_RECORD_ID))
-        m_log->get(LogLevel::Error) << "Can't decode LAS GeoTiff VLR to "
-            "SRS - PDAL not built with GeoTiff." << std::endl;
-#endif
-}
-
-
-ILeStream& operator>>(ILeStream& in, LasHeader& h)
-{
-    uint8_t versionMajor;
-    uint32_t legacyPointCount;
-    uint32_t legacyReturnCount;
-
-    in.get(h.m_fileSig, 4);
-    if (!Utils::iequals(h.m_fileSig, "LASF"))
-    {
-        throw pdal::pdal_error("File signature is not 'LASF', "
-            "is this an LAS/LAZ file?");
-    }
-    in >> h.m_sourceId >> h.m_globalEncoding;
-    LasHeader::get(in, h.m_projectUuid);
-    in >> versionMajor >> h.m_versionMinor;
-    in.get(h.m_systemId, 32);
-
-    in.get(h.m_softwareId, 32);
-    in >> h.m_createDOY >> h.m_createYear >> h.m_vlrOffset >>
-        h.m_pointOffset >> h.m_vlrCount >> h.m_pointFormat >>
-        h.m_pointLen >> legacyPointCount;
-    h.m_pointCount = legacyPointCount;
-
-    // Although it isn't part of the LAS spec, the two high bits have been used
-    // to indicate compression, though only the high bit is currently used.
-    if (h.m_pointFormat & 0x80)
-        h.setCompressed(true);
-    h.m_pointFormat &= ~0xC0;
-
-    for (size_t i = 0; i < LasHeader::LEGACY_RETURN_COUNT; ++i)
-    {
-        in >> legacyReturnCount;
-        h.m_pointCountByReturn[i] = legacyReturnCount;
-    }
-
-    in >> h.m_scales[0] >> h.m_scales[1] >> h.m_scales[2];
-    in >> h.m_offsets[0] >> h.m_offsets[1] >> h.m_offsets[2];
-
-    double maxX, minX;
-    double maxY, minY;
-    double maxZ, minZ;
-    in >> maxX >> minX >> maxY >> minY >> maxZ >> minZ;
-    h.m_bounds = BOX3D(minX, minY, minZ, maxX, maxY, maxZ);
-
-    if (h.versionAtLeast(1, 3))
-    {
-        uint64_t waveformOffset;
-        in >> waveformOffset;
-    }
-    if (h.versionAtLeast(1, 4))
-    {
-        in >> h.m_eVlrOffset >> h.m_eVlrCount >> h.m_pointCount;
-        for (size_t i = 0; i < LasHeader::RETURN_COUNT; ++i)
-            in >> h.m_pointCountByReturn[i];
-    }
-
-    // Read regular VLRs.
-    in.seek(h.m_vlrOffset);
-    for (size_t i = 0; i < h.m_vlrCount; ++i)
-    {
-        VariableLengthRecord r;
-        in >> r;
-        h.m_vlrs.push_back(std::move(r));
-    }
-
-    // Read extended VLRs.
-    if (h.versionAtLeast(1, 4))
-    {
-        in.seek(h.m_eVlrOffset);
-        for (size_t i = 0; i < h.m_eVlrCount; ++i)
-        {
-            ExtVariableLengthRecord r;
-            in >> r;
-            h.m_vlrs.push_back(std::move(r));
-        }
-    }
-    h.setSrs();
-
-    return in;
-}
-
-
-OLeStream& operator<<(OLeStream& out, const LasHeader& h)
-{
-    uint32_t legacyPointCount = 0;
-    if (h.m_pointCount <= (std::numeric_limits<uint32_t>::max)())
-        legacyPointCount = (uint32_t)h.m_pointCount;
-
-    out.put(h.m_fileSig, 4);
-    if (h.versionEquals(1, 0))
-        out << (uint32_t)0;
-    else if (h.versionEquals(1, 1))
-        out << h.m_sourceId << (uint16_t)0;
-    else
-        out << h.m_sourceId << h.m_globalEncoding;
-    LasHeader::put(out, h.m_projectUuid);
-    out << (uint8_t)1 << h.m_versionMinor;
-    out.put(h.m_systemId, 32);
-    out.put(h.m_softwareId, 32);
-
-    uint8_t pointFormat = h.m_pointFormat;
-    if (h.compressed())
-        pointFormat |= 0x80;
-
-    out << h.m_createDOY << h.m_createYear << h.m_vlrOffset <<
-        h.m_pointOffset << h.m_vlrCount << pointFormat <<
-        h.m_pointLen << legacyPointCount;
-
-    for (size_t i = 0; i < LasHeader::LEGACY_RETURN_COUNT; ++i)
-    {
-        uint32_t legacyReturnCount = std::min(h.m_pointCountByReturn[i],
-            (uint64_t)(std::numeric_limits<uint32_t>::max)());
-        out << legacyReturnCount;
-    }
-
-    out << h.m_scales[0] << h.m_scales[1] << h.m_scales[2];
-    out << h.m_offsets[0] << h.m_offsets[1] << h.m_offsets[2];
-
-    out << h.maxX() << h.minX() << h.maxY() << h.minY() << h.maxZ() << h.minZ();
-
-    if (h.versionAtLeast(1, 3))
-        out << (uint64_t)0;
-    if (h.versionAtLeast(1, 4))
-    {
-        out << h.m_eVlrOffset << h.m_eVlrCount << h.m_pointCount;
-        for (size_t i = 0; i < LasHeader::RETURN_COUNT; ++i)
-            out << h.m_pointCountByReturn[i];
-    }
-
-    return out;
-}
-
-
-std::ostream& operator<<(std::ostream& out, const LasHeader& h)
-{
-    out << "File version = " << "1." << (int)h.m_versionMinor << "\n";
-    out << "File signature: " << h.m_fileSig << "\n";
-    out << "File source ID: " << h.m_sourceId << "\n";
-    out << "Global encoding: " << h.m_globalEncoding << "\n";
-    out << "Project UUID: " << h.m_projectUuid << "\n";
-    out << "System ID: " << h.m_systemId << "\n";
-    out << "Software ID: " << h.m_softwareId << "\n";
-    out << "Creation DOY: " << h.m_createDOY << "\n";
-    out << "Creation Year: " << h.m_createYear << "\n";
-    out << "VLR offset (header size): " << h.m_vlrOffset << "\n";
-    out << "VLR Count: " << h.m_vlrCount << "\n";
-    out << "Point format: " << (int)h.m_pointFormat << "\n";
-    out << "Point offset: " << h.m_pointOffset << "\n";
-    out << "Point count: " << h.m_pointCount << "\n";
-    for (size_t i = 0; i < LasHeader::RETURN_COUNT; ++i)
-        out << "Point count by return[" << i << "]: " <<
-            h.m_pointCountByReturn[i] << "\n";
-    out << "Scales X/Y/Z: " << h.m_scales[0] << "/" <<
-        h.m_scales[1] << "/" << h.m_scales[2] << "\n";
-    out << "Offsets X/Y/Z: " << h.m_offsets[0] << "/" <<
-        h.m_offsets[1] << "/" << h.m_offsets[2] << "\n";
-    out << "Max X/Y/Z: " << h.maxX() << "/" <<
-        h.maxY() << "/" << h.maxZ() << "\n";
-    out << "Min X/Y/Z: " << h.minX() << "/" <<
-        h.minY() << "/" << h.minZ() << "\n";
-    if (h.versionAtLeast(1, 4))
-    {
-        out << "Ext. VLR offset: " << h.m_eVlrOffset << "\n";
-        out << "Ext. VLR count: " << h.m_eVlrCount << "\n";
-    }
-    out << "Compressed: " << (h.m_isCompressed ? "true" : "false") << "\n";
-    return out;
-}
-
-} // namespace pdal
diff --git a/io/las/LasHeader.hpp b/io/las/LasHeader.hpp
deleted file mode 100644
index 3ab709a..0000000
--- a/io/las/LasHeader.hpp
+++ /dev/null
@@ -1,425 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2008, Mateusz Loskot
- * Copyright (c) 2008, Phil Vachon
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of the Martin Isenburg or Iowa Department
- *       of Natural Resources nor the names of its contributors may be
- *       used to endorse or promote products derived from this software
- *       without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#pragma once
-
-#include <array>
-#include <vector>
-
-#include <pdal/Dimension.hpp>
-#include <pdal/Log.hpp>
-#include <pdal/util/Bounds.hpp>
-#include <pdal/util/Uuid.hpp>
-#include <pdal/pdal_config.hpp>
-#include <pdal/gitsha.h>
-
-#include "VariableLengthRecord.hpp"
-
-namespace pdal
-{
-class OLeStream;
-class ILeStream;
-
-typedef uint8_t PointFormat;
-std::string GetDefaultSoftwareId();
-class SummaryData;
-class Scaling;
-
-class PDAL_DLL LasHeader
-{
-public:
-    static const size_t LEGACY_RETURN_COUNT = 5;
-    static const size_t RETURN_COUNT = 15;
-    static const std::string FILE_SIGNATURE;
-    inline std::string getSystemIdentifier() const { return "PDAL"; }
-
-    LasHeader();
-
-    /// Get ASPRS LAS file signature.
-    /// \return 4-characters long string - \b "LASF".
-    std::string fileSignature() const
-        { return m_fileSig; }
-
-    /// Set ASPRS LAS file signature.
-    /// The only value allowed as file signature is \b "LASF",
-    /// defined as FileSignature constant.
-    /// \exception std::invalid_argument - if invalid signature given.
-    /// \param v - string contains file signature, at least 4-bytes long
-    /// with "LASF" as first four bytes.
-    void SetFileSignature(std::string const& v);
-
-    /// Get file source identifier.
-    /// \exception No throw
-    uint16_t fileSourceId() const
-        { return m_sourceId; }
-
-    /// Set file source identifier.
-    /// \param v - should be set to a value between 1 and 65535.
-    /// \exception No throw
-    void setFileSourceId(uint16_t v)
-        { m_sourceId = v; }
-
-    uint16_t globalEncoding() const
-        { return m_globalEncoding; }
-    void setGlobalEncoding(uint16_t globalEncoding)
-        { m_globalEncoding = globalEncoding; }
-
-    /// Get project identifier.
-    /// \return UUID
-    Uuid projectId() const
-        { return m_projectUuid; }
-
-    /// Set project identifier.
-    void setProjectId(const Uuid& v)
-        { m_projectUuid = v; }
-
-    /// Get the LAS major version.
-    /// \return  LAS major version
-    uint8_t versionMajor() const
-        { return (uint8_t)1; }
-
-    /// Get minor component of version of LAS format.
-    /// \return Valid values are 0, 1, 2, 3.
-    uint8_t versionMinor() const
-        { return m_versionMinor; }
-
-    /// Set minor component of version of LAS format.
-    /// \exception std::out_of_range - invalid value given.
-    /// \param v - value between eVersionMinorMin and eVersionMinorMax.
-    void setVersionMinor(uint8_t v)
-    {
-        assert(v <= 4);
-        m_versionMinor = v;
-    }
-
-    /// Determine if the header is for a LAS file version of at least
-    ///   a certain level.
-    /// \param major - Major version.
-    /// \param minor - Minor version.
-    /// \return  Whether the version meets the criteria.
-    bool versionAtLeast(uint8_t major, uint8_t minor) const
-        { return (1 >= major && m_versionMinor >= minor); }
-
-    /// Determine if the header is for a particular LAS file version.
-    /// \param major - Major version.
-    /// \param minor - Minor version.
-    /// \return  Whether the version meets the criteria.
-    bool versionEquals(uint8_t major, uint8_t minor) const
-        { return (major == 1 && minor == m_versionMinor); }
-
-    /// Get system identifier.
-    /// Default value is \b "libLAS" specified as the SystemIdentifier constant.
-    /// \param pad - if true the returned string is padded right with spaces and
-    /// its length is 32 bytes, if false (default) no padding occurs and
-    /// length of the returned string is <= 32 bytes.
-    /// \return value of system identifier field.
-    std::string systemId() const
-        { return m_systemId; }
-
-    /// Set system identifier.
-    /// \param v - system identifiers string.
-    void setSystemId(std::string const& v)
-        { m_systemId = v; }
-
-    /// Get software identifier.
-    /// Default value is \b "libLAS 1.0", specified as the SoftwareIdentifier
-    /// constant.
-    std::string softwareId() const
-        { return m_softwareId; }
-
-    /// Set software identifier.
-    /// \param v - software identifiers string.
-    void setSoftwareId(std::string const& v)
-        { m_softwareId = v; }
-
-    /// Get day of year of file creation date.
-    uint16_t creationDOY() const
-        { return m_createDOY; }
-
-    /// Set day of year of file creation date.
-    /// \exception std::out_of_range - given value is higher than number 366.
-    void setCreationDOY(uint16_t v)
-        { m_createDOY = v; }
-
-    /// Set year of file creation date.
-    uint16_t creationYear() const
-        { return m_createYear; }
-
-    /// Get year of file creation date.
-    /// \exception std::out_of_range - given value is higher than number 9999.
-    void setCreationYear(uint16_t v)
-        { m_createYear = v; }
-
-    /// Get number of bytes of generic verion of public header block storage.
-    /// Standard version of the public header block is 227 bytes long.
-    uint16_t vlrOffset() const
-        { return m_vlrOffset; }
-
-    void setVlrOffset(uint16_t offset)
-        { m_vlrOffset = offset; }
-
-    /// Get number of bytes from the beginning to the first point record.
-    uint32_t pointOffset() const
-        { return m_pointOffset; }
-
-    /// Set number of bytes from the beginning to the first point record.
-    /// \param  offset - Offset to start of point data.
-    void setPointOffset(uint32_t offset)
-          { m_pointOffset = offset; }
-
-    /// Set the point format.
-    /// \param format  Point format
-    void setPointFormat(uint8_t format)
-        { m_pointFormat = format; }
-
-    /// Get identifier of point data (record) format.
-    uint8_t pointFormat() const
-        { return m_pointFormat; }
-    bool pointFormatSupported() const
-    {
-        if (versionAtLeast(1, 4))
-            return m_pointFormat <= 10 && !hasWave();
-        else
-            return m_pointFormat <= 5 && !hasWave();
-    }
-
-    /// The length in bytes of each point.  All points in the file are
-    /// considered to be fixed in size, and the PointFormatName is used
-    /// to determine the fixed portion of the dimensions in the point.
-    uint16_t pointLen() const
-        { return m_pointLen; }
-	void setPointLen(uint16_t v)
-        { m_pointLen = v; }
-    uint16_t basePointLen()
-        { return basePointLen(m_pointFormat); }
-    uint16_t basePointLen(uint8_t format);
-
-    /// Set the number of points.
-    /// \param pointCount  Number of points in the file.
-    void setPointCount(uint64_t pointCount)
-        { m_pointCount = pointCount; }
-    /// Get total number of point records stored in the LAS file.
-    uint64_t pointCount() const
-        { return m_pointCount; }
-    //
-    /// Set values point count by return number.
-    /// \param index - Return number.
-    /// \param v - Point count for return number.
-    void setPointCountByReturn(std::size_t index, uint64_t v)
-        { m_pointCountByReturn[index] = v; }
-
-    /// Get the point count by return number.
-    /// \param index - Return number.
-    /// \return - Point count.
-    uint64_t pointCountByReturn(std::size_t index)
-        { return m_pointCountByReturn[index]; }
-
-    size_t maxReturnCount() const
-        { return (versionAtLeast(1, 4) ? RETURN_COUNT : LEGACY_RETURN_COUNT); }
-
-    /// Get scale factor for X coordinate.
-    double scaleX() const
-        { return m_scales[0]; }
-
-    /// Get scale factor for Y coordinate.
-    double scaleY() const
-        { return m_scales[1]; }
-
-    /// Get scale factor for Z coordinate.
-    double scaleZ() const
-        { return m_scales[2]; }
-
-    /// Set values of scale/offset factor for X, Y and Z coordinates.
-    void setScaling(const Scaling& scaling);
-
-    /// Get X coordinate offset.
-    double offsetX() const
-        { return m_offsets[0]; }
-
-    /// Get Y coordinate offset.
-    double offsetY() const
-        { return m_offsets[1]; }
-
-    /// Get Z coordinate offset.
-    double offsetZ() const
-        { return m_offsets[2]; }
-
-    /// Set values of X, Y and Z coordinates offset.
-    void setOffset(double x, double y, double z);
-
-    /// Get minimum value of extent of X coordinate.
-    double maxX() const
-        { return m_bounds.maxx; }
-
-    /// Get maximum value of extent of X coordinate.
-    double minX() const
-        { return m_bounds.minx; }
-
-    /// Get minimum value of extent of Y coordinate.
-    double maxY() const
-        { return m_bounds.maxy; }
-
-    /// Get maximum value of extent of Y coordinate.
-    double minY() const
-        { return m_bounds.miny; }
-
-    /// Get minimum value of extent of Z coordinate.
-    double maxZ() const
-        { return m_bounds.maxz; }
-
-    /// Get maximum value of extent of Z coordinate.
-    double minZ() const
-       { return m_bounds.minz; }
-
-    const BOX3D& getBounds() const
-        { return m_bounds; }
-    void setBounds(const BOX3D& bounds)
-        { m_bounds = bounds; }
-
-    bool hasTime() const
-    {
-        PointFormat f = pointFormat();
-        return f == 1 || f >= 3;
-    }
-
-    bool hasColor() const
-    {
-        PointFormat f = pointFormat();
-        return f == 2 || f == 3 || f == 5 || f == 7 || f == 8 || f == 10;
-    }
-
-    bool hasWave() const
-    {
-        PointFormat f = pointFormat();
-        return f == 4 || f == 5 || f == 9 || f == 10;
-    }
-
-    bool hasInfrared() const
-    {
-        PointFormat f = pointFormat();
-        return f == 8;
-    }
-
-    bool has14Format() const
-    {
-        PointFormat f = pointFormat();
-        return f > 5;
-    }
-
-    bool useWkt() const
-        { return (bool)((m_globalEncoding >> 4) & 1); }
-
-    bool incompatibleSrs() const
-        { return !useWkt() && has14Format(); }
-
-    /// Returns true iff the file is compressed (laszip),
-    /// as determined by the high bit in the point type
-    bool compressed() const
-        { return m_isCompressed; }
-
-    /// Sets whether or not the points are compressed.
-    void setCompressed(bool b)
-        { m_isCompressed = b; }
-
-    void setVlrCount(uint32_t vlrCount)
-        { m_vlrCount = vlrCount; }
-    uint32_t vlrCount() const
-        { return m_vlrCount; }
-    void setEVlrOffset(uint64_t offset)
-        { m_eVlrOffset = offset; }
-    uint64_t eVlrOffset() const
-        { return m_eVlrOffset; }
-    void setEVlrCount(uint32_t count)
-        { m_eVlrCount = count; }
-    uint32_t eVlrCount() const
-        { return m_eVlrCount; }
-    std::string const& compressionInfo() const
-        { return m_compressionInfo; }
-    void setCompressionInfo(std::string const& info)
-        { m_compressionInfo = info; }
-    SpatialReference srs() const
-        { return m_srs; }
-
-    void setSummary(const SummaryData& summary);
-    bool valid() const;
-    Dimension::IdList usedDims() const;
-    VariableLengthRecord *findVlr(const std::string& userId, uint16_t recordId);
-    void setLog(LogPtr log)
-        { m_log = log; }
-    const VlrList& vlrs() const
-        { return m_vlrs; }
-
-    PDAL_DLL friend ILeStream& operator>>(ILeStream&, LasHeader& h);
-    friend OLeStream& operator<<(OLeStream&, const LasHeader& h);
-    friend std::ostream& operator<<(std::ostream& ostr, const LasHeader& h);
-
-private:
-    std::string m_fileSig;
-    uint16_t m_sourceId;
-    uint16_t m_globalEncoding;
-    Uuid m_projectUuid;
-    uint8_t m_versionMinor;
-    std::string m_systemId;
-    std::string m_softwareId;
-    uint16_t m_createDOY;
-    uint16_t m_createYear;
-    uint16_t m_vlrOffset;  // Same as header size.
-    uint32_t m_pointOffset;
-    uint32_t m_vlrCount;
-    uint8_t m_pointFormat;
-    uint16_t m_pointLen;
-    uint64_t m_pointCount;
-    std::array<uint64_t, RETURN_COUNT> m_pointCountByReturn;
-    std::array<double, 3> m_scales;
-    std::array<double, 3> m_offsets;
-    bool m_isCompressed;
-    uint64_t m_eVlrOffset;
-    uint32_t m_eVlrCount;
-    BOX3D m_bounds;
-    std::string m_compressionInfo;
-    LogPtr m_log;
-    SpatialReference m_srs;
-    VlrList m_vlrs;
-    VlrList m_eVlrs;
-
-    void setSrs();
-    void setSrsFromWkt();
-    void setSrsFromGeotiff();
-
-    static void get(ILeStream& in, Uuid& uuid);
-    static void put(OLeStream& in, Uuid uuid);
-};
-
-} // namespace pdal
diff --git a/io/las/LasReader.cpp b/io/las/LasReader.cpp
deleted file mode 100644
index 1b2ef4a..0000000
--- a/io/las/LasReader.cpp
+++ /dev/null
@@ -1,794 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "LasReader.hpp"
-
-#include <sstream>
-#include <string.h>
-
-#include <pdal/Metadata.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/QuickInfo.hpp>
-#include <pdal/util/Extractor.hpp>
-#include <pdal/util/IStream.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#ifdef PDAL_HAVE_LIBGEOTIFF
-#include "GeotiffSupport.hpp"
-#endif
-#include "LasHeader.hpp"
-#include "VariableLengthRecord.hpp"
-#include "ZipPoint.hpp"
-
-namespace pdal
-{
-
-namespace
-{
-
-class invalid_stream : public pdal_error
-{
-public:
-    invalid_stream(const std::string& msg) : pdal_error(msg)
-        {}
-};
-
-} // unnamed namespace
-
-void LasReader::addArgs(ProgramArgs& args)
-{
-    addSpatialReferenceArg(args);
-    args.add("extra_dims", "Dimensions to assign to extra byte data",
-        m_extraDimSpec);
-    args.add("compression", "Decompressor to use", m_compression, "LASZIP");
-}
-
-
-static PluginInfo const s_info = PluginInfo(
-    "readers.las",
-    "ASPRS LAS 1.0 - 1.4 read support. LASzip support is also \n" \
-        "enabled through this driver if LASzip was found during \n" \
-        "compilation.",
-    "http://pdal.io/stages/readers.las.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, LasReader, Reader, s_info)
-
-std::string LasReader::getName() const { return s_info.name; }
-
-QuickInfo LasReader::inspect()
-{
-    QuickInfo qi;
-    std::unique_ptr<PointLayout> layout(new PointLayout());
-
-    PointTable table;
-    initialize(table);
-    addDimensions(layout.get());
-
-    Dimension::IdList dims = layout->dims();
-    for (auto di = dims.begin(); di != dims.end(); ++di)
-        qi.m_dimNames.push_back(layout->dimName(*di));
-    if (!Utils::numericCast(m_header.pointCount(), qi.m_pointCount))
-        qi.m_pointCount = std::numeric_limits<point_count_t>::max();
-    qi.m_bounds = m_header.getBounds();
-    qi.m_srs = getSpatialReference();
-    qi.m_valid = true;
-
-    done(table);
-
-    return qi;
-}
-
-
-void LasReader::initializeLocal(PointTableRef table, MetadataNode& m)
-{
-    m_extraDims = LasUtils::parse(m_extraDimSpec);
-
-    std::string compression = Utils::toupper(m_compression);
-#ifndef PDAL_HAVE_LAZPERF
-    if (compression == "LAZPERF")
-        throw pdal_error("Can't decompress with LAZperf.  PDAL not built "
-            "with LAZperf.");
-#endif
-    if (compression != "LAZPERF" && compression != "LASZIP")
-    {
-        std::ostringstream oss;
-
-        oss << "Invalid value for option for compression: '" <<
-            m_compression << "'.  Value values are 'lazperf' and 'laszip'.";
-        throw pdal_error(oss.str());
-    }
-
-    // Set case-corrected value.
-    m_compression = compression;
-    m_error.setFilename(m_filename);
-
-    m_error.setLog(log());
-    m_header.setLog(log());
-    createStream();
-
-    std::istream *stream(m_streamIf->m_istream);
-
-    stream->seekg(0);
-    ILeStream in(stream);
-    try
-    {
-        in >> m_header;
-    }
-    catch (pdal_error e)
-    {
-        std::ostringstream oss;
-
-        oss << getName() << e.what();
-        throw pdal_error(oss.str());
-    }
-
-    if (!m_header.pointFormatSupported())
-    {
-        std::ostringstream oss;
-        oss << "Unsupported LAS input point format: " <<
-            (int)m_header.pointFormat() << ".";
-       throw pdal_error(oss.str());
-    }
-
-    if (m_header.versionAtLeast(1, 4))
-        readExtraBytesVlr();
-    setSrs(m);
-    MetadataNode forward = table.privateMetadata("lasforward");
-    extractHeaderMetadata(forward, m);
-    extractVlrMetadata(forward, m);
-
-    m_streamIf.reset();
-}
-
-
-void LasReader::ready(PointTableRef table)
-{
-    createStream();
-    std::istream *stream(m_streamIf->m_istream);
-
-    m_index = 0;
-    if (m_header.compressed())
-    {
-#ifdef PDAL_HAVE_LASZIP
-        if (m_compression == "LASZIP")
-        {
-            VariableLengthRecord *vlr = m_header.findVlr(LASZIP_USER_ID,
-                LASZIP_RECORD_ID);
-            m_zipPoint.reset(new ZipPoint(vlr));
-
-            if (!m_unzipper)
-            {
-                m_unzipper.reset(new LASunzipper());
-
-                stream->seekg(m_header.pointOffset(), std::ios::beg);
-
-                // Once we open the zipper, don't touch the stream until the
-                // zipper is closed or bad things happen.
-                if (!m_unzipper->open(*stream, m_zipPoint->GetZipper()))
-                {
-                    std::ostringstream oss;
-                    const char* err = m_unzipper->get_error();
-                    if (err == NULL)
-                        err = "(unknown error)";
-                    oss << "Failed to open LASzip stream: " << std::string(err);
-                    throw pdal_error(oss.str());
-                }
-            }
-        }
-#endif
-
-#ifdef PDAL_HAVE_LAZPERF
-        if (m_compression == "LAZPERF")
-        {
-            VariableLengthRecord *vlr = m_header.findVlr(LASZIP_USER_ID,
-                LASZIP_RECORD_ID);
-            m_decompressor.reset(new LazPerfVlrDecompressor(*stream,
-                vlr->data(), m_header.pointOffset()));
-            m_decompressorBuf.resize(m_decompressor->pointSize());
-        }
-#endif
-
-#if !defined(PDAL_HAVE_LAZPERF) && !defined(PDAL_HAVE_LASZIP)
-        throw pdal_error("Can't read compressed file without LASzip or "
-            "LAZperf decompression library.");
-#endif
-    }
-    else
-        stream->seekg(m_header.pointOffset());
-}
-
-
-// Store data in the normal metadata place.  Also store it in the private
-// lasforward metadata node.
-template <typename T>
-void addForwardMetadata(MetadataNode& forward, MetadataNode& m,
-    const std::string& name, T val, const std::string description = "")
-{
-    MetadataNode n = m.add(name, val, description);
-
-    // If the entry doesn't already exist, just add it.
-    MetadataNode f = forward.findChild(name);
-    if (!f.valid())
-    {
-        forward.add(n);
-        return;
-    }
-
-    // If the old value and new values aren't the same, set an invalid flag.
-    MetadataNode temp = f.addOrUpdate("temp", val);
-    if (f.value<std::string>() != temp.value<std::string>())
-        forward.addOrUpdate(name + "INVALID", "");
-}
-
-
-void LasReader::extractHeaderMetadata(MetadataNode& forward, MetadataNode& m)
-{
-    m.add<bool>("compressed", m_header.compressed(),
-        "true if this LAS file is compressed");
-
-    addForwardMetadata(forward, m, "major_version", m_header.versionMajor(),
-        "The major LAS version for the file, always 1 for now");
-    addForwardMetadata(forward, m, "minor_version", m_header.versionMinor(),
-        "The minor LAS version for the file");
-    addForwardMetadata(forward, m, "dataformat_id", m_header.pointFormat(),
-        "LAS Point Data Format");
-    if (m_header.versionAtLeast(1, 1))
-        addForwardMetadata(forward, m, "filesource_id",
-            m_header.fileSourceId(), "File Source ID (Flight Line Number "
-            "if this file was derived from an original flight line).");
-    if (m_header.versionAtLeast(1, 2))
-    {
-        // For some reason we've written global encoding as a base 64
-        // encoded value in the past.  In an effort to standardize things,
-        // I'm writing this as a special value, and will also write
-        // global_encoding like we write all other header metadata.
-        uint16_t globalEncoding = m_header.globalEncoding();
-        m.addEncoded("global_encoding_base64", (uint8_t *)&globalEncoding,
-            sizeof(globalEncoding),
-            "Global Encoding: general property bit field.");
-
-        addForwardMetadata(forward, m, "global_encoding",
-            m_header.globalEncoding(),
-            "Global Encoding: general property bit field.");
-    }
-
-    addForwardMetadata(forward, m, "project_id", m_header.projectId(),
-        "Project ID.");
-    addForwardMetadata(forward, m, "system_id", m_header.systemId());
-    addForwardMetadata(forward, m, "software_id", m_header.softwareId(),
-        "Generating software description.");
-    addForwardMetadata(forward, m, "creation_doy", m_header.creationDOY(),
-        "Day, expressed as an unsigned short, on which this file was created. "
-        "Day is computed as the Greenwich Mean Time (GMT) day. January 1 is "
-        "considered day 1.");
-    addForwardMetadata(forward, m, "creation_year", m_header.creationYear(),
-        "The year, expressed as a four digit number, in which the file was "
-        "created.");
-    addForwardMetadata(forward, m, "scale_x", m_header.scaleX(),
-        "The scale factor for X values.");
-    addForwardMetadata(forward, m, "scale_y", m_header.scaleY(),
-        "The scale factor for Y values.");
-    addForwardMetadata(forward, m, "scale_z", m_header.scaleZ(),
-        "The scale factor for Z values.");
-    addForwardMetadata(forward, m, "offset_x", m_header.offsetX(),
-        "The offset for X values.");
-    addForwardMetadata(forward, m, "offset_y", m_header.offsetY(),
-        "The offset for Y values.");
-    addForwardMetadata(forward, m, "offset_z", m_header.offsetZ(),
-        "The offset for Z values.");
-
-    m.add("header_size", m_header.vlrOffset(),
-        "The size, in bytes, of the header block, including any extension "
-        "by specific software.");
-    m.add("dataoffset", m_header.pointOffset(),
-        "The actual number of bytes from the beginning of the file to the "
-        "first field of the first point record data field. This data offset "
-        "must be updated if any software adds data from the Public Header "
-        "Block or adds/removes data to/from the Variable Length Records.");
-    m.add<double>("minx", m_header.minX(),
-        "The max and min data fields are the actual unscaled extents of the "
-        "LAS point file data, specified in the coordinate system of the LAS "
-        "data.");
-    m.add<double>("miny", m_header.minY(),
-        "The max and min data fields are the actual unscaled extents of the "
-        "LAS point file data, specified in the coordinate system of the LAS "
-        "data.");
-    m.add<double>("minz", m_header.minZ(),
-        "The max and min data fields are the actual unscaled extents of the "
-        "LAS point file data, specified in the coordinate system of the LAS "
-        "data.");
-    m.add<double>("maxx", m_header.maxX(),
-        "The max and min data fields are the actual unscaled extents of the "
-        "LAS point file data, specified in the coordinate system of the LAS "
-        "data.");
-    m.add<double>("maxy", m_header.maxY(),
-        "The max and min data fields are the actual unscaled extents of the "
-        "LAS point file data, specified in the coordinate system of the LAS "
-        "data.");
-    m.add<double>("maxz", m_header.maxZ(),
-        "The max and min data fields are the actual unscaled extents of the "
-        "LAS point file data, specified in the coordinate system of the LAS "
-        "data.");
-    m.add<uint32_t>("count",
-        m_header.pointCount(), "This field contains the total "
-        "number of point records within the file.");
-}
-
-
-void LasReader::readExtraBytesVlr()
-{
-    VariableLengthRecord *vlr = m_header.findVlr(SPEC_USER_ID,
-        EXTRA_BYTES_RECORD_ID);
-    if (!vlr)
-        return;
-    const char *pos = vlr->data();
-    size_t size = vlr->dataLen();
-    if (size % sizeof(ExtraBytesSpec) != 0)
-    {
-        log()->get(LogLevel::Warning) << "Bad size for extra bytes VLR.  "
-            "Ignoring.";
-        return;
-    }
-    size /= sizeof(ExtraBytesSpec);
-    std::vector<ExtraBytesIf> ebList;
-    while (size--)
-    {
-        ExtraBytesIf eb;
-        eb.readFrom(pos);
-        ebList.push_back(eb);
-        pos += sizeof(ExtraBytesSpec);
-    }
-
-    std::vector<ExtraDim> extraDims;
-    for (ExtraBytesIf& eb : ebList)
-    {
-       std::vector<ExtraDim> eds = eb.toExtraDims();
-       for (auto& ed : eds)
-           extraDims.push_back(std::move(ed));
-    }
-    if (m_extraDims.size() && m_extraDims != extraDims)
-        log()->get(LogLevel::Warning) << "Extra byte dimensions specified "
-            "in pineline and VLR don't match.  Ignoring pipeline-specified "
-            "dimensions";
-    m_extraDims = extraDims;
-}
-
-
-void LasReader::setSrs(MetadataNode& m)
-{
-    // If the user is already overriding this by setting it on the stage, we'll
-    // take their overridden value
-    SpatialReference srs = getSpatialReference();
-
-    if (srs.getWKT(pdal::SpatialReference::eCompoundOK).empty())
-        srs = m_header.srs();
-    setSpatialReference(m, srs);
-}
-
-
-void LasReader::extractVlrMetadata(MetadataNode& forward, MetadataNode& m)
-{
-    static const size_t DATA_LEN_MAX = 1000000;
-
-    int i = 0;
-    for (auto vlr : m_header.vlrs())
-    {
-        if (vlr.dataLen() > DATA_LEN_MAX)
-            continue;
-
-        std::ostringstream name;
-        name << "vlr_" << i++;
-        MetadataNode vlrNode = m.addEncoded(name.str(),
-            (const uint8_t *)vlr.data(), vlr.dataLen(), vlr.description());
-
-        vlrNode.add("user_id", vlr.userId(),
-            "User ID of the record or pre-defined value from the "
-            "specification.");
-        vlrNode.add("record_id", vlr.recordId(),
-            "Record ID specified by the user.");
-        vlrNode.add("description", vlr.description());
-
-        if ((vlr.userId() != TRANSFORM_USER_ID) &&
-            (vlr.userId() != SPEC_USER_ID) &&
-            (vlr.userId() != LASZIP_USER_ID) &&
-            (vlr.userId() != LIBLAS_USER_ID))
-            forward.add(vlrNode);
-    }
-}
-
-
-void LasReader::addDimensions(PointLayoutPtr layout)
-{
-    using namespace Dimension;
-
-    layout->registerDim(Id::X, Type::Double);
-    layout->registerDim(Id::Y, Type::Double);
-    layout->registerDim(Id::Z, Type::Double);
-    layout->registerDim(Id::Intensity, Type::Unsigned16);
-    layout->registerDim(Id::ReturnNumber, Type::Unsigned8);
-    layout->registerDim(Id::NumberOfReturns, Type::Unsigned8);
-    layout->registerDim(Id::ScanDirectionFlag, Type::Unsigned8);
-    layout->registerDim(Id::EdgeOfFlightLine, Type::Unsigned8);
-    layout->registerDim(Id::Classification, Type::Unsigned8);
-    layout->registerDim(Id::ScanAngleRank, Type::Float);
-    layout->registerDim(Id::UserData, Type::Unsigned8);
-    layout->registerDim(Id::PointSourceId, Type::Unsigned16);
-
-    if (m_header.hasTime())
-        layout->registerDim(Id::GpsTime, Type::Double);
-    if (m_header.hasColor())
-    {
-        layout->registerDim(Id::Red, Type::Unsigned16);
-        layout->registerDim(Id::Green, Type::Unsigned16);
-        layout->registerDim(Id::Blue, Type::Unsigned16);
-    }
-    if (m_header.hasInfrared())
-        layout->registerDim(Id::Infrared);
-    if (m_header.versionAtLeast(1, 4))
-    {
-        layout->registerDim(Id::ScanChannel);
-        layout->registerDim(Id::ClassFlags);
-    }
-
-    for (auto& dim : m_extraDims)
-    {
-        Dimension::Type type = dim.m_dimType.m_type;
-        if (type == Dimension::Type::None)
-            continue;
-        if (dim.m_dimType.m_xform.nonstandard())
-            type = Dimension::Type::Double;
-        dim.m_dimType.m_id = layout->assignDim(dim.m_name, type);
-    }
-}
-
-
-bool LasReader::processOne(PointRef& point)
-{
-    if (m_index >= getNumPoints())
-        return false;
-
-    size_t pointLen = m_header.pointLen();
-
-    if (m_header.compressed())
-    {
-#ifdef PDAL_HAVE_LASZIP
-        if (m_compression == "LASZIP")
-        {
-            if (!m_unzipper->read(m_zipPoint->m_lz_point))
-            {
-                std::string error = "Error reading compressed point data: ";
-                const char* err = m_unzipper->get_error();
-                if (!err)
-                    err = "(unknown error)";
-                error += err;
-                throw pdal_error(error);
-            }
-            loadPoint(point, (char *)m_zipPoint->m_lz_point_data.data(),
-                pointLen);
-        }
-#endif
-
-#ifdef PDAL_HAVE_LAZPERF
-        if (m_compression == "LAZPERF")
-        {
-            m_decompressor->decompress(m_decompressorBuf.data());
-            loadPoint(point, m_decompressorBuf.data(), pointLen);
-        }
-#endif
-#if !defined(PDAL_HAVE_LAZPERF) && !defined(PDAL_HAVE_LASZIP)
-        throw pdal_error("Can't read compressed file without LASzip or "
-            "LAZperf decompression library.");
-#endif
-    } // compression
-    else
-    {
-        std::vector<char> buf(m_header.pointLen());
-
-        m_streamIf->m_istream->read(buf.data(), pointLen);
-        loadPoint(point, buf.data(), pointLen);
-    }
-    m_index++;
-    return true;
-}
-
-
-point_count_t LasReader::read(PointViewPtr view, point_count_t count)
-{
-    size_t pointLen = m_header.pointLen();
-    count = std::min(count, getNumPoints() - m_index);
-
-    PointId i = 0;
-    if (m_header.compressed())
-    {
-#if defined(PDAL_HAVE_LAZPERF) || defined(PDAL_HAVE_LASZIP)
-        if (m_compression == "LASZIP" || m_compression == "LAZPERF")
-        {
-            for (i = 0; i < count; i++)
-            {
-                PointRef point = view->point(i);
-                PointId id = view->size();
-                processOne(point);
-                if (m_cb)
-                    m_cb(*view, id);
-            }
-        }
-#else
-        throw pdal_error("Can't read compressed file without LASzip or "
-            "LAZperf decompression library.");
-#endif
-    }
-    else
-    {
-        point_count_t remaining = count;
-
-        // Make a buffer at most a meg.
-        size_t bufsize = std::min<size_t>((point_count_t)1000000,
-            count * pointLen);
-        std::vector<char> buf(bufsize);
-        try
-        {
-            do
-            {
-                point_count_t blockPoints = readFileBlock(buf, remaining);
-                remaining -= blockPoints;
-                char *pos = buf.data();
-                while (blockPoints--)
-                {
-                    PointId id = view->size();
-                    PointRef point = view->point(id);
-                    loadPoint(point, pos, pointLen);
-                    if (m_cb)
-                        m_cb(*view, id);
-                    pos += pointLen;
-                    i++;
-                }
-            } while (remaining);
-        }
-        catch (std::out_of_range&)
-        {}
-        catch (invalid_stream&)
-        {}
-    }
-    m_index += i;
-    return (point_count_t)i;
-}
-
-
-point_count_t LasReader::readFileBlock(std::vector<char>& buf,
-    point_count_t maxpoints)
-{
-    std::istream *stream(m_streamIf->m_istream);
-
-    size_t ptLen = m_header.pointLen();
-    point_count_t blockpoints = buf.size() / ptLen;
-
-    blockpoints = std::min(maxpoints, blockpoints);
-    if (stream->eof())
-        throw invalid_stream("stream is done");
-
-    stream->read(buf.data(), blockpoints * ptLen);
-    if (stream->gcount() != (std::streamsize)(blockpoints * ptLen))
-    {
-        // we read fewer bytes than we asked for
-        // because the file was either truncated
-        // or the header is bunk.
-        blockpoints = stream->gcount() / ptLen;
-    }
-    return blockpoints;
-}
-
-
-void LasReader::loadPoint(PointRef& point, char *buf, size_t bufsize)
-{
-    if (m_header.has14Format())
-        loadPointV14(point, buf, bufsize);
-    else
-        loadPointV10(point, buf, bufsize);
-}
-
-
-void LasReader::loadPointV10(PointRef& point, char *buf, size_t bufsize)
-{
-    LeExtractor istream(buf, bufsize);
-
-    int32_t xi, yi, zi;
-    istream >> xi >> yi >> zi;
-
-    const LasHeader& h = m_header;
-
-    double x = xi * h.scaleX() + h.offsetX();
-    double y = yi * h.scaleY() + h.offsetY();
-    double z = zi * h.scaleZ() + h.offsetZ();
-
-    uint16_t intensity;
-    uint8_t flags;
-    uint8_t classification;
-    int8_t scanAngleRank;
-    uint8_t user;
-    uint16_t pointSourceId;
-
-    istream >> intensity >> flags >> classification >> scanAngleRank >>
-        user >> pointSourceId;
-
-    uint8_t returnNum = flags & 0x07;
-    uint8_t numReturns = (flags >> 3) & 0x07;
-    uint8_t scanDirFlag = (flags >> 6) & 0x01;
-    uint8_t flight = (flags >> 7) & 0x01;
-
-    if (returnNum == 0 || returnNum > 5)
-        m_error.returnNumWarning(returnNum);
-
-    if (numReturns == 0 || numReturns > 5)
-        m_error.numReturnsWarning(numReturns);
-
-    point.setField(Dimension::Id::X, x);
-    point.setField(Dimension::Id::Y, y);
-    point.setField(Dimension::Id::Z, z);
-    point.setField(Dimension::Id::Intensity, intensity);
-    point.setField(Dimension::Id::ReturnNumber, returnNum);
-    point.setField(Dimension::Id::NumberOfReturns, numReturns);
-    point.setField(Dimension::Id::ScanDirectionFlag, scanDirFlag);
-    point.setField(Dimension::Id::EdgeOfFlightLine, flight);
-    point.setField(Dimension::Id::Classification, classification);
-    point.setField(Dimension::Id::ScanAngleRank, scanAngleRank);
-    point.setField(Dimension::Id::UserData, user);
-    point.setField(Dimension::Id::PointSourceId, pointSourceId);
-
-    if (h.hasTime())
-    {
-        double time;
-        istream >> time;
-        point.setField(Dimension::Id::GpsTime, time);
-    }
-
-    if (h.hasColor())
-    {
-        uint16_t red, green, blue;
-        istream >> red >> green >> blue;
-        point.setField(Dimension::Id::Red, red);
-        point.setField(Dimension::Id::Green, green);
-        point.setField(Dimension::Id::Blue, blue);
-    }
-
-    if (m_extraDims.size())
-        loadExtraDims(istream, point);
-
-}
-
-void LasReader::loadPointV14(PointRef& point, char *buf, size_t bufsize)
-{
-    LeExtractor istream(buf, bufsize);
-
-    int32_t xi, yi, zi;
-    istream >> xi >> yi >> zi;
-
-    const LasHeader& h = m_header;
-
-    double x = xi * h.scaleX() + h.offsetX();
-    double y = yi * h.scaleY() + h.offsetY();
-    double z = zi * h.scaleZ() + h.offsetZ();
-
-    uint16_t intensity;
-    uint8_t returnInfo;
-    uint8_t flags;
-    uint8_t classification;
-    uint8_t user;
-    int16_t scanAngle;
-    uint16_t pointSourceId;
-    double gpsTime;
-
-    istream >> intensity >> returnInfo >> flags >> classification >> user >>
-        scanAngle >> pointSourceId >> gpsTime;
-
-    uint8_t returnNum = returnInfo & 0x0F;
-    uint8_t numReturns = (returnInfo >> 4) & 0x0F;
-    uint8_t classFlags = flags & 0x0F;
-    uint8_t scanChannel = (flags >> 4) & 0x03;
-    uint8_t scanDirFlag = (flags >> 6) & 0x01;
-    uint8_t flight = (flags >> 7) & 0x01;
-
-    point.setField(Dimension::Id::X, x);
-    point.setField(Dimension::Id::Y, y);
-    point.setField(Dimension::Id::Z, z);
-    point.setField(Dimension::Id::Intensity, intensity);
-    point.setField(Dimension::Id::ReturnNumber, returnNum);
-    point.setField(Dimension::Id::NumberOfReturns, numReturns);
-    point.setField(Dimension::Id::ClassFlags, classFlags);
-    point.setField(Dimension::Id::ScanChannel, scanChannel);
-    point.setField(Dimension::Id::ScanDirectionFlag, scanDirFlag);
-    point.setField(Dimension::Id::EdgeOfFlightLine, flight);
-    point.setField(Dimension::Id::Classification, classification);
-    point.setField(Dimension::Id::ScanAngleRank, scanAngle * .006);
-    point.setField(Dimension::Id::UserData, user);
-    point.setField(Dimension::Id::PointSourceId, pointSourceId);
-    point.setField(Dimension::Id::GpsTime, gpsTime);
-
-    if (h.hasColor())
-    {
-        uint16_t red, green, blue;
-        istream >> red >> green >> blue;
-        point.setField(Dimension::Id::Red, red);
-        point.setField(Dimension::Id::Green, green);
-        point.setField(Dimension::Id::Blue, blue);
-    }
-
-    if (h.hasInfrared())
-    {
-        uint16_t nearInfraRed;
-
-        istream >> nearInfraRed;
-        point.setField(Dimension::Id::Infrared, nearInfraRed);
-    }
-
-    if (m_extraDims.size())
-        loadExtraDims(istream, point);
-}
-
-
-void LasReader::loadExtraDims(LeExtractor& istream, PointRef& point)
-{
-    for (auto& dim : m_extraDims)
-    {
-        // Dimension type of None is undefined and unprocessed
-        if (dim.m_dimType.m_type == Dimension::Type::None)
-        {
-            istream.skip(dim.m_size);
-            continue;
-        }
-
-        Everything e = Utils::extractDim(istream, dim.m_dimType.m_type);
-        if (dim.m_dimType.m_xform.nonstandard())
-        {
-            double d = Utils::toDouble(e, dim.m_dimType.m_type);
-            d = d * dim.m_dimType.m_xform.m_scale.m_val +
-                dim.m_dimType.m_xform.m_offset.m_val;
-            point.setField(dim.m_dimType.m_id, d);
-        }
-        else
-            point.setField(dim.m_dimType.m_id, dim.m_dimType.m_type, &e);
-    }
-}
-
-
-void LasReader::done(PointTableRef)
-{
-#ifdef PDAL_HAVE_LASZIP
-    m_zipPoint.reset();
-    m_unzipper.reset();
-#endif
-    m_streamIf.reset();
-}
-
-} // namespace pdal
diff --git a/io/las/LasReader.hpp b/io/las/LasReader.hpp
deleted file mode 100644
index 6d68ff0..0000000
--- a/io/las/LasReader.hpp
+++ /dev/null
@@ -1,152 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_export.hpp>
-#include <pdal/plugin.hpp>
-#include <pdal/Compression.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/Reader.hpp>
-
-#include "LasError.hpp"
-#include "LasHeader.hpp"
-#include "LasUtils.hpp"
-#include "ZipPoint.hpp"
-
-extern "C" int32_t LasReader_ExitFunc();
-extern "C" PF_ExitFunc LasReader_InitPlugin();
-
-namespace pdal
-{
-
-class NitfReader;
-class LasHeader;
-class LeExtractor;
-class PointDimensions;
-
-class PDAL_DLL LasReader : public pdal::Reader
-{
-protected:
-    class LasStreamIf
-    {
-    protected:
-        LasStreamIf()
-        {}
-
-    public:
-        LasStreamIf(const std::string& filename)
-            { m_istream = Utils::openFile(filename); }
-
-        ~LasStreamIf()
-        {
-            if (m_istream)
-                Utils::closeFile(m_istream);
-        }
-
-        std::istream *m_istream;
-    };
-
-    friend class NitfReader;
-public:
-    LasReader() : pdal::Reader(), m_index(0)
-        {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-    const LasHeader& header() const
-        { return m_header; }
-    point_count_t getNumPoints() const
-        { return m_header.pointCount(); }
-
-protected:
-    virtual void createStream()
-    {
-        if (m_streamIf)
-            std::cerr << "Attempt to create stream twice!\n";
-        m_streamIf.reset(new LasStreamIf(m_filename));
-        if (!m_streamIf->m_istream)
-        {
-            std::ostringstream oss;
-            oss << "Unable to create open stream for '"
-                << m_filename <<"' with error '" << strerror(errno) <<"'";
-            throw pdal_error(oss.str());
-        }
-    }
-
-    std::unique_ptr<LasStreamIf> m_streamIf;
-
-private:
-    LasError m_error;
-    LasHeader m_header;
-    std::unique_ptr<ZipPoint> m_zipPoint;
-    std::unique_ptr<LASunzipper> m_unzipper;
-    std::unique_ptr<LazPerfVlrDecompressor> m_decompressor;
-    std::vector<char> m_decompressorBuf;
-    point_count_t m_index;
-    StringList m_extraDimSpec;
-    std::vector<ExtraDim> m_extraDims;
-    std::string m_compression;
-
-    virtual void addArgs(ProgramArgs& args);
-    virtual void initialize(PointTableRef table)
-        { initializeLocal(table, m_metadata); }
-    virtual void initializeLocal(PointTableRef table, MetadataNode& m);
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual QuickInfo inspect();
-    virtual void ready(PointTableRef table);
-    virtual point_count_t read(PointViewPtr view, point_count_t count);
-    virtual bool processOne(PointRef& point);
-    virtual void done(PointTableRef table);
-    virtual bool eof()
-        { return m_index >= getNumPoints(); }
-
-    void setSrs(MetadataNode& m);
-    void readExtraBytesVlr();
-    void extractHeaderMetadata(MetadataNode& forward, MetadataNode& m);
-    void extractVlrMetadata(MetadataNode& forward, MetadataNode& m);
-    void loadPoint(PointRef& point, char *buf, size_t bufsize);
-    void loadPointV10(PointRef& point, char *buf, size_t bufsize);
-    void loadPointV14(PointRef& point, char *buf, size_t bufsize);
-    void loadExtraDims(LeExtractor& istream, PointRef& data);
-    point_count_t readFileBlock(std::vector<char>& buf,
-        point_count_t maxPoints);
-
-    LasReader& operator=(const LasReader&); // not implemented
-    LasReader(const LasReader&); // not implemented
-};
-
-} // namespace pdal
diff --git a/io/las/LasWriter.cpp b/io/las/LasWriter.cpp
deleted file mode 100644
index 233235d..0000000
--- a/io/las/LasWriter.cpp
+++ /dev/null
@@ -1,935 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "LasWriter.hpp"
-
-#include <iostream>
-
-#include <pdal/Compression.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/util/Algorithm.hpp>
-#include <pdal/util/Inserter.hpp>
-#include <pdal/util/OStream.hpp>
-#include <pdal/util/Utils.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#ifdef PDAL_HAVE_LIBGEOTIFF
-#include "GeotiffSupport.hpp"
-#endif
-#include "ZipPoint.hpp"
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "writers.las",
-    "ASPRS LAS 1.0 - 1.4 writer. LASzip support is also \n" \
-        "available if enabled at compile-time. Note that LAZ \n" \
-        "does not provide LAS 1.4 support at this time.",
-    "http://pdal.io/stages/writers.las.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, LasWriter, Writer, s_info)
-
-std::string LasWriter::getName() const { return s_info.name; }
-
-LasWriter::LasWriter() : m_ostream(NULL), m_compression(LasCompression::None)
-{}
-
-
-void LasWriter::addArgs(ProgramArgs& args)
-{
-    std::time_t now;
-    std::time(&now);
-    std::tm* ptm = std::gmtime(&now);
-    uint16_t year = ptm->tm_year + 1900;
-    uint16_t doy = ptm->tm_yday;
-
-    args.add("filename", "Output filename", m_filename).setPositional();
-    args.add("a_srs", "Spatial reference to use to write output", m_aSrs);
-    args.add("compression", "Compression to use for output ('LASZIP' or "
-        "'LAZPERF')", m_compression, LasCompression::None);
-    args.add("discard_high_return_numbers", "Discard points with out-of-spec "
-        "return numbers.", m_discardHighReturnNumbers);
-    args.add("extra_dims", "Dimensions to write above those in point format",
-        m_extraDimSpec);
-    args.add("forward", "Dimensions to forward from LAS reader", m_forwardSpec);
-
-    args.add("major_version", "LAS major version", m_majorVersion,
-        decltype(m_majorVersion)(1));
-    args.add("minor_version", "LAS minor version", m_minorVersion,
-        decltype(m_minorVersion)(2));
-    args.add("dataformat_id", "Point format", m_dataformatId,
-        decltype(m_dataformatId)(3));
-    args.add("format", "Point format", m_dataformatId,
-        decltype(m_dataformatId)(3));
-    args.add("global_encoding", "Global encoding byte", m_globalEncoding);
-    args.add("project_id", "Project ID", m_projectId);
-    args.add("system_id", "System ID", m_systemId,
-        decltype(m_systemId)(m_lasHeader.getSystemIdentifier()));
-    args.add("software_id", "Software ID", m_softwareId,
-        decltype(m_softwareId)(GetDefaultSoftwareId()));
-    args.add("creation_doy", "Creation day of year", m_creationDoy,
-        decltype(m_creationDoy)(doy));
-    args.add("creation_year", "Creation year", m_creationYear,
-        decltype(m_creationYear)(year));
-    args.add("scale_x", "X scale factor", m_scaleX, decltype(m_scaleX)(".01"));
-    args.add("scale_y", "Y scale factor", m_scaleY, decltype(m_scaleY)(".01"));
-    args.add("scale_z", "Z scale factor", m_scaleZ, decltype(m_scaleZ)(".01"));
-    args.add("offset_x", "X offset", m_offsetX);
-    args.add("offset_y", "Y offset", m_offsetY);
-    args.add("offset_z", "Z offset", m_offsetZ);
-}
-
-void LasWriter::initialize()
-{
-    std::string ext = FileUtils::extension(m_filename);
-    ext = Utils::tolower(ext);
-    if ((ext == ".laz") && (m_compression == LasCompression::None))
-        m_compression = LasCompression::LasZip;
-
-    if (!m_aSrs.empty())
-        setSpatialReference(m_aSrs);
-    if (m_compression != LasCompression::None)
-        m_lasHeader.setCompressed(true);
-#if !defined(PDAL_HAVE_LASZIP) && !defined(PDAL_HAVE_LAZPERF)
-    if (m_compression != LasCompression::None)
-        throw pdal_error("Can't write LAZ output.  "
-            "PDAL not built with LASzip or LAZperf.");
-#endif
-    m_extraDims = LasUtils::parse(m_extraDimSpec);
-    fillForwardList();
-}
-
-
-void LasWriter::prepared(PointTableRef table)
-{
-    FlexWriter::validateFilename(table);
-
-    PointLayoutPtr layout = table.layout();
-
-    // If we've asked for all dimensions, add to extraDims all dimensions
-    // in the layout that aren't already destined for LAS output.
-    if (m_extraDims.size() == 1 && m_extraDims[0].m_name == "all")
-    {
-        m_extraDims.clear();
-        Dimension::IdList ids = m_lasHeader.usedDims();
-        DimTypeList dimTypes = layout->dimTypes();
-        for (auto& dt : dimTypes)
-        {
-            if (!Utils::contains(ids, dt.m_id))
-                m_extraDims.push_back(
-                    ExtraDim(layout->dimName(dt.m_id), dt.m_type));
-        }
-    }
-
-    m_extraByteLen = 0;
-    for (auto& dim : m_extraDims)
-    {
-        dim.m_dimType.m_id = table.layout()->findDim(dim.m_name);
-        if (dim.m_dimType.m_id == Dimension::Id::Unknown)
-        {
-            std::ostringstream oss;
-            oss << "Dimension '" << dim.m_name << "' specified in "
-                "'extra_dim' option not found.";
-            throw pdal_error(oss.str());
-        }
-        m_extraByteLen += Dimension::size(dim.m_dimType.m_type);
-    }
-}
-
-
-// Get header info from options and store in map for processing with
-// metadata.
-void LasWriter::fillForwardList()
-{
-    static const StringList header = {
-        "dataformat_id", "major_version", "minor_version", "filesource_id",
-        "global_encoding", "project_id", "system_id", "software_id",
-        "creation_doy", "creation_year"
-    };
-
-    static const StringList scale = { "scale_x", "scale_y", "scale_z" };
-
-    static const StringList offset = { "offset_x", "offset_y", "offset_z" };
-
-    static StringList all;
-    all.insert(all.begin(), header.begin(), header.end());
-    all.insert(all.begin(), scale.begin(), scale.end());
-    all.insert(all.begin(), offset.begin(), offset.end());
-
-    // Build the forward list, replacing special keywords with the proper
-    // field names.
-    for (auto& name : m_forwardSpec)
-    {
-        if (name == "all")
-        {
-            m_forwards.insert(all.begin(), all.end());
-            m_forwardVlrs = true;
-        }
-        else if (name == "header")
-            m_forwards.insert(header.begin(), header.end());
-        else if (name == "scale")
-            m_forwards.insert(scale.begin(), scale.end());
-        else if (name == "offset")
-            m_forwards.insert(offset.begin(), offset.end());
-        else if (name == "format")
-            m_forwards.insert("dataformat_id");
-        else if (name == "vlr")
-            m_forwardVlrs = true;
-        else if (Utils::contains(all, name))
-            m_forwards.insert(name);
-        else
-        {
-            std::ostringstream oss;
-
-            oss << "Error in 'forward' option.  Unknown field for "
-                "forwarding: '" << name << "'.";
-            throw pdal_error(oss.str());
-        }
-    }
-}
-
-
-void LasWriter::readyTable(PointTableRef table)
-{
-    m_forwardMetadata = table.privateMetadata("lasforward");
-    setExtraBytesVlr();
-}
-
-
-void LasWriter::readyFile(const std::string& filename,
-    const SpatialReference& srs)
-{
-    std::ostream *out = Utils::createFile(filename, true);
-    if (!out)
-    {
-        std::stringstream out;
-
-        out << "writers.las couldn't open file '" << filename <<
-            "' for output.";
-        throw pdal_error(out.str());
-    }
-    m_curFilename = filename;
-    m_error.setFilename(filename);
-    Utils::writeProgress(m_progressFd, "READYFILE", filename);
-    prepOutput(out, srs);
-}
-
-
-void LasWriter::prepOutput(std::ostream *outStream, const SpatialReference& srs)
-{
-    // Use stage SRS if provided.
-    m_srs = getSpatialReference().empty() ? srs : getSpatialReference();
-
-    handleHeaderForwards(m_forwardMetadata);
-
-    // Filling the header here gives the VLR functions below easy access to
-    // the version information and so on.
-    fillHeader();
-
-    // Spatial reference can potentially change for multiple output files.
-    setVlrsFromSpatialRef();
-    setVlrsFromMetadata(m_forwardMetadata);
-
-    m_summaryData.reset(new SummaryData());
-    m_ostream = outStream;
-    if (m_lasHeader.compressed())
-        readyCompression();
-
-    // Compression should cause the last of the VLRs to get filled.  We now
-    // have a valid count, so fill the header again.
-    fillHeader();
-
-    // Write the header.
-    OLeStream out(m_ostream);
-    out << m_lasHeader;
-
-    m_lasHeader.setVlrOffset((uint32_t)m_ostream->tellp());
-
-    for (auto vi = m_vlrs.begin(); vi != m_vlrs.end(); ++vi)
-    {
-        VariableLengthRecord& vlr = *vi;
-        vlr.write(out, m_lasHeader.versionEquals(1, 0) ? 0xAABB : 0);
-    }
-
-    // Write the point data start signature for version 1.0.
-    if (m_lasHeader.versionEquals(1, 0))
-        out << (uint16_t)0xCCDD;
-    m_lasHeader.setPointOffset((uint32_t)m_ostream->tellp());
-    if (m_compression == LasCompression::LasZip)
-        openCompression();
-
-    // Set the point buffer size here in case we're using the streaming
-    // interface.
-    m_pointBuf.resize(m_lasHeader.pointLen());
-
-    m_error.setLog(log());
-}
-
-
-/// Search for metadata associated with the provided recordId and userId.
-/// \param  node - Top-level node to use for metadata search.
-/// \param  recordId - Record ID to match.
-/// \param  userId - User ID to match.
-MetadataNode LasWriter::findVlrMetadata(MetadataNode node,
-    uint16_t recordId, const std::string& userId)
-{
-    std::string sRecordId = std::to_string(recordId);
-
-    // Find a node whose name starts with vlr and that has child nodes
-    // with the name and recordId we're looking for.
-    auto pred = [sRecordId,userId](MetadataNode n)
-    {
-        auto recPred = [sRecordId](MetadataNode n)
-        {
-            return n.name() == "record_id" &&
-                n.value() == sRecordId;
-        };
-        auto userPred = [userId](MetadataNode n)
-        {
-            return n.name() == "user_id" &&
-                n.value() == userId;
-        };
-        return (Utils::startsWith(n.name(), "vlr") &&
-            !n.findChild(recPred).empty() &&
-            !n.findChild(userPred).empty());
-    };
-    return node.find(pred);
-}
-
-
-/// Set VLRs from metadata for forwarded info.
-void LasWriter::setVlrsFromMetadata(MetadataNode& forward)
-{
-    std::vector<uint8_t> data;
-
-    if (!m_forwardVlrs)
-        return;
-
-    auto pred = [](MetadataNode n)
-        { return Utils::startsWith(n.name(), "vlr_"); };
-
-    MetadataNodeList nodes = forward.findChildren(pred);
-    for (auto& n : nodes)
-    {
-        const MetadataNode& userIdNode = n.findChild("user_id");
-        const MetadataNode& recordIdNode = n.findChild("record_id");
-        if (recordIdNode.valid() && userIdNode.valid())
-        {
-            data = Utils::base64_decode(n.value());
-            uint16_t recordId = (uint16_t)std::stoi(recordIdNode.value());
-            addVlr(userIdNode.value(), recordId, n.description(), data);
-        }
-    }
-}
-
-
-/// Set VLRs from the active spatial reference.
-/// \param  srs - Active spatial reference.
-void LasWriter::setVlrsFromSpatialRef()
-{
-    // Delete any existing spatial ref VLRs.  This can be an issue if we're
-    // using the reader to write multiple output files via a filename template.
-    deleteVlr(TRANSFORM_USER_ID, GEOTIFF_DIRECTORY_RECORD_ID);
-    deleteVlr(TRANSFORM_USER_ID, GEOTIFF_DOUBLES_RECORD_ID);
-    deleteVlr(TRANSFORM_USER_ID, GEOTIFF_ASCII_RECORD_ID);
-    deleteVlr(TRANSFORM_USER_ID, WKT_RECORD_ID);
-    deleteVlr(LIBLAS_USER_ID, WKT_RECORD_ID);
-
-    if (m_lasHeader.versionAtLeast(1, 4))
-        addWktVlr();
-    else
-        addGeotiffVlrs();
-}
-
-void LasWriter::addGeotiffVlrs()
-{
-#ifdef PDAL_HAVE_LIBGEOTIFF
-    GeotiffSupport geotiff;
-    geotiff.resetTags();
-
-    std::string wkt = m_srs.getWKT(SpatialReference::eCompoundOK, false);
-    geotiff.setWkt(wkt);
-
-    addGeotiffVlr(geotiff, GEOTIFF_DIRECTORY_RECORD_ID,
-        "GeoTiff GeoKeyDirectoryTag");
-    addGeotiffVlr(geotiff, GEOTIFF_DOUBLES_RECORD_ID,
-        "GeoTiff GeoDoubleParamsTag");
-    addGeotiffVlr(geotiff, GEOTIFF_ASCII_RECORD_ID,
-        "GeoTiff GeoAsciiParamsTag");
-#else
-    log()->get(LogLevel::Error) << getName() << ": PDAL not built with "
-        "libGeoTiff.  Can't write valid LAS file." << std::endl;
-#endif // PDAL_HAVE_LIBGEOTIFF
-}
-
-
-/// Add a geotiff VLR from the information associated with the record ID.
-/// \param  geotiff - Geotiff support structure reference.
-/// \param  recordId - Record ID associated with the VLR/Geotiff ref.
-/// \param  description - Description to use with the VLR
-/// \return  Whether the VLR was added.
-void LasWriter::addGeotiffVlr(GeotiffSupport& geotiff, uint16_t recordId,
-    const std::string& description)
-{
-#ifdef PDAL_HAVE_LIBGEOTIFF
-    void *data;
-    int count;
-
-    size_t size = geotiff.getKey(recordId, &count, &data);
-    if (size == 0)
-    {
-        log()->get(LogLevel::Warning) << getName() << ": Invalid spatial "
-            "reference for writing GeoTiff VLR." << std::endl;
-        return;
-    }
-
-    std::vector<uint8_t> buf(size);
-    memcpy(buf.data(), data, size);
-    addVlr(TRANSFORM_USER_ID, recordId, description, buf);
-#endif // PDAL_HAVE_LIBGEOTIFF
-}
-
-
-/// Add a Well-known Text VLR associated with the spatial reference.
-/// \return  Whether the VLR was added.
-bool LasWriter::addWktVlr()
-{
-    std::string wkt = m_srs.getWKT(SpatialReference::eCompoundOK);
-    if (wkt.empty())
-        return false;
-
-    std::vector<uint8_t> wktBytes(wkt.begin(), wkt.end());
-    // This tacks a NULL to the end of the data, which is required by the spec.
-    wktBytes.resize(wktBytes.size() + 1, 0);
-    addVlr(TRANSFORM_USER_ID, WKT_RECORD_ID, "OGC Transformation Record",
-        wktBytes);
-
-    // The data in the vector gets moved to the VLR, so we have to recreate it.
-    std::vector<uint8_t> wktBytes2(wkt.begin(), wkt.end());
-    wktBytes2.resize(wktBytes2.size() + 1, 0);
-    addVlr(LIBLAS_USER_ID, WKT_RECORD_ID,
-        "OGR variant of OpenGIS WKT SRS", wktBytes2);
-    return true;
-}
-
-
-void LasWriter::setExtraBytesVlr()
-{
-    if (m_extraDims.empty())
-        return;
-
-    std::vector<uint8_t> ebBytes;
-    for (auto& dim : m_extraDims)
-    {
-        ExtraBytesIf eb(dim.m_name, dim.m_dimType.m_type,
-            Dimension::description(dim.m_dimType.m_id));
-        eb.appendTo(ebBytes);
-    }
-
-    addVlr(SPEC_USER_ID, EXTRA_BYTES_RECORD_ID, "Extra Bytes Record", ebBytes);
-}
-
-
-/// Add a standard or variable-length VLR depending on the data size.
-/// \param  userId - VLR user ID
-/// \param  recordId - VLR record ID
-/// \param  description - VLR description
-/// \param  data - Raw VLR data
-void LasWriter::addVlr(const std::string& userId, uint16_t recordId,
-   const std::string& description, std::vector<uint8_t>& data)
-{
-    if (data.size() > VariableLengthRecord::MAX_DATA_SIZE)
-    {
-        ExtVariableLengthRecord evlr(userId, recordId, description, data);
-        m_eVlrs.push_back(std::move(evlr));
-    }
-    else
-    {
-        VariableLengthRecord vlr(userId, recordId, description, data);
-        m_vlrs.push_back(std::move(vlr));
-    }
-}
-
-/// Delete a VLR from the vlr list.
-///
-void LasWriter::deleteVlr(const std::string& userId, uint16_t recordId)
-{
-    auto matches = [&userId, recordId](const VariableLengthRecord& vlr)
-    {
-        return vlr.matches(userId, recordId);
-    };
-
-    Utils::remove_if(m_vlrs, matches);
-    Utils::remove_if(m_eVlrs, matches);
-}
-
-
-template <typename T>
-void LasWriter::handleHeaderForward(const std::string& s, T& headerVal,
-    const MetadataNode& base)
-{
-    if (Utils::contains(m_forwards, s) && !headerVal.valSet())
-    {
-        MetadataNode invalid = base.findChild(s + "INVALID");
-        MetadataNode m = base.findChild(s);
-        if (!invalid.valid() && m.valid())
-            headerVal.setVal(m.value<typename T::type>());
-    }
-}
-
-
-void LasWriter::handleHeaderForwards(MetadataNode& forward)
-{
-    handleHeaderForward("major_version", m_majorVersion, forward);
-    handleHeaderForward("minor_version", m_minorVersion, forward);
-    handleHeaderForward("dataformat_id", m_dataformatId, forward);
-    handleHeaderForward("filesource_id", m_filesourceId, forward);
-    handleHeaderForward("global_encoding", m_globalEncoding, forward);
-    handleHeaderForward("project_id", m_projectId, forward);
-    handleHeaderForward("system_id", m_systemId, forward);
-    handleHeaderForward("software_id", m_softwareId, forward);
-    handleHeaderForward("creation_doy", m_creationDoy, forward);
-    handleHeaderForward("creation_year", m_creationYear, forward);
-
-    handleHeaderForward("scale_x", m_scaleX, forward);
-    handleHeaderForward("scale_y", m_scaleY, forward);
-    handleHeaderForward("scale_z", m_scaleZ, forward);
-    handleHeaderForward("offset_x", m_offsetX, forward);
-    handleHeaderForward("offset_y", m_offsetY, forward);
-    handleHeaderForward("offset_z", m_offsetZ, forward);
-
-    m_scaling.m_xXform.m_scale.set(m_scaleX.val());
-    m_scaling.m_yXform.m_scale.set(m_scaleY.val());
-    m_scaling.m_zXform.m_scale.set(m_scaleZ.val());
-    m_scaling.m_xXform.m_offset.set(m_offsetX.val());
-    m_scaling.m_yXform.m_offset.set(m_offsetY.val());
-    m_scaling.m_zXform.m_offset.set(m_offsetZ.val());
-}
-
-/// Fill the LAS header with values as provided in options or forwarded
-/// metadata.
-void LasWriter::fillHeader()
-{
-    const uint16_t WKT_MASK = (1 << 4);
-
-    m_lasHeader.setScaling(m_scaling);
-    m_lasHeader.setVlrCount(m_vlrs.size());
-    m_lasHeader.setEVlrCount(m_eVlrs.size());
-
-    m_lasHeader.setPointFormat(m_dataformatId.val());
-    m_lasHeader.setPointLen(m_lasHeader.basePointLen() + m_extraByteLen);
-    m_lasHeader.setVersionMinor(m_minorVersion.val());
-    m_lasHeader.setCreationYear(m_creationYear.val());
-    m_lasHeader.setCreationDOY(m_creationDoy.val());
-    m_lasHeader.setSoftwareId(m_softwareId.val());
-    m_lasHeader.setSystemId(m_systemId.val());
-    m_lasHeader.setProjectId(m_projectId.val());
-    m_lasHeader.setFileSourceId(m_filesourceId.val());
-
-    // We always write a WKT VLR for version 1.4 and later.
-    uint16_t globalEncoding = m_globalEncoding.val();
-    if (m_lasHeader.versionAtLeast(1, 4))
-        globalEncoding |= WKT_MASK;
-    m_lasHeader.setGlobalEncoding(globalEncoding);
-
-    if (!m_lasHeader.pointFormatSupported())
-    {
-        std::ostringstream oss;
-        oss << "Unsupported LAS output point format: " <<
-            (int)m_lasHeader.pointFormat() << ".";
-        throw pdal_error(oss.str());
-    }
-}
-
-
-void LasWriter::readyCompression()
-{
-    if (m_compression == LasCompression::LasZip)
-        readyLasZipCompression();
-    else if (m_compression == LasCompression::LazPerf)
-        readyLazPerfCompression();
-}
-
-
-void LasWriter::readyLasZipCompression()
-{
-#ifdef PDAL_HAVE_LASZIP
-    m_zipPoint.reset(new ZipPoint(m_lasHeader.pointFormat(),
-        m_lasHeader.pointLen()));
-    m_zipper.reset(new LASzipper());
-    // Note: this will make the VLR count in the header incorrect, but we
-    // rewrite that bit in finishOutput() to fix it up.
-    std::vector<uint8_t> data = m_zipPoint->vlrData();
-    addVlr(LASZIP_USER_ID, LASZIP_RECORD_ID, "http://laszip.org", data);
-#endif
-}
-
-
-void LasWriter::readyLazPerfCompression()
-{
-#ifdef PDAL_HAVE_LAZPERF
-    if (m_lasHeader.versionAtLeast(1, 4))
-        throw pdal_error("Can't write version 1.4 output with LAZperf.");
-
-    laszip::factory::record_schema schema;
-    schema.push(laszip::factory::record_item::POINT10);
-    if (m_lasHeader.hasTime())
-        schema.push(laszip::factory::record_item::GPSTIME);
-    if (m_lasHeader.hasColor())
-        schema.push(laszip::factory::record_item::RGB12);
-    laszip::io::laz_vlr zipvlr = laszip::io::laz_vlr::from_schema(schema);
-    std::vector<uint8_t> data(zipvlr.size());
-    zipvlr.extract((char *)data.data());
-    addVlr(LASZIP_USER_ID, LASZIP_RECORD_ID, "http://laszip.org", data);
-
-    m_compressor.reset(new LazPerfVlrCompressor(*m_ostream, schema,
-        zipvlr.chunk_size));
-#endif
-}
-
-
-/// Prepare the compressor to write points.
-/// \param  pointFormat - Formt of points we're writing.
-void LasWriter::openCompression()
-{
-#ifdef PDAL_HAVE_LASZIP
-    if (!m_zipper->open(*m_ostream, m_zipPoint->GetZipper()))
-    {
-        std::ostringstream oss;
-        const char* err = m_zipper->get_error();
-        if (err == NULL)
-            err = "(unknown error)";
-        oss << "Error opening LASzipper: " << std::string(err);
-        throw pdal_error(oss.str());
-    }
-#endif
-}
-
-
-bool LasWriter::processOne(PointRef& point)
-{
-    //ABELL - Need to do something about auto offset.
-    LeInserter ostream(m_pointBuf.data(), m_pointBuf.size());
-
-    if (!fillPointBuf(point, ostream))
-        return false;
-
-    if (m_compression == LasCompression::LasZip)
-        writeLasZipBuf(m_pointBuf.data(), m_lasHeader.pointLen(), 1);
-    else if (m_compression == LasCompression::LazPerf)
-        writeLazPerfBuf(m_pointBuf.data(), m_lasHeader.pointLen(), 1);
-    else
-        m_ostream->write(m_pointBuf.data(), m_lasHeader.pointLen());
-    return true;
-}
-
-
-void LasWriter::writeView(const PointViewPtr view)
-{
-    Utils::writeProgress(m_progressFd, "READYVIEW",
-        std::to_string(view->size()));
-    m_scaling.setAutoXForm(view);
-
-    point_count_t pointLen = m_lasHeader.pointLen();
-
-    // Make a buffer of at most a meg.
-    m_pointBuf.resize(std::min((point_count_t)1000000, pointLen * view->size()));
-
-    const PointView& viewRef(*view.get());
-
-    point_count_t remaining = view->size();
-    PointId idx = 0;
-    while (remaining)
-    {
-        point_count_t filled = fillWriteBuf(viewRef, idx, m_pointBuf);
-        idx += filled;
-        remaining -= filled;
-
-        if (m_compression == LasCompression::LasZip)
-            writeLasZipBuf(m_pointBuf.data(), pointLen, filled);
-        else if (m_compression == LasCompression::LazPerf)
-            writeLazPerfBuf(m_pointBuf.data(), pointLen, filled);
-        else
-            m_ostream->write(m_pointBuf.data(), filled * pointLen);
-    }
-    Utils::writeProgress(m_progressFd, "DONEVIEW",
-        std::to_string(view->size()));
-}
-
-
-void LasWriter::writeLasZipBuf(char *pos, size_t pointLen, point_count_t numPts)
-{
-#ifdef PDAL_HAVE_LASZIP
-    for (point_count_t i = 0; i < numPts; i++)
-    {
-        memcpy(m_zipPoint->m_lz_point_data.data(), pos, pointLen);
-        if (!m_zipper->write(m_zipPoint->m_lz_point))
-        {
-            std::ostringstream oss;
-            const char* err = m_zipper->get_error();
-            if (err == NULL)
-                err = "(unknown error)";
-            oss << "Error writing point: " << std::string(err);
-            throw pdal_error(oss.str());
-        }
-        pos += pointLen;
-    }
-#endif
-}
-
-
-void LasWriter::writeLazPerfBuf(char *pos, size_t pointLen,
-    point_count_t numPts)
-{
-#ifdef PDAL_HAVE_LAZPERF
-    for (point_count_t i = 0; i < numPts; i++)
-    {
-        m_compressor->compress(pos);
-        pos += pointLen;
-    }
-#endif
-}
-
-
-bool LasWriter::fillPointBuf(PointRef& point, LeInserter& ostream)
-{
-    bool has14Format = m_lasHeader.has14Format();
-    bool hasColor = m_lasHeader.hasColor();
-    bool hasTime = m_lasHeader.hasTime();
-    bool hasInfrared = m_lasHeader.hasInfrared();
-    static const size_t maxReturnCount = m_lasHeader.maxReturnCount();
-
-    // we always write the base fields
-    using namespace Dimension;
-
-    uint8_t returnNumber(1);
-    uint8_t numberOfReturns(1);
-    if (point.hasDim(Id::ReturnNumber))
-    {
-        returnNumber = point.getFieldAs<uint8_t>(Id::ReturnNumber);
-        if (returnNumber < 1 || returnNumber > maxReturnCount)
-            m_error.returnNumWarning(returnNumber);
-    }
-    if (point.hasDim(Id::NumberOfReturns))
-        numberOfReturns = point.getFieldAs<uint8_t>(Id::NumberOfReturns);
-    if (numberOfReturns == 0)
-        m_error.numReturnsWarning(0);
-    if (numberOfReturns > maxReturnCount)
-    {
-        if (m_discardHighReturnNumbers)
-        {
-            // If this return number is too high, pitch the point.
-            if (returnNumber > maxReturnCount)
-                return false;
-            numberOfReturns = maxReturnCount;
-        }
-        else
-            m_error.numReturnsWarning(numberOfReturns);
-    }
-
-    auto converter = [this](double d, Dimension::Id dim) -> int32_t
-    {
-        int32_t i;
-
-        if (!Utils::numericCast(d, i))
-        {
-            std::ostringstream oss;
-            oss << "Unable to convert scaled value (" << d << ") to "
-                "int32 for dimension '" << Dimension::name(dim) <<
-                "' when writing LAS/LAZ file " << m_curFilename << ".";
-            throw pdal_error(oss.str());
-        }
-        return i;
-    };
-
-    double xOrig = point.getFieldAs<double>(Id::X);
-    double yOrig = point.getFieldAs<double>(Id::Y);
-    double zOrig = point.getFieldAs<double>(Id::Z);
-    double x = m_scaling.m_xXform.toScaled(xOrig);
-    double y = m_scaling.m_yXform.toScaled(yOrig);
-    double z = m_scaling.m_zXform.toScaled(zOrig);
-
-    ostream << converter(x, Id::X);
-    ostream << converter(y, Id::Y);
-    ostream << converter(z, Id::Z);
-
-    ostream << point.getFieldAs<uint16_t>(Id::Intensity);
-
-    uint8_t scanChannel = point.getFieldAs<uint8_t>(Id::ScanChannel);
-    uint8_t scanDirectionFlag =
-        point.getFieldAs<uint8_t>(Id::ScanDirectionFlag);
-    uint8_t edgeOfFlightLine =
-        point.getFieldAs<uint8_t>(Id::EdgeOfFlightLine);
-
-    if (has14Format)
-    {
-        uint8_t bits = returnNumber | (numberOfReturns << 4);
-        ostream << bits;
-
-        uint8_t classFlags = point.getFieldAs<uint8_t>(Id::ClassFlags);
-        bits = (classFlags & 0x0F) |
-            ((scanChannel & 0x03) << 4) |
-            ((scanDirectionFlag & 0x01) << 6) |
-            ((edgeOfFlightLine & 0x01) << 7);
-        ostream << bits;
-    }
-    else
-    {
-        uint8_t bits = returnNumber | (numberOfReturns << 3) |
-            (scanDirectionFlag << 6) | (edgeOfFlightLine << 7);
-        ostream << bits;
-    }
-
-    ostream << point.getFieldAs<uint8_t>(Id::Classification);
-
-    uint8_t userData = point.getFieldAs<uint8_t>(Id::UserData);
-    if (has14Format)
-    {
-         int16_t scanAngleRank =
-             point.getFieldAs<float>(Id::ScanAngleRank) / .006;
-         ostream << userData << scanAngleRank;
-    }
-    else
-    {
-        int8_t scanAngleRank = point.getFieldAs<int8_t>(Id::ScanAngleRank);
-        ostream << scanAngleRank << userData;
-    }
-
-    ostream << point.getFieldAs<uint16_t>(Id::PointSourceId);
-
-    if (hasTime)
-        ostream << point.getFieldAs<double>(Id::GpsTime);
-
-    if (hasColor)
-    {
-        ostream << point.getFieldAs<uint16_t>(Id::Red);
-        ostream << point.getFieldAs<uint16_t>(Id::Green);
-        ostream << point.getFieldAs<uint16_t>(Id::Blue);
-    }
-
-    if (hasInfrared)
-        ostream << point.getFieldAs<uint16_t>(Id::Infrared);
-
-    Everything e;
-    for (auto& dim : m_extraDims)
-    {
-        point.getField((char *)&e, dim.m_dimType.m_id, dim.m_dimType.m_type);
-        Utils::insertDim(ostream, dim.m_dimType.m_type, e);
-    }
-
-    m_summaryData->addPoint(xOrig, yOrig, zOrig, returnNumber);
-    return true;
-}
-
-
-point_count_t LasWriter::fillWriteBuf(const PointView& view,
-    PointId startId, std::vector<char>& buf)
-{
-    point_count_t blocksize = buf.size() / m_lasHeader.pointLen();
-    blocksize = std::min(blocksize, view.size() - startId);
-    PointId lastId = startId + blocksize;
-
-    LeInserter ostream(buf.data(), buf.size());
-    PointRef point = (const_cast<PointView&>(view)).point(0);
-    for (PointId idx = startId; idx < lastId; idx++)
-    {
-        point.setPointId(idx);
-        fillPointBuf(point, ostream);
-    }
-    return blocksize;
-}
-
-
-void LasWriter::doneFile()
-{
-    finishOutput();
-    Utils::writeProgress(m_progressFd, "DONEFILE", m_curFilename);
-    m_curFilename.clear();
-    delete m_ostream;
-    m_ostream = NULL;
-}
-
-
-void LasWriter::finishOutput()
-{
-    if (m_compression == LasCompression::LasZip)
-        finishLasZipOutput();
-    else if (m_compression == LasCompression::LazPerf)
-        finishLazPerfOutput();
-
-    log()->get(LogLevel::Debug) << "Wrote " <<
-        m_summaryData->getTotalNumPoints() <<
-        " points to the LAS file" << std::endl;
-
-    OLeStream out(m_ostream);
-
-    for (auto vi = m_eVlrs.begin(); vi != m_eVlrs.end(); ++vi)
-    {
-        ExtVariableLengthRecord evlr = *vi;
-        out << evlr;
-    }
-
-    // Reset the offset/scale since it may have been auto-computed
-    m_lasHeader.setScaling(m_scaling);
-
-    // The summary is calculated as points are written.
-    m_lasHeader.setSummary(*m_summaryData);
-
-    out.seek(0);
-    out << m_lasHeader;
-    out.seek(m_lasHeader.pointOffset());
-
-    m_ostream->flush();
-}
-
-
-void LasWriter::finishLasZipOutput()
-{
-#ifdef PDAL_HAVE_LASZIP
-    m_zipper->close();
-#endif
-}
-
-
-void LasWriter::finishLazPerfOutput()
-{
-#ifdef PDAL_HAVE_LAZPERF
-    m_compressor->done();
-#endif
-}
-
-} // namespace pdal
diff --git a/io/las/LasWriter.hpp b/io/las/LasWriter.hpp
deleted file mode 100644
index f6ceb9f..0000000
--- a/io/las/LasWriter.hpp
+++ /dev/null
@@ -1,173 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Compression.hpp>
-#include <pdal/FlexWriter.hpp>
-#include <pdal/plugin.hpp>
-
-#include "HeaderVal.hpp"
-#include "LasError.hpp"
-#include "LasHeader.hpp"
-#include "LasUtils.hpp"
-#include "SummaryData.hpp"
-#include "ZipPoint.hpp"
-
-extern "C" int32_t LasWriter_ExitFunc();
-extern "C" PF_ExitFunc LasWriter_InitPlugin();
-
-namespace pdal
-{
-class LeInserter;
-class LasTester;
-class NitfWriter;
-class GeotiffSupport;
-
-struct VlrOptionInfo
-{
-    std::string m_name;
-    std::string m_value;
-    std::string m_userId;
-    uint16_t m_recordId;
-    std::string m_description;
-};
-
-class PDAL_DLL LasWriter : public FlexWriter
-{
-    friend class LasTester;
-    friend class NitfWriter;
-public:
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-    LasWriter();
-
-protected:
-    void prepOutput(std::ostream *out, const SpatialReference& srs);
-    void finishOutput();
-
-private:
-    LasError m_error;
-    LasHeader m_lasHeader;
-    std::unique_ptr<SummaryData> m_summaryData;
-    std::unique_ptr<LASzipper> m_zipper;
-    std::unique_ptr<ZipPoint> m_zipPoint;
-    std::unique_ptr<LazPerfVlrCompressor> m_compressor;
-    bool m_discardHighReturnNumbers;
-    std::map<std::string, std::string> m_headerVals;
-    std::vector<VlrOptionInfo> m_optionInfos;
-    std::ostream *m_ostream;
-    std::vector<VariableLengthRecord> m_vlrs;
-    std::vector<ExtVariableLengthRecord> m_eVlrs;
-    StringList m_extraDimSpec;
-    std::vector<ExtraDim> m_extraDims;
-    uint16_t m_extraByteLen;
-    SpatialReference m_srs;
-    std::string m_curFilename;
-    StringList m_forwardSpec;
-    std::set<std::string> m_forwards;
-    bool m_forwardVlrs;
-    LasCompression m_compression;
-    std::vector<char> m_pointBuf;
-    SpatialReference m_aSrs;
-
-    NumHeaderVal<uint8_t, 1, 1> m_majorVersion;
-    NumHeaderVal<uint8_t, 1, 4> m_minorVersion;
-    NumHeaderVal<uint8_t, 0, 10> m_dataformatId;
-    // MSVC doesn't see numeric_limits::max() as constexpr do doesn't allow
-    // it as defaults for templates.  Remove when possible.
-    NumHeaderVal<uint16_t, 0, 65535> m_filesourceId;
-    NumHeaderVal<uint16_t, 0, 31> m_globalEncoding;
-    UuidHeaderVal m_projectId;
-    StringHeaderVal<32> m_systemId;
-    StringHeaderVal<32> m_softwareId;
-    NumHeaderVal<uint16_t, 0, 366> m_creationDoy;
-    // MSVC doesn't see numeric_limits::max() as constexpr so doesn't allow
-    // them as defaults for templates.  Remove when possible.
-    NumHeaderVal<uint16_t, 0, 65535> m_creationYear;
-    StringHeaderVal<20> m_scaleX;
-    StringHeaderVal<20> m_scaleY;
-    StringHeaderVal<20> m_scaleZ;
-    StringHeaderVal<20> m_offsetX;
-    StringHeaderVal<20> m_offsetY;
-    StringHeaderVal<20> m_offsetZ;
-    MetadataNode m_forwardMetadata;
-
-    virtual void addArgs(ProgramArgs& args);
-    virtual void initialize();
-    virtual void prepared(PointTableRef table);
-    virtual void readyTable(PointTableRef table);
-    virtual void readyFile(const std::string& filename,
-        const SpatialReference& srs);
-    virtual void writeView(const PointViewPtr view);
-    virtual bool processOne(PointRef& point);
-    virtual void doneFile();
-
-    void fillForwardList();
-    template <typename T>
-    void handleHeaderForward(const std::string& s, T& headerVal,
-        const MetadataNode& base);
-    void handleHeaderForwards(MetadataNode& forward);
-    void fillHeader();
-    bool fillPointBuf(PointRef& point, LeInserter& ostream);
-    point_count_t fillWriteBuf(const PointView& view, PointId startId,
-        std::vector<char>& buf);
-    void writeLasZipBuf(char *data, size_t pointLen, point_count_t numPts);
-    void writeLazPerfBuf(char *data, size_t pointLen, point_count_t numPts);
-    void setVlrsFromMetadata(MetadataNode& forward);
-    MetadataNode findVlrMetadata(MetadataNode node, uint16_t recordId,
-        const std::string& userId);
-    void setExtraBytesVlr();
-    void setVlrsFromSpatialRef();
-    void readyCompression();
-    void readyLasZipCompression();
-    void readyLazPerfCompression();
-    void openCompression();
-    void addVlr(const std::string& userId, uint16_t recordId,
-        const std::string& description, std::vector<uint8_t>& data);
-    void deleteVlr(const std::string& userId, uint16_t recordId);
-    void addGeotiffVlrs();
-    void addGeotiffVlr(GeotiffSupport& geotiff, uint16_t recordId,
-        const std::string& description);
-    bool addWktVlr();
-    void finishLasZipOutput();
-    void finishLazPerfOutput();
-
-    LasWriter& operator=(const LasWriter&); // not implemented
-    LasWriter(const LasWriter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/io/las/SummaryData.cpp b/io/las/SummaryData.cpp
deleted file mode 100644
index c02cf89..0000000
--- a/io/las/SummaryData.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "SummaryData.hpp"
-
-namespace pdal
-{
-
-SummaryData::SummaryData() :
-    m_minX((std::numeric_limits<double>::max)()),
-    m_minY((std::numeric_limits<double>::max)()),
-    m_minZ((std::numeric_limits<double>::max)()),
-    m_maxX(std::numeric_limits<double>::lowest()),
-    m_maxY(std::numeric_limits<double>::lowest()),
-    m_maxZ(std::numeric_limits<double>::lowest()),
-    m_totalNumPoints(0)
-{
-    m_returnCounts.fill(0);
-}
-
-
-void SummaryData::addPoint(double x, double y, double z, int returnNumber)
-{
-    ++m_totalNumPoints;
-    m_minX = (std::min)(m_minX, x);
-    m_minY = (std::min)(m_minY, y);
-    m_minZ = (std::min)(m_minZ, z);
-    m_maxX = (std::max)(m_maxX, x);
-    m_maxY = (std::max)(m_maxY, y);
-    m_maxZ = (std::max)(m_maxZ, z);
-
-    // Returns numbers are indexed from one, but the array indexes from 0.
-    returnNumber--;
-    if (returnNumber >= 0 && (size_t)returnNumber < m_returnCounts.size())
-        m_returnCounts[returnNumber]++;
-}
-
-
-BOX3D SummaryData::getBounds() const
-{
-    BOX3D output(m_minX, m_minY, m_minZ, m_maxX, m_maxY, m_maxZ);
-    return output;
-}
-
-
-point_count_t SummaryData::getReturnCount(int returnNumber) const
-{
-    if (returnNumber < 0 || (size_t)returnNumber >= m_returnCounts.size())
-        throw pdal_error("getReturnCount: point returnNumber is out of range");
-    return m_returnCounts[returnNumber];
-}
-
-
-void SummaryData::dump(std::ostream& str) const
-{
-    str << "MinX: " << m_minX << "\n";
-    str << "MinY: " << m_minY << "\n";
-    str << "MinZ: " << m_minZ << "\n";
-    str << "MaxX: " << m_maxX << "\n";
-    str << "MaxY: " << m_maxY << "\n";
-    str << "MaxZ: " << m_maxZ << "\n";
-
-    str << "Number of returns:";
-    for (size_t i = 0; i < m_returnCounts.size(); ++i)
-        str << " " << m_returnCounts[i];
-    str << "\n";
-
-    str << "Total number of points: " << m_totalNumPoints << "\n";
-}
-
-
-std::ostream& operator<<(std::ostream& ostr, const SummaryData& data)
-{
-    data.dump(ostr);
-    return ostr;
-}
-
-} // namespace pdal
diff --git a/io/las/SummaryData.hpp b/io/las/SummaryData.hpp
deleted file mode 100644
index d7570fc..0000000
--- a/io/las/SummaryData.hpp
+++ /dev/null
@@ -1,76 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <ostream>
-#include <array>
-
-#include <pdal/pdal_internal.hpp>
-
-#include "LasHeader.hpp"
-
-namespace pdal
-{
-
-class PDAL_DLL SummaryData
-{
-public:
-    SummaryData();
-
-    void addPoint(double x, double y, double z, int returnNumber);
-    uint32_t getTotalNumPoints() const
-        { return m_totalNumPoints; }
-    BOX3D getBounds() const;
-    point_count_t getReturnCount(int returnNumber) const;
-
-    void dump(std::ostream&) const;
-
-private:
-    double m_minX;
-    double m_minY;
-    double m_minZ;
-    double m_maxX;
-    double m_maxY;
-    double m_maxZ;
-    std::array<point_count_t, LasHeader::RETURN_COUNT> m_returnCounts;
-    point_count_t m_totalNumPoints;
-
-    SummaryData& operator=(const SummaryData&); // not implemented
-    SummaryData(const SummaryData&); // not implemented
-};
-
-PDAL_DLL std::ostream& operator<<(std::ostream& ostr, const SummaryData&);
-
-} // namespace pdal
diff --git a/io/las/VariableLengthRecord.cpp b/io/las/VariableLengthRecord.cpp
deleted file mode 100644
index 982880d..0000000
--- a/io/las/VariableLengthRecord.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Hobu Inc. (hobu at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "VariableLengthRecord.hpp"
-
-namespace pdal
-{
-
-const uint16_t VariableLengthRecord::MAX_DATA_SIZE =
-    (std::numeric_limits<uint16_t>::max)();
-
-ILeStream& operator>>(ILeStream& in, VariableLengthRecord& v)
-{
-    uint16_t reserved;
-    uint16_t dataLen;
-
-    in >> reserved;
-    in.get(v.m_userId, 16);
-    in >> v.m_recordId >> dataLen;
-    in.get(v.m_description, 32);
-    v.m_data.resize(dataLen);
-    in.get(v.m_data);
-
-    return in;
-}
-
-
-OLeStream& operator<<(OLeStream& out, const VariableLengthRecord& v)
-{
-    out << v.m_recordSig;
-    out.put(v.m_userId, 16);
-    out << v.m_recordId << (uint16_t)v.dataLen();
-    out.put(v.m_description, 32);
-    out.put(v.data(), v.dataLen());
-
-    return out;
-}
-
-
-ILeStream& operator>>(ILeStream& in, ExtVariableLengthRecord& v)
-{
-    uint64_t dataLen;
-
-    in >> v.m_recordSig;
-    in.get(v.m_userId, 16);
-    in >> v.m_recordId >> dataLen;
-    in.get(v.m_description, 32);
-    v.m_data.resize(dataLen);
-    in.get(v.m_data);
-
-    return in;
-}
-
-
-OLeStream& operator<<(OLeStream& out, const ExtVariableLengthRecord& v)
-{
-    out << (uint16_t)0;
-    out.put(v.userId(), 16);
-    out << v.recordId() << v.dataLen();
-    out.put(v.description(), 32);
-    out.put(v.data(), v.dataLen());
-
-    return out;
-}
-
-    void VariableLengthRecord::write(OLeStream& out, uint16_t recordSig)
-    {
-        m_recordSig = recordSig;
-        out << *this;
-    }
-
-} // namespace pdal
diff --git a/io/las/VariableLengthRecord.hpp b/io/las/VariableLengthRecord.hpp
deleted file mode 100644
index 805b307..0000000
--- a/io/las/VariableLengthRecord.hpp
+++ /dev/null
@@ -1,124 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <limits>
-#include <string>
-#include <vector>
-
-#include <pdal/SpatialReference.hpp>
-#include <pdal/util/IStream.hpp>
-#include <pdal/util/OStream.hpp>
-
-namespace pdal
-{
-
-static const int WKT_RECORD_ID = 2112;
-static const uint16_t GEOTIFF_DIRECTORY_RECORD_ID = 34735;
-static const uint16_t GEOTIFF_DOUBLES_RECORD_ID = 34736;
-static const uint16_t GEOTIFF_ASCII_RECORD_ID = 34737;
-static const uint16_t LASZIP_RECORD_ID = 22204;
-static const uint16_t EXTRA_BYTES_RECORD_ID = 4;
-
-static const char TRANSFORM_USER_ID[] = "LASF_Projection";
-static const char SPEC_USER_ID[] = "LASF_Spec";
-static const char LIBLAS_USER_ID[] = "liblas";
-static const char LASZIP_USER_ID[] = "laszip encoded";
-
-class VariableLengthRecord;
-typedef std::vector<VariableLengthRecord> VlrList;
-
-class PDAL_DLL VariableLengthRecord
-{
-public:
-    static const uint16_t MAX_DATA_SIZE;
-
-    VariableLengthRecord(const std::string& userId, uint16_t recordId,
-            const std::string& description, std::vector<uint8_t>& data) :
-        m_userId(userId), m_recordId(recordId), m_description(description),
-        m_data(std::move(data)), m_recordSig(0)
-    {}
-    VariableLengthRecord() : m_recordId(0), m_recordSig(0)
-    {}
-
-    std::string userId() const
-        { return m_userId;}
-    uint16_t recordId() const
-        { return m_recordId; }
-    std::string description() const
-        { return m_description; }
-    
-    bool matches(const std::string& userId) const
-        { return userId == m_userId; }
-    bool matches(const std::string& userId, uint16_t recordId) const
-        { return matches(userId) && (recordId == m_recordId); }
-
-    const char* data() const
-        { return (const char *)m_data.data(); }
-    char* data()
-        { return (char *)m_data.data(); }
-    uint64_t dataLen() const
-        { return m_data.size(); }
-    void setDataLen(uint64_t size)
-        { m_data.resize((size_t)size); }
-    void write(OLeStream& out, uint16_t recordSig);
-
-    friend ILeStream& operator>>(ILeStream& in, VariableLengthRecord& v);
-    friend OLeStream& operator<<(OLeStream& out, const VariableLengthRecord& v);
-
-protected:
-    std::string m_userId;
-    uint16_t m_recordId;
-    std::string m_description;
-    std::vector<uint8_t> m_data;
-    uint16_t m_recordSig;
-};
-
-class ExtVariableLengthRecord : public VariableLengthRecord
-{
-public:
-    ExtVariableLengthRecord(const std::string& userId, uint16_t recordId,
-            const std::string& description, std::vector<uint8_t>& data) :
-        VariableLengthRecord(userId, recordId, description, data)
-    {}
-    ExtVariableLengthRecord()
-    {}
-
-    friend ILeStream& operator>>(ILeStream& in, ExtVariableLengthRecord& v);
-    friend OLeStream& operator<<(OLeStream& out,
-        const ExtVariableLengthRecord& v);
-};
-
-} // namespace pdal
diff --git a/io/las/ZipPoint.cpp b/io/las/ZipPoint.cpp
deleted file mode 100644
index f843d1b..0000000
--- a/io/las/ZipPoint.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <sstream>
-#include <string.h>
-
-#include <pdal/pdal_internal.hpp>
-
-#ifdef PDAL_HAVE_LASZIP
-
-#include "VariableLengthRecord.hpp"
-#include "ZipPoint.hpp"
-
-namespace pdal
-{
-
-// Read-mode ctor.
-ZipPoint::ZipPoint(VariableLengthRecord *vlr) :
-    m_zip(new LASzip()), m_lz_point(NULL), m_lz_point_size(0)
-{
-    if (!vlr || !m_zip->unpack((unsigned char *)vlr->data(),
-        (int)vlr->dataLen()))
-    {
-        std::ostringstream oss;
-        const char* err = m_zip->get_error();
-        if (err == NULL) 
-            err = "(unknown error)";
-        oss << "Error unpacking zip VLR data: " << std::string(err);
-        throw pdal_error(oss.str());
-    }
-    ConstructItems();
-}
-
-
-// Write-mode ctor.
-ZipPoint::ZipPoint(uint8_t format, uint16_t pointLen) :
-    m_zip(new LASzip()), m_lz_point(NULL), m_lz_point_size(0)
-{
-    if (!m_zip->setup(format, pointLen))
-    {
-        std::ostringstream oss;
-        const char* err = m_zip->get_error();
-        if (err == NULL)
-            err = "(unknown error)";
-        oss << "Error setting up LASzip for format " << format << ": " <<
-            err;
-        throw pdal_error(oss.str());
-    }
-    ConstructItems();
-}
-
-
-ZipPoint::~ZipPoint()
-{
-    delete[] m_lz_point;
-}
-
-
-void ZipPoint::ConstructItems()
-{
-    // construct the object that will hold a laszip point
-
-    // compute the point size
-    m_lz_point_size = 0;
-    for (unsigned int i = 0; i < m_zip->num_items; i++)
-        m_lz_point_size += m_zip->items[i].size;
-
-    // create the point data
-    unsigned int point_offset = 0;
-    m_lz_point = new unsigned char*[m_zip->num_items];
-
-    m_lz_point_data.resize(m_lz_point_size);
-    for (unsigned i = 0; i < m_zip->num_items; i++)
-    {
-        m_lz_point[i] = &(m_lz_point_data[point_offset]);
-        point_offset += m_zip->items[i].size;
-    }
-}
-
-
-std::vector<uint8_t> ZipPoint::vlrData() const
-{
-    // This puts a bunch of data into an array of data pointed to by 'data',
-    // suitable for storage in a VLR.
-    uint8_t* data;
-    int num;
-    m_zip->pack(data, num);
-    return std::vector<uint8_t>(data, data + num);
-}
-
-} // namespace pdal
-
-#endif // PDAL_HAVE_LASZIP
diff --git a/io/las/ZipPoint.hpp b/io/las/ZipPoint.hpp
deleted file mode 100644
index 76dffcd..0000000
--- a/io/las/ZipPoint.hpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <vector>
-
-#ifdef PDAL_HAVE_LASZIP
-#include <laszip/laszip.hpp>
-#include <laszip/lasunzipper.hpp>
-#include <laszip/laszipper.hpp>
-#endif
-
-namespace pdal
-{
-
-#ifdef PDAL_HAVE_LASZIP
-
-class VariableLengthRecord;
-
-class PDAL_DLL ZipPoint
-{
-public:
-    ZipPoint(VariableLengthRecord *lasHeader);
-    ZipPoint(uint8_t format, uint16_t pointLen);
-    ~ZipPoint();
-
-    std::vector<uint8_t> vlrData() const;
-    LASzip* GetZipper() const
-        { return m_zip.get(); }
-    
-private:
-    std::unique_ptr<LASzip> m_zip;
-
-//ABELL - This block should be made private.
-public:
-    unsigned char** m_lz_point;
-    unsigned int m_lz_point_size;
-    std::vector<uint8_t> m_lz_point_data;
-
-private:
-    void ConstructItems();
-};
-#else // PDAL_HAVE_LASZIP
-// The types here just need to be something suitable for a smart pointer.
-// They aren't ever used beyond testing for NULL.
-typedef char LASzipper;
-typedef char LASunzipper;
-typedef char ZipPoint;
-#endif
-
-} // namespace pdal
diff --git a/io/null/CMakeLists.txt b/io/null/CMakeLists.txt
deleted file mode 100644
index d3533cf..0000000
--- a/io/null/CMakeLists.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-#
-# NULL writer CMake configuration
-#
-
-set(srcs
-    NullWriter.cpp
-)
-
-set(incs
-    NullWriter.hpp
-)
-
-PDAL_ADD_DRIVER(writer null "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/io/optech/CMakeLists.txt b/io/optech/CMakeLists.txt
deleted file mode 100644
index 53c4d9e..0000000
--- a/io/optech/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-set(srcs
-    OptechReader.cpp
-    )
-
-set(incs
-    OptechReader.hpp
-    )
-
-PDAL_ADD_DRIVER(reader optech "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/io/optech/OptechReader.cpp b/io/optech/OptechReader.cpp
deleted file mode 100644
index b8e0180..0000000
--- a/io/optech/OptechReader.cpp
+++ /dev/null
@@ -1,285 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#define _USE_MATH_DEFINES
-#include "OptechReader.hpp"
-
-#include <cmath>
-#include <cstring>
-#include <sstream>
-
-#include <pdal/PDALUtils.hpp>
-#include <pdal/SpatialReference.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "readers.optech",
-    "Optech reader support.",
-    "http://pdal.io/stages/readers.optech.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, OptechReader, Reader, s_info);
-
-std::string OptechReader::getName() const
-{
-    return s_info.name;
-}
-
-#ifndef _WIN32
-const size_t OptechReader::MaximumNumberOfReturns;
-const size_t OptechReader::MaxNumRecordsInBuffer;
-const size_t OptechReader::NumBytesInRecord;
-#endif
-
-OptechReader::OptechReader()
-    : Reader()
-    , m_header()
-    , m_boresightMatrix(georeference::createIdentityMatrix())
-    , m_istream()
-    , m_buffer()
-    , m_extractor(m_buffer.data(), 0)
-    , m_recordIndex(0)
-    , m_returnIndex(0)
-    , m_pulse()
-{
-    // The Optech docs say that their lat/longs are referenced
-    // to the WGS84 reference frame.
-    SpatialReference spatialReference;
-    spatialReference.setFromUserInput("EPSG:4326");
-    setSpatialReference(spatialReference);
-}
-
-
-Dimension::IdList OptechReader::getDefaultDimensions()
-{
-    Dimension::IdList dims;
-    dims.push_back(Dimension::Id::X);
-    dims.push_back(Dimension::Id::Y);
-    dims.push_back(Dimension::Id::Z);
-    dims.push_back(Dimension::Id::GpsTime);
-    dims.push_back(Dimension::Id::ReturnNumber);
-    dims.push_back(Dimension::Id::NumberOfReturns);
-    dims.push_back(Dimension::Id::EchoRange);
-    dims.push_back(Dimension::Id::Intensity);
-    dims.push_back(Dimension::Id::ScanAngleRank);
-    return dims;
-}
-
-
-const CsdHeader& OptechReader::getHeader() const { return m_header; }
-
-
-void OptechReader::initialize()
-{
-    ILeStream stream(Utils::openFile(m_filename));
-    if (!stream)
-    {
-        std::stringstream ss;
-        ss << "Unable to open " << m_filename << " for reading.";
-        throw pdal_error(ss.str());
-    }
-
-    stream.get(m_header.signature, 4);
-    if (strcmp(m_header.signature, "CSD") != 0)
-    {
-        std::stringstream ss;
-        ss << "Invalid header signature when reading CSD file: '"
-           << m_header.signature << "'";
-        throw optech_error(ss.str());
-    }
-    stream.get(m_header.vendorId, 64);
-    stream.get(m_header.softwareVersion, 32);
-    stream >> m_header.formatVersion >> m_header.headerSize >>
-        m_header.gpsWeek >> m_header.minTime >> m_header.maxTime >>
-        m_header.numRecords >> m_header.numStrips;
-    for (size_t i = 0; i < 256; ++i)
-    {
-        stream >> m_header.stripPointers[i];
-    }
-    stream >> m_header.misalignmentAngles[0] >>
-        m_header.misalignmentAngles[1] >> m_header.misalignmentAngles[2] >>
-        m_header.imuOffsets[0] >> m_header.imuOffsets[1] >>
-        m_header.imuOffsets[2] >> m_header.temperature >> m_header.pressure;
-    stream.get(m_header.freeSpace, 830);
-
-    m_boresightMatrix = createOptechRotationMatrix(
-        m_header.misalignmentAngles[0] + m_header.imuOffsets[0],
-        m_header.misalignmentAngles[1] + m_header.imuOffsets[1],
-        m_header.misalignmentAngles[2] + m_header.imuOffsets[2]);
-}
-
-
-void OptechReader::addDimensions(PointLayoutPtr layout)
-{
-    for (auto it : getDefaultDimensions())
-    {
-        layout->registerDim(it);
-    }
-}
-
-
-void OptechReader::ready(PointTableRef)
-{
-    m_istream.reset(new IStream(m_filename));
-    if (!*m_istream)
-    {
-        std::stringstream ss;
-        ss << "Unable to open " << m_filename << " for reading.";
-        throw pdal_error(ss.str());
-    }
-
-    m_istream->seek(m_header.headerSize);
-    m_recordIndex = 0;
-    m_returnIndex = 0;
-    m_pulse = CsdPulse();
-}
-
-
-point_count_t OptechReader::read(PointViewPtr data,
-                                 point_count_t countRequested)
-{
-    point_count_t numRead = 0;
-    point_count_t dataIndex = data->size();
-
-    while (numRead < countRequested)
-    {
-        if (m_returnIndex == 0)
-        {
-            if (!m_extractor.good())
-            {
-                if (m_recordIndex >= m_header.numRecords)
-                {
-                    break;
-                }
-                m_recordIndex += fillBuffer();
-            }
-
-            m_extractor >> m_pulse.gpsTime >> m_pulse.returnCount >>
-                m_pulse.range[0] >> m_pulse.range[1] >> m_pulse.range[2] >>
-                m_pulse.range[3] >> m_pulse.intensity[0] >>
-                m_pulse.intensity[1] >> m_pulse.intensity[2] >>
-                m_pulse.intensity[3] >> m_pulse.scanAngle >> m_pulse.roll >>
-                m_pulse.pitch >> m_pulse.heading >> m_pulse.latitude >>
-                m_pulse.longitude >> m_pulse.elevation;
-
-            if (m_pulse.returnCount == 0)
-            {
-                m_returnIndex = 0;
-                continue;
-            }
-
-            // In all the csd files that we've tested, the longitude
-            // values have been less than -2pi.
-            if (m_pulse.longitude < -M_PI * 2)
-            {
-                m_pulse.longitude = m_pulse.longitude + M_PI * 2;
-            }
-            else if (m_pulse.longitude > M_PI * 2)
-            {
-                m_pulse.longitude = m_pulse.longitude - M_PI * 2;
-            }
-        }
-
-        georeference::Xyz gpsPoint = georeference::Xyz(
-            m_pulse.longitude, m_pulse.latitude, m_pulse.elevation);
-        georeference::RotationMatrix rotationMatrix =
-            createOptechRotationMatrix(m_pulse.roll, m_pulse.pitch,
-                                       m_pulse.heading);
-        georeference::Xyz point = pdal::georeference::georeferenceWgs84(
-            m_pulse.range[m_returnIndex], m_pulse.scanAngle,
-            m_boresightMatrix, rotationMatrix, gpsPoint);
-
-        data->setField(Dimension::Id::X, dataIndex, point.X * 180 / M_PI);
-        data->setField(Dimension::Id::Y, dataIndex, point.Y * 180 / M_PI);
-        data->setField(Dimension::Id::Z, dataIndex, point.Z);
-        data->setField(Dimension::Id::GpsTime, dataIndex, m_pulse.gpsTime);
-        if (m_returnIndex == MaximumNumberOfReturns - 1)
-        {
-            data->setField(Dimension::Id::ReturnNumber, dataIndex,
-                          m_pulse.returnCount);
-        }
-        else
-        {
-            data->setField(Dimension::Id::ReturnNumber, dataIndex,
-                          m_returnIndex + 1);
-        }
-        data->setField(Dimension::Id::NumberOfReturns, dataIndex,
-                      m_pulse.returnCount);
-        data->setField(Dimension::Id::EchoRange, dataIndex,
-                      m_pulse.range[m_returnIndex]);
-        data->setField(Dimension::Id::Intensity, dataIndex,
-                      m_pulse.intensity[m_returnIndex]);
-        data->setField(Dimension::Id::ScanAngleRank, dataIndex,
-                      m_pulse.scanAngle * 180 / M_PI);
-
-        if (m_cb)
-            m_cb(*data, dataIndex);
-
-        ++dataIndex;
-        ++numRead;
-        ++m_returnIndex;
-
-        if (m_returnIndex >= m_pulse.returnCount ||
-            m_returnIndex >= MaximumNumberOfReturns)
-        {
-            m_returnIndex = 0;
-        }
-    }
-    return numRead;
-}
-
-
-size_t OptechReader::fillBuffer()
-{
-    size_t numRecords = std::min<size_t>(m_header.numRecords - m_recordIndex,
-                                         MaxNumRecordsInBuffer);
-
-    buffer_size_t bufferSize = NumBytesInRecord * numRecords;
-    m_buffer.resize(bufferSize);
-    m_istream->get(m_buffer);
-    m_extractor = LeExtractor(m_buffer.data(), m_buffer.size());
-    return numRecords;
-}
-
-
-void OptechReader::done(PointTableRef)
-{
-    m_istream.reset();
-}
-
-} // namespace pdal
-
diff --git a/io/ply/CMakeLists.txt b/io/ply/CMakeLists.txt
deleted file mode 100644
index 2c24995..0000000
--- a/io/ply/CMakeLists.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-set(src
-    PlyReader.cpp
-    PlyWriter.cpp
-    ${PROJECT_SOURCE_DIR}/vendor/rply-1.1.4/rply.c
-)
-
-set(inc
-    PlyReader.hpp
-    PlyWriter.hpp
-    ${PROJECT_SOURCE_DIR}/vendor/rply-1.1.4/rply.h
-)
-
-PDAL_ADD_DRIVER(reader ply "${src}" "${inc}" objs)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objs} PARENT_SCOPE)
diff --git a/io/ply/PlyReader.hpp b/io/ply/PlyReader.hpp
deleted file mode 100644
index f252437..0000000
--- a/io/ply/PlyReader.hpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <string>
-
-#include "rply.h"
-
-#include <pdal/Dimension.hpp>
-#include <pdal/Reader.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/plugin.hpp>
-
-extern "C" int32_t PlyReader_ExitFunc();
-extern "C" PF_ExitFunc PlyReader_InitPlugin();
-
-
-namespace pdal
-{
-
-class PDAL_DLL PlyReader : public Reader
-{
-public:
-    static void *create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-    typedef std::map<std::string, Dimension::Id> DimensionMap;
-
-    PlyReader();
-
-    static Dimension::IdList getDefaultDimensions();
-
-private:
-    virtual void initialize();
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual void ready(PointTableRef table);
-    virtual point_count_t read(PointViewPtr view, point_count_t num);
-    virtual void done(PointTableRef table);
-
-    p_ply m_ply;
-
-    DimensionMap m_vertexDimensions;
-    std::map<std::string, Dimension::Type> m_vertexTypes;
-};
-}
-
diff --git a/io/ply/PlyWriter.cpp b/io/ply/PlyWriter.cpp
deleted file mode 100644
index 31fa1e2..0000000
--- a/io/ply/PlyWriter.cpp
+++ /dev/null
@@ -1,206 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "PlyWriter.hpp"
-
-#include <sstream>
-
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-namespace
-{
-
-void createErrorCallback(p_ply ply, const char* message)
-{
-    std::stringstream ss;
-    ss << "Error when creating ply file: " << message;
-    throw pdal_error(ss.str());
-}
-
-
-e_ply_type getPlyType(Dimension::Type type)
-{
-    static std::map<Dimension::Type, e_ply_type> types =
-    {
-        { Dimension::Type::Unsigned8, PLY_UINT8 },
-        { Dimension::Type::Signed8, PLY_INT8 },
-        { Dimension::Type::Unsigned16, PLY_UINT16 },
-        { Dimension::Type::Signed16, PLY_INT16 },
-        { Dimension::Type::Unsigned32, PLY_UINT32 },
-        { Dimension::Type::Signed32, PLY_INT32 },
-        { Dimension::Type::Float, PLY_FLOAT32 },
-        { Dimension::Type::Double, PLY_FLOAT64 }
-    };
-
-    return types[type];
-}
-
-} // unnamed namespace
-
-
-static PluginInfo const s_info = PluginInfo(
-        "writers.ply",
-        "ply writer",
-        "http://pdal.io/stages/writers.ply.html"
-        );
-
-CREATE_STATIC_PLUGIN(1, 0, PlyWriter, Writer, s_info)
-
-std::string PlyWriter::getName() const { return s_info.name; }
-
-
-PlyWriter::PlyWriter()
-    : m_ply(nullptr)
-    , m_pointCollector(nullptr)
-    , m_storageMode(PLY_DEFAULT)
-{}
-
-
-void PlyWriter::addArgs(ProgramArgs& args)
-{
-    args.add("filename", "Output filename", m_filename).setPositional();
-    args.add("storage_mode", "PLY Storage mode", m_storageModeSpec, "default");
-}
-
-
-void PlyWriter::initialize()
-{
-    std::string storageMode(m_storageModeSpec);
-    storageMode = Utils::tolower(storageMode);
-
-    if (storageMode == "ascii")
-    {
-        m_storageMode = PLY_ASCII;
-    }
-    else if (storageMode == "little endian")
-    {
-        m_storageMode = PLY_LITTLE_ENDIAN;
-    }
-    else if (storageMode == "big endian")
-    {
-        m_storageMode = PLY_BIG_ENDIAN;
-    }
-    else if (storageMode == "default")
-    {
-        m_storageMode = PLY_DEFAULT;
-    }
-    else
-    {
-        std::stringstream ss;
-        ss << "Unknown storage mode '" << m_storageModeSpec <<
-            "'. Known storage modes are: 'ascii', 'little endian', "
-            "'big endian', and 'default'";
-        throw pdal_error(ss.str());
-    }
-}
-
-
-void PlyWriter::ready(PointTableRef table)
-{
-    m_ply = ply_create(m_filename.c_str(), m_storageMode, createErrorCallback,
-        0, nullptr);
-    if (!m_ply)
-    {
-        std::stringstream ss;
-        ss << "Could not open file for writing: " << m_filename;
-        throw pdal_error(ss.str());
-    }
-    m_pointCollector.reset(new PointView(table));
-}
-
-
-void PlyWriter::write(const PointViewPtr data)
-{
-    m_pointCollector->append(*data);
-}
-
-
-void PlyWriter::done(PointTableRef table)
-{
-    if (!ply_add_element(m_ply, "vertex", m_pointCollector->size()))
-    {
-        std::stringstream ss;
-        ss << "Could not add vertex element";
-        throw pdal_error(ss.str());
-    }
-    auto dimensions = table.layout()->dims();
-    for (auto dim : dimensions) {
-        std::string name = Dimension::name(dim);
-        e_ply_type plyType = getPlyType(Dimension::defaultType(dim));
-        if (!ply_add_scalar_property(m_ply, name.c_str(), plyType))
-        {
-            std::stringstream ss;
-            ss << "Could not add scalar property '" << name << "'";
-            throw pdal_error(ss.str());
-        }
-    }
-    if (!ply_add_comment(m_ply, "Generated by PDAL"))
-    {
-        std::stringstream ss;
-        ss << "Could not add comment";
-        throw pdal_error(ss.str());
-    }
-    if (!ply_write_header(m_ply))
-    {
-        std::stringstream ss;
-        ss << "Could not write ply header";
-        throw pdal_error(ss.str());
-    }
-
-    for (PointId index = 0; index < m_pointCollector->size(); ++index)
-    {
-        for (auto dim : dimensions)
-        {
-            double value = m_pointCollector->getFieldAs<double>(dim, index);
-            if (!ply_write(m_ply, value))
-            {
-                std::stringstream ss;
-                ss << "Error writing dimension '" << Dimension::name(dim) <<
-                    "' of point number " << index;
-                throw pdal_error(ss.str());
-            }
-        }
-    }
-
-    if (!ply_close(m_ply))
-    {
-        throw pdal_error("Error closing ply file");
-    }
-}
-
-
-}
diff --git a/io/ply/PlyWriter.hpp b/io/ply/PlyWriter.hpp
deleted file mode 100644
index 30b52f8..0000000
--- a/io/ply/PlyWriter.hpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "rply.h"
-
-#include <pdal/PointView.hpp>
-#include <pdal/Writer.hpp>
-#include <pdal/plugin.hpp>
-
-extern "C" int32_t PlyWriter_ExitFunc();
-extern "C" PF_ExitFunc PlyWriter_InitPlugin();
-
-
-namespace pdal
-{
-
-
-class PDAL_DLL PlyWriter : public Writer
-{
-public:
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-    PlyWriter();
-
-private:
-    virtual void addArgs(ProgramArgs& args);
-    virtual void initialize();
-    virtual void ready(PointTableRef table);
-    virtual void write(const PointViewPtr data);
-    virtual void done(PointTableRef table);
-
-    p_ply m_ply;
-    PointViewPtr m_pointCollector;
-    std::string m_storageModeSpec;
-    e_ply_storage_mode m_storageMode;
-
-};
-
-
-}
diff --git a/io/pts/CMakeLists.txt b/io/pts/CMakeLists.txt
deleted file mode 100644
index feaec35..0000000
--- a/io/pts/CMakeLists.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-#
-# Text driver CMake configuration
-#
-
-set(srcs
-    PtsReader.cpp
-)
-
-set(incs
-    PtsReader.hpp
-)
-
-PDAL_ADD_DRIVER(reader pts "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/io/qfit/CMakeLists.txt b/io/qfit/CMakeLists.txt
deleted file mode 100644
index b544bff..0000000
--- a/io/qfit/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# QFIT driver CMake configuration
-#
-
-#
-# QFIT Reader
-#
-set(srcs
-    QfitReader.cpp
-)
-
-set(incs
-    QfitReader.hpp
-)
-
-PDAL_ADD_DRIVER(reader qfit "${srcs}" "${incs}" objs)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objs} PARENT_SCOPE)
diff --git a/io/sbet/CMakeLists.txt b/io/sbet/CMakeLists.txt
deleted file mode 100644
index e671b37..0000000
--- a/io/sbet/CMakeLists.txt
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# SBET driver CMake configuration
-#
-
-set(objs "")
-
-add_library(sbetcommon OBJECT SbetCommon.cpp SbetCommon.hpp)
-add_dependencies(sbetcommon generate_dimension_hpp)
-set(objs ${objs} $<TARGET_OBJECTS:sbetcommon>)
-
-#
-# SBET Reader
-#
-set(srcs
-    SbetReader.cpp
-)
-
-set(incs
-    SbetReader.hpp
-)
-
-PDAL_ADD_DRIVER(reader sbet "${srcs}" "${incs}" reader_objs)
-set(objs ${objs} ${reader_objs})
-
-#
-# SBET Writer
-#
-set(srcs
-    SbetWriter.cpp
-)
-
-set(incs
-    SbetWriter.hpp
-)
-
-PDAL_ADD_DRIVER(writer sbet "${srcs}" "${incs}" writer_objs)
-set(objs ${objs} ${writer_objs})
-
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objs} PARENT_SCOPE)
diff --git a/io/sbet/SbetReader.cpp b/io/sbet/SbetReader.cpp
deleted file mode 100644
index 21cd0fe..0000000
--- a/io/sbet/SbetReader.cpp
+++ /dev/null
@@ -1,119 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Peter J. Gadomski (pete.gadomski at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/PointRef.hpp>
-
-#include "SbetReader.hpp"
-
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "readers.sbet",
-    "SBET Reader",
-    "http://pdal.io/stages/readers.sbet.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, SbetReader, Reader, s_info)
-
-std::string SbetReader::getName() const { return s_info.name; }
-
-void SbetReader::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDims(getDefaultDimensions());
-}
-
-
-void SbetReader::ready(PointTableRef)
-{
-    size_t fileSize = FileUtils::fileSize(m_filename);
-    size_t pointSize = getDefaultDimensions().size() * sizeof(double);
-    if (fileSize % pointSize != 0)
-        throw pdal_error("invalid sbet file size");
-    m_numPts = fileSize / pointSize;
-    m_index = 0;
-    m_stream.reset(new ILeStream(m_filename));
-    m_dims = fileDimensions();
-    seek(m_index);
-}
-
-
-bool SbetReader::processOne(PointRef& point)
-{
-    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
-    {
-        double d;
-        *m_stream >> d;
-        Dimension::Id dim = *di;
-        point.setField(dim, d);
-    }
-    return (m_stream->good());
-}
-
-
-point_count_t SbetReader::read(PointViewPtr view, point_count_t count)
-{
-    PointId nextId = view->size();
-    PointId idx = m_index;
-    point_count_t numRead = 0;
-    seek(idx);
-    while (numRead < count && idx < m_numPts)
-    {
-        PointRef point = view->point(nextId);
-        processOne(point);
-        if (m_cb)
-            m_cb(*view, nextId);
-
-        idx++;
-        nextId++;
-        numRead++;
-    }
-    m_index = idx;
-    return numRead;
-}
-
-
-bool SbetReader::eof()
-{
-    return m_index >= m_numPts;
-}
-
-
-void SbetReader::seek(PointId idx)
-{
-    m_stream->seek(idx * sizeof(double) * getDefaultDimensions().size());
-}
-
-} // namespace pdal
diff --git a/io/sbet/SbetWriter.cpp b/io/sbet/SbetWriter.cpp
deleted file mode 100644
index 63c700e..0000000
--- a/io/sbet/SbetWriter.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Peter J. Gadomski (pete.gadomski at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "SbetWriter.hpp"
-
-#include <pdal/PointView.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "writers.sbet",
-    "SBET Writer",
-    "http://pdal.io/stages/writers.sbet.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, SbetWriter, Writer, s_info)
-
-std::string SbetWriter::getName() const { return s_info.name; }
-
-void SbetWriter::addArgs(ProgramArgs& args)
-{
-    args.add("filename", "Output filename", m_filename).setPositional();
-}
-
-
-void SbetWriter::ready(PointTableRef)
-{
-    m_stream.reset(new OLeStream(m_filename));
-}
-
-
-void SbetWriter::write(const PointViewPtr view)
-{
-    Dimension::IdList dims = getDefaultDimensions();
-    for (PointId idx = 0; idx < view->size(); ++idx)
-    {
-        for (auto di = dims.begin(); di != dims.end(); ++di)
-        {
-            // If a dimension doesn't exist, write 0.
-            Dimension::Id dim = *di;
-            *m_stream << (view->hasDim(dim) ?
-                view->getFieldAs<double>(dim, idx) : 0.0);
-        }
-    }
-}
-
-} // namespace pdal
diff --git a/io/sbet/SbetWriter.hpp b/io/sbet/SbetWriter.hpp
deleted file mode 100644
index 83fefd9..0000000
--- a/io/sbet/SbetWriter.hpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Peter J. Gadomski (pete.gadomski at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/plugin.hpp>
-#include <pdal/util/OStream.hpp>
-#include <pdal/Writer.hpp>
-
-#include "SbetCommon.hpp"
-
-extern "C" int32_t SbetWriter_ExitFunc();
-extern "C" PF_ExitFunc SbetWriter_InitPlugin();
-
-namespace pdal
-{
-
-class PDAL_DLL SbetWriter : public Writer
-{
-public:
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-    static Dimension::IdList getDefaultDimensions()
-        { return fileDimensions(); }
-
-private:
-    std::unique_ptr<OLeStream> m_stream;
-    std::string m_filename;
-
-    virtual void addArgs(ProgramArgs& args);
-    virtual void ready(PointTableRef table);
-    virtual void write(const PointViewPtr view);
-};
-
-} // namespace pdal
diff --git a/io/terrasolid/CMakeLists.txt b/io/terrasolid/CMakeLists.txt
deleted file mode 100644
index 267e311..0000000
--- a/io/terrasolid/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Terrasolid driver CMake configuration
-#
-
-#
-# Terrasolid Reader
-#
-set(srcs
-    TerrasolidReader.cpp
-)
-
-set(incs
-    TerrasolidReader.hpp
-)
-
-PDAL_ADD_DRIVER(reader terrasolid "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/io/text/CMakeLists.txt b/io/text/CMakeLists.txt
deleted file mode 100644
index 6f7e112..0000000
--- a/io/text/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# Text driver CMake configuration
-#
-
-set(srcs
-    TextReader.cpp
-    TextWriter.cpp
-)
-
-set(incs
-    TextReader.hpp
-    TextWriter.hpp
-)
-
-PDAL_ADD_DRIVER(writer text "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/io/text/TextReader.cpp b/io/text/TextReader.cpp
deleted file mode 100644
index 54b3018..0000000
--- a/io/text/TextReader.cpp
+++ /dev/null
@@ -1,181 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Hobu Inc., info at hobu.co
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/PDALUtils.hpp>
-#include <pdal/util/Algorithm.hpp>
-
-#include "TextReader.hpp"
-
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "readers.text",
-    "Text Reader",
-    "http://pdal.io/stages/readers.text.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, TextReader, Reader, s_info)
-
-std::string TextReader::getName() const { return s_info.name; }
-
-void TextReader::initialize(PointTableRef table)
-{
-    m_istream = Utils::openFile(m_filename);
-    if (!m_istream)
-    {
-        std::ostringstream oss;
-        oss << getName() << ": Unable to open text file '" <<
-            m_filename << "'.";
-        throw pdal_error(oss.str());
-    }
-
-    std::string buf;
-    std::getline(*m_istream, buf);
-
-    auto isspecial = [](char c)
-        { return (!std::isalnum(c) && c != ' '); };
-
-    // Scan string for some character not a number, space or letter.
-    for (size_t i = 0; i < buf.size(); ++i)
-        if (isspecial(buf[i]))
-        {
-            m_separator = buf[i];
-            break;
-        }
-
-    if (m_separator != ' ')
-    {
-        Utils::remove(buf, ' ');
-        m_dimNames = Utils::split(buf, m_separator);
-    }
-    else
-        m_dimNames = Utils::split2(buf, m_separator);
-    for (auto f: m_dimNames)
-    {
-        log()->get(LogLevel::Error) << "field '" << f << "'" << std::endl;
-
-    }
-    Utils::closeFile(m_istream);
-}
-
-
-void TextReader::addDimensions(PointLayoutPtr layout)
-{
-    for (auto name : m_dimNames)
-    {
-        Dimension::Id id = layout->registerOrAssignDim(name,
-            Dimension::Type::Double);
-        m_dims.push_back(id);
-    }
-}
-
-
-void TextReader::ready(PointTableRef table)
-{
-    m_istream = Utils::openFile(m_filename);
-    if (!m_istream)
-    {
-        std::ostringstream oss;
-        oss << getName() << ": Unable to open text file '" <<
-            m_filename << "'.";
-        throw pdal_error(oss.str());
-    }
-
-    // Skip header line.
-    std::string buf;
-    std::getline(*m_istream, buf);
-}
-
-
-point_count_t TextReader::read(PointViewPtr view, point_count_t numPts)
-{
-    PointId idx = view->size();
-
-    point_count_t cnt = 0;
-    size_t line = 1;
-    while (m_istream->good() && cnt < numPts)
-    {
-        std::string buf;
-        StringList fields;
-
-        std::getline(*m_istream, buf);
-        line++;
-        if (buf.empty())
-            continue;
-        if (m_separator != ' ')
-        {
-            Utils::remove(buf, ' ');
-            fields = Utils::split(buf, m_separator);
-        }
-        else
-            fields = Utils::split2(buf, m_separator);
-        if (fields.size() != m_dims.size())
-        {
-            log()->get(LogLevel::Error) << "Line " << line <<
-               " in '" << m_filename << "' contains " << fields.size() <<
-               " fields when " << m_dims.size() << " were expected.  "
-               "Ignoring." << std::endl;
-            continue;
-        }
-
-        double d;
-        for (size_t i = 0; i < fields.size(); ++i)
-        {
-            if (!Utils::fromString(fields[i], d))
-            {
-                log()->get(LogLevel::Error) << "Can't convert "
-                    "field '" << fields[i] << "' to numeric value on line " <<
-                    line << " in '" << m_filename << "'.  Setting to 0." <<
-                    std::endl;
-                d = 0;
-            }
-            view->setField(m_dims[i], idx, d);
-        }
-        cnt++;
-        idx++;
-    }
-    return cnt;
-}
-
-
-void TextReader::done(PointTableRef table)
-{
-    Utils::closeFile(m_istream);
-}
-
-
-} // namespace pdal
-
diff --git a/io/text/TextWriter.cpp b/io/text/TextWriter.cpp
deleted file mode 100644
index 99ae771..0000000
--- a/io/text/TextWriter.cpp
+++ /dev/null
@@ -1,250 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Howard Butler, hobu.inc at gmail.com
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "TextWriter.hpp"
-
-#include <pdal/pdal_export.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/Algorithm.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <iostream>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "writers.text",
-    "Text Writer",
-    "http://pdal.io/stages/writers.text.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, TextWriter, Writer, s_info)
-
-std::string TextWriter::getName() const { return s_info.name; }
-
-struct FileStreamDeleter
-{
-
-    template <typename T>
-    void operator()(T* ptr)
-    {
-        if (ptr)
-        {
-            ptr->flush();
-            Utils::closeFile(ptr);
-        }
-    }
-};
-
-
-void TextWriter::addArgs(ProgramArgs& args)
-{
-    args.add("filename", "Output filename", m_filename);
-    args.add("format", "Output format", m_outputType, "csv");
-    args.add("jscallback", "", m_callback);
-    args.add("keep_unspecified", "Write all dimensions", m_writeAllDims, true);
-    args.add("order", "Dimension order", m_dimOrder);
-    args.add("write_header", "Whether a header should be written",
-        m_writeHeader, true);
-    args.add("newline", "String to use as newline", m_newline, "\n");
-    args.add("delimiter", "Dimension delimiter", m_delimiter, ",");
-    args.add("quote_header", "Whether a header should be quoted",
-        m_quoteHeader, true);
-    args.add("precision", "Output precision", m_precision, 3);
-}
-
-
-void TextWriter::initialize(PointTableRef table)
-{
-    m_stream = FileStreamPtr(Utils::createFile(m_filename, true),
-        FileStreamDeleter());
-    if (!m_stream)
-    {
-        std::stringstream out;
-        out << "writers.text couldn't open '" << m_filename <<
-            "' for output.";
-        throw pdal_error(out.str());
-    }
-    m_outputType = Utils::toupper(m_outputType);
-}
-
-
-void TextWriter::ready(PointTableRef table)
-{
-    m_stream->precision(m_precision);
-    *m_stream << std::fixed;
-
-    // Find the dimensions listed and put them on the id list.
-    StringList dimNames = Utils::split2(m_dimOrder, ',');
-    for (std::string dim : dimNames)
-    {
-        Utils::trim(dim);
-        Dimension::Id d = table.layout()->findDim(dim);
-        if (d == Dimension::Id::Unknown)
-        {
-            std::ostringstream oss;
-            oss << getName() << ": Dimension not found with name '" <<
-                dim << "'.";
-            throw pdal_error(oss.str());
-        }
-        m_dims.push_back(d);
-    }
-
-    // Add the rest of the dimensions to the list if we're doing that.
-    // Yes, this isn't efficient when, but it's simple.
-    if (m_dimOrder.empty() || m_writeAllDims)
-    {
-        Dimension::IdList all = table.layout()->dims();
-        for (auto di = all.begin(); di != all.end(); ++di)
-            if (!Utils::contains(m_dims, *di))
-                m_dims.push_back(*di);
-    }
-
-    if (!m_writeHeader)
-        log()->get(LogLevel::Debug) << "Not writing header" << std::endl;
-    else
-        writeHeader(table);
-}
-
-
-void TextWriter::writeHeader(PointTableRef table)
-{
-    log()->get(LogLevel::Debug) << "Writing header to filename: " <<
-        m_filename << std::endl;
-    if (m_outputType == "GEOJSON")
-        writeGeoJSONHeader();
-    else if (m_outputType == "CSV")
-        writeCSVHeader(table);
-}
-
-
-void TextWriter::writeFooter()
-{
-    if (m_outputType == "GEOJSON")
-    {
-        *m_stream << "]}";
-        if (m_callback.size())
-            *m_stream  <<")";
-    }
-    m_stream.reset();
-}
-
-
-void TextWriter::writeGeoJSONHeader()
-{
-    if (m_callback.size())
-        *m_stream << m_callback <<"(";
-    *m_stream << "{ \"type\": \"FeatureCollection\", \"features\": [";
-}
-
-
-void TextWriter::writeCSVHeader(PointTableRef table)
-{
-    const PointLayoutPtr layout(table.layout());
-    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
-    {
-        if (di != m_dims.begin())
-            *m_stream << m_delimiter;
-
-        if (m_quoteHeader)
-            *m_stream << "\"" << layout->dimName(*di) << "\"";
-        else
-            *m_stream << layout->dimName(*di);
-    }
-    *m_stream << m_newline;
-}
-
-void TextWriter::writeCSVBuffer(const PointViewPtr view)
-{
-    for (PointId idx = 0; idx < view->size(); ++idx)
-    {
-        for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
-        {
-            if (di != m_dims.begin())
-                *m_stream << m_delimiter;
-            *m_stream << view->getFieldAs<double>(*di, idx);
-        }
-        *m_stream << m_newline;
-    }
-}
-
-void TextWriter::writeGeoJSONBuffer(const PointViewPtr view)
-{
-    using namespace Dimension;
-
-    for (PointId idx = 0; idx < view->size(); ++idx)
-    {
-        if (idx)
-            *m_stream << ",";
-
-        *m_stream << "{ \"type\":\"Feature\",\"geometry\": "
-            "{ \"type\": \"Point\", \"coordinates\": [";
-        *m_stream << view->getFieldAs<double>(Id::X, idx) << ",";
-        *m_stream << view->getFieldAs<double>(Id::Y, idx) << ",";
-        *m_stream << view->getFieldAs<double>(Id::Z, idx) << "]},";
-
-        *m_stream << "\"properties\": {";
-
-        for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
-        {
-            if (di != m_dims.begin())
-                *m_stream << ",";
-
-            *m_stream << "\"" << view->dimName(*di) << "\":";
-            *m_stream << "\"";
-            *m_stream << view->getFieldAs<double>(*di, idx);
-            *m_stream <<"\"";
-        }
-        *m_stream << "}"; // end properties
-        *m_stream << "}"; // end feature
-    }
-}
-
-void TextWriter::write(const PointViewPtr view)
-{
-    if (m_outputType == "CSV")
-        writeCSVBuffer(view);
-    else if (m_outputType == "GEOJSON")
-        writeGeoJSONBuffer(view);
-}
-
-
-void TextWriter::done(PointTableRef /*table*/)
-{
-    writeFooter();
-}
-
-} // namespace pdal
diff --git a/io/tindex/CMakeLists.txt b/io/tindex/CMakeLists.txt
deleted file mode 100644
index 8957acf..0000000
--- a/io/tindex/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-set(src
-    TIndexReader.cpp
-)
-
-set(inc
-    TIndexReader.hpp
-)
-
-PDAL_ADD_DRIVER(reader tindex "${src}" "${inc}" objs)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objs} PARENT_SCOPE)
diff --git a/io/tindex/TIndexReader.hpp b/io/tindex/TIndexReader.hpp
deleted file mode 100644
index 7443072..0000000
--- a/io/tindex/TIndexReader.hpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/PointView.hpp>
-#include <pdal/Reader.hpp>
-#include <merge/MergeFilter.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/GDALUtils.hpp>
-#include <pdal/plugin.hpp>
-
-extern "C" int32_t TIndexReader_ExitFunc();
-extern "C" PF_ExitFunc TIndexReader_InitPlugin();
-
-namespace pdal
-{
-class PDAL_DLL TIndexReader : public pdal::Reader
-{
-    struct FileInfo
-    {
-        std::string m_filename;
-        std::string m_srs;
-        std::string m_boundary;
-        struct tm m_ctime;
-        struct tm m_mtime;
-    };
-
-    struct FieldIndexes
-    {
-        int m_filename;
-        int m_srs;
-        int m_ctime;
-        int m_mtime;
-    };
-
-public:
-    TIndexReader() : m_dataset(NULL) , m_layer(NULL)
-        {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-    static Dimension::IdList getDefaultDimensions();
-
-private:
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual void addArgs(ProgramArgs& args);
-    virtual void initialize();
-    virtual void ready(PointTableRef table);
-    virtual PointViewSet run(PointViewPtr view);
-
-    std::string m_layerName;
-    std::string m_driverName;
-    std::string m_tileIndexColumnName;
-    std::string m_srsColumnName;
-    std::string m_wkt;
-    std::string m_tgtSrsString;
-    std::string m_filterSRS;
-    std::string m_attributeFilter;
-    std::string m_dialect;
-    BOX2D m_bounds;
-    std::string m_sql;
-
-    std::unique_ptr<gdal::SpatialRef> m_out_ref;
-    void *m_dataset;
-    void *m_layer;
-
-    StageFactory m_factory;
-    MergeFilter m_merge;
-    PointViewSet m_pvSet;
-
-    std::vector<FileInfo> getFiles();
-    FieldIndexes getFields();
-};
-
-
-} // namespace pdal
diff --git a/java/.gitignore b/java/.gitignore
new file mode 100644
index 0000000..d3aff5f
--- /dev/null
+++ b/java/.gitignore
@@ -0,0 +1,43 @@
+# Operating System Files
+
+# *.DS_Store
+Thumbs.db
+
+# Build Files
+
+bin
+target
+build/
+.gradle
+
+# Eclipse Project Files
+
+.classpath
+.project
+.settings
+
+# IntelliJ IDEA Files
+
+*.iml
+*.ipr
+*.iws
+*.idea
+
+# Spring Bootstrap artifacts
+
+dependency-reduced-pom.xml
+README.html
+
+# Sublime files
+
+*.sublime-workspace
+
+# Test data files #
+
+java/data
+
+# Compiled libs #
+
+java/*.dylib
+java/*.so
+java/*dll
diff --git a/java/README.md b/java/README.md
new file mode 100644
index 0000000..ea1fc10
--- /dev/null
+++ b/java/README.md
@@ -0,0 +1,34 @@
+# PDAL Java bindings
+
+[![Join the chat at https://gitter.im/PDAL/PDAL](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/PDAL/PDAL?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+Java bindings to use PDAL on JVM.
+
+## How to compile
+
+1. Install PDAL (using brew / package managers (unix) / build from sources / etc)
+2. Build native libs `./sbt native/nativeCompile` (optionally, binaries would be built during tests run)
+3. Run `./sbt core/test` to run PDAL tests
+
+## Using with SBT
+
+```scala
+resolvers ++= Seq(
+  Resolver.sonatypeRepo("releases"),
+  Resolver.sonatypeRepo("snapshots") // for snaphots
+)
+
+libraryDependencies ++= Seq(
+  "io.pdal" %% "pdal" % "1.4.0"
+)
+```
+
+It's required to have native JNI binary into your app classpath:
+
+```scala
+// Mac OS X example with manual jni installation 
+// It's strongly recommended to use WITH_PDAL_JNI flag to build the whole PDAL
+// cp -f native/target/resource_managed/main/native/x86_64-darwin/libpdaljni.1.4.dylib /usr/local/lib/libpdaljni.1.4.dylib
+// place built binary into /usr/local/lib, and pass java.library.path to your JVM
+javaOptions += "-Djava.library.path=/usr/local/lib"
+```
diff --git a/java/build.sbt b/java/build.sbt
new file mode 100644
index 0000000..617052c
--- /dev/null
+++ b/java/build.sbt
@@ -0,0 +1,64 @@
+name := "pdal-jni"
+
+lazy val commonSettings = Seq(
+  version := "1.4.0" + Environment.versionSuffix,
+  scalaVersion := "2.11.8",
+  crossScalaVersions := Seq("2.12.1", "2.11.8"),
+  organization := "io.pdal",
+  description := "PDAL JNI bindings",
+  licenses := Seq("BSD" -> url("https://github.com/PDAL/PDAL/blob/master/LICENSE.txt")),
+  homepage := Some(url("http://www.pdal.io")),
+  publishMavenStyle := true,
+  pomIncludeRepository := { _ => false },
+  scalacOptions ++= Seq(
+    "-deprecation",
+    "-unchecked",
+    "-language:implicitConversions",
+    "-language:reflectiveCalls",
+    "-language:higherKinds",
+    "-language:postfixOps",
+    "-language:existentials",
+    "-feature"),
+  test in assembly := {},
+  shellPrompt := { s => Project.extract(s).currentProject.id + " > " },
+  commands += Command.command("publish-javastyle")((state: State) => {
+    val extracted = Project extract state
+    import extracted._
+    val publishState = Command.process("publish", append(Seq(crossPaths := false), state))
+    append(Seq(crossPaths := true), publishState)
+  }),
+  publishArtifact in Test := false,
+  publishTo := {
+    val nexus = "https://oss.sonatype.org/"
+    if (isSnapshot.value)
+      Some("snapshots" at nexus + "content/repositories/snapshots")
+    else
+      Some("releases"  at nexus + "service/local/staging/deploy/maven2")
+  },
+  pomExtra := (
+    <scm>
+      <url>git at github.com:PDAL/PDAL.git</url>
+      <connection>scm:git:git at github.com:PDAL/PDAL.git</connection>
+    </scm>
+      <developers>
+        <developer>
+          <id>pomadchin</id>
+          <name>Grigory Pomadchin</name>
+          <url>http://github.com/pomadchin/</url>
+        </developer>
+      </developers>
+    )
+)
+
+lazy val root = (project in file(".")).aggregate(core, native)
+
+lazy val core = (project in file("core")).
+  settings(commonSettings: _*).
+  settings(name := "pdal").
+  settings(target in javah := (sourceDirectory in nativeCompile in native).value / "include").
+  settings(libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.0" % "test").
+  dependsOn(Environment.dependOnNative(native % Runtime):_*)
+
+lazy val native = (project in file("native")).
+  settings(sourceDirectory in nativeCompile := sourceDirectory.value).
+  enablePlugins(JniNative)
diff --git a/java/core/src/main/scala/io/pdal/DimType.scala b/java/core/src/main/scala/io/pdal/DimType.scala
new file mode 100644
index 0000000..a0b40b2
--- /dev/null
+++ b/java/core/src/main/scala/io/pdal/DimType.scala
@@ -0,0 +1,49 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+case class DimType(id: String, `type`: String, scale: Double = 1, offset: Double = 0)
+
+object DimType {
+  object Id {
+    val Unknown = "Unknown"
+    val X = "X"
+    val Y = "Y"
+    val Z = "Z"
+  }
+
+  def X = DimType(Id.X, "double")
+  def Y = DimType(Id.Y, "double")
+  def Z = DimType(Id.Z, "double")
+}
diff --git a/java/core/src/main/scala/io/pdal/Native.scala b/java/core/src/main/scala/io/pdal/Native.scala
new file mode 100644
index 0000000..949b0dd
--- /dev/null
+++ b/java/core/src/main/scala/io/pdal/Native.scala
@@ -0,0 +1,40 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+trait Native {
+  protected var nativeHandle = 0l // C++ pointer
+  def ptr(): Long = nativeHandle
+  def dispose(): Unit
+}
diff --git a/java/core/src/main/scala/io/pdal/Pipeline.scala b/java/core/src/main/scala/io/pdal/Pipeline.scala
new file mode 100644
index 0000000..c6882c0
--- /dev/null
+++ b/java/core/src/main/scala/io/pdal/Pipeline.scala
@@ -0,0 +1,56 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+import ch.jodersky.jni.nativeLoader
+
+class Pipeline(val json: String) extends Native {
+  Pipeline // reference the object so that the nativeLoader will load the JNI native libraries
+
+  @native def initialise(): Unit
+  @native def execute(): Unit
+  @native def getPointViews(): PointViewIterator
+  @native def dispose(): Unit
+  @native def getMetadata(): String
+  @native def getSchema(): String
+  @native def validate(): Boolean
+  @native def setLogLevel(i: Int): Unit
+  @native def getLogLevel(): Int
+  @native def getLog(): String
+}
+
+ at nativeLoader("pdaljni.1.4")
+object Pipeline {
+  def apply(json: String): Pipeline = { val p = new Pipeline(json); p.initialise(); p }
+}
diff --git a/java/core/src/main/scala/io/pdal/PointCloud.scala b/java/core/src/main/scala/io/pdal/PointCloud.scala
new file mode 100644
index 0000000..c743fcc
--- /dev/null
+++ b/java/core/src/main/scala/io/pdal/PointCloud.scala
@@ -0,0 +1,134 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+import java.nio.{ByteBuffer, ByteOrder}
+import java.util
+
+import scala.collection.JavaConversions._
+
+/**
+  * PointCloud abstraction to work with packed point(s) in JVM memory.
+  * SizedDimType contains size and offset for a particular packed point with the current set of dims.
+  **/
+case class PointCloud(bytes: Array[Byte], dimTypes: util.Map[String, SizedDimType]) {
+  val pointSize: Int = dimTypes.values.map(_.size).sum.toInt
+  val length: Int = bytes.length / pointSize
+  val isPoint: Boolean = length == pointSize
+
+  def dimSize(dim: SizedDimType) = dimTypes(dim.dimType.id).size
+  def dimSize(dim: DimType) = dimTypes(dim.id).size
+  def dimSize(dim: String) = dimTypes(dim).size
+  def findDimType(dim: String) = dimTypes(dim).dimType
+  def findSizedDimType(dim: String) = dimTypes(dim)
+
+  /**
+    * Reads a packed point by point id from a set of packed points.
+    */
+  def get(i: Int): Array[Byte] = {
+    if (isPoint) bytes
+    else {
+      val from = i * pointSize
+      val result = new Array[Byte](pointSize)
+      var j = 0
+      while(j < pointSize) {
+        result(j) = bytes(from + j)
+        j += 1
+      }
+      result
+    }
+  }
+
+  def getDouble(idx: Int, dim: SizedDimType): Double = getDouble(idx, dim.dimType)
+  def getDouble(idx: Int, dim: DimType): Double = getDouble(idx, dim.id)
+  def getDouble(idx: Int, dim: String): Double = get(idx, dim).getDouble
+
+  def getFloat(idx: Int, dim: SizedDimType): Float = getFloat(idx, dim.dimType.id)
+  def getFloat(idx: Int, dim: DimType): Float = getFloat(idx, dim.id)
+  def getFloat(idx: Int, dim: String): Float = get(idx, dim).getFloat
+
+  def getLong(idx: Int, dim: SizedDimType): Long = getLong(idx, dim.dimType.id)
+  def getLong(idx: Int, dim: DimType): Long = getLong(idx, dim.id)
+  def getLong(idx: Int, dim: String): Long = get(idx, dim).getLong
+
+  def getInt(idx: Int, dim: SizedDimType): Int = getInt(idx, dim.dimType.id)
+  def getInt(idx: Int, dim: DimType): Int = getInt(idx, dim.id)
+  def getInt(idx: Int, dim: String): Int = get(idx, dim).getInt
+
+  def getShort(idx: Int, dim: SizedDimType): Short = getShort(idx, dim.dimType.id)
+  def getShort(idx: Int, dim: DimType): Short = getShort(idx, dim.id)
+  def getShort(idx: Int, dim: String): Short = get(idx, dim).getShort
+
+  def getChar(idx: Int, dim: SizedDimType): Char = getChar(idx, dim.dimType.id)
+  def getChar(idx: Int, dim: DimType): Char = getChar(idx, dim.id)
+  def getChar(idx: Int, dim: String): Char = get(idx, dim).getChar
+
+  def getByte(idx: Int, dim: SizedDimType): Byte = getByte(idx, dim.dimType.id)
+  def getByte(idx: Int, dim: DimType): Byte = getByte(idx, dim.id)
+  def getByte(idx: Int, dim: String): Byte = get(idx, dim).get()
+
+  def get(idx: Int, dim: SizedDimType): ByteBuffer = get(idx, dim.dimType.id)
+  def get(idx: Int, dim: DimType): ByteBuffer = get(idx, dim.id)
+  def get(idx: Int, dim: String): ByteBuffer = ByteBuffer.wrap(get(get(idx), dim)).order(ByteOrder.nativeOrder())
+
+  def get(idx: Int, dims: Array[SizedDimType]): ByteBuffer = get(idx, dims.map(_.dimType.id))
+  def get(idx: Int, dims: Array[DimType]): ByteBuffer = get(idx, dims.map(_.id))
+  def get(idx: Int, dims: Array[String]): ByteBuffer = ByteBuffer.wrap(get(get(idx), dims)).order(ByteOrder.nativeOrder())
+
+  def getX(idx: Int): Double = getDouble(idx, DimType.Id.X)
+  def getY(idx: Int): Double = getDouble(idx, DimType.Id.Y)
+  def getZ(idx: Int): Double = getDouble(idx, DimType.Id.Z)
+
+  /**
+    * Reads dim from a packed point.
+    */
+  def get(packedPoint: Array[Byte], dim: String): Array[Byte] = {
+    val sdt = dimTypes(dim)
+    val from = sdt.offset.toInt
+    val dimSize = sdt.size.toInt
+    val result = new Array[Byte](dimSize)
+    var j = 0
+    while(j < dimSize) {
+      result(j) = packedPoint(from + j)
+      j += 1
+    }
+    result
+  }
+
+  /**
+    * Reads dims from a packed point.
+    */
+  private def get(packedPoint: Array[Byte], dims: Array[String]): Array[Byte] =
+    dims.map(get(bytes, _)).fold(Array[Byte]())(_ ++ _)
+}
diff --git a/java/core/src/main/scala/io/pdal/PointLayout.scala b/java/core/src/main/scala/io/pdal/PointLayout.scala
new file mode 100644
index 0000000..1585dec
--- /dev/null
+++ b/java/core/src/main/scala/io/pdal/PointLayout.scala
@@ -0,0 +1,67 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+import java.util
+import scala.collection.JavaConversions._
+
+class PointLayout extends Native {
+  def dimSize(dimType: DimType): Long = dimSize(dimType.id)
+  def dimPackedOffset(dimType: DimType): Long = dimPackedOffset(dimType.id)
+
+  def sizedDimTypes(): util.Map[String, SizedDimType] = toSizedDimTypes(dimTypes())
+  def toSizedDimTypes(dimTypes: Array[DimType]): util.Map[String, SizedDimType] = {
+    var (i, offset, length) = (0, 0l, dimTypes.length)
+    val result = new util.HashMap[String, SizedDimType]()
+    while(i < length) {
+      val dt = dimTypes(i)
+      val size = dimSize(dt)
+      result += dt.id -> SizedDimType(dt, size, offset)
+      offset += size
+      i += 1
+    }
+    result
+  }
+
+  @native def dimTypes(): Array[DimType]
+  @native def findDimType(name: String): DimType
+  @native def dimSize(id: String): Long
+  /**
+    * Offset of a dim in a packed points byte array calculated as a sum of previous dim sizes.
+    * Valid for a point with all dims.
+    */
+  @native def dimPackedOffset(id: String): Long
+  @native def pointSize(): Long
+  @native def dispose(): Unit
+}
diff --git a/java/core/src/main/scala/io/pdal/PointView.scala b/java/core/src/main/scala/io/pdal/PointView.scala
new file mode 100644
index 0000000..5d62256
--- /dev/null
+++ b/java/core/src/main/scala/io/pdal/PointView.scala
@@ -0,0 +1,156 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+import java.nio.{ByteBuffer, ByteOrder}
+
+class PointView extends Native {
+  def getPointCloud(idx: Long): PointCloud = getPointCloud(idx, layout.dimTypes())
+  def getPointCloud(idx: Long, dims: Array[DimType]): PointCloud =
+    PointCloud(
+      bytes    = getPackedPoint(idx, dims),
+      dimTypes = layout.toSizedDimTypes(dims)
+    )
+
+  def getPointCloud(): PointCloud = getPointCloud(layout.dimTypes())
+  def getPointCloud(dims: Array[DimType]): PointCloud =
+    PointCloud(
+      bytes    = getPackedPoints(dims),
+      dimTypes = layout.toSizedDimTypes(dims)
+    )
+
+  def getPackedPoint(idx: Long): Array[Byte] = getPackedPoint(idx, layout.dimTypes())
+  def getPackedPoints(): Array[Byte] = getPackedPoints(layout.dimTypes())
+  def findDimType(name: String): DimType = layout.findDimType(name)
+  def length(): Int = size()
+  def getCrsWKT(): String = getCrsWKT(pretty = false)
+
+  /**
+    * Reads a packed point by point id from a set of packed points.
+    */
+  def get(idx: Int, packedPoints: Array[Byte]): Array[Byte] = get(idx, packedPoints, layout.dimTypes())
+  def get(idx: Int, packedPoints: Array[Byte], dims: Array[DimType]): Array[Byte] = {
+    val pointSize = dims.map(layout.dimSize(_)).sum.toInt
+    val from = idx * pointSize
+    val result = new Array[Byte](pointSize)
+    var j = 0
+    while(j < pointSize) {
+      result(j) = packedPoints(from + j)
+      j += 1
+    }
+    result
+  }
+
+  /**
+    * Reads dim from a packed point, point should contain all layout dims.
+    */
+  def get(packedPoint: Array[Byte], dim: DimType): ByteBuffer = {
+    val from = layout.dimPackedOffset(dim).toInt
+    val dimSize = layout.dimSize(dim).toInt
+    val result = new Array[Byte](dimSize)
+    var j = 0
+    while(j < dimSize) {
+      result(j) = packedPoint(from + j)
+      j += 1
+    }
+    ByteBuffer.wrap(result).order(ByteOrder.nativeOrder())
+  }
+
+  def getDouble(packedPoint: Array[Byte], dim: String): Double = getDouble(packedPoint, findDimType(dim))
+  def getDouble(packedPoint: Array[Byte], dim: DimType): Double = get(packedPoint, dim).getDouble
+
+  def getFloat(packedPoint: Array[Byte], dim: String): Float = getFloat(packedPoint, findDimType(dim))
+  def getFloat(packedPoint: Array[Byte], dim: DimType): Float = get(packedPoint, dim).getFloat
+
+  def getLong(packedPoint: Array[Byte], dim: String): Long = getLong(packedPoint, findDimType(dim))
+  def getLong(packedPoint: Array[Byte], dim: DimType): Long = get(packedPoint, dim).getLong
+
+  def getInt(packedPoint: Array[Byte], dim: String): Int = getInt(packedPoint, findDimType(dim))
+  def getInt(packedPoint: Array[Byte], dim: DimType): Int = get(packedPoint, dim).getInt
+
+  def getShort(packedPoint: Array[Byte], dim: String): Short = getShort(packedPoint, findDimType(dim))
+  def getShort(packedPoint: Array[Byte], dim: DimType): Short = get(packedPoint, dim).getShort
+
+  def getChar(packedPoint: Array[Byte], dim: String): Char = getChar(packedPoint, findDimType(dim))
+  def getChar(packedPoint: Array[Byte], dim: DimType): Char = get(packedPoint, dim).getChar
+
+  def getByte(packedPoint: Array[Byte], dim: String): Byte = getByte(packedPoint, findDimType(dim))
+  def getByte(packedPoint: Array[Byte], dim: DimType): Byte = get(packedPoint, dim).get()
+
+  /**
+    * One dimension read; for multiple dims custom logic required.
+    */
+
+  def getDouble(idx: Int, dim: String): Double = getDouble(idx, findDimType(dim))
+  def getDouble(idx: Int, dim: DimType): Double = get(idx, dim).getDouble
+
+  def getFloat(idx: Int, dim: String): Float = getFloat(idx, findDimType(dim))
+  def getFloat(idx: Int, dim: DimType): Float = get(idx, dim).getFloat
+
+  def getLong(idx: Int, dim: String): Long = getLong(idx, findDimType(dim))
+  def getLong(idx: Int, dim: DimType): Long = get(idx, dim).getLong
+
+  def getInt(idx: Int, dim: String): Int = getInt(idx, findDimType(dim))
+  def getInt(idx: Int, dim: DimType): Int = get(idx, dim).getInt
+
+  def getShort(idx: Int, dim: String): Short = getShort(idx, findDimType(dim))
+  def getShort(idx: Int, dim: DimType): Short = get(idx, dim).getShort
+
+  def getChar(idx: Int, dim: String): Char = getChar(idx, findDimType(dim))
+  def getChar(idx: Int, dim: DimType): Char = get(idx, dim).getChar
+
+  def getByte(idx: Int, dim: String): Byte = getByte(idx, findDimType(dim))
+  def getByte(idx: Int, dim: DimType): Byte = get(idx, dim).get()
+
+  def get(idx: Int, dim: String): ByteBuffer = get(idx, findDimType(dim))
+  def get(idx: Int, dim: DimType): ByteBuffer =
+    ByteBuffer.wrap(getPackedPoint(idx, Array(dim))).order(ByteOrder.nativeOrder())
+
+  def getX(idx: Int): Double = getDouble(idx, DimType.X)
+  def getY(idx: Int): Double = getDouble(idx, DimType.Y)
+  def getZ(idx: Int): Double = getDouble(idx, DimType.Z)
+
+  def getX(packedPoint: Array[Byte]): Double = getDouble(packedPoint, DimType.X)
+  def getY(packedPoint: Array[Byte]): Double = getDouble(packedPoint, DimType.Y)
+  def getZ(packedPoint: Array[Byte]): Double = getDouble(packedPoint, DimType.Z)
+
+  @native def layout(): PointLayout
+  @native def size(): Int
+  @native def empty(): Boolean
+  @native def getCrsProj4(): String
+  @native def getCrsWKT(pretty: Boolean): String
+  @native def getPackedPoint(idx: Long, dims: Array[DimType]): Array[Byte]
+  @native def getPackedPoints(dims: Array[DimType]): Array[Byte]
+  @native def dispose(): Unit
+}
diff --git a/java/core/src/main/scala/io/pdal/PointViewIterator.scala b/java/core/src/main/scala/io/pdal/PointViewIterator.scala
new file mode 100644
index 0000000..3b2a2ce
--- /dev/null
+++ b/java/core/src/main/scala/io/pdal/PointViewIterator.scala
@@ -0,0 +1,42 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+import java.util
+
+class PointViewIterator extends util.Iterator[PointView] with Native {
+  @native def hasNext: Boolean
+  @native def next(): PointView
+  @native def dispose(): Unit
+}
diff --git a/java/core/src/main/scala/io/pdal/SizedDimType.scala b/java/core/src/main/scala/io/pdal/SizedDimType.scala
new file mode 100644
index 0000000..a1b3d46
--- /dev/null
+++ b/java/core/src/main/scala/io/pdal/SizedDimType.scala
@@ -0,0 +1,36 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+case class SizedDimType(dimType: DimType, size: Long, offset: Long)
diff --git a/java/core/src/test/resources/las.json b/java/core/src/test/resources/las.json
new file mode 100644
index 0000000..ae8ac35
--- /dev/null
+++ b/java/core/src/test/resources/las.json
@@ -0,0 +1,8 @@
+{
+  "pipeline":[
+    {
+      "filename":"../test/data/las/1.2-with-color.las",
+      "spatialreference":"EPSG:2993"
+    }
+  ]
+}
diff --git a/java/core/src/test/scala/io/pdal/PipelineSpec.scala b/java/core/src/test/scala/io/pdal/PipelineSpec.scala
new file mode 100644
index 0000000..a7d5eae
--- /dev/null
+++ b/java/core/src/test/scala/io/pdal/PipelineSpec.scala
@@ -0,0 +1,177 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+import java.nio.{ByteBuffer, ByteOrder}
+
+import scala.collection.JavaConverters._
+
+class PipelineSpec extends TestEnvironmentSpec {
+  describe("Pipeline execution") {
+    it("should validate as incorrect json (bad json passed)") {
+      val badPipeline = Pipeline(badJson)
+      badPipeline.validate() should be (false)
+      badPipeline.dispose()
+      badPipeline.ptr should be (0)
+    }
+
+    it("should validate json") {
+      pipeline.validate() should be (true)
+    }
+
+    it("should execute pipeline") {
+      pipeline.execute()
+    }
+
+    it("should create pointViews iterator") {
+      val pvi = pipeline.getPointViews()
+      pvi.asScala.length should be (1)
+      pvi.dispose()
+    }
+
+    it("should have a valid point view size") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.length should be (1065)
+      pvi.hasNext should be (false)
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read a valid (X, Y, Z) data") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.getX(0) should be (637012.24)
+      pv.getY(0) should be (849028.31)
+      pv.getZ(0) should be (431.66)
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read a valid packed data") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      val layout = pv.layout
+      val arr = pv.getPackedPoint(0, Array(DimType.X, DimType.Y))
+      val (xarr, yarr) = arr.take(layout.dimSize(DimType.X).toInt) -> arr.drop(layout.dimSize(DimType.Y).toInt)
+
+      ByteBuffer.wrap(xarr).order(ByteOrder.nativeOrder()).getDouble should be (pv.getX(0))
+      ByteBuffer.wrap(yarr).order(ByteOrder.nativeOrder()).getDouble should be (pv.getY(0))
+
+      layout.dispose()
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read the whole packed point and grab only one dim") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      val arr = pv.getPackedPoint(0)
+      pv.get(arr, DimType.Y).getDouble should be (pv.getY(0))
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read all packed points and grab only one point out of it") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.get(3, pv.getPackedPoints) should be (pv.getPackedPoint(3))
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read a valid value by name") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.getByte(0, "ReturnNumber") should be (1)
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read correctly data as a packed point") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      val layout = pv.layout
+      val arr = pv.getPackedPoint(0)
+      layout.dimTypes().foreach { dt => pv.get(0, dt).array() should be(pv.get(arr, dt).array())}
+      layout.dispose()
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("layout should have a valid number of dims") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.layout.dimTypes().length should be (16)
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should find a dim by name") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.findDimType("Red") should be (DimType("Red", "uint16_t"))
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("dim sizes should be of a valid size") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      val layout = pv.layout
+      layout.dimTypes().map(pv.layout.dimSize(_)).sum should be (layout.pointSize())
+      layout.dispose()
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read all packed points valid") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      val layout = pv.layout
+      pv.getPackedPoints.length should be (pv.length * layout.pointSize())
+      layout.dispose()
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read crs correct") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.getCrsProj4 should be (proj4String)
+      pv.dispose()
+      pvi.dispose()
+    }
+  }
+}
diff --git a/java/core/src/test/scala/io/pdal/PointCloudSpec.scala b/java/core/src/test/scala/io/pdal/PointCloudSpec.scala
new file mode 100644
index 0000000..ab8844d
--- /dev/null
+++ b/java/core/src/test/scala/io/pdal/PointCloudSpec.scala
@@ -0,0 +1,183 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+import java.nio.{ByteBuffer, ByteOrder}
+
+import scala.collection.JavaConversions._
+
+class PointCloudSpec extends TestEnvironmentSpec {
+  var packedPoints: PointCloud = _
+
+  describe("PointCloud in JVM memory operations") {
+    it("should init PackedPoints") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+
+      packedPoints = pv.getPointCloud
+
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should have a valid point view size") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.length should be (packedPoints.length)
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read a valid (X, Y, Z) data") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.getX(0) should be (packedPoints.getX(0))
+      pv.getY(0) should be (packedPoints.getY(0))
+      pv.getZ(0) should be (packedPoints.getZ(0))
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read a valid packed data") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      val layout = pv.layout
+      val arr = pv.getPackedPoint(0, Array(DimType.X, DimType.Y))
+      val (xarr, yarr) = arr.take(layout.dimSize(DimType.X).toInt) -> arr.drop(layout.dimSize(DimType.Y).toInt)
+
+      val marr = packedPoints.get(0, Array(DimType.X, DimType.Y))
+      val (xmarr, ymarr) = arr.take(packedPoints.dimSize(DimType.X).toInt) -> arr.drop(packedPoints.dimSize(DimType.Y).toInt)
+
+      xarr should be (xmarr)
+      yarr should be (ymarr)
+      ByteBuffer.wrap(xmarr).order(ByteOrder.nativeOrder()).getDouble should be (pv.getX(0))
+      ByteBuffer.wrap(ymarr).order(ByteOrder.nativeOrder()).getDouble should be (pv.getY(0))
+
+      layout.dispose()
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read the whole packed point and grab only one dim") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      packedPoints.get(0, DimType.Y).getDouble should be (pv.getY(0))
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read all packed points and grab only one point out of it") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.get(3, pv.getPackedPoints) should be (packedPoints.get(3))
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read a valid value by name") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.getByte(0, "ReturnNumber") should be (packedPoints.getByte(0, "ReturnNumber"))
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read correctly data as a packed point") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      packedPoints.dimTypes.foreach { case (_, sdt) =>
+        pv.get(0, sdt.dimType) should be (packedPoints.get(0, sdt))
+      }
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("layout should have a valid number of dims") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.layout.dimTypes().length should be (packedPoints.dimTypes.size)
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should find a dim by name") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      pv.findDimType("Red") should be (packedPoints.findDimType("Red"))
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("dim sizes should be of a valid size") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      val layout = pv.layout
+      layout.dimTypes().map(pv.layout.dimSize(_)).sum should be (packedPoints.pointSize)
+      layout.dispose()
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should read all packed points valid") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      val length = packedPoints.bytes.length
+      pv.getPackedPoints.length should be (length)
+      pv.dispose()
+      pvi.dispose()
+    }
+
+    it("should get correct points and all values") {
+      val pvi = pipeline.getPointViews()
+      val pv = pvi.next()
+      val length = pv.length
+      val dimTypes = packedPoints.dimTypes.values().map(_.dimType)
+      for (i <- 0 until length) {
+        packedPoints.get(i) should be (pv.getPackedPoint(i))
+        packedPoints.getX(i) should be (pv.getX(i))
+        packedPoints.getY(i) should be (pv.getY(i))
+        packedPoints.getZ(i) should be (pv.getZ(i))
+        dimTypes.foreach { dt =>
+          packedPoints.get(i, dt).array() should be (pv.get(i, dt).array())
+        }
+      }
+      pv.dispose()
+      pvi.dispose()
+    }
+  }
+
+  override def beforeAll() = {
+    pipeline.execute()
+  }
+}
diff --git a/java/core/src/test/scala/io/pdal/TestEnvironmentSpec.scala b/java/core/src/test/scala/io/pdal/TestEnvironmentSpec.scala
new file mode 100644
index 0000000..23c51ea
--- /dev/null
+++ b/java/core/src/test/scala/io/pdal/TestEnvironmentSpec.scala
@@ -0,0 +1,68 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+package io.pdal
+
+import org.scalatest._
+
+trait TestEnvironmentSpec extends FunSpec with Matchers with BeforeAndAfterAll {
+  def getJson(resource: String): String = {
+    val stream = getClass.getResourceAsStream(resource)
+    val lines = scala.io.Source.fromInputStream(stream).getLines
+    val json = lines.mkString(" ")
+    stream.close()
+    json
+  }
+
+  val json = getJson("/las.json")
+  val badJson =
+    """
+      |{
+      |  "pipeline": [
+      |    "nofile.las",
+      |    {
+      |        "type": "filters.sort",
+      |        "dimension": "X"
+      |    }
+      |  ]
+      |}
+     """.stripMargin
+
+  val proj4String = "+proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5 +x_0=400000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"
+
+  val pipeline: Pipeline = Pipeline(json)
+
+  override def afterAll() = {
+    pipeline.dispose()
+  }
+}
diff --git a/java/native/src/Accessors.cpp b/java/native/src/Accessors.cpp
new file mode 100644
index 0000000..ab46eca
--- /dev/null
+++ b/java/native/src/Accessors.cpp
@@ -0,0 +1,41 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "Accessors.hpp"
+
+jfieldID getHandleField(JNIEnv *env, jobject obj)
+{
+    jclass c = env->GetObjectClass(obj);
+    // J is the type signature for long:
+    return env->GetFieldID(c, "nativeHandle", "J");
+}
diff --git a/java/native/src/CMakeLists.txt b/java/native/src/CMakeLists.txt
new file mode 100644
index 0000000..e216478
--- /dev/null
+++ b/java/native/src/CMakeLists.txt
@@ -0,0 +1,71 @@
+cmake_minimum_required(VERSION 2.8.0)
+
+set(ignoreMe "${SBT}") # sbt-jni defines -DSBT
+set(MAKE_COLOR_MAKEFILE ON)
+
+# Define project and related variables
+# (required by sbt-jni) please use semantic versioning
+#
+
+project (pdaljni)
+set(PROJECT_VERSION_MAJOR 1)
+set(PROJECT_VERSION_MINOR 4)
+set(PROJECT_VERSION_PATCH 0)
+
+set(PDAL_LIB_NAME pdalcpp)
+
+if (APPLE)
+  set(CMAKE_MACOSX_RPATH ON)
+  SET(CMAKE_SKIP_BUILD_RPATH TRUE)
+  SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
+  SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
+endif ()
+
+if (NOT PDAL_BUILD)
+    set(CMAKE_CXX_FLAGS "-std=c++11")
+endif()
+
+# Setup JNI
+find_package(JNI REQUIRED)
+if (JNI_FOUND)
+    message (STATUS "JNI include directories: ${JNI_INCLUDE_DIRS}")
+endif()
+
+if (NOT PDAL_BUILD)
+    find_package(PDAL 1.0.0 REQUIRED CONFIG)
+endif()
+
+# Include directories
+include_directories(.)
+include_directories(include)
+include_directories(${JNI_INCLUDE_DIRS})
+if (APPLE)
+    include_directories(/usr/local/opt/libxml2/include/libxml2)
+else ()
+    include_directories(/usr/include/libxml2)
+endif ()
+
+# Sources
+file(GLOB LIB_SRC
+  "*.c"
+  "*.cc"
+  "*.cpp"
+)
+
+# Setup installation targets
+# (required by sbt-jni) major version should always be appended to library name
+#
+set (LIB_NAME ${PROJECT_NAME}.${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR})
+if (PDAL_BUILD)
+    PDAL_ADD_LIBRARY(${LIB_NAME} ${LIB_SRC})
+    target_link_libraries(${LIB_NAME} PUBLIC
+        ${PDAL_BASE_LIB_NAME}
+        ${PDAL_UTIL_LIB_NAME})
+    target_include_directories(${LIB_NAME} PRIVATE
+        ${PROJECT_BINARY_DIR}/../../../include)
+    install(TARGETS ${LIB_NAME} LIBRARY DESTINATION ${PDAL_LIB_INSTALL_DIR})
+else ()
+    add_library(${LIB_NAME} SHARED ${LIB_SRC})
+    install(TARGETS ${LIB_NAME} LIBRARY DESTINATION . OPTIONAL)
+    target_link_libraries(${LIB_NAME} PRIVATE ${PDAL_LIB_NAME})
+endif ()
diff --git a/java/native/src/JavaPipeline.cpp b/java/native/src/JavaPipeline.cpp
new file mode 100644
index 0000000..d39277c
--- /dev/null
+++ b/java/native/src/JavaPipeline.cpp
@@ -0,0 +1,83 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "JavaPipeline.hpp"
+#ifdef PDAL_HAVE_LIBXML2
+#include <pdal/XMLSchema.hpp>
+#endif
+
+using pdal::PointViewSet;
+
+namespace libpdaljava
+{
+
+Pipeline::Pipeline(std::string const& json)
+    : m_executor(json)
+{
+
+}
+
+Pipeline::~Pipeline()
+{
+}
+
+void Pipeline::setLogLevel(int level)
+{
+    m_executor.setLogLevel(level);
+}
+
+int Pipeline::getLogLevel() const
+{
+    return static_cast<int>(m_executor.getLogLevel());
+}
+
+int64_t Pipeline::execute()
+{
+
+    int64_t count = m_executor.execute();
+    return count;
+}
+
+bool Pipeline::validate()
+{
+    return m_executor.validate();
+}
+
+PointViewSet Pipeline::getPointViews() const
+{
+    if (!m_executor.executed())
+        throw java_error("call execute() before fetching arrays");
+
+    return m_executor.getManagerConst().views();
+}
+} //namespace libpdaljava
diff --git a/java/native/src/PointViewRawPtr.cpp b/java/native/src/PointViewRawPtr.cpp
new file mode 100644
index 0000000..8350e33
--- /dev/null
+++ b/java/native/src/PointViewRawPtr.cpp
@@ -0,0 +1,46 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "PointViewRawPtr.hpp"
+
+using pdal::PointViewPtr;
+
+namespace libpdaljava
+{
+PointViewRawPtr::PointViewRawPtr(PointViewPtr p)
+    : shared_pointer{p}
+{ }
+
+PointViewRawPtr::~PointViewRawPtr()
+{ }
+}
diff --git a/java/native/src/include/Accessors.hpp b/java/native/src/include/Accessors.hpp
new file mode 100644
index 0000000..22f63a4
--- /dev/null
+++ b/java/native/src/include/Accessors.hpp
@@ -0,0 +1,55 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <jni.h>
+#include <string>
+
+#ifndef _ACCESSORS_H_INCLUDED_
+#define _ACCESSORS_H_INCLUDED_
+
+jfieldID getHandleField(JNIEnv *, jobject);
+
+template <typename T>
+T *getHandle(JNIEnv *env, jobject obj)
+{
+    jlong handle = env->GetLongField(obj, getHandleField(env, obj));
+    return reinterpret_cast<T *>(handle);
+}
+
+template <typename T>
+void setHandle(JNIEnv *env, jobject obj, T *t)
+{
+    jlong handle = reinterpret_cast<jlong>(t);
+    env->SetLongField(obj, getHandleField(env, obj), handle);
+}
+#endif
diff --git a/java/native/src/include/JavaIterator.hpp b/java/native/src/include/JavaIterator.hpp
new file mode 100644
index 0000000..0ba678e
--- /dev/null
+++ b/java/native/src/include/JavaIterator.hpp
@@ -0,0 +1,77 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <iostream>
+#include <string>
+#include <set>
+#include "JavaPipeline.hpp"
+
+#ifndef _JAVAITERATOR_H_INCLUDED_
+#define _JAVAITERATOR_H_INCLUDED_
+
+using pdal::PointViewLess;
+using pdal::PointViewPtr;
+
+namespace libpdaljava
+{
+template <class K, class V>
+class JavaIterator {
+public:
+	JavaIterator() {}
+	JavaIterator(const std::set<K, V> set)
+        : container{set}, curr_pos{0}
+    { }
+    JavaIterator(const std::set<K, V> *set)
+        : container{*set}, curr_pos{0}
+    { }
+	bool hasNext() const {
+		return curr_pos < container.size();
+	}
+	K next() {
+	    if(!hasNext())
+            throw java_error("iterator is out of bounds");
+
+	    return *std::next(container.begin(), curr_pos++);
+    }
+    int size() const {
+        return container.size();
+    }
+
+private:
+	std::set<K, V> container;
+	unsigned int curr_pos;
+};
+
+typedef JavaIterator<PointViewPtr, PointViewLess> PointViewIterator;
+}
+#endif
diff --git a/java/native/src/include/JavaPipeline.hpp b/java/native/src/include/JavaPipeline.hpp
new file mode 100644
index 0000000..229652a
--- /dev/null
+++ b/java/native/src/include/JavaPipeline.hpp
@@ -0,0 +1,91 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/PipelineManager.hpp>
+#include <pdal/PipelineWriter.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/PipelineExecutor.hpp>
+
+#include <string>
+#include <sstream>
+#undef toupper
+#undef tolower
+#undef isspace
+
+namespace libpdaljava
+{
+
+class java_error : public std::runtime_error
+{
+public:
+    inline java_error(std::string const& msg) : std::runtime_error(msg)
+        {}
+};
+
+class Pipeline {
+public:
+    Pipeline(std::string const& xml);
+    ~Pipeline();
+
+    int64_t execute();
+    bool validate();
+    inline std::string getPipeline() const
+    {
+        return m_executor.getPipeline();
+    }
+    inline std::string getMetadata() const
+    {
+        return m_executor.getMetadata();
+    }
+    inline std::string getSchema() const
+    {
+        return m_executor.getSchema();
+    }
+    inline std::string getLog() const
+    {
+        return m_executor.getLog();
+    }
+    pdal::PointViewSet getPointViews() const;
+
+    void setLogLevel(int level);
+    int getLogLevel() const;
+
+private:
+
+    pdal::PipelineExecutor m_executor;
+
+};
+
+}
diff --git a/java/native/src/include/PointViewRawPtr.hpp b/java/native/src/include/PointViewRawPtr.hpp
new file mode 100644
index 0000000..23ed2f6
--- /dev/null
+++ b/java/native/src/include/PointViewRawPtr.hpp
@@ -0,0 +1,55 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "JavaPipeline.hpp"
+
+#ifndef _POINTVIEWRAWPTR_H_INCLUDED_
+#define _POINTVIEWRAWPTR_H_INCLUDED_
+
+using pdal::PointViewPtr;
+
+/**
+ * PointView wrapper for safer work with PointView shared_pointer as with a common pointer
+ */
+namespace libpdaljava
+{
+class PointViewRawPtr
+{
+public:
+    PointViewPtr shared_pointer;
+
+    PointViewRawPtr(PointViewPtr);
+    ~PointViewRawPtr();
+};
+}
+#endif
diff --git a/java/native/src/include/io_pdal_Pipeline.h b/java/native/src/include/io_pdal_Pipeline.h
new file mode 100644
index 0000000..2c8c5ad
--- /dev/null
+++ b/java/native/src/include/io_pdal_Pipeline.h
@@ -0,0 +1,93 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class io_pdal_Pipeline */
+
+#ifndef _Included_io_pdal_Pipeline
+#define _Included_io_pdal_Pipeline
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     io_pdal_Pipeline
+ * Method:    initialise
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_io_pdal_Pipeline_initialise
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_Pipeline
+ * Method:    execute
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_io_pdal_Pipeline_execute
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_Pipeline
+ * Method:    getPointViews
+ * Signature: ()Lio/pdal/PointViewIterator;
+ */
+JNIEXPORT jobject JNICALL Java_io_pdal_Pipeline_getPointViews
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_Pipeline
+ * Method:    dispose
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_io_pdal_Pipeline_dispose
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_Pipeline
+ * Method:    getMetadata
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_io_pdal_Pipeline_getMetadata
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_Pipeline
+ * Method:    getSchema
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_io_pdal_Pipeline_getSchema
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_Pipeline
+ * Method:    validate
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_io_pdal_Pipeline_validate
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_Pipeline
+ * Method:    setLogLevel
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_io_pdal_Pipeline_setLogLevel
+  (JNIEnv *, jobject, jint);
+
+/*
+ * Class:     io_pdal_Pipeline
+ * Method:    getLogLevel
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_io_pdal_Pipeline_getLogLevel
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_Pipeline
+ * Method:    getLog
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_io_pdal_Pipeline_getLog
+  (JNIEnv *, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/java/native/src/include/io_pdal_PointLayout.h b/java/native/src/include/io_pdal_PointLayout.h
new file mode 100644
index 0000000..bba5277
--- /dev/null
+++ b/java/native/src/include/io_pdal_PointLayout.h
@@ -0,0 +1,61 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class io_pdal_PointLayout */
+
+#ifndef _Included_io_pdal_PointLayout
+#define _Included_io_pdal_PointLayout
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     io_pdal_PointLayout
+ * Method:    dimTypes
+ * Signature: ()[Lio/pdal/DimType;
+ */
+JNIEXPORT jobjectArray JNICALL Java_io_pdal_PointLayout_dimTypes
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_PointLayout
+ * Method:    findDimType
+ * Signature: (Ljava/lang/String;)Lio/pdal/DimType;
+ */
+JNIEXPORT jobject JNICALL Java_io_pdal_PointLayout_findDimType
+  (JNIEnv *, jobject, jstring);
+
+/*
+ * Class:     io_pdal_PointLayout
+ * Method:    dimSize
+ * Signature: (Ljava/lang/String;)J
+ */
+JNIEXPORT jlong JNICALL Java_io_pdal_PointLayout_dimSize
+  (JNIEnv *, jobject, jstring);
+
+/*
+ * Class:     io_pdal_PointLayout
+ * Method:    dimPackedOffset
+ * Signature: (Ljava/lang/String;)J
+ */
+JNIEXPORT jlong JNICALL Java_io_pdal_PointLayout_dimPackedOffset
+  (JNIEnv *, jobject, jstring);
+
+/*
+ * Class:     io_pdal_PointLayout
+ * Method:    pointSize
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_io_pdal_PointLayout_pointSize
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_PointLayout
+ * Method:    dispose
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_io_pdal_PointLayout_dispose
+  (JNIEnv *, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/java/native/src/include/io_pdal_PointView.h b/java/native/src/include/io_pdal_PointView.h
new file mode 100644
index 0000000..0d26817
--- /dev/null
+++ b/java/native/src/include/io_pdal_PointView.h
@@ -0,0 +1,77 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class io_pdal_PointView */
+
+#ifndef _Included_io_pdal_PointView
+#define _Included_io_pdal_PointView
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     io_pdal_PointView
+ * Method:    layout
+ * Signature: ()Lio/pdal/PointLayout;
+ */
+JNIEXPORT jobject JNICALL Java_io_pdal_PointView_layout
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_PointView
+ * Method:    size
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_io_pdal_PointView_size
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_PointView
+ * Method:    empty
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_io_pdal_PointView_empty
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_PointView
+ * Method:    getCrsProj4
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_io_pdal_PointView_getCrsProj4
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_PointView
+ * Method:    getCrsWKT
+ * Signature: (IZ)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_io_pdal_PointView_getCrsWKT
+  (JNIEnv *, jobject, jboolean);
+
+/*
+ * Class:     io_pdal_PointView
+ * Method:    getPackedPoint
+ * Signature: (J[Lio/pdal/DimType;)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_io_pdal_PointView_getPackedPoint
+  (JNIEnv *, jobject, jlong, jobjectArray);
+
+/*
+ * Class:     io_pdal_PointView
+ * Method:    getPackedPoints
+ * Signature: ([Lio/pdal/DimType;)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_io_pdal_PointView_getPackedPoints
+  (JNIEnv *, jobject, jobjectArray);
+
+/*
+ * Class:     io_pdal_PointView
+ * Method:    dispose
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_io_pdal_PointView_dispose
+  (JNIEnv *, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/java/native/src/include/io_pdal_PointViewIterator.h b/java/native/src/include/io_pdal_PointViewIterator.h
new file mode 100644
index 0000000..72518b2
--- /dev/null
+++ b/java/native/src/include/io_pdal_PointViewIterator.h
@@ -0,0 +1,37 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class io_pdal_PointViewIterator */
+
+#ifndef _Included_io_pdal_PointViewIterator
+#define _Included_io_pdal_PointViewIterator
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     io_pdal_PointViewIterator
+ * Method:    hasNext
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_io_pdal_PointViewIterator_hasNext
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_PointViewIterator
+ * Method:    next
+ * Signature: ()Lio/pdal/PointView;
+ */
+JNIEXPORT jobject JNICALL Java_io_pdal_PointViewIterator_next
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     io_pdal_PointViewIterator
+ * Method:    dispose
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_io_pdal_PointViewIterator_dispose
+  (JNIEnv *, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/java/native/src/io_pdal_Pipeline.cpp b/java/native/src/io_pdal_Pipeline.cpp
new file mode 100644
index 0000000..2184d30
--- /dev/null
+++ b/java/native/src/io_pdal_Pipeline.cpp
@@ -0,0 +1,144 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <stdio.h>
+#include <iostream>
+#include <string>
+
+#include "io_pdal_Pipeline.h"
+#include "JavaPipeline.hpp"
+#include "JavaIterator.hpp"
+#include "Accessors.hpp"
+
+using libpdaljava::Pipeline;
+using libpdaljava::PointViewIterator;
+
+using pdal::PointViewSet;
+using pdal::PointView;
+using pdal::PointViewLess;
+using pdal::PointViewPtr;
+using pdal::pdal_error;
+
+JNIEXPORT void JNICALL Java_io_pdal_Pipeline_initialise
+  (JNIEnv *env, jobject obj)
+{
+    jclass c = env->GetObjectClass(obj);
+    jfieldID fid = env->GetFieldID(c, "json", "Ljava/lang/String;");
+    jstring jstr = reinterpret_cast<jstring>(env->GetObjectField(obj, fid));
+    setHandle(env, obj, new Pipeline(std::string(env->GetStringUTFChars(jstr, 0))));
+}
+
+JNIEXPORT void JNICALL Java_io_pdal_Pipeline_dispose
+  (JNIEnv *env, jobject obj)
+{
+    Pipeline *p = getHandle<Pipeline>(env, obj);
+    setHandle<int>(env, obj, 0);
+    delete p;
+}
+
+JNIEXPORT void JNICALL Java_io_pdal_Pipeline_execute
+  (JNIEnv *env, jobject obj)
+{
+    Pipeline *p = getHandle<Pipeline>(env, obj);
+    p->execute();
+}
+
+JNIEXPORT jstring JNICALL Java_io_pdal_Pipeline_getMetadata
+  (JNIEnv *env, jobject obj)
+{
+    Pipeline *p = getHandle<Pipeline>(env, obj);
+    return env->NewStringUTF(p->getMetadata().c_str());
+}
+
+JNIEXPORT jstring JNICALL Java_io_pdal_Pipeline_getSchema
+  (JNIEnv *env, jobject obj)
+{
+    Pipeline *p = getHandle<Pipeline>(env, obj);
+    return env->NewStringUTF(p->getSchema().c_str());
+}
+
+JNIEXPORT jboolean JNICALL Java_io_pdal_Pipeline_validate
+  (JNIEnv *env, jobject obj)
+{
+    Pipeline *p = getHandle<Pipeline>(env, obj);
+    bool result;
+    try
+    {
+        result = p->validate();
+    }
+    catch(const pdal_error& pe)
+    {
+        std::cerr << "Runtime error: " << pe.what() << std::endl;
+        result = false;
+    }
+
+    return result;
+}
+
+JNIEXPORT void JNICALL Java_io_pdal_Pipeline_setLogLevel
+  (JNIEnv *env, jobject obj, jint i)
+{
+    Pipeline *p = getHandle<Pipeline>(env, obj);
+    p->setLogLevel(i);
+}
+
+JNIEXPORT jint JNICALL Java_io_pdal_Pipeline_getLogLevel
+  (JNIEnv *env, jobject obj)
+{
+    Pipeline *p = getHandle<Pipeline>(env, obj);
+    return p->getLogLevel();
+}
+
+JNIEXPORT jstring JNICALL Java_io_pdal_Pipeline_getLog
+  (JNIEnv *env, jobject obj)
+{
+    Pipeline *p = getHandle<Pipeline>(env, obj);
+    return env->NewStringUTF(p->getLog().c_str());
+}
+
+JNIEXPORT jobject JNICALL Java_io_pdal_Pipeline_getPointViews
+  (JNIEnv *env, jobject obj)
+{
+    Pipeline *p = getHandle<Pipeline>(env, obj);
+    PointViewSet pvset = p->getPointViews();
+
+    jclass pviClass = env->FindClass("io/pdal/PointViewIterator");
+    jmethodID pviCtor = env->GetMethodID(pviClass, "<init>", "()V");
+    jobject pvi = env->NewObject(pviClass, pviCtor);
+
+    PointViewIterator *it = new PointViewIterator(pvset);
+
+    setHandle(env, pvi, it);
+
+    return pvi;
+}
diff --git a/java/native/src/io_pdal_PointLayout.cpp b/java/native/src/io_pdal_PointLayout.cpp
new file mode 100644
index 0000000..8ee09f5
--- /dev/null
+++ b/java/native/src/io_pdal_PointLayout.cpp
@@ -0,0 +1,133 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <stdio.h>
+#include <vector>
+#include "io_pdal_PointLayout.h"
+#include "JavaPipeline.hpp"
+#include "Accessors.hpp"
+
+using pdal::PointLayout;
+using pdal::DimTypeList;
+using pdal::DimType;
+
+JNIEXPORT jobjectArray JNICALL Java_io_pdal_PointLayout_dimTypes
+  (JNIEnv *env, jobject obj)
+{
+    PointLayout *pl = getHandle<PointLayout>(env, obj);
+    DimTypeList dimTypes = pl->dimTypes();
+
+    jclass dtClass = env->FindClass("io/pdal/DimType");
+    jmethodID dtCtor = env->GetMethodID(dtClass, "<init>", "(Ljava/lang/String;Ljava/lang/String;DD)V");
+
+    jobjectArray result = env->NewObjectArray(dimTypes.size(), dtClass, NULL);
+
+    for (long i = 0; i < static_cast<long>(dimTypes.size()); i++)
+    {
+        auto dt = dimTypes.at(i);
+        jstring id = env->NewStringUTF(pdal::Dimension::name(dt.m_id).c_str());
+        jstring type = env->NewStringUTF(pdal::Dimension::interpretationName(dt.m_type).c_str());
+        jobject element = env->NewObject(dtClass, dtCtor, id, type, dt.m_xform.m_scale.m_val, dt.m_xform.m_offset.m_val);
+
+        env->SetObjectArrayElement(result, i, element);
+
+        env->DeleteLocalRef(element);
+        env->DeleteLocalRef(type);
+        env->DeleteLocalRef(id);
+    }
+
+    return result;
+}
+
+JNIEXPORT jobject JNICALL Java_io_pdal_PointLayout_findDimType
+  (JNIEnv *env, jobject obj, jstring jstr)
+{
+    std::string fid = std::string(env->GetStringUTFChars(jstr, 0));
+    PointLayout *pl = getHandle<PointLayout>(env, obj);
+    DimType dt = pl->findDimType(fid);
+    jstring id = env->NewStringUTF(pdal::Dimension::name(dt.m_id).c_str());
+    jstring type = env->NewStringUTF(pdal::Dimension::interpretationName(dt.m_type).c_str());
+
+    jclass dtClass = env->FindClass("io/pdal/DimType");
+    jmethodID dtCtor = env->GetMethodID(dtClass, "<init>", "(Ljava/lang/String;Ljava/lang/String;DD)V");
+    jobject result = env->NewObject(dtClass, dtCtor, id, type, dt.m_xform.m_scale.m_val, dt.m_xform.m_offset.m_val);
+
+    return result;
+}
+
+JNIEXPORT jlong JNICALL Java_io_pdal_PointLayout_dimSize
+  (JNIEnv *env, jobject obj, jstring jstr)
+{
+    std::string fid = std::string(env->GetStringUTFChars(jstr, 0));
+    PointLayout *pl = getHandle<PointLayout>(env, obj);
+
+    return pl->dimSize(pl->findDim(fid));
+}
+
+JNIEXPORT jlong JNICALL Java_io_pdal_PointLayout_dimPackedOffset
+  (JNIEnv *env, jobject obj, jstring jstr)
+{
+    std::string fid = std::string(env->GetStringUTFChars(jstr, 0));
+    PointLayout *pl = getHandle<PointLayout>(env, obj);
+    DimType dimType = pl->findDimType(fid);
+    DimTypeList dims = pl->dimTypes();
+
+    auto it = std::find_if(dims.begin(), dims.end(), [&dimType](const DimType& dt) {
+        return pdal::Dimension::name(dt.m_id) == pdal::Dimension::name(dimType.m_id);
+    });
+    auto index = std::distance(dims.begin(), it);
+    long offset = 0;
+
+    for(int i = 0; i < index; i++)
+    {
+        offset += pl->dimSize(dims.at(i).m_id);
+    }
+
+    return offset;
+}
+
+JNIEXPORT jlong JNICALL Java_io_pdal_PointLayout_pointSize
+  (JNIEnv *env, jobject obj)
+{
+    PointLayout *pl = getHandle<PointLayout>(env, obj);
+    return pl->pointSize();
+}
+
+JNIEXPORT void JNICALL Java_io_pdal_PointLayout_dispose
+  (JNIEnv *env, jobject obj)
+{
+    // A bit unclear why we can't remove this pointer, probably wrapping here makes sense as well
+    // PointLayout *pl = getHandle<PointLayout>(env, obj);
+    setHandle<int>(env, obj, 0);
+    // delete pl;
+}
diff --git a/java/native/src/io_pdal_PointView.cpp b/java/native/src/io_pdal_PointView.cpp
new file mode 100644
index 0000000..ff87288
--- /dev/null
+++ b/java/native/src/io_pdal_PointView.cpp
@@ -0,0 +1,213 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <stdio.h>
+#include <vector>
+#include "io_pdal_PointView.h"
+#include "JavaPipeline.hpp"
+#include "PointViewRawPtr.hpp"
+#include "Accessors.hpp"
+
+using libpdaljava::Pipeline;
+using libpdaljava::PointViewRawPtr;
+
+using pdal::PointView;
+using pdal::PointViewPtr;
+using pdal::PointLayoutPtr;
+using pdal::Dimension::Type;
+using pdal::Dimension::Id;
+using pdal::PointId;
+using pdal::DimTypeList;
+using pdal::SpatialReference;
+using pdal::DimType;
+
+/// Converts JavaArray of DimTypes (In Java interpretation DimType is a pair of strings)
+/// into DimTypeList (vector of DimTypes), puts dim size into bufSize
+/// \param[in] env       JNI environment
+/// \param[in] dims      JavaArray of DimTypes
+/// \param[in] bufSize   Dims sum size
+/// \param[in] dimTypes  Vector of DimTypes
+void convertDimTypeJavaArrayToVector(JNIEnv *env, jobjectArray dims, std::size_t *pointSize, DimTypeList *dimTypes) {
+    for (jint i = 0; i < env->GetArrayLength(dims); i++) {
+        jobject jDimType = reinterpret_cast<jobject>(env->GetObjectArrayElement(dims, i));
+        jclass cDimType = env->GetObjectClass(jDimType);
+        jfieldID fid = env->GetFieldID(cDimType, "id", "Ljava/lang/String;");
+        jfieldID ftype = env->GetFieldID(cDimType, "type", "Ljava/lang/String;");
+        jfieldID fscale = env->GetFieldID(cDimType, "scale", "D");
+        jfieldID foffset = env->GetFieldID(cDimType, "offset", "D");
+
+        jstring jid = reinterpret_cast<jstring>(env->GetObjectField(jDimType, fid));
+        jstring jtype = reinterpret_cast<jstring>(env->GetObjectField(jDimType, ftype));
+        jdouble jscale = env->GetDoubleField(jDimType, fscale);
+        jdouble joffset = env->GetDoubleField(jDimType, foffset);
+
+        Id id = pdal::Dimension::id(std::string(env->GetStringUTFChars(jid, 0)));
+        Type type = pdal::Dimension::type(std::string(env->GetStringUTFChars(jtype, 0)));
+
+        *pointSize += pdal::Dimension::size(type);
+        dimTypes->insert(dimTypes->begin() + i, DimType(id, type, jscale, joffset));
+    }
+}
+
+/// Fill a buffer with point data specified by the dimension list, accounts index
+/// Using this functions it is possible to pack all points into one buffer
+/// \param[in] pv    PointView pointer.
+/// \param[in] dims  List of dimensions/types to retrieve.
+/// \param[in] idx   Index of point to get.
+/// \param[in] buf   Pointer to buffer to fill.
+void appendPackedPoint(PointViewPtr pv, const DimTypeList& dims, PointId idx, std::size_t pointSize, char *buf)
+{
+    std::size_t from = idx * pointSize;
+    if(from >= pv->size() * pointSize) return;
+    buf += from;
+    pv->getPackedPoint(dims, idx, buf);
+}
+
+JNIEXPORT jobject JNICALL Java_io_pdal_PointView_layout
+  (JNIEnv *env, jobject obj)
+{
+    PointViewRawPtr *pvrp = getHandle<PointViewRawPtr>(env, obj);
+    PointViewPtr pv = pvrp->shared_pointer;
+    PointLayoutPtr pl = pv->layout();
+
+    jclass pvlClass = env->FindClass("io/pdal/PointLayout");
+    jmethodID pvlCtor = env->GetMethodID(pvlClass, "<init>", "()V");
+    jobject pvl = env->NewObject(pvlClass, pvlCtor);
+
+    setHandle(env, pvl, pl);
+
+    return pvl;
+}
+
+JNIEXPORT jint JNICALL Java_io_pdal_PointView_size
+  (JNIEnv *env, jobject obj)
+{
+    PointViewRawPtr *pvrp = getHandle<PointViewRawPtr>(env, obj);
+    PointViewPtr pv = pvrp->shared_pointer;
+    return pv->size();
+}
+
+JNIEXPORT jboolean JNICALL Java_io_pdal_PointView_empty
+  (JNIEnv *env, jobject obj)
+{
+    PointViewRawPtr *pvrp = getHandle<PointViewRawPtr>(env, obj);
+    PointViewPtr pv = pvrp->shared_pointer;
+    return pv->empty();
+}
+
+JNIEXPORT jstring JNICALL Java_io_pdal_PointView_getCrsProj4
+  (JNIEnv *env, jobject obj)
+{
+    PointViewRawPtr *pvrp = getHandle<PointViewRawPtr>(env, obj);
+    PointViewPtr pv = pvrp->shared_pointer;
+    return env->NewStringUTF(pv->spatialReference().getProj4().c_str());
+}
+
+JNIEXPORT jstring JNICALL Java_io_pdal_PointView_getCrsWKT
+  (JNIEnv *env, jobject obj, jboolean pretty)
+{
+    PointViewRawPtr *pvrp = getHandle<PointViewRawPtr>(env, obj);
+    PointViewPtr pv = pvrp->shared_pointer;
+
+    std::string wkt = pv->spatialReference().getWKT();
+
+    if(pretty) wkt = SpatialReference::prettyWkt(wkt);
+
+    return env->NewStringUTF(wkt.c_str());
+}
+
+JNIEXPORT jbyteArray JNICALL Java_io_pdal_PointView_getPackedPoint
+  (JNIEnv *env, jobject obj, jlong idx, jobjectArray dims)
+{
+    PointViewRawPtr *pvrp = getHandle<PointViewRawPtr>(env, obj);
+    PointViewPtr pv = pvrp->shared_pointer;
+
+    PointLayoutPtr pl = pv->layout();
+
+    // we need to calculate buffer size
+    std::size_t pointSize = 0;
+    DimTypeList dimTypes;
+
+    // calculate result buffer length (for one point) and get dimTypes
+    convertDimTypeJavaArrayToVector(env, dims, &pointSize, &dimTypes);
+
+    char *buf = new char[pointSize];
+
+    pv->getPackedPoint(dimTypes, idx, buf);
+
+    jbyteArray array = env->NewByteArray(pointSize);
+    env->SetByteArrayRegion (array, 0, pointSize, reinterpret_cast<jbyte *>(buf));
+
+    delete[] buf;
+
+    return array;
+}
+
+JNIEXPORT jbyteArray JNICALL Java_io_pdal_PointView_getPackedPoints
+  (JNIEnv *env, jobject obj, jobjectArray dims)
+{
+    PointViewRawPtr *pvrp = getHandle<PointViewRawPtr>(env, obj);
+    PointViewPtr pv = pvrp->shared_pointer;
+
+    PointLayoutPtr pl = pv->layout();
+
+    // we need to calculate buffer size
+    std::size_t pointSize = 0;
+    DimTypeList dimTypes;
+
+    // calculate result buffer length (for one point) and get dimTypes
+    convertDimTypeJavaArrayToVector(env, dims, &pointSize, &dimTypes);
+
+    // reading all points
+    std::size_t bufSize = pointSize * pv->size();
+    char *buf = new char[bufSize];
+
+    for (PointId idx = 0; idx < pv->size(); idx++) {
+        appendPackedPoint(pv, dimTypes, idx, pointSize, buf);
+    }
+
+    jbyteArray array = env->NewByteArray(bufSize);
+    env->SetByteArrayRegion (array, 0, bufSize, reinterpret_cast<jbyte *>(buf));
+
+    delete[] buf;
+
+    return array;
+}
+
+JNIEXPORT void JNICALL Java_io_pdal_PointView_dispose
+  (JNIEnv *env, jobject obj)
+{
+    PointViewRawPtr *pvrp = getHandle<PointViewRawPtr>(env, obj);
+    setHandle<int>(env, obj, 0);
+    delete pvrp;
+}
diff --git a/java/native/src/io_pdal_PointViewIterator.cpp b/java/native/src/io_pdal_PointViewIterator.cpp
new file mode 100644
index 0000000..1a007dd
--- /dev/null
+++ b/java/native/src/io_pdal_PointViewIterator.cpp
@@ -0,0 +1,75 @@
+/******************************************************************************
+* Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its
+*       contributors may be used to endorse or promote products derived
+*       from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <stdio.h>
+#include "io_pdal_PointViewIterator.h"
+#include "JavaPipeline.hpp"
+#include "JavaIterator.hpp"
+#include "PointViewRawPtr.hpp"
+#include "Accessors.hpp"
+
+using libpdaljava::PointViewIterator;
+using libpdaljava::PointViewRawPtr;
+
+JNIEXPORT jboolean JNICALL Java_io_pdal_PointViewIterator_hasNext
+  (JNIEnv *env, jobject obj)
+{
+    PointViewIterator *it = getHandle<PointViewIterator>(env, obj);
+    return it->hasNext();
+}
+
+JNIEXPORT jobject JNICALL Java_io_pdal_PointViewIterator_next
+  (JNIEnv *env, jobject obj)
+{
+    PointViewIterator *it = getHandle<PointViewIterator>(env, obj);
+
+    PointViewPtr pvptr = it->next();
+
+    jclass jpvClass = env->FindClass("io/pdal/PointView");
+    jmethodID jpvCtor = env->GetMethodID(jpvClass, "<init>", "()V");
+    jobject jpv = env->NewObject(jpvClass, jpvCtor);
+
+    PointViewRawPtr *pvrp = new PointViewRawPtr(pvptr);
+
+    setHandle(env, jpv, pvrp);
+
+    return jpv;
+}
+
+JNIEXPORT void JNICALL Java_io_pdal_PointViewIterator_dispose
+  (JNIEnv *env, jobject obj)
+{
+    PointViewIterator *it = getHandle<PointViewIterator>(env, obj);
+    setHandle<int>(env, obj, 0);
+    delete it;
+}
diff --git a/java/project/Environment.scala b/java/project/Environment.scala
new file mode 100644
index 0000000..5926822
--- /dev/null
+++ b/java/project/Environment.scala
@@ -0,0 +1,45 @@
+/******************************************************************************
+  * Copyright (c) 2016, hobu Inc.  (info at hobu.co)
+  *
+  * All rights reserved.
+  *
+  * Redistribution and use in source and binary forms, with or without
+  * modification, are permitted provided that the following
+  * conditions are met:
+  *
+  *     * Redistributions of source code must retain the above copyright
+  *       notice, this list of conditions and the following disclaimer.
+  *     * Redistributions in binary form must reproduce the above copyright
+  *       notice, this list of conditions and the following disclaimer in
+  *       the documentation and/or other materials provided
+  *       with the distribution.
+  *     * Neither the name of Hobu, Inc. nor the names of its
+  *       contributors may be used to endorse or promote products derived
+  *       from this software without specific prior written permission.
+  *
+  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+  * OF SUCH DAMAGE.
+  ****************************************************************************/
+
+import sbt.ClasspathDependency
+
+import scala.util.Properties
+
+object Environment {
+  def either(environmentVariable: String, default: String): String =
+    Properties.envOrElse(environmentVariable, default)
+
+  lazy val versionSuffix = either("PDAL_VERSION_SUFFIX", "-SNAPSHOT")
+  lazy val pdalDependOnNative = either("PDAL_DEPEND_ON_NATIVE", "true")
+  def dependOnNative(native: ClasspathDependency) = if(pdalDependOnNative == "true") Seq(native) else Seq.empty
+}
\ No newline at end of file
diff --git a/java/project/build.properties b/java/project/build.properties
new file mode 100644
index 0000000..24be09b
--- /dev/null
+++ b/java/project/build.properties
@@ -0,0 +1,2 @@
+sbt.version=0.13.13
+
diff --git a/java/project/plugins.sbt b/java/project/plugins.sbt
new file mode 100644
index 0000000..37e72c0
--- /dev/null
+++ b/java/project/plugins.sbt
@@ -0,0 +1,5 @@
+addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3")
+
+addSbtPlugin("ch.jodersky" % "sbt-jni" % "1.2.4")
+
+addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
diff --git a/java/sbt b/java/sbt
new file mode 100755
index 0000000..bd47e3b
--- /dev/null
+++ b/java/sbt
@@ -0,0 +1,568 @@
+#!/usr/bin/env bash
+#
+# A more capable sbt runner, coincidentally also called sbt.
+# Author: Paul Phillips <paulp at improving.org>
+
+set -o pipefail
+
+declare -r sbt_release_version="0.13.13"
+declare -r sbt_unreleased_version="0.13.13"
+
+declare -r latest_212="2.12.0"
+declare -r latest_211="2.11.8"
+declare -r latest_210="2.10.6"
+declare -r latest_29="2.9.3"
+declare -r latest_28="2.8.2"
+
+declare -r buildProps="project/build.properties"
+
+declare -r sbt_launch_ivy_release_repo="http://repo.typesafe.com/typesafe/ivy-releases"
+declare -r sbt_launch_ivy_snapshot_repo="https://repo.scala-sbt.org/scalasbt/ivy-snapshots"
+declare -r sbt_launch_mvn_release_repo="http://repo.scala-sbt.org/scalasbt/maven-releases"
+declare -r sbt_launch_mvn_snapshot_repo="http://repo.scala-sbt.org/scalasbt/maven-snapshots"
+
+declare -r default_jvm_opts_common="-Xms512m -Xmx1536m -Xss2m"
+declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy"
+
+declare sbt_jar sbt_dir sbt_create sbt_version sbt_script sbt_new
+declare sbt_explicit_version
+declare verbose noshare batch trace_level
+declare sbt_saved_stty debugUs
+
+declare java_cmd="java"
+declare sbt_launch_dir="$HOME/.sbt/launchers"
+declare sbt_launch_repo
+
+# pull -J and -D options to give to java.
+declare -a java_args scalac_args sbt_commands residual_args
+
+# args to jvm/sbt via files or environment variables
+declare -a extra_jvm_opts extra_sbt_opts
+
+echoerr () { echo >&2 "$@"; }
+vlog ()    { [[ -n "$verbose" ]] && echoerr "$@"; }
+die ()     { echo "Aborting: $@" ; exit 1; }
+
+# restore stty settings (echo in particular)
+onSbtRunnerExit() {
+  [[ -n "$sbt_saved_stty" ]] || return
+  vlog ""
+  vlog "restoring stty: $sbt_saved_stty"
+  stty "$sbt_saved_stty"
+  unset sbt_saved_stty
+}
+
+# save stty and trap exit, to ensure echo is re-enabled if we are interrupted.
+trap onSbtRunnerExit EXIT
+sbt_saved_stty="$(stty -g 2>/dev/null)"
+vlog "Saved stty: $sbt_saved_stty"
+
+# this seems to cover the bases on OSX, and someone will
+# have to tell me about the others.
+get_script_path () {
+  local path="$1"
+  [[ -L "$path" ]] || { echo "$path" ; return; }
+
+  local target="$(readlink "$path")"
+  if [[ "${target:0:1}" == "/" ]]; then
+    echo "$target"
+  else
+    echo "${path%/*}/$target"
+  fi
+}
+
+declare -r script_path="$(get_script_path "$BASH_SOURCE")"
+declare -r script_name="${script_path##*/}"
+
+init_default_option_file () {
+  local overriding_var="${!1}"
+  local default_file="$2"
+  if [[ ! -r "$default_file" && "$overriding_var" =~ ^@(.*)$ ]]; then
+    local envvar_file="${BASH_REMATCH[1]}"
+    if [[ -r "$envvar_file" ]]; then
+      default_file="$envvar_file"
+    fi
+  fi
+  echo "$default_file"
+}
+
+declare sbt_opts_file="$(init_default_option_file SBT_OPTS .sbtopts)"
+declare jvm_opts_file="$(init_default_option_file JVM_OPTS .jvmopts)"
+
+build_props_sbt () {
+  [[ -r "$buildProps" ]] && \
+    grep '^sbt\.version' "$buildProps" | tr '=\r' ' ' | awk '{ print $2; }'
+}
+
+update_build_props_sbt () {
+  local ver="$1"
+  local old="$(build_props_sbt)"
+
+  [[ -r "$buildProps" ]] && [[ "$ver" != "$old" ]] && {
+    perl -pi -e "s/^sbt\.version\b.*\$/sbt.version=${ver}/" "$buildProps"
+    grep -q '^sbt.version[ =]' "$buildProps" || printf "\nsbt.version=%s\n" "$ver" >> "$buildProps"
+
+    vlog "!!!"
+    vlog "!!! Updated file $buildProps setting sbt.version to: $ver"
+    vlog "!!! Previous value was: $old"
+    vlog "!!!"
+  }
+}
+
+set_sbt_version () {
+  sbt_version="${sbt_explicit_version:-$(build_props_sbt)}"
+  [[ -n "$sbt_version" ]] || sbt_version=$sbt_release_version
+  export sbt_version
+}
+
+url_base () {
+  local version="$1"
+
+  case "$version" in
+        0.7.*) echo "http://simple-build-tool.googlecode.com" ;;
+      0.10.* ) echo "$sbt_launch_ivy_release_repo" ;;
+    0.11.[12]) echo "$sbt_launch_ivy_release_repo" ;;
+    0.*-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) # ie "*-yyyymmdd-hhMMss"
+               echo "$sbt_launch_ivy_snapshot_repo" ;;
+          0.*) echo "$sbt_launch_ivy_release_repo" ;;
+    *-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) # ie "*-yyyymmdd-hhMMss"
+               echo "$sbt_launch_mvn_snapshot_repo" ;;
+            *) echo "$sbt_launch_mvn_release_repo" ;;
+  esac
+}
+
+make_url () {
+  local version="$1"
+
+  local base="${sbt_launch_repo:-$(url_base "$version")}"
+
+  case "$version" in
+        0.7.*) echo "$base/files/sbt-launch-0.7.7.jar" ;;
+      0.10.* ) echo "$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;;
+    0.11.[12]) echo "$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;;
+          0.*) echo "$base/org.scala-sbt/sbt-launch/$version/sbt-launch.jar" ;;
+            *) echo "$base/org/scala-sbt/sbt-launch/$version/sbt-launch.jar" ;;
+  esac
+}
+
+addJava ()     { vlog "[addJava] arg = '$1'"   ;     java_args+=("$1"); }
+addSbt ()      { vlog "[addSbt] arg = '$1'"    ;  sbt_commands+=("$1"); }
+addScalac ()   { vlog "[addScalac] arg = '$1'" ;   scalac_args+=("$1"); }
+addResidual () { vlog "[residual] arg = '$1'"  ; residual_args+=("$1"); }
+
+addResolver () { addSbt "set resolvers += $1"; }
+addDebugger () { addJava "-Xdebug" ; addJava "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1"; }
+setThisBuild () {
+  vlog "[addBuild] args = '$@'"
+  local key="$1" && shift
+  addSbt "set $key in ThisBuild := $@"
+}
+setScalaVersion () {
+  [[ "$1" == *"-SNAPSHOT" ]] && addResolver 'Resolver.sonatypeRepo("snapshots")'
+  addSbt "++ $1"
+}
+setJavaHome () {
+  java_cmd="$1/bin/java"
+  setThisBuild javaHome "_root_.scala.Some(file(\"$1\"))"
+  export JAVA_HOME="$1"
+  export JDK_HOME="$1"
+  export PATH="$JAVA_HOME/bin:$PATH"
+}
+
+getJavaVersion() { "$1" -version 2>&1 | grep -E -e '(java|openjdk) version' | awk '{ print $3 }' | tr -d \"; }
+
+checkJava() {
+  # Warn if there is a Java version mismatch between PATH and JAVA_HOME/JDK_HOME
+
+  [[ -n "$JAVA_HOME" && -e "$JAVA_HOME/bin/java"     ]] && java="$JAVA_HOME/bin/java"
+  [[ -n "$JDK_HOME"  && -e "$JDK_HOME/lib/tools.jar" ]] && java="$JDK_HOME/bin/java"
+
+  if [[ -n "$java" ]]; then
+    pathJavaVersion=$(getJavaVersion java)
+    homeJavaVersion=$(getJavaVersion "$java")
+    if [[ "$pathJavaVersion" != "$homeJavaVersion" ]]; then
+      echoerr "Warning: Java version mismatch between PATH and JAVA_HOME/JDK_HOME, sbt will use the one in PATH"
+      echoerr "  Either: fix your PATH, remove JAVA_HOME/JDK_HOME or use -java-home"
+      echoerr "  java version from PATH:               $pathJavaVersion"
+      echoerr "  java version from JAVA_HOME/JDK_HOME: $homeJavaVersion"
+    fi
+  fi
+}
+
+java_version () {
+  local version=$(getJavaVersion "$java_cmd")
+  vlog "Detected Java version: $version"
+  echo "${version:2:1}"
+}
+
+# MaxPermSize critical on pre-8 JVMs but incurs noisy warning on 8+
+default_jvm_opts () {
+  local v="$(java_version)"
+  if [[ $v -ge 8 ]]; then
+    echo "$default_jvm_opts_common"
+  else
+    echo "-XX:MaxPermSize=384m $default_jvm_opts_common"
+  fi
+}
+
+build_props_scala () {
+  if [[ -r "$buildProps" ]]; then
+    versionLine="$(grep '^build.scala.versions' "$buildProps")"
+    versionString="${versionLine##build.scala.versions=}"
+    echo "${versionString%% .*}"
+  fi
+}
+
+execRunner () {
+  # print the arguments one to a line, quoting any containing spaces
+  vlog "# Executing command line:" && {
+    for arg; do
+      if [[ -n "$arg" ]]; then
+        if printf "%s\n" "$arg" | grep -q ' '; then
+          printf >&2 "\"%s\"\n" "$arg"
+        else
+          printf >&2 "%s\n" "$arg"
+        fi
+      fi
+    done
+    vlog ""
+  }
+
+  [[ -n "$batch" ]] && exec </dev/null
+  exec "$@"
+}
+
+jar_url ()  { make_url "$1"; }
+
+is_cygwin () [[ "$(uname -a)" == "CYGWIN"* ]]
+
+jar_file () {
+  is_cygwin \
+  && echo "$(cygpath -w $sbt_launch_dir/"$1"/sbt-launch.jar)" \
+  || echo "$sbt_launch_dir/$1/sbt-launch.jar"
+}
+
+download_url () {
+  local url="$1"
+  local jar="$2"
+
+  echoerr "Downloading sbt launcher for $sbt_version:"
+  echoerr "  From  $url"
+  echoerr "    To  $jar"
+
+  mkdir -p "${jar%/*}" && {
+    if which curl >/dev/null; then
+      curl --fail --silent --location "$url" --output "$jar"
+    elif which wget >/dev/null; then
+      wget -q -O "$jar" "$url"
+    fi
+  } && [[ -r "$jar" ]]
+}
+
+acquire_sbt_jar () {
+  {
+    sbt_jar="$(jar_file "$sbt_version")"
+    [[ -r "$sbt_jar" ]]
+  } || {
+    sbt_jar="$HOME/.ivy2/local/org.scala-sbt/sbt-launch/$sbt_version/jars/sbt-launch.jar"
+    [[ -r "$sbt_jar" ]]
+  } || {
+    sbt_jar="$(jar_file "$sbt_version")"
+    download_url "$(make_url "$sbt_version")" "$sbt_jar"
+  }
+}
+
+usage () {
+  set_sbt_version
+  cat <<EOM
+Usage: $script_name [options]
+
+Note that options which are passed along to sbt begin with -- whereas
+options to this runner use a single dash. Any sbt command can be scheduled
+to run first by prefixing the command with --, so --warn, --error and so on
+are not special.
+
+Output filtering: if there is a file in the home directory called .sbtignore
+and this is not an interactive sbt session, the file is treated as a list of
+bash regular expressions. Output lines which match any regex are not echoed.
+One can see exactly which lines would have been suppressed by starting this
+runner with the -x option.
+
+  -h | -help         print this message
+  -v                 verbose operation (this runner is chattier)
+  -d, -w, -q         aliases for --debug, --warn, --error (q means quiet)
+  -x                 debug this script
+  -trace <level>     display stack traces with a max of <level> frames (default: -1, traces suppressed)
+  -debug-inc         enable debugging log for the incremental compiler
+  -no-colors         disable ANSI color codes
+  -sbt-create        start sbt even if current directory contains no sbt project
+  -sbt-dir   <path>  path to global settings/plugins directory (default: ~/.sbt/<version>)
+  -sbt-boot  <path>  path to shared boot directory (default: ~/.sbt/boot in 0.11+)
+  -ivy       <path>  path to local Ivy repository (default: ~/.ivy2)
+  -no-share          use all local caches; no sharing
+  -offline           put sbt in offline mode
+  -jvm-debug <port>  Turn on JVM debugging, open at the given port.
+  -batch             Disable interactive mode
+  -prompt <expr>     Set the sbt prompt; in expr, 's' is the State and 'e' is Extracted
+  -script <file>     Run the specified file as a scala script
+
+  # sbt version (default: sbt.version from $buildProps if present, otherwise $sbt_release_version)
+  -sbt-force-latest         force the use of the latest release of sbt: $sbt_release_version
+  -sbt-version  <version>   use the specified version of sbt (default: $sbt_release_version)
+  -sbt-dev                  use the latest pre-release version of sbt: $sbt_unreleased_version
+  -sbt-jar      <path>      use the specified jar as the sbt launcher
+  -sbt-launch-dir <path>    directory to hold sbt launchers (default: $sbt_launch_dir)
+  -sbt-launch-repo <url>    repo url for downloading sbt launcher jar (default: $(url_base "$sbt_version"))
+
+  # scala version (default: as chosen by sbt)
+  -28                       use $latest_28
+  -29                       use $latest_29
+  -210                      use $latest_210
+  -211                      use $latest_211
+  -212                      use $latest_212
+  -scala-home <path>        use the scala build at the specified directory
+  -scala-version <version>  use the specified version of scala
+  -binary-version <version> use the specified scala version when searching for dependencies
+
+  # java version (default: java from PATH, currently $(java -version 2>&1 | grep version))
+  -java-home <path>         alternate JAVA_HOME
+
+  # passing options to the jvm - note it does NOT use JAVA_OPTS due to pollution
+  # The default set is used if JVM_OPTS is unset and no -jvm-opts file is found
+  <default>        $(default_jvm_opts)
+  JVM_OPTS         environment variable holding either the jvm args directly, or
+                   the reference to a file containing jvm args if given path is prepended by '@' (e.g. '@/etc/jvmopts')
+                   Note: "@"-file is overridden by local '.jvmopts' or '-jvm-opts' argument.
+  -jvm-opts <path> file containing jvm args (if not given, .jvmopts in project root is used if present)
+  -Dkey=val        pass -Dkey=val directly to the jvm
+  -J-X             pass option -X directly to the jvm (-J is stripped)
+
+  # passing options to sbt, OR to this runner
+  SBT_OPTS         environment variable holding either the sbt args directly, or
+                   the reference to a file containing sbt args if given path is prepended by '@' (e.g. '@/etc/sbtopts')
+                   Note: "@"-file is overridden by local '.sbtopts' or '-sbt-opts' argument.
+  -sbt-opts <path> file containing sbt args (if not given, .sbtopts in project root is used if present)
+  -S-X             add -X to sbt's scalacOptions (-S is stripped)
+EOM
+}
+
+process_args () {
+  require_arg () {
+    local type="$1"
+    local opt="$2"
+    local arg="$3"
+
+    if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then
+      die "$opt requires <$type> argument"
+    fi
+  }
+  while [[ $# -gt 0 ]]; do
+    case "$1" in
+          -h|-help) usage; exit 1 ;;
+                -v) verbose=true && shift ;;
+                -d) addSbt "--debug" && shift ;;
+                -w) addSbt "--warn"  && shift ;;
+                -q) addSbt "--error" && shift ;;
+                -x) debugUs=true && shift ;;
+            -trace) require_arg integer "$1" "$2" && trace_level="$2" && shift 2 ;;
+              -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;;
+        -no-colors) addJava "-Dsbt.log.noformat=true" && shift ;;
+         -no-share) noshare=true && shift ;;
+         -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;;
+          -sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;;
+        -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;;
+          -offline) addSbt "set offline := true" && shift ;;
+        -jvm-debug) require_arg port "$1" "$2" && addDebugger "$2" && shift 2 ;;
+            -batch) batch=true && shift ;;
+           -prompt) require_arg "expr" "$1" "$2" && setThisBuild shellPrompt "(s => { val e = Project.extract(s) ; $2 })" && shift 2 ;;
+           -script) require_arg file "$1" "$2" && sbt_script="$2" && addJava "-Dsbt.main.class=sbt.ScriptMain" && shift 2 ;;
+
+       -sbt-create) sbt_create=true && shift ;;
+          -sbt-jar) require_arg path "$1" "$2" && sbt_jar="$2" && shift 2 ;;
+      -sbt-version) require_arg version "$1" "$2" && sbt_explicit_version="$2" && shift 2 ;;
+ -sbt-force-latest) sbt_explicit_version="$sbt_release_version" && shift ;;
+          -sbt-dev) sbt_explicit_version="$sbt_unreleased_version" && shift ;;
+   -sbt-launch-dir) require_arg path "$1" "$2" && sbt_launch_dir="$2" && shift 2 ;;
+  -sbt-launch-repo) require_arg path "$1" "$2" && sbt_launch_repo="$2" && shift 2 ;;
+    -scala-version) require_arg version "$1" "$2" && setScalaVersion "$2" && shift 2 ;;
+   -binary-version) require_arg version "$1" "$2" && setThisBuild scalaBinaryVersion "\"$2\"" && shift 2 ;;
+       -scala-home) require_arg path "$1" "$2" && setThisBuild scalaHome "_root_.scala.Some(file(\"$2\"))" && shift 2 ;;
+        -java-home) require_arg path "$1" "$2" && setJavaHome "$2" && shift 2 ;;
+         -sbt-opts) require_arg path "$1" "$2" && sbt_opts_file="$2" && shift 2 ;;
+         -jvm-opts) require_arg path "$1" "$2" && jvm_opts_file="$2" && shift 2 ;;
+
+               -D*) addJava "$1" && shift ;;
+               -J*) addJava "${1:2}" && shift ;;
+               -S*) addScalac "${1:2}" && shift ;;
+               -28) setScalaVersion "$latest_28" && shift ;;
+               -29) setScalaVersion "$latest_29" && shift ;;
+              -210) setScalaVersion "$latest_210" && shift ;;
+              -211) setScalaVersion "$latest_211" && shift ;;
+              -212) setScalaVersion "$latest_212" && shift ;;
+               new) sbt_new=true && sbt_explicit_version="$sbt_release_version"  && addResidual "$1" && shift ;;
+                 *) addResidual "$1" && shift ;;
+    esac
+  done
+}
+
+# process the direct command line arguments
+process_args "$@"
+
+# skip #-styled comments and blank lines
+readConfigFile() {
+  local end=false
+  until $end; do
+    read || end=true
+    [[ $REPLY =~ ^# ]] || [[ -z $REPLY ]] || echo "$REPLY"
+  done < "$1"
+}
+
+# if there are file/environment sbt_opts, process again so we
+# can supply args to this runner
+if [[ -r "$sbt_opts_file" ]]; then
+  vlog "Using sbt options defined in file $sbt_opts_file"
+  while read opt; do extra_sbt_opts+=("$opt"); done < <(readConfigFile "$sbt_opts_file")
+elif [[ -n "$SBT_OPTS" && ! ("$SBT_OPTS" =~ ^@.*) ]]; then
+  vlog "Using sbt options defined in variable \$SBT_OPTS"
+  extra_sbt_opts=( $SBT_OPTS )
+else
+  vlog "No extra sbt options have been defined"
+fi
+
+[[ -n "${extra_sbt_opts[*]}" ]] && process_args "${extra_sbt_opts[@]}"
+
+# reset "$@" to the residual args
+set -- "${residual_args[@]}"
+argumentCount=$#
+
+# set sbt version
+set_sbt_version
+
+checkJava
+
+# only exists in 0.12+
+setTraceLevel() {
+  case "$sbt_version" in
+    "0.7."* | "0.10."* | "0.11."* ) echoerr "Cannot set trace level in sbt version $sbt_version" ;;
+                                 *) setThisBuild traceLevel $trace_level ;;
+  esac
+}
+
+# set scalacOptions if we were given any -S opts
+[[ ${#scalac_args[@]} -eq 0 ]] || addSbt "set scalacOptions in ThisBuild += \"${scalac_args[@]}\""
+
+# Update build.properties on disk to set explicit version - sbt gives us no choice
+[[ -n "$sbt_explicit_version" && -z "$sbt_new" ]] && update_build_props_sbt "$sbt_explicit_version"
+vlog "Detected sbt version $sbt_version"
+
+if [[ -n "$sbt_script" ]]; then
+  residual_args=( $sbt_script ${residual_args[@]} )
+else
+  # no args - alert them there's stuff in here
+  (( argumentCount > 0 )) || {
+    vlog "Starting $script_name: invoke with -help for other options"
+    residual_args=( shell )
+  }
+fi
+
+# verify this is an sbt dir, -create was given or user attempts to run a scala script
+[[ -r ./build.sbt || -d ./project || -n "$sbt_create" || -n "$sbt_script" || -n "$sbt_new" ]] || {
+  cat <<EOM
+$(pwd) doesn't appear to be an sbt project.
+If you want to start sbt anyway, run:
+  $0 -sbt-create
+
+EOM
+  exit 1
+}
+
+# pick up completion if present; todo
+[[ -r .sbt_completion.sh ]] && source .sbt_completion.sh
+
+# directory to store sbt launchers
+[[ -d "$sbt_launch_dir" ]] || mkdir -p "$sbt_launch_dir"
+[[ -w "$sbt_launch_dir" ]] || sbt_launch_dir="$(mktemp -d -t sbt_extras_launchers.XXXXXX)"
+
+# no jar? download it.
+[[ -r "$sbt_jar" ]] || acquire_sbt_jar || {
+  # still no jar? uh-oh.
+  echo "Download failed. Obtain the jar manually and place it at $sbt_jar"
+  exit 1
+}
+
+if [[ -n "$noshare" ]]; then
+  for opt in ${noshare_opts}; do
+    addJava "$opt"
+  done
+else
+  case "$sbt_version" in
+    "0.7."* | "0.10."* | "0.11."* | "0.12."* )
+      [[ -n "$sbt_dir" ]] || {
+        sbt_dir="$HOME/.sbt/$sbt_version"
+        vlog "Using $sbt_dir as sbt dir, -sbt-dir to override."
+      }
+    ;;
+  esac
+
+  if [[ -n "$sbt_dir" ]]; then
+    addJava "-Dsbt.global.base=$sbt_dir"
+  fi
+fi
+
+if [[ -r "$jvm_opts_file" ]]; then
+  vlog "Using jvm options defined in file $jvm_opts_file"
+  while read opt; do extra_jvm_opts+=("$opt"); done < <(readConfigFile "$jvm_opts_file")
+elif [[ -n "$JVM_OPTS" && ! ("$JVM_OPTS" =~ ^@.*) ]]; then
+  vlog "Using jvm options defined in \$JVM_OPTS variable"
+  extra_jvm_opts=( $JVM_OPTS )
+else
+  vlog "Using default jvm options"
+  extra_jvm_opts=( $(default_jvm_opts) )
+fi
+
+# traceLevel is 0.12+
+[[ -n "$trace_level" ]] && setTraceLevel
+
+main () {
+  execRunner "$java_cmd" \
+    "${extra_jvm_opts[@]}" \
+    "${java_args[@]}" \
+    -jar "$sbt_jar" \
+    "${sbt_commands[@]}" \
+    "${residual_args[@]}"
+}
+
+# sbt inserts this string on certain lines when formatting is enabled:
+#   val OverwriteLine = "\r\u001BM\u001B[2K"
+# ...in order not to spam the console with a million "Resolving" lines.
+# Unfortunately that makes it that much harder to work with when
+# we're not going to print those lines anyway. We strip that bit of
+# line noise, but leave the other codes to preserve color.
+mainFiltered () {
+  local ansiOverwrite='\r\x1BM\x1B[2K'
+  local excludeRegex=$(egrep -v '^#|^$' ~/.sbtignore | paste -sd'|' -)
+
+  echoLine () {
+    local line="$1"
+    local line1="$(echo "$line" | sed 's/\r\x1BM\x1B\[2K//g')"       # This strips the OverwriteLine code.
+    local line2="$(echo "$line1" | sed 's/\x1B\[[0-9;]*[JKmsu]//g')" # This strips all codes - we test regexes against this.
+
+    if [[ $line2 =~ $excludeRegex ]]; then
+      [[ -n $debugUs ]] && echo "[X] $line1"
+    else
+      [[ -n $debugUs ]] && echo "    $line1" || echo "$line1"
+    fi
+  }
+
+  echoLine "Starting sbt with output filtering enabled."
+  main | while read -r line; do echoLine "$line"; done
+}
+
+# Only filter if there's a filter file and we don't see a known interactive command.
+# Obviously this is super ad hoc but I don't know how to improve on it. Testing whether
+# stdin is a terminal is useless because most of my use cases for this filtering are
+# exactly when I'm at a terminal, running sbt non-interactively.
+shouldFilter () { [[ -f ~/.sbtignore ]] && ! egrep -q '\b(shell|console|consoleProject)\b' <<<"${residual_args[@]}"; }
+
+# run sbt
+if shouldFilter; then mainFiltered; else main; fi
diff --git a/java/scripts/publish-212.sh b/java/scripts/publish-212.sh
new file mode 100755
index 0000000..dbce4dd
--- /dev/null
+++ b/java/scripts/publish-212.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+PDAL_DEPEND_ON_NATIVE=false ./sbt "-212" "project core" publish
diff --git a/java/scripts/publish-all.sh b/java/scripts/publish-all.sh
new file mode 100755
index 0000000..84edf56
--- /dev/null
+++ b/java/scripts/publish-all.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+./scripts/publish.sh
+./scripts/publish-212.sh
+./scripts/publish-javastyle.sh
diff --git a/java/scripts/publish-javastyle.sh b/java/scripts/publish-javastyle.sh
new file mode 100755
index 0000000..2ccf8b5
--- /dev/null
+++ b/java/scripts/publish-javastyle.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+PDAL_DEPEND_ON_NATIVE=false ./sbt "-212" "project core" publish-javastyle
diff --git a/java/scripts/publish-local.sh b/java/scripts/publish-local.sh
new file mode 100755
index 0000000..fa8f3df
--- /dev/null
+++ b/java/scripts/publish-local.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+PDAL_DEPEND_ON_NATIVE=false ./sbt "project core" publish-local
diff --git a/java/scripts/publish.sh b/java/scripts/publish.sh
new file mode 100755
index 0000000..29b0bc8
--- /dev/null
+++ b/java/scripts/publish.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+PDAL_DEPEND_ON_NATIVE=false ./sbt "project core" publish
diff --git a/kernels/CMakeLists.txt b/kernels/CMakeLists.txt
deleted file mode 100644
index 4c58fe4..0000000
--- a/kernels/CMakeLists.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-
-add_subdirectory(delta)
-add_subdirectory(diff)
-add_subdirectory(info)
-add_subdirectory(merge)
-add_subdirectory(pipeline)
-add_subdirectory(random)
-add_subdirectory(sort)
-add_subdirectory(tindex)
-add_subdirectory(split)
-add_subdirectory(translate)
-
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} PARENT_SCOPE)
diff --git a/kernels/DeltaKernel.cpp b/kernels/DeltaKernel.cpp
new file mode 100644
index 0000000..b369ee8
--- /dev/null
+++ b/kernels/DeltaKernel.cpp
@@ -0,0 +1,212 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "DeltaKernel.hpp"
+
+#include <pdal/PDALUtils.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.delta", "Delta Kernel",
+    "http://www.pdal.io/apps/delta.html");
+
+CREATE_STATIC_PLUGIN(1, 0, DeltaKernel, Kernel, s_info)
+
+std::string DeltaKernel::getName() const { return s_info.name; }
+
+DeltaKernel::DeltaKernel() : m_3d(true), m_detail(false), m_allDims(false)
+{}
+
+
+void DeltaKernel::addSwitches(ProgramArgs& args)
+{
+    Arg& src = args.add("source", "source file name", m_sourceFile);
+    src.setPositional();
+    Arg& candidate = args.add("candidate", "candidate file name",
+        m_candidateFile);
+    candidate.setPositional();
+    Arg& output = args.add("output", "output file name", m_outputFile);
+    output.setPositional();
+    args.add("2d", "only 2D comparisons/indexing", m_3d, true);
+    args.add("detail", "Output deltas per-point", m_detail);
+    args.add("alldims", "Compute diffs for all dimensions (not just X,Y,Z)",
+        m_allDims);
+}
+
+
+PointViewPtr DeltaKernel::loadSet(const std::string& filename,
+    PointTable& table)
+{
+    Stage& reader = makeReader(filename, m_driverOverride);
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    assert(viewSet.size() == 1);
+    return *viewSet.begin();
+}
+
+
+int DeltaKernel::execute()
+{
+    PointTable srcTable;
+    PointTable candTable;
+    DimIndexMap dims;
+
+    PointViewPtr srcView = loadSet(m_sourceFile, srcTable);
+    PointViewPtr candView = loadSet(m_candidateFile, candTable);
+
+    PointLayoutPtr srcLayout = srcTable.layout();
+    PointLayoutPtr candLayout = candTable.layout();
+
+    Dimension::IdList ids = srcLayout->dims();
+    for (Dimension::Id dim : ids)
+    {
+        std::string name = srcLayout->dimName(dim);
+        if (!m_allDims)
+            if (name != "X" && name != "Y" && name != "Z")
+                continue;
+        DimIndex d;
+        d.m_name = name;
+        d.m_srcId = dim;
+        dims[name] = d;
+    }
+    ids = candLayout->dims();
+    for (Dimension::Id dim : ids)
+    {
+        std::string name = candLayout->dimName(dim);
+        auto di = dims.find(name);
+        if (di == dims.end())
+            continue;
+        DimIndex& d = di->second;
+        d.m_candId = dim;
+    }
+
+    // Remove dimensions that aren't in both the source and candidate lists.
+    for (auto di = dims.begin(); di != dims.end();)
+    {
+        DimIndex& d = di->second;
+        if (d.m_candId == Dimension::Id::Unknown)
+            dims.erase(di++);
+        else
+            ++di;
+    }
+
+    // Index the candidate data.
+    KD3Index index(*candView);
+    index.build();
+
+    MetadataNode root;
+
+    if (m_detail)
+        root = dumpDetail(srcView, candView, index, dims);
+    else
+        root = dump(srcView, candView, index, dims);
+    Utils::toJSON(root, std::cout);
+
+    return 0;
+}
+
+
+MetadataNode DeltaKernel::dump(PointViewPtr& srcView, PointViewPtr& candView,
+    KD3Index& index, DimIndexMap& dims)
+{
+    MetadataNode root;
+
+    for (PointId id = 0; id < srcView->size(); ++id)
+    {
+        PointRef point = srcView->point(id);
+        PointId candId = index.neighbor(point);
+
+        // It may be faster to put in a special case to avoid having to
+        // fetch X, Y and Z, more than once but this is simpler and
+        // I'm thinking in most cases it will make no practical difference.
+        for (auto di = dims.begin(); di != dims.end(); ++di)
+        {
+            DimIndex& d = di->second;
+            double sv = srcView->getFieldAs<double>(d.m_srcId, id);
+            double cv = candView->getFieldAs<double>(d.m_candId, candId);
+            accumulate(d, sv - cv);
+        }
+    }
+
+    root.add("source", m_sourceFile);
+    root.add("candidate", m_candidateFile);
+    for (auto dpair : dims)
+    {
+        DimIndex& d = dpair.second;
+
+        MetadataNode dimNode = root.add(d.m_name);
+        dimNode.add("min", d.m_min);
+        dimNode.add("max", d.m_max);
+        dimNode.add("mean", d.m_avg);
+    }
+    return root;
+}
+
+
+void DeltaKernel::accumulate(DimIndex& d, double v)
+{
+    d.m_cnt++;
+    d.m_min = std::min(v, d.m_min);
+    d.m_max = std::max(v, d.m_max);
+    d.m_avg += (v - d.m_avg) / d.m_cnt;
+}
+
+
+MetadataNode DeltaKernel::dumpDetail(PointViewPtr& srcView,
+    PointViewPtr& candView, KD3Index& index, DimIndexMap& dims)
+{
+    MetadataNode root;
+
+    for (PointId id = 0; id < srcView->size(); ++id)
+    {
+        PointRef point = srcView->point(id);
+        PointId candId = index.neighbor(point);
+
+        MetadataNode delta = root.add("delta");
+        delta.add("i", id);
+        for (auto di = dims.begin(); di != dims.end(); ++di)
+        {
+            DimIndex& d = di->second;
+            double sv = srcView->getFieldAs<double>(d.m_srcId, id);
+            double cv = candView->getFieldAs<double>(d.m_candId, candId);
+
+            delta.add(d.m_name, sv - cv);
+        }
+    }
+    return root;
+}
+
+} // pdal
diff --git a/kernels/delta/DeltaKernel.hpp b/kernels/DeltaKernel.hpp
similarity index 100%
rename from kernels/delta/DeltaKernel.hpp
rename to kernels/DeltaKernel.hpp
diff --git a/kernels/DiffKernel.cpp b/kernels/DiffKernel.cpp
new file mode 100644
index 0000000..711564f
--- /dev/null
+++ b/kernels/DiffKernel.cpp
@@ -0,0 +1,158 @@
+/******************************************************************************
+* Copyright (c) 2014, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "DiffKernel.hpp"
+
+#include <memory>
+
+#include <pdal/PDALUtils.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/pdal_macros.hpp>
+
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.diff", "Diff Kernel",
+    "http://www.pdal.io/apps/diff.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, DiffKernel, Kernel, s_info)
+
+std::string DiffKernel::getName() const { return s_info.name; }
+
+void DiffKernel::addSwitches(ProgramArgs& args)
+{
+    Arg& source = args.add("source", "Source filename", m_sourceFile);
+    source.setPositional();
+    Arg& candidate = args.add("candidate", "Candidate filename",
+        m_candidateFile);
+    candidate.setPositional();
+}
+
+
+void DiffKernel::checkPoints(const PointView& source_data,
+    const PointView& candidate_data, MetadataNode errors)
+{
+    uint32_t MAX_BADBYTES(20);
+    uint32_t badbytes(0);
+
+    // Both schemas have already been determined to be equal, so are the
+    // same size and in the same order.
+    Dimension::IdList const& sourceDims = source_data.dims();
+    Dimension::IdList const& candidateDims = candidate_data.dims();
+
+    char sbuf[8];
+    char cbuf[8];
+    for (PointId idx = 0; idx < source_data.size(); ++idx)
+    {
+        for (size_t d = 0; d < sourceDims.size(); ++d)
+        {
+            Dimension::Id sd = sourceDims[d];
+            Dimension::Id cd = candidateDims[d];
+
+            source_data.getRawField(sd, idx, (void *)sbuf);
+            candidate_data.getRawField(cd, idx, (void *)cbuf);
+            Dimension::Type t = Dimension::defaultType(cd);
+            size_t size = Dimension::size(t);
+            if (memcmp(sbuf, cbuf, size))
+            {
+                std::ostringstream oss;
+
+                oss << "Point " << idx << " differs for dimension \"" <<
+                    Dimension::name(sd) << "\" for source and candidate";
+                errors.add("data.error", oss.str());
+                badbytes++;
+            }
+        }
+        if (badbytes > MAX_BADBYTES )
+            break;
+    }
+}
+
+
+int DiffKernel::execute()
+{
+    PointTable sourceTable;
+
+    Stage& source = makeReader(m_sourceFile, m_driverOverride);
+    source.prepare(sourceTable);
+    PointViewSet sourceSet = source.execute(sourceTable);
+
+    MetadataNode errors;
+
+    PointTable candidateTable;
+
+    Stage& candidate = makeReader(m_candidateFile, m_driverOverride);
+    candidate.prepare(candidateTable);
+    PointViewSet candidateSet = candidate.execute(candidateTable);
+
+    assert(sourceSet.size() == 1);
+    assert(candidateSet.size() == 1);
+    PointViewPtr sourceView = *sourceSet.begin();
+    PointViewPtr candidateView = *candidateSet.begin();
+    if (candidateView->size() != sourceView->size())
+    {
+        std::ostringstream oss;
+
+        oss << "Source and candidate files do not have the same point count";
+        errors.add("count.error", oss.str());
+        errors.add("count.candidate", candidateView->size());
+        errors.add("count.source", sourceView->size());
+    }
+
+    MetadataNode source_metadata = sourceTable.metadata();
+    MetadataNode candidate_metadata = candidateTable.metadata();
+    if (source_metadata != candidate_metadata)
+    {
+        std::ostringstream oss;
+
+        oss << "Source and candidate files do not have the same metadata count";
+        errors.add("metadata.error", oss.str());
+        errors.add(source_metadata);
+        errors.add(candidate_metadata);
+    }
+
+    if (candidateTable.layout()->dims().size() !=
+        sourceTable.layout()->dims().size())
+    {
+        std::ostringstream oss;
+
+        oss << "Source and candidate files do not have the same "
+            "number of dimensions";
+    }
+
+    return 0;
+}
+
+} // namespace pdal
diff --git a/kernels/diff/DiffKernel.hpp b/kernels/DiffKernel.hpp
similarity index 100%
rename from kernels/diff/DiffKernel.hpp
rename to kernels/DiffKernel.hpp
diff --git a/kernels/GroundKernel.cpp b/kernels/GroundKernel.cpp
new file mode 100644
index 0000000..453051c
--- /dev/null
+++ b/kernels/GroundKernel.cpp
@@ -0,0 +1,123 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+* Copyright (c) 2014-2015, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "GroundKernel.hpp"
+
+#include <pdal/KernelFactory.hpp>
+#include <pdal/Options.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/PointTable.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/Stage.hpp>
+#include <pdal/StageFactory.hpp>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.ground", "Ground Kernel",
+    "http://pdal.io/apps/ground.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, GroundKernel, Kernel, s_info)
+
+std::string GroundKernel::getName() const { return s_info.name; }
+
+GroundKernel::GroundKernel()
+    : Kernel()
+    , m_inputFile("")
+    , m_outputFile("")
+    , m_maxWindowSize(33)
+    , m_slope(1)
+    , m_maxDistance(2.5)
+    , m_initialDistance(0.15)
+    , m_cellSize(1)
+    , m_classify(true)
+    , m_extract(false)
+    , m_approximate(false)
+{}
+
+void GroundKernel::addSwitches(ProgramArgs& args)
+{
+    args.add("input,i", "Input filename", m_inputFile).setPositional();
+    args.add("output,o", "Output filename", m_outputFile).setPositional();
+    args.add("max_window_size", "Max window size", m_maxWindowSize, 33.0);
+    args.add("slope", "Slope", m_slope, 1.0);
+    args.add("max_distance", "Max distance", m_maxDistance, 2.5);
+    args.add("initial_distance", "Initial distance", m_initialDistance, .15);
+    args.add("cell_size", "Cell size", m_cellSize, 1.0);
+    args.add("classify", "Apply classification labels?", m_classify);
+    args.add("extract", "extract ground returns?", m_extract);
+    args.add("approximate,a", "use approximate algorithm? (much faster)",
+        m_approximate);
+}
+
+int GroundKernel::execute()
+{
+    PointTable table;
+
+    Stage& readerStage(makeReader(m_inputFile, ""));
+
+    Options groundOptions;
+    groundOptions.add("max_window_size", m_maxWindowSize);
+    groundOptions.add("slope", m_slope);
+    groundOptions.add("max_distance", m_maxDistance);
+    groundOptions.add("initial_distance", m_initialDistance);
+    groundOptions.add("cell_size", m_cellSize);
+    groundOptions.add("classify", m_classify);
+    groundOptions.add("extract", m_extract);
+    groundOptions.add("approximate", m_approximate);
+
+    Stage& groundStage = makeFilter("filters.pmf", readerStage,
+        groundOptions);
+
+    // setup the Writer and write the results
+    Stage& writer(makeWriter(m_outputFile, groundStage, ""));
+
+    writer.prepare(table);
+
+    // process the data, grabbing the PointViewSet for visualization of the
+    // resulting PointView
+    PointViewSet viewSetOut = writer.execute(table);
+
+    if (isVisualize())
+        visualize(*viewSetOut.begin());
+
+    return 0;
+}
+
+} // namespace pdal
diff --git a/kernels/GroundKernel.hpp b/kernels/GroundKernel.hpp
new file mode 100644
index 0000000..2c10e9f
--- /dev/null
+++ b/kernels/GroundKernel.hpp
@@ -0,0 +1,79 @@
+/******************************************************************************
+* Copyright (c) 2013, Howard Butler (hobu.inc at gmail.com)
+* Copyright (c) 2014-2015, Brad Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Kernel.hpp>
+#include <pdal/pdal_export.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/plugin.hpp>
+
+#include <memory>
+#include <string>
+
+extern "C" int32_t GroundKernel_ExitFunc();
+extern "C" PF_ExitFunc GroundKernel_InitPlugin();
+
+namespace pdal
+{
+
+class Options;
+class Stage;
+
+class PDAL_DLL GroundKernel : public Kernel
+{
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+    int execute();
+
+private:
+    GroundKernel();
+    virtual void addSwitches(ProgramArgs& args);
+
+    std::string m_inputFile;
+    std::string m_outputFile;
+    double m_maxWindowSize;
+    double m_slope;
+    double m_maxDistance;
+    double m_initialDistance;
+    double m_cellSize;
+    bool m_classify;
+    bool m_extract;
+    bool m_approximate;
+};
+
+} // namespace pdal
diff --git a/kernels/HausdorffKernel.cpp b/kernels/HausdorffKernel.cpp
new file mode 100644
index 0000000..bf0a23b
--- /dev/null
+++ b/kernels/HausdorffKernel.cpp
@@ -0,0 +1,99 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "HausdorffKernel.hpp"
+
+#include <memory>
+
+#include <pdal/PDALUtils.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/pdal_config.hpp>
+#include <pdal/pdal_macros.hpp>
+
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.hausdorff",
+    "Hausdorff Kernel", "http://pdal.io/apps/hausdorff.html");
+
+CREATE_STATIC_PLUGIN(1, 0, HausdorffKernel, Kernel, s_info)
+
+std::string HausdorffKernel::getName() const
+{
+    return s_info.name;
+}
+
+void HausdorffKernel::addSwitches(ProgramArgs& args)
+{
+    Arg& source = args.add("source", "Source filename", m_sourceFile);
+    source.setPositional();
+    Arg& candidate = args.add("candidate", "Candidate filename",
+                              m_candidateFile);
+    candidate.setPositional();
+}
+
+
+PointViewPtr HausdorffKernel::loadSet(const std::string& filename,
+                                      PointTable& table)
+{
+    Stage& reader = makeReader(filename, "");
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    assert(viewSet.size() == 1);
+    return *viewSet.begin();
+}
+
+
+int HausdorffKernel::execute()
+{
+    PointTable srcTable;
+    PointViewPtr srcView = loadSet(m_sourceFile, srcTable);
+
+    PointTable candTable;
+    PointViewPtr candView = loadSet(m_candidateFile, candTable);
+
+    double hausdorff = Utils::computeHausdorff(srcView, candView);
+
+    MetadataNode root;
+    root.add("filenames", m_sourceFile);
+    root.add("filenames", m_candidateFile);
+    root.add("hausdorff", hausdorff);
+    root.add("pdal_version", pdal::GetFullVersionString());
+    Utils::toJSON(root, std::cout);
+
+    return 0;
+}
+
+} // namespace pdal
diff --git a/kernels/HausdorffKernel.hpp b/kernels/HausdorffKernel.hpp
new file mode 100644
index 0000000..28cb8a9
--- /dev/null
+++ b/kernels/HausdorffKernel.hpp
@@ -0,0 +1,67 @@
+/******************************************************************************
+* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Kernel.hpp>
+#include <pdal/Stage.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/plugin.hpp>
+
+extern "C" int32_t HausdorffKernel_ExitFunc();
+extern "C" PF_ExitFunc HausdorffKernel_InitPlugin();
+
+namespace pdal
+{
+
+class PointView;
+
+class PDAL_DLL HausdorffKernel : public Kernel
+{
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+    int execute(); // overrride
+
+
+private:
+    virtual void addSwitches(ProgramArgs& args);
+    PointViewPtr loadSet(const std::string& filename, PointTable& table);
+
+    std::string m_sourceFile;
+    std::string m_candidateFile;
+};
+
+} // namespace pdal
diff --git a/kernels/InfoKernel.cpp b/kernels/InfoKernel.cpp
new file mode 100644
index 0000000..fafcc75
--- /dev/null
+++ b/kernels/InfoKernel.cpp
@@ -0,0 +1,462 @@
+/******************************************************************************
+* Copyright (c) 2013, Howard Butler (hobu.inc at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "InfoKernel.hpp"
+
+#include <algorithm>
+
+#include <pdal/KDIndex.hpp>
+#include <pdal/PipelineWriter.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/pdal_config.hpp>
+#include <pdal/StageFactory.hpp>
+#ifdef PDAL_HAVE_LIBXML2
+#include <pdal/XMLSchema.hpp>
+#endif
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.info", "Info Kernel",
+    "http://pdal.io/apps/info.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, InfoKernel, Kernel, s_info)
+
+std::string InfoKernel::getName() const { return s_info.name; }
+
+InfoKernel::InfoKernel()
+    : m_showStats(false)
+    , m_showSchema(false)
+    , m_showAll(false)
+    , m_showMetadata(false)
+    , m_boundary(false)
+    , m_showSummary(false)
+    , m_needPoints(false)
+    , m_statsStage(NULL)
+{}
+
+
+void InfoKernel::validateSwitches(ProgramArgs& args)
+{
+    int functions = 0;
+
+    if (!m_usestdin && m_inputFile.empty())
+        throw pdal_error("No input file specified.");
+
+    // All isn't really all.
+    if (m_showAll)
+    {
+        m_showStats = true;
+        m_showMetadata = true;
+        m_showSchema = true;
+    }
+
+    if (m_boundary)
+    {
+        functions++;
+        m_needPoints = true;
+    }
+    if (m_queryPoint.size())
+    {
+        functions++;
+        m_needPoints = true;
+    }
+    if (m_pointIndexes.size())
+    {
+        functions++;
+        m_needPoints = true;
+    }
+    if (m_showSchema)
+        functions++;
+    if (m_showMetadata)
+        functions++;
+    if (m_showSummary)
+        functions++;
+    if (m_showStats || functions == 0 )
+    {
+        functions++;
+        m_showStats = true;
+        m_needPoints = true;
+    }
+
+    if (m_pointIndexes.size() && m_queryPoint.size())
+        throw pdal_error("--point option incompatible with --query option.");
+
+    if (m_showSummary && functions > 1)
+        throw pdal_error("--summary option incompatible with other "
+            "specified options.");
+}
+
+
+void InfoKernel::addSwitches(ProgramArgs& args)
+{
+    args.add("input,i", "input file name", m_inputFile).setOptionalPositional();
+    args.add("all", "dump statistics, schema and metadata", m_showAll);
+    args.add("point,p", "point to dump\n--point=\"1-5,10,100-200\"",
+        m_pointIndexes);
+    args.add("query",
+         "Return points in order of distance from the specified "
+         "location (2D or 3D)\n"
+         "--query Xcoord,Ycoord[,Zcoord][/count]",
+         m_queryPoint);
+    args.add("stats", "dump stats on all points (reads entire dataset)",
+        m_showStats);
+    args.add("boundary", "compute a hexagonal hull/boundary of dataset",
+        m_boundary);
+    args.add("dimensions", "dimensions on which to compute statistics",
+        m_dimensions);
+    args.add("schema", "dump the schema", m_showSchema);
+    args.add("pipeline-serialization", "Output file for pipeline serialization",
+         m_pipelineFile);
+    args.add("summary", "dump summary of the info", m_showSummary);
+    args.add("metadata", "dump file metadata info", m_showMetadata);
+    args.add("pointcloudschema", "dump PointCloudSchema XML output",
+        m_PointCloudSchemaOutput).setHidden();
+    args.add("stdin,s", "Read a pipeline file from standard input", m_usestdin);
+}
+
+// Support for parsing point numbers.  Points can be specified singly or as
+// dash-separated ranges.  i.e. 6-7,8,19-20
+namespace {
+
+using namespace std;
+
+uint32_t parseInt(const string& s)
+{
+    uint32_t i;
+
+    if (!Utils::fromString(s, i))
+        throw pdal_error(string("Invalid integer: ") + s);
+    return i;
+}
+
+
+void addRange(const string& begin, const string& end, vector<PointId>& points)
+{
+    PointId low = parseInt(begin);
+    PointId high = parseInt(end);
+    if (low > high)
+        throw pdal_error(string("Range invalid: ") + begin + "-" + end);
+    while (low <= high)
+        points.push_back(low++);
+}
+
+
+vector<PointId> getListOfPoints(std::string p)
+{
+    vector<PointId> output;
+
+    //Remove whitespace from string with awful remove/erase idiom.
+    p.erase(remove_if(p.begin(), p.end(), ::isspace), p.end());
+
+    vector<string> ranges = Utils::split2(p, ',');
+    for (string s : ranges)
+    {
+        vector<string> limits = Utils::split(s, '-');
+        if (limits.size() == 1)
+            output.push_back(parseInt(limits[0]));
+        else if (limits.size() == 2)
+            addRange(limits[0], limits[1], output);
+        else
+            throw pdal_error(string("Invalid point range: ") + s);
+    }
+    return output;
+}
+
+} //namespace
+
+MetadataNode InfoKernel::dumpPoints(PointViewPtr inView) const
+{
+    MetadataNode root;
+    PointViewPtr outView = inView->makeNew();
+
+    // Stick points in a inViewfer.
+    std::vector<PointId> points = getListOfPoints(m_pointIndexes);
+    for (size_t i = 0; i < points.size(); ++i)
+    {
+        PointId id = (PointId)points[i];
+        if (id < inView->size())
+            outView->appendPoint(*inView.get(), id);
+    }
+
+    MetadataNode tree = outView->toMetadata();
+    std::string prefix("point ");
+    for (size_t i = 0; i < outView->size(); ++i)
+    {
+        MetadataNode n = tree.findChild(std::to_string(i));
+        n.add("PointId", points[i]);
+        root.add(n.clone("point"));
+    }
+    return root;
+}
+
+
+MetadataNode InfoKernel::dumpSummary(const QuickInfo& qi)
+{
+    MetadataNode summary;
+    summary.add("num_points", qi.m_pointCount);
+    summary.add("spatial_reference", qi.m_srs.getWKT());
+    MetadataNode srs = qi.m_srs.toMetadata();
+    summary.add(srs);
+    MetadataNode bounds = summary.add("bounds");
+    MetadataNode x = bounds.add("X");
+    x.add("min", qi.m_bounds.minx);
+    x.add("max", qi.m_bounds.maxx);
+    MetadataNode y = bounds.add("Y");
+    y.add("min", qi.m_bounds.miny);
+    y.add("max", qi.m_bounds.maxy);
+    MetadataNode z = bounds.add("Z");
+    z.add("min", qi.m_bounds.minz);
+    z.add("max", qi.m_bounds.maxz);
+
+    std::string dims;
+    auto di = qi.m_dimNames.begin();
+    while (di != qi.m_dimNames.end())
+    {
+        dims += *di;
+        ++di;
+        if (di != qi.m_dimNames.end())
+           dims += ", ";
+    }
+    summary.add("dimensions", dims);
+    return summary;
+}
+
+
+void InfoKernel::makePipeline(const std::string& filename, bool noPoints)
+{
+    if (!pdal::Utils::fileExists(filename))
+        throw pdal_error("File not found: " + filename);
+
+    if (filename == "STDIN")
+    {
+        m_manager.readPipeline(std::cin);
+        m_reader = m_manager.getStage();
+    }
+    else if (FileUtils::extension(filename) == ".xml" ||
+        FileUtils::extension(filename) == ".json")
+    {
+        m_manager.readPipeline(filename);
+        m_reader = m_manager.getStage();
+    }
+    else
+    {
+        Options ops;
+        if (noPoints)
+            ops.add("count", 0);
+        Stage& reader = m_manager.makeReader(filename, m_driverOverride, ops);
+        m_reader = &reader;
+    }
+    if (!m_reader)
+        throw pdal_error("Pipeline contains no valid stages.");
+}
+
+
+void InfoKernel::setup(const std::string& filename)
+{
+    makePipeline(filename, !m_needPoints);
+
+    Stage *stage = m_reader;
+    if (m_showStats)
+    {
+        Options filterOptions;
+        if (m_dimensions.size())
+            filterOptions.add({"dimensions", m_dimensions});
+        m_statsStage = &m_manager.makeFilter("filters.stats", *stage,
+            filterOptions);
+        stage = m_statsStage;
+    }
+    if (m_boundary)
+        m_hexbinStage = &m_manager.makeFilter("filters.hexbin", *stage);
+}
+
+
+MetadataNode InfoKernel::run(const std::string& filename)
+{
+    MetadataNode root;
+
+    root.add("filename", filename);
+    if (m_showSummary)
+    {
+        QuickInfo qi = m_reader->preview();
+        MetadataNode summary = dumpSummary(qi).clone("summary");
+        root.add(summary);
+    }
+    else
+    {
+        if (m_needPoints || m_showMetadata)
+            m_manager.execute();
+        else
+            m_manager.prepare();
+        dump(root);
+    }
+    root.add("pdal_version", pdal::GetFullVersionString());
+    return root;
+}
+
+
+void InfoKernel::dump(MetadataNode& root)
+{
+    if (m_showSchema)
+        root.add(m_manager.pointTable().toMetadata().clone("schema"));
+
+    if (m_PointCloudSchemaOutput.size() > 0)
+    {
+#ifdef PDAL_HAVE_LIBXML2
+        XMLSchema schema(m_manager.pointTable().layout());
+
+        std::ostream *out = Utils::createFile(m_PointCloudSchemaOutput);
+        std::string xml(schema.xml());
+        out->write(xml.c_str(), xml.size());
+        Utils::closeFile(out);
+#else
+        std::cerr << "libxml2 support not enabled, no schema is produced" <<
+            std::endl;
+#endif
+
+    }
+    if (m_showStats)
+        root.add(m_statsStage->getMetadata().clone("stats"));
+
+    if (m_pipelineFile.size() > 0)
+        PipelineWriter::writePipeline(m_manager.getStage(), m_pipelineFile);
+
+    if (m_pointIndexes.size())
+    {
+        PointViewSet viewSet = m_manager.views();
+        assert(viewSet.size() == 1);
+        root.add(dumpPoints(*viewSet.begin()).clone("points"));
+    }
+
+    if (m_queryPoint.size())
+    {
+        PointViewSet viewSet = m_manager.views();
+        assert(viewSet.size() == 1);
+        root.add(dumpQuery(*viewSet.begin()));
+    }
+
+    if (m_showMetadata)
+    {
+        // If we have a reader cached, this means we
+        // weren't reading a pipeline file directly. In that
+        // case, use the metadata from the reader (old behavior).
+        // Otherwise, return the full metadata of the entire pipeline
+        if (m_reader)
+            root.add(m_reader->getMetadata().clone("metadata"));
+        else
+            root.add(m_manager.getMetadata().clone("metadata"));
+    }
+
+    if (m_boundary)
+    {
+        PointViewSet viewSet = m_manager.views();
+        assert(viewSet.size() == 1);
+        root.add(m_hexbinStage->getMetadata().clone("boundary"));
+    }
+}
+
+
+MetadataNode InfoKernel::dumpQuery(PointViewPtr inView) const
+{
+    int count;
+    std::string location;
+
+    // See if there's a provided point count.
+    StringList parts = Utils::split2(m_queryPoint, '/');
+    if (parts.size() == 2)
+    {
+        location = parts[0];
+        count = atoi(parts[1].c_str());
+    }
+    else if (parts.size() == 1)
+    {
+        location = parts[0];
+        count = inView->size();
+    }
+    else
+        count = 0;
+    if (count == 0)
+        throw pdal_error("Invalid location specification. "
+            "--query=\"X,Y[/count]\"");
+
+    auto seps = [](char c){ return (c == ',' || c == '|' || c == ' '); };
+
+    std::vector<std::string> tokens = Utils::split2(location, seps);
+    std::vector<double> values;
+    for (auto ti = tokens.begin(); ti != tokens.end(); ++ti)
+    {
+        double d;
+        if (Utils::fromString(*ti, d))
+            values.push_back(d);
+    }
+
+    if (values.size() != 2 && values.size() != 3)
+        throw pdal_error("--points must be two or three values");
+
+    PointViewPtr outView = inView->makeNew();
+
+    std::vector<PointId> ids;
+    if (values.size() >= 3)
+    {
+        KD3Index kdi(*inView);
+        kdi.build();
+        ids = kdi.neighbors(values[0], values[1], values[2], count);
+    }
+    else
+    {
+        KD2Index kdi(*inView);
+        kdi.build();
+        ids = kdi.neighbors(values[0], values[1], count);
+    }
+
+    for (auto i = ids.begin(); i != ids.end(); ++i)
+        outView->appendPoint(*inView.get(), *i);
+
+    return outView->toMetadata();
+}
+
+
+int InfoKernel::execute()
+{
+    std::string filename = m_usestdin ? std::string("STDIN") : m_inputFile;
+    setup(filename);
+    MetadataNode root = run(filename);
+    Utils::toJSON(root, std::cout);
+
+    return 0;
+}
+
+
+} // namespace pdal
diff --git a/kernels/info/InfoKernel.hpp b/kernels/InfoKernel.hpp
similarity index 100%
rename from kernels/info/InfoKernel.hpp
rename to kernels/InfoKernel.hpp
diff --git a/kernels/MergeKernel.cpp b/kernels/MergeKernel.cpp
new file mode 100644
index 0000000..65e69bc
--- /dev/null
+++ b/kernels/MergeKernel.cpp
@@ -0,0 +1,88 @@
+/******************************************************************************
+* Copyright (c) 2015, Hobu Inc. (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "MergeKernel.hpp"
+
+#include <filters/MergeFilter.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.merge", "Merge Kernel",
+    "http://pdal.io/apps/merge.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, MergeKernel, Kernel, s_info)
+
+std::string MergeKernel::getName() const
+{
+    return s_info.name;
+}
+
+
+void MergeKernel::addSwitches(ProgramArgs& args)
+{
+    args.add("files,f", "input/output files", m_files).setPositional();
+}
+
+
+void MergeKernel::validateSwitches(ProgramArgs& args)
+{
+    if (m_files.size() < 2)
+        throw pdal_error("Must specify an input and output file.");
+    m_outputFile = m_files.back();
+    m_files.resize(m_files.size() - 1);
+}
+
+
+int MergeKernel::execute()
+{
+    PointTable table;
+
+    MergeFilter filter;
+
+    for (size_t i = 0; i < m_files.size(); ++i)
+    {
+        Stage& reader = makeReader(m_files[i], m_driverOverride);
+        filter.setInput(reader);
+    }
+
+    Stage& writer = makeWriter(m_outputFile, filter, "");
+    writer.prepare(table);
+    writer.execute(table);
+    return 0;
+}
+
+} // namespace pdal
diff --git a/kernels/merge/MergeKernel.hpp b/kernels/MergeKernel.hpp
similarity index 100%
rename from kernels/merge/MergeKernel.hpp
rename to kernels/MergeKernel.hpp
diff --git a/kernels/PipelineKernel.cpp b/kernels/PipelineKernel.cpp
new file mode 100644
index 0000000..52c1f8f
--- /dev/null
+++ b/kernels/PipelineKernel.cpp
@@ -0,0 +1,127 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "PipelineKernel.hpp"
+
+#ifdef PDAL_HAVE_LIBXML2
+#include <pdal/XMLSchema.hpp>
+#endif
+
+#include <pdal/PDALUtils.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.pipeline",
+    "Pipeline Kernel", "http://pdal.io/apps/pipeline.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, PipelineKernel, Kernel, s_info)
+
+std::string PipelineKernel::getName() const { return s_info.name; }
+
+PipelineKernel::PipelineKernel() : m_validate(false), m_progressFd(-1)
+{}
+
+
+void PipelineKernel::validateSwitches(ProgramArgs& args)
+{
+    if (m_usestdin)
+        m_inputFile = "STDIN";
+
+    if (m_inputFile.empty())
+        throw pdal_error("Input filename required.");
+}
+
+
+void PipelineKernel::addSwitches(ProgramArgs& args)
+{
+    args.add("input,i", "input file name", m_inputFile).setOptionalPositional();
+    args.add("pipeline-serialization", "Output file for pipeline serialization",
+        m_pipelineFile);
+    args.add("validate", "Validate the pipeline (including serialization), "
+        "but do not write points", m_validate);
+    args.add("progress",
+        "Name of file or FIFO to which stages should write progress "
+        "information.  The file/FIFO must exist.  PDAL will not create "
+        "the progress file.",
+        m_progressFile);
+    args.add("pointcloudschema", "dump PointCloudSchema XML output",
+        m_PointCloudSchemaOutput).setHidden();
+    args.add("stdin,s", "Read pipeline from standard input", m_usestdin);
+}
+
+int PipelineKernel::execute()
+{
+    if (!Utils::fileExists(m_inputFile))
+        throw pdal_error("file not found: " + m_inputFile);
+    if (m_progressFile.size())
+        m_progressFd = Utils::openProgress(m_progressFile);
+
+    m_manager.readPipeline(m_inputFile);
+
+    if (m_validate)
+    {
+        // Validate the options of the pipeline we were
+        // given, and once we succeed, we're done
+        m_manager.prepare();
+        Utils::closeProgress(m_progressFd);
+        return 0;
+    }
+
+    m_manager.execute();
+
+    if (m_pipelineFile.size() > 0)
+        PipelineWriter::writePipeline(m_manager.getStage(), m_pipelineFile);
+
+    if (m_PointCloudSchemaOutput.size() > 0)
+    {
+#ifdef PDAL_HAVE_LIBXML2
+        XMLSchema schema(m_manager.pointTable().layout());
+
+        std::ostream *out = Utils::createFile(m_PointCloudSchemaOutput);
+        std::string xml(schema.xml());
+        out->write(xml.c_str(), xml.size());
+        Utils::closeFile(out);
+#else
+        std::cerr << "libxml2 support not available, no schema is produced" <<
+            std::endl;
+#endif
+
+    }
+    Utils::closeProgress(m_progressFd);
+    return 0;
+}
+
+} // pdal
diff --git a/kernels/pipeline/PipelineKernel.hpp b/kernels/PipelineKernel.hpp
similarity index 100%
rename from kernels/pipeline/PipelineKernel.hpp
rename to kernels/PipelineKernel.hpp
diff --git a/kernels/RandomKernel.cpp b/kernels/RandomKernel.cpp
new file mode 100644
index 0000000..813cf7b
--- /dev/null
+++ b/kernels/RandomKernel.cpp
@@ -0,0 +1,110 @@
+/******************************************************************************
+* Copyright (c) 2014, Brad Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "RandomKernel.hpp"
+
+#include <pdal/StageFactory.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.random", "Random Kernel",
+    "http://pdal.io/apps/random.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, RandomKernel, Kernel, s_info)
+
+std::string RandomKernel::getName() const { return s_info.name; }
+
+RandomKernel::RandomKernel()
+    : m_bCompress(false)
+    , m_numPointsToWrite(0)
+    , m_distribution("uniform")
+{
+}
+
+
+void RandomKernel::addSwitches(ProgramArgs& args)
+{
+    args.add("output,o", "Output file name", m_outputFile).setPositional();
+    args.add("compress,z",
+        "Compress output data (if supported by output format)", m_bCompress);
+    args.add("count", "How many points should we write?", m_numPointsToWrite);
+    args.add("bounds", "Extent (in XYZ to clip output to)", m_bounds);
+    args.add("mean", "A comma-separated or quoted, space-separated list "
+        "of means (normal mode): \n--mean 0.0,0.0,0.0\n--mean \"0.0 0.0 0.0\"",
+        m_means);
+    args.add("stdev", "A comma-separated or quoted, space-separated list "
+        "of standard deviations (normal mode): \n"
+        "--stdev 0.0,0.0,0.0\n--stdev \"0.0 0.0 0.0\"", m_stdevs);
+    args.add("distribution", "Distribution (uniform / normal)", m_distribution,
+        "uniform");
+}
+
+
+int RandomKernel::execute()
+{
+    Options readerOptions;
+
+    if (!m_bounds.empty())
+        readerOptions.add("bounds", m_bounds);
+
+    std::string distribution(Utils::tolower(m_distribution));
+    if (distribution == "uniform")
+        readerOptions.add("mode", "uniform");
+    else if (distribution == "normal")
+        readerOptions.add("mode", "normal");
+    else if (distribution == "random")
+        readerOptions.add("mode", "random");
+    else
+        throw pdal_error("invalid distribution: " + m_distribution);
+    readerOptions.add("count", m_numPointsToWrite);
+    Stage& reader = makeReader("", "readers.faux", readerOptions);
+
+    Options writerOptions;
+    if (m_bCompress)
+        writerOptions.add("compression", true);
+    Stage& writer = makeWriter(m_outputFile, reader, "", writerOptions);
+
+    PointTable table;
+    writer.prepare(table);
+    PointViewSet viewSet = writer.execute(table);
+
+    if (isVisualize())
+        visualize(*viewSet.begin());
+
+    return 0;
+}
+
+} // pdal
diff --git a/kernels/random/RandomKernel.hpp b/kernels/RandomKernel.hpp
similarity index 100%
rename from kernels/random/RandomKernel.hpp
rename to kernels/RandomKernel.hpp
diff --git a/kernels/SortKernel.cpp b/kernels/SortKernel.cpp
new file mode 100644
index 0000000..a81368e
--- /dev/null
+++ b/kernels/SortKernel.cpp
@@ -0,0 +1,109 @@
+/******************************************************************************
+* Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "SortKernel.hpp"
+
+#include <io/BufferReader.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.sort", "Sort Kernel",
+    "http://pdal.io/apps/sort.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, SortKernel, Kernel, s_info)
+
+std::string SortKernel::getName() const
+{
+    return s_info.name;
+}
+
+
+SortKernel::SortKernel() : m_bCompress(false), m_bForwardMetadata(false)
+{}
+
+
+void SortKernel::addSwitches(ProgramArgs& args)
+{
+    args.add("input,i", "Input filename", m_inputFile).setPositional();
+    args.add("output,o", "Output filename", m_outputFile).setPositional();
+    args.add("compress,z",
+        "Compress output data (if supported by output format)", m_bCompress);
+    args.add("metadata,m",
+        "Forward metadata (VLRs, header entries, etc) from previous stages",
+        m_bForwardMetadata);
+}
+
+
+int SortKernel::execute()
+{
+    Stage& readerStage = makeReader(m_inputFile, m_driverOverride);
+
+    // go ahead and prepare/execute on reader stage only to grab input
+    // PointViewSet, this makes the input PointView available to both the
+    // processing pipeline and the visualizer
+    PointTable table;
+    readerStage.prepare(table);
+    PointViewSet viewSetIn = readerStage.execute(table);
+
+    // the input PointViewSet will be used to populate a BufferReader that is
+    // consumed by the processing pipeline
+    PointViewPtr inView = *viewSetIn.begin();
+
+    BufferReader bufferReader;
+    bufferReader.addView(inView);
+
+    Stage& sortStage = makeFilter("filters.mortonorder", bufferReader);
+
+    Options writerOptions;
+    if (m_bCompress)
+        writerOptions.add("compression", true);
+    if (m_bForwardMetadata)
+        writerOptions.add("forward_metadata", true);
+    Stage& writer = makeWriter(m_outputFile, sortStage, "", writerOptions);
+
+    writer.prepare(table);
+
+    // process the data, grabbing the PointViewSet for visualization of the
+    PointViewSet viewSetOut = writer.execute(table);
+
+    if (isVisualize())
+        visualize(*viewSetOut.begin());
+
+    return 0;
+}
+
+} // namespace pdal
diff --git a/kernels/sort/SortKernel.hpp b/kernels/SortKernel.hpp
similarity index 100%
rename from kernels/sort/SortKernel.hpp
rename to kernels/SortKernel.hpp
diff --git a/kernels/SplitKernel.cpp b/kernels/SplitKernel.cpp
new file mode 100644
index 0000000..7b1508e
--- /dev/null
+++ b/kernels/SplitKernel.cpp
@@ -0,0 +1,136 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "SplitKernel.hpp"
+
+#include <io/BufferReader.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/pdal_macros.hpp>
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.split", "Split Kernel",
+    "http://pdal.io/apps/split.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, SplitKernel, Kernel, s_info)
+
+std::string SplitKernel::getName() const
+{
+    return s_info.name;
+}
+
+
+void SplitKernel::addSwitches(ProgramArgs& args)
+{
+    args.add("input,i", "Input filename", m_inputFile).setPositional();
+    args.add("output,o", "Output filename", m_outputFile).setPositional();
+    args.add("length", "Edge length for splitter cells", m_length, 0.0);
+    args.add("capacity", "Point capacity of chipper cells", m_capacity);
+    args.add("origin_x", "Origin in X axis for splitter cells", m_xOrigin,
+        std::numeric_limits<double>::quiet_NaN());
+    args.add("origin_y", "Origin in Y axis for splitter cells", m_yOrigin,
+        std::numeric_limits<double>::quiet_NaN());
+}
+
+
+void SplitKernel::validateSwitches(ProgramArgs& args)
+{
+#ifdef WIN32
+    char pathSeparator = '\\';
+#else
+    char pathSeparator = '/';
+#endif
+
+    if (m_length && m_capacity)
+        throw pdal_error("Can't specify both length and capacity.");
+    if (!m_length && !m_capacity)
+        m_capacity = 100000;
+    if (m_outputFile.back() == pathSeparator)
+        m_outputFile += m_inputFile;
+}
+
+
+namespace
+{
+std::string makeFilename(const std::string& s, int i)
+{
+    std::string out = s;
+    auto pos = out.find_last_of('.');
+    if (pos == out.npos)
+        pos = out.length();
+    out.insert(pos, std::string("_") + std::to_string(i));
+    return out;
+}
+}
+
+
+int SplitKernel::execute()
+{
+    PointTable table;
+
+    Stage& reader = makeReader(m_inputFile, m_driverOverride);
+
+    Options filterOpts;
+    std::string driver = (m_length ? "filters.splitter" : "filters.chipper");
+    if (m_length)
+    {
+        filterOpts.add("length", m_length);
+        filterOpts.add("origin_x", m_xOrigin);
+        filterOpts.add("origin_y", m_yOrigin);
+    }
+    else
+    {
+        filterOpts.add("capacity", m_capacity);
+    }
+    Stage& f = makeFilter(driver, reader, filterOpts);
+    f.prepare(table);
+    PointViewSet pvSet = f.execute(table);
+
+    int filenum = 1;
+    for (auto& pvp : pvSet)
+    {
+        BufferReader reader;
+        reader.addView(pvp);
+
+        std::string filename = makeFilename(m_outputFile, filenum++);
+        Stage& writer = makeWriter(filename, reader, "");
+
+        writer.prepare(table);
+        writer.execute(table);
+    }
+    return 0;
+}
+
+} // namespace pdal
diff --git a/kernels/split/SplitKernel.hpp b/kernels/SplitKernel.hpp
similarity index 100%
rename from kernels/split/SplitKernel.hpp
rename to kernels/SplitKernel.hpp
diff --git a/kernels/TIndexKernel.cpp b/kernels/TIndexKernel.cpp
new file mode 100644
index 0000000..6e73395
--- /dev/null
+++ b/kernels/TIndexKernel.cpp
@@ -0,0 +1,678 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "TIndexKernel.hpp"
+
+#include <memory>
+#include <vector>
+
+#include <pdal/KernelFactory.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/pdal_macros.hpp>
+
+#include <cpl_string.h>
+
+namespace
+{
+
+void setDate(OGRFeatureH feature, const tm& tyme, int fieldNumber)
+{
+    OGR_F_SetFieldDateTime(feature, fieldNumber,
+        tyme.tm_year + 1900, tyme.tm_mon + 1, tyme.tm_mday, tyme.tm_hour,
+        tyme.tm_min, tyme.tm_sec, 100);
+}
+
+
+} // anonymous namespace
+
+
+namespace pdal
+{
+
+static PluginInfo const s_info = PluginInfo("kernels.tindex", "TIndex Kernel",
+    "http://pdal.io/apps/tindex.html" );
+
+CREATE_STATIC_PLUGIN(1, 0, TIndexKernel, Kernel, s_info)
+
+std::string TIndexKernel::getName() const { return s_info.name; }
+
+TIndexKernel::TIndexKernel()
+    : Kernel()
+//ABELL - need to option this.
+    , m_srsColumnName("srs")
+    , m_merge(false)
+    , m_dataset(NULL)
+    , m_layer(NULL)
+    , m_fastBoundary(false)
+
+{}
+
+
+void TIndexKernel::addSwitches(ProgramArgs& args)
+{
+    args.add("tindex", "OGR-readable/writeable tile index output",
+        m_idxFilename).setPositional();
+    args.add("filespec", "Build: Pattern of files to index. "
+        "Merge: Output filename", m_filespec).setPositional();
+    args.add("fast_boundary", "Use extent instead of exact boundary",
+        m_fastBoundary);
+    args.add("lyr_name", "OGR layer name to write into datasource",
+        m_layerName);
+    args.add("tindex_name", "Tile index column name", m_tileIndexColumnName,
+        "location");
+    args.add("ogrdriver,f", "OGR driver name to use ", m_driverName,
+        "ESRI Shapefile");
+    args.add("t_srs", "Target SRS of tile index", m_tgtSrsString,
+        "EPSG:4326");
+    args.add("a_srs", "Assign SRS of tile with no SRS to this value",
+        m_assignSrsString, "EPSG:4326");
+    args.add("bounds", "Extent (in XYZ) to clip output to", m_bounds);
+    args.add("polygon", "Well-known text of polygon to clip output", m_wkt);
+    args.add("write_absolute_path",
+        "Write absolute rather than relative file paths", m_absPath);
+    args.add("merge", "Whether we're merging the entries in a tindex file.",
+        m_merge);
+    args.add("stdin,s", "Read filespec pattern from standard input",
+        m_usestdin);
+}
+
+
+void TIndexKernel::validateSwitches(ProgramArgs& args)
+{
+    if (m_merge)
+    {
+        if (!m_wkt.empty() && !m_bounds.empty())
+            throw pdal_error("Can't specify both 'polygon' and "
+                "'bounds' options.");
+        if (!m_bounds.empty())
+            m_wkt = m_bounds.toWKT();
+        if (m_filespec.empty())
+            throw pdal_error("No output filename provided.");
+        StringList invalidArgs;
+        invalidArgs.push_back("a_srs");
+        invalidArgs.push_back("src_srs_name");
+        for (auto arg : invalidArgs)
+            if (args.set(arg))
+            {
+                std::ostringstream out;
+
+                out << "option '" << arg << "' not supported during merge.";
+                throw pdal_error(out.str());
+            }
+    }
+    else
+    {
+        if (m_filespec.empty() && !m_usestdin)
+            throw pdal_error("No input pattern specified");
+        if (args.set("polygon"))
+            throw pdal_error("'polygon' option not supported when building "
+                "index.");
+        if (args.set("bounds"))
+            throw pdal_error("'bounds' option not supported when building "
+                "index.");
+    }
+}
+
+
+int TIndexKernel::execute()
+{
+    gdal::registerDrivers();
+
+    if (m_merge)
+        mergeFile();
+    else
+    {
+        try
+        {
+            createFile();
+        }
+        catch (pdal_error&)
+        {
+            if (m_dataset)
+                OGR_DS_Destroy(m_dataset);
+            throw;
+        }
+    }
+    return 0;
+}
+
+
+StringList readSTDIN()
+{
+    std::string line;
+    StringList output;
+    while (std::getline(std::cin, line))
+    {
+        output.push_back(line);
+    }
+    return output;
+}
+
+
+bool TIndexKernel::isFileIndexed(const FieldIndexes& indexes,
+    const FileInfo& fileInfo)
+{
+    std::ostringstream qstring;
+
+    qstring << Utils::toupper(m_tileIndexColumnName) << "=" <<
+        "'" << fileInfo.m_filename << "'";
+    std::string query = qstring.str();
+    OGRErr err = OGR_L_SetAttributeFilter(m_layer, query.c_str());
+    if (err != OGRERR_NONE)
+    {
+        std::ostringstream oss;
+        oss << "Unable to set attribute filter for file '" <<
+             fileInfo.m_filename << "'";
+        throw pdal_error(oss.str());
+    }
+
+    bool output(false);
+    OGR_L_ResetReading(m_layer);
+    if (OGR_L_GetNextFeature(m_layer))
+        output = true;
+    OGR_L_ResetReading(m_layer);
+    OGR_L_SetAttributeFilter(m_layer, NULL);
+    return output;
+}
+
+
+void TIndexKernel::createFile()
+{
+    if (!m_usestdin)
+        m_files = FileUtils::glob(m_filespec);
+    else
+        m_files = readSTDIN();
+
+    if (m_files.empty())
+    {
+        std::ostringstream out;
+        out << "Couldn't find files to index: " << m_filespec << ".";
+        throw pdal_error(out.str());
+    }
+
+//ABELL - Remove CPLGetBasename use.
+    const std::string filename = m_files.front();
+    if (m_layerName.empty())
+       m_layerName = CPLGetBasename(filename.c_str());
+
+    // Open or create the dataset.
+    if (!openDataset(m_idxFilename))
+        if (!createDataset(m_idxFilename))
+        {
+            std::ostringstream out;
+            out << "Couldn't open or create index dataset file '" <<
+                m_idxFilename << "'.";
+            throw pdal_error(out.str());
+        }
+
+    // Open or create a layer
+    if (!openLayer(m_layerName))
+        if (!createLayer(m_layerName))
+        {
+            std::ostringstream out;
+            out << "Couldn't open or create layer '" << m_layerName <<
+                "' in output file '" << m_idxFilename << "'.";
+            throw pdal_error(out.str());
+        }
+
+    FieldIndexes indexes = getFields();
+
+    KernelFactory factory(false);
+    for (auto f : m_files)
+    {
+        //ABELL - Not sure why we need to get absolute path here.
+        f = FileUtils::toAbsolutePath(f);
+        FileInfo info = getFileInfo(factory, f);
+        if (!isFileIndexed(indexes, info))
+        {
+            if (createFeature(indexes, info))
+                m_log->get(LogLevel::Info) << "Indexed file " << f << std::endl;
+            else
+                m_log->get(LogLevel::Error) << "Failed to create feature for "
+                    "file '" << f << "'" << std::endl;
+
+        }
+    }
+    OGR_DS_Destroy(m_dataset);
+}
+
+
+void TIndexKernel::mergeFile()
+{
+    using namespace gdal;
+
+    std::ostringstream out;
+
+    if (!openDataset(m_idxFilename))
+    {
+        std::ostringstream out;
+        out << "Couldn't open index dataset file '" << m_idxFilename << "'.";
+        throw pdal_error(out.str());
+    }
+    if (!openLayer(m_layerName))
+    {
+        std::ostringstream out;
+        out << "Couldn't open layer '" << m_layerName <<
+            "' in output file '" << m_idxFilename << "'.";
+        throw pdal_error(out.str());
+    }
+
+    FieldIndexes indexes = getFields();
+
+    SpatialRef outSrs(m_tgtSrsString);
+    if (!outSrs)
+        throw pdal_error("Couldn't interpret target SRS string.");
+
+    if (!m_wkt.empty())
+    {
+        Geometry g(m_wkt, outSrs);
+
+        if (!g)
+            throw pdal_error("Couldn't interpret geometry filter string.");
+        OGR_L_SetSpatialFilter(m_layer, g.get());
+    }
+
+    std::vector<FileInfo> files;
+
+    // Docs are bad here.  You need this call even if you haven't read anything
+    // or nothing happens.
+    OGR_L_ResetReading(m_layer);
+    while (true)
+    {
+        OGRFeatureH feature = OGR_L_GetNextFeature(m_layer);
+        if (!feature)
+            break;
+
+        FileInfo fileInfo;
+        fileInfo.m_filename =
+            OGR_F_GetFieldAsString(feature, indexes.m_filename);
+        fileInfo.m_srs =
+            OGR_F_GetFieldAsString(feature, indexes.m_srs);
+        files.push_back(fileInfo);
+
+        OGR_F_Destroy(feature);
+    }
+
+    Options cropOptions;
+    if (!m_bounds.empty())
+        cropOptions.add("bounds", m_bounds);
+    else
+        cropOptions.add("polygon", m_wkt);
+
+    Stage& merge = makeFilter("filters.merge");
+    for (auto f : files)
+    {
+        Stage& reader = makeReader(f.m_filename, m_driverOverride);
+        Stage *premerge = &reader;
+
+        if (m_tgtSrsString != f.m_srs)
+        {
+            Options reproOptions;
+            reproOptions.add("out_srs", m_tgtSrsString);
+            reproOptions.add("in_srs", f.m_srs);
+            Stage& repro = makeFilter("filters.reprojection", reader,
+                reproOptions);
+            premerge = &repro;
+        }
+
+        // WKT is set, even if we're using a bounding box for fitering, so
+        // can be used as a test here.
+        if (!m_wkt.empty())
+        {
+            Stage& crop = makeFilter("filters.crop", *premerge, cropOptions);
+            premerge = &crop;
+        }
+        merge.setInput(*premerge);
+    }
+
+    Options writerOptions;
+    writerOptions.add("offset_x", "auto");
+    writerOptions.add("offset_y", "auto");
+    writerOptions.add("offset_z", "auto");
+    Stage& writer = makeWriter(m_filespec, merge, "", writerOptions);
+
+    PointTable table;
+    writer.prepare(table);
+    writer.execute(table);
+}
+
+
+bool TIndexKernel::createFeature(const FieldIndexes& indexes,
+    FileInfo& fileInfo)
+{
+    using namespace gdal;
+
+    OGRFeatureH hFeature = OGR_F_Create(OGR_L_GetLayerDefn(m_layer));
+
+    // Set the creation time into the feature.
+    setDate(hFeature, fileInfo.m_ctime, indexes.m_ctime);
+
+    // Set the file mod time into the feature.
+    setDate(hFeature, fileInfo.m_mtime, indexes.m_mtime);
+
+    // Set the filename into the feature.
+    OGR_F_SetFieldString(hFeature, indexes.m_filename,
+        fileInfo.m_filename.c_str());
+
+    // Set the SRS into the feature.
+    // We override if m_assignSrsString is set
+    if (fileInfo.m_srs.empty() || m_assignSrsString.size())
+        fileInfo.m_srs = m_assignSrsString;
+
+    SpatialRef srcSrs(fileInfo.m_srs);
+    if (srcSrs.empty())
+    {
+        std::ostringstream oss;
+
+        oss << "Unable to import source spatial reference '" <<
+            fileInfo.m_srs << "' for file '" <<
+            fileInfo.m_filename << "'.";
+        throw pdal_error(oss.str());
+    }
+
+    // We have a limit of like 254 characters in some formats (notably
+    // shapefile), so try to get the condensed version of the SRS.
+
+    // Failing that, get the proj.4 version.  Not sure what's supposed to
+    // happen if we overflow 254 with proj.4.
+
+    const char* pszAuthorityCode = OSRGetAuthorityCode(srcSrs.get(), NULL);
+    const char* pszAuthorityName = OSRGetAuthorityName(srcSrs.get(), NULL);
+    if (pszAuthorityName && pszAuthorityCode)
+    {
+        std::string auth = std::string(pszAuthorityName) + ":" +
+            pszAuthorityCode;
+        OGR_F_SetFieldString(hFeature, indexes.m_srs, auth.data());
+    }
+    else
+    {
+        char* pszProj4 = NULL;
+        int err = -1;
+        try
+        {
+            err = OSRExportToProj4(srcSrs.get(), &pszProj4);
+        }
+        catch (pdal_error)
+        {}
+        if (err != OGRERR_NONE)
+        {
+            m_log->get(LogLevel::Warning) << "Unable to convert SRS to "
+                "proj.4 format for file '" << fileInfo.m_filename << "'" <<
+                std::endl;
+            return false;
+        }
+        std::string srs = std::string(pszProj4);
+        OGR_F_SetFieldString(hFeature, indexes.m_srs, srs.c_str());
+        CPLFree(pszProj4);
+    }
+
+    // Set the geometry in the feature
+    Geometry g = prepareGeometry(fileInfo);
+    char *pgeom;
+    OGR_G_ExportToWkt(g.get(), &pgeom);
+    OGR_F_SetGeometry(hFeature, g.get());
+
+    bool ok = (OGR_L_CreateFeature(m_layer, hFeature) == OGRERR_NONE);
+    OGR_F_Destroy(hFeature);
+    return ok;
+}
+
+
+TIndexKernel::FileInfo TIndexKernel::getFileInfo(KernelFactory& factory,
+    const std::string& filename)
+{
+    FileInfo fileInfo;
+
+    PipelineManager manager;
+    manager.commonOptions() = m_manager.commonOptions();
+    manager.stageOptions() = m_manager.stageOptions();
+
+    // Need to make sure options get set.
+    Stage& reader = manager.makeReader(filename, "");
+
+    if (m_fastBoundary)
+    {
+        QuickInfo qi = reader.preview();
+
+        std::stringstream polygon;
+        polygon << "POLYGON ((";
+
+        polygon <<         qi.m_bounds.minx << " " << qi.m_bounds.miny;
+        polygon << ", " << qi.m_bounds.maxx << " " << qi.m_bounds.miny;
+        polygon << ", " << qi.m_bounds.maxx << " " << qi.m_bounds.maxy;
+        polygon << ", " << qi.m_bounds.minx << " " << qi.m_bounds.maxy;
+        polygon << ", " << qi.m_bounds.minx << " " << qi.m_bounds.miny;
+        polygon << "))";
+        fileInfo.m_boundary = polygon.str();
+        if (!qi.m_srs.empty())
+            fileInfo.m_srs = qi.m_srs.getWKT();
+    }
+    else
+    {
+        Stage& hexer = manager.makeFilter("filters.hexbin", reader);
+
+        PointTable table;
+        hexer.prepare(table);
+        PointViewSet set = hexer.execute(table);
+
+        MetadataNode m = table.metadata();
+        m = m.findChild("filters.hexbin:boundary");
+        fileInfo.m_boundary = m.value();
+
+        PointViewPtr v = *set.begin();
+        if (!v->spatialReference().empty())
+            fileInfo.m_srs = v->spatialReference().getWKT();
+    }
+
+    FileUtils::fileTimes(filename, &fileInfo.m_ctime, &fileInfo.m_mtime);
+    fileInfo.m_filename = filename;
+
+    return fileInfo;
+}
+
+
+bool TIndexKernel::openDataset(const std::string& filename)
+{
+    m_dataset = OGROpen(filename.c_str(), TRUE, NULL);
+    return (bool)m_dataset;
+}
+
+
+bool TIndexKernel::createDataset(const std::string& filename)
+{
+    OGRSFDriverH hDriver = OGRGetDriverByName(m_driverName.c_str());
+    if (!hDriver)
+    {
+        std::ostringstream oss;
+
+        oss << "Can't create dataset using driver '" << m_driverName <<
+            "'. Driver is not available.";
+        throw pdal_error(oss.str());
+    }
+
+    std::string dsname = FileUtils::toAbsolutePath(filename);
+    m_dataset = OGR_Dr_CreateDataSource(hDriver, dsname.c_str(), NULL);
+    return (bool)m_dataset;
+}
+
+
+bool TIndexKernel::openLayer(const std::string& layerName)
+{
+    if (OGR_DS_GetLayerCount(m_dataset) == 1)
+        m_layer = OGR_DS_GetLayer(m_dataset, 0);
+    else if (layerName.size())
+        m_layer = OGR_DS_GetLayerByName(m_dataset, m_layerName.c_str());
+
+    return (bool)m_layer;
+}
+
+
+bool TIndexKernel::createLayer(std::string const& layername)
+{
+    using namespace gdal;
+
+    SpatialRef srs(m_tgtSrsString);
+    if (!srs)
+        m_log->get(LogLevel::Error) << "Unable to import srs for layer "
+           "creation" << std::endl;
+
+    m_layer = OGR_DS_CreateLayer(m_dataset, m_layerName.c_str(),
+        srs.get(), wkbPolygon, NULL);
+
+    if (m_layer)
+        createFields();
+
+    //ABELL - At this point we should essentially "sync" things so that
+    //  index file gets created with the proper fields.  If this doesn't
+    //  and a failure occurs, the file may be left with a layer that doesn't
+    //  have the requisite fields.  Note that OGR_DS_SyncToDisk doesn't seem
+    //  to work reliably enough to warrant use.
+    return (bool)m_layer;
+}
+
+
+void TIndexKernel::createFields()
+{
+    OGRFieldDefnH hFieldDefn = OGR_Fld_Create(
+        m_tileIndexColumnName.c_str(), OFTString);
+    OGR_Fld_SetWidth(hFieldDefn, 254);
+    OGR_L_CreateField(m_layer, hFieldDefn, TRUE);
+    OGR_Fld_Destroy(hFieldDefn);
+
+    hFieldDefn = OGR_Fld_Create(m_srsColumnName.c_str(), OFTString);
+    OGR_Fld_SetWidth(hFieldDefn, 254);
+    OGR_L_CreateField(m_layer, hFieldDefn, TRUE );
+    OGR_Fld_Destroy(hFieldDefn);
+
+    hFieldDefn = OGR_Fld_Create("modified", OFTDateTime);
+    OGR_L_CreateField(m_layer, hFieldDefn, TRUE);
+    OGR_Fld_Destroy(hFieldDefn);
+
+    hFieldDefn = OGR_Fld_Create("created", OFTDateTime);
+    OGR_L_CreateField(m_layer, hFieldDefn, TRUE);
+    OGR_Fld_Destroy(hFieldDefn);
+}
+
+
+TIndexKernel::FieldIndexes TIndexKernel::getFields()
+{
+    FieldIndexes indexes;
+
+    void *fDefn = OGR_L_GetLayerDefn(m_layer);
+
+    indexes.m_filename = OGR_FD_GetFieldIndex(fDefn,
+        m_tileIndexColumnName.c_str());
+    if (indexes.m_filename < 0)
+    {
+        std::ostringstream out;
+
+        out << "Unable to find field '" << m_tileIndexColumnName <<
+            "' in file '" << m_idxFilename << "'.";
+        throw pdal_error(out.str());
+    }
+    indexes.m_srs = OGR_FD_GetFieldIndex(fDefn, m_srsColumnName.c_str());
+    if (indexes.m_srs < 0)
+    {
+        std::ostringstream out;
+
+        out << "Unable to find field '" << m_srsColumnName << "' in file '" <<
+            m_idxFilename << "'.";
+        throw pdal_error(out.str());
+    }
+
+    indexes.m_ctime = OGR_FD_GetFieldIndex(fDefn, "created");
+    indexes.m_mtime = OGR_FD_GetFieldIndex(fDefn, "modified");
+
+//     /* Load in memory existing file names in SHP */
+//     int nExistingFiles = (int)OGR_L_GetFeatureCount(m_layer, FALSE);
+//     for (auto i = 0; i < nExistingFiles; i++)
+//     {
+//         OGRFeatureH hFeature = OGR_L_GetNextFeature(m_layer);
+//         m_files.push_back(OGR_F_GetFieldAsString(hFeature, indexes.m_filename));
+//         OGR_F_Destroy(hFeature);
+//     }
+    return indexes;
+}
+
+
+gdal::Geometry TIndexKernel::prepareGeometry(const FileInfo& fileInfo)
+{
+    using namespace gdal;
+
+    std::ostringstream oss;
+
+
+    SpatialRef srcSrs(fileInfo.m_srs);
+    SpatialRef tgtSrs(m_tgtSrsString);
+    if (!tgtSrs)
+        throw pdal_error("Unable to import target SRS.");
+
+    Geometry g;
+    try
+    {
+       g = prepareGeometry(fileInfo.m_boundary, srcSrs, tgtSrs);
+    }
+    catch (pdal_error& e)
+    {
+        oss << "Unable to transform geometry from source to target SRS for " <<
+            fileInfo.m_filename << "'. Message is '" << e.what() << "'";
+        throw pdal_error(oss.str());
+    }
+    if (!g)
+    {
+        oss << "Update to create geometry from WKT for '" <<
+            fileInfo.m_filename << "'.";
+        throw pdal_error(oss.str());
+    }
+    return g;
+}
+
+
+gdal::Geometry TIndexKernel::prepareGeometry(const std::string& wkt,
+   const gdal::SpatialRef& inSrs, const gdal::SpatialRef& outSrs)
+{
+    // Create OGR geometry from text.
+
+    gdal::Geometry g(wkt, inSrs);
+
+    if (g)
+        if (OGR_G_TransformTo(g.get(), outSrs.get()) != OGRERR_NONE)
+            throw pdal_error("Unable to transform geometry.");
+
+    return g;
+}
+
+} // namespace pdal
diff --git a/kernels/TIndexKernel.hpp b/kernels/TIndexKernel.hpp
new file mode 100644
index 0000000..f87e9bb
--- /dev/null
+++ b/kernels/TIndexKernel.hpp
@@ -0,0 +1,119 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/GDALUtils.hpp>
+#include <pdal/Kernel.hpp>
+#include <pdal/Stage.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/plugin.hpp>
+
+
+extern "C" int32_t TIndexKernel_ExitFunc();
+extern "C" PF_ExitFunc TIndexKernel_InitPlugin();
+
+namespace pdal
+{
+
+class KernelFactory;
+
+class PDAL_DLL TIndexKernel : public Kernel
+{
+    struct FileInfo
+    {
+        std::string m_filename;
+        std::string m_srs;
+        std::string m_boundary;
+        struct tm m_ctime;
+        struct tm m_mtime;
+    };
+
+    struct FieldIndexes
+    {
+        int m_filename;
+        int m_srs;
+        int m_ctime;
+        int m_mtime;
+    };
+
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+    int execute(); // overrride
+
+private:
+    TIndexKernel();
+    virtual void addSwitches(ProgramArgs& args);
+    virtual void validateSwitches(ProgramArgs& args);
+
+    void createFile();
+    void mergeFile();
+    bool openDataset(const std::string& filename);
+    bool createDataset(const std::string& filename);
+    bool openLayer(const std::string& layerName);
+    bool createLayer(const std::string& layerName);
+    FieldIndexes getFields();
+    FileInfo getFileInfo(KernelFactory& factory, const std::string& filename);
+    bool createFeature(const FieldIndexes& indexes, FileInfo& info);
+    gdal::Geometry prepareGeometry(const FileInfo& fileInfo);
+    gdal::Geometry prepareGeometry(const std::string& wkt,
+        const gdal::SpatialRef& inSrs, const gdal::SpatialRef& outSrs);
+    void createFields();
+
+    bool isFileIndexed( const FieldIndexes& indexes, const FileInfo& fileInfo);
+
+    std::string m_idxFilename;
+    std::string m_filespec;
+    StringList m_files;
+    std::string m_layerName;
+    std::string m_driverName;
+    std::string m_tileIndexColumnName;
+    std::string m_srsColumnName;
+    std::string m_wkt;
+    BOX2D m_bounds;
+    bool m_merge;
+    bool m_absPath;
+
+    void *m_dataset;
+    void *m_layer;
+    std::string m_tgtSrsString;
+    std::string m_assignSrsString;
+    bool m_fastBoundary;
+    bool m_usestdin;
+};
+
+} // namespace pdal
+
diff --git a/kernels/TranslateKernel.cpp b/kernels/TranslateKernel.cpp
new file mode 100644
index 0000000..2595be6
--- /dev/null
+++ b/kernels/TranslateKernel.cpp
@@ -0,0 +1,200 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "TranslateKernel.hpp"
+
+#include <pdal/KernelFactory.hpp>
+#include <pdal/pdal_macros.hpp>
+#include <pdal/PipelineWriter.hpp>
+#include <pdal/PointTable.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/Stage.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/PipelineReaderJSON.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <json/json.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace pdal
+{
+
+static PluginInfo const s_info =
+    PluginInfo("kernels.translate",
+               "The Translate kernel allows users to construct a pipeline " \
+               "consisting of a reader, a writer, and N filter stages. " \
+               "Any supported stage type can be specified from the command " \
+               "line, reducing the need to create custom kernels for every " \
+               "combination.",
+               "http://pdal.io/apps/translate.html");
+
+CREATE_STATIC_PLUGIN(1, 0, TranslateKernel, Kernel, s_info)
+
+std::string TranslateKernel::getName() const
+{
+    return s_info.name;
+}
+
+TranslateKernel::TranslateKernel()
+{}
+
+void TranslateKernel::addSwitches(ProgramArgs& args)
+{
+    args.add("input,i", "Input filename", m_inputFile).
+        setPositional();
+    args.add("output,o", "Output filename", m_outputFile).
+        setPositional();
+    args.add("filter,f", "Filter type", m_filterType).
+        setOptionalPositional();
+    args.add("json", "JSON array of filters", m_filterJSON);
+    args.add("pipeline,p", "Pipeline output", m_pipelineOutput);
+    args.add("metadata,m", "Dump metadata output to the specified file",
+        m_metadataFile);
+    args.add("reader,r", "Reader type", m_readerType);
+    args.add("writer,w", "Writer type", m_writerType);
+}
+
+/*
+  Build a pipeline from a JSON filter specification.
+*/
+void TranslateKernel::makeJSONPipeline()
+{
+    std::string json;
+
+    if (pdal::FileUtils::fileExists(m_filterJSON))
+        json = pdal::FileUtils::readFileIntoString(m_filterJSON);
+
+    if (json.empty())
+        json = m_filterJSON;
+
+    Json::Reader jsonReader;
+    Json::Value filters;
+    jsonReader.parse(json, filters);
+    if (filters.type() != Json::arrayValue || filters.empty())
+        throw pdal_error("JSON must be an array of filter specifications");
+
+    Json::Value pipeline(Json::arrayValue);
+
+    // Add the input file, the filters (as provided) and the output file.
+    if (m_readerType.size())
+    {
+        Json::Value node(Json::objectValue);
+        node["filename"] = m_inputFile;
+        node["type"] = m_readerType;
+        pipeline.append(node);
+    }
+    else
+        pipeline.append(Json::Value(m_inputFile));
+    for (Json::ArrayIndex i = 0; i < filters.size(); ++i)
+        pipeline.append(filters[i]);
+    if (m_writerType.size())
+    {
+        Json::Value node(Json::objectValue);
+        node["filename"] = m_outputFile;
+        node["type"] = m_writerType;
+        pipeline.append(node);
+    }
+    else
+        pipeline.append(Json::Value(m_outputFile));
+
+    Json::Value root;
+    root["pipeline"] = pipeline;
+
+    std::stringstream pipeline_str;
+    pipeline_str << root;
+    m_manager.readPipeline(pipeline_str);
+}
+
+
+/*
+  Build a pipeline from filters specified as command-line arguments.
+*/
+void TranslateKernel::makeArgPipeline()
+{
+    Stage& reader = m_manager.makeReader(m_inputFile, m_readerType);
+    Stage* stage = &reader;
+
+    // add each filter provided on the command-line,
+    // updating the stage pointer
+    for (auto const f : m_filterType)
+    {
+        std::string filter_name(f);
+
+        if (!Utils::startsWith(f, "filters."))
+            filter_name.insert(0, "filters.");
+
+        Stage& filter = m_manager.makeFilter(filter_name, *stage);
+        stage = &filter;
+    }
+    m_manager.makeWriter(m_outputFile, m_writerType, *stage);
+}
+
+
+int TranslateKernel::execute()
+{
+    std::ostream *metaOut(nullptr);
+
+    if (m_metadataFile.size())
+    {
+        metaOut = FileUtils::createFile(m_metadataFile);
+        if (! metaOut)
+            throw pdal_error("Couldn't output metadata output file '" +
+                m_metadataFile + "'.");
+    }
+
+    if (m_filterJSON.size() && m_filterType.size())
+        throw pdal_error("Cannot set both --filter options and --json options");
+
+    if (!m_filterJSON.empty())
+        makeJSONPipeline();
+    else
+        makeArgPipeline();
+
+    if (m_pipelineOutput.size() > 0)
+        PipelineWriter::writePipeline(m_manager.getStage(), m_pipelineOutput);
+    m_manager.execute();
+    if (metaOut)
+    {
+        MetadataNode m = m_manager.getMetadata();
+        *metaOut << Utils::toJSON(m);
+        FileUtils::closeFile(metaOut);
+    }
+
+    return 0;
+}
+
+} // namespace pdal
diff --git a/kernels/TranslateKernel.hpp b/kernels/TranslateKernel.hpp
new file mode 100644
index 0000000..f51cf8f
--- /dev/null
+++ b/kernels/TranslateKernel.hpp
@@ -0,0 +1,77 @@
+/******************************************************************************
+* Copyright (c) 2013, Howard Butler (hobu.inc at gmail.com)
+* Copyright (c) 2015, Brad Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Kernel.hpp>
+#include <pdal/PipelineManager.hpp>
+#include <pdal/pdal_export.hpp>
+#include <pdal/plugin.hpp>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+extern "C" int32_t TranslateKernel_ExitFunc();
+extern "C" PF_ExitFunc TranslateKernel_InitPlugin();
+
+namespace pdal
+{
+
+class PDAL_DLL TranslateKernel : public Kernel
+{
+public:
+    static void * create();
+    static int32_t destroy(void *);
+    std::string getName() const;
+    int execute();
+
+private:
+    TranslateKernel();
+    virtual void addSwitches(ProgramArgs& args);
+    void makeJSONPipeline();
+    void makeArgPipeline();
+
+    std::string m_inputFile;
+    std::string m_outputFile;
+    std::string m_pipelineOutput;
+    std::string m_readerType;
+    StringList m_filterType;
+    std::string m_writerType;
+    std::string m_filterJSON;
+    std::string m_metadataFile;
+};
+
+} // namespace pdal
diff --git a/kernels/delta/CMakeLists.txt b/kernels/delta/CMakeLists.txt
deleted file mode 100644
index 7ce7714..0000000
--- a/kernels/delta/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Delta kernel CMake configuration
-#
-
-#
-# Delta Kernel
-#
-set(srcs
-    DeltaKernel.cpp
-)
-
-set(incs
-    DeltaKernel.hpp
-)
-
-PDAL_ADD_DRIVER(kernel delta "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/kernels/delta/DeltaKernel.cpp b/kernels/delta/DeltaKernel.cpp
deleted file mode 100644
index 5883e81..0000000
--- a/kernels/delta/DeltaKernel.cpp
+++ /dev/null
@@ -1,219 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "DeltaKernel.hpp"
-
-#include <pdal/PDALUtils.hpp>
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.delta",
-    "Delta Kernel",
-    "http://pdal.io/kernels/kernels.delta.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, DeltaKernel, Kernel, s_info)
-
-std::string DeltaKernel::getName() const { return s_info.name; }
-
-DeltaKernel::DeltaKernel() : m_3d(true), m_detail(false), m_allDims(false)
-{}
-
-
-void DeltaKernel::addSwitches(ProgramArgs& args)
-{
-    Arg& src = args.add("source", "source file name", m_sourceFile);
-    src.setPositional();
-    Arg& candidate = args.add("candidate", "candidate file name",
-        m_candidateFile);
-    candidate.setPositional();
-    Arg& output = args.add("output", "output file name", m_outputFile);
-    output.setPositional();
-    args.add("2d", "only 2D comparisons/indexing", m_3d, true);
-    args.add("detail", "Output deltas per-point", m_detail);
-    args.add("alldims", "Compute diffs for all dimensions (not just X,Y,Z)",
-        m_allDims);
-}
-
-
-PointViewPtr DeltaKernel::loadSet(const std::string& filename,
-    PointTable& table)
-{
-    Stage& reader = makeReader(filename, m_driverOverride);
-    reader.prepare(table);
-    PointViewSet viewSet = reader.execute(table);
-    assert(viewSet.size() == 1);
-    return *viewSet.begin();
-}
-
-
-int DeltaKernel::execute()
-{
-    PointTable srcTable;
-    PointTable candTable;
-    DimIndexMap dims;
-
-    PointViewPtr srcView = loadSet(m_sourceFile, srcTable);
-    PointViewPtr candView = loadSet(m_candidateFile, candTable);
-
-    PointLayoutPtr srcLayout = srcTable.layout();
-    PointLayoutPtr candLayout = candTable.layout();
-
-    Dimension::IdList ids = srcLayout->dims();
-    for (Dimension::Id dim : ids)
-    {
-        std::string name = srcLayout->dimName(dim);
-        if (!m_allDims)
-            if (name != "X" && name != "Y" && name != "Z")
-                continue;
-        DimIndex d;
-        d.m_name = name;
-        d.m_srcId = dim;
-        dims[name] = d;
-    }
-    ids = candLayout->dims();
-    for (Dimension::Id dim : ids)
-    {
-        std::string name = candLayout->dimName(dim);
-        auto di = dims.find(name);
-        if (di == dims.end())
-            continue;
-        DimIndex& d = di->second;
-        d.m_candId = dim;
-    }
-
-    // Remove dimensions that aren't in both the source and candidate lists.
-    for (auto di = dims.begin(); di != dims.end();)
-    {
-        DimIndex& d = di->second;
-        if (d.m_candId == Dimension::Id::Unknown)
-            dims.erase(di++);
-        else
-            ++di;
-    }
-
-    // Index the candidate data.
-    KD3Index index(*candView);
-    index.build();
-
-    MetadataNode root;
-
-    if (m_detail)
-        root = dumpDetail(srcView, candView, index, dims);
-    else
-        root = dump(srcView, candView, index, dims);
-    Utils::toJSON(root, std::cout);
-
-    return 0;
-}
-
-
-MetadataNode DeltaKernel::dump(PointViewPtr& srcView, PointViewPtr& candView,
-    KD3Index& index, DimIndexMap& dims)
-{
-    MetadataNode root;
-
-    for (PointId id = 0; id < srcView->size(); ++id)
-    {
-        double x = srcView->getFieldAs<double>(Dimension::Id::X, id);
-        double y = srcView->getFieldAs<double>(Dimension::Id::Y, id);
-        double z = srcView->getFieldAs<double>(Dimension::Id::Z, id);
-
-        PointId candId = index.neighbor(x, y, z);
-
-        // It may be faster to put in a special case to avoid having to
-        // fetch X, Y and Z, more than once but this is simpler and
-        // I'm thinking in most cases it will make no practical difference.
-        for (auto di = dims.begin(); di != dims.end(); ++di)
-        {
-            DimIndex& d = di->second;
-            double sv = srcView->getFieldAs<double>(d.m_srcId, id);
-            double cv = candView->getFieldAs<double>(d.m_candId, candId);
-            accumulate(d, sv - cv);
-        }
-    }
-
-    root.add("source", m_sourceFile);
-    root.add("candidate", m_candidateFile);
-    for (auto dpair : dims)
-    {
-        DimIndex& d = dpair.second;
-
-        MetadataNode dimNode = root.add(d.m_name);
-        dimNode.add("min", d.m_min);
-        dimNode.add("max", d.m_max);
-        dimNode.add("mean", d.m_avg);
-    }
-    return root;
-}
-
-
-void DeltaKernel::accumulate(DimIndex& d, double v)
-{
-    d.m_cnt++;
-    d.m_min = std::min(v, d.m_min);
-    d.m_max = std::max(v, d.m_max);
-    d.m_avg += (v - d.m_avg) / d.m_cnt;
-}
-
-
-MetadataNode DeltaKernel::dumpDetail(PointViewPtr& srcView,
-    PointViewPtr& candView, KD3Index& index, DimIndexMap& dims)
-{
-    MetadataNode root;
-
-    for (PointId id = 0; id < srcView->size(); ++id)
-    {
-        double x = srcView->getFieldAs<double>(Dimension::Id::X, id);
-        double y = srcView->getFieldAs<double>(Dimension::Id::Y, id);
-        double z = srcView->getFieldAs<double>(Dimension::Id::Z, id);
-        PointId candId = index.neighbor(x, y, z);
-
-        MetadataNode delta = root.add("delta");
-        delta.add("i", id);
-        for (auto di = dims.begin(); di != dims.end(); ++di)
-        {
-            DimIndex& d = di->second;
-            double sv = srcView->getFieldAs<double>(d.m_srcId, id);
-            double cv = candView->getFieldAs<double>(d.m_candId, candId);
-
-            delta.add(d.m_name, sv - cv);
-        }
-    }
-    return root;
-}
-
-} // pdal
diff --git a/kernels/diff/CMakeLists.txt b/kernels/diff/CMakeLists.txt
deleted file mode 100644
index 6869c27..0000000
--- a/kernels/diff/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Diff kernel CMake configuration
-#
-
-#
-# Diff Kernel
-#
-set(srcs
-    DiffKernel.cpp
-)
-
-set(incs
-    DiffKernel.hpp
-)
-
-PDAL_ADD_DRIVER(kernel diff "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/kernels/diff/DiffKernel.cpp b/kernels/diff/DiffKernel.cpp
deleted file mode 100644
index 0c1e9e3..0000000
--- a/kernels/diff/DiffKernel.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "DiffKernel.hpp"
-
-#include <memory>
-
-#include <pdal/PDALUtils.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/pdal_macros.hpp>
-
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.diff",
-    "Diff Kernel",
-    "http://pdal.io/kernels/kernels.diff.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, DiffKernel, Kernel, s_info)
-
-std::string DiffKernel::getName() const { return s_info.name; }
-
-void DiffKernel::addSwitches(ProgramArgs& args)
-{
-    Arg& source = args.add("source", "Source filename", m_sourceFile);
-    source.setPositional();
-    Arg& candidate = args.add("candidate", "Candidate filename",
-        m_candidateFile);
-    candidate.setPositional();
-}
-
-
-void DiffKernel::checkPoints(const PointView& source_data,
-    const PointView& candidate_data, MetadataNode errors)
-{
-    uint32_t MAX_BADBYTES(20);
-    uint32_t badbytes(0);
-
-    // Both schemas have already been determined to be equal, so are the
-    // same size and in the same order.
-    Dimension::IdList const& sourceDims = source_data.dims();
-    Dimension::IdList const& candidateDims = candidate_data.dims();
-
-    char sbuf[8];
-    char cbuf[8];
-    for (PointId idx = 0; idx < source_data.size(); ++idx)
-    {
-        for (size_t d = 0; d < sourceDims.size(); ++d)
-        {
-            Dimension::Id sd = sourceDims[d];
-            Dimension::Id cd = candidateDims[d];
-
-            source_data.getRawField(sd, idx, (void *)sbuf);
-            candidate_data.getRawField(cd, idx, (void *)cbuf);
-            Dimension::Type t = Dimension::defaultType(cd);
-            size_t size = Dimension::size(t);
-            if (memcmp(sbuf, cbuf, size))
-            {
-                std::ostringstream oss;
-
-                oss << "Point " << idx << " differs for dimension \"" <<
-                    Dimension::name(sd) << "\" for source and candidate";
-                errors.add("data.error", oss.str());
-                badbytes++;
-            }
-        }
-        if (badbytes > MAX_BADBYTES )
-            break;
-    }
-}
-
-
-int DiffKernel::execute()
-{
-    PointTable sourceTable;
-
-    Stage& source = makeReader(m_sourceFile, m_driverOverride);
-    source.prepare(sourceTable);
-    PointViewSet sourceSet = source.execute(sourceTable);
-
-    MetadataNode errors;
-
-    PointTable candidateTable;
-
-    Stage& candidate = makeReader(m_candidateFile, m_driverOverride);
-    candidate.prepare(candidateTable);
-    PointViewSet candidateSet = candidate.execute(candidateTable);
-
-    assert(sourceSet.size() == 1);
-    assert(candidateSet.size() == 1);
-    PointViewPtr sourceView = *sourceSet.begin();
-    PointViewPtr candidateView = *candidateSet.begin();
-    if (candidateView->size() != sourceView->size())
-    {
-        std::ostringstream oss;
-
-        oss << "Source and candidate files do not have the same point count";
-        errors.add("count.error", oss.str());
-        errors.add("count.candidate", candidateView->size());
-        errors.add("count.source", sourceView->size());
-    }
-
-    MetadataNode source_metadata = sourceTable.metadata();
-    MetadataNode candidate_metadata = candidateTable.metadata();
-    if (source_metadata != candidate_metadata)
-    {
-        std::ostringstream oss;
-
-        oss << "Source and candidate files do not have the same metadata count";
-        errors.add("metadata.error", oss.str());
-        errors.add(source_metadata);
-        errors.add(candidate_metadata);
-    }
-
-    if (candidateTable.layout()->dims().size() !=
-        sourceTable.layout()->dims().size())
-    {
-        std::ostringstream oss;
-
-        oss << "Source and candidate files do not have the same "
-            "number of dimensions";
-    }
-
-    return 0;
-}
-
-} // namespace pdal
-
diff --git a/kernels/info/CMakeLists.txt b/kernels/info/CMakeLists.txt
deleted file mode 100644
index a40b34a..0000000
--- a/kernels/info/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Info kernel CMake configuration
-#
-
-#
-# Info Kernel
-#
-set(srcs
-    InfoKernel.cpp
-)
-
-set(incs
-    InfoKernel.hpp
-)
-
-PDAL_ADD_DRIVER(kernel info "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/kernels/info/InfoKernel.cpp b/kernels/info/InfoKernel.cpp
deleted file mode 100644
index 5ed58d0..0000000
--- a/kernels/info/InfoKernel.cpp
+++ /dev/null
@@ -1,460 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2013, Howard Butler (hobu.inc at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "InfoKernel.hpp"
-
-#include <algorithm>
-
-#include <pdal/KDIndex.hpp>
-#include <pdal/PipelineWriter.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/pdal_config.hpp>
-#include <pdal/StageFactory.hpp>
-#ifdef PDAL_HAVE_LIBXML2
-#include <pdal/XMLSchema.hpp>
-#endif
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.info",
-    "Info Kernel",
-    "http://pdal.io/kernels/kernels.info.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, InfoKernel, Kernel, s_info)
-
-std::string InfoKernel::getName() const { return s_info.name; }
-
-InfoKernel::InfoKernel()
-    : m_showStats(false)
-    , m_showSchema(false)
-    , m_showAll(false)
-    , m_showMetadata(false)
-    , m_boundary(false)
-    , m_showSummary(false)
-    , m_needPoints(false)
-    , m_statsStage(NULL)
-{}
-
-
-void InfoKernel::validateSwitches(ProgramArgs& args)
-{
-    int functions = 0;
-
-    if (!m_usestdin && m_inputFile.empty())
-        throw pdal_error("No input file specified.");
-
-    // All isn't really all.
-    if (m_showAll)
-    {
-        m_showStats = true;
-        m_showMetadata = true;
-        m_showSchema = true;
-    }
-
-    if (m_boundary)
-    {
-        functions++;
-        m_needPoints = true;
-    }
-    if (m_queryPoint.size())
-    {
-        functions++;
-        m_needPoints = true;
-    }
-    if (m_pointIndexes.size())
-    {
-        functions++;
-        m_needPoints = true;
-    }
-    if (m_showSchema)
-        functions++;
-    if (m_showMetadata)
-        functions++;
-    if (m_showSummary)
-        functions++;
-    if (m_showStats || functions == 0 )
-    {
-        functions++;
-        m_showStats = true;
-        m_needPoints = true;
-    }
-
-    if (m_pointIndexes.size() && m_queryPoint.size())
-        throw pdal_error("--point option incompatible with --query option.");
-
-    if (m_showSummary && functions > 1)
-        throw pdal_error("--summary option incompatible with other "
-            "specified options.");
-}
-
-
-void InfoKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("input,i", "input file name", m_inputFile).setOptionalPositional();
-    args.add("all", "dump statistics, schema and metadata", m_showAll);
-    args.add("point,p", "point to dump\n--point=\"1-5,10,100-200\"",
-        m_pointIndexes);
-    args.add("query",
-         "Return points in order of distance from the specified "
-         "location (2D or 3D)\n"
-         "--query Xcoord,Ycoord[,Zcoord][/count]",
-         m_queryPoint);
-    args.add("stats", "dump stats on all points (reads entire dataset)",
-        m_showStats);
-    args.add("boundary", "compute a hexagonal hull/boundary of dataset",
-        m_boundary);
-    args.add("dimensions", "dimensions on which to compute statistics",
-        m_dimensions);
-    args.add("schema", "dump the schema", m_showSchema);
-    args.add("pipeline-serialization", "Output file for pipeline serialization",
-         m_pipelineFile);
-    args.add("summary", "dump summary of the info", m_showSummary);
-    args.add("metadata", "dump file metadata info", m_showMetadata);
-    args.add("pointcloudschema", "dump PointCloudSchema XML output",
-        m_PointCloudSchemaOutput).setHidden();
-    args.add("stdin,s", "Read a pipeline file from standard input", m_usestdin);
-}
-
-// Support for parsing point numbers.  Points can be specified singly or as
-// dash-separated ranges.  i.e. 6-7,8,19-20
-namespace {
-
-using namespace std;
-
-uint32_t parseInt(const string& s)
-{
-    uint32_t i;
-
-    if (!Utils::fromString(s, i))
-        throw pdal_error(string("Invalid integer: ") + s);
-    return i;
-}
-
-
-void addRange(const string& begin, const string& end, vector<PointId>& points)
-{
-    PointId low = parseInt(begin);
-    PointId high = parseInt(end);
-    if (low > high)
-        throw pdal_error(string("Range invalid: ") + begin + "-" + end);
-    while (low <= high)
-        points.push_back(low++);
-}
-
-
-vector<PointId> getListOfPoints(std::string p)
-{
-    vector<PointId> output;
-
-    //Remove whitespace from string with awful remove/erase idiom.
-    p.erase(remove_if(p.begin(), p.end(), ::isspace), p.end());
-
-    vector<string> ranges = Utils::split2(p, ',');
-    for (string s : ranges)
-    {
-        vector<string> limits = Utils::split(s, '-');
-        if (limits.size() == 1)
-            output.push_back(parseInt(limits[0]));
-        else if (limits.size() == 2)
-            addRange(limits[0], limits[1], output);
-        else
-            throw pdal_error(string("Invalid point range: ") + s);
-    }
-    return output;
-}
-
-} //namespace
-
-MetadataNode InfoKernel::dumpPoints(PointViewPtr inView) const
-{
-    MetadataNode root;
-    PointViewPtr outView = inView->makeNew();
-
-    // Stick points in a inViewfer.
-    std::vector<PointId> points = getListOfPoints(m_pointIndexes);
-    for (size_t i = 0; i < points.size(); ++i)
-    {
-        PointId id = (PointId)points[i];
-        if (id < inView->size())
-            outView->appendPoint(*inView.get(), id);
-    }
-
-    MetadataNode tree = outView->toMetadata();
-    std::string prefix("point ");
-    for (size_t i = 0; i < outView->size(); ++i)
-    {
-        MetadataNode n = tree.findChild(std::to_string(i));
-        n.add("PointId", points[i]);
-        root.add(n.clone("point"));
-    }
-    return root;
-}
-
-
-MetadataNode InfoKernel::dumpSummary(const QuickInfo& qi)
-{
-    MetadataNode summary;
-    summary.add("num_points", qi.m_pointCount);
-    summary.add("spatial_reference", qi.m_srs.getWKT());
-    MetadataNode srs = qi.m_srs.toMetadata();
-    summary.add(srs);
-    MetadataNode bounds = summary.add("bounds");
-    MetadataNode x = bounds.add("X");
-    x.add("min", qi.m_bounds.minx);
-    x.add("max", qi.m_bounds.maxx);
-    MetadataNode y = bounds.add("Y");
-    y.add("min", qi.m_bounds.miny);
-    y.add("max", qi.m_bounds.maxy);
-    MetadataNode z = bounds.add("Z");
-    z.add("min", qi.m_bounds.minz);
-    z.add("max", qi.m_bounds.maxz);
-
-    std::string dims;
-    auto di = qi.m_dimNames.begin();
-    while (di != qi.m_dimNames.end())
-    {
-        dims += *di;
-        ++di;
-        if (di != qi.m_dimNames.end())
-           dims += ", ";
-    }
-    summary.add("dimensions", dims);
-    return summary;
-}
-
-
-void InfoKernel::makePipeline(const std::string& filename, bool noPoints)
-{
-    if (!pdal::Utils::fileExists(filename))
-        throw pdal_error("File not found: " + filename);
-
-    if (filename == "STDIN")
-    {
-        m_manager.readPipeline(std::cin);
-    }
-    else if (FileUtils::extension(filename) == ".xml" ||
-        FileUtils::extension(filename) == ".json")
-    {
-        m_manager.readPipeline(filename);
-    }
-    else
-    {
-        Options ops;
-        if (noPoints)
-            ops.add("count", 0);
-        Stage& reader = m_manager.makeReader(filename, m_driverOverride, ops);
-        m_reader = &reader;
-    }
-}
-
-
-void InfoKernel::setup(const std::string& filename)
-{
-    makePipeline(filename, !m_needPoints);
-
-    Stage *stage = m_reader;
-    if (m_showStats)
-    {
-        Options filterOptions;
-        if (m_dimensions.size())
-            filterOptions.add({"dimensions", m_dimensions});
-        m_statsStage = &m_manager.makeFilter("filters.stats", *stage,
-            filterOptions);
-        stage = m_statsStage;
-    }
-    if (m_boundary)
-        m_hexbinStage = &m_manager.makeFilter("filters.hexbin", *stage);
-}
-
-
-MetadataNode InfoKernel::run(const std::string& filename)
-{
-    MetadataNode root;
-
-    root.add("filename", filename);
-    if (m_showSummary)
-    {
-        QuickInfo qi = m_reader->preview();
-        MetadataNode summary = dumpSummary(qi).clone("summary");
-        root.add(summary);
-    }
-    else
-    {
-        if (m_needPoints || m_showMetadata)
-            m_manager.execute();
-        else
-            m_manager.prepare();
-        dump(root);
-    }
-    root.add("pdal_version", pdal::GetFullVersionString());
-    return root;
-}
-
-
-void InfoKernel::dump(MetadataNode& root)
-{
-    if (m_showSchema)
-        root.add(m_manager.pointTable().toMetadata().clone("schema"));
-
-    if (m_PointCloudSchemaOutput.size() > 0)
-    {
-#ifdef PDAL_HAVE_LIBXML2
-        XMLSchema schema(m_manager.pointTable().layout());
-
-        std::ostream *out = Utils::createFile(m_PointCloudSchemaOutput);
-        std::string xml(schema.xml());
-        out->write(xml.c_str(), xml.size());
-        Utils::closeFile(out);
-#else
-        std::cerr << "libxml2 support not enabled, no schema is produced" <<
-            std::endl;
-#endif
-
-    }
-    if (m_showStats)
-        root.add(m_statsStage->getMetadata().clone("stats"));
-
-    if (m_pipelineFile.size() > 0)
-        PipelineWriter::writePipeline(m_manager.getStage(), m_pipelineFile);
-
-    if (m_pointIndexes.size())
-    {
-        PointViewSet viewSet = m_manager.views();
-        assert(viewSet.size() == 1);
-        root.add(dumpPoints(*viewSet.begin()).clone("points"));
-    }
-
-    if (m_queryPoint.size())
-    {
-        PointViewSet viewSet = m_manager.views();
-        assert(viewSet.size() == 1);
-        root.add(dumpQuery(*viewSet.begin()));
-    }
-
-    if (m_showMetadata)
-    {
-        // If we have a reader cached, this means we
-        // weren't reading a pipeline file directly. In that
-        // case, use the metadata from the reader (old behavior).
-        // Otherwise, return the full metadata of the entire pipeline
-        if (m_reader)
-            root.add(m_reader->getMetadata().clone("metadata"));
-        else
-            root.add(m_manager.getMetadata().clone("metadata"));
-    }
-
-    if (m_boundary)
-    {
-        PointViewSet viewSet = m_manager.views();
-        assert(viewSet.size() == 1);
-        root.add(m_hexbinStage->getMetadata().clone("boundary"));
-    }
-}
-
-
-MetadataNode InfoKernel::dumpQuery(PointViewPtr inView) const
-{
-    int count;
-    std::string location;
-
-    // See if there's a provided point count.
-    StringList parts = Utils::split2(m_queryPoint, '/');
-    if (parts.size() == 2)
-    {
-        location = parts[0];
-        count = atoi(parts[1].c_str());
-    }
-    else if (parts.size() == 1)
-    {
-        location = parts[0];
-        count = inView->size();
-    }
-    else
-        count = 0;
-    if (count == 0)
-        throw pdal_error("Invalid location specificiation. "
-            "--query=\"X,Y[/count]\"");
-
-    auto seps = [](char c){ return (c == ',' || c == '|' || c == ' '); };
-
-    std::vector<std::string> tokens = Utils::split2(location, seps);
-    std::vector<double> values;
-    for (auto ti = tokens.begin(); ti != tokens.end(); ++ti)
-    {
-        double d;
-        if (Utils::fromString(*ti, d))
-            values.push_back(d);
-    }
-
-    if (values.size() != 2 && values.size() != 3)
-        throw pdal_error("--points must be two or three values");
-
-    PointViewPtr outView = inView->makeNew();
-
-    std::vector<PointId> ids;
-    if (values.size() >= 3)
-    {
-        KD3Index kdi(*inView);
-        kdi.build();
-        ids = kdi.neighbors(values[0], values[1], values[2], count);
-    }
-    else
-    {
-        KD2Index kdi(*inView);
-        kdi.build();
-        ids = kdi.neighbors(values[0], values[1], count);
-    }
-
-    for (auto i = ids.begin(); i != ids.end(); ++i)
-        outView->appendPoint(*inView.get(), *i);
-
-    return outView->toMetadata();
-}
-
-
-int InfoKernel::execute()
-{
-    std::string filename = m_usestdin ? std::string("STDIN") : m_inputFile;
-    setup(filename);
-    MetadataNode root = run(filename);
-    Utils::toJSON(root, std::cout);
-
-    return 0;
-}
-
-
-} // namespace pdal
diff --git a/kernels/merge/CMakeLists.txt b/kernels/merge/CMakeLists.txt
deleted file mode 100644
index b48f257..0000000
--- a/kernels/merge/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Merge kernel CMake configuration
-#
-
-#
-# Merge Kernel
-#
-set(srcs
-    MergeKernel.cpp
-)
-
-set(incs
-    MergeKernel.hpp
-)
-
-PDAL_ADD_DRIVER(kernel merge "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/kernels/merge/MergeKernel.cpp b/kernels/merge/MergeKernel.cpp
deleted file mode 100644
index ae57e0d..0000000
--- a/kernels/merge/MergeKernel.cpp
+++ /dev/null
@@ -1,91 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Hobu Inc. (info at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "MergeKernel.hpp"
-
-#include <merge/MergeFilter.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.merge",
-    "Merge Kernel",
-    "http://pdal.io/kernels/kernels.merge.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, MergeKernel, Kernel, s_info)
-
-std::string MergeKernel::getName() const
-{
-    return s_info.name;
-}
-
-
-void MergeKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("files,f", "input/output files", m_files).setPositional();
-}
-
-
-void MergeKernel::validateSwitches(ProgramArgs& args)
-{
-    if (m_files.size() < 2)
-        throw pdal_error("Must specify an input and output file.");
-    m_outputFile = m_files.back();
-    m_files.resize(m_files.size() - 1);
-}
-
-
-int MergeKernel::execute()
-{
-    PointTable table;
-
-    MergeFilter filter;
-
-    for (size_t i = 0; i < m_files.size(); ++i)
-    {
-        Stage& reader = makeReader(m_files[i], m_driverOverride);
-        filter.setInput(reader);
-    }
-
-    Stage& writer = makeWriter(m_outputFile, filter, "");
-    writer.prepare(table);
-    writer.execute(table);
-    return 0;
-}
-
-} // namespace pdal
-
diff --git a/kernels/pipeline/CMakeLists.txt b/kernels/pipeline/CMakeLists.txt
deleted file mode 100644
index 0297059..0000000
--- a/kernels/pipeline/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Pipeline kernel CMake configuration
-#
-
-#
-# Pipeline Kernel
-#
-set(srcs
-    PipelineKernel.cpp
-)
-
-set(incs
-    PipelineKernel.hpp
-)
-
-PDAL_ADD_DRIVER(kernel pipeline "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/kernels/pipeline/PipelineKernel.cpp b/kernels/pipeline/PipelineKernel.cpp
deleted file mode 100644
index fdd681e..0000000
--- a/kernels/pipeline/PipelineKernel.cpp
+++ /dev/null
@@ -1,119 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "PipelineKernel.hpp"
-
-#ifdef PDAL_HAVE_LIBXML2
-#include <pdal/XMLSchema.hpp>
-#endif
-
-#include <pdal/PDALUtils.hpp>
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.pipeline",
-    "Pipeline Kernel",
-    "http://pdal.io/kernels/kernels.pipeline.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, PipelineKernel, Kernel, s_info)
-
-std::string PipelineKernel::getName() const { return s_info.name; }
-
-PipelineKernel::PipelineKernel() : m_validate(false), m_progressFd(-1)
-{}
-
-
-void PipelineKernel::validateSwitches(ProgramArgs& args)
-{
-    if (m_usestdin)
-        m_inputFile = "STDIN";
-
-    if (m_inputFile.empty())
-        throw pdal_error("Input filename required.");
-}
-
-
-void PipelineKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("input,i", "input file name", m_inputFile).setOptionalPositional();
-    args.add("pipeline-serialization", "Output file for pipeline serialization",
-        m_pipelineFile);
-    args.add("validate", "Validate the pipeline (including serialization), "
-        "but do not write points", m_validate);
-    args.add("progress",
-        "Name of file or FIFO to which stages should write progress "
-        "information.  The file/FIFO must exist.  PDAL will not create "
-        "the progress file.",
-        m_progressFile);
-    args.add("pointcloudschema", "dump PointCloudSchema XML output",
-        m_PointCloudSchemaOutput).setHidden();
-    args.add("stdin,s", "Read pipeline from standard input", m_usestdin);
-}
-
-int PipelineKernel::execute()
-{
-    if (!Utils::fileExists(m_inputFile))
-        throw pdal_error("file not found: " + m_inputFile);
-    if (m_progressFile.size())
-        m_progressFd = Utils::openProgress(m_progressFile);
-
-    m_manager.readPipeline(m_inputFile);
-    m_manager.execute();
-
-    if (m_pipelineFile.size() > 0)
-        PipelineWriter::writePipeline(m_manager.getStage(), m_pipelineFile);
-
-    if (m_PointCloudSchemaOutput.size() > 0)
-    {
-#ifdef PDAL_HAVE_LIBXML2
-        XMLSchema schema(m_manager.pointTable().layout());
-
-        std::ostream *out = Utils::createFile(m_PointCloudSchemaOutput);
-        std::string xml(schema.xml());
-        out->write(xml.c_str(), xml.size());
-        Utils::closeFile(out);
-#else
-        std::cerr << "libxml2 support not available, no schema is produced" <<
-            std::endl;
-#endif
-
-    }
-    Utils::closeProgress(m_progressFd);
-    return 0;
-}
-
-} // pdal
diff --git a/kernels/random/CMakeLists.txt b/kernels/random/CMakeLists.txt
deleted file mode 100644
index c240b3c..0000000
--- a/kernels/random/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Random kernel CMake configuration
-#
-
-#
-# Random Kernel
-#
-set(srcs
-    RandomKernel.cpp
-)
-
-set(incs
-    RandomKernel.hpp
-)
-
-PDAL_ADD_DRIVER(kernel random "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/kernels/random/RandomKernel.cpp b/kernels/random/RandomKernel.cpp
deleted file mode 100644
index a883c51..0000000
--- a/kernels/random/RandomKernel.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Brad Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "RandomKernel.hpp"
-
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.random",
-    "Random Kernel",
-    "http://pdal.io/kernels/kernels.random.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, RandomKernel, Kernel, s_info)
-
-std::string RandomKernel::getName() const { return s_info.name; }
-
-RandomKernel::RandomKernel()
-    : m_bCompress(false)
-    , m_numPointsToWrite(0)
-    , m_distribution("uniform")
-{
-}
-
-
-void RandomKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("output,o", "Output file name", m_outputFile).setPositional();
-    args.add("compress,z",
-        "Compress output data (if supported by output format)", m_bCompress);
-    args.add("count", "How many points should we write?", m_numPointsToWrite);
-    args.add("bounds", "Extent (in XYZ to clip output to)", m_bounds);
-    args.add("mean", "A comma-separated or quoted, space-separated list "
-        "of means (normal mode): \n--mean 0.0,0.0,0.0\n--mean \"0.0 0.0 0.0\"",
-        m_means);
-    args.add("stdev", "A comma-separated or quoted, space-separated list "
-        "of standard deviations (normal mode): \n"
-        "--stdev 0.0,0.0,0.0\n--stdev \"0.0 0.0 0.0\"", m_stdevs);
-    args.add("distribution", "Distribution (uniform / normal)", m_distribution,
-        "uniform");
-}
-
-
-int RandomKernel::execute()
-{
-    Options readerOptions;
-
-    if (!m_bounds.empty())
-        readerOptions.add("bounds", m_bounds);
-
-    std::string distribution(Utils::tolower(m_distribution));
-    if (distribution == "uniform")
-        readerOptions.add("mode", "uniform");
-    else if (distribution == "normal")
-        readerOptions.add("mode", "normal");
-    else if (distribution == "random")
-        readerOptions.add("mode", "random");
-    else
-        throw pdal_error("invalid distribution: " + m_distribution);
-    readerOptions.add("count", m_numPointsToWrite);
-    Stage& reader = makeReader("", "readers.faux", readerOptions);
-
-    Options writerOptions;
-    if (m_bCompress)
-        writerOptions.add("compression", true);
-    Stage& writer = makeWriter(m_outputFile, reader, "", writerOptions);
-
-    PointTable table;
-    writer.prepare(table);
-    PointViewSet viewSet = writer.execute(table);
-
-    if (isVisualize())
-        visualize(*viewSet.begin());
-
-    return 0;
-}
-
-} // pdal
-
diff --git a/kernels/sort/CMakeLists.txt b/kernels/sort/CMakeLists.txt
deleted file mode 100644
index 9c5ed71..0000000
--- a/kernels/sort/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Sort kernel CMake configuration
-#
-
-#
-# Sort Kernel
-#
-set(srcs
-    SortKernel.cpp
-)
-
-set(incs
-    SortKernel.hpp
-)
-
-PDAL_ADD_DRIVER(kernel sort "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/kernels/sort/SortKernel.cpp b/kernels/sort/SortKernel.cpp
deleted file mode 100644
index 635f8ee..0000000
--- a/kernels/sort/SortKernel.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "SortKernel.hpp"
-
-#include <buffer/BufferReader.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.sort",
-    "Sort Kernel",
-    "http://pdal.io/kernels/kernels.sort.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, SortKernel, Kernel, s_info)
-
-std::string SortKernel::getName() const
-{
-    return s_info.name;
-}
-
-
-SortKernel::SortKernel() : m_bCompress(false), m_bForwardMetadata(false)
-{}
-
-
-void SortKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("input,i", "Input filename", m_inputFile).setPositional();
-    args.add("output,o", "Output filename", m_outputFile).setPositional();
-    args.add("compress,z",
-        "Compress output data (if supported by output format)", m_bCompress);
-    args.add("metadata,m",
-        "Forward metadata (VLRs, header entries, etc) from previous stages",
-        m_bForwardMetadata);
-}
-
-
-int SortKernel::execute()
-{
-    Stage& readerStage = makeReader(m_inputFile, m_driverOverride);
-
-    // go ahead and prepare/execute on reader stage only to grab input
-    // PointViewSet, this makes the input PointView available to both the
-    // processing pipeline and the visualizer
-    PointTable table;
-    readerStage.prepare(table);
-    PointViewSet viewSetIn = readerStage.execute(table);
-
-    // the input PointViewSet will be used to populate a BufferReader that is
-    // consumed by the processing pipeline
-    PointViewPtr inView = *viewSetIn.begin();
-
-    BufferReader bufferReader;
-    bufferReader.addView(inView);
-
-    Stage& sortStage = makeFilter("filters.mortonorder", bufferReader);
-
-    Options writerOptions;
-    if (m_bCompress)
-        writerOptions.add("compression", true);
-    if (m_bForwardMetadata)
-        writerOptions.add("forward_metadata", true);
-    Stage& writer = makeWriter(m_outputFile, sortStage, "", writerOptions);
-
-    writer.prepare(table);
-
-    // process the data, grabbing the PointViewSet for visualization of the
-    PointViewSet viewSetOut = writer.execute(table);
-
-    if (isVisualize())
-        visualize(*viewSetOut.begin());
-
-    return 0;
-}
-
-} // namespace pdal
-
diff --git a/kernels/split/CMakeLists.txt b/kernels/split/CMakeLists.txt
deleted file mode 100644
index f2b41b9..0000000
--- a/kernels/split/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Split kernel CMake configuration
-#
-
-#
-# Split Kernel
-#
-set(srcs
-    SplitKernel.cpp
-)
-
-set(incs
-    SplitKernel.hpp
-)
-
-PDAL_ADD_DRIVER(kernel split "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/kernels/split/SplitKernel.cpp b/kernels/split/SplitKernel.cpp
deleted file mode 100644
index 816a56d..0000000
--- a/kernels/split/SplitKernel.cpp
+++ /dev/null
@@ -1,139 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "SplitKernel.hpp"
-
-#include <buffer/BufferReader.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.split",
-    "Split Kernel",
-    "http://pdal.io/kernels/kernels.split.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, SplitKernel, Kernel, s_info)
-
-std::string SplitKernel::getName() const
-{
-    return s_info.name;
-}
-
-
-void SplitKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("input,i", "Input filename", m_inputFile).setPositional();
-    args.add("output,o", "Output filename", m_outputFile).setPositional();
-    args.add("length", "Edge length for splitter cells", m_length, 0.0);
-    args.add("capacity", "Point capacity of chipper cells", m_capacity);
-    args.add("origin_x", "Origin in X axis for splitter cells", m_xOrigin,
-        std::numeric_limits<double>::quiet_NaN());
-    args.add("origin_y", "Origin in Y axis for splitter cells", m_yOrigin,
-        std::numeric_limits<double>::quiet_NaN());
-}
-
-
-void SplitKernel::validateSwitches(ProgramArgs& args)
-{
-#ifdef WIN32
-    char pathSeparator = '\\';
-#else
-    char pathSeparator = '/';
-#endif
-
-    if (m_length && m_capacity)
-        throw pdal_error("Can't specify both length and capacity.");
-    if (!m_length && !m_capacity)
-        m_capacity = 100000;
-    if (m_outputFile.back() == pathSeparator)
-        m_outputFile += m_inputFile;
-}
-
-
-namespace
-{
-std::string makeFilename(const std::string& s, int i)
-{
-    std::string out = s;
-    auto pos = out.find_last_of('.');
-    if (pos == out.npos)
-        pos = out.length();
-    out.insert(pos, std::string("_") + std::to_string(i));
-    return out;
-}
-}
-
-
-int SplitKernel::execute()
-{
-    PointTable table;
-
-    Stage& reader = makeReader(m_inputFile, m_driverOverride);
-
-    Options filterOpts;
-    std::string driver = (m_length ? "filters.splitter" : "filters.chipper");
-    if (m_length)
-    {
-        filterOpts.add("length", m_length);
-        filterOpts.add("origin_x", m_xOrigin);
-        filterOpts.add("origin_y", m_yOrigin);
-    }
-    else
-    {
-        filterOpts.add("capacity", m_capacity);
-    }
-    Stage& f = makeFilter(driver, reader, filterOpts);
-    f.prepare(table);
-    PointViewSet pvSet = f.execute(table);
-
-    int filenum = 1;
-    for (auto& pvp : pvSet)
-    {
-        BufferReader reader;
-        reader.addView(pvp);
-
-        std::string filename = makeFilename(m_outputFile, filenum++);
-        Stage& writer = makeWriter(filename, reader, "");
-
-        writer.prepare(table);
-        writer.execute(table);
-    }
-    return 0;
-}
-
-} // namespace pdal
-
diff --git a/kernels/tindex/CMakeLists.txt b/kernels/tindex/CMakeLists.txt
deleted file mode 100644
index 5e5dac4..0000000
--- a/kernels/tindex/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-#
-# TIndex kernel CMake configuration
-#
-
-#
-# TIndex Kernel
-#
-
-set(srcs
-    TIndexKernel.cpp
-)
-
-set(incs
-    TIndexKernel.hpp
-)
-
-PDAL_ADD_DRIVER(kernel tindex "${srcs}" "${incs}" objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/kernels/tindex/TIndexKernel.cpp b/kernels/tindex/TIndexKernel.cpp
deleted file mode 100644
index 19777be..0000000
--- a/kernels/tindex/TIndexKernel.cpp
+++ /dev/null
@@ -1,709 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "TIndexKernel.hpp"
-
-#ifndef WIN32
-#include <glob.h>
-#include <time.h>
-#endif
-
-#include <memory>
-#include <vector>
-
-#include <pdal/KernelFactory.hpp>
-#include <pdal/util/FileUtils.hpp>
-#include <merge/MergeFilter.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-
-#include <cpl_string.h>
-
-namespace
-{
-
-void setDate(OGRFeatureH feature, const tm& tyme, int fieldNumber)
-{
-    OGR_F_SetFieldDateTime(feature, fieldNumber,
-        tyme.tm_year + 1900, tyme.tm_mon + 1, tyme.tm_mday, tyme.tm_hour,
-        tyme.tm_min, tyme.tm_sec, 100);
-}
-
-
-} // anonymous namespace
-
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.tindex",
-    "TIndex Kernel",
-    "http://pdal.io/kernels/kernels.tindex.html" );
-
-CREATE_STATIC_PLUGIN(1, 0, TIndexKernel, Kernel, s_info)
-
-std::string TIndexKernel::getName() const { return s_info.name; }
-
-TIndexKernel::TIndexKernel()
-    : Kernel()
-//ABELL - need to option this.
-    , m_srsColumnName("srs")
-    , m_merge(false)
-    , m_dataset(NULL)
-    , m_layer(NULL)
-    , m_fastBoundary(false)
-
-{}
-
-
-void TIndexKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("tindex", "OGR-readable/writeable tile index output",
-        m_idxFilename).setPositional();
-    args.add("filespec", "Build: Pattern of files to index. "
-        "Merge: Output filename", m_filespec).setPositional();
-    args.add("fast_boundary", "Use extent instead of exact boundary",
-        m_fastBoundary);
-    args.add("lyr_name", "OGR layer name to write into datasource",
-        m_layerName);
-    args.add("tindex_name", "Tile index column name", m_tileIndexColumnName,
-        "location");
-    args.add("ogrdriver,f", "OGR driver name to use ", m_driverName,
-        "ESRI Shapefile");
-    args.add("t_srs", "Target SRS of tile index", m_tgtSrsString,
-        "EPSG:4326");
-    args.add("a_srs", "Assign SRS of tile with no SRS to this value",
-        m_assignSrsString, "EPSG:4326");
-    args.add("bounds", "Extent (in XYZ) to clip output to", m_bounds);
-    args.add("polygon", "Well-known text of polygon to clip output", m_wkt);
-    args.add("write_absolute_path",
-        "Write absolute rather than relative file paths", m_absPath);
-    args.add("merge", "Whether we're merging the entries in a tindex file.",
-        m_merge);
-    args.add("stdin,s", "Read filespec pattern from standard input",
-        m_usestdin);
-}
-
-
-void TIndexKernel::validateSwitches(ProgramArgs& args)
-{
-    if (m_merge)
-    {
-        if (!m_wkt.empty() && !m_bounds.empty())
-            throw pdal_error("Can't specify both 'polygon' and "
-                "'bounds' options.");
-        if (!m_bounds.empty())
-            m_wkt = m_bounds.toWKT();
-        if (m_filespec.empty())
-            throw pdal_error("No output filename provided.");
-        StringList invalidArgs;
-        invalidArgs.push_back("a_srs");
-        invalidArgs.push_back("src_srs_name");
-        for (auto arg : invalidArgs)
-            if (args.set(arg))
-            {
-                std::ostringstream out;
-
-                out << "option '" << arg << "' not supported during merge.";
-                throw pdal_error(out.str());
-            }
-    }
-    else
-    {
-        if (m_filespec.empty() && !m_usestdin)
-            throw pdal_error("No input pattern specified");
-        if (args.set("polygon"))
-            throw pdal_error("'polygon' option not supported when building "
-                "index.");
-        if (args.set("bounds"))
-            throw pdal_error("'bounds' option not supported when building "
-                "index.");
-    }
-}
-
-
-int TIndexKernel::execute()
-{
-    gdal::registerDrivers();
-
-    if (m_merge)
-        mergeFile();
-    else
-    {
-        try
-        {
-            createFile();
-        }
-        catch (pdal_error&)
-        {
-            if (m_dataset)
-                OGR_DS_Destroy(m_dataset);
-            throw;
-        }
-    }
-    return 0;
-}
-
-
-StringList TIndexKernel::glob(std::string& path)
-{
-    StringList filenames;
-
-#ifndef WIN32
-    glob_t glob_result;
-
-    ::glob(path.c_str(), GLOB_TILDE, NULL, &glob_result);
-    for (unsigned int i = 0; i < glob_result.gl_pathc; ++i)
-    {
-        std::string filename = glob_result.gl_pathv[i];
-        if (m_absPath)
-            filename = FileUtils::toAbsolutePath(filename);
-        filenames.push_back(filename);
-    }
-    globfree(&glob_result);
-#endif
-
-    return filenames;
-}
-
-
-StringList readSTDIN()
-{
-    std::string line;
-    StringList output;
-    while (std::getline(std::cin, line))
-    {
-        output.push_back(line);
-    }
-    return output;
-}
-
-
-bool TIndexKernel::isFileIndexed(const FieldIndexes& indexes,
-    const FileInfo& fileInfo)
-{
-    std::ostringstream qstring;
-
-    qstring << Utils::toupper(m_tileIndexColumnName) << "=" <<
-        "'" << fileInfo.m_filename << "'";
-    std::string query = qstring.str();
-    OGRErr err = OGR_L_SetAttributeFilter(m_layer, query.c_str());
-    if (err != OGRERR_NONE)
-    {
-        std::ostringstream oss;
-        oss << "Unable to set attribute filter for file '" <<
-             fileInfo.m_filename << "'";
-        throw pdal_error(oss.str());
-    }
-
-    bool output(false);
-    OGR_L_ResetReading(m_layer);
-    if (OGR_L_GetNextFeature(m_layer))
-        output = true;
-    OGR_L_ResetReading(m_layer);
-    OGR_L_SetAttributeFilter(m_layer, NULL);
-    return output;
-}
-
-
-void TIndexKernel::createFile()
-{
-    if (!m_usestdin)
-        m_files = glob(m_filespec);
-    else
-        m_files = readSTDIN();
-
-    if (m_files.empty())
-    {
-        std::ostringstream out;
-        out << "Couldn't find files to index: " << m_filespec << ".";
-        throw pdal_error(out.str());
-    }
-
-//ABELL - Remove CPLGetBasename use.
-    const std::string filename = m_files.front();
-    if (m_layerName.empty())
-       m_layerName = CPLGetBasename(filename.c_str());
-
-    // Open or create the dataset.
-    if (!openDataset(m_idxFilename))
-        if (!createDataset(m_idxFilename))
-        {
-            std::ostringstream out;
-            out << "Couldn't open or create index dataset file '" <<
-                m_idxFilename << "'.";
-            throw pdal_error(out.str());
-        }
-
-    // Open or create a layer
-    if (!openLayer(m_layerName))
-        if (!createLayer(m_layerName))
-        {
-            std::ostringstream out;
-            out << "Couldn't open or create layer '" << m_layerName <<
-                "' in output file '" << m_idxFilename << "'.";
-            throw pdal_error(out.str());
-        }
-
-    FieldIndexes indexes = getFields();
-
-    KernelFactory factory(false);
-    for (auto f : m_files)
-    {
-        //ABELL - Not sure why we need to get absolute path here.
-        f = FileUtils::toAbsolutePath(f);
-        FileInfo info = getFileInfo(factory, f);
-        if (!isFileIndexed(indexes, info))
-        {
-            if (createFeature(indexes, info))
-                m_log->get(LogLevel::Info) << "Indexed file " << f << std::endl;
-            else
-                m_log->get(LogLevel::Error) << "Failed to create feature for "
-                    "file '" << f << "'" << std::endl;
-
-        }
-    }
-    OGR_DS_Destroy(m_dataset);
-}
-
-
-void TIndexKernel::mergeFile()
-{
-    using namespace gdal;
-
-    std::ostringstream out;
-
-    if (!openDataset(m_idxFilename))
-    {
-        std::ostringstream out;
-        out << "Couldn't open index dataset file '" << m_idxFilename << "'.";
-        throw pdal_error(out.str());
-    }
-    if (!openLayer(m_layerName))
-    {
-        std::ostringstream out;
-        out << "Couldn't open layer '" << m_layerName <<
-            "' in output file '" << m_idxFilename << "'.";
-        throw pdal_error(out.str());
-    }
-
-    FieldIndexes indexes = getFields();
-
-    SpatialRef outSrs(m_tgtSrsString);
-    if (!outSrs)
-        throw pdal_error("Couldn't interpret target SRS string.");
-
-    if (!m_wkt.empty())
-    {
-        Geometry g(m_wkt, outSrs);
-
-        if (!g)
-            throw pdal_error("Couldn't interpret geometry filter string.");
-        OGR_L_SetSpatialFilter(m_layer, g.get());
-    }
-
-    std::vector<FileInfo> files;
-
-    // Docs are bad here.  You need this call even if you haven't read anything
-    // or nothing happens.
-    OGR_L_ResetReading(m_layer);
-    while (true)
-    {
-        OGRFeatureH feature = OGR_L_GetNextFeature(m_layer);
-        if (!feature)
-            break;
-
-        FileInfo fileInfo;
-        fileInfo.m_filename =
-            OGR_F_GetFieldAsString(feature, indexes.m_filename);
-        fileInfo.m_srs =
-            OGR_F_GetFieldAsString(feature, indexes.m_srs);
-        files.push_back(fileInfo);
-
-        OGR_F_Destroy(feature);
-    }
-
-    Options cropOptions;
-    if (!m_bounds.empty())
-        cropOptions.add("bounds", m_bounds);
-    else
-        cropOptions.add("polygon", m_wkt);
-
-    Stage& merge = makeFilter("filters.merge");
-    for (auto f : files)
-    {
-        Stage& reader = makeReader(f.m_filename, m_driverOverride);
-        Stage *premerge = &reader;
-
-        if (m_tgtSrsString != f.m_srs)
-        {
-            Options reproOptions;
-            reproOptions.add("out_srs", m_tgtSrsString);
-            reproOptions.add("in_srs", f.m_srs);
-            Stage& repro = makeFilter("filters.reprojection", reader,
-                reproOptions);
-            premerge = &repro;
-        }
-
-        // WKT is set, even if we're using a bounding box for fitering, so
-        // can be used as a test here.
-        if (!m_wkt.empty())
-        {
-            Stage& crop = makeFilter("filters.crop", *premerge, cropOptions);
-            premerge = &crop;
-        }
-        merge.setInput(*premerge);
-    }
-
-    Options writerOptions;
-    writerOptions.add("offset_x", "auto");
-    writerOptions.add("offset_y", "auto");
-    writerOptions.add("offset_z", "auto");
-    Stage& writer = makeWriter(m_filespec, merge, "", writerOptions);
-
-    PointTable table;
-    writer.prepare(table);
-    writer.execute(table);
-}
-
-
-bool TIndexKernel::createFeature(const FieldIndexes& indexes,
-    FileInfo& fileInfo)
-{
-    using namespace gdal;
-
-    OGRFeatureH hFeature = OGR_F_Create(OGR_L_GetLayerDefn(m_layer));
-
-    // Set the creation time into the feature.
-    setDate(hFeature, fileInfo.m_ctime, indexes.m_ctime);
-
-    // Set the file mod time into the feature.
-    setDate(hFeature, fileInfo.m_mtime, indexes.m_mtime);
-
-    // Set the filename into the feature.
-    OGR_F_SetFieldString(hFeature, indexes.m_filename,
-        fileInfo.m_filename.c_str());
-
-    // Set the SRS into the feature.
-    // We override if m_assignSrsString is set
-    if (fileInfo.m_srs.empty() || m_assignSrsString.size())
-        fileInfo.m_srs = m_assignSrsString;
-
-    SpatialRef srcSrs(fileInfo.m_srs);
-    if (srcSrs.empty())
-    {
-        std::ostringstream oss;
-
-        oss << "Unable to import source spatial reference '" <<
-            fileInfo.m_srs << "' for file '" <<
-            fileInfo.m_filename << "'.";
-        throw pdal_error(oss.str());
-    }
-
-    // We have a limit of like 254 characters in some formats (notably
-    // shapefile), so try to get the condensed version of the SRS.
-
-    // Failing that, get the proj.4 version.  Not sure what's supposed to
-    // happen if we overflow 254 with proj.4.
-
-    const char* pszAuthorityCode = OSRGetAuthorityCode(srcSrs.get(), NULL);
-    const char* pszAuthorityName = OSRGetAuthorityName(srcSrs.get(), NULL);
-    if (pszAuthorityName && pszAuthorityCode)
-    {
-        std::string auth = std::string(pszAuthorityName) + ":" +
-            pszAuthorityCode;
-        OGR_F_SetFieldString(hFeature, indexes.m_srs, auth.data());
-    }
-    else
-    {
-        char* pszProj4 = NULL;
-        int err = -1;
-        try
-        {
-            err = OSRExportToProj4(srcSrs.get(), &pszProj4);
-        }
-        catch (pdal_error)
-        {}
-        if (err != OGRERR_NONE)
-        {
-            m_log->get(LogLevel::Warning) << "Unable to convert SRS to "
-                "proj.4 format for file '" << fileInfo.m_filename << "'" <<
-                std::endl;
-            return false;
-        }
-        std::string srs = std::string(pszProj4);
-        OGR_F_SetFieldString(hFeature, indexes.m_srs, srs.c_str());
-        CPLFree(pszProj4);
-    }
-
-    // Set the geometry in the feature
-    Geometry g = prepareGeometry(fileInfo);
-    char *pgeom;
-    OGR_G_ExportToWkt(g.get(), &pgeom);
-    OGR_F_SetGeometry(hFeature, g.get());
-
-    bool ok = (OGR_L_CreateFeature(m_layer, hFeature) == OGRERR_NONE);
-    OGR_F_Destroy(hFeature);
-    return ok;
-}
-
-
-TIndexKernel::FileInfo TIndexKernel::getFileInfo(KernelFactory& factory,
-    const std::string& filename)
-{
-    FileInfo fileInfo;
-
-    PipelineManager manager;
-    manager.commonOptions() = m_manager.commonOptions();
-    manager.stageOptions() = m_manager.stageOptions();
-
-    // Need to make sure options get set.
-    Stage& reader = manager.makeReader(filename, "");
-
-    if (m_fastBoundary)
-    {
-        QuickInfo qi = reader.preview();
-
-        std::stringstream polygon;
-        polygon << "POLYGON ((";
-
-        polygon <<         qi.m_bounds.minx << " " << qi.m_bounds.miny;
-        polygon << ", " << qi.m_bounds.maxx << " " << qi.m_bounds.miny;
-        polygon << ", " << qi.m_bounds.maxx << " " << qi.m_bounds.maxy;
-        polygon << ", " << qi.m_bounds.minx << " " << qi.m_bounds.maxy;
-        polygon << ", " << qi.m_bounds.minx << " " << qi.m_bounds.miny;
-        polygon << "))";
-        fileInfo.m_boundary = polygon.str();
-        if (!qi.m_srs.empty())
-            fileInfo.m_srs = qi.m_srs.getWKT();
-    }
-    else
-    {
-        Stage& hexer = manager.makeFilter("filters.hexbin", reader);
-
-        PointTable table;
-        hexer.prepare(table);
-        PointViewSet set = hexer.execute(table);
-
-        MetadataNode m = table.metadata();
-        m = m.findChild("filters.hexbin:boundary");
-        fileInfo.m_boundary = m.value();
-
-        PointViewPtr v = *set.begin();
-        if (!v->spatialReference().empty())
-            fileInfo.m_srs = v->spatialReference().getWKT();
-    }
-
-    FileUtils::fileTimes(filename, &fileInfo.m_ctime, &fileInfo.m_mtime);
-    fileInfo.m_filename = filename;
-
-    return fileInfo;
-}
-
-
-bool TIndexKernel::openDataset(const std::string& filename)
-{
-    m_dataset = OGROpen(filename.c_str(), TRUE, NULL);
-    return (bool)m_dataset;
-}
-
-
-bool TIndexKernel::createDataset(const std::string& filename)
-{
-    OGRSFDriverH hDriver = OGRGetDriverByName(m_driverName.c_str());
-    if (!hDriver)
-    {
-        std::ostringstream oss;
-
-        oss << "Can't create dataset using driver '" << m_driverName <<
-            "'. Driver is not available.";
-        throw pdal_error(oss.str());
-    }
-
-    std::string dsname = FileUtils::toAbsolutePath(filename);
-    m_dataset = OGR_Dr_CreateDataSource(hDriver, dsname.c_str(), NULL);
-    return (bool)m_dataset;
-}
-
-
-bool TIndexKernel::openLayer(const std::string& layerName)
-{
-    if (OGR_DS_GetLayerCount(m_dataset) == 1)
-        m_layer = OGR_DS_GetLayer(m_dataset, 0);
-    else if (layerName.size())
-        m_layer = OGR_DS_GetLayerByName(m_dataset, m_layerName.c_str());
-
-    return (bool)m_layer;
-}
-
-
-bool TIndexKernel::createLayer(std::string const& layername)
-{
-    using namespace gdal;
-
-    SpatialRef srs(m_tgtSrsString);
-    if (!srs)
-        m_log->get(LogLevel::Error) << "Unable to import srs for layer "
-           "creation" << std::endl;
-
-    m_layer = OGR_DS_CreateLayer(m_dataset, m_layerName.c_str(),
-        srs.get(), wkbPolygon, NULL);
-
-    if (m_layer)
-        createFields();
-
-    //ABELL - At this point we should essentially "sync" things so that
-    //  index file gets created with the proper fields.  If this doesn't
-    //  and a failure occurs, the file may be left with a layer that doesn't
-    //  have the requisite fields.  Note that OGR_DS_SyncToDisk doesn't seem
-    //  to work reliably enough to warrant use.
-    return (bool)m_layer;
-}
-
-
-void TIndexKernel::createFields()
-{
-    OGRFieldDefnH hFieldDefn = OGR_Fld_Create(
-        m_tileIndexColumnName.c_str(), OFTString);
-    OGR_Fld_SetWidth(hFieldDefn, 254);
-    OGR_L_CreateField(m_layer, hFieldDefn, TRUE);
-    OGR_Fld_Destroy(hFieldDefn);
-
-    hFieldDefn = OGR_Fld_Create(m_srsColumnName.c_str(), OFTString);
-    OGR_Fld_SetWidth(hFieldDefn, 254);
-    OGR_L_CreateField(m_layer, hFieldDefn, TRUE );
-    OGR_Fld_Destroy(hFieldDefn);
-
-    hFieldDefn = OGR_Fld_Create("modified", OFTDateTime);
-    OGR_L_CreateField(m_layer, hFieldDefn, TRUE);
-    OGR_Fld_Destroy(hFieldDefn);
-
-    hFieldDefn = OGR_Fld_Create("created", OFTDateTime);
-    OGR_L_CreateField(m_layer, hFieldDefn, TRUE);
-    OGR_Fld_Destroy(hFieldDefn);
-}
-
-
-TIndexKernel::FieldIndexes TIndexKernel::getFields()
-{
-    FieldIndexes indexes;
-
-    void *fDefn = OGR_L_GetLayerDefn(m_layer);
-
-    indexes.m_filename = OGR_FD_GetFieldIndex(fDefn,
-        m_tileIndexColumnName.c_str());
-    if (indexes.m_filename < 0)
-    {
-        std::ostringstream out;
-
-        out << "Unable to find field '" << m_tileIndexColumnName <<
-            "' in file '" << m_idxFilename << "'.";
-        throw pdal_error(out.str());
-    }
-    indexes.m_srs = OGR_FD_GetFieldIndex(fDefn, m_srsColumnName.c_str());
-    if (indexes.m_srs < 0)
-    {
-        std::ostringstream out;
-
-        out << "Unable to find field '" << m_srsColumnName << "' in file '" <<
-            m_idxFilename << "'.";
-        throw pdal_error(out.str());
-    }
-
-    indexes.m_ctime = OGR_FD_GetFieldIndex(fDefn, "created");
-    indexes.m_mtime = OGR_FD_GetFieldIndex(fDefn, "modified");
-
-//     /* Load in memory existing file names in SHP */
-//     int nExistingFiles = (int)OGR_L_GetFeatureCount(m_layer, FALSE);
-//     for (auto i = 0; i < nExistingFiles; i++)
-//     {
-//         OGRFeatureH hFeature = OGR_L_GetNextFeature(m_layer);
-//         m_files.push_back(OGR_F_GetFieldAsString(hFeature, indexes.m_filename));
-//         OGR_F_Destroy(hFeature);
-//     }
-    return indexes;
-}
-
-
-gdal::Geometry TIndexKernel::prepareGeometry(const FileInfo& fileInfo)
-{
-    using namespace gdal;
-
-    std::ostringstream oss;
-
-
-    SpatialRef srcSrs(fileInfo.m_srs);
-    SpatialRef tgtSrs(m_tgtSrsString);
-    if (!tgtSrs)
-        throw pdal_error("Unable to import target SRS.");
-
-    Geometry g;
-    try
-    {
-       g = prepareGeometry(fileInfo.m_boundary, srcSrs, tgtSrs);
-    }
-    catch (pdal_error& e)
-    {
-        oss << "Unable to transform geometry from source to target SRS for " <<
-            fileInfo.m_filename << "'. Message is '" << e.what() << "'";
-        throw pdal_error(oss.str());
-    }
-    if (!g)
-    {
-        oss << "Update to create geometry from WKT for '" <<
-            fileInfo.m_filename << "'.";
-        throw pdal_error(oss.str());
-    }
-    return g;
-}
-
-
-gdal::Geometry TIndexKernel::prepareGeometry(const std::string& wkt,
-   const gdal::SpatialRef& inSrs, const gdal::SpatialRef& outSrs)
-{
-    // Create OGR geometry from text.
-
-    gdal::Geometry g(wkt, inSrs);
-
-    if (g)
-        if (OGR_G_TransformTo(g.get(), outSrs.get()) != OGRERR_NONE)
-            throw pdal_error("Unable to transform geometry.");
-
-    return g;
-}
-
-} // namespace pdal
-
diff --git a/kernels/tindex/TIndexKernel.hpp b/kernels/tindex/TIndexKernel.hpp
deleted file mode 100644
index bf3be9a..0000000
--- a/kernels/tindex/TIndexKernel.hpp
+++ /dev/null
@@ -1,120 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/GDALUtils.hpp>
-#include <pdal/Kernel.hpp>
-#include <pdal/Stage.hpp>
-#include <pdal/util/FileUtils.hpp>
-#include <pdal/plugin.hpp>
-
-
-extern "C" int32_t TIndexKernel_ExitFunc();
-extern "C" PF_ExitFunc TIndexKernel_InitPlugin();
-
-namespace pdal
-{
-
-class KernelFactory;
-
-class PDAL_DLL TIndexKernel : public Kernel
-{
-    struct FileInfo
-    {
-        std::string m_filename;
-        std::string m_srs;
-        std::string m_boundary;
-        struct tm m_ctime;
-        struct tm m_mtime;
-    };
-
-    struct FieldIndexes
-    {
-        int m_filename;
-        int m_srs;
-        int m_ctime;
-        int m_mtime;
-    };
-
-public:
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-    int execute(); // overrride
-
-private:
-    TIndexKernel();
-    virtual void addSwitches(ProgramArgs& args);
-    virtual void validateSwitches(ProgramArgs& args);
-
-    StringList glob(std::string& path);
-    void createFile();
-    void mergeFile();
-    bool openDataset(const std::string& filename);
-    bool createDataset(const std::string& filename);
-    bool openLayer(const std::string& layerName);
-    bool createLayer(const std::string& layerName);
-    FieldIndexes getFields();
-    FileInfo getFileInfo(KernelFactory& factory, const std::string& filename);
-    bool createFeature(const FieldIndexes& indexes, FileInfo& info);
-    gdal::Geometry prepareGeometry(const FileInfo& fileInfo);
-    gdal::Geometry prepareGeometry(const std::string& wkt,
-        const gdal::SpatialRef& inSrs, const gdal::SpatialRef& outSrs);
-    void createFields();
-
-    bool isFileIndexed( const FieldIndexes& indexes, const FileInfo& fileInfo);
-
-    std::string m_idxFilename;
-    std::string m_filespec;
-    StringList m_files;
-    std::string m_layerName;
-    std::string m_driverName;
-    std::string m_tileIndexColumnName;
-    std::string m_srsColumnName;
-    std::string m_wkt;
-    BOX2D m_bounds;
-    bool m_merge;
-    bool m_absPath;
-
-    void *m_dataset;
-    void *m_layer;
-    std::string m_tgtSrsString;
-    std::string m_assignSrsString;
-    bool m_fastBoundary;
-    bool m_usestdin;
-};
-
-} // namespace pdal
-
diff --git a/kernels/translate/CMakeLists.txt b/kernels/translate/CMakeLists.txt
deleted file mode 100644
index 3a142db..0000000
--- a/kernels/translate/CMakeLists.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# Translate Kernel
-PDAL_ADD_DRIVER(kernel translate TranslateKernel.cpp TranslateKernel.hpp objects)
-set(PDAL_TARGET_OBJECTS ${PDAL_TARGET_OBJECTS} ${objects} PARENT_SCOPE)
diff --git a/kernels/translate/TranslateKernel.cpp b/kernels/translate/TranslateKernel.cpp
deleted file mode 100644
index edfb260..0000000
--- a/kernels/translate/TranslateKernel.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "TranslateKernel.hpp"
-
-#include <pdal/KernelFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/PipelineWriter.hpp>
-#include <pdal/PointTable.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/Stage.hpp>
-#include <pdal/StageFactory.hpp>
-
-#include <memory>
-#include <string>
-#include <vector>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("kernels.translate",
-               "The Translate kernel allows users to construct a pipeline " \
-               "consisting of a reader, a writer, and N filter stages. " \
-               "Any supported stage type can be specified from the command " \
-               "line, reducing the need to create custom kernels for every " \
-               "combination.",
-               "http://pdal.io/kernels/kernels.translate.html");
-
-CREATE_STATIC_PLUGIN(1, 0, TranslateKernel, Kernel, s_info)
-
-std::string TranslateKernel::getName() const
-{
-    return s_info.name;
-}
-
-TranslateKernel::TranslateKernel()
-    : Kernel()
-    , m_inputFile("")
-    , m_outputFile("")
-    , m_pipelineOutput("")
-    , m_readerType("")
-    , m_writerType("")
-{}
-
-void TranslateKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("input,i", "Input filename", m_inputFile).setPositional();
-    args.add("output,o", "Output filename", m_outputFile).setPositional();
-    args.add("filter,f", "Filter type", m_filterType).setOptionalPositional();
-    args.add("pipeline,p", "Pipeline output", m_pipelineOutput);
-    args.add("reader,r", "Reader type", m_readerType);
-    args.add("writer,w", "Writer type", m_writerType);
-}
-
-int TranslateKernel::execute()
-{
-    Stage& reader = m_manager.makeReader(m_inputFile, m_readerType);
-    Stage* stage = &reader;
-
-    // add each filter provided on the command-line, updating the stage pointer
-    for (auto const f : m_filterType)
-    {
-        std::string filter_name(f);
-
-        if (!Utils::startsWith(f, "filters."))
-            filter_name.insert(0, "filters.");
-
-        Stage& filter = m_manager.makeFilter(filter_name, *stage);
-        stage = &filter;
-    }
-
-    Stage& writer = m_manager.makeWriter(m_outputFile, m_writerType, *stage);
-    m_manager.execute();
-    if (m_pipelineOutput.size() > 0)
-        PipelineWriter::writePipeline(&writer, m_pipelineOutput);
-
-    return 0;
-}
-
-} // namespace pdal
diff --git a/kernels/translate/TranslateKernel.hpp b/kernels/translate/TranslateKernel.hpp
deleted file mode 100644
index 28424d7..0000000
--- a/kernels/translate/TranslateKernel.hpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2013, Howard Butler (hobu.inc at gmail.com)
-* Copyright (c) 2015, Brad Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Kernel.hpp>
-#include <pdal/PipelineManager.hpp>
-#include <pdal/pdal_export.hpp>
-#include <pdal/plugin.hpp>
-
-#include <memory>
-#include <string>
-#include <vector>
-
-extern "C" int32_t TranslateKernel_ExitFunc();
-extern "C" PF_ExitFunc TranslateKernel_InitPlugin();
-
-namespace pdal
-{
-
-class PDAL_DLL TranslateKernel : public Kernel
-{
-public:
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-    int execute();
-
-private:
-    TranslateKernel();
-    virtual void addSwitches(ProgramArgs& args);
-
-    std::string m_inputFile;
-    std::string m_outputFile;
-    std::string m_pipelineOutput;
-    std::string m_readerType;
-    std::vector<std::string> m_filterType;
-    std::string m_writerType;
-};
-
-} // namespace pdal
diff --git a/src/libpdalcpp b/libpdalcpp
similarity index 100%
rename from src/libpdalcpp
rename to libpdalcpp
diff --git a/include/pdal/Compression.hpp b/pdal/Compression.hpp
similarity index 100%
rename from include/pdal/Compression.hpp
rename to pdal/Compression.hpp
diff --git a/src/DbReader.cpp b/pdal/DbReader.cpp
similarity index 100%
rename from src/DbReader.cpp
rename to pdal/DbReader.cpp
diff --git a/include/pdal/DbReader.hpp b/pdal/DbReader.hpp
similarity index 100%
rename from include/pdal/DbReader.hpp
rename to pdal/DbReader.hpp
diff --git a/pdal/DbWriter.cpp b/pdal/DbWriter.cpp
new file mode 100644
index 0000000..5a57a94
--- /dev/null
+++ b/pdal/DbWriter.cpp
@@ -0,0 +1,271 @@
+/******************************************************************************
+* Copyright (c) 2014,  Hobu Inc., hobu at hobu.co
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its contributors
+*       may be used to endorse or promote products derived from this
+*       software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/DbWriter.hpp>
+#include <pdal/util/Utils.hpp>
+
+namespace pdal
+{
+
+void DbWriter::addArgs(ProgramArgs& args)
+{
+    args.add("output_dims", "Output dimensions", m_outputDims);
+    m_scaling.addArgs(args);
+}
+
+// Build the list of dimensions for the output schema.
+// Placing this here allows validation of dimensions before execution begins.
+void DbWriter::prepared(PointTableRef table)
+{
+    using namespace Dimension;
+
+    PointLayoutPtr layout = table.layout();
+
+    if (m_outputDims.empty())
+    {
+        for (auto& dimType : layout->dimTypes())
+            m_dbDims.push_back(XMLDim(dimType, layout->dimName(dimType.m_id)));
+        return;
+    }
+
+    DimTypeList dims;
+    for (std::string& s : m_outputDims)
+    {
+        DimType dt = layout->findDimType(s);
+        if (dt.m_id == Id::Unknown)
+        {
+            std::ostringstream oss;
+            oss << "Invalid dimension '" << s << "' specified for "
+                "'output_dims' option.";
+            throw pdal_error(oss.str());
+        }
+        m_dbDims.push_back(XMLDim(dt, layout->dimName(dt.m_id)));
+    }
+}
+
+
+void DbWriter::ready(PointTableRef /*table*/)
+{
+    using namespace Dimension;
+
+    // Determine if X, Y and Z values should be written as Signed32 along with
+    // a scale factor and offset instead of being written as Double.
+    m_locationScaling = m_scaling.nonstandard();
+
+    auto cmp = [](const XMLDim& d1, const XMLDim& d2) -> bool
+    {
+        long id1 = Utils::toNative(d1.m_dimType.m_id);
+        long id2 = Utils::toNative(d2.m_dimType.m_id);
+
+        const auto isXyz([](long native)->bool
+        {
+            const Id e(static_cast<Id>(native));
+            return (e == Id::X || e == Id::Y || e == Id::Z);
+        });
+
+        // Put X, Y and Z at the end of the list.
+        if (isXyz(id1))
+            id1 += 1000000;
+        if (isXyz(id2))
+            id2 += 1000000;
+        return id1 < id2;
+    };
+
+    // Sort the dimensions so that X, Y & Z are at the end.
+    std::sort(m_dbDims.begin(), m_dbDims.end(), cmp);
+
+    // Suck the dimTypes out of the dbDims so that they can be used to
+    // retrieve data from the point table.
+    // Set the packed type into the dbDim if necessary and save off the
+    // index of X, Y and Z for location scaling.
+    m_dimTypes.clear();
+    m_xOffsets = std::make_pair(-1, -1);
+    m_yOffsets = std::make_pair(-1, -1);
+    m_zOffsets = std::make_pair(-1, -1);
+    m_packedPointSize = 0;
+    m_dbPointSize = 0;
+    for (auto& xmlDim : m_dbDims)
+    {
+        m_dimTypes.push_back(xmlDim.m_dimType);
+        DimType& dt = m_dimTypes.back();
+        // Dim types are stored in the map to allow fast access in readField.
+        m_dimMap[Utils::toNative(dt.m_id)] = dt;
+
+        if (m_locationScaling)
+        {
+            if (xmlDim.m_dimType.m_id == Id::X)
+            {
+                xmlDim.m_dimType.m_xform = m_scaling.m_xXform;
+                xmlDim.m_dimType.m_type = Type::Signed32;
+                m_xOffsets = std::make_pair(m_packedPointSize, m_dbPointSize);
+            }
+            if (xmlDim.m_dimType.m_id == Id::Y)
+            {
+                xmlDim.m_dimType.m_xform = m_scaling.m_yXform;
+                xmlDim.m_dimType.m_type = Type::Signed32;
+                m_yOffsets = std::make_pair(m_packedPointSize, m_dbPointSize);
+            }
+            if (xmlDim.m_dimType.m_id == Id::Z)
+            {
+                xmlDim.m_dimType.m_xform = m_scaling.m_zXform;
+                xmlDim.m_dimType.m_type = Type::Signed32;
+                m_zOffsets = std::make_pair(m_packedPointSize, m_dbPointSize);
+            }
+        }
+        m_packedPointSize += Dimension::size(dt.m_type);
+        m_dbPointSize += Dimension::size(xmlDim.m_dimType.m_type);
+    }
+}
+
+
+/// Make sure that computed offsets are stored in the schema.
+void DbWriter::setAutoXForm(const PointViewPtr view)
+{
+    using namespace Dimension;
+
+    m_scaling.setAutoXForm(view);
+    for (auto& xmlDim : m_dbDims)
+    {
+        if (xmlDim.m_dimType.m_id == Id::X)
+            xmlDim.m_dimType.m_xform = m_scaling.m_xXform;
+        if (xmlDim.m_dimType.m_id == Id::Y)
+            xmlDim.m_dimType.m_xform = m_scaling.m_yXform;
+        if (xmlDim.m_dimType.m_id == Id::Z)
+            xmlDim.m_dimType.m_xform = m_scaling.m_zXform;
+    }
+}
+
+
+/// Read a field from a PointView and write its value as formatted for output
+/// to the DB schema to the location as requested.
+/// \param[in] view     PointView to read from.
+/// \param[in] pos      Location in which to store field value.
+/// \param[in] id       ID of the dimension to read.
+/// \param[in] idx      Index of point to read.
+/// \return  Size of field as read.
+size_t DbWriter::readField(const PointView& view, char *pos,
+    Dimension::Id id, PointId idx)
+{
+    using namespace Dimension;
+
+    DimType& dt = m_dimMap[(int)id];
+    size_t size = Dimension::size(dt.m_type);
+
+    // Using the ID instead of a dimType as the arugment hides the complication
+    // of the "type" of the dimension to retrieve in the case of location
+    // scaling.
+    view.getField(pos, id, dt.m_type, idx);
+
+    auto iconvert = [pos](const XForm& xform, Dimension::Id dim)
+    {
+        double d;
+        int32_t i;
+
+        memcpy(&d, pos, sizeof(double));
+
+        d = xform.toScaled(d);
+        if (!Utils::numericCast(d, i))
+        {
+            std::ostringstream oss;
+            oss << "Unable to convert double to int32 for packed DB output: ";
+            oss << Dimension::name(dim) << ": (" << d << ").";
+            throw pdal_error(oss.str());
+        }
+        memcpy(pos, &i, sizeof(int32_t));
+    };
+
+    if (m_locationScaling)
+    {
+        // For X, Y or Z.
+        if (id == Id::X)
+        {
+            iconvert(m_scaling.m_xXform, Id::X);
+            size = sizeof(int32_t);
+        }
+        else if (id == Id::Y)
+        {
+            iconvert(m_scaling.m_yXform, Id::Y);
+            size = sizeof(int32_t);
+        }
+        else if (id == Id::Z)
+        {
+            iconvert(m_scaling.m_zXform, Id::Z);
+            size = sizeof(int32_t);
+        }
+    }
+    return size;
+}
+
+
+/// Read a point's data packed into a buffer.
+/// \param[in] view  PointView to read from.
+/// \param[in] idx  Index of point to read.
+/// \param[in] outbuf  Buffer to write to.
+/// \return  Number of bytes written to buffer.
+size_t DbWriter::readPoint(const PointView& view, PointId idx, char *outbuf)
+{
+    using namespace Dimension;
+
+    // Read the data for the output dimensions from the view into the outbuf.
+    view.getPackedPoint(m_dimTypes, idx, outbuf);
+
+    auto iconvert = [](const XForm& xform, Id dim,
+        const char *inpos, char *outpos)
+    {
+        double d;
+        int32_t i;
+
+        memcpy(&d, inpos, sizeof(double));
+        d = xform.toScaled(d);
+        if (!Utils::numericCast(d, i))
+        {
+            std::ostringstream oss;
+            oss << "Unable to convert double to int32 for packed DB output: ";
+            oss << Dimension::name(dim) << ": (" << d << ").";
+            throw pdal_error(oss.str());
+        }
+        memcpy(outpos, &i, sizeof(int32_t));
+    };
+
+    if (m_xOffsets.first >= 0)
+        iconvert(m_scaling.m_xXform, Id::X, outbuf + (size_t)m_xOffsets.first,
+            outbuf + (size_t)m_xOffsets.second);
+    if (m_yOffsets.first >= 0)
+        iconvert(m_scaling.m_yXform, Id::Y, outbuf + (size_t)m_yOffsets.first,
+            outbuf + (size_t)m_yOffsets.second);
+    if (m_zOffsets.first >= 0)
+        iconvert(m_scaling.m_zXform, Id::Z, outbuf + (size_t)m_zOffsets.first,
+            outbuf + (size_t)m_zOffsets.second);
+    return m_dbPointSize;
+}
+
+} // namespace pdal
diff --git a/include/pdal/DbWriter.hpp b/pdal/DbWriter.hpp
similarity index 100%
rename from include/pdal/DbWriter.hpp
rename to pdal/DbWriter.hpp
diff --git a/include/pdal/DimDetail.hpp b/pdal/DimDetail.hpp
similarity index 100%
rename from include/pdal/DimDetail.hpp
rename to pdal/DimDetail.hpp
diff --git a/include/pdal/DimType.hpp b/pdal/DimType.hpp
similarity index 100%
rename from include/pdal/DimType.hpp
rename to pdal/DimType.hpp
diff --git a/pdal/DimUtil.hpp b/pdal/DimUtil.hpp
new file mode 100644
index 0000000..b22b7e4
--- /dev/null
+++ b/pdal/DimUtil.hpp
@@ -0,0 +1,192 @@
+/******************************************************************************
+ * Copyright (c) 2016, Hobu Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of the Martin Isenburg or Iowa Department
+ *       of Natural Resources nor the names of its contributors may be
+ *       used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <pdal/util/Utils.hpp>
+
+namespace pdal
+{
+namespace Dimension
+{
+
+enum class BaseType
+{
+    None = 0x000,
+    Signed = 0x100,
+    Unsigned = 0x200,
+    Floating = 0x400
+};
+
+inline BaseType fromName(std::string name)
+{
+    if (name == "signed")
+        return BaseType::Signed;
+    else if (name == "unsigned")
+        return BaseType::Unsigned;
+    else if (name == "floating")
+        return BaseType::Floating;
+    return BaseType::None;
+}
+
+inline std::string toName(BaseType b)
+{
+    switch (b)
+    {
+    case BaseType::Signed:
+        return "signed";
+    case BaseType::Unsigned:
+        return "unsigned";
+    case BaseType::Floating:
+        return "floating";
+    default:
+        return "";
+    }
+}
+
+enum class Type
+{
+    None = 0,
+    Unsigned8 = unsigned(BaseType::Unsigned) | 1,
+    Signed8 = unsigned(BaseType::Signed) | 1,
+    Unsigned16 = unsigned(BaseType::Unsigned) | 2,
+    Signed16 = unsigned(BaseType::Signed) | 2,
+    Unsigned32 = unsigned(BaseType::Unsigned) | 4,
+    Signed32 = unsigned(BaseType::Signed) | 4,
+    Unsigned64 = unsigned(BaseType::Unsigned) | 8,
+    Signed64 = unsigned(BaseType::Signed) | 8,
+    Float = unsigned(BaseType::Floating) | 4,
+    Double = unsigned(BaseType::Floating) | 8
+};
+
+inline std::size_t size(Type t)
+{
+    return Utils::toNative(t) & 0xFF;
+}
+
+inline BaseType base(Type t)
+{
+    return BaseType(Utils::toNative(t) & 0xFF00);
+}
+
+static const int COUNT = std::numeric_limits<uint16_t>::max();
+static const int PROPRIETARY = 0xFF00;
+
+/// Get a string reresentation of a datatype.
+/// \param[in] dimtype  Dimension type.
+/// \return  String representation of dimension type.
+inline std::string interpretationName(Type dimtype)
+{
+    switch (dimtype)
+    {
+    case Type::None:
+        return "unknown";
+    case Type::Signed8:
+        return "int8_t";
+    case Type::Signed16:
+        return "int16_t";
+    case Type::Signed32:
+        return "int32_t";
+    case Type::Signed64:
+        return "int64_t";
+    case Type::Unsigned8:
+        return "uint8_t";
+    case Type::Unsigned16:
+        return "uint16_t";
+    case Type::Unsigned32:
+        return "uint32_t";
+    case Type::Unsigned64:
+        return "uint64_t";
+    case Type::Float:
+        return "float";
+    case Type::Double:
+        return "double";
+    }
+    return "unknown";
+}
+
+
+/// Get the type corresponding to a type name.
+/// \param s  Name of type.
+/// \return  Corresponding type enumeration value.
+inline Type type(std::string s)
+{
+    s = Utils::tolower(s);
+
+    if (s == "int8_t" || s == "int8")
+       return Type::Signed8;
+    if (s == "int16_t" || s == "int16")
+       return Type::Signed16;
+    if (s == "int32_t" || s == "int32")
+       return Type::Signed32;
+    if (s == "int64_t" || s == "int64")
+       return Type::Signed64;
+    if (s == "uint8_t" || s == "uint8")
+        return Type::Unsigned8;
+    if (s == "uint16_t" || s == "uint16")
+        return Type::Unsigned16;
+    if (s == "uint32_t" || s == "uint32")
+        return Type::Unsigned32;
+    if (s == "uint64_t" || s == "uint64")
+        return Type::Unsigned64;
+    if (s == "float")
+        return Type::Float;
+    if (s == "double")
+        return Type::Double;
+    return Type::None;
+}
+
+
+/// Extract a dimension name of a string.  Dimension names start with an alpha
+/// and continue with numbers or underscores.
+/// \param s  String from which to extract dimension name.
+/// \param p  Position at which to start extracting.
+/// \return  Number of characters in the extracted name.
+inline std::size_t extractName(const std::string& s, std::string::size_type p)
+{
+    if (!std::isalpha(s[p++]))
+        return 0;
+    auto isvalid = [](int c)
+    {
+        return std::isalpha(c) || std::isdigit(c) || c == '_';
+    };
+    return Utils::extract(s, p, isvalid) + 1;
+}
+
+} // namespace Dimension
+} // namespace pdal
+
diff --git a/pdal/Dimension.json b/pdal/Dimension.json
new file mode 100644
index 0000000..ee9c4bd
--- /dev/null
+++ b/pdal/Dimension.json
@@ -0,0 +1,341 @@
+{ "dimensions": [
+    {
+    "name": "X",
+    "type": "double",
+    "description": "X coordinate"
+    },
+    {
+    "name": "Y",
+    "type": "double",
+    "description": "Y coordinate"
+    },
+    {
+    "name": "Z",
+    "type": "double",
+    "description": "Z coordinate"
+    },
+    {
+    "name": "Intensity",
+    "type": "uint16",
+    "description": "Representation of the pulse return magnitude"
+    },
+    {
+    "name": "Amplitude",
+    "type": "float",
+    "description": "This is the ratio of the received power to the power received at the detection limit expressed in dB"
+    },
+    {
+    "name": "Reflectance",
+    "type": "float",
+    "description": "Ratio of the received power to the power that would be received from a white diffuse target at the same distance expressed in dB. The reflectance represents a range independent property of the target.  The surface normal of this target is assumed to be in parallel to the laser beam direction."
+    },
+    {
+    "name": "ReturnNumber",
+    "type": "uint8",
+    "description": "Pulse return number for a given output pulse. A given output laser pulse can have many returns, and they must be marked in order, starting with 1"
+    },
+    {
+    "name": "NumberOfReturns",
+    "type": "uint8",
+    "description": "Total number of returns for a given pulse."
+    },
+    {
+    "name": "ScanDirectionFlag",
+    "type": "uint8",
+    "description": "Direction at which the scanner mirror was traveling at the time of the output pulse. A value of 1 is a positive scan direction, and a bit value of 0 is a negative scan direction, where positive scan direction is a scan moving from the left side of the in-track direction to the right side and negative the opposite"
+    },
+    {
+    "name": "EdgeOfFlightLine",
+    "type": "uint8",
+    "description": "Indicates the end of scanline before a direction change with a value of 1 - 0 otherwise"
+    },
+    {
+    "name": "Classification",
+    "type": "uint8",
+    "description": "ASPRS classification.  0 for no classification.  See LAS specification for details."
+    },
+    {
+    "name": "ScanAngleRank",
+    "alt_names": "ScanAngle",
+    "type": "float",
+    "description": "Angle degree at which the laster point was output from the system, including the roll of the aircraft.  The scan angle is based on being nadir, and -90 the left side of the aircraft in the direction of flight"
+    },
+    {
+    "name": "UserData",
+    "type": "uint8",
+    "description": "Unspecified user data"
+    },
+    {
+    "name": "PointSourceId",
+    "type": "uint16",
+    "description": "File source ID from which the point originated.  Zero indicates that the point originated in the current file"
+    },
+    {
+    "name": "Red",
+    "type": "uint16",
+    "description": "Red image channel value"
+    },
+    {
+    "name": "Green",
+    "type": "uint16",
+    "description": "Green image channel value"
+    },
+    {
+    "name": "Blue",
+    "type": "uint16",
+    "description": "Blue image channel value"
+    },
+    {
+    "name": "GpsTime",
+    "type": "double",
+    "description": "GPS time that the point was acquired"
+    },
+    {
+    "name": "InternalTime",
+    "type": "double",
+    "description": "Scanner's internal time when the point was acquired, in seconds"
+    },
+    {
+    "name": "OffsetTime",
+    "alt_names": "Time",
+    "type": "uint32",
+    "description": "Milliseconds from first acquired point"
+    },
+    {
+    "name": "IsPpsLocked",
+    "type": "uint8",
+    "description": "The external PPS signal was found to be synchronized at the time of the current laser shot."
+    },
+    {
+    "name": "StartPulse",
+    "type": "int32",
+    "description": "Relative pulse signal strength"
+    },
+    {
+    "name": "ReflectedPulse",
+    "type": "int32",
+    "description": "Relative reflected pulse signal strength"
+    },
+    {
+    "name": "Pdop",
+    "type": "float",
+    "description": "GPS PDOP (dilution of precision)"
+    },
+    {
+    "name": "Pitch",
+    "type": "float",
+    "description": "Pitch in degrees"
+    },
+    {
+    "name": "Roll",
+    "type": "float",
+    "description": "Roll in degrees"
+    },
+    {
+    "name": "PulseWidth",
+    "type": "float",
+    "description": "Laser received pulse width (digitizer samples)"
+    },
+    {
+    "name": "Deviation",
+    "type": "float",
+    "description": "A larger value for deviation indicates larger distortion."
+    },
+    {
+    "name": "PassiveSignal",
+    "type": "int32",
+    "description": "Relative passive signal"
+    },
+    {
+    "name": "BackgroundRadiation",
+    "type": "float",
+    "description": "A measure of background radiation."
+    },
+    {
+    "name": "PassiveX",
+    "type": "double",
+    "description": "Passive X footprint"
+    },
+    {
+    "name": "PassiveY",
+    "type": "double",
+    "description": "Passive Y footprint"
+    },
+    {
+    "name": "PassiveZ",
+    "type": "double",
+    "description": "Passive Z footprint"
+    },
+    {
+    "name": "XVelocity",
+    "type": "double",
+    "description": "X Velocity"
+    },
+    {
+    "name": "YVelocity",
+    "type": "double",
+    "description": "Y Velocity"
+    },
+    {
+    "name": "ZVelocity",
+    "type": "double",
+    "description": "Z Velocity"
+    },
+    {
+    "name": "Azimuth",
+    "alt_names": "PlatformHeading",
+    "type": "double",
+    "description": "Scanner azimuth"
+    },
+    {
+    "name": "WanderAngle",
+    "type": "double",
+    "description": "Wander Angle"
+    },
+    {
+    "name": "XBodyAccel",
+    "type": "double",
+    "description": "X Body Acceleration"
+    },
+    {
+    "name": "YBodyAccel",
+    "type": "double",
+    "description": "Y Body Acceleration"
+    },
+    {
+    "name": "ZBodyAccel",
+    "type": "double",
+    "description": "Z Body Acceleration"
+    },
+    {
+    "name": "XBodyAngRate",
+    "type": "double",
+    "description": "X Body Angle Rate"
+    },
+    {
+    "name": "YBodyAngRate",
+    "type": "double",
+    "description": "Y Body Angle Rate"
+    },
+    {
+    "name": "ZBodyAngRate",
+    "type": "double",
+    "description": "Z Body Angle Rate"
+    },
+    {
+    "name": "Flag",
+    "type": "uint8",
+    "description": "Flag"
+    },
+    {
+    "name": "Mark",
+    "type": "uint8",
+    "description": "Mark"
+    },
+    {
+    "name": "Alpha",
+    "type": "uint16",
+    "description": "Alpha"
+    },
+    {
+    "name": "EchoRange",
+    "type": "double",
+    "description": "Echo Range"
+    },
+    {
+    "name": "ScanChannel",
+    "type": "uint8",
+    "description": "Scan Channel"
+    },
+    {
+    "name": "Infrared",
+    "alt_names": "NearInfrared",
+    "type": "uint16",
+    "description": "Infrared"
+    },
+    {
+    "name": "HeightAboveGround",
+    "type": "double",
+    "description": "Height Above Ground"
+    },
+    {
+    "name": "ClassFlags",
+    "type": "uint8",
+    "description": "Class Flags"
+    },
+    {
+    "name": "LvisLfid",
+    "alt_names": "Lvis_Lfid",
+    "type": "uint64",
+    "description": "LVIS_LFID"
+    },
+    {
+    "name": "ShotNumber",
+    "type": "uint64",
+    "description": "Shot Number"
+    },
+    {
+    "name": "LongitudeCentroid",
+    "alt_names": "Longitude_Centroid",
+    "type": "double",
+    "description": "Longitude Centroid"
+    },
+    {
+    "name": "LatitudeCentroid",
+    "alt_names": "Latitude_Centroid",
+    "type": "double",
+    "description": "Latitude Centroid"
+    },
+    {
+    "name": "ElevationCentroid",
+    "alt_names": "Elevation_Centroid",
+    "type": "double",
+    "description": "Elevation Centroid"
+    },
+    {
+    "name": "LongitudeLow",
+    "alt_names": "Longitude_Low",
+    "type": "double",
+    "description": "Longitude Low"
+    },
+    {
+    "name": "LatitudeLow",
+    "alt_names": "Latitude_Low",
+    "type": "double",
+    "description": "Latitude Low"
+    },
+    {
+    "name": "ElevationLow",
+    "alt_names": "Elevation_Low",
+    "type": "double",
+    "description": "Elevation Low"
+    },
+    {
+    "name": "LongitudeHigh",
+    "alt_names": "Longitude_High",
+    "type": "double",
+    "description": "Longitude High"
+    },
+    {
+    "name": "LatitudeHigh",
+    "alt_names": "Latitude_High",
+    "type": "double",
+    "description": "Latitude High"
+    },
+    {
+    "name": "ElevationHigh",
+    "alt_names": "Elevation_High",
+    "type": "double",
+    "description": "Elevation High"
+    },
+    {
+    "name": "PointId",
+    "type": "uint32",
+    "description": "An explicit representation of point ordering within a file, which allows this usually-implicit information to be preserved when reordering points."
+    },
+    {
+    "name": "OriginId",
+    "type": "uint32",
+    "description": "A file source ID from which the point originated.  This ID is global to a derivative dataset which may be aggregated from multiple files."
+    }
+] }
diff --git a/pdal/DynamicLibrary.cpp b/pdal/DynamicLibrary.cpp
new file mode 100755
index 0000000..eccbc2a
--- /dev/null
+++ b/pdal/DynamicLibrary.cpp
@@ -0,0 +1,128 @@
+/******************************************************************************
+* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+// The DynamicLibrary was modeled very closely after the work of Gigi Sayfan in
+// the Dr. Dobbs article:
+// http://www.drdobbs.com/cpp/building-your-own-plugin-framework-part/206503957
+// The original work was released under the Apache License v2.
+
+#ifdef _WIN32
+  #include <Windows.h>
+#else
+  #include <dlfcn.h>
+#endif
+
+#include <sstream>
+#include <iostream>
+
+#include "private/DynamicLibrary.hpp"
+
+namespace pdal
+{
+
+DynamicLibrary::~DynamicLibrary()
+{
+    if (m_handle)
+    {
+#ifndef _WIN32
+        ::dlclose(m_handle);
+#else
+        ::FreeLibrary((HMODULE)m_handle);
+#endif
+    }
+}
+
+
+void DynamicLibrary::clear()
+{
+    m_handle = NULL;
+}
+
+
+DynamicLibrary *DynamicLibrary::load(const std::string &name, 
+    std::string &errorString)
+{
+    if (name.empty()) 
+    {
+        errorString = "Empty path.";
+        return NULL;
+    }
+
+    void *handle = NULL;
+
+#ifdef _WIN32
+    handle = ::LoadLibraryA(name.c_str());
+    if (handle == NULL)
+    {
+        DWORD errorCode = ::GetLastError();
+        std::stringstream ss;
+        ss << std::string("LoadLibrary(") << name 
+            << std::string(") Failed. errorCode: ") 
+            << errorCode; 
+        errorString = ss.str();
+    }
+#else
+    handle = ::dlopen(name.c_str(), RTLD_NOW);
+    if (!handle) 
+    {
+        std::string dlErrorString;
+        const char *zErrorString = ::dlerror();
+        if (zErrorString)
+            dlErrorString = zErrorString;
+        errorString += "Failed to load \"" + name + '"';
+        if (dlErrorString.size())
+            errorString += ": " + dlErrorString;
+        return NULL;
+    }
+#endif
+    return new DynamicLibrary(handle);
+}
+
+
+void *DynamicLibrary::getSymbol(const std::string& symbol)
+{
+    if (!m_handle)
+        return NULL;
+
+    void *sym;
+#ifdef _WIN32
+    sym = ::GetProcAddress((HMODULE)m_handle, symbol.c_str());
+#else
+    sym = ::dlsym(m_handle, symbol.c_str());
+#endif
+    return sym;
+}
+
+} // namespace pdal
+
diff --git a/pdal/EigenUtils.cpp b/pdal/EigenUtils.cpp
new file mode 100644
index 0000000..2dae5d4
--- /dev/null
+++ b/pdal/EigenUtils.cpp
@@ -0,0 +1,604 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/EigenUtils.hpp>
+#include <pdal/GDALUtils.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/SpatialReference.hpp>
+#include <pdal/util/Bounds.hpp>
+#include <pdal/util/Utils.hpp>
+
+#include <Eigen/Dense>
+
+#include <cfloat>
+#include <vector>
+
+namespace pdal
+{
+
+namespace eigen
+{
+
+Eigen::Vector3f computeCentroid(PointView& view, std::vector<PointId> ids)
+{
+    using namespace Eigen;
+
+    auto n = ids.size();
+
+    double mx, my, mz;
+    mx = my = mz = 0.0;
+    for (auto const& j : ids)
+    {
+        mx += view.getFieldAs<double>(Dimension::Id::X, j);
+        my += view.getFieldAs<double>(Dimension::Id::Y, j);
+        mz += view.getFieldAs<double>(Dimension::Id::Z, j);
+    }
+
+    Vector3f centroid;
+    centroid << mx/n, my/n, mz/n;
+
+    return centroid;
+}
+
+Eigen::Matrix3f computeCovariance(PointView& view, std::vector<PointId> ids)
+{
+    using namespace Eigen;
+
+    auto n = ids.size();
+
+    Vector3f centroid = computeCentroid(view, ids);
+
+    // demean the neighborhood
+    MatrixXf A(3, n);
+    size_t k = 0;
+    for (auto const& j : ids)
+    {
+        A(0, k) = view.getFieldAs<double>(Dimension::Id::X, j) - centroid[0];
+        A(1, k) = view.getFieldAs<double>(Dimension::Id::Y, j) - centroid[1];
+        A(2, k) = view.getFieldAs<double>(Dimension::Id::Z, j) - centroid[2];
+        k++;
+    }
+
+    return A * A.transpose();
+}
+
+uint8_t computeRank(PointView& view, std::vector<PointId> ids, double threshold)
+{
+    using namespace Eigen;
+
+    Matrix3f B = computeCovariance(view, ids);
+
+    JacobiSVD<Matrix3f> svd(B);
+    svd.setThreshold(threshold);
+
+    return static_cast<uint8_t>(svd.rank());
+}
+
+Eigen::MatrixXd computeSpline(Eigen::MatrixXd x, Eigen::MatrixXd y,
+                              Eigen::MatrixXd z, Eigen::MatrixXd xx,
+                              Eigen::MatrixXd yy)
+{
+    using namespace Eigen;
+
+    int num_rows = xx.rows();
+    int num_cols = xx.cols();
+
+    MatrixXd S = MatrixXd::Zero(num_rows, num_cols);
+
+    for (auto col = 0; col < num_cols; ++col)
+    {
+        for (auto row = 0; row < num_rows; ++row)
+        {
+            // Further optimizations are achieved by estimating only the
+            // interpolated surface within a local neighbourhood (e.g. a 7 x 7
+            // neighbourhood is used in our case) of the cell being filtered.
+            int radius = 3;
+
+            int c = std::floor(col/2);
+            int r = std::floor(row/2);
+
+            int cs = Utils::clamp(c-radius, 0, static_cast<int>(z.cols()-1));
+            int ce = Utils::clamp(c+radius, 0, static_cast<int>(z.cols()-1));
+            int col_size = ce - cs + 1;
+            int rs = Utils::clamp(r-radius, 0, static_cast<int>(z.rows()-1));
+            int re = Utils::clamp(r+radius, 0, static_cast<int>(z.rows()-1));
+            int row_size = re - rs + 1;
+
+            MatrixXd Xn = x.block(rs, cs, row_size, col_size);
+            MatrixXd Yn = y.block(rs, cs, row_size, col_size);
+            MatrixXd Hn = z.block(rs, cs, row_size, col_size);
+
+            int nsize = row_size * col_size;
+            VectorXd T = VectorXd::Zero(nsize);
+            MatrixXd P = MatrixXd::Zero(nsize, 3);
+            MatrixXd K = MatrixXd::Zero(nsize, nsize);
+
+            for (auto id = 0; id < nsize; ++id)
+            {
+                double xj = Xn(id);
+                double yj = Yn(id);
+                double zj = Hn(id);
+                if (std::isnan(xj) || std::isnan(yj) || std::isnan(zj))
+                    continue;
+                T(id) = zj;
+                P.row(id) << 1, xj, yj;
+                for (auto id2 = 0; id2 < nsize; ++id2)
+                {
+                    if (id == id2)
+                        continue;
+                    double xk = Xn(id2);
+                    double yk = Yn(id2);
+                    double zk = Hn(id2);
+                    if (std::isnan(xk) || std::isnan(yk) || std::isnan(zk))
+                        continue;
+                    double rsqr = (xj - xk) * (xj - xk) + (yj - yk) * (yj - yk);
+                    if (rsqr == 0.0)
+                        continue;
+                    K(id, id2) = rsqr * std::log10(std::sqrt(rsqr));
+                }
+            }
+
+            MatrixXd A = MatrixXd::Zero(nsize+3, nsize+3);
+            A.block(0,0,nsize,nsize) = K;
+            A.block(0,nsize,nsize,3) = P;
+            A.block(nsize,0,3,nsize) = P.transpose();
+
+            VectorXd b = VectorXd::Zero(nsize+3);
+            b.head(nsize) = T;
+
+            VectorXd x = A.fullPivHouseholderQr().solve(b);
+
+            Vector3d a = x.tail(3);
+            VectorXd w = x.head(nsize);
+
+            double sum = 0.0;
+            double xi2 = xx(row, col);
+            double yi2 = yy(row, col);
+            for (auto j = 0; j < nsize; ++j)
+            {
+                double xj = Xn(j);
+                double yj = Yn(j);
+                double zj = Hn(j);
+                if (std::isnan(xj) || std::isnan(yj) || std::isnan(zj))
+                    continue;
+                double rsqr = (xj - xi2) * (xj - xi2) + (yj - yi2) * (yj - yi2);
+                if (rsqr == 0.0)
+                    continue;
+                sum += w(j) * rsqr * std::log10(std::sqrt(rsqr));
+            }
+
+            S(row, col) = a(0) + a(1)*xi2 + a(2)*yi2 + sum;
+        }
+    }
+
+    return S;
+}
+
+Eigen::MatrixXd createMinMatrix(PointView& view, int rows, int cols,
+                                double cell_size, BOX2D bounds)
+{
+    using namespace Dimension;
+    using namespace Eigen;
+
+    MatrixXd ZImin(rows, cols);
+    ZImin.setConstant(std::numeric_limits<double>::quiet_NaN());
+
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        double x = view.getFieldAs<double>(Id::X, i);
+        double y = view.getFieldAs<double>(Id::Y, i);
+        double z = view.getFieldAs<double>(Id::Z, i);
+
+        int c = Utils::clamp(static_cast<int>(floor(x-bounds.minx)/cell_size), 0, cols-1);
+        int r = Utils::clamp(static_cast<int>(floor(y-bounds.miny)/cell_size), 0, rows-1);
+
+        if (z < ZImin(r, c) || std::isnan(ZImin(r, c)))
+            ZImin(r, c) = z;
+    }
+
+    return ZImin;
+}
+
+Eigen::MatrixXd createMaxMatrix(PointView& view, int rows, int cols,
+                                double cell_size, BOX2D bounds)
+{
+    using namespace Dimension;
+    using namespace Eigen;
+
+    MatrixXd ZImax(rows, cols);
+    ZImax.setConstant(std::numeric_limits<double>::quiet_NaN());
+
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        double x = view.getFieldAs<double>(Id::X, i);
+        double y = view.getFieldAs<double>(Id::Y, i);
+        double z = view.getFieldAs<double>(Id::Z, i);
+
+        int c = Utils::clamp(static_cast<int>(floor(x-bounds.minx)/cell_size), 0, cols-1);
+        int r = Utils::clamp(static_cast<int>(floor(y-bounds.miny)/cell_size), 0, rows-1);
+
+        if (z > ZImax(r, c) || std::isnan(ZImax(r, c)))
+            ZImax(r, c) = z;
+    }
+
+    return ZImax;
+}
+
+Eigen::MatrixXd extendedLocalMinimum(PointView& view, int rows, int cols,
+                                     double cell_size, BOX2D bounds)
+{
+    using namespace Dimension;
+    using namespace Eigen;
+
+    // Index elevation values by row and column.
+    std::map<uint32_t, std::vector<double>> hash;
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        double x = view.getFieldAs<double>(Id::X, i);
+        double y = view.getFieldAs<double>(Id::Y, i);
+        double z = view.getFieldAs<double>(Id::Z, i);
+
+        int c = Utils::clamp(static_cast<int>(floor(x-bounds.minx)/cell_size), 0, cols-1);
+        int r = Utils::clamp(static_cast<int>(floor(y-bounds.miny)/cell_size), 0, rows-1);
+
+        hash[r*cols+c].push_back(z);
+    }
+
+    // For each grid cell, sort elevations and detect local minimum, rejecting
+    // low outliers.
+    MatrixXd ZImin(rows, cols);
+    ZImin.setConstant(std::numeric_limits<double>::quiet_NaN());
+    for (int c = 0; c < cols; ++c)
+    {
+        for (int r = 0; r < rows; ++r)
+        {
+            std::vector<double> cp(hash[r*cols+c]);
+            if (cp.empty())
+                continue;
+            std::sort(cp.begin(), cp.end());
+            if (cp.size() == 1)
+            {
+                ZImin(r, c) = cp[0];
+                continue;
+            }
+            for (size_t i = 0; i < cp.size()-1; ++i)
+            {
+                if (std::fabs(cp[i] - cp[i+1]) < 1.0)
+                {
+                    ZImin(r, c) = cp[i];
+                    break;
+                }
+            }
+        }
+    }
+
+    return ZImin;
+}
+
+Eigen::MatrixXd matrixClose(Eigen::MatrixXd data, int radius)
+{
+    using namespace Eigen;
+
+    MatrixXd data2 = padMatrix(data, radius);
+
+    int nrows = data2.rows();
+    int ncols = data2.cols();
+
+    MatrixXd minZ(nrows, ncols);
+    minZ.setConstant(std::numeric_limits<double>::max());
+    MatrixXd maxZ(nrows, ncols);
+    maxZ.setConstant(std::numeric_limits<double>::lowest());
+    for (auto c = 0; c < ncols; ++c)
+    {
+        int cs = Utils::clamp(c-radius, 0, ncols-1);
+        int ce = Utils::clamp(c+radius, 0, ncols-1);
+
+        for (auto r = 0; r < nrows; ++r)
+        {
+            int rs = Utils::clamp(r-radius, 0, nrows-1);
+            int re = Utils::clamp(r+radius, 0, nrows-1);
+
+            for (auto col = cs; col <= ce; ++col)
+            {
+                for (auto row = rs; row <= re; ++row)
+                {
+                    if ((row-r)*(row-r)+(col-c)*(col-c) > radius*radius)
+                        continue;
+                    if (data2(row, col) > maxZ(r, c))
+                        maxZ(r, c) = data2(row, col);
+                }
+            }
+        }
+    }
+    for (auto c = 0; c < ncols; ++c)
+    {
+        int cs = Utils::clamp(c-radius, 0, ncols-1);
+        int ce = Utils::clamp(c+radius, 0, ncols-1);
+
+        for (auto r = 0; r < nrows; ++r)
+        {
+            int rs = Utils::clamp(r-radius, 0, nrows-1);
+            int re = Utils::clamp(r+radius, 0, nrows-1);
+
+            for (auto col = cs; col <= ce; ++col)
+            {
+                for (auto row = rs; row <= re; ++row)
+                {
+                    if ((row-r)*(row-r)+(col-c)*(col-c) > radius*radius)
+                        continue;
+                    if (maxZ(row, col) < minZ(r, c))
+                        minZ(r, c) = maxZ(row, col);
+                }
+            }
+        }
+    }
+
+    return minZ.block(radius, radius, data.rows(), data.cols());
+}
+
+Eigen::MatrixXd matrixOpen(Eigen::MatrixXd data, int radius)
+{
+    using namespace Eigen;
+
+    MatrixXd data2 = padMatrix(data, radius);
+
+    int nrows = data2.rows();
+    int ncols = data2.cols();
+
+    MatrixXd minZ(nrows, ncols);
+    minZ.setConstant(std::numeric_limits<double>::max());
+    MatrixXd maxZ(nrows, ncols);
+    maxZ.setConstant(std::numeric_limits<double>::lowest());
+    for (auto c = 0; c < ncols; ++c)
+    {
+        int cs = Utils::clamp(c-radius, 0, ncols-1);
+        int ce = Utils::clamp(c+radius, 0, ncols-1);
+
+        for (auto r = 0; r < nrows; ++r)
+        {
+            int rs = Utils::clamp(r-radius, 0, nrows-1);
+            int re = Utils::clamp(r+radius, 0, nrows-1);
+
+            for (auto col = cs; col <= ce; ++col)
+            {
+                for (auto row = rs; row <= re; ++row)
+                {
+                    if ((row-r)*(row-r)+(col-c)*(col-c) > radius*radius)
+                        continue;
+                    if (data2(row, col) < minZ(r, c))
+                        minZ(r, c) = data2(row, col);
+                }
+            }
+        }
+    }
+    for (auto c = 0; c < ncols; ++c)
+    {
+        int cs = Utils::clamp(c-radius, 0, ncols-1);
+        int ce = Utils::clamp(c+radius, 0, ncols-1);
+
+        for (auto r = 0; r < nrows; ++r)
+        {
+            int rs = Utils::clamp(r-radius, 0, nrows-1);
+            int re = Utils::clamp(r+radius, 0, nrows-1);
+
+            for (auto col = cs; col <= ce; ++col)
+            {
+                for (auto row = rs; row <= re; ++row)
+                {
+                    if ((row-r)*(row-r)+(col-c)*(col-c) > radius*radius)
+                        continue;
+                    if (minZ(row, col) > maxZ(r, c))
+                        maxZ(r, c) = minZ(row, col);
+                }
+            }
+        }
+    }
+
+    return maxZ.block(radius, radius, data.rows(), data.cols());
+}
+
+Eigen::MatrixXd padMatrix(Eigen::MatrixXd d, int r)
+{
+    using namespace Eigen;
+
+    MatrixXd out = MatrixXd::Zero(d.rows()+2*r, d.cols()+2*r);
+    out.block(r, r, d.rows(), d.cols()) = d;
+    out.block(r, 0, d.rows(), r) =
+        d.block(0, 0, d.rows(), r).rowwise().reverse();
+    out.block(r, d.cols()+r, d.rows(), r) =
+        d.block(0, d.cols()-r, d.rows(), r).rowwise().reverse();
+    out.block(0, 0, r, out.cols()) =
+        out.block(r, 0, r, out.cols()).colwise().reverse();
+    out.block(d.rows()+r, 0, r, out.cols()) =
+        out.block(out.rows()-r-1, 0, r, out.cols()).colwise().reverse();
+
+    return out;
+}
+
+Eigen::MatrixXd pointViewToEigen(const PointView& view)
+{
+    Eigen::MatrixXd matrix(view.size(), 3);
+    for (PointId i = 0; i < view.size(); ++i)
+    {
+        matrix(i, 0) = view.getFieldAs<double>(Dimension::Id::X, i);
+        matrix(i, 1) = view.getFieldAs<double>(Dimension::Id::Y, i);
+        matrix(i, 2) = view.getFieldAs<double>(Dimension::Id::Z, i);
+    }
+    return matrix;
+}
+
+void writeMatrix(Eigen::MatrixXd data, const std::string& filename,
+                 const std::string& driver, double cell_size, BOX2D bounds,
+                 SpatialReference srs)
+{
+    using namespace Eigen;
+
+    gdal::registerDrivers();
+
+    std::array<double, 6> pixelToPos;
+    pixelToPos[0] = bounds.minx;
+    pixelToPos[1] = cell_size;
+    pixelToPos[2] = 0.0;
+    pixelToPos[3] = bounds.miny;
+    pixelToPos[4] = 0.0;
+    pixelToPos[5] = cell_size;
+    gdal::Raster raster(filename, driver, srs, pixelToPos);
+
+    gdal::GDALError err = raster.open(data.cols(), data.rows(), 1,
+                                      Dimension::Type::Float, -9999.0);
+
+    if (err != gdal::GDALError::None)
+        throw pdal_error(raster.errorMsg());
+
+    // Two things going on here. First, Eigen defaults to column major order,
+    // but GDALUtils expects row major, so we can convert it. Also, double
+    // doesn't seem to work for some reason, so maybe we go back and make the
+    // incoming matrix always be a float, but for now just cast it.
+    Eigen::Matrix<float, Dynamic, Dynamic, RowMajor> dataRowMajor;
+    dataRowMajor = data.cast<float>();
+
+    raster.writeBand((uint8_t *)dataRowMajor.data(), 1);
+}
+
+Eigen::MatrixXd cleanDSM(Eigen::MatrixXd data)
+{
+    using namespace Eigen;
+
+    auto CleanRasterScanLine = [](Eigen::MatrixXd data, Eigen::VectorXd datarow,
+                                  int mDim, int row, bool* prevSetCols,
+                                  bool* curSetCols)
+    {
+        auto InterpolateRasterPixelScanLine = [](Eigen::MatrixXd data, int mDim,
+                                              int x, int y, bool* prevSetCols)
+        {
+            const float c_background = FLT_MIN;
+            int yMinus, yPlus, xMinus, xPlus;
+            float tInterpValue;
+            bool tPrevInterp;
+
+            yMinus = y - 1;
+            yPlus = y + 1;
+            xMinus = x - 1;
+            xPlus = x + 1;
+
+            //North
+            tInterpValue = data(yMinus, x);
+            tPrevInterp = prevSetCols[x];
+            if (tInterpValue != c_background && tPrevInterp != true)
+                return tInterpValue;
+
+            //South
+            tInterpValue = data(yPlus, x);
+            if (tInterpValue != c_background)
+                return tInterpValue;
+
+            //East
+            tInterpValue = data(y, xPlus);
+            if (tInterpValue != c_background)
+                return tInterpValue;
+
+            //West
+            tInterpValue = data(y, xMinus);
+            if (tInterpValue != c_background)
+                return tInterpValue;
+
+            //NorthWest
+            tInterpValue = data(yMinus, xMinus);
+            tPrevInterp = prevSetCols[xMinus];
+            if (tInterpValue != c_background && tPrevInterp != true)
+                return tInterpValue;
+
+            //NorthWest
+            tInterpValue = data(yMinus, xPlus);
+            tPrevInterp = prevSetCols[xPlus];
+            if (tInterpValue != c_background && tPrevInterp != true)
+                return tInterpValue;
+
+            //SouthWest
+            tInterpValue = data(yPlus, xMinus);
+            if (tInterpValue != c_background)
+                return tInterpValue;
+
+            //SouthEast
+            tInterpValue = data(yPlus, xPlus);
+            if (tInterpValue != c_background)
+                return tInterpValue;
+
+            return 0.0f;
+        };
+
+        float tInterpValue;
+        float tValue;
+
+        int y = row;
+        for (int x = 1; x < mDim-1; ++x)
+        {
+            tValue = datarow(x);
+            const float c_background = FLT_MIN;
+
+            if (tValue == c_background)
+            {
+                tInterpValue = InterpolateRasterPixelScanLine(data, mDim, x, y,
+                               prevSetCols);
+                if (tInterpValue != c_background)
+                {
+                    curSetCols[x] = true;
+                    datarow(x) = tInterpValue;
+                }
+            }
+        }
+    };
+
+    {
+        int rows = data.rows();
+        int cols = data.cols();
+
+        std::unique_ptr<bool> prevSetCols(new bool[cols]);
+        std::unique_ptr<bool> curSetCols(new bool[cols]);
+
+        for (int y = 1; y < rows-1; ++y)
+        {
+            CleanRasterScanLine(data, data.row(1), cols, y,
+                                prevSetCols.get(), curSetCols.get());
+            memcpy(prevSetCols.get(), curSetCols.get(), cols);
+            memset(curSetCols.get(), 0, cols);
+        }
+    }
+
+    return data;
+}
+
+} // namespace eigen
+
+} // namespace pdal
diff --git a/pdal/EigenUtils.hpp b/pdal/EigenUtils.hpp
new file mode 100644
index 0000000..0120b95
--- /dev/null
+++ b/pdal/EigenUtils.hpp
@@ -0,0 +1,780 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/pdal_internal.hpp>
+#include <pdal/util/Bounds.hpp>
+
+#include <Eigen/Dense>
+
+#include <memory>
+#include <vector>
+
+namespace pdal
+{
+class PointView;
+class SpatialReference;
+
+typedef std::shared_ptr<PointView> PointViewPtr;
+
+namespace eigen
+{
+
+/**
+  Compute the centroid of a collection of points.
+
+  Computes the 3D centroid of a collection of points (specified by PointId)
+  sampled from the input PointView.
+
+  \code
+  // build 3D kd-tree
+  KD3Index kdi(view);
+  kdi.build();
+
+  // find the k-nearest neighbors of the first point (k=8)
+  auto ids = kdi.neighbors(0, 8);
+
+  // compute the centroid
+  auto centroid = computeCentroid(view, ids);
+  \endcode
+
+  \param view the source PointView.
+  \param ids a vector of PointIds specifying a subset of points.
+  \return the 3D centroid of the XYZ dimensions.
+*/
+PDAL_DLL Eigen::Vector3f computeCentroid(PointView& view,
+        std::vector<PointId> ids);
+
+/**
+  Compute the covariance matrix of a collection of points.
+
+  Computes the covariance matrix of a collection of points (specified by
+  PointId) sampled from the input PointView.
+
+  \code
+  // build 3D kd-tree
+  KD3Index kdi(view);
+  kdi.build();
+
+  // find the k-nearest neighbors of the first point (k=8)
+  auto ids = kdi.neighbors(0, 8);
+
+  // compute the covariance
+  auto cov = computeCovariance(view, ids);
+  \endcode
+
+  \param view the source PointView.
+  \param ids a vector of PointIds specifying a subset of points.
+  \return the covariance matrix of the XYZ dimensions.
+*/
+PDAL_DLL Eigen::Matrix3f computeCovariance(PointView& view,
+        std::vector<PointId> ids);
+
+/**
+  Compute second derivative in X direction using central difference method.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing of the matrix.
+  \return the second derivative in X.
+*/
+template <typename Derived>
+PDAL_DLL double centralDiffX2(const Eigen::MatrixBase<Derived>& data,
+                              double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to centralDiffX2");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to centralDiffX2");
+    return (data(1, 2) - 2.0 * data(1, 1) + data(1, 0)) / (spacing * spacing);
+}
+
+/**
+  Compute second derivative in Y direction using central difference method.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing of the matrix.
+  \return the second derivative in Y.
+*/
+template <typename Derived>
+PDAL_DLL double centralDiffY2(const Eigen::MatrixBase<Derived>& data,
+                              double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to centralDiffY2");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to centralDiffY2");
+    return (data(0, 1) - 2.0 * data(1, 1) + data(2, 1)) / (spacing * spacing);
+}
+
+/**
+  Compute mixed second derivative of X in the Y direction using central
+  difference method.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing of the matrix.
+  \return the second derivative of X in the Y direction.
+*/
+template <typename Derived>
+PDAL_DLL double centralDiffXY(const Eigen::MatrixBase<Derived>& data,
+                              double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to centralDiffXY");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to centralDiffXY");
+    return ((-1.0 * data(0, 0)) + data(0, 2) + data(2, 0) - data(2, 2)) /
+           (4.0 * spacing * spacing);
+}
+
+/**
+  Compute first derivative in X direction using central difference method.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing of the matrix.
+  \return the first derivative in X.
+*/
+template <typename Derived>
+PDAL_DLL double centralDiffX(const Eigen::MatrixBase<Derived>& data,
+                             double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to centralDiffX");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to centralDiffX");
+    return (data(1, 2) - data(1, 0)) / (2 * spacing);
+}
+
+/**
+  Compute first derivative in Y direction using central difference method.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing of the matrix.
+  \return the first derivative in Y.
+*/
+template <typename Derived>
+PDAL_DLL double centralDiffY(const Eigen::MatrixBase<Derived>& data,
+                             double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to centralDiffY");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to centralDiffY");
+    return (data(0, 1) - data(2, 1)) / (2 * spacing);
+}
+
+/**
+  Clean a raster.
+
+  \param data the Eigen matrix to clean.
+  \return the cleaned raster.
+*/
+PDAL_DLL Eigen::MatrixXd cleanDSM(Eigen::MatrixXd data);
+
+/**
+  Compute the rank of a collection of points.
+
+  Computes the rank of a collection of points (specified by PointId) sampled
+  from the input PointView. This method uses Eigen's JacobiSVD class to solve
+  the singular value decomposition and to estimate the rank using the given
+  threshold. A singular value will be considered nonzero if its absolute value
+  is greater than the product of the user-supplied threshold and the absolute
+  value of the maximum singular value.
+
+  More on JacobiSVD can be found at
+  https://eigen.tuxfamily.org/dox/classEigen_1_1JacobiSVD.html.
+
+  \code
+  // build 3D kd-tree
+  KD3Index kdi(view);
+  kdi.build();
+
+  // find the k-nearest neighbors of the first point (k=8)
+  auto ids = kdi.neighbors(0, 8);
+
+  // compute the rank using threshold of 0.01
+  auto rank = computeRank(view, ids, 0.01);
+  \endcode
+
+  \param view the source PointView.
+  \param ids a vector of PointIds specifying a subset of points.
+  \return the estimated rank.
+*/
+PDAL_DLL uint8_t computeRank(PointView& view, std::vector<PointId> ids,
+                             double threshold);
+
+/**
+  Create matrix of maximum Z values.
+
+  Create a DSM from the provided PointVieew, where each cell contains the
+  maximum Z value of all contributing elevations.
+
+  \param view the input PointView.
+  \param rows the number of rows.
+  \param cols the number of columns.
+  \param cell_size the edge length of raster cell.
+  \param bounds the 2D bounds of the PointView.
+  \return the matrix of maximum Z values.
+*/
+PDAL_DLL Eigen::MatrixXd createMaxMatrix(PointView& view, int rows, int cols,
+        double cell_size, BOX2D bounds);
+
+/**
+  Create matrix of minimum Z values.
+
+  Create a DTM from the provided PointVieew, where each cell contains the
+  minimum Z value of all contributing elevations.
+
+  \param view the input PointView.
+  \param rows the number of rows.
+  \param cols the number of columns.
+  \param cell_size the edge length of raster cell.
+  \param bounds the 2D bounds of the PointView.
+  \return the matrix of minimum Z values.
+*/
+PDAL_DLL Eigen::MatrixXd createMinMatrix(PointView& view, int rows, int cols,
+        double cell_size, BOX2D bounds);
+
+/**
+  Find local minimum elevations by extended local minimum.
+
+  Extended local minimum can be used to select seed points for ground return
+  segmentation. Several low-lying points are considered for each grid cell. The
+  difference between the lowest and second lowest points is evaluated and, if
+  the difference exceeds 1.0, the lowest points is considered an outlier. The
+  process continues with the next pair of lowest points (second and third
+  lowest). When the points under consideration are within the given tolerance,
+  the lowest is retained as a seed point.
+
+  \param view the input PointView.
+  \param rows the number of rows.
+  \param cols the cnumber of columns.
+  \param cell_size the edge length of raster cell.
+  \param bounds the 2D bounds of the PointView.
+  \return the matrix of minimum Z values (ignoring low outliers).
+*/
+PDAL_DLL Eigen::MatrixXd extendedLocalMinimum(PointView& view, int rows,
+        int cols, double cell_size, BOX2D bounds);
+
+/**
+  Perform a morphological closing of the input matrix.
+
+  Performs a morphological closing of the input matrix using a circular
+  structuring element of given radius. Data will be symmetrically padded at its
+  edges.
+
+  \param data the input matrix.
+  \param radius the radius of the circular structuring element.
+  \return the morphological closing of the input radius.
+*/
+PDAL_DLL Eigen::MatrixXd matrixClose(Eigen::MatrixXd data, int radius);
+
+/**
+  Perform a morphological opening of the input matrix.
+
+  Performs a morphological opening of the input matrix using a circular
+  structuring element of given radius. Data will be symmetrically padded at its
+  edges.
+
+  \param data the input matrix.
+  \param radius the radius of the circular structuring element.
+  \return the morphological opening of the input radius.
+*/
+PDAL_DLL Eigen::MatrixXd matrixOpen(Eigen::MatrixXd data, int radius);
+
+/**
+  Pad input matrix symmetrically.
+
+  Symmetrically pads the input matrix with given radius.
+
+  \param d the input matrix.
+  \param r the radius of the padding.
+  \return the padded matrix.
+*/
+PDAL_DLL Eigen::MatrixXd padMatrix(Eigen::MatrixXd d, int r);
+
+/**
+  Converts a PointView into an Eigen::MatrixXd.
+
+  This method exists (as of this writing) purely as a convenience method in the
+  API. It is not currently used in the PDAL codebase itself.
+*/
+PDAL_DLL Eigen::MatrixXd pointViewToEigen(const PointView& view);
+
+/**
+  Replace NaNs with mean.
+
+  \param data the incoming Eigen matrix.
+  \return the updated Eigen matrix.
+*/
+template <typename Derived>
+PDAL_DLL Eigen::MatrixXd replaceNaNs(const Eigen::MatrixBase<Derived>& data)
+{
+    Derived out(data);
+
+    double mean(0.0);
+    int nv(0);
+    for (int i = 0; i < data.size(); ++i)
+    {
+        if (std::isnan(data(i)))
+            continue;
+        mean += data(i);
+        nv++;
+    }
+    mean /= nv;
+
+    // replace NaNs with mean
+    for (int i = 0; i < data.size(); ++i)
+    {
+        if (std::isnan(data(i)))
+            out(i) = mean;
+    }
+
+    return out;
+}
+
+/**
+  Write Eigen Matrix as a GDAL raster.
+
+  \param data the Eigen matrix to write.
+  \param filename the filename of the output raster.
+  \param cell_size the edge length of raster cell.
+  \param bounds the 2D bounds of the data.
+  \param srs the spatial reference system of the data.
+*/
+PDAL_DLL void writeMatrix(Eigen::MatrixXd data, const std::string& filename,
+                          const std::string& driver, double cell_size,
+                          BOX2D bounds, SpatialReference srs);
+
+/**
+  Compute the numerical gradient in the X direction.
+
+  This is meant to mimic MATLAB's gradient function. The spacing between points
+  in each direction is assumed to be one.
+
+  \param data the input matrix.
+  \return the X component of the two-dimensional gradient.
+*/
+template <typename Derived>
+PDAL_DLL Derived gradX(const Eigen::MatrixBase<Derived>& A)
+{
+    Derived out = Derived::Zero(A.rows(), A.cols());
+
+    // Interior points are obtained by central differences.
+    out.block(0, 1, A.rows(), A.cols()-2) =
+        0.5 * (A.rightCols(A.cols()-2) - A.leftCols(A.cols()-2));
+
+    // Edge columns are obtained by single-sided differences.
+    out.col(0) = A.col(1) - A.col(0);
+    out.col(out.cols()-1) = A.col(A.cols()-1) - A.col(A.cols()-2);
+
+    return out;
+};
+
+/**
+  Compute the numerical gradient in the Y direction.
+
+  This is meant to mimic MATLAB's gradient function. The spacing between points
+  in each direction is assumed to be one.
+
+  \param data the input matrix.
+  \return the Y component of the two-dimensional gradient.
+*/
+template <typename Derived>
+PDAL_DLL Derived gradY(const Eigen::MatrixBase<Derived>& A)
+{
+    Derived out = Derived::Zero(A.rows(), A.cols());
+
+    // Interior points are obtained by central differences.
+    out.block(1, 0, A.rows()-2, A.cols()) =
+        0.5 * (A.bottomRows(A.rows()-2) - A.topRows(A.rows()-2));
+
+    // Edge rows are obtained by single-sided differences.
+    out.row(0) = A.row(1) - A.row(0);
+    out.row(out.rows()-1) = A.row(A.rows()-1) - A.row(A.rows()-2);
+
+    return out;
+};
+
+/**
+  Compute contour curvature for a single 3x3 matrix.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the spacing between cells (or edge length of a cell).
+  \return the contour curvature.
+*/
+template <typename Derived>
+PDAL_DLL double computeContour(const Eigen::MatrixBase<Derived>& data,
+                               double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeContour");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeContour");
+    Derived filled = replaceNaNs(data);
+    double zXX = centralDiffX2(filled, spacing);
+    double zYY = centralDiffY2(filled, spacing);
+    double zXY = centralDiffXY(filled, spacing);
+    double zX = centralDiffX(filled, spacing);
+    double zY = centralDiffY(filled, spacing);
+    double p = (zX * zX) + (zY * zY);
+    double q = p + 1;
+    return ((zXX*zX*zX)-(2*zXY*zX*zY)+(zYY*zY*zY))/(p*std::sqrt(q*q*q));
+}
+
+/**
+  Compute total curvature for a single 3x3 matrix.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the spacing between cells (or edge length of a cell).
+  \return the total curvature.
+*/
+template <typename Derived>
+PDAL_DLL double computeTotal(const Eigen::MatrixBase<Derived>& data,
+                             double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeTotal");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeTotal");
+    Derived filled = replaceNaNs(data);
+    double zXX = centralDiffX2(filled, spacing);
+    double zYY = centralDiffY2(filled, spacing);
+    double zXY = centralDiffXY(filled, spacing);
+    return (zXX * zXX) + (2.0 * zXY * zXY) + (zYY * zYY);
+}
+
+/**
+  Compute profile curvature for a single 3x3 matrix.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the spacing between cells (or edge length of a cell).
+  \return the profile curvature.
+*/
+template <typename Derived>
+PDAL_DLL double computeProfile(const Eigen::MatrixBase<Derived>& data,
+                               double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeProfile");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeProfile");
+    Derived filled = replaceNaNs(data);
+    double zXX = centralDiffX2(filled, spacing);
+    double zYY = centralDiffY2(filled, spacing);
+    double zXY = centralDiffXY(filled, spacing);
+    double zX = centralDiffX(filled, spacing);
+    double zY = centralDiffY(filled, spacing);
+    double p = (zX * zX) + (zY * zY);
+    double q = p + 1;
+    return ((zXX*zX*zX)+(2*zXY*zX*zY)+(zYY*zY*zY))/(p*std::sqrt(q*q*q));
+}
+
+/**
+  Compute tangential curvature for a single 3x3 matrix.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the spacing between cells (or edge length of a cell).
+  \return the tangential curvature.
+*/
+template <typename Derived>
+PDAL_DLL double computeTangential(const Eigen::MatrixBase<Derived>& data,
+                                  double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeTangential");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeTangential");
+    Derived filled = replaceNaNs(data);
+    double zXX = centralDiffX2(filled, spacing);
+    double zYY = centralDiffY2(filled, spacing);
+    double zXY = centralDiffXY(filled, spacing);
+    double zX = centralDiffX(filled, spacing);
+    double zY = centralDiffY(filled, spacing);
+    double p = (zX * zX) + (zY * zY);
+    double q = p + 1;
+    return ((zXX*zY*zY)-(2*zXY*zX*zY)+(zYY*zX*zX))/(p*std::sqrt(q));
+}
+
+/**
+  Compute first derivative in X using 3x3 window.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing.
+  \return the first derivative in X.
+*/
+template <typename Derived>
+PDAL_DLL double computeDZDX(const Eigen::MatrixBase<Derived>& data,
+                            double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeDZDX");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeDZDX");
+    return ((data(0, 2) + 2.0 * data(1, 2) + data(2, 2)) -
+            (data(0, 0) + 2.0 * data(1, 0) + data(2, 0))) / (8.0 * spacing);
+}
+
+/**
+  Compute first derivative in Y using 3x3 window.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing.
+  \return the first derivative in Y.
+*/
+template <typename Derived>
+PDAL_DLL double computeDZDY(const Eigen::MatrixBase<Derived>& data,
+                            double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeDZDY");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeDZDY");
+    return ((data(2, 0) + 2.0 * data(2, 1) + data(2, 2)) -
+            (data(0, 0) + 2.0 * data(0, 1) + data(0, 2)))  / (8.0 * spacing);
+}
+
+inline
+PDAL_DLL double computeSlopeRad(double dZdX, double dZdY)
+{
+    return std::atan(std::sqrt(std::pow(dZdX, 2) + std::pow(dZdY, 2)));
+}
+
+/**
+  Compute hillshade.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing.
+  \param illumAltitudeDegree the illumination altitude in degrees.
+  \param illumAzimuthDegree the illumination azimuth in degrees.
+  \return the local slope.
+*/
+template <typename Derived>
+PDAL_DLL double computeHillshade(const Eigen::MatrixBase<Derived>& data,
+                                 double spacing, double illumAltitudeDegree,
+                                 double illumAzimuthDegree)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeHillshade");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeHillshade");
+
+    double zenithRad = (90.0 - illumAltitudeDegree) *
+                       (3.14159265358979323846 / 180.0);
+    double azimuthMath = 360.0 - illumAzimuthDegree + 90.0;
+    if (azimuthMath >= 360.0)
+    {
+        azimuthMath = azimuthMath - 360.0;
+    }
+    double azimuthRad = azimuthMath * (3.14159265358979323846 / 180.0);
+
+    double dZdX = computeDZDX(data, spacing);
+    double dZdY = computeDZDY(data, spacing);
+    double slopeRad = computeSlopeRad(dZdX, dZdY);
+
+    double aspectRad(0.0);
+    if (dZdX == 0.0)
+    {
+        if (dZdY > 0.0)
+        {
+            aspectRad = 3.14159265358979323846 / 2.0;
+        }
+        else if (dZdY < 0.0)
+        {
+            aspectRad = (2 * 3.14159265358979323846) -
+                        (3.14159265358979323846 / 2.0);
+        }
+    }
+    else
+    {
+        aspectRad = std::atan2(dZdY, -dZdX);
+        if (aspectRad < 0.0)
+        {
+            aspectRad = 2.0 * 3.14159265358979323846 + aspectRad;
+        }
+    }
+
+    return std::cos(zenithRad) * std::cos(slopeRad) +
+           std::sin(zenithRad) * std::sin(slopeRad) * std::cos(azimuthRad - aspectRad);
+}
+
+/**
+  Compute aspect using finite difference method.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing.
+  \return the local aspect.
+*/
+template <typename Derived>
+PDAL_DLL double computeAspectFD(const Eigen::MatrixBase<Derived>& data,
+                                double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeAspectFD");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeAspectFD");
+    Derived filled = replaceNaNs(data);
+    double zX = centralDiffX(filled, spacing);
+    double zY = centralDiffY(filled, spacing);
+    double p = (zX * zX) + (zY * zY);
+    return 180.0 - std::atan(zY/zX) + 90.0 * (zX / std::fabs(zX));
+}
+
+/**
+  Compute aspect using D8 method.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing.
+  \return the local aspect.
+*/
+template <typename Derived>
+PDAL_DLL double computeAspectD8(const Eigen::MatrixBase<Derived>& data,
+                                double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeAspectD8");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeAspectD8");
+    Derived submatrix(3, 3);
+    submatrix.setConstant(data(1, 1));
+    submatrix -= data;
+    submatrix /= spacing;
+    submatrix(0, 1) /= std::sqrt(2.0);
+    submatrix(1, 0) /= std::sqrt(2.0);
+    submatrix(1, 2) /= std::sqrt(2.0);
+    submatrix(2, 1) /= std::sqrt(2.0);
+
+    // find max and convert to degrees
+    double maxval = std::numeric_limits<double>::lowest();
+    int j(0);
+    for (int i = 0; i < submatrix.size(); ++i)
+    {
+        if (std::isnan(submatrix(i)))
+            continue;
+        // skip center value, which will always be 0.0
+        if (i == 5)
+            continue;
+        if (submatrix(i) > maxval)
+        {
+            maxval = submatrix(i);
+            if (i == 1) j = 6;
+            if (i == 2) j = 5;
+            if (i == 3) j = 4;
+            if (i == 4) j = 7;
+            if (i == 6) j = 3;
+            if (i == 7) j = 0;
+            if (i == 8) j = 1;
+            if (i == 9) j = 2;
+        }
+    }
+
+    return std::pow(2.0, j);
+}
+
+/**
+  Compute slope using D8 method.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing.
+  \return the local slope.
+*/
+template <typename Derived>
+PDAL_DLL double computeSlopeD8(const Eigen::MatrixBase<Derived>& data,
+                               double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeSlopeD8");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeSlopeD8");
+    Derived submatrix(3, 3);
+    submatrix.setConstant(data(1, 1));
+    submatrix -= data;
+    submatrix /= spacing;
+    submatrix(0, 1) /= std::sqrt(2.0);
+    submatrix(1, 0) /= std::sqrt(2.0);
+    submatrix(1, 2) /= std::sqrt(2.0);
+    submatrix(2, 1) /= std::sqrt(2.0);
+
+    // find max and convert to degrees
+    double maxval = std::numeric_limits<double>::lowest();
+    for (int i = 0; i < submatrix.size(); ++i)
+    {
+        if (std::isnan(submatrix(i)))
+            continue;
+        if (submatrix(i) > maxval)
+            maxval = submatrix(i);
+    }
+    return 100.0 * maxval;
+}
+
+/**
+  Compute slope using finite difference method.
+
+  \param data the incoming Eigen matrix.
+  \param spacing the grid spacing.
+  \return the local slope.
+*/
+template <typename Derived>
+PDAL_DLL double computeSlopeFD(const Eigen::MatrixBase<Derived>& data,
+                               double spacing)
+{
+    if (data.rows() != 3 || data.cols() != 3)
+        throw pdal_error("Must provide 3x3 matrix to computeSlopeFD");
+    if (spacing <= 0.0)
+        throw pdal_error("Must provide positive spacing to computeSlopeFD");
+    Derived filled = replaceNaNs(data);
+    double zX = centralDiffX(filled, spacing);
+    double zY = centralDiffY(filled, spacing);
+    double p = (zX * zX) + (zY * zY);
+    return 100.0 * std::sqrt(p);
+}
+
+/**
+  Thin Plate Spline interpolation.
+
+  \param x the x coordinate of the input data.
+  \param y the y coordinate of the input data.
+  \param z the z coordinate of the input data.
+  \param xx the x coordinate of the points to be interpolated.
+  \param yy the y coordinate of the points to be interpolated.
+  \return the values of the interpolated data at xx and yy.
+*/
+PDAL_DLL Eigen::MatrixXd computeSpline(Eigen::MatrixXd x, Eigen::MatrixXd y,
+                                       Eigen::MatrixXd z, Eigen::MatrixXd xx,
+                                       Eigen::MatrixXd yy);
+
+
+} // namespace eigen
+
+} // namespace pdal
diff --git a/include/pdal/Filter.hpp b/pdal/Filter.hpp
similarity index 100%
rename from include/pdal/Filter.hpp
rename to pdal/Filter.hpp
diff --git a/pdal/FlexWriter.hpp b/pdal/FlexWriter.hpp
new file mode 100644
index 0000000..b881ff2
--- /dev/null
+++ b/pdal/FlexWriter.hpp
@@ -0,0 +1,145 @@
+/******************************************************************************
+* Copyright (c) 2015, Hobu Inc. (hobu at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of its contributors
+*       may be used to endorse or promote products derived from this
+*       software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/PDALUtils.hpp>
+#include <pdal/Scaling.hpp>
+#include <pdal/Writer.hpp>
+
+namespace pdal
+{
+
+class PDAL_DLL FlexWriter : public Writer
+{
+protected:
+    FlexWriter() : m_filenum(1)
+    {}
+
+    std::string m_filename;
+    Scaling m_scaling;
+
+    void validateFilename(PointTableRef table)
+    {
+        if (!table.supportsView() &&
+            (m_filename.find('#') != std::string::npos))
+        {
+            std::ostringstream oss;
+            oss << getName() << ": Can't write with template-based "
+                "filename using streaming point table.";
+            throw oss.str();
+        }
+    }
+
+private:
+    std::string::size_type m_hashPos;
+
+    virtual void writerInitialize(PointTableRef table)
+    {
+        Writer::writerInitialize(table);
+        m_hashPos = handleFilenameTemplate(m_filename);
+    }
+
+    std::string generateFilename()
+    {
+        std::string filename = m_filename;
+        if (m_hashPos != std::string::npos) {
+            std::string fileCount = std::to_string(m_filenum++);
+            filename.replace(m_hashPos, 1, fileCount);
+        }
+        return filename;
+    }
+
+#if (__GNUG__ < 4 || (__GNUG__ == 4 && __GNUG_MINOR__ < 7))
+#define final
+#endif
+
+    virtual void ready(PointTableRef table) final
+    {
+        readyTable(table);
+        if (m_hashPos == std::string::npos)
+        {
+            if (!table.spatialReferenceUnique())
+            {
+                std::ostringstream oss;
+                oss << getName() << ": Attempting to write '" << m_filename <<
+                    "' with multiple spatial references.";
+                Utils::printError(oss.str());
+            }
+            readyFile(generateFilename(), table.spatialReference());
+        }
+    }
+
+    // This essentially moves ready() and done() into write(), which means
+    // that they get executed once for each view.  The check for m_hashPos
+    // is a test to see if the filename specification is a template.  If it's
+    // not a template, ready() and done() are taken care of in the ready()
+    // and done() functions in this class.
+    virtual void write(const PointViewPtr view) final
+    {
+        if (m_hashPos != std::string::npos)
+            readyFile(generateFilename(), view->spatialReference());
+        writeView(view);
+        if (m_hashPos != std::string::npos)
+            doneFile();
+    }
+
+    virtual void done(PointTableRef table) final
+    {
+        if (m_hashPos == std::string::npos)
+            doneFile();
+        doneTable(table);
+    }
+
+#undef final
+
+    virtual void readyTable(PointTableRef table)
+    {}
+
+    virtual void doneTable(PointTableRef table)
+    {}
+
+    virtual void readyFile(const std::string& filename,
+        const SpatialReference& srs) = 0;
+    virtual void writeView(const PointViewPtr view) = 0;
+    virtual void doneFile()
+    {}
+
+    size_t m_filenum;
+
+    FlexWriter& operator=(const FlexWriter&); // not implemented
+    FlexWriter(const FlexWriter&); // not implemented
+};
+
+} // namespace pdal
+
diff --git a/pdal/GDALUtils.cpp b/pdal/GDALUtils.cpp
new file mode 100644
index 0000000..1d6608e
--- /dev/null
+++ b/pdal/GDALUtils.cpp
@@ -0,0 +1,818 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/GDALUtils.hpp>
+#include <pdal/SpatialReference.hpp>
+#include <pdal/util/Utils.hpp>
+
+#include <functional>
+#include <map>
+#include <mutex>
+
+#include <ogr_spatialref.h>
+
+#ifdef PDAL_COMPILER_MSVC
+#  pragma warning(disable: 4127)  // conditional expression is constant
+#endif
+
+namespace pdal
+{
+namespace gdal
+{
+
+namespace
+{
+
+Dimension::Type toPdalType(GDALDataType t)
+{
+    switch (t)
+    {
+        case GDT_Byte:
+            return Dimension::Type::Unsigned8;
+        case GDT_UInt16:
+            return Dimension::Type::Unsigned16;
+        case GDT_Int16:
+            return Dimension::Type::Signed16;
+        case GDT_UInt32:
+            return Dimension::Type::Unsigned32;
+        case GDT_Int32:
+            return Dimension::Type::Signed32;
+        case GDT_Float32:
+            return Dimension::Type::Float;
+        case GDT_Float64:
+            return Dimension::Type::Double;
+        case GDT_CInt16:
+        case GDT_CInt32:
+        case GDT_CFloat32:
+        case GDT_CFloat64:
+            throw pdal_error("GDAL complex float type unsupported.");
+        case GDT_Unknown:
+            throw pdal_error("GDAL unknown type unsupported.");
+        case GDT_TypeCount:
+            throw pdal_error("Detected bad GDAL data type.");
+    }
+    return Dimension::Type::None;
+}
+
+GDALDataType toGdalType(Dimension::Type t)
+{
+    switch (t)
+    {
+    case Dimension::Type::Unsigned8:
+    case Dimension::Type::Signed8:
+        return GDT_Byte;
+    case Dimension::Type::Unsigned16:
+        return GDT_UInt16;
+    case Dimension::Type::Signed16:
+        return GDT_Int16;
+    case Dimension::Type::Unsigned32:
+        return GDT_UInt32;
+    case Dimension::Type::Signed32:
+        return GDT_Int32;
+    case Dimension::Type::Float:
+        return GDT_Float32;
+    case Dimension::Type::Double:
+        return GDT_Float64;
+    case Dimension::Type::Unsigned64:
+    case Dimension::Type::Signed64:
+        throw pdal_error("PDAL 64-bit integer type unsupported.");
+    case Dimension::Type::None:
+        throw pdal_error("PDAL 'none' type unsupported.");
+	default:
+        throw pdal_error("Unrecognized PDAL dimension type.");
+	
+    }
+}
+
+} //unnamed namespace
+
+/**
+  Reproject a bounds box from a source projection to a destination.
+  \param box  Bounds box to be reprojected in-place.
+  \param srcSrs  String in WKT or other suitable format of box coordinates.
+  \param dstSrs  String in WKT or other suitable format to which
+    coordinates should be projected.
+  \return  Whether the reprojection was successful or not.
+*/
+bool reprojectBounds(BOX3D& box, const std::string& srcSrs,
+    const std::string& dstSrs)
+{
+    OGRSpatialReference src;
+    OGRSpatialReference dst;
+
+    OGRErr srcOk = OSRSetFromUserInput(&src, srcSrs.c_str());
+    OGRErr dstOk = OSRSetFromUserInput(&dst, dstSrs.c_str());
+    if (srcOk != OGRERR_NONE || dstOk != OGRERR_NONE)
+        return false;
+
+    OGRCoordinateTransformationH transform =
+        OCTNewCoordinateTransformation(&src, &dst);
+
+    bool ok = (OCTTransform(transform, 1, &box.minx, &box.miny, &box.minz) &&
+        OCTTransform(transform, 1, &box.maxx, &box.maxy, &box.maxz));
+    OCTDestroyCoordinateTransformation(transform);
+    return ok;
+}
+
+
+std::string lastError()
+{
+    return CPLGetLastErrorMsg();
+}
+
+
+static ErrorHandler* s_gdalErrorHandler= 0;
+
+void registerDrivers()
+{
+    static std::once_flag flag;
+
+    auto init = []() -> void
+    {
+        GDALAllRegister();
+        OGRRegisterAll();
+    };
+
+    std::call_once(flag, init);
+}
+
+
+void unregisterDrivers()
+{
+    GDALDestroyDriverManager();
+}
+
+
+ErrorHandler& ErrorHandler::getGlobalErrorHandler()
+{
+    static std::once_flag flag;
+
+    auto init = []()
+    {
+       s_gdalErrorHandler = new ErrorHandler();
+    };
+
+    std::call_once(flag, init);
+    return *s_gdalErrorHandler;
+}
+
+ErrorHandler::ErrorHandler() : m_errorNum(0)
+{
+    std::string value;
+
+    // Will return thread-local setting
+    const char* set = CPLGetConfigOption("CPL_DEBUG", "");
+    m_cplSet = (bool)set ;
+    m_debug = m_cplSet;
+
+    // Push on a thread-local error handler
+    CPLSetErrorHandler(&ErrorHandler::trampoline);
+}
+
+
+void ErrorHandler::set(LogPtr log, bool debug)
+{
+    setLog(log);
+    setDebug(debug);
+}
+
+
+void ErrorHandler::setLog(LogPtr log)
+{
+    m_log = log;
+}
+
+
+void ErrorHandler::setDebug(bool debug)
+{
+    m_debug = debug;
+
+    if (debug)
+        CPLSetThreadLocalConfigOption("CPL_DEBUG", "ON");
+    else
+        CPLSetThreadLocalConfigOption("CPL_DEBUG", NULL);
+}
+
+
+int ErrorHandler::errorNum()
+{
+    int errorNum = m_errorNum;
+    return errorNum;
+}
+
+void ErrorHandler::handle(::CPLErr level, int num, char const* msg)
+{
+    std::ostringstream oss;
+
+    m_errorNum = num;
+    if (level == CE_Failure || level == CE_Fatal)
+    {
+        oss << "GDAL failure (" << num << ") " << msg;
+        if (m_log)
+            m_log->get(LogLevel::Error) << oss.str() << std::endl;
+    }
+    else if (m_debug && level == CE_Debug)
+    {
+        oss << "GDAL debug: " << msg;
+        if (m_log)
+            m_log->get(LogLevel::Debug) << oss.str() << std::endl;
+    }
+}
+
+
+struct InvalidBand {};
+struct CantReadBlock {};
+struct CantWriteBlock {};
+
+/*
+  Slight abstraction of a GDAL raster band.
+*/
+class Band
+{
+public:
+    /**
+      Create an object for reading a band of a GDAL dataset.
+
+      \param ds  GDAL dataset handle.
+      \param bandNum  Band number (1-indexed).
+    */
+    Band(GDALDataset *ds, int bandNum, const std::string& name = "");
+
+    /**
+      Create an object for writing a band of data to a GDAL dataset.
+
+      \param ds  GDAL dataset handle.
+      \param width  Raster band width.
+      \param height  Raster band height.
+      \param bandNum  Band number in raster (1-indexed).
+      \param name  Band name
+    */
+    /**
+    Band(GDALDataset *ds, int width, int height, int bandNum,
+        const std::string& name = "");
+**/
+
+    /*
+      Read the band into the vector.  Reads a block at a time.  Each
+      block is either fully populated with data or a partial block.
+      Partial blocks appear at the X and Y margins when the total size in
+      the doesn't divide evenly by the block size for both the X and Y
+      dimensions.
+
+      \param  Data Vector into which the data should be read.  The vector is
+        resized as necessary.
+    */
+    void read(std::vector<uint8_t>& ptData);
+
+    /*
+      Write linearized data pointed to by \c data into the band.
+
+      \param data  Pointer to beginning of band
+    */
+    void write(const uint8_t *data);
+private:
+    GDALDataset *m_ds;  /// Dataset handle
+    int m_bandNum;  /// Band number.  Band numbers start at 1.
+    GDALRasterBand *m_band;  /// Band handle
+    int m_xTotalSize, m_yTotalSize;  /// Total size (x and y) of the raster
+    int m_xBlockSize, m_yBlockSize;  /// Size (x and y) of blocks
+    int m_xBlockCnt, m_yBlockCnt;    /// Number of blocks in each direction
+    size_t m_eltSize;                /// Size in bytes of each band element.
+    std::vector<uint8_t> m_buf;      /// Block read buffer.
+    std::string m_name;              /// Band name.
+
+    /*
+      Read a block's worth of data.
+
+      \param x  X coordinate of block to read.
+      \param y  Y coordinate of block to read.
+      \param data  Pointer to vector in which to store data.  Vector must
+        be sufficiently sized to hold all data.
+    */
+    void readBlock(int x, int y, uint8_t *data);
+
+    /**
+      Write a block of a band to a raster.
+
+      \param x  X coordinate of block.
+      \param y  Y coordinate of block.
+      \param data  Pointer to beginning of raster data (not block data).
+    */
+    void writeBlock(int x, int y, const uint8_t *data);
+};
+
+
+Band::Band(GDALDataset *ds, int bandNum, const std::string& name) : m_ds(ds),
+    m_bandNum(bandNum), m_xBlockSize(0), m_yBlockSize(0)
+{
+    m_band = m_ds->GetRasterBand(m_bandNum);
+    if (!m_band)
+        throw InvalidBand();
+
+    if (name.size())
+    {
+        m_band->SetDescription(name.data());
+        // We don't care about offset, but this sets the flag to indicate
+        // that the metadata has changed.
+        m_band->SetOffset(m_band->GetOffset(NULL) + .00001);
+        m_band->SetOffset(m_band->GetOffset(NULL) - .00001);
+    }
+
+    m_xTotalSize = m_band->GetXSize();
+    m_yTotalSize = m_band->GetYSize();
+
+    m_band->GetBlockSize(&m_xBlockSize, &m_yBlockSize);
+    GDALDataType t = m_band->GetRasterDataType();
+    m_eltSize = GDALGetDataTypeSize(t) / CHAR_BIT;
+    m_buf.resize(m_xBlockSize * m_yBlockSize * m_eltSize);
+
+    m_xBlockCnt = ((m_xTotalSize - 1) / m_xBlockSize) + 1;
+    m_yBlockCnt = ((m_yTotalSize - 1) / m_yBlockSize) + 1;
+}
+
+
+void Band::read(std::vector<uint8_t>& ptData)
+{
+    ptData.resize(m_xTotalSize * m_yTotalSize * m_eltSize);
+
+    uint8_t *data = ptData.data();
+    for (int y = 0; y < m_yBlockCnt; ++y)
+        for (int x = 0; x < m_xBlockCnt; ++x)
+            readBlock(x, y, data);
+}
+
+
+/*
+  Read a block's worth of data.
+
+  Read data into a block-sized buffer.  Then copy data from the block buffer
+  into the destination array at the proper location to build a complete
+  raster.
+
+  \param x  X coordinate of the block to read.
+  \param y  Y coordinate of the block to read.
+  \param data  Pointer to the data vector that contains the raster information.
+*/
+void Band::readBlock(int x, int y, uint8_t *data)
+{
+    if (m_band->ReadBlock(x, y, m_buf.data()) != CPLE_None)
+        throw CantReadBlock();
+
+    int xWidth = 0;
+    if (x == m_xBlockCnt - 1)
+        xWidth = m_xTotalSize % m_xBlockSize;
+    if (xWidth == 0)
+        xWidth = m_xBlockSize;
+
+    int yHeight = 0;
+    if (y == m_yBlockCnt - 1)
+        yHeight = m_yTotalSize % m_yBlockSize;
+    if (yHeight == 0)
+        yHeight = m_yBlockSize;
+
+    uint8_t *bp = m_buf.data();
+    // Go through rows copying data.  Increment the buffer pointer by the
+    // width of the row.
+    for (int row = 0; row < yHeight; ++row)
+    {
+        int wholeRows = m_xTotalSize * ((y * m_yBlockSize) + row);
+        int partialRows = m_xBlockSize * x;
+        uint8_t *dp = data + ((wholeRows + partialRows) * m_eltSize);
+        std::copy(bp, bp + (xWidth * m_eltSize), dp);
+
+        // Blocks are always full-sized, even if only some of the data is valid,
+        // so we use m_xBlockSize instead of xWidth.
+        bp += (m_xBlockSize * m_eltSize);
+    }
+}
+
+
+void Band::write(const uint8_t *data)
+{
+    for (int y = 0; y < m_yBlockCnt; ++y)
+        for (int x = 0; x < m_xBlockCnt; ++x)
+            writeBlock(x, y, data);
+}
+
+
+void Band::writeBlock(int x, int y, const uint8_t *data)
+{
+    int xWidth = 0;
+    if (x == m_xBlockCnt - 1)
+        xWidth = m_xTotalSize % m_xBlockSize;
+    if (xWidth == 0)
+        xWidth = m_xBlockSize;
+
+    int yHeight = 0;
+    if (y == m_yBlockCnt - 1)
+        yHeight = m_yTotalSize % m_yBlockSize;
+    if (yHeight == 0)
+        yHeight = m_yBlockSize;
+
+    // Go through rows copying data.
+    uint8_t *dstStart = m_buf.data();
+    // Go through rows copying data.  Increment the buffer pointer by the
+    // width of the row.
+    for (int row = 0; row < yHeight; ++row)
+    {
+        // Find the offset location in the source container.
+        int wholeRowElts = m_xTotalSize * ((y * m_yBlockSize) + row);
+        int partialRowElts = m_xBlockSize * x;
+
+        const uint8_t *srcStart = data +
+            ((wholeRowElts + partialRowElts) * m_eltSize);
+        const uint8_t *srcEnd = srcStart + (m_xBlockSize * m_eltSize);
+        std::copy(srcStart, srcEnd, dstStart);
+
+        // Blocks are always full-sized, even if only some of the data is valid,
+        // so we use m_xBlockSize instead of xWidth.
+        dstStart += (m_xBlockSize * m_eltSize);
+    }
+    if (m_band->WriteBlock(x, y, m_buf.data()) != CPLE_None)
+        throw CantWriteBlock();
+}
+
+Raster::Raster(const std::string& filename, const std::string& drivername)
+    : m_filename(filename)
+    , m_width(0)
+    , m_height(0)
+    , m_numBands(0)
+    , m_drivername(drivername)
+    , m_ds(0)
+{
+    m_forwardTransform.fill(0);
+    m_forwardTransform[1] = 1;
+    m_forwardTransform[5] = 1;
+    m_inverseTransform.fill(0);
+    m_inverseTransform[1] = 1;
+    m_inverseTransform[5] = 1;
+}
+
+
+Raster::Raster(const std::string& filename, const std::string& drivername,
+    const SpatialReference& srs, const std::array<double, 6> pixelToPos)
+    : m_filename(filename)
+    , m_width(0)
+    , m_height(0)
+    , m_numBands(0)
+    , m_drivername(drivername)
+    , m_forwardTransform(pixelToPos)
+    , m_srs(srs)
+    , m_ds(0)
+{}
+
+
+GDALError Raster::open(int width, int height, int numBands,
+    Dimension::Type type, double noData, StringList options)
+{
+    if (m_drivername.empty())
+        m_drivername = "GTiff";
+
+    m_width = width;
+    m_height = height;
+    m_numBands = numBands;
+
+    if (!GDALInvGeoTransform(m_forwardTransform.data(),
+        m_inverseTransform.data()))
+    {
+        m_errorMsg = "Geotransform for raster '" + m_filename + "' not "
+           "invertible";
+        return GDALError::NotInvertible;
+    }
+
+    GDALDriver *driver = GetGDALDriverManager()->GetDriverByName(
+        m_drivername.data());
+    if (!driver)
+    {
+        m_errorMsg = "Driver '" + m_drivername + "' not found.";
+        return GDALError::DriverNotFound;
+    }
+
+    std::string item;
+    const char *itemp = driver->GetMetadataItem(GDAL_DCAP_CREATE);
+    if (itemp)
+        item = itemp;
+    if (item != "YES")
+    {
+        m_errorMsg = "Requested driver '" + m_drivername + "' does not "
+            "support file creation.";
+        return GDALError::InvalidDriver;
+    }
+
+    std::vector<const char *> opts;
+
+    for (size_t i = 0; i < options.size(); ++i)
+    {
+        if (options[i].find("INTERLEAVE") == 0)
+        {
+            m_errorMsg = "INTERLEAVE GDAL driver option not supported.";
+            return GDALError::InvalidOption;
+        }
+        opts.push_back(options[i].data());
+    }
+    opts.push_back("INTERLEAVE=BAND");
+    opts.push_back(NULL);
+
+    m_ds = driver->Create(m_filename.data(), m_width, m_height, m_numBands,
+        toGdalType(type), const_cast<char **>(opts.data()));
+    if (m_ds == NULL)
+    {
+        m_errorMsg = "Unable to open GDAL datasource '" + m_filename + "'.";
+        return GDALError::CantCreate;
+    }
+
+    if (m_srs.valid())
+        m_ds->SetProjection(m_srs.getWKT().data());
+
+    m_ds->SetGeoTransform(m_forwardTransform.data());
+    for (int i = 0; i < m_numBands; ++i)
+    {
+        GDALRasterBand *band = m_ds->GetRasterBand(i + 1);
+        band->SetNoDataValue(noData);
+    }
+
+    return GDALError::None;
+}
+
+
+GDALError Raster::open()
+{
+    GDALError error = GDALError::None;
+    if (m_ds)
+        return error;
+
+#if (GDAL_VERSION_MAJOR > 1)
+    const char ** driverP = NULL;
+    const char *drivers[2] = {0};
+    if (!m_drivername.empty())
+    {
+        drivers[0] = m_drivername.c_str();
+        driverP = drivers;
+    }
+
+    m_ds = (GDALDataset *)GDALOpenEx(m_filename.c_str(), GA_ReadOnly, driverP,
+        nullptr, nullptr);
+#else
+    m_ds = (GDALDataset *)GDALOpen(m_filename.c_str(), GA_ReadOnly);
+#endif
+    if (m_ds == NULL)
+    {
+        m_errorMsg = "Unable to open GDAL datasource '" + m_filename + "'.";
+        return GDALError::CantOpen;
+    }
+
+    // An identity transform is returned on error.
+    if (m_ds->GetGeoTransform(m_forwardTransform.data()) != CE_None)
+    {
+        m_errorMsg = "Unable to get geotransform for raster '" +
+            m_filename + "'.";
+        error = GDALError::NoTransform;
+    }
+
+    if (!GDALInvGeoTransform(m_forwardTransform.data(),
+        m_inverseTransform.data()))
+    {
+        m_errorMsg = "Geotransform for raster '" + m_filename + "' not "
+           "invertible";
+        error = GDALError::NotInvertible;
+    }
+
+    m_width = m_ds->GetRasterXSize();
+    m_height = m_ds->GetRasterYSize();
+    m_numBands = m_ds->GetRasterCount();
+
+    if (computePDALDimensionTypes() == GDALError::InvalidBand)
+        error = GDALError::InvalidBand;
+    return error;
+}
+
+
+GDALError Raster::readBand(std::vector<uint8_t>& points, int nBand)
+{
+    try
+    {
+        Band(m_ds, nBand).read(points);
+    }
+    catch (InvalidBand)
+    {
+        std::stringstream oss;
+        oss << "Unable to get band " << nBand << " from raster '" <<
+            m_filename << "'.";
+        m_errorMsg = oss.str();
+        return GDALError::InvalidBand;
+    }
+    catch (CantReadBlock)
+    {
+        std::ostringstream oss;
+        oss << "Unable to read block for for raster '" << m_filename << "'.";
+        m_errorMsg = oss.str();
+        return GDALError::CantReadBlock;
+    }
+    return GDALError::None;
+}
+
+
+GDALError Raster::writeBand(const uint8_t *data, int nBand,
+    const std::string& name)
+{
+    try
+    {
+        Band(m_ds, nBand, name).write(data);
+    }
+    catch (CantWriteBlock)
+    {
+        std::ostringstream oss;
+        oss << "Unable to write block for for raster '" << m_filename << "'.";
+        m_errorMsg = oss.str();
+        return GDALError::CantWriteBlock;
+    }
+    return GDALError::None;
+}
+
+
+void Raster::pixelToCoord(int col, int row, std::array<double, 2>& output) const
+{
+    /**
+    double *xform = const_cast<double *>(m_forwardTransform.data());
+    GDALApplyGeoTransform(xform, col, row, &output[0], &output[1]);
+    **/
+
+    // from http://gis.stackexchange.com/questions/53617/how-to-find-lat-lon-values-for-every-pixel-in-a-geotiff-file
+    double c = m_forwardTransform[0];
+    double a = m_forwardTransform[1];
+    double b = m_forwardTransform[2];
+    double f = m_forwardTransform[3];
+    double d = m_forwardTransform[4];
+    double e = m_forwardTransform[5];
+
+    //ABELL - Not sure why this is right.  You can think of this like:
+    //   output[0] = a * (col + .5) + b * (row + .5) + c;
+    //   output[1] = d * (col + .5) + e * (row + .5) + f;
+    //   Is there some reason why you want to "move" the points in the raster
+    //   to a location between the rows/columns?  Seems that you would just
+    //   use 'c' and 'f' to shift everything a half-row and half-column if
+    //   that's what you wanted.
+    //   Also, this isn't what GDALApplyGeoTransform does.  And why aren't
+    //   we just calling GDALApplyGeoTransform?
+    output[0] = a*col + b*row + a*0.5 + b*0.5 + c;
+    output[1] = d*col + e*row + d*0.5 + e*0.5 + f;
+}
+
+
+// Determines the pixel/line position given an x/y.
+// No reprojection is done at this time.
+bool Raster::getPixelAndLinePosition(double x, double y,
+    int32_t& pixel, int32_t& line)
+{
+    pixel = (int32_t)std::floor(m_inverseTransform[0] +
+        (m_inverseTransform[1] * x) + (m_inverseTransform[2] * y));
+    line = (int32_t) std::floor(m_inverseTransform[3] +
+        (m_inverseTransform[4] * x) + (m_inverseTransform[5] * y));
+
+    // Return false if we're out of bounds.
+    return (pixel >= 0 && pixel < m_width &&
+        line >= 0 && line < m_height);
+}
+
+
+/*
+  Compute a vector of the PDAL datatypes that are stored in the raster
+  bands of a dataset.
+*/
+GDALError Raster::computePDALDimensionTypes()
+{
+    if (!m_ds)
+    {
+        m_errorMsg = "Raster not open.";
+        return GDALError::NotOpen;
+    }
+
+    m_types.clear();
+    for (int i = 0; i < m_numBands; ++i)
+    {
+        // Raster bands are numbered from 1.
+        GDALRasterBand *band = m_ds->GetRasterBand(i + 1);
+        if (!band)
+        {
+            std::ostringstream oss;
+
+            oss << "Unable to get band " << (i + 1) <<
+                " from raster data source '" << m_filename << "'.";
+            m_errorMsg = oss.str();
+            return GDALError::InvalidBand;
+        }
+        m_types.push_back(toPdalType(band->GetRasterDataType()));
+    }
+    return GDALError::None;
+}
+
+
+GDALError Raster::read(double x, double y, std::vector<double>& data)
+{
+    if (!m_ds)
+    {
+        m_errorMsg = "Raster not open.";
+        return GDALError::NotOpen;
+    }
+
+    int32_t pixel(0);
+    int32_t line(0);
+    data.resize(m_numBands);
+
+    std::array<double, 2> pix = { {0.0, 0.0} };
+
+    // No data at this x,y if we can't compute a pixel/line location
+    // for it.
+    if (!getPixelAndLinePosition(x, y, pixel, line))
+    {
+        m_errorMsg = "Requested location is not in the raster.";
+        return GDALError::NoData;
+    }
+
+    for (int i=0; i < m_numBands; ++i)
+    {
+        GDALRasterBandH b = GDALGetRasterBand(m_ds, i + 1);
+        if (GDALRasterIO(b, GF_Read, pixel, line, 1, 1,
+            &pix[0], 1, 1, GDT_Float64, 0, 0) == CE_None)
+        {
+            // we read a pixel put its values in our vector
+            data[i] = pix[0];
+        }
+    }
+
+    return GDALError::None;
+}
+
+
+SpatialReference Raster::getSpatialRef() const
+{
+    SpatialReference srs;
+
+    if (m_ds)
+        srs = SpatialReference(m_ds->GetProjectionRef());
+    return srs;
+}
+
+
+Raster::~Raster()
+{
+    close();
+}
+
+
+void Raster::close()
+{
+    delete m_ds;
+    m_ds = nullptr;
+    m_types.clear();
+}
+
+} // namespace gdal
+
+std::string transformWkt(std::string wkt, const SpatialReference& from,
+    const SpatialReference& to)
+{
+    //ABELL - Should this throw?  Return empty string?
+    if (from.empty() || to.empty())
+        return wkt;
+
+    gdal::SpatialRef fromRef(from.getWKT());
+    gdal::SpatialRef toRef(to.getWKT());
+    gdal::Geometry geom(wkt, fromRef);
+    geom.transform(toRef);
+    return geom.wkt();
+}
+
+} // namespace pdal
+
diff --git a/pdal/GDALUtils.hpp b/pdal/GDALUtils.hpp
new file mode 100644
index 0000000..284e504
--- /dev/null
+++ b/pdal/GDALUtils.hpp
@@ -0,0 +1,443 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/pdal_internal.hpp>
+#include <pdal/Dimension.hpp>
+#include <pdal/SpatialReference.hpp>
+#include <pdal/util/Bounds.hpp>
+
+#include <pdal/Log.hpp>
+
+#include <array>
+#include <functional>
+#include <sstream>
+#include <vector>
+
+#include <cpl_port.h>
+#include <gdal_priv.h>
+#include <cpl_vsi.h>
+#include <cpl_conv.h>
+#include <ogr_api.h>
+#include <ogr_srs_api.h>
+
+namespace pdal
+{
+
+class SpatialReference;
+
+namespace gdal
+{
+
+PDAL_DLL void registerDrivers();
+PDAL_DLL void unregisterDrivers();
+PDAL_DLL bool reprojectBounds(BOX3D& box, const std::string& srcSrs,
+    const std::string& dstSrs);
+PDAL_DLL std::string lastError();
+
+typedef std::shared_ptr<void> RefPtr;
+
+class SpatialRef
+{
+public:
+    SpatialRef()
+        { newRef(OSRNewSpatialReference("")); }
+    SpatialRef(const std::string& srs)
+    {
+        newRef(OSRNewSpatialReference(""));
+        OSRSetFromUserInput(get(), srs.data());
+    }
+
+    void setFromLayer(OGRLayerH layer)
+        {
+            if (layer)
+            {
+                OGRSpatialReferenceH s = OGR_L_GetSpatialRef(layer);
+                if (s)
+                {
+                    OGRSpatialReferenceH clone = OSRClone(s);
+                    newRef(clone);
+                }
+
+            }
+        }
+    operator bool () const
+        { return m_ref.get() != NULL; }
+    OGRSpatialReferenceH get() const
+        { return m_ref.get(); }
+    std::string wkt() const
+    {
+        char *pszWKT = NULL;
+        OSRExportToWkt(m_ref.get(), &pszWKT);
+        bool valid = (bool)*pszWKT;
+        std::string output(pszWKT);
+        CPLFree(pszWKT);
+        return output;
+    }
+
+    bool empty() const
+    {
+        return wkt().empty();
+    }
+
+private:
+    void newRef(void *v)
+    {
+        m_ref = RefPtr(v, [](void* t){ OSRDestroySpatialReference(t); } );
+    }
+
+    RefPtr m_ref;
+};
+
+class Geometry
+{
+public:
+    Geometry()
+        {}
+    Geometry(const std::string& wkt, const SpatialRef& srs)
+    {
+        OGRGeometryH geom;
+
+        char *p_wkt = const_cast<char *>(wkt.data());
+        OGRSpatialReferenceH ref = srs.get();
+        if (srs.empty())
+        {
+            ref = NULL;
+        }
+        bool isJson = wkt.find("{") != wkt.npos ||
+                      wkt.find("}") != wkt.npos;
+
+        if (!isJson)
+        {
+            OGRErr err = OGR_G_CreateFromWkt(&p_wkt, ref, &geom);
+            if (err != OGRERR_NONE)
+            {
+                std::cout << "wkt: " << wkt << std::endl;
+                std::ostringstream oss;
+                oss << "unable to construct OGR Geometry";
+                oss << " '" << CPLGetLastErrorMsg() << "'";
+                throw pdal::pdal_error(oss.str());
+            }
+        }
+        else
+        {
+            // Assume it is GeoJSON and try constructing from that
+            geom = OGR_G_CreateGeometryFromJson(p_wkt);
+
+            if (!geom)
+                throw pdal_error("Unable to create geometry from "
+                    "input GeoJSON");
+
+            OGR_G_AssignSpatialReference(geom, ref);
+        }
+
+        newRef(geom);
+    }
+
+    operator bool () const
+        { return get() != NULL; }
+    OGRGeometryH get() const
+        { return m_ref.get(); }
+
+    void transform(const SpatialRef& out_srs)
+    {
+        OGR_G_TransformTo(m_ref.get(), out_srs.get());
+    }
+
+    std::string wkt() const
+    {
+        char* p_wkt = 0;
+        OGRErr err = OGR_G_ExportToWkt(m_ref.get(), &p_wkt);
+        return std::string(p_wkt);
+    }
+
+    void setFromGeometry(OGRGeometryH geom)
+        {
+            if (geom)
+                newRef(OGR_G_Clone(geom));
+        }
+
+private:
+    void newRef(void *v)
+    {
+        m_ref = RefPtr(v, [](void* t){ OGR_G_DestroyGeometry(t); } );
+    }
+    RefPtr m_ref;
+};
+
+
+class PDAL_DLL ErrorHandler
+{
+public:
+
+    /**
+      Get the singleton error handler.
+
+      \return  Reference to the error handler.
+    */
+    static ErrorHandler& getGlobalErrorHandler();
+
+    /**
+      Set the log and debug state of the error handler.  This is
+      a convenience and is equivalent to calling setLog() and setDebug().
+
+      \param log  Log to write to.
+      \param doDebug  Debug state of the error handler.
+    */
+    void set(LogPtr log, bool doDebug);
+
+    /**
+      Set the log to which error/debug messages should be written.
+
+      \param log  Log to write to.
+    */
+    void setLog(LogPtr log);
+
+    /**
+      Set the debug state of the error handler.  Setting to true will also
+      set the environment variable CPL_DEBUG to "ON".  This will force GDAL
+      to emit debug error messages which will be logged by this handler.
+
+      \param doDebug  Whether we're setting or clearing the debug state.
+    */
+    void setDebug(bool doDebug);
+
+    /**
+      Get the last error and clear the error last error value.
+
+      \return  The last error number.
+    */
+    int errorNum();
+
+    static void CPL_STDCALL trampoline(::CPLErr code, int num, char const* msg)
+    {
+        ErrorHandler::getGlobalErrorHandler().handle(code, num, msg);
+    }
+
+    ErrorHandler();
+
+private:
+
+    void handle(::CPLErr level, int num, const char *msg);
+
+private:
+    bool m_debug;
+    pdal::LogPtr m_log;
+    int m_errorNum;
+    bool m_cplSet;
+
+};
+
+
+enum class GDALError
+{
+    None,
+    NotOpen,
+    CantOpen,
+    NoData,
+    InvalidBand,
+    NoTransform,
+    NotInvertible,
+    CantReadBlock,
+    InvalidDriver,
+    DriverNotFound,
+    CantCreate,
+    InvalidOption,
+    CantWriteBlock
+};
+
+class PDAL_DLL Raster
+{
+
+public:
+    /**
+      Constructor.
+
+      \param filename  Filename of raster file.
+      \param drivername  Optional name of driver to use to open raster file.
+    */
+    Raster(const std::string& filename, const std::string& drivername = "");
+
+    /**
+      Constructor.
+
+      \param filename  Filename of raster file.
+      \param drivername  Optional name of driver to use to open raster file.
+      \param srs  SpatialReference of the raster.
+      \param pixelToPos  Transformation matrix to convert raster positions to
+        geolocations.
+    */
+    Raster(const std::string& filename, const std::string& drivername,
+        const SpatialReference& srs, const std::array<double, 6> pixelToPos);
+
+
+    /**
+      Destructor.  Closes an open raster.
+    */
+    ~Raster();
+
+    /**
+      Open raster file for reading.
+    */
+    GDALError open();
+
+    /**
+      Open a raster for writing.
+
+      \param width  Width of the raster in cells (X direction)
+      \param height  Height of the raster in cells (Y direction)
+      \param numBands  Number of bands in the raster.
+      \param type  Datatype (int, float, etc.) of the raster data.
+      \param noData  Value that indiciates no data in a raster cell.
+      \param options  GDAL driver options.
+    */
+    GDALError open(int width, int height, int numBands, Dimension::Type type,
+        double noData, StringList options = StringList());
+
+    /**
+      Close the raster and deallocate the underlying dataset.
+    */
+    void close();
+
+    /**
+      Read an entire raster band (layer) into a vector.
+
+      \param band  Vector into which data will be read.  The vector will
+        be resized appropriately to hold the data.
+      \param nBand  Band number to read.  Band numbers start at 1.
+      \return Error code or GDALError::None.
+    */
+    GDALError readBand(std::vector<uint8_t>& band, int nBand);
+
+    /**
+      Write an entire raster band (layer) into raster to be written with GDAL.
+
+      \param data  Linearized raster data to be written.
+      \param nBand  Band number to write.
+      \param name  Name of the raster band.
+    */
+    GDALError writeBand(const uint8_t *data, int nBand,
+        const std::string& name = "");
+
+    /**
+      Read the data for each band at x/y into a vector of doubles.  x and y
+      are transformed to the basis of the raster before the data is fetched.
+
+      \param x  X position to read
+      \param y  Y position to read
+      \param data  Vector in which to store data.
+    */
+    GDALError read(double x, double y, std::vector<double>& data);
+
+    /**
+      Get a vector of dimensions that map to the bands of a raster.
+    */
+    std::vector<pdal::Dimension::Type> getPDALDimensionTypes() const
+       { return m_types; }
+
+    /**
+      Convert an X/Y raster position into geo-located position using the
+      raster's transformation matrix.
+
+      \param column  raster column whose position should be calculated
+      \param row  raster row whose position should be calculated
+      \param[out]  Array containing the geo-located position of the pixel.
+    */
+    void pixelToCoord(int column, int row, std::array<double, 2>& output) const;
+
+    /**
+      Get the spatial reference associated with the raster.
+
+      \return  The associated spatial reference.
+    */
+    SpatialReference getSpatialRef() const;
+
+    /**
+      Get the most recent error message.
+    */
+    std::string errorMsg() const
+        { return m_errorMsg; }
+
+    /**
+      Get the number of bands in the raster.
+
+      \return  The number of bands in the raster.
+    */
+    int bandCount() const
+        { return m_numBands; }
+
+    /**
+      Get the width of the raster (X direction)
+    */
+    int width() const
+        { return m_width; }
+
+    /**
+      Get the height of the raster (Y direction)
+    */
+    int height() const
+        { return m_height; }
+
+    std::string const& filename() { return m_filename; }
+
+private:
+    std::string m_filename;
+
+    int m_width;
+    int m_height;
+    int m_numBands;
+    std::string m_drivername;
+    std::array<double, 6> m_forwardTransform;
+    std::array<double, 6> m_inverseTransform;
+    SpatialReference m_srs;
+    GDALDataset *m_ds;
+
+    std::string m_errorMsg;
+    mutable std::vector<pdal::Dimension::Type> m_types;
+    std::vector<std::array<double, 2>> m_block_sizes;
+
+    bool getPixelAndLinePosition(double x, double y,
+        int32_t& pixel, int32_t& line);
+    GDALError computePDALDimensionTypes();
+};
+
+} // namespace gdal
+
+
+PDAL_DLL std::string transformWkt(std::string wkt, const SpatialReference& from,
+    const SpatialReference& to);
+
+} // namespace pdal
+
diff --git a/pdal/GEOSUtils.cpp b/pdal/GEOSUtils.cpp
new file mode 100644
index 0000000..3fb3458
--- /dev/null
+++ b/pdal/GEOSUtils.cpp
@@ -0,0 +1,166 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/GEOSUtils.hpp>
+#include <pdal/Log.hpp>
+
+#include <cstdarg>
+#include <functional>
+#include <map>
+#include <sstream>
+
+#ifdef PDAL_COMPILER_MSVC
+#  pragma warning(disable: 4127)  // conditional expression is constant
+#endif
+
+namespace pdal
+{
+namespace geos
+{
+
+std::unique_ptr<ErrorHandler> ErrorHandler::m_instance;
+
+// Allocating here instead of just making a static because I'm not really
+// sure what GEOS_init_r does and when it can be called.  It doesn't hurt
+// anything.
+ErrorHandler& ErrorHandler::get()
+{
+    if (!m_instance)
+        m_instance.reset(new ErrorHandler);
+    return *m_instance;
+}
+
+
+void ErrorHandler::vaErrorCb(const char *msg, ...)
+{
+    va_list args;
+    va_start(args, msg);
+    char buf[1024];
+    vsnprintf(buf, sizeof(buf), msg, args);
+    ErrorHandler::get().handle(buf, false);
+    va_end(args);
+}
+
+
+void ErrorHandler::vaNoticeCb(const char *msg, ...)
+{
+    va_list args;
+    va_start(args, msg);
+    char buf[1024];
+    vsnprintf(buf, sizeof(buf), msg, args);
+    ErrorHandler::get().handle(buf, true);
+    va_end(args);
+}
+
+
+ErrorHandler::ErrorHandler() : m_debug(false)
+{
+// #ifdef GEOS_init_r
+
+#if (GEOS_CAPI_VERSION_MINOR >= 9)
+
+    m_ctx = GEOS_init_r();
+
+    auto errorCb = [](const char *msg, void *userData)
+    {
+        get().handle(msg, false);
+    };
+    GEOSContext_setErrorMessageHandler_r(m_ctx, errorCb, NULL);
+
+    auto noticeCb = [](const char *msg, void *userData)
+    {
+        get().handle(msg, true);
+    };
+    GEOSContext_setNoticeMessageHandler_r(m_ctx, noticeCb, NULL);
+#else
+    m_ctx = initGEOS_r(NULL, NULL);
+    GEOSContext_setErrorHandler_r(m_ctx, vaErrorCb);
+    GEOSContext_setNoticeHandler_r(m_ctx, vaNoticeCb);
+#endif
+}
+
+
+ErrorHandler::~ErrorHandler()
+{
+#ifdef GEOS_finish_r
+    GEOS_finish_r(m_ctx);
+#else
+    finishGEOS_r(m_ctx);
+#endif
+}
+
+
+void ErrorHandler::set(LogPtr log, bool debug)
+{
+    setLog(log);
+    setDebug(debug);
+}
+
+
+void ErrorHandler::setDebug(bool debug)
+{
+    m_debug = debug;
+}
+
+
+void ErrorHandler::setLog(LogPtr log)
+{
+    m_log = log;
+}
+
+
+void ErrorHandler::handle(const char *msg, bool notice)
+{
+    std::ostringstream oss;
+    if (!notice)
+    {
+        oss << "GEOS failure: '" << msg << "'";
+        throw pdal_error(oss.str());
+    }
+    else if (m_debug)
+    {
+        oss << "GEOS debug: " << msg;
+        if (m_log)
+            m_log->get(LogLevel::Debug) << oss.str() << std::endl;
+    }
+}
+
+GEOSContextHandle_t ErrorHandler::ctx() const
+{
+    return m_ctx;
+}
+
+} // namespace geos
+} // namespace pdal
+
diff --git a/pdal/GEOSUtils.hpp b/pdal/GEOSUtils.hpp
new file mode 100644
index 0000000..d08bd14
--- /dev/null
+++ b/pdal/GEOSUtils.hpp
@@ -0,0 +1,107 @@
+/******************************************************************************
+* Copyright (c) 2015, Hobu Inc. (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+#pragma once
+
+#include <memory>
+
+#include <geos_c.h>
+
+#include <pdal/pdal_types.hpp>
+#include <pdal/Log.hpp>
+
+namespace pdal
+{
+
+namespace geos
+{
+
+class PDAL_DLL ErrorHandler
+{
+public:
+    ~ErrorHandler();
+
+    /**
+      Get the singleton error handler.
+
+      \return  Reference to the error handler.
+    */
+    static ErrorHandler& get();
+
+    /**
+      Set the log and debug state of the error handler.  This is a convenience
+      and is equivalent to calling setLog() and setDebug().
+
+      \param log  Log to write to.
+      \param doDebug  Debug state of the error handler.
+    */
+    void set(LogPtr log, bool doDebug);
+
+    /**
+      Set the log to which error/debug messages should be written.
+
+      \param log  Log to write to.
+    */
+    void setLog(LogPtr log);
+
+    /**
+      Set the debug state of the error handler.  If the error handler is set
+      to debug, output is logged instead of causing an exception.
+
+      \param debug  The debug state of the error handler.
+    */
+    void setDebug(bool debug);
+
+    /**
+      Get the GEOS context handle.
+
+      \return  The GEOS context handle.
+    */
+    GEOSContextHandle_t ctx() const;
+
+private:
+    ErrorHandler();
+
+    void handle(const char *msg, bool notice);
+    static void vaErrorCb(const char *msg, ...);
+    static void vaNoticeCb(const char *msg, ...);
+
+    GEOSContextHandle_t m_ctx;
+    bool m_debug;
+    LogPtr m_log;
+    static std::unique_ptr<ErrorHandler> m_instance;
+};
+
+} // namespace geos
+} // namespace pdal
+
diff --git a/pdal/Geometry.cpp b/pdal/Geometry.cpp
new file mode 100644
index 0000000..3689175
--- /dev/null
+++ b/pdal/Geometry.cpp
@@ -0,0 +1,344 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/Geometry.hpp>
+#include "cpl_string.h"
+
+#include <ogr_geometry.h>
+
+
+namespace pdal
+{
+
+Geometry::Geometry()
+    : m_prepGeom(0)
+    , m_geoserr(geos::ErrorHandler::get())
+{
+    geos::GeometryDeleter geom_del(m_geoserr);
+    GEOSGeomPtr p(GEOSGeom_createEmptyPolygon_r(m_geoserr.ctx()), geom_del);
+    m_geom.swap(p);
+}
+
+
+Geometry::Geometry(const std::string& wkt_or_json, SpatialReference ref)
+    : m_prepGeom(0)
+    , m_srs(ref)
+    , m_geoserr(geos::ErrorHandler::get())
+{
+    update(wkt_or_json, ref);
+}
+
+
+Geometry::~Geometry()
+{
+    m_geom.reset();
+    if (m_prepGeom)
+        GEOSPreparedGeom_destroy_r(m_geoserr.ctx(), m_prepGeom);
+    m_prepGeom = 0;
+}
+
+
+void Geometry::update(const std::string& wkt_or_json, SpatialReference ref)
+{
+    bool isJson = wkt_or_json.find("{") != wkt_or_json.npos ||
+                  wkt_or_json.find("}") != wkt_or_json.npos;
+
+    GEOSWKTReader* geosreader = GEOSWKTReader_create_r(m_geoserr.ctx());
+
+    if (!isJson)
+    {
+        geos::GeometryDeleter geom_del(m_geoserr);
+        GEOSGeomPtr p(GEOSWKTReader_read_r(m_geoserr.ctx(), geosreader, wkt_or_json.c_str()), geom_del);
+        m_geom.swap(p);
+    }
+    else
+    {
+        // Assume it is GeoJSON and try constructing from that
+        OGRGeometryH json = OGR_G_CreateGeometryFromJson(wkt_or_json.c_str());
+
+        if (!json)
+            throw pdal_error("Unable to create geometry from "
+                "input GeoJSON");
+
+        char* gdal_wkt(0);
+        OGRErr err = OGR_G_ExportToWkt(json, &gdal_wkt);
+
+        geos::GeometryDeleter geom_del(m_geoserr);
+        GEOSGeomPtr p(GEOSWKTReader_read_r(m_geoserr.ctx(), geosreader, gdal_wkt), geom_del);
+        m_geom.swap(p);
+
+        OGRFree(gdal_wkt);
+        OGR_G_DestroyGeometry(json);
+    }
+    prepare();
+
+    GEOSWKTReader_destroy_r(m_geoserr.ctx(), geosreader);
+}
+
+
+void Geometry::prepare()
+{
+    if (m_geom.get())
+    {
+        m_prepGeom = GEOSPrepare_r(m_geoserr.ctx(), m_geom.get());
+        if (!m_prepGeom)
+            throw pdal_error("unable to prepare geometry for "
+                "index-accelerated access");
+    }
+}
+
+
+Geometry& Geometry::operator=(const Geometry& input)
+{
+
+    if (&input!= this)
+    {
+        m_geoserr = input.m_geoserr;
+        m_srs = input.m_srs;
+        geos::GeometryDeleter geom_del(m_geoserr);
+        GEOSGeomPtr p(GEOSGeom_clone_r(m_geoserr.ctx(),  input.m_geom.get()), geom_del);
+        m_geom.swap(p);
+        prepare();
+    }
+    return *this;
+}
+
+
+Geometry::Geometry(const Geometry& input)
+    : m_srs(input.m_srs)
+    , m_geoserr(input.m_geoserr)
+{
+    assert(input.m_geom.get() != 0);
+    geos::GeometryDeleter geom_del(m_geoserr);
+    GEOSGeomPtr p(GEOSGeom_clone_r(m_geoserr.ctx(),  input.m_geom.get()), geom_del);
+    m_geom.swap(p);
+    assert(m_geom.get() != 0);
+    m_prepGeom = 0;
+    prepare();
+}
+
+
+Geometry::Geometry(GEOSGeometry* g, const SpatialReference& srs)
+    : m_srs(srs) , m_geoserr(geos::ErrorHandler::get())
+{
+    geos::GeometryDeleter geom_del(m_geoserr);
+    GEOSGeomPtr p(GEOSGeom_clone_r(m_geoserr.ctx(),  g), geom_del);
+    m_geom.swap(p);
+    prepare();
+}
+
+
+Geometry::Geometry(OGRGeometryH g, const SpatialReference& srs)
+    : m_srs(srs)
+    , m_geoserr(geos::ErrorHandler::get())
+{
+
+    OGRGeometry *ogr_g = (OGRGeometry*)g;
+
+    // Convert the the GDAL geom to WKB in order to avoid the version
+    // context and DLL boundary issues with exporting directoly to GEOS
+    // from GDAL
+    OGRwkbByteOrder bo =
+        GEOS_getWKBByteOrder() == GEOS_WKB_XDR ? wkbXDR : wkbNDR;
+    int wkbSize = ogr_g->WkbSize();
+    std::vector<unsigned char> wkb(wkbSize);
+
+    ogr_g->exportToWkb(bo, wkb.data());
+
+    GEOSWKBReader* reader = GEOSWKBReader_create_r(m_geoserr.ctx());
+
+    geos::GeometryDeleter geom_del(m_geoserr);
+    GEOSGeomPtr p(GEOSWKBReader_read_r(m_geoserr.ctx(),  reader, wkb.data(), wkbSize), geom_del);
+    m_geom.swap(p);
+    prepare();
+
+    GEOSWKBReader_destroy_r(m_geoserr.ctx(), reader);
+}
+
+
+
+Geometry Geometry::transform(const SpatialReference& ref) const
+{
+    if (m_srs.empty())
+        throw pdal_error("Geometry::transform failed due to m_srs being empty");
+    if (ref.empty())
+        throw pdal_error("Geometry::transform failed due to ref being empty");
+
+    gdal::SpatialRef fromRef(m_srs.getWKT());
+    gdal::SpatialRef toRef(ref.getWKT());
+    gdal::Geometry geom(wkt(12, true), fromRef);
+    geom.transform(toRef);
+    return Geometry(geom.wkt(), ref);
+}
+
+
+BOX3D Geometry::bounds() const
+{
+    uint32_t numInputDims;
+    BOX3D output;
+
+    GEOSGeometry* boundary = GEOSGeom_clone_r(m_geoserr.ctx(), m_geom.get());
+
+    // Smash out multi*
+    if (GEOSGeomTypeId_r(m_geoserr.ctx(), m_geom.get()) > 3)
+        boundary = GEOSEnvelope_r(m_geoserr.ctx(), m_geom.get());
+
+    GEOSGeometry const* ring = GEOSGetExteriorRing_r(m_geoserr.ctx(), boundary);
+    GEOSCoordSequence const* coords = GEOSGeom_getCoordSeq_r(m_geoserr.ctx(), ring);
+
+    GEOSCoordSeq_getDimensions_r(m_geoserr.ctx(), coords, &numInputDims);
+
+    uint32_t count(0);
+    GEOSCoordSeq_getSize_r(m_geoserr.ctx(), coords, &count);
+
+    double x(0.0);
+    double y(0.0);
+    double z(0.0);
+    for (unsigned i = 0; i < count; ++i)
+    {
+        GEOSCoordSeq_getOrdinate_r(m_geoserr.ctx(), coords, i, 0, &x);
+        GEOSCoordSeq_getOrdinate_r(m_geoserr.ctx(), coords, i, 1, &y);
+        if (numInputDims > 2)
+            GEOSCoordSeq_getOrdinate_r(m_geoserr.ctx(), coords, i, 2, &z);
+        output.grow(x, y, z);
+    }
+    GEOSGeom_destroy_r(m_geoserr.ctx(), boundary);
+
+    return output;
+}
+
+
+bool Geometry::equals(const Geometry& p, double tolerance) const
+{
+    return (bool) GEOSEqualsExact_r(m_geoserr.ctx(), m_geom.get(), p.m_geom.get(), tolerance);
+}
+
+
+bool Geometry::operator==(const Geometry& input) const
+{
+    return this->equals(input);
+}
+
+
+bool Geometry::operator!=(const Geometry& input) const
+{
+    return !(this->equals(input));
+}
+
+
+bool Geometry::valid() const
+{
+    int gtype = GEOSGeomTypeId_r(m_geoserr.ctx(), m_geom.get());
+    if (gtype != GEOS_POLYGON && gtype != GEOS_MULTIPOLYGON)
+        return false;
+
+    return (bool)GEOSisValid_r(m_geoserr.ctx(), m_geom.get());
+}
+
+
+std::string Geometry::validReason() const
+{
+    int gtype = GEOSGeomTypeId_r(m_geoserr.ctx(), m_geom.get());
+
+    char *reason = GEOSisValidReason_r(m_geoserr.ctx(), m_geom.get());
+    std::string output(reason);
+    GEOSFree_r(m_geoserr.ctx(), reason);
+    return output;
+}
+
+
+std::string Geometry::wkt(double precision, bool bOutputZ) const
+{
+    GEOSWKTWriter *writer = GEOSWKTWriter_create_r(m_geoserr.ctx());
+    GEOSWKTWriter_setRoundingPrecision_r(m_geoserr.ctx(), writer, (int)precision);
+    if (bOutputZ)
+        GEOSWKTWriter_setOutputDimension_r(m_geoserr.ctx(), writer, 3);
+
+    char *smoothWkt = GEOSWKTWriter_write_r(m_geoserr.ctx(), writer, m_geom.get());
+    std::string output(smoothWkt);
+    GEOSFree_r(m_geoserr.ctx(), smoothWkt);
+    GEOSWKTWriter_destroy_r(m_geoserr.ctx(), writer);
+    return output;
+}
+
+
+std::string Geometry::json(double precision) const
+{
+    std::ostringstream prec;
+    prec << precision;
+    char **papszOptions = NULL;
+    papszOptions = CSLSetNameValue(papszOptions, "COORDINATE_PRECISION",
+        prec.str().c_str() );
+
+    std::string w(wkt());
+
+    gdal::SpatialRef srs(m_srs.getWKT());
+    gdal::Geometry g(w, srs);
+
+    char* json = OGR_G_ExportToJsonEx(g.get(), papszOptions);
+
+    std::string output(json);
+    OGRFree(json);
+    return output;
+}
+
+
+std::ostream& operator<<(std::ostream& ostr, const Geometry& p)
+{
+    ostr << p.wkt();
+    return ostr;
+}
+
+
+std::istream& operator>>(std::istream& istr, Geometry& p)
+{
+
+    std::ostringstream oss;
+    oss << istr.rdbuf();
+
+    std::string wkt = oss.str();
+
+    try
+    {
+        p.update(wkt);
+    }
+    catch (pdal_error& err)
+    {
+        istr.setstate(std::ios::failbit);
+        throw;
+    }
+    return istr;
+}
+
+} // namespace geos
diff --git a/pdal/Geometry.hpp b/pdal/Geometry.hpp
new file mode 100644
index 0000000..7b6e45d
--- /dev/null
+++ b/pdal/Geometry.hpp
@@ -0,0 +1,148 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+#pragma once
+
+#include <pdal/GDALUtils.hpp>
+#include <pdal/GEOSUtils.hpp>
+#include <pdal/Log.hpp>
+#include <pdal/PointRef.hpp>
+#include <pdal/SpatialReference.hpp>
+#include <pdal/util/Bounds.hpp>
+
+#include <geos_c.h>
+
+#include <memory>
+
+namespace pdal
+{
+
+namespace geos {
+
+class ErrorHandler;
+
+
+struct GeometryDeleter
+{
+    explicit GeometryDeleter(pdal::geos::ErrorHandler& ctx)
+    : m_ctx(ctx)
+    {}
+
+    GeometryDeleter() : m_ctx(geos::ErrorHandler::get()) {};
+
+    GeometryDeleter& operator=(const GeometryDeleter& other)
+    {
+       if (&other!= this)
+           m_ctx = other.m_ctx;
+       return *this;
+    }
+
+    GeometryDeleter(const GeometryDeleter& other) : m_ctx(other.m_ctx) {};
+
+    void operator()(GEOSGeometry* geometry) const
+    {
+        GEOSGeom_destroy_r(m_ctx.ctx(), geometry);
+    }
+
+    pdal::geos::ErrorHandler& m_ctx;
+};
+
+} // namespace geos
+
+
+typedef std::unique_ptr<GEOSGeometry, geos::GeometryDeleter> GEOSGeomPtr;
+
+class PDAL_DLL Geometry
+{
+public:
+    Geometry();
+    virtual ~Geometry();
+    Geometry(const std::string& wkt_or_json,
+           SpatialReference ref = SpatialReference());
+
+    Geometry(GEOSGeometry* g, const SpatialReference& srs);
+    Geometry(OGRGeometryH g, const SpatialReference& srs);
+
+    Geometry(const Geometry&);
+    Geometry& operator=(const Geometry&);
+
+    OGRGeometryH getOGRHandle();
+
+
+    virtual void update(const std::string& wkt_or_json,
+        SpatialReference ref = SpatialReference());
+
+    void setSpatialReference( const SpatialReference& ref)
+        { m_srs = ref; }
+
+    const SpatialReference& getSpatialReference() const
+        { return m_srs; }
+
+    Geometry transform(const SpatialReference& ref) const;
+
+    bool equals(const Geometry& other, double tolerance=0.0001) const;
+    bool operator==(const Geometry& other) const;
+    bool operator!=(const Geometry& other) const;
+    bool operator<(const Geometry& other) const
+        { return wkt() < other.wkt(); }
+
+
+    virtual  bool valid() const;
+    virtual std::string validReason() const;
+
+    std::string wkt(double precision=8, bool bOutputZ=false) const;
+    std::string json(double precision=8) const;
+
+    BOX3D bounds() const;
+
+    operator bool () const
+        { return m_geom != NULL; }
+
+protected:
+
+    GEOSGeomPtr m_geom;
+    const GEOSPreparedGeometry *m_prepGeom;
+
+    SpatialReference m_srs;
+    geos::ErrorHandler& m_geoserr;
+
+    void prepare();
+
+    friend PDAL_DLL std::ostream& operator<<(std::ostream& ostr,
+        const Geometry& p);
+    friend PDAL_DLL std::istream& operator>>(std::istream& istr,
+        Geometry& p);
+};
+
+} // namespace pdal
+
diff --git a/pdal/KDIndex.hpp b/pdal/KDIndex.hpp
new file mode 100644
index 0000000..e6dee01
--- /dev/null
+++ b/pdal/KDIndex.hpp
@@ -0,0 +1,462 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <memory>
+
+#include <nanoflann/nanoflann.hpp>
+
+#include <pdal/PointView.hpp>
+
+namespace nanoflann
+{
+    template<typename Distance, class DatasetAdaptor, int DIM,
+        typename IndexType> class KDTreeSingleIndexAdaptor;
+
+    template<class T, class DataSource, typename _DistanceType>
+    struct L2_Adaptor;
+}
+
+namespace pdal
+{
+
+template<int DIM>
+class PDAL_DLL KDIndex
+{
+protected:
+    KDIndex(const PointView& buf) : m_buf(buf)
+    {}
+   
+    ~KDIndex()
+    {}
+
+public:
+    std::size_t kdtree_get_point_count() const
+        { return m_buf.size(); }
+
+    double kdtree_get_pt(const PointId idx, int dim) const;
+    double kdtree_distance(const double *p1, const PointId p2_idx,
+        size_t /*numDims*/) const;
+    template <class BBOX> bool kdtree_get_bbox(BBOX& bb) const;
+    void build()
+    {
+        m_index.reset(new my_kd_tree_t(DIM, *this,
+            nanoflann::KDTreeSingleIndexAdaptorParams(10, DIM)));
+        m_index->buildIndex();
+    }
+
+protected:
+    const PointView& m_buf;
+
+    typedef nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<
+        double, KDIndex, double>, KDIndex, -1, std::size_t> my_kd_tree_t;
+
+    std::unique_ptr<my_kd_tree_t> m_index;
+
+private:
+    KDIndex(const KDIndex&);
+    KDIndex& operator=(KDIndex&);
+};
+
+class PDAL_DLL KD2Index : public KDIndex<2>
+{
+public:
+    KD2Index(const PointView& buf) : KDIndex<2>(buf)
+    {
+        if (!buf.hasDim(Dimension::Id::X))
+            throw pdal_error("KD2Index: point view missing 'X' dimension.");
+        if (!buf.hasDim(Dimension::Id::Y))
+            throw pdal_error("KD2Index: point view missing 'Y' dimension.");
+    }
+
+    PointId neighbor(double x, double y)
+    {
+        std::vector<PointId> ids = neighbors(x, y, 1);
+        return (ids.size() ? ids[0] : 0);
+    }
+
+    PointId neighbor(PointId idx)
+    {
+        std::vector<PointId> ids = neighbors(idx, 1);
+        return (ids.size() ? ids[0] : 0);
+    }
+
+    PointId neighbor(PointRef &point)
+    {
+        std::vector<PointId> ids = neighbors(point, 1);
+        return (ids.size() ? ids[0] : 0);
+    }
+
+    std::vector<PointId> neighbors(double x, double y, point_count_t k)
+    {
+        k = std::min(m_buf.size(), k);
+        std::vector<PointId> output(k);
+        std::vector<double> out_dist_sqr(k);
+        nanoflann::KNNResultSet<double, PointId, point_count_t> resultSet(k);
+
+        resultSet.init(&output[0], &out_dist_sqr[0]);
+
+        std::vector<double> pt;
+        pt.push_back(x);
+        pt.push_back(y);
+        m_index->findNeighbors(resultSet, &pt[0], nanoflann::SearchParams(10));
+        return output;
+    }
+
+    std::vector<PointId> neighbors(PointId idx, point_count_t k)
+    {
+        double x = m_buf.getFieldAs<double>(Dimension::Id::X, idx);
+        double y = m_buf.getFieldAs<double>(Dimension::Id::Y, idx);
+ 
+        return neighbors(x, y, k);
+    }
+
+    std::vector<PointId> neighbors(PointRef &point, point_count_t k)
+    {
+        double x = point.getFieldAs<double>(Dimension::Id::X);
+        double y = point.getFieldAs<double>(Dimension::Id::Y);
+ 
+        return neighbors(x, y, k);
+    }
+
+    std::vector<PointId> radius(double const& x, double const& y,
+        double const& r) const
+    {
+        std::vector<PointId> output;
+        std::vector<std::pair<std::size_t, double>> ret_matches;
+        nanoflann::SearchParams params;
+        params.sorted = true;
+
+        std::vector<double> pt;
+        pt.push_back(x);
+        pt.push_back(y);
+
+        // Our distance metric is square distance, so we use the square of
+        // the radius.
+        const std::size_t count =
+            m_index->radiusSearch(&pt[0], r * r, ret_matches, params);
+
+        for (std::size_t i = 0; i < count; ++i)
+            output.push_back(ret_matches[i].first);
+        return output;
+    }
+
+    std::vector<PointId> radius(PointId idx, double const& r) const
+    {
+        double x = m_buf.getFieldAs<double>(Dimension::Id::X, idx);
+        double y = m_buf.getFieldAs<double>(Dimension::Id::Y, idx);
+
+        return radius(x, y, r);
+    }
+
+    std::vector<PointId> radius(PointRef &point, double const& r) const
+    {
+        double x = point.getFieldAs<double>(Dimension::Id::X);
+        double y = point.getFieldAs<double>(Dimension::Id::Y);
+
+        return radius(x, y, r);
+    }
+};
+
+class PDAL_DLL KD3Index : public KDIndex<3>
+{
+public:
+    KD3Index(const PointView& buf) : KDIndex<3>(buf)
+    {
+        if (!buf.hasDim(Dimension::Id::X))
+            throw pdal_error("KD3Index: point view missing 'X' dimension.");
+        if (!buf.hasDim(Dimension::Id::Y))
+            throw pdal_error("KD3Index: point view missing 'Y' dimension.");
+        if (!buf.hasDim(Dimension::Id::Z))
+            throw pdal_error("KD3Index: point view missing 'Z' dimension.");
+    }
+
+    PointId neighbor(double x, double y, double z)
+    {
+        std::vector<PointId> ids = neighbors(x, y, z, 1);
+        return (ids.size() ? ids[0] : 0);
+    }
+
+    PointId neighbor(PointId idx)
+    {
+        std::vector<PointId> ids = neighbors(idx, 1);
+        return (ids.size() ? ids[0] : 0);
+    }
+
+    PointId neighbor(PointRef &point)
+    {
+        std::vector<PointId> ids = neighbors(point, 1);
+        return (ids.size() ? ids[0] : 0);
+    }
+
+    std::vector<PointId> neighbors(double x, double y, double z,
+        point_count_t k)
+    {
+        k = std::min(m_buf.size(), k);
+        std::vector<PointId> output(k);
+        std::vector<double> out_dist_sqr(k);
+        nanoflann::KNNResultSet<double, PointId, point_count_t> resultSet(k);
+
+        resultSet.init(&output[0], &out_dist_sqr[0]);
+
+        std::vector<double> pt;
+        pt.push_back(x);
+        pt.push_back(y);
+        pt.push_back(z);
+        m_index->findNeighbors(resultSet, &pt[0], nanoflann::SearchParams(10));
+        return output;
+    }
+
+    std::vector<PointId> neighbors(PointId idx, point_count_t k)
+    {
+        double x = m_buf.getFieldAs<double>(Dimension::Id::X, idx);
+        double y = m_buf.getFieldAs<double>(Dimension::Id::Y, idx);
+        double z = m_buf.getFieldAs<double>(Dimension::Id::Z, idx);
+
+        return neighbors(x, y, z, k);
+    }
+
+    std::vector<PointId> neighbors(PointRef &point, point_count_t k)
+    {
+        double x = point.getFieldAs<double>(Dimension::Id::X);
+        double y = point.getFieldAs<double>(Dimension::Id::Y);
+        double z = point.getFieldAs<double>(Dimension::Id::Z);
+
+        return neighbors(x, y, z, k);
+    }
+   
+    void knnSearch(double x, double y, double z, point_count_t k,
+        std::vector<PointId> *indices, std::vector<double> *sqr_dists)
+    {
+        k = std::min(m_buf.size(), k);
+        nanoflann::KNNResultSet<double, PointId, point_count_t> resultSet(k);
+        
+        resultSet.init(&indices->front(), &sqr_dists->front());
+        
+        std::vector<double> pt;
+        pt.push_back(x);
+        pt.push_back(y);
+        pt.push_back(z);
+        m_index->findNeighbors(resultSet, &pt[0], nanoflann::SearchParams(10));
+    }
+
+    void knnSearch(PointId idx, point_count_t k, std::vector<PointId> *indices,
+        std::vector<double> *sqr_dists)
+    {
+        double x = m_buf.getFieldAs<double>(Dimension::Id::X, idx);
+        double y = m_buf.getFieldAs<double>(Dimension::Id::Y, idx);
+        double z = m_buf.getFieldAs<double>(Dimension::Id::Z, idx);
+
+        knnSearch(x, y, z, k, indices, sqr_dists);
+    }
+
+    void knnSearch(PointRef &point, point_count_t k, std::vector<PointId> *indices,
+        std::vector<double> *sqr_dists)
+    {
+        double x = point.getFieldAs<double>(Dimension::Id::X);
+        double y = point.getFieldAs<double>(Dimension::Id::Y);
+        double z = point.getFieldAs<double>(Dimension::Id::Z);
+
+        knnSearch(x, y, z, k, indices, sqr_dists);
+    }
+
+    std::vector<PointId> radius(double x, double y, double z, double r) const
+    {
+        std::vector<PointId> output;
+        std::vector<std::pair<std::size_t, double>> ret_matches;
+        nanoflann::SearchParams params;
+        params.sorted = true;
+
+        std::vector<double> pt;
+        pt.push_back(x);
+        pt.push_back(y);
+        pt.push_back(z);
+
+        // Our distance metric is square distance, so we use the square of
+        // the radius.
+        const std::size_t count =
+            m_index->radiusSearch(&pt[0], r * r, ret_matches, params);
+
+        for (std::size_t i = 0; i < count; ++i)
+            output.push_back(ret_matches[i].first);
+        return output;
+    }
+
+    std::vector<PointId> radius(PointId idx, double r) const
+    {
+        double x = m_buf.getFieldAs<double>(Dimension::Id::X, idx);
+        double y = m_buf.getFieldAs<double>(Dimension::Id::Y, idx);
+        double z = m_buf.getFieldAs<double>(Dimension::Id::Z, idx);
+
+        return radius(x, y, z, r);
+    }
+
+    std::vector<PointId> radius(PointRef &point, double r) const
+    {
+        double x = point.getFieldAs<double>(Dimension::Id::X);
+        double y = point.getFieldAs<double>(Dimension::Id::Y);
+        double z = point.getFieldAs<double>(Dimension::Id::Z);
+
+        return radius(x, y, z, r);
+    }
+
+};
+
+template<>
+inline
+double KDIndex<2>::kdtree_get_pt(const PointId idx, int dim) const
+{
+    if (idx >= m_buf.size())
+        return 0.0;
+
+    Dimension::Id id = Dimension::Id::Unknown;
+    switch (dim)
+    {
+    case 0:
+        id = Dimension::Id::X;
+        break;
+    case 1:
+        id = Dimension::Id::Y;
+        break;
+    default:
+        throw pdal_error("kdtree_get_pt: Request for invalid dimension "
+            "from nanoflann");
+    }
+    return m_buf.getFieldAs<double>(id, idx);
+}
+
+template<>
+inline
+double KDIndex<3>::kdtree_get_pt(const PointId idx, int dim) const
+{
+    if (idx >= m_buf.size())
+        return 0.0;
+
+    Dimension::Id id = Dimension::Id::Unknown;
+    switch (dim)
+    {
+    case 0:
+        id = Dimension::Id::X;
+        break;
+    case 1:
+        id = Dimension::Id::Y;
+        break;
+    case 2:
+        id = Dimension::Id::Z;
+        break;
+    default:
+        throw pdal_error("kdtree_get_pt: Request for invalid dimension "
+            "from nanoflann");
+    }
+    return m_buf.getFieldAs<double>(id, idx);
+}
+
+// nanoflann hands us a vector that represents the position of p1.  We fetch
+// the position of p2 and and compute the square distance.
+template<>
+inline double KDIndex<2>::kdtree_distance(const double *p1, const PointId idx,
+    size_t /*numDims*/) const
+{
+    double d0 = p1[0] - m_buf.getFieldAs<double>(Dimension::Id::X, idx);
+    double d1 = p1[1] - m_buf.getFieldAs<double>(Dimension::Id::Y, idx);
+
+    return (d0 * d0 + d1 * d1);
+}
+
+template<>
+inline double KDIndex<3>::kdtree_distance(const double *p1, const PointId idx,
+    size_t /*numDims*/) const
+{
+    double d0 = p1[0] - m_buf.getFieldAs<double>(Dimension::Id::X, idx);
+    double d1 = p1[1] - m_buf.getFieldAs<double>(Dimension::Id::Y, idx);
+    double d2 = p1[2] - m_buf.getFieldAs<double>(Dimension::Id::Z, idx);
+
+    return (d0 * d0 + d1 * d1 + d2 * d2);
+}
+
+
+template<>
+template <class BBOX>
+bool KDIndex<2>::kdtree_get_bbox(BBOX& bb) const
+{
+    if (m_buf.empty())
+    {
+        bb[0].low = 0.0;
+        bb[0].high = 0.0;
+        bb[1].low = 0.0;
+        bb[1].high = 0.0;
+    }
+    else
+    {
+        BOX2D bounds;
+        m_buf.calculateBounds(bounds);
+
+        bb[0].low = bounds.minx;
+        bb[0].high = bounds.maxx;
+        bb[1].low = bounds.miny;
+        bb[1].high = bounds.maxy;
+    }
+    return true;
+}
+
+template<>
+template <class BBOX>
+bool KDIndex<3>::kdtree_get_bbox(BBOX& bb) const
+{
+    if (m_buf.empty())
+    {
+        bb[0].low = 0.0;
+        bb[0].high = 0.0;
+        bb[1].low = 0.0;
+        bb[1].high = 0.0;
+        bb[2].low = 0.0;
+        bb[2].high = 0.0;
+    }
+    else
+    {
+        BOX3D bounds;
+        m_buf.calculateBounds(bounds);
+
+        bb[0].low = bounds.minx;
+        bb[0].high = bounds.maxx;
+        bb[1].low = bounds.miny;
+        bb[1].high = bounds.maxy;
+        bb[2].low = bounds.minz;
+        bb[2].high = bounds.maxz;
+    }
+    return true;
+}
+
+} // namespace pdal
diff --git a/pdal/Kernel.cpp b/pdal/Kernel.cpp
new file mode 100644
index 0000000..0c6988f
--- /dev/null
+++ b/pdal/Kernel.cpp
@@ -0,0 +1,442 @@
+/******************************************************************************
+* Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <cctype>
+#include <iostream>
+
+#include <pdal/Kernel.hpp>
+#include <pdal/Options.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/pdal_config.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include <pdal/pdal_config.hpp>
+
+#include <io/BufferReader.hpp>
+
+#include <memory>
+#include <vector>
+
+namespace pdal
+{
+
+namespace
+{
+
+bool parseOption(std::string o, std::string& stage, std::string& option,
+    std::string& value)
+{
+    value.clear();
+    if (o.size() < 2)
+        return false;
+    if (o[0] != '-' || o[1] != '-')
+        return false;
+
+    o = o.substr(2);
+
+    // Options are stage_type.stage_name.option_name
+    // stage_type is always lowercase stage_names start with lowercase and
+    // then are lowercase or digits.  Option names start with lowercase and
+    // then contain lowercase, digits or underscore.
+
+    // This awfulness is to work around the multiply-defined islower.  Seems
+    // a bit better than the cast solution.
+    auto islc = [](char c)
+        { return std::islower(c); };
+    auto islcOrDigit = [](char c)
+        { return std::islower(c) || std::isdigit(c); };
+
+    std::string::size_type pos = 0;
+    std::string::size_type count = 0;
+
+    // Get stage_type.
+    count = Utils::extract(o, pos, islc);
+    pos += count;
+    std::string stage_type = o.substr(0, pos);
+    if (stage_type != "readers" && stage_type != "writers" &&
+        stage_type != "filters")
+        return false;
+    if (pos >= o.length() || o[pos++] != '.')
+        return false;
+
+    // Get stage_name.
+    count = Utils::extract(o, pos, islcOrDigit);
+    if (std::isdigit(o[pos]))
+        return false;
+    pos += count;
+    stage = o.substr(0, pos);
+    if (pos >= o.length() || o[pos++] != '.')
+        return false;
+
+    // Get option name.
+    std::string::size_type optionStart = pos;
+    count = Option::parse(o, pos);
+    pos += count;
+    option = o.substr(optionStart, count);
+
+    // We've gotten a good option name, so return true, even if the value
+    // is missing.  The caller can handle the missing value if desired.
+    if (pos >= o.length() || o[pos++] != '=')
+        return true;
+
+    // The command-line parser takes care of quotes around an argument
+    // value and such.  May want to do something to handle escaped characters?
+    value = o.substr(pos);
+    return true;
+}
+
+} // unnamed namespace
+
+
+Kernel::Kernel() :
+    m_showTime(false)
+    , m_hardCoreDebug(false)
+    , m_visualize(false)
+{}
+
+
+std::ostream& operator<<(std::ostream& ostr, const Kernel& kernel)
+{
+    ostr << "  Name: " << kernel.getName() << std::endl;
+    return ostr;
+}
+
+
+void Kernel::doSwitches(const StringList& cmdArgs, ProgramArgs& args)
+{
+    OptionsMap& stageOptions = m_manager.stageOptions();
+    StringList stringArgs;
+
+    // Scan the argument vector for extra stage options.  Pull them out and
+    // stick them in the list.  Let the ProgramArgs handle everything else.
+    // NOTE: This depends on the format being "option=value" rather than
+    //   "option value".  This is what we've always expected, so no problem,
+    //   but it would be better to be more flexible.
+    for (size_t i = 0; i < cmdArgs.size(); ++i)
+    {
+        std::string stageName, opName, value;
+
+        if (parseOption(cmdArgs[i], stageName, opName, value))
+        {
+            if (value.empty())
+            {
+                std::ostringstream oss;
+                oss << "Stage option '" << stageName << "." << opName <<
+                    "' must be specified " << " as --" << stageName << "." <<
+                    opName << "=<value>" << ".";
+                throw pdal_error(oss.str());
+            }
+            Option op(opName, value);
+            stageOptions[stageName].add(op);
+        }
+        else
+            stringArgs.push_back(cmdArgs[i]);
+    }
+
+    try
+    {
+        // parseSimple allows us to scan for the help option without
+        // raising exception about missing arguments and so on.
+        // It also removes consumed args from the arg list, so for now,
+        // parse a copy that will be ignored by parse().
+        ProgramArgs hargs;
+        hargs.add("help,h", "Print help message", m_showHelp);
+        hargs.parseSimple(stringArgs);
+
+        addBasicSwitches(args);
+        addSwitches(args);
+        if (!m_showHelp)
+            args.parse(stringArgs);
+    }
+    catch (arg_error& e)
+    {
+        throw pdal_error(getName() + ": " + e.m_error);
+    }
+}
+
+
+int Kernel::doStartup()
+{
+    return 0;
+}
+
+
+int Kernel::doExecution(ProgramArgs& args)
+{
+    if (m_hardCoreDebug)
+    {
+        int status = innerRun(args);
+        return status;
+    }
+
+    int status = 1;
+
+    try
+    {
+        status = innerRun(args);
+    }
+    catch (pdal::pdal_error const& e)
+    {
+        Utils::printError(e.what());
+        return 1;
+    }
+    catch (std::exception const& e)
+    {
+        Utils::printError(e.what());
+        return 1;
+    }
+    catch (...)
+    {
+        Utils::printError("Caught unexpected exception.");
+        return 1;
+    }
+
+    return status;
+}
+
+
+// this just wraps ALL the code in total catch block
+int Kernel::run(const StringList& cmdArgs, LogPtr& log)
+{
+    m_log = log;
+    m_manager.setLog(m_log);
+
+    ProgramArgs args;
+
+    try
+    {
+        doSwitches(cmdArgs, args);
+    }
+    catch (const pdal_error& e)
+    {
+        Utils::printError(e.what());
+        return 1;
+    }
+
+    if (m_showHelp)
+    {
+        outputHelp(args);
+        return 0;
+    }
+
+    int startup_status = doStartup();
+    if (startup_status)
+        return startup_status;
+
+    return doExecution(args);
+}
+
+
+int Kernel::innerRun(ProgramArgs& args)
+{
+    try
+    {
+        // do any user-level sanity checking
+        validateSwitches(args);
+    }
+    catch (pdal_error e)
+    {
+        Utils::printError(e.what());
+        outputHelp(args);
+        return -1;
+    }
+
+    parseCommonOptions();
+    return execute();
+}
+
+
+bool Kernel::isVisualize() const
+{
+    return m_visualize;
+}
+
+
+void Kernel::visualize(PointViewPtr view)
+{
+    PipelineManager manager;
+
+    manager.commonOptions() = m_manager.commonOptions();
+    manager.stageOptions() = m_manager.stageOptions();
+
+    BufferReader& reader =
+        static_cast<BufferReader&>(manager.makeReader("", "readers.buffer"));
+    reader.addView(view);
+
+    Stage& writer = manager.makeWriter("", "writers.pclvisualizer", reader);
+
+    PointTable table;
+    writer.prepare(table);
+    writer.execute(table);
+}
+
+/*
+void Kernel::visualize(PointViewPtr input_view, PointViewPtr output_view) const
+{
+#ifdef PDAL_HAVE_PCL_VISUALIZE
+    int viewport = 0;
+
+    // Determine XYZ bounds
+    BOX3D const& input_bounds = input_view->calculateBounds();
+    BOX3D const& output_bounds = output_view->calculateBounds();
+
+    // Convert PointView to a PCL PointCloud
+    pcl::PointCloud<pcl::PointXYZ>::Ptr input_cloud(
+        new pcl::PointCloud<pcl::PointXYZ>);
+    pclsupport::PDALtoPCD(
+        const_cast<PointViewPtr>(*input_view), *input_cloud, input_bounds);
+    pcl::PointCloud<pcl::PointXYZ>::Ptr output_cloud(
+        new pcl::PointCloud<pcl::PointXYZ>);
+    pclsupport::PDALtoPCD(
+        const_cast<PointViewPtr>(*output_view), *output_cloud, output_bounds);
+
+    // Create PCLVisualizer
+    std::shared_ptr<pcl::visualization::PCLVisualizer> p(
+        new pcl::visualization::PCLVisualizer("3D Viewer"));
+
+    // Set background to black
+    p->setBackgroundColor(0, 0, 0);
+
+    // Use Z dimension to colorize points
+    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZ>
+        input_color(input_cloud, "z");
+    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZ>
+        output_color(output_cloud, "z");
+
+    // Add point cloud to the viewer with the Z dimension color handler
+    p->createViewPort(0, 0, 0.5, 1, viewport);
+    p->addPointCloud<pcl::PointXYZ> (input_cloud, input_color, "cloud");
+    p->createViewPort(0.5, 0, 1, 1, viewport);
+    p->addPointCloud<pcl::PointXYZ> (output_cloud, output_color, "cloud1");
+
+    p->resetCamera();
+
+    while (!p->wasStopped())
+    {
+        p->spinOnce(100);
+        std::this_thread::sleep_for(std::chrono::microseconds(100000));
+    }
+#endif
+}
+*/
+
+
+void Kernel::parseCommonOptions()
+{
+    Options& options = m_manager.commonOptions();
+
+    if (m_visualize)
+        options.add("visualize", m_visualize);
+}
+
+
+void Kernel::outputHelp(ProgramArgs& args)
+{
+    std::cout << "usage: " << "pdal " << getShortName() << " [options] " <<
+        args.commandLine() << std::endl;
+
+    std::cout << "options:" << std::endl;
+    args.dump(std::cout, 2, Utils::screenWidth());
+
+    //ABELL - Fix me.
+
+    std::cout <<"\nFor more information, see the full documentation for "
+        "PDAL at http://pdal.io/\n" << std::endl;
+}
+
+
+void Kernel::addBasicSwitches(ProgramArgs& args)
+{
+    args.add("developer-debug",
+        "Enable developer debug (don't trap exceptions)", m_hardCoreDebug);
+    args.add("label", "A string to label the process with", m_label);
+
+    args.add("visualize", "Visualize result", m_visualize);
+    args.add("driver", "Override reader driver", m_driverOverride, "");
+}
+
+Stage& Kernel::makeReader(const std::string& inputFile, std::string driver)
+{
+    return m_manager.makeReader(inputFile, driver);
+}
+
+
+Stage& Kernel::makeReader(const std::string& inputFile, std::string driver,
+    Options options)
+{
+    return m_manager.makeReader(inputFile, driver, options);
+}
+
+
+Stage& Kernel::makeFilter(const std::string& driver)
+{
+    return m_manager.makeFilter(driver);
+}
+
+
+Stage& Kernel::makeFilter(const std::string& driver, Stage& parent)
+{
+    return m_manager.makeFilter(driver, parent);
+}
+
+
+Stage& Kernel::makeFilter(const std::string& driver, Stage& parent,
+    Options options)
+{
+    return m_manager.makeFilter(driver, parent, options);
+}
+
+
+Stage& Kernel::makeWriter(const std::string& outputFile, Stage& parent,
+    std::string driver)
+{
+    return m_manager.makeWriter(outputFile, driver, parent);
+}
+
+
+Stage& Kernel::makeWriter(const std::string& outputFile, Stage& parent,
+    std::string driver, Options options)
+{
+    return m_manager.makeWriter(outputFile, driver, parent, options);
+}
+
+
+bool Kernel::test_parseOption(std::string o, std::string& stage,
+    std::string& option, std::string& value)
+{
+    return parseOption(o, stage, option, value);
+}
+
+} // namespace pdal
diff --git a/pdal/Kernel.hpp b/pdal/Kernel.hpp
new file mode 100644
index 0000000..46f1265
--- /dev/null
+++ b/pdal/Kernel.hpp
@@ -0,0 +1,141 @@
+/******************************************************************************
+* Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <cstdint>
+#include <iosfwd>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <pdal/PipelineManager.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+namespace pdal
+{
+
+class Options;
+class PointView;
+
+typedef std::shared_ptr<PointView> PointViewPtr;
+
+//
+// The application base class gives us these common options:
+//    --help / -h
+//    --verbose / -v
+//    --version
+//
+class PDAL_DLL Kernel
+{
+    FRIEND_TEST(KernelTest, parseOption);
+
+public:
+    virtual ~Kernel()
+    {}
+
+    // call this, to start the machine
+    int run(const StringList& cmdArgs, LogPtr& log);
+
+    virtual std::string getName() const = 0;
+    std::string getShortName() const
+    {
+        StringList names = Utils::split2(getName(), '.');
+        return names.size() == 2 ? names[1] : std::string();
+    }
+    bool isVisualize() const;
+    void visualize(PointViewPtr view);
+
+protected:
+    // this is protected; your derived class ctor will be the public entry point
+    Kernel();
+    Stage& makeReader(const std::string& inputFile, std::string driver);
+    Stage& makeReader(const std::string& inputFile, std::string driver,
+        Options options);
+    Stage& makeFilter(const std::string& driver, Stage& parent);
+    Stage& makeFilter(const std::string& driver, Stage& parent,
+        Options options);
+    Stage& makeFilter(const std::string& driver);
+    Stage& makeWriter(const std::string& outputFile, Stage& parent,
+        std::string driver);
+    Stage& makeWriter(const std::string& outputFile, Stage& parent,
+        std::string driver, Options options);
+
+public:
+    virtual void addSwitches(ProgramArgs& args)
+    {}
+
+    // implement this, to do sanity checking of cmd line
+    // will throw if the user gave us bad options
+    virtual void validateSwitches(ProgramArgs& args)
+    {}
+
+    // implement this, to do your actual work
+    // it will be wrapped in a global catch try/block for you
+    virtual int execute() = 0;
+
+protected:
+    LogPtr m_log;
+    PipelineManager m_manager;
+    std::string m_driverOverride;
+
+private:
+    int innerRun(ProgramArgs& args);
+    void outputHelp(ProgramArgs& args);
+    void outputVersion();
+    void addBasicSwitches(ProgramArgs& args);
+    void parseCommonOptions();
+
+    void doSwitches(const StringList& cmdArgs, ProgramArgs& args);
+    int doStartup();
+    int doExecution(ProgramArgs& args);
+
+    static bool test_parseOption(std::string o, std::string& stage,
+        std::string& option, std::string& value);
+
+    bool m_showHelp;
+    bool m_showOptions;
+    bool m_showTime;
+    bool m_hardCoreDebug;
+    bool m_visualize;
+    std::string m_label;
+
+    Kernel& operator=(const Kernel&); // not implemented
+    Kernel(const Kernel&); // not implemented
+};
+
+PDAL_DLL std::ostream& operator<<(std::ostream& ostr, const Kernel&);
+
+} // namespace pdal
diff --git a/pdal/KernelFactory.cpp b/pdal/KernelFactory.cpp
new file mode 100644
index 0000000..f3b768a
--- /dev/null
+++ b/pdal/KernelFactory.cpp
@@ -0,0 +1,73 @@
+/******************************************************************************
+* Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/KernelFactory.hpp>
+#include <pdal/PluginManager.hpp>
+
+#include <kernels/DeltaKernel.hpp>
+#include <kernels/DiffKernel.hpp>
+#include <kernels/GroundKernel.hpp>
+#include <kernels/HausdorffKernel.hpp>
+#include <kernels/InfoKernel.hpp>
+#include <kernels/MergeKernel.hpp>
+#include <kernels/PipelineKernel.hpp>
+#include <kernels/RandomKernel.hpp>
+#include <kernels/SortKernel.hpp>
+#include <kernels/SplitKernel.hpp>
+#include <kernels/TIndexKernel.hpp>
+#include <kernels/TranslateKernel.hpp>
+
+namespace pdal
+{
+
+KernelFactory::KernelFactory(bool no_plugins)
+{
+    if (!no_plugins)
+        PluginManager::loadAll(PF_PluginType_Kernel);
+
+    PluginManager::initializePlugin(DeltaKernel_InitPlugin);
+    PluginManager::initializePlugin(DiffKernel_InitPlugin);
+    PluginManager::initializePlugin(GroundKernel_InitPlugin);
+    PluginManager::initializePlugin(HausdorffKernel_InitPlugin);
+    PluginManager::initializePlugin(InfoKernel_InitPlugin);
+    PluginManager::initializePlugin(MergeKernel_InitPlugin);
+    PluginManager::initializePlugin(PipelineKernel_InitPlugin);
+    PluginManager::initializePlugin(RandomKernel_InitPlugin);
+    PluginManager::initializePlugin(SortKernel_InitPlugin);
+    PluginManager::initializePlugin(SplitKernel_InitPlugin);
+    PluginManager::initializePlugin(TIndexKernel_InitPlugin);
+    PluginManager::initializePlugin(TranslateKernel_InitPlugin);
+}
+
+} // namespace pdal
diff --git a/include/pdal/KernelFactory.hpp b/pdal/KernelFactory.hpp
similarity index 100%
rename from include/pdal/KernelFactory.hpp
rename to pdal/KernelFactory.hpp
diff --git a/pdal/Log.cpp b/pdal/Log.cpp
new file mode 100644
index 0000000..f02982d
--- /dev/null
+++ b/pdal/Log.cpp
@@ -0,0 +1,153 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/Log.hpp>
+#include <pdal/PDALUtils.hpp>
+
+#include <fstream>
+#include <ostream>
+
+namespace pdal
+{
+
+Log::Log(std::string const& leaderString,
+         std::string const& outputName)
+    : m_level(LogLevel::Error)
+    , m_deleteStreamOnCleanup(false)
+{
+
+    makeNullStream();
+    if (Utils::iequals(outputName, "stdlog"))
+        m_log = &std::clog;
+    else if (Utils::iequals(outputName, "stderr"))
+        m_log = &std::cerr;
+    else if (Utils::iequals(outputName, "stdout"))
+        m_log = &std::cout;
+    else if (Utils::iequals(outputName, "devnull"))
+        m_log = m_nullStream;
+    else
+    {
+        m_log = Utils::createFile(outputName);
+        m_deleteStreamOnCleanup = true;
+    }
+    m_leaders.push(leaderString);
+}
+
+
+Log::Log(std::string const& leaderString,
+         std::ostream* v)
+    : m_level(LogLevel::Error)
+    , m_deleteStreamOnCleanup(false)
+{
+    m_log = v;
+    makeNullStream();
+    m_leaders.push(leaderString);
+}
+
+
+Log::~Log()
+{
+
+    if (m_deleteStreamOnCleanup)
+    {
+        m_log->flush();
+        delete m_log;
+    }
+    delete m_nullStream;
+}
+
+
+void Log::makeNullStream()
+{
+#ifdef _WIN32
+    std::string nullFilename = "nul";
+#else
+    std::string nullFilename = "/dev/null";
+#endif
+
+    m_nullStream = new std::ofstream(nullFilename);
+}
+
+
+void Log::floatPrecision(int level)
+{
+    m_log->setf(std::ios_base::fixed, std::ios_base::floatfield);
+    m_log->precision(level);
+}
+
+
+void Log::clearFloat()
+{
+    m_log->unsetf(std::ios_base::fixed);
+    m_log->unsetf(std::ios_base::floatfield);
+}
+
+
+std::ostream& Log::get(LogLevel level)
+{
+    const auto incoming(Utils::toNative(level));
+    const auto stored(Utils::toNative(m_level));
+    const auto nativeDebug(Utils::toNative(LogLevel::Debug));
+    if (incoming <= stored)
+    {
+        *m_log << "(" << leader() << " "<< getLevelString(level) <<": " <<
+            incoming << "): " <<
+            std::string(incoming < nativeDebug ? 0 : incoming - nativeDebug,
+                    '\t');
+        return *m_log;
+    }
+    return *m_nullStream;
+
+}
+
+
+std::string Log::getLevelString(LogLevel level) const
+{
+    switch (level)
+    {
+        case LogLevel::Error:
+            return "Error";
+            break;
+        case LogLevel::Warning:
+            return "Warning";
+            break;
+        case LogLevel::Info:
+            return "Info";
+            break;
+        default:
+            return "Debug";
+    }
+}
+
+} // namespace
diff --git a/pdal/Log.hpp b/pdal/Log.hpp
new file mode 100644
index 0000000..e28944a
--- /dev/null
+++ b/pdal/Log.hpp
@@ -0,0 +1,152 @@
+/******************************************************************************
+* Copyright (c) 2011, Howard Butler, hobu.inc at gmail.com
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <cassert>
+#include <memory> // shared_ptr
+#include <stack>
+
+#include <pdal/pdal_internal.hpp>
+
+// Adapted from http://drdobbs.com/cpp/201804215
+
+namespace pdal
+{
+
+/// pdal::Log is a logging object that is provided by pdal::Stage to
+/// facilitate logging operations.
+class PDAL_DLL Log
+{
+public:
+
+    /// Constructs a pdal::Log instance.
+    /// @param leaderString A string to presage all log entries with
+    /// @param outputName A filename or one of 'stdout', 'stdlog', or 'stderr'
+    ///                   to use for outputting log information.
+    Log(std::string const& leaderString, std::string const& outputName);
+
+    /// Constructs a pdal::Log instance.
+    /// @param leaderString A string to presage all log entries with
+    /// @param v An existing std::ostream to use for logging (instead of the
+    ///          the instance creating its own)
+    Log(std::string const& leaderString, std::ostream* v);
+
+    /** @name Destructor
+    */
+    /// The destructor will clean up its own internal log stream, but it will
+    /// not touch one that is given via the constructor
+    ~Log();
+
+    /** @name Logging level
+    */
+    /// @return the logging level of the pdal::Log instance
+    LogLevel getLevel()
+    {
+        return m_level;
+    }
+
+    /// Sets the logging level of the pdal::Log instance
+    /// @param v logging level to use for get() comparison operations
+    void setLevel(LogLevel v)
+    {
+        assert(v != LogLevel::None);
+        m_level = v;
+    }
+
+    /// Set the leader string (deprecated).
+    /// \param[in]  leader  Leader string.
+    void setLeader(const std::string& leader)
+        { pushLeader(leader); }
+
+    /// Push the leader string onto the stack.
+    /// \param  leader  Leader string
+    void pushLeader(const std::string& leader)
+        { m_leaders.push(leader); }
+
+    /// Get the leader string.
+    /// \return  The current leader string.
+    std::string leader() const
+        { return m_leaders.empty() ? std::string() : m_leaders.top(); }
+
+    /// Pop the current leader string.
+    void popLeader()
+    {
+        if (!m_leaders.empty())
+            m_leaders.pop();
+    }
+
+    /// @return A string representing the LogLevel
+    std::string getLevelString(LogLevel v) const;
+
+    /** @name Log stream operations
+    */
+    /// @return the stream object that is currently being used to for log
+    /// operations regardless of logging level of the instance.
+    std::ostream* getLogStream()
+    {
+        return m_log;
+    }
+
+    /// Returns the log stream given the logging level.
+    /// @param level logging level to request
+    /// If the logging level asked for with
+    /// pdal::Log::get is less than the logging level of the pdal::Log instance
+    std::ostream& get(LogLevel level = LogLevel::Info);
+
+    /// Sets the floating point precision
+    void floatPrecision(int level);
+
+    /// Clears the floating point precision settings of the streams
+    void clearFloat();
+
+protected:
+    std::ostream *m_log;
+    std::ostream *m_nullStream;
+
+private:
+    Log(const Log&);
+    Log& operator =(const Log&);
+
+    void makeNullStream();
+
+    LogLevel m_level;
+    bool m_deleteStreamOnCleanup;
+    std::stack<std::string> m_leaders;
+};
+
+typedef std::shared_ptr<Log> LogPtr;
+
+} // namespace pdal
+
diff --git a/src/Metadata.cpp b/pdal/Metadata.cpp
similarity index 100%
rename from src/Metadata.cpp
rename to pdal/Metadata.cpp
diff --git a/include/pdal/Metadata.hpp b/pdal/Metadata.hpp
similarity index 100%
rename from include/pdal/Metadata.hpp
rename to pdal/Metadata.hpp
diff --git a/src/Options.cpp b/pdal/Options.cpp
similarity index 100%
rename from src/Options.cpp
rename to pdal/Options.cpp
diff --git a/include/pdal/Options.hpp b/pdal/Options.hpp
similarity index 100%
rename from include/pdal/Options.hpp
rename to pdal/Options.hpp
diff --git a/pdal/PDALUtils.cpp b/pdal/PDALUtils.cpp
new file mode 100644
index 0000000..0ba8e33
--- /dev/null
+++ b/pdal/PDALUtils.cpp
@@ -0,0 +1,404 @@
+/******************************************************************************
+* Copyright (c) 2014, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/PDALUtils.hpp>
+
+#ifdef PDAL_ARBITER_ENABLED
+    #include <arbiter/arbiter.hpp>
+#endif
+
+#include <pdal/KDIndex.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/Options.hpp>
+#include <pdal/util/FileUtils.hpp>
+
+using namespace std;
+
+namespace pdal
+{
+
+namespace {
+
+void toJSON(const MetadataNode& m, std::ostream& o, int level);
+void arrayToJSON(const MetadataNodeList& children, std::ostream& o, int level);
+void arrayEltToJSON(const MetadataNode& m, std::ostream& o, int level);
+void subnodesToJSON(const MetadataNode& parent, std::ostream& o, int level)
+{
+    const std::string indent(level * 2, ' ');
+
+    std::vector<std::string> names = parent.childNames();
+
+    o << indent << "{" << endl;
+    for (auto ni = names.begin(); ni != names.end(); ++ni)
+    {
+        MetadataNodeList children = parent.children(*ni);
+
+        MetadataNode& node = *children.begin();
+        if (node.kind() == MetadataType::Array)
+        {
+            o << indent << "  \"" << node.name() << "\":" << std::endl;
+            arrayToJSON(children, o, level + 1);
+        }
+        else
+            toJSON(node, o, level + 1);
+        if (ni != names.rbegin().base() - 1)
+            o << ",";
+        o << std::endl;
+    }
+    o << indent << "}";
+}
+
+void arrayToJSON(const MetadataNodeList& children, std::ostream& o, int level)
+{
+    const std::string indent(level * 2, ' ');
+
+    o << indent << "[" << std::endl;
+    for (auto ci = children.begin(); ci != children.end(); ++ci)
+    {
+        const MetadataNode& m = *ci;
+
+        arrayEltToJSON(m, o, level + 1);
+        if (ci != children.rbegin().base() - 1)
+            o << ",";
+        o << std::endl;
+    }
+    o << indent << "]";
+}
+
+void arrayEltToJSON(const MetadataNode& m, std::ostream& o, int level)
+{
+    std::string indent(level * 2, ' ');
+    std::string value = m.jsonValue();
+    bool children = m.hasChildren();
+
+    // This is a case from XML.  In JSON, you can't have two values.
+    if (!value.empty() && children)
+    {
+        o << value << "," << std::endl;
+        subnodesToJSON(m, o, level);
+    }
+    else if (!value.empty())
+        o << indent << value;
+    else
+        subnodesToJSON(m, o, level);
+    // There is the case where we have a name and no value to handle.  What
+    // should be done?
+}
+
+void toJSON(const MetadataNode& m, std::ostream& o, int level)
+{
+    std::string indent(level * 2, ' ');
+    std::string name = m.name();
+    std::string value = m.jsonValue();
+    bool children = m.hasChildren();
+
+    if (name.empty())
+        name = "unnamed";
+
+    // This is a case from XML.  In JSON, you can't have two values.
+    if (!value.empty() && children)
+    {
+        o << indent << "\"" << name << "\": " << value << "," << std::endl;
+        o << indent << "\"" << name << "\": ";
+        subnodesToJSON(m, o, level);
+    }
+    else if (!value.empty())
+        o << indent << "\"" << name << "\": " << value;
+    else
+    {
+        o << indent << "\"" << name << "\":" << std::endl;
+        subnodesToJSON(m, o, level);
+    }
+    // There is the case where we have a name and no value to handle.  What
+    // should be done?
+}
+
+} // unnamed namespace
+
+namespace Utils
+{
+
+std::string toJSON(const MetadataNode& m)
+{
+    std::ostringstream o;
+
+    toJSON(m, o);
+    return o.str();
+}
+
+void toJSON(const MetadataNode& m, std::ostream& o)
+{
+    if (m.name().empty())
+        pdal::subnodesToJSON(m, o, 0);
+    else if (m.kind() == MetadataType::Array)
+        pdal::arrayToJSON(m.children(), o, 0);
+    else
+    {
+        o << "{" << std::endl;
+        pdal::toJSON(m, o, 1);
+        o << std::endl;
+        o << "}";
+    }
+    o << std::endl;
+}
+
+namespace
+{
+
+std::string tempFilename(const std::string& path)
+{
+#ifdef PDAL_ARBITER_ENABLED
+    const std::string tempdir(arbiter::fs::getTempPath());
+    const std::string basename(arbiter::util::getBasename(path));
+
+    return arbiter::util::join(tempdir, basename);
+#else
+    throw pdal_error("Arbiter is not enabled for this configuration (tempFilename)!");
+#endif
+};
+
+// RAII handling of a temp file to make sure file gets deleted.
+class TempFile
+{
+public:
+    TempFile(const std::string path) : m_filename(path)
+    {}
+
+    virtual ~TempFile()
+        { FileUtils::deleteFile(m_filename); }
+
+    const std::string& filename()
+        { return m_filename; }
+
+private:
+    std::string m_filename;
+};
+
+class ArbiterOutStream : public std::ofstream
+{
+public:
+    ArbiterOutStream(const std::string& localPath,
+            const std::string& remotePath, std::ios::openmode mode) :
+        std::ofstream(localPath, mode), m_remotePath(remotePath),
+        m_localFile(localPath)
+    {}
+
+    virtual ~ArbiterOutStream()
+    {
+#ifdef PDAL_ARBITER_ENABLED
+        close();
+        arbiter::Arbiter a;
+        a.put(m_remotePath, a.getBinary(m_localFile.filename()));
+#else
+        throw pdal_error("Arbiter is not enabled for this configuration!");
+#endif
+    }
+
+private:
+    std::string m_remotePath;
+    TempFile m_localFile;
+};
+
+class ArbiterInStream : public std::ifstream
+{
+public:
+    ArbiterInStream(const std::string& localPath, const std::string& remotePath,
+            std::ios::openmode mode) :
+        m_localFile(localPath)
+    {
+#ifdef PDAL_ARBITER_ENABLED
+        arbiter::Arbiter a;
+        a.put(localPath, a.getBinary(remotePath));
+        open(localPath, mode);
+#else
+        throw pdal_error("Arbiter is not enabled for this configuration!");
+#endif
+    }
+
+private:
+    TempFile m_localFile;
+};
+
+}  // unnamed namespace
+
+/**
+  Create a file (may be on a supported remote filesystem).
+
+  \param path  Path to file to create.
+  \param asBinary  Whether the file should be written in binary mode.
+  \return  Pointer to the created stream, or NULL.
+*/
+std::ostream *createFile(const std::string& path, bool asBinary)
+{
+    ostream *ofs(nullptr);
+
+#ifdef PDAL_ARBITER_ENABLED
+    arbiter::Arbiter a;
+    const bool remote(a.hasDriver(path) && a.isRemote(path));
+
+    if (remote)
+    {
+        try
+        {
+            ofs = new ArbiterOutStream(tempFilename(path), path,
+                asBinary ? ios::out | ios::binary : ios::out);
+        }
+        catch (arbiter::ArbiterError)
+        {}
+        if (ofs && !ofs->good())
+        {
+            delete ofs;
+            ofs = nullptr;
+        }
+    }
+    else
+#endif
+        ofs = FileUtils::createFile(path, asBinary);
+    return ofs;
+}
+
+
+/**
+  Open a file (potentially on a remote filesystem).
+
+  \param path  Path (potentially remote) of file to open.
+  \param asBinary  Whether the file should be opened binary.
+  \return  Pointer to stream opened for input.
+*/
+std::istream *openFile(const std::string& path, bool asBinary)
+{
+#ifdef PDAL_ARBITER_ENABLED
+    arbiter::Arbiter a;
+    if (a.hasDriver(path) && a.isRemote(path))
+    {
+        try
+        {
+            return new ArbiterInStream(tempFilename(path), path,
+                asBinary ? ios::in | ios::binary : ios::in);
+        }
+        catch (arbiter::ArbiterError)
+        {
+            return nullptr;
+        }
+    }
+#endif
+    return FileUtils::openFile(path, asBinary);
+}
+
+/**
+  Close an output stream.
+
+  \param out  Stream to close.
+*/
+void closeFile(std::ostream *out)
+{
+    FileUtils::closeFile(out);
+}
+
+
+/**
+  Close an input stream.
+
+  \param out  Stream to close.
+*/
+void closeFile(std::istream *in)
+{
+    FileUtils::closeFile(in);
+}
+
+
+/**
+  Check to see if a file exists.
+
+  \param path  Path to file.
+  \return  Whether the file exists or not.
+*/
+bool fileExists(const std::string& path)
+{
+#ifdef PDAL_ARBITER_ENABLED
+    arbiter::Arbiter a;
+    if (a.hasDriver(path) && a.isRemote(path) && a.exists(path))
+    {
+        return true;
+    }
+#endif
+
+    // Arbiter doesn't handle our STDIN hacks.
+    return FileUtils::fileExists(path);
+}
+
+double computeHausdorff(PointViewPtr srcView, PointViewPtr candView)
+{
+    using namespace Dimension;
+        
+    KD3Index srcIndex(*srcView);
+    srcIndex.build();
+    
+    KD3Index candIndex(*candView);
+    candIndex.build();
+    
+    double maxDistSrcToCand = std::numeric_limits<double>::lowest();
+    double maxDistCandToSrc = std::numeric_limits<double>::lowest();
+    
+    for (PointId i = 0; i < srcView->size(); ++i)
+    {
+        std::vector<PointId> indices(1);
+        std::vector<double> sqr_dists(1);
+        PointRef srcPoint = srcView->point(i);
+        candIndex.knnSearch(srcPoint, 1, &indices, &sqr_dists);
+
+        if (sqr_dists[0] > maxDistSrcToCand)
+            maxDistSrcToCand = sqr_dists[0];
+    }
+
+    for (PointId i = 0; i < candView->size(); ++i)
+    {
+        std::vector<PointId> indices(1);
+        std::vector<double> sqr_dists(1);
+        PointRef candPoint = candView->point(i);
+        srcIndex.knnSearch(candPoint, 1, &indices, &sqr_dists);
+
+        if (sqr_dists[0] > maxDistCandToSrc)
+            maxDistCandToSrc = sqr_dists[0];
+    }
+
+    maxDistSrcToCand = std::sqrt(maxDistSrcToCand);
+    maxDistCandToSrc = std::sqrt(maxDistCandToSrc);
+    
+    return std::max(maxDistSrcToCand, maxDistCandToSrc);
+}
+
+} // namespace Utils
+} // namespace pdal
diff --git a/pdal/PDALUtils.hpp b/pdal/PDALUtils.hpp
new file mode 100644
index 0000000..ca53a7a
--- /dev/null
+++ b/pdal/PDALUtils.hpp
@@ -0,0 +1,280 @@
+/******************************************************************************
+ * Copyright (c) 2014, Hobu Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of the Martin Isenburg or Iowa Department
+ *       of Natural Resources nor the names of its contributors may be
+ *       used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#pragma once
+
+#include <pdal/Metadata.hpp>
+#include <pdal/Dimension.hpp>
+#include <pdal/pdal_defines.h>
+#include <pdal/pdal_export.hpp>
+#include <pdal/util/Inserter.hpp>
+#include <pdal/util/Extractor.hpp>
+
+#ifndef WIN32
+#include <sys/fcntl.h>
+#include <unistd.h>
+#endif
+
+#ifdef PDAL_COMPILER_MSVC
+//#  pragma warning(disable: 4127)  // conditional expression is constant
+#endif
+
+
+namespace pdal
+{
+class Options;
+class PointView;
+
+typedef std::shared_ptr<PointView> PointViewPtr;
+
+namespace Utils
+{
+
+inline void printError(const std::string& s)
+{
+    std::cerr << "PDAL: " << s << std::endl;
+    std::cerr << std::endl;
+}
+
+inline double toDouble(const Everything& e, Dimension::Type type)
+{
+    using Type = Dimension::Type;
+
+    double d = 0;
+    switch (type)
+    {
+    case Type::Unsigned8:
+        d = e.u8;
+        break;
+    case Type::Unsigned16:
+        d = e.u16;
+        break;
+    case Type::Unsigned32:
+        d = e.u32;
+        break;
+    case Type::Unsigned64:
+        d = (double)e.u64;
+        break;
+    case Type::Signed8:
+        d = e.s8;
+        break;
+    case Type::Signed16:
+        d = e.s16;
+        break;
+    case Type::Signed32:
+        d = e.s32;
+        break;
+    case Type::Signed64:
+        d = (double)e.s64;
+        break;
+    case Type::Float:
+        d = e.f;
+        break;
+    case Type::Double:
+        d = e.d;
+        break;
+    default:
+        break;
+    }
+    return d;
+}
+
+inline Everything extractDim(Extractor& ext, Dimension::Type type)
+{
+    using Type = Dimension::Type;
+
+	Everything e;
+    switch (type)
+    {
+        case Type::Unsigned8:
+            ext >> e.u8;
+            break;
+        case Type::Unsigned16:
+            ext >> e.u16;
+            break;
+        case Type::Unsigned32:
+            ext >> e.u32;
+            break;
+        case Type::Unsigned64:
+            ext >> e.u64;
+            break;
+        case Type::Signed8:
+            ext >> e.s8;
+            break;
+        case Type::Signed16:
+            ext >> e.s16;
+            break;
+        case Type::Signed32:
+            ext >> e.s32;
+            break;
+        case Type::Signed64:
+            ext >> e.s64;
+            break;
+        case Type::Float:
+            ext >> e.f;
+            break;
+        case Type::Double:
+            ext >> e.d;
+            break;
+        case Type::None:
+            break;
+    }
+    return e;
+}
+
+inline void insertDim(Inserter& ins, Dimension::Type type,
+    const Everything& e)
+{
+    using Type = Dimension::Type;
+
+    switch (type)
+    {
+        case Type::Unsigned8:
+            ins << e.u8;
+            break;
+        case Type::Unsigned16:
+            ins << e.u16;
+            break;
+        case Type::Unsigned32:
+            ins << e.u32;
+            break;
+        case Type::Unsigned64:
+            ins << e.u64;
+            break;
+        case Type::Signed8:
+            ins << e.s8;
+            break;
+        case Type::Signed16:
+            ins << e.s16;
+            break;
+        case Type::Signed32:
+            ins << e.s32;
+            break;
+        case Type::Signed64:
+            ins << e.s64;
+            break;
+        case Type::Float:
+            ins << e.f;
+            break;
+        case Type::Double:
+            ins << e.d;
+            break;
+        case Type::None:
+            break;
+    }
+}
+
+
+
+inline MetadataNode toMetadata(const BOX2D& bounds)
+{
+    MetadataNode output("bbox");
+    output.add("minx", bounds.minx);
+    output.add("miny", bounds.miny);
+    output.add("maxx", bounds.maxx);
+    output.add("maxy", bounds.maxy);
+    return output;
+}
+
+inline MetadataNode toMetadata(const BOX3D& bounds)
+{
+    MetadataNode output("bbox");
+    output.add("minx", bounds.minx);
+    output.add("miny", bounds.miny);
+    output.add("minz", bounds.minz);
+    output.add("maxx", bounds.maxx);
+    output.add("maxy", bounds.maxy);
+    output.add("maxz", bounds.maxz);
+    return output;
+}
+
+inline int openProgress(const std::string& filename)
+{
+#ifdef WIN32
+    return -1;
+#else
+    int fd = open(filename.c_str(), O_WRONLY | O_NONBLOCK);
+    if (fd == -1)
+    {
+        std::string out = "Can't open progress file '";
+        out += filename + "'.";
+        printError(out);
+    }
+    return fd;
+#endif
+}
+
+
+inline void closeProgress(int fd)
+{
+#ifdef WIN32
+#else
+    if (fd >= 0)
+        close(fd);
+#endif
+}
+
+
+inline void writeProgress(int fd, const std::string& type,
+    const std::string& text)
+{
+#ifdef WIN32
+#else
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-result"
+    if (fd >= 0)
+    {
+        std::string out = type + ':' + text + '\n';
+
+        // This may error, but we don't care.
+        write(fd, out.c_str(), out.length());
+    }
+#pragma GCC diagnostic pop
+#endif
+}
+
+std::string PDAL_DLL toJSON(const MetadataNode& m);
+void PDAL_DLL toJSON(const MetadataNode& m, std::ostream& o);
+std::istream PDAL_DLL *openFile(const std::string& path, bool asBinary = true);
+std::ostream PDAL_DLL *createFile(const std::string& path,
+    bool asBinary = true);
+void PDAL_DLL closeFile(std::istream *in);
+void PDAL_DLL closeFile(std::ostream *out);
+bool PDAL_DLL fileExists(const std::string& path);
+std::string PDAL_DLL expandTilde(const std::string& path);
+std::vector<std::string> PDAL_DLL maybeGlob(const std::string& path);
+double PDAL_DLL computeHausdorff(PointViewPtr srcView, PointViewPtr candView);
+
+} // namespace Utils
+} // namespace pdal
diff --git a/pdal/PipelineExecutor.cpp b/pdal/PipelineExecutor.cpp
new file mode 100644
index 0000000..ce96fa1
--- /dev/null
+++ b/pdal/PipelineExecutor.cpp
@@ -0,0 +1,138 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/PipelineExecutor.hpp>
+#include <pdal/PDALUtils.hpp>
+
+namespace pdal
+{
+
+
+PipelineExecutor::PipelineExecutor(std::string const& json)
+    : m_json(json)
+    , m_executed(false)
+    , m_logLevel(pdal::LogLevel::Error)
+{
+}
+
+
+std::string PipelineExecutor::getPipeline() const
+{
+    if (!m_executed)
+        throw pdal_error("Pipeline has not been executed!");
+
+    std::stringstream strm;
+    pdal::PipelineWriter::writePipeline(m_manager.getStage(), strm);
+    return strm.str();
+}
+
+
+std::string PipelineExecutor::getMetadata() const
+{
+    if (!m_executed)
+        throw pdal_error("Pipeline has not been executed!");
+
+    std::stringstream strm;
+    MetadataNode root = m_manager.getMetadata().clone("metadata");
+    pdal::Utils::toJSON(root, strm);
+    return strm.str();
+}
+
+
+std::string PipelineExecutor::getSchema() const
+{
+    if (!m_executed)
+        throw pdal_error("Pipeline has not been executed!");
+
+    std::stringstream strm;
+    MetadataNode root = m_manager.pointTable().toMetadata().clone("schema");
+    pdal::Utils::toJSON(root, strm);
+    return strm.str();
+}
+
+
+bool PipelineExecutor::validate()
+{
+    std::stringstream strm;
+    strm << m_json;
+    m_manager.readPipeline(strm);
+    m_manager.prepare();
+
+    return true;
+}
+
+int64_t PipelineExecutor::execute()
+{
+    std::stringstream strm;
+    strm << m_json;
+    m_manager.readPipeline(strm);
+    point_count_t count = m_manager.execute();
+
+    m_executed = true;
+
+    return count;
+}
+
+
+void PipelineExecutor::setLogStream(std::ostream& strm)
+{
+
+    LogPtr log = pdal::LogPtr(new pdal::Log("pypipeline", &strm));
+    log->setLevel(m_logLevel);
+    m_manager.setLog(log);
+
+}
+
+
+void PipelineExecutor::setLogLevel(int level)
+{
+    m_logLevel = static_cast<pdal::LogLevel>(level);
+    setLogStream(m_logStream);
+}
+
+
+int PipelineExecutor::getLogLevel() const
+{
+    return static_cast<int>(m_logLevel);
+}
+
+
+std::string PipelineExecutor::getLog() const
+{
+    return m_logStream.str();
+}
+
+
+} //namespace pdal
+
diff --git a/pdal/PipelineExecutor.hpp b/pdal/PipelineExecutor.hpp
new file mode 100644
index 0000000..5a55dd2
--- /dev/null
+++ b/pdal/PipelineExecutor.hpp
@@ -0,0 +1,143 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/PipelineManager.hpp>
+#include <pdal/PipelineWriter.hpp>
+#include <pdal/util/FileUtils.hpp>
+
+#include <string>
+
+namespace pdal
+{
+
+/**
+  An executor hides the management of constructing, executing, and
+  fetching data from a PipelineManager.
+
+  It is constructed with JSON defining a pipeline.
+*/
+
+class PipelineExecutor {
+public:
+
+    /**
+      Construct a PipelineExecutor
+
+      \param json Pipeline JSON defining the PDAL operations
+    */
+    PipelineExecutor(std::string const& json);
+
+    /**
+      dtor
+    */
+    ~PipelineExecutor(){};
+
+    /**
+      Execute the pipeline
+
+      \return total number of points produced by the pipeline.
+    */
+    int64_t execute();
+
+    /**
+      Validate the pipeline
+
+      \return does PDAL think the pipeline is valid?
+    */
+    bool validate();
+
+    /**
+      \return the transliterated pipeline
+    */
+    std::string getPipeline() const;
+
+    /**
+      \return computed metadata for the pipeline and all stages
+    */
+    std::string getMetadata() const;
+
+    /**
+      \return computed schema for the pipeline
+    */
+    std::string getSchema() const;
+
+    /**
+      \return log output for the executed pipeline. use
+      setLogLevel to adjust verbosity.
+    */
+    std::string getLog() const;
+
+    /**
+      set the log verbosity. Use values 0-8.
+    */
+    void setLogLevel(int level);
+
+    /**
+      \return log verbosity
+    */
+    int getLogLevel() const;
+
+    /**
+      \return has the pipeline been executed
+    */
+    inline bool executed() const
+    {
+        return m_executed;
+    }
+
+
+    /**
+      \return a const reference to the pipeline manager
+    */
+    PipelineManager const& getManagerConst() const { return m_manager; }
+
+    /**
+      \return a reference to the pipeline manager
+    */
+    PipelineManager & getManager() { return m_manager; }
+
+private:
+    void setLogStream(std::ostream& strm);
+
+    std::string m_json;
+    pdal::PipelineManager m_manager;
+    bool m_executed;
+    std::stringstream m_logStream;
+    pdal::LogLevel m_logLevel;
+
+};
+
+}
diff --git a/pdal/PipelineManager.cpp b/pdal/PipelineManager.cpp
new file mode 100644
index 0000000..ea0c32a
--- /dev/null
+++ b/pdal/PipelineManager.cpp
@@ -0,0 +1,358 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/PipelineManager.hpp>
+#include <pdal/PipelineReaderJSON.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/util/FileUtils.hpp>
+
+#include "private/PipelineReaderXML.hpp"
+
+namespace pdal
+{
+
+PipelineManager::~PipelineManager()
+{
+    Utils::closeFile(m_input);
+}
+
+
+void PipelineManager::readPipeline(std::istream& input)
+{
+    std::istreambuf_iterator<char> eos;
+
+    // Read stream into string.
+    std::string s(std::istreambuf_iterator<char>(input), eos);
+
+    std::istringstream ss(s);
+    if (s.find("?xml") != std::string::npos)
+        PipelineReaderXML(*this).readPipeline(ss);
+    else if (s.find("\"pipeline\"") != std::string::npos)
+        PipelineReaderJSON(*this).readPipeline(ss);
+    else
+    {
+        try
+        {
+            PipelineReaderXML(*this).readPipeline(ss);
+        }
+        catch (pdal_error)
+        {
+            // Rewind to make sure the stream is properly positioned after
+            // attempting an XML pipeline.
+            ss.seekg(0);
+            PipelineReaderJSON(*this).readPipeline(ss);
+        }
+    }
+}
+
+
+void PipelineManager::readPipeline(const std::string& filename)
+{
+    if (FileUtils::extension(filename) == ".xml")
+    {
+        PipelineReaderXML pipeReader(*this);
+        return pipeReader.readPipeline(filename);
+    }
+    else if (FileUtils::extension(filename) == ".json")
+    {
+        PipelineReaderJSON pipeReader(*this);
+        return pipeReader.readPipeline(filename);
+    }
+    else
+    {
+        Utils::closeFile(m_input);
+        m_input = Utils::openFile(filename);
+        readPipeline(*m_input);
+    }
+}
+
+
+Stage& PipelineManager::addReader(const std::string& type)
+{
+    Stage *reader = m_factory.createStage(type);
+    if (!reader)
+    {
+        std::ostringstream ss;
+        ss << "Couldn't create reader stage of type '" << type << "'.";
+        throw pdal_error(ss.str());
+    }
+    reader->setLog(m_log);
+    reader->setProgressFd(m_progressFd);
+    m_stages.push_back(reader);
+    return *reader;
+}
+
+
+Stage& PipelineManager::addFilter(const std::string& type)
+{
+    Stage *filter = m_factory.createStage(type);
+    if (!filter)
+    {
+        std::ostringstream ss;
+        ss << "Couldn't create filter stage of type '" << type << "'.";
+        throw pdal_error(ss.str());
+    }
+    filter->setLog(m_log);
+    filter->setProgressFd(m_progressFd);
+    m_stages.push_back(filter);
+    return *filter;
+}
+
+
+Stage& PipelineManager::addWriter(const std::string& type)
+{
+    Stage *writer = m_factory.createStage(type);
+    if (!writer)
+    {
+        std::ostringstream ss;
+        ss << "Couldn't create writer stage of type '" << type << "'.";
+        throw pdal_error(ss.str());
+    }
+    writer->setLog(m_log);
+    writer->setProgressFd(m_progressFd);
+    m_stages.push_back(writer);
+    return *writer;
+}
+
+
+void PipelineManager::validateStageOptions() const
+{
+    // Make sure that the options specified are for relevant stages.
+    for (auto& si : m_stageOptions)
+    {
+        const std::string& stageName = si.first;
+        auto it = std::find_if(m_stages.begin(), m_stages.end(),
+            [stageName](Stage *s)
+            { return (s->getName() == stageName); });
+
+        // If the option stage name matches no created stage, then error.
+        if (it == m_stages.end())
+        {
+            std::ostringstream oss;
+            oss << "Argument references invalid/unused stage: '" <<
+                stageName << "'.";
+            throw pdal_error(oss.str());
+        }
+    }
+}
+
+
+QuickInfo PipelineManager::preview() const
+{
+    QuickInfo qi;
+
+    validateStageOptions();
+    Stage *s = getStage();
+    if (s)
+       qi = s->preview();
+    return qi;
+}
+
+
+void PipelineManager::prepare() const
+{
+    validateStageOptions();
+    Stage *s = getStage();
+    if (s)
+       s->prepare(m_table);
+}
+
+
+point_count_t PipelineManager::execute()
+{
+    prepare();
+
+    Stage *s = getStage();
+    if (!s)
+        return 0;
+    m_viewSet = s->execute(m_table);
+    point_count_t cnt = 0;
+    for (auto pi = m_viewSet.begin(); pi != m_viewSet.end(); ++pi)
+    {
+        PointViewPtr view = *pi;
+        cnt += view->size();
+    }
+    return cnt;
+}
+
+
+MetadataNode PipelineManager::getMetadata() const
+{
+    MetadataNode output("stages");
+
+    for (auto s : m_stages)
+    {
+        output.add(s->getMetadata());
+    }
+    return output;
+}
+
+
+Stage& PipelineManager::makeReader(const std::string& inputFile,
+    std::string driver)
+{
+    static Options nullOpts;
+
+    return makeReader(inputFile, driver, nullOpts);
+}
+
+
+Stage& PipelineManager::makeReader(const std::string& inputFile,
+    std::string driver, Options options)
+{
+    if (driver.empty())
+    {
+        driver = StageFactory::inferReaderDriver(inputFile);
+        if (driver.empty())
+            throw pdal_error("Cannot determine reader for input file: " +
+                inputFile);
+    }
+    if (!inputFile.empty())
+        options.replace("filename", inputFile);
+
+    Stage& reader = addReader(driver);
+    setOptions(reader, options);
+    return reader;
+}
+
+
+Stage& PipelineManager::makeFilter(const std::string& driver)
+{
+    static Options nullOps;
+
+    Stage& filter = addFilter(driver);
+    setOptions(filter, nullOps);
+    return filter;
+}
+
+
+Stage& PipelineManager::makeFilter(const std::string& driver, Options options)
+{
+    Stage& filter = addFilter(driver);
+    setOptions(filter, options);
+    return filter;
+}
+
+
+Stage& PipelineManager::makeFilter(const std::string& driver, Stage& parent)
+{
+    static Options nullOps;
+
+    return makeFilter(driver, parent, nullOps);
+}
+
+
+Stage& PipelineManager::makeFilter(const std::string& driver, Stage& parent,
+    Options options)
+{
+    Stage& filter = addFilter(driver);
+    setOptions(filter, options);
+    filter.setInput(parent);
+    return filter;
+}
+
+
+Stage& PipelineManager::makeWriter(const std::string& outputFile,
+    std::string driver)
+{
+    static Options nullOps;
+
+    return makeWriter(outputFile, driver, nullOps);
+}
+
+Stage& PipelineManager::makeWriter(const std::string& outputFile,
+    std::string driver, Options options)
+{
+    if (driver.empty())
+    {
+        driver = StageFactory::inferWriterDriver(outputFile);
+        if (driver.empty())
+            throw pdal_error("Cannot determine writer for output file: " +
+                outputFile);
+    }
+
+    if (!outputFile.empty())
+        options.replace("filename", outputFile);
+
+    auto& writer = addWriter(driver);
+    setOptions(writer, options);
+    return writer;
+}
+
+
+Stage& PipelineManager::makeWriter(const std::string& outputFile,
+    std::string driver, Stage& parent)
+{
+    static Options nullOps;
+
+    return makeWriter(outputFile, driver, parent, nullOps);
+}
+
+Stage& PipelineManager::makeWriter(const std::string& outputFile,
+    std::string driver, Stage& parent, Options options)
+{
+    Stage& writer = makeWriter(outputFile, driver, options);
+    writer.setInput(parent);
+    return writer;
+}
+
+
+void PipelineManager::setOptions(Stage& stage, const Options& addOps)
+{
+    // First apply common options.
+    stage.setOptions(m_commonOptions);
+
+    // Apply additional reader/writer options, making sure they replace any
+    // common options.
+    stage.removeOptions(addOps);
+    stage.addOptions(addOps);
+
+    // Apply options provided on the command line, overriding others.
+    Options& ops = stageOptions(stage);
+    stage.removeOptions(ops);
+    stage.addOptions(ops);
+}
+
+
+Options& PipelineManager::stageOptions(Stage& stage)
+{
+    static Options nullOpts;
+
+    auto oi = m_stageOptions.find(stage.getName());
+    if (oi == m_stageOptions.end())
+        return nullOpts;
+    return oi->second;
+}
+
+} // namespace pdal
diff --git a/pdal/PipelineManager.hpp b/pdal/PipelineManager.hpp
new file mode 100644
index 0000000..e3c159c
--- /dev/null
+++ b/pdal/PipelineManager.hpp
@@ -0,0 +1,146 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/pdal_internal.hpp>
+#include <pdal/StageFactory.hpp>
+
+#include <vector>
+#include <string>
+
+namespace pdal
+{
+
+class Options;
+
+class PDAL_DLL PipelineManager
+{
+    FRIEND_TEST(json, tags);
+public:
+    PipelineManager() : m_tablePtr(new PointTable()), m_table(*m_tablePtr),
+            m_progressFd(-1), m_input(nullptr)
+        {}
+    PipelineManager(int progressFd) : m_tablePtr(new PointTable()),
+            m_table(*m_tablePtr), m_progressFd(progressFd), m_input(nullptr)
+        {}
+    PipelineManager(PointTableRef table) : m_table(table), m_progressFd(-1),
+            m_input(nullptr)
+        {}
+    PipelineManager(PointTableRef table, int progressFd) : m_table(table),
+            m_progressFd(progressFd), m_input(nullptr)
+        {}
+    ~PipelineManager();
+
+    void readPipeline(std::istream& input);
+    void readPipeline(const std::string& filename);
+
+    // Use these to manually add stages into the pipeline manager.
+    Stage& addReader(const std::string& type);
+    Stage& addFilter(const std::string& type);
+    Stage& addWriter(const std::string& type);
+
+    // These add stages, hook dependencies and set necessary options.
+    // They're preferable to the above as they're more flexible and safer.
+    Stage& makeReader(const std::string& inputFile, std::string driver);
+    Stage& makeReader(const std::string& inputFile, std::string driver,
+        Options options);
+
+    Stage& makeFilter(const std::string& driver);
+    Stage& makeFilter(const std::string& driver, Options options);
+    Stage& makeFilter(const std::string& driver, Stage& parent);
+    Stage& makeFilter(const std::string& driver, Stage& parent,
+        Options options);
+
+    Stage& makeWriter(const std::string& outputFile, std::string driver);
+    Stage& makeWriter(const std::string& outputFile, std::string driver,
+        Options options);
+    Stage& makeWriter(const std::string& outputFile, std::string driver,
+        Stage& parent);
+    Stage& makeWriter(const std::string& outputFile, std::string driver,
+        Stage& parent, Options options);
+
+    // returns true if the pipeline endpoint is a writer
+    bool isWriterPipeline() const
+        { return (bool)getStage(); }
+
+    // return the pipeline reader endpoint (or nullptr, if not a reader
+    // pipeline)
+    Stage* getStage() const
+        { return m_stages.empty() ? nullptr : m_stages.back(); }
+
+    // Set the log to be available to stages.
+    void setLog(LogPtr& log)
+        { m_log = log; }
+
+    QuickInfo preview() const;
+    void prepare() const;
+    point_count_t execute();
+    void validateStageOptions() const;
+
+    // Get the resulting point views.
+    const PointViewSet& views() const
+        { return m_viewSet; }
+
+    // Get the point table data.
+    PointTableRef pointTable() const
+        { return m_table; }
+
+    MetadataNode getMetadata() const;
+    Options& commonOptions()
+        { return m_commonOptions; }
+    OptionsMap& stageOptions()
+        { return m_stageOptions; }
+    Options& stageOptions(Stage& stage);
+
+private:
+    void setOptions(Stage& stage, const Options& addOps);
+
+    StageFactory m_factory;
+    std::unique_ptr<PointTable> m_tablePtr;
+    PointTableRef m_table;
+    Options m_commonOptions;
+    OptionsMap m_stageOptions;
+    PointViewSet m_viewSet;
+    std::vector<Stage*> m_stages; // stage observer, never owner
+    int m_progressFd;
+    std::istream *m_input;
+    LogPtr m_log;
+
+    PipelineManager& operator=(const PipelineManager&); // not implemented
+    PipelineManager(const PipelineManager&); // not implemented
+};
+typedef std::unique_ptr<PipelineManager> PipelineManagerPtr;
+
+} // namespace pdal
diff --git a/pdal/PipelineReaderJSON.cpp b/pdal/PipelineReaderJSON.cpp
new file mode 100644
index 0000000..2d2ca9e
--- /dev/null
+++ b/pdal/PipelineReaderJSON.cpp
@@ -0,0 +1,341 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+
+#include <pdal/Filter.hpp>
+#include <pdal/PipelineReaderJSON.hpp>
+#include <pdal/PipelineManager.hpp>
+#include <pdal/PluginManager.hpp>
+#include <pdal/Options.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/util/Utils.hpp>
+
+#include <json/json.h>
+
+#include <memory>
+#include <vector>
+
+namespace pdal
+{
+
+PipelineReaderJSON::PipelineReaderJSON(PipelineManager& manager) :
+    m_manager(manager)
+{}
+
+
+void PipelineReaderJSON::parsePipeline(Json::Value& tree)
+{
+    TagMap tags;
+    std::vector<Stage*> inputs;
+
+    Json::ArrayIndex last = tree.size() - 1;
+    for (Json::ArrayIndex i = 0; i < tree.size(); ++i)
+    {
+        Json::Value& node = tree[i];
+
+        std::string filename;
+        std::string tag;
+        std::string type;
+        std::vector<Stage*> specifiedInputs;
+        Options options;
+
+        // strings are assumed to be filenames
+        if (node.isString())
+        {
+            filename = node.asString();
+        }
+        else
+        {
+            type = extractType(node);
+            filename = extractFilename(node);
+            tag = extractTag(node, tags);
+            specifiedInputs = extractInputs(node, tags);
+            if (!specifiedInputs.empty())
+                inputs = specifiedInputs;
+            options = extractOptions(node);
+        }
+
+        Stage *s = nullptr;
+
+        // The type is inferred from a filename as a reader if it's not
+        // the last stage or if there's only one.
+        if ((type.empty() && (i == 0 || i != last)) ||
+            Utils::startsWith(type, "readers."))
+        {
+            StringList files = FileUtils::glob(filename);
+            if (files.empty())
+                files.push_back(filename);
+
+            for (const std::string& path : files)
+            {
+                s = &m_manager.makeReader(path, type, options);
+
+                if (specifiedInputs.size())
+                    throw pdal_error("JSON pipeline: Inputs not permitted for "
+                        " reader: '" + path + "'.");
+                inputs.push_back(s);
+            }
+        }
+        else if (type.empty() || Utils::startsWith(type, "writers."))
+        {
+            s = &m_manager.makeWriter(filename, type, options);
+            for (Stage *ts : inputs)
+                s->setInput(*ts);
+            inputs.clear();
+        }
+        else
+        {
+            if (filename.size())
+                options.add("filename", filename);
+            s = &m_manager.makeFilter(type, options);
+            for (Stage *ts : inputs)
+                s->setInput(*ts);
+            inputs.clear();
+            inputs.push_back(s);
+        }
+        // 's' should be valid at this point.  makeXXX will throw if the stage
+        // couldn't be constructed.
+        if (tag.size())
+            tags[tag] = s;
+    }
+}
+
+
+void PipelineReaderJSON::readPipeline(std::istream& input)
+{
+    Json::Value root;
+    Json::Reader jsonReader;
+    if (!jsonReader.parse(input, root))
+    {
+        std::string err = "JSON pipeline: Unable to parse pipeline:\n";
+        err += jsonReader.getFormattedErrorMessages();
+        throw pdal_error(err);
+    }
+
+    Json::Value& subtree = root["pipeline"];
+    if (!subtree)
+        throw pdal_error("JSON pipeline: Root element is not a Pipeline");
+    parsePipeline(subtree);
+}
+
+
+void PipelineReaderJSON::readPipeline(const std::string& filename)
+{
+    m_inputJSONFile = filename;
+
+    std::istream* input = Utils::openFile(filename);
+    if (!input)
+    {
+        throw pdal_error("JSON pipeline: Unable to open stream for "
+            "file \"" + filename + "\"");
+    }
+
+    try
+    {
+        readPipeline(*input);
+    }
+    catch (...)
+    {
+        Utils::closeFile(input);
+        throw;
+    }
+
+    Utils::closeFile(input);
+    m_inputJSONFile = "";
+}
+
+
+std::string PipelineReaderJSON::extractType(Json::Value& node)
+{
+    std::string type;
+
+    if (node.isMember("type"))
+    {
+        Json::Value& val = node["type"];
+        if (!val.isNull())
+        {
+            if (val.isString())
+                type = val.asString();
+            else
+                throw pdal_error("JSON pipeline: 'type' must be specified as "
+                        "a string.");
+        }
+        node.removeMember("type");
+        if (node.isMember("type"))
+            throw pdal_error("JSON pipeline: found duplicate 'type' "
+               "entry in stage definition.");
+    }
+    return type;
+}
+
+
+std::string PipelineReaderJSON::extractFilename(Json::Value& node)
+{
+    std::string filename;
+
+    if (node.isMember("filename"))
+    {
+        Json::Value& val = node["filename"];
+        if (!val.isNull())
+        {
+            if (val.isString())
+                filename = val.asString();
+            else
+                throw pdal_error("JSON pipeline: 'filename' must be "
+                    "specified as a string.");
+        }
+        node.removeMember("filename");
+        if (node.isMember("filename"))
+            throw pdal_error("JSON pipeline: found duplicate 'filename' "
+               "entry in stage definition.");
+    }
+    return filename;
+}
+
+
+std::string PipelineReaderJSON::extractTag(Json::Value& node, TagMap& tags)
+{
+    std::string tag;
+
+    if (node.isMember("tag"))
+    {
+        Json::Value& val = node["tag"];
+        if (!val.isNull())
+        {
+            if (val.isString())
+            {
+                tag = val.asString();
+                if (tags.find(tag) != tags.end())
+                    throw pdal_error("JSON pipeline: duplicate tag '" +
+                        tag + "'.");
+            }
+            else
+                throw pdal_error("JSON pipeline: 'tag' must be "
+                    "specified as a string.");
+        }
+        node.removeMember("tag");
+        if (node.isMember("tag"))
+            throw pdal_error("JSON pipeline: found duplicate 'tag' "
+               "entry in stage definition.");
+    }
+    return tag;
+}
+
+
+void PipelineReaderJSON::handleInputTag(const std::string& tag,
+    const TagMap& tags, std::vector<Stage *>& inputs)
+{
+    auto ii = tags.find(tag);
+    if (ii == tags.end())
+        throw pdal_error("JSON pipeline: Invalid pipeline: "
+            "undefined stage tag '" + tag + "'.");
+    else
+        inputs.push_back(ii->second);
+}
+
+
+std::vector<Stage *> PipelineReaderJSON::extractInputs(Json::Value& node,
+    TagMap& tags)
+{
+    std::vector<Stage *> inputs;
+    std::string filename;
+
+    if (node.isMember("inputs"))
+    {
+        Json::Value& val = node["inputs"];
+        if (val.isString())
+            handleInputTag(val.asString(), tags, inputs);
+        else if (val.isArray())
+        {
+            for (const Json::Value& input : node["inputs"])
+            {
+                if (!input.isString())
+                    throw pdal_error("JSON pipeline: 'inputs' tag must "
+                            " be specified as a string or array of strings.");
+                handleInputTag(input.asString(), tags, inputs);
+            }
+        }
+        else
+            throw pdal_error("JSON pipeline: 'inputs' tag must "
+                    " be specified as a string or array of strings.");
+        node.removeMember("inputs");
+        if (node.isMember("inputs"))
+            throw pdal_error("JSON pipeline: found duplicate 'inputs' "
+               "entry in stage definition.");
+    }
+    return inputs;
+}
+
+
+Options PipelineReaderJSON::extractOptions(Json::Value& node)
+{
+    Options options;
+
+    for (const std::string& name : node.getMemberNames())
+    {
+        if (name == "plugin")
+        {
+            PluginManager::loadPlugin(node[name].asString());
+
+            // Don't actually put a "plugin" option on
+            // any stage
+            continue;
+        }
+
+        if (node[name].isString())
+            options.add(name, node[name].asString());
+        else if (node[name].isInt())
+            options.add(name, node[name].asInt64());
+        else if (node[name].isUInt())
+            options.add(name, node[name].asUInt64());
+        else if (node[name].isDouble())
+            options.add(name, node[name].asDouble());
+        else if (node[name].isBool())
+            options.add(name, node[name].asBool());
+        else if (node[name].isNull())
+            options.add(name, "");
+        else if (node[name].isArray() || node[name].isObject())
+        {
+            Json::FastWriter w;
+            options.add(name, w.write(node[name]));
+        }
+        else
+            throw pdal_error("JSON pipeline: Value of stage option '" +
+                name + "' cannot be converted.");
+    }
+    node.clear();
+    return options;
+}
+
+} // namespace pdal
diff --git a/pdal/PipelineReaderJSON.hpp b/pdal/PipelineReaderJSON.hpp
new file mode 100644
index 0000000..4ec18eb
--- /dev/null
+++ b/pdal/PipelineReaderJSON.hpp
@@ -0,0 +1,82 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/pdal_internal.hpp>
+#include <pdal/StageFactory.hpp>
+
+#include <json/json.h>
+
+#include <vector>
+#include <string>
+
+#include <pdal/Options.hpp>
+#include <pdal/StageFactory.hpp>
+
+namespace pdal
+{
+
+class Stage;
+class PipelineManager;
+
+class PDAL_DLL PipelineReaderJSON
+{
+    friend class PipelineManager;
+
+public:
+    PipelineReaderJSON(PipelineManager&);
+    void parsePipeline(Json::Value&);
+
+private:
+    typedef std::map<std::string, Stage *> TagMap;
+
+    void readPipeline(const std::string& filename);
+    void readPipeline(std::istream& input);
+    std::string extractType(Json::Value& node);
+    std::string extractFilename(Json::Value& node);
+    std::string extractTag(Json::Value& node, TagMap& tags);
+    std::vector<Stage *> extractInputs(Json::Value& node, TagMap& tags);
+    Options extractOptions(Json::Value& node);
+    void handleInputTag(const std::string& tag, const TagMap& tags,
+        std::vector<Stage *>& inputs);
+
+    PipelineManager& m_manager;
+    std::string m_inputJSONFile;
+
+    PipelineReaderJSON& operator=(const PipelineReaderJSON&); // not implemented
+    PipelineReaderJSON(const PipelineReaderJSON&); // not implemented
+};
+
+} // namespace pdal
diff --git a/pdal/PipelineReaderXML.cpp b/pdal/PipelineReaderXML.cpp
new file mode 100644
index 0000000..a0e21e6
--- /dev/null
+++ b/pdal/PipelineReaderXML.cpp
@@ -0,0 +1,484 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "private/PipelineReaderXML.hpp"
+
+#include <pdal/Filter.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/PipelineManager.hpp>
+#include <pdal/PluginManager.hpp>
+#include <pdal/Options.hpp>
+#include <pdal/util/FileUtils.hpp>
+
+#include <boost/property_tree/xml_parser.hpp>
+
+#ifndef _WIN32
+#include <wordexp.h>
+#endif
+
+namespace pdal
+{
+
+using namespace pdalboost::property_tree;
+
+// ------------------------------------------------------------------------
+
+// this class helps keep tracks of what child nodes we've seen, so we
+// can keep all the error checking in one place
+class PipelineReaderXML::StageParserContext
+{
+public:
+    enum Cardinality { None, One, Many };
+
+    StageParserContext()
+        : m_numTypes(0)
+        , m_cardinality(One)
+        , m_numStages(0)
+    {}
+
+    void setCardinality(Cardinality cardinality)
+    {
+        m_cardinality = cardinality;
+    }
+
+    void addType()
+    {
+        ++m_numTypes;
+    }
+
+    int getNumTypes()
+    {
+        return m_numTypes;
+    }
+
+    void addStage()
+    {
+        ++m_numStages;
+    }
+
+    void addUnknown(const std::string& name)
+    {
+        throw pdal_error("unknown child of element: " + name);
+    }
+
+    void validate()
+    {
+        if (m_numTypes == 0)
+            throw pdal_error("PipelineReaderXML: expected Type element "
+                "missing");
+        if (m_numTypes > 1)
+            throw pdal_error("PipelineReaderXML: extra Type element found");
+
+        if (m_cardinality == None)
+        {
+            if (m_numStages != 0)
+                throw pdal_error("PipelineReaderXML: found child stages where "
+                    "none were expected");
+        }
+        if (m_cardinality == One)
+        {
+            if (m_numStages == 0)
+                throw pdal_error("PipelineReaderXML: "
+                    "expected child stage missing");
+            if (m_numStages > 1)
+                throw pdal_error("PipelineReaderXML: extra child stages found");
+        }
+        if (m_cardinality == Many)
+        {
+            if (m_numStages == 0)
+                throw pdal_error("PipelineReaderXML: expected child stage "
+                    "missing");
+        }
+    }
+
+private:
+    int m_numTypes;
+    Cardinality m_cardinality; // num child stages allowed
+    int m_numStages;
+};
+
+
+PipelineReaderXML::PipelineReaderXML(PipelineManager& manager) :
+    m_manager(manager)
+{}
+
+
+Option PipelineReaderXML::parseElement_Option(const ptree& tree)
+{
+    // cur is an option element, such as this:
+    //     <option>
+    //       <name>myname</name>
+    //       <description>my descr</description>
+    //       <value>17</value>
+    //     </option>
+    // this function will process the element and return an Option from it
+
+    map_t attrs;
+    collect_attributes(attrs, tree);
+
+    std::string name = attrs["name"];
+    std::string value = tree.get_value<std::string>();
+    Utils::trim(value);
+
+    // filenames in the XML are fixed up as follows:
+    //   - if absolute path, leave it alone
+    //   - if relative path, make it absolute using the XML file's directory
+    // The toAbsolutePath function does exactly that magic for us.
+    if (name == "filename")
+    {
+        std::string path = value;
+#ifndef _WIN32
+        wordexp_t result;
+        if (wordexp(path.c_str(), &result, 0) == 0)
+        {
+            if (result.we_wordc == 1)
+                path = result.we_wordv[0];
+        }
+        wordfree(&result);
+#endif
+        if (!FileUtils::isAbsolutePath(path))
+        {
+            std::string abspath = FileUtils::toAbsolutePath(m_inputXmlFile);
+            std::string absdir = FileUtils::getDirectory(abspath);
+            path = FileUtils::toAbsolutePath(path, absdir);
+
+            assert(FileUtils::isAbsolutePath(path));
+        }
+        return Option(name, path);
+    }
+    else if (name == "plugin")
+    {
+       PluginManager::loadPlugin(value);
+    }
+    return Option(name, value);
+}
+
+
+Stage *PipelineReaderXML::parseElement_anystage(const std::string& name,
+    const ptree& subtree)
+{
+    if (name == "Filter")
+    {
+        return parseElement_Filter(subtree);
+    }
+    else if (name == "Reader")
+    {
+        return parseElement_Reader(subtree);
+    }
+    else if (name == "<xmlattr>")
+    {
+        // ignore: will parse later
+    }
+    else
+    {
+        throw pdal_error("PipelineReaderXML: encountered unknown stage type");
+    }
+
+    return NULL;
+}
+
+
+Stage *PipelineReaderXML::parseElement_Reader(const ptree& tree)
+{
+    Options options;
+    StageParserContext context;
+    std::string filename;
+    context.setCardinality(StageParserContext::None);
+
+    map_t attrs;
+    collect_attributes(attrs, tree);
+
+    auto iter = tree.begin();
+    while (iter != tree.end())
+    {
+        const std::string& name = iter->first;
+        const ptree& subtree = iter->second;
+
+        if (name == "<xmlattr>")
+        {
+            // already parsed
+        }
+        else if (name == "Option")
+        {
+            Option option = parseElement_Option(subtree);
+            if (option.getName() == "filename")
+                filename = option.getValue();
+            options.add(option);
+        }
+        else if (name == "Metadata")
+        {
+            // ignored for now
+        }
+        else
+        {
+            context.addUnknown(name);
+        }
+        ++iter;
+    }
+
+    std::string type;
+    if (attrs.count("type"))
+    {
+        type = attrs["type"];
+    }
+
+    Stage& reader = m_manager.makeReader(filename, type, options);
+
+    context.addType();
+    context.validate();
+    return &reader;
+}
+
+
+Stage *PipelineReaderXML::parseElement_Filter(const ptree& tree)
+{
+    Options options;
+
+    StageParserContext context;
+
+    map_t attrs;
+    collect_attributes(attrs, tree);
+
+    std::vector<Stage*> prevStages;
+    for (auto iter = tree.begin(); iter != tree.end(); ++iter)
+    {
+        const std::string& name = iter->first;
+        const ptree& subtree = iter->second;
+
+        if (name == "<xmlattr>")
+        {
+            // already parsed
+        }
+        else if (name == "Option")
+        {
+            Option option = parseElement_Option(subtree);
+            options.add(option);
+        }
+        else if (name == "Metadata")
+        {
+            // ignored
+        }
+        else if (name == "Filter" || name == "Reader")
+        {
+            context.addStage();
+            prevStages.push_back(parseElement_anystage(name, subtree));
+        }
+        else
+        {
+            context.addUnknown(name);
+        }
+    }
+
+    std::string type;
+    if (attrs.count("type"))
+        type = attrs["type"];
+
+    Stage& filter = m_manager.makeFilter(type, options);
+    for (auto sp : prevStages)
+        filter.setInput(*sp);
+    context.setCardinality(StageParserContext::Many);
+    context.addType();
+    context.validate();
+    return &filter;
+}
+
+
+void PipelineReaderXML::parse_attributes(map_t& attrs, const ptree& tree)
+{
+    for (auto iter = tree.begin(); iter != tree.end(); ++iter)
+    {
+        std::string name = iter->first;
+        std::string value = tree.get<std::string>(name);
+        Utils::trim(value);
+
+        attrs[name] = value;
+    }
+}
+
+
+void PipelineReaderXML::collect_attributes(map_t& attrs, const ptree& tree)
+{
+    if (tree.count("<xmlattr>"))
+    {
+        const ptree& subtree = tree.get_child("<xmlattr>");
+        parse_attributes(attrs, subtree);
+    }
+}
+
+
+Stage *PipelineReaderXML::parseElement_Writer(const ptree& tree)
+{
+    Options options;
+    StageParserContext context;
+    std::string filename;
+
+    map_t attrs;
+    collect_attributes(attrs, tree);
+
+    std::vector<Stage *> prevStages;
+    for (auto iter = tree.begin(); iter != tree.end(); ++iter)
+    {
+        const std::string& name = iter->first;
+        const ptree& subtree = iter->second;
+
+        if (name == "<xmlattr>")
+        {
+            // already parsed -- ignore it
+        }
+        else if (name == "Option")
+        {
+            Option option = parseElement_Option(subtree);
+            if (option.getName() == "filename")
+                filename = option.getValue();
+            options.add(option);
+        }
+        else if (name == "Metadata")
+        {
+            // ignored
+        }
+        else if (name == "Filter" || name == "Reader")
+        {
+            context.addStage();
+            prevStages.push_back(parseElement_anystage(name, subtree));
+        }
+        else
+        {
+            context.addUnknown(name);
+        }
+    }
+
+    std::string type;
+    if (attrs.count("type"))
+    {
+        type = attrs["type"];
+        context.addType();
+    }
+
+    context.validate();
+    Stage& writer = m_manager.makeWriter(filename, type, options);
+    for (auto sp : prevStages)
+        writer.setInput(*sp);
+    return &writer;
+}
+
+
+void PipelineReaderXML::parseElement_Pipeline(const ptree& tree)
+{
+    Stage *stage = NULL;
+    Stage *writer = NULL;
+
+    map_t attrs;
+    collect_attributes(attrs, tree);
+
+    std::string version = "";
+    if (attrs.count("version"))
+        version = attrs["version"];
+    if (version != "1.0")
+        throw pdal_error("PipelineReaderXML: unsupported pipeline xml version");
+
+    for (auto iter = tree.begin(); iter != tree.end(); ++iter)
+    {
+        const std::string& name = iter->first;
+        const ptree subtree = iter->second;
+
+        if (name == "Reader" || name == "Filter" )
+        {
+            stage = parseElement_anystage(name, subtree);
+        }
+        else if (name == "Writer")
+        {
+            writer = parseElement_Writer(subtree);
+        }
+        else if (name == "<xmlattr>")
+        {
+            // ignore it, already parsed
+        }
+        else
+        {
+            throw pdal_error("PipelineReaderXML: xml reader invalid child of "
+                "ReaderPipeline element");
+        }
+    }
+
+    if (writer && stage)
+    {
+        throw pdal_error("PipelineReaderXML: extra nodes at front of "
+            "writer pipeline");
+    }
+}
+
+
+void PipelineReaderXML::readPipeline(std::istream& input)
+{
+    ptree tree;
+
+    xml_parser::read_xml(input, tree, xml_parser::no_comments);
+
+    pdalboost::optional<ptree> opt(tree.get_child_optional("Pipeline"));
+    if (!opt.is_initialized())
+        throw pdal_error("PipelineReaderXML: root element is not Pipeline");
+    parseElement_Pipeline(opt.get());
+}
+
+
+void PipelineReaderXML::readPipeline(const std::string& filename)
+{
+    m_inputXmlFile = filename;
+
+    std::istream* input = Utils::openFile(filename);
+
+    try
+    {
+        readPipeline(*input);
+    }
+    catch (const pdal_error& )
+    {
+        throw;
+    }
+    catch (...)
+    {
+        Utils::closeFile(input);
+        std::ostringstream oss;
+        oss << "Unable to process pipeline file \"" << filename << "\"." <<
+            "  XML is invalid.";
+        throw pdal_error(oss.str());
+    }
+
+    Utils::closeFile(input);
+
+    m_inputXmlFile = "";
+}
+
+
+} // namespace pdal
diff --git a/src/PipelineWriter.cpp b/pdal/PipelineWriter.cpp
similarity index 100%
rename from src/PipelineWriter.cpp
rename to pdal/PipelineWriter.cpp
diff --git a/include/pdal/PipelineWriter.hpp b/pdal/PipelineWriter.hpp
similarity index 100%
rename from include/pdal/PipelineWriter.hpp
rename to pdal/PipelineWriter.hpp
diff --git a/pdal/PluginManager.cpp b/pdal/PluginManager.cpp
new file mode 100644
index 0000000..a242e07
--- /dev/null
+++ b/pdal/PluginManager.cpp
@@ -0,0 +1,499 @@
+/******************************************************************************
+* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+// The PluginManager was modeled very closely after the work of Gigi Sayfan in
+// the Dr. Dobbs article:
+// http://www.drdobbs.com/cpp/building-your-own-plugin-framework-part/206503957
+// The original work was released under the Apache License v2.
+
+#include <pdal/PluginManager.hpp>
+
+#include <pdal/pdal_defines.h>
+#include <pdal/util/Algorithm.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/util/Utils.hpp>
+
+#include "private/DynamicLibrary.hpp"
+
+#include <memory>
+#include <sstream>
+#include <string>
+
+namespace pdal
+{
+
+namespace
+{
+
+static PluginManager s_instance;
+
+#if defined(__APPLE__) && defined(__MACH__)
+    const std::string dynamicLibraryExtension(".dylib");
+#elif defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__GNU__)
+    const std::string dynamicLibraryExtension(".so");
+#elif defined _WIN32
+    const std::string dynamicLibraryExtension(".dll");
+#endif
+
+
+bool pluginTypeValid(std::string pathname, PF_PluginType type)
+{
+    return ((Utils::startsWith(pathname, "libpdal_plugin_kernel") &&
+            type & PF_PluginType_Kernel) ||
+        (Utils::startsWith(pathname, "libpdal_plugin_filter") &&
+            type & PF_PluginType_Filter) ||
+        (Utils::startsWith(pathname, "libpdal_plugin_reader") &&
+            type & PF_PluginType_Reader) ||
+        (Utils::startsWith(pathname, "libpdal_plugin_writer") &&
+            type & PF_PluginType_Writer));
+}
+
+StringList pluginSearchPaths()
+{
+    StringList searchPaths;
+    std::string envOverride;
+
+    Utils::getenv("PDAL_DRIVER_PATH", envOverride);
+
+    if (!envOverride.empty())
+        searchPaths = Utils::split2(envOverride, ':');
+    else
+    {
+        StringList standardPaths = { ".", "./lib", "../lib", "./bin", "../bin" };
+        for (std::string& s : standardPaths)
+        {
+            if (FileUtils::toAbsolutePath(s) !=
+                FileUtils::toAbsolutePath(PDAL_PLUGIN_INSTALL_PATH))
+                searchPaths.push_back(s);
+        }
+        searchPaths.push_back(PDAL_PLUGIN_INSTALL_PATH);
+    }
+    return searchPaths;
+}
+
+
+} // unnamed namespace;
+
+
+bool PluginManager::registerObject(const std::string& name,
+    const PF_RegisterParams *params)
+{
+    return s_instance.l_registerObject(name, params);
+}
+
+
+bool PluginManager::l_registerObject(const std::string& name,
+    const PF_RegisterParams *params)
+{
+    if (params && params->createFunc && params->destroyFunc &&
+        (m_version.major == params->version.major))
+    {
+        auto entry(std::make_pair(name, *params));
+
+        std::lock_guard<std::mutex> lock(m_mutex);
+        return m_plugins.insert(entry).second;
+    }
+    return false;
+}
+
+
+void PluginManager::loadAll(int type)
+{
+    s_instance.l_loadAll(type);
+}
+
+
+void PluginManager::l_loadAll(PF_PluginType type)
+{
+    for (const auto& pluginPath : pluginSearchPaths())
+        loadAll(pluginPath, type);
+}
+
+
+StringList PluginManager::test_pluginSearchPaths()
+{
+    return pluginSearchPaths();
+}
+
+
+StringList PluginManager::names(int typeMask)
+{
+    return s_instance.l_names(typeMask);
+}
+
+
+void PluginManager::setLog(LogPtr& log)
+{
+    s_instance.m_log = log;
+}
+
+
+StringList PluginManager::l_names(int typeMask)
+{
+    StringList l;
+
+    std::lock_guard<std::mutex> lock(m_mutex);
+    for (auto p : m_plugins)
+        if (p.second.pluginType & typeMask)
+            l.push_back(p.first);
+    return l;
+}
+
+std::string PluginManager::link(const std::string& name)
+{
+    return s_instance.l_link(name);
+}
+
+
+std::string PluginManager::l_link(const std::string& name)
+{
+    std::string link;
+
+    std::lock_guard<std::mutex> lock(m_mutex);
+    auto ei = m_plugins.find(name);
+    if (ei != m_plugins.end())
+        link= ei->second.link;
+    return link;
+}
+
+
+std::string PluginManager::description(const std::string& name)
+{
+    return s_instance.l_description(name);
+}
+
+
+std::string PluginManager::l_description(const std::string& name)
+{
+    std::string descrip;
+
+    std::lock_guard<std::mutex> lock(m_mutex);
+    auto ei = m_plugins.find(name);
+    if (ei != m_plugins.end())
+        descrip = ei->second.description;
+    return descrip;
+}
+
+
+void PluginManager::loadAll(const std::string& pluginDirectory, int type)
+{
+    const bool pluginDirectoryValid = pluginDirectory.size() &&
+        (FileUtils::fileExists(pluginDirectory) ||
+            FileUtils::isDirectory(pluginDirectory));
+
+    if (pluginDirectoryValid)
+    {
+        m_log->get(LogLevel::Debug) << "Loading plugins from directory " <<
+            pluginDirectory << std::endl;
+        StringList files = FileUtils::directoryList(pluginDirectory);
+        for (auto file : files)
+        {
+            if ((FileUtils::extension(file) == dynamicLibraryExtension) &&
+                !FileUtils::isDirectory(file))
+                loadByPath(file, type);
+        }
+    }
+}
+
+
+bool PluginManager::initializePlugin(PF_InitFunc initFunc)
+{
+    return s_instance.l_initializePlugin(initFunc);
+}
+
+
+bool PluginManager::l_initializePlugin(PF_InitFunc initFunc)
+{
+    if (PF_ExitFunc exitFunc = initFunc())
+    {
+        std::lock_guard<std::mutex> lock(m_mutex);
+        m_exitFuncVec.push_back(exitFunc);
+        return true;
+    }
+    return false;
+}
+
+
+PluginManager::PluginManager() : m_log(new Log("PDAL", &std::clog))
+{
+    m_version.major = 1;
+    m_version.minor = 0;
+}
+
+
+PluginManager::~PluginManager()
+{
+    if (!shutdown())
+        m_log->get(LogLevel::Error) <<
+            "Error destroying PluginManager" << std::endl;
+}
+
+
+bool PluginManager::shutdown()
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+
+    bool success(true);
+
+    for (auto const& func : m_exitFuncVec)
+    {
+        try
+        {
+            // Exit functions return 0 if successful.
+            if ((*func)() != 0)
+            {
+                success = false;
+            }
+        }
+        catch (...)
+        {
+            success = false;
+        }
+    }
+
+    // This clears the handles on the dynamic libraries so that they won't
+    // be closed with dlclose().  Depending on the order of dll unloading,
+    // it's possible for a plugin library to be unloaded before this function
+    // (which is usually in a dll) is called, which can raise an error on
+    // some systems when dlclose() is called on a library for which the
+    // reference count is already 0.  Since we're exiting anyway and all the
+    // dlls will be closed, there is no need to call dlclose() on them
+    // explicitly.
+    for (auto l : m_dynamicLibraryMap)
+        l.second->clear();
+
+    m_dynamicLibraryMap.clear();
+    m_plugins.clear();
+    m_exitFuncVec.clear();
+
+    return success;
+}
+
+
+bool PluginManager::loadPlugin(const std::string& driverFileName)
+{
+    return s_instance.l_loadPlugin(driverFileName);
+}
+
+
+bool PluginManager::l_loadPlugin(const std::string& driverFileName)
+{
+    std::vector<std::string> driverPathVec;
+    driverPathVec = Utils::split2(driverFileName, '.');
+
+    if ((FileUtils::extension(driverFileName) != dynamicLibraryExtension) ||
+            FileUtils::isDirectory(driverFileName))
+        return false;
+
+    std::vector<std::string> driverNameVec;
+    driverNameVec = Utils::split2(driverPathVec[0], '_');
+
+    std::string ptype;
+    if (driverNameVec.size() >= 3)
+        ptype = driverNameVec[2];
+
+    PF_PluginType type;
+    if (Utils::iequals(ptype, "reader"))
+        type = PF_PluginType_Reader;
+    else if (Utils::iequals(ptype,"kernel"))
+        type = PF_PluginType_Kernel;
+    else if (Utils::iequals(ptype, "filter"))
+        type = PF_PluginType_Filter;
+    else if (Utils::iequals(ptype, "writer"))
+        type = PF_PluginType_Writer;
+    else
+        throw pdal_error("Unknown plugin type '" + ptype + "'");
+
+    return loadByPath(driverFileName, type);
+}
+
+
+bool PluginManager::guessLoadByPath(const std::string& driverName)
+{
+    // parse the driver name into an expected pluginName, e.g.,
+    // writers.las => libpdal_plugin_writer_las
+
+    std::vector<std::string> driverNameVec;
+    driverNameVec = Utils::split2(driverName, '.');
+
+    for (const auto& pluginPath : pluginSearchPaths())
+    {
+        m_log->get(LogLevel::Debug) <<
+           "Plugin search path: '" << pluginPath << "'" << std::endl;
+        if (!FileUtils::fileExists(pluginPath) ||
+            !FileUtils::isDirectory(pluginPath))
+            continue;
+
+        StringList files = FileUtils::directoryList(pluginPath);
+        for (auto file : files)
+        {
+            if ((FileUtils::extension(file) != dynamicLibraryExtension) ||
+                FileUtils::isDirectory(file))
+                continue;
+
+            std::string stem = FileUtils::stem(file);
+            std::string::size_type pos = stem.find_last_of('_');
+            if (pos == std::string::npos || pos == stem.size() - 1 ||
+                    stem.substr(pos + 1) != driverNameVec[1])
+                continue;
+
+            PF_PluginType type;
+            if (driverNameVec[0] == "readers")
+                type = PF_PluginType_Reader;
+            else if (driverNameVec[0] == "kernels")
+                type = PF_PluginType_Kernel;
+            else if (driverNameVec[0] == "filters")
+                type = PF_PluginType_Filter;
+            else if (driverNameVec[0] == "writers")
+                type = PF_PluginType_Writer;
+            else
+                type = PF_PluginType_Reader;
+
+            if (loadByPath(file, type))
+                return true;
+        }
+    }
+
+    return false;
+}
+
+
+bool PluginManager::loadByPath(const std::string& pluginPath, int type)
+{
+    // Only filenames that start with libpdal_plugin are candidates to be loaded
+    // at runtime.  PDAL plugins are to be named in a specified form:
+
+    // libpdal_plugin_{stagetype}_{name}
+
+    // For example, libpdal_plugin_writer_text or libpdal_plugin_filter_color
+
+    bool loaded(false);
+
+    std::string filename = Utils::tolower(FileUtils::getFilename(pluginPath));
+
+    // If we are a valid type, and we're not yet already
+    // loaded in the LibraryMap, load it.
+
+    if (pluginTypeValid(filename, type) && !libraryLoaded(pluginPath))
+    {
+        std::string errorString;
+        auto completePath(FileUtils::toAbsolutePath(pluginPath));
+
+        m_log->get(LogLevel::Debug) << "Attempting to load plugin '" <<
+            completePath << "'." << std::endl;
+
+        if (DynamicLibrary *d = loadLibrary(completePath, errorString))
+        {
+            m_log->get(LogLevel::Debug) << "Loaded plugin '" << completePath <<
+                "'." << std::endl;
+            if (PF_InitFunc initFunc =
+                    (PF_InitFunc)(d->getSymbol("PF_initPlugin")))
+            {
+                loaded = initializePlugin(initFunc);
+                m_log->get(LogLevel::Debug) << "Initialized plugin '" <<
+                    completePath << "'." << std::endl;
+            }
+            else
+                m_log->get(LogLevel::Error) <<
+                    "Failed to initialize plugin function for plugin '" <<
+                    completePath << "'." << std::endl;
+        }
+        else
+            m_log->get(LogLevel::Error) << "Plugin '" << completePath <<
+                "' found but failed to load: " << errorString << std::endl;
+    }
+
+    return loaded;
+}
+
+
+void *PluginManager::createObject(const std::string& objectType)
+{
+    return s_instance.l_createObject(objectType);
+}
+
+
+void *PluginManager::l_createObject(const std::string& objectType)
+{
+    auto find([this, &objectType]()->bool
+    {
+        std::lock_guard<std::mutex> lock(m_mutex);
+        return m_plugins.count(objectType);
+    });
+
+
+    void *obj(0);
+    if (find() || (guessLoadByPath(objectType) && find()))
+    {
+        PF_CreateFunc f;
+        {
+            std::lock_guard<std::mutex> lock(m_mutex);
+            f = m_plugins[objectType].createFunc;
+        }
+        obj = f();
+    }
+
+    return obj;
+}
+
+
+DynamicLibrary *PluginManager::loadLibrary(const std::string& path,
+    std::string& errorString)
+{
+    DynamicLibrary *d = DynamicLibrary::load(path, errorString);
+
+    if (d)
+    {
+        std::lock_guard<std::mutex> lock(m_mutex);
+        m_dynamicLibraryMap[FileUtils::toAbsolutePath(path)] = DynLibPtr(d);
+    }
+    else
+    {
+        m_log->get(LogLevel::Error) << "Can't load library " << path <<
+            ": " << errorString;
+    }
+
+    return d;
+}
+
+bool PluginManager::libraryLoaded(const std::string& path)
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+
+    std::string p = FileUtils::toAbsolutePath(path);
+    return Utils::contains(m_dynamicLibraryMap, p);
+}
+
+} // namespace pdal
+
diff --git a/include/pdal/PluginManager.hpp b/pdal/PluginManager.hpp
similarity index 100%
rename from include/pdal/PluginManager.hpp
rename to pdal/PluginManager.hpp
diff --git a/include/pdal/PointContainer.hpp b/pdal/PointContainer.hpp
similarity index 100%
rename from include/pdal/PointContainer.hpp
rename to pdal/PointContainer.hpp
diff --git a/src/PointLayout.cpp b/pdal/PointLayout.cpp
similarity index 100%
rename from src/PointLayout.cpp
rename to pdal/PointLayout.cpp
diff --git a/pdal/PointLayout.hpp b/pdal/PointLayout.hpp
new file mode 100644
index 0000000..aa23eb5
--- /dev/null
+++ b/pdal/PointLayout.hpp
@@ -0,0 +1,246 @@
+/******************************************************************************
+* Copyright (c) 2014, Hobu Inc.
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <cstddef>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <pdal/DimDetail.hpp>
+#include <pdal/DimType.hpp>
+
+namespace pdal
+{
+
+class  PointLayout
+{
+public:
+    /**
+      Default constructor.
+    */
+    PDAL_DLL PointLayout();
+    PDAL_DLL virtual ~PointLayout() {}
+
+    /**
+      Mark a layout as finalized.  Dimensions can't be added to a finalized
+      PointLayout.
+    */
+    PDAL_DLL void finalize();
+
+    /**
+      Determine if the PointLayout is finalized.
+
+      \return  Whether the PointLayout is finalized.
+    */
+    PDAL_DLL bool finalized() const
+        { return m_finalized; }
+
+    /**
+      Register a vector of dimensions.
+
+      \param ids  Vector of IDs to register.
+    */
+    PDAL_DLL void registerDims(std::vector<Dimension::Id> ids);
+
+    /**
+      Register a list of dimensions.
+
+      \param id  Pointer to list of IDs to register.  The last ID in the list
+        must have the value Unknown.
+    */
+    PDAL_DLL void registerDims(Dimension::Id *id);
+
+    /**
+      Register use of a standard dimension (declare that a point will contain
+      data for this dimension).  Use the default type for the dimension.
+
+      \param id  ID of dimension to be registered.
+    */
+    PDAL_DLL void registerDim(Dimension::Id id);
+
+    /**
+      Register use of a standard dimension (declare that a point will contain
+      data for this dimension) if it hasn't already been registered with a
+      "larger" type.  It the dimension already exists with a larger type, this
+      does nothing.
+
+      \param id  ID of dimension to be registered.
+      \param type  Minimum type to assign to the dimension.
+    */
+    PDAL_DLL void registerDim(Dimension::Id id, Dimension::Type type);
+
+    /**
+      Assign a non-existing (proprietary) dimension with the given name and
+      type.  No check is made to see if the dimension exists as a standard
+      (non-propietary) dimension.  If the dimension has already been
+      assigned as a proprietary dimension, update the type but use the
+      existing Id.  If the dimension has already been assigned with a
+      larger type, this does nothing.
+
+      \param name  Name of the proprietary dimension to add.
+      \param type  Minimum type to assign to the dimension.
+      \return  ID of the new or existing dimension, or Unknown on failure.
+    */
+    PDAL_DLL Dimension::Id assignDim(const std::string& name,
+        Dimension::Type type);
+
+    /**
+      Register a dimension if one already exists with the given name using the
+      provided type.  If the dimension doesn't already exist, create it.
+
+      \param name  Name of the dimension to register or assign.
+      \param type  Requested type of the dimension.  Dimension will at least
+        accomodate values of this type.
+      \return  ID of dimension registered or assigned.
+    */
+    PDAL_DLL Dimension::Id registerOrAssignDim(const std::string name,
+        Dimension::Type type);
+
+    /**
+      Get a list of DimType objects that define the layout.
+
+      \return  A list of DimType objects.
+    */
+    PDAL_DLL DimTypeList dimTypes() const;
+
+    /**
+      Get a DimType structure for a named dimension.
+
+      \param name  Name of the dimension
+      \return  A DimType associated with the named dimension.  Returns a
+        DimType with an Unknown ID if the dimension isn't part of the layout.
+    */
+    PDAL_DLL DimType findDimType(const std::string& name) const;
+
+    /**
+      Get the ID of a dimension (standard or proprietary) given its name.
+
+      \param name  Name of the dimension.
+      \return  ID of the dimension or Unknown.
+    */
+    PDAL_DLL Dimension::Id findDim(const std::string& name) const;
+
+    /**
+      Get the ID of a proprietary dimension given its name.
+
+      \param name  Name of the dimension.
+      \return  ID of the dimension or Unknown.
+    */
+    PDAL_DLL Dimension::Id findProprietaryDim(const std::string& name) const;
+
+    /**
+      Get the name of a dimension give its ID.  A dimension may have more
+      than one name.  The first one associated with the ID is returned.
+
+      \param id  ID of the dimension.
+      \return  A name associated with the dimension, or a NULL string.
+    */
+    PDAL_DLL std::string dimName(Dimension::Id id) const;
+
+    /**
+      Determine if the PointLayout uses the dimension with the given ID.
+
+      \param id  ID of the dimension to check.
+      \return \c true if the layout uses the dimension, \c false otherwise.
+    */
+    PDAL_DLL bool hasDim(Dimension::Id id) const;
+
+    /**
+      Get a reference to vector of the IDs of currently used dimensions.
+
+      \return  Vector of IDs of dimensions that are part of the layout.
+    */
+    PDAL_DLL const Dimension::IdList& dims() const;
+
+    /**
+      Get the type of a dimension.
+
+      \param id  ID of the dimension.
+      \return  Type of the dimension.
+    */
+    PDAL_DLL Dimension::Type dimType(Dimension::Id id) const;
+
+    /**
+      Get the current size in bytes of the dimension.
+
+      \param id  ID of the dimension.
+      \return  Size of the dimension in bytes.
+    */
+    PDAL_DLL size_t dimSize(Dimension::Id id) const;
+
+    /**
+      Get the offset of the dimension in the layout.
+
+      \param id  ID of the dimension.
+      \return  Offset of the dimension in bytes.
+    */
+    PDAL_DLL size_t dimOffset(Dimension::Id id) const;
+
+    /**
+      Get number of bytes that make up a point.  Returns the sum of the dimSize
+      for all dimensions in the layout.
+
+      \return  Size of a point in bytes.
+    */
+    PDAL_DLL size_t pointSize() const;
+
+    /**
+      Get a pointer to a dimension's detail information.
+
+      \param id  ID of the dimension.
+      \return  A pointer a dimension's detail.
+    */
+    PDAL_DLL const Dimension::Detail *dimDetail(Dimension::Id id) const;
+
+private:
+    PDAL_DLL virtual bool update(Dimension::Detail dd, const std::string& name);
+
+    Dimension::Type resolveType( Dimension::Type t1,
+        Dimension::Type t2);
+
+protected:
+    std::vector<Dimension::Detail> m_detail;
+    Dimension::IdList m_used;
+    std::map<std::string, Dimension::Id> m_propIds;
+    int m_nextFree;
+    std::size_t m_pointSize;
+    bool m_finalized;
+};
+
+typedef PointLayout* PointLayoutPtr;
+
+} // namespace pdal
+
diff --git a/include/pdal/PointRef.hpp b/pdal/PointRef.hpp
similarity index 100%
rename from include/pdal/PointRef.hpp
rename to pdal/PointRef.hpp
diff --git a/src/PointTable.cpp b/pdal/PointTable.cpp
similarity index 100%
rename from src/PointTable.cpp
rename to pdal/PointTable.cpp
diff --git a/include/pdal/PointTable.hpp b/pdal/PointTable.hpp
similarity index 100%
rename from include/pdal/PointTable.hpp
rename to pdal/PointTable.hpp
diff --git a/pdal/PointView.cpp b/pdal/PointView.cpp
new file mode 100644
index 0000000..36b542e
--- /dev/null
+++ b/pdal/PointView.cpp
@@ -0,0 +1,245 @@
+/******************************************************************************
+* Copyright (c) 2014, Hobu Inc.
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <iomanip>
+
+#include <pdal/PointView.hpp>
+#include <pdal/PointViewIter.hpp>
+
+namespace pdal
+{
+
+int PointView::m_lastId = 0;
+
+PointView::PointView(PointTableRef pointTable) : m_pointTable(pointTable),
+m_size(0), m_id(0)
+{
+	m_id = ++m_lastId;
+}
+
+PointView::PointView(PointTableRef pointTable, const SpatialReference& srs) :
+	m_pointTable(pointTable), m_size(0), m_id(0), m_spatialReference(srs)
+{
+	m_id = ++m_lastId;
+}
+
+PointViewIter PointView::begin()
+{
+    return PointViewIter(this, 0);
+}
+
+
+PointViewIter PointView::end()
+{
+    return PointViewIter(this, size());
+}
+
+
+void PointView::setFieldInternal(Dimension::Id dim, PointId idx,
+    const void *buf)
+{
+    PointId rawId = 0;
+    if (idx == size())
+    {
+        rawId = m_pointTable.addPoint();
+        m_index.push_back(rawId);
+        m_size++;
+        assert(m_temps.empty());
+    }
+    else if (idx > size())
+    {
+        std::cerr << "Point index must increment.\n";
+        //error - throw?
+        return;
+    }
+    else
+    {
+        rawId = m_index[idx];
+    }
+    m_pointTable.setFieldInternal(dim, rawId, buf);
+}
+
+
+void PointView::calculateBounds(BOX2D& output) const
+{
+    for (PointId idx = 0; idx < size(); idx++)
+    {
+        double x = getFieldAs<double>(Dimension::Id::X, idx);
+        double y = getFieldAs<double>(Dimension::Id::Y, idx);
+
+        output.grow(x, y);
+    }
+}
+
+
+void PointView::calculateBounds(const PointViewSet& set, BOX2D& output)
+{
+    for (auto iter = set.begin(); iter != set.end(); ++iter)
+    {
+        PointViewPtr buf = *iter;
+        buf->calculateBounds(output);
+    }
+}
+
+
+void PointView::calculateBounds(BOX3D& output) const
+{
+    for (PointId idx = 0; idx < size(); idx++)
+    {
+        double x = getFieldAs<double>(Dimension::Id::X, idx);
+        double y = getFieldAs<double>(Dimension::Id::Y, idx);
+        double z = getFieldAs<double>(Dimension::Id::Z, idx);
+
+        output.grow(x, y, z);
+    }
+}
+
+
+void PointView::calculateBounds(const PointViewSet& set, BOX3D& output)
+{
+    for (auto iter = set.begin(); iter != set.end(); ++iter)
+    {
+        PointViewPtr buf = *iter;
+        buf->calculateBounds(output);
+    }
+}
+
+
+MetadataNode PointView::toMetadata() const
+{
+    MetadataNode node;
+
+    const Dimension::IdList& dims = layout()->dims();
+
+    for (PointId idx = 0; idx < size(); idx++)
+    {
+        MetadataNode pointnode = node.add(std::to_string(idx));
+        for (auto di = dims.begin(); di != dims.end(); ++di)
+        {
+            double v = getFieldAs<double>(*di, idx);
+            pointnode.add(layout()->dimName(*di), v);
+        }
+    }
+    return node;
+}
+
+
+void PointView::dump(std::ostream& ostr) const
+{
+    using std::endl;
+    PointLayoutPtr layout = m_pointTable.layout();
+    const Dimension::IdList& dims = layout->dims();
+
+    point_count_t numPoints = size();
+    ostr << "Contains " << numPoints << "  points" << endl;
+    for (PointId idx = 0; idx < numPoints; idx++)
+    {
+        ostr << "Point: " << idx << endl;
+
+        for (auto di = dims.begin(); di != dims.end(); ++di)
+        {
+            Dimension::Id d = *di;
+            const Dimension::Detail *dd = layout->dimDetail(d);
+            ostr << Dimension::name(d) << " (" <<
+                Dimension::interpretationName(dd->type()) << ") : ";
+
+            switch (dd->type())
+            {
+            case Dimension::Type::Signed8:
+                {
+                    ostr << (int)(getFieldInternal<int8_t>(d, idx));
+                    break;
+                }
+            case Dimension::Type::Signed16:
+                {
+                    ostr << getFieldInternal<int16_t>(d, idx);
+                    break;
+                }
+            case Dimension::Type::Signed32:
+                {
+                    ostr << getFieldInternal<int32_t>(d, idx);
+                    break;
+                }
+            case Dimension::Type::Signed64:
+                {
+                    ostr << getFieldInternal<int64_t>(d, idx);
+                    break;
+                }
+            case Dimension::Type::Unsigned8:
+                {
+                    ostr << (unsigned)(getFieldInternal<uint8_t>(d, idx));
+                    break;
+                }
+            case Dimension::Type::Unsigned16:
+                {
+                    ostr << getFieldInternal<uint16_t>(d, idx);
+                    break;
+                }
+            case Dimension::Type::Unsigned32:
+                {
+                    ostr << getFieldInternal<uint32_t>(d, idx);
+                    break;
+                }
+            case Dimension::Type::Unsigned64:
+                {
+                    ostr << getFieldInternal<uint64_t>(d, idx);
+                    break;
+                }
+            case Dimension::Type::Float:
+                {
+                    ostr << getFieldInternal<float>(d, idx);
+                    break;
+                }
+            case Dimension::Type::Double:
+                {
+                    ostr << getFieldInternal<double>(d, idx);
+                    break;
+                }
+            case Dimension::Type::None:
+                ostr << "NONE";
+                break;
+            }
+            ostr << endl;
+        }
+    }
+}
+
+
+std::ostream& operator<<(std::ostream& ostr, const PointView& buf)
+{
+    buf.dump(ostr);
+    return ostr;
+}
+
+} // namespace pdal
diff --git a/pdal/PointView.hpp b/pdal/PointView.hpp
new file mode 100644
index 0000000..bf0bccf
--- /dev/null
+++ b/pdal/PointView.hpp
@@ -0,0 +1,587 @@
+/******************************************************************************
+* Copyright (c) 2014, Hobu Inc.
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/DimDetail.hpp>
+#include <pdal/DimType.hpp>
+#include <pdal/PointContainer.hpp>
+#include <pdal/PointLayout.hpp>
+#include <pdal/PointRef.hpp>
+#include <pdal/PointTable.hpp>
+#include <pdal/util/Bounds.hpp>
+
+#include <memory>
+#include <queue>
+#include <set>
+#include <vector>
+#include <deque>
+
+#ifdef PDAL_COMPILER_MSVC
+#  pragma warning(disable: 4244)  // conversion from 'type1' to 'type2', possible loss of data
+#endif
+
+namespace pdal
+{
+namespace plang
+{
+    class BufferedInvocation;
+}
+
+struct PointViewLess;
+class PointView;
+class PointViewIter;
+
+typedef std::shared_ptr<PointView> PointViewPtr;
+typedef std::set<PointViewPtr, PointViewLess> PointViewSet;
+
+class PDAL_DLL PointView : public PointContainer
+{
+    friend class plang::BufferedInvocation;
+    friend class PointIdxRef;
+    friend struct PointViewLess;
+public:
+    PointView(PointTableRef pointTable);
+    PointView(PointTableRef pointTable, const SpatialReference& srs);
+
+    virtual ~PointView()
+    {}
+
+    PointViewIter begin();
+    PointViewIter end();
+
+    int id() const
+        { return m_id; }
+
+    point_count_t size() const
+        { return m_size; }
+
+    bool empty() const
+        { return m_size == 0; }
+
+    inline void appendPoint(const PointView& buffer, PointId id);
+    void append(const PointView& buf)
+    {
+        // We use size() instead of the index end because temp points
+        // might have been placed at the end of the buffer.
+        auto thisEnd = m_index.begin() + size();
+        auto bufEnd = buf.m_index.begin() + buf.size();
+        m_index.insert(thisEnd, buf.m_index.begin(), bufEnd);
+        m_size += buf.size();
+        clearTemps();
+    }
+
+    /// Return a new point view with the same point table as this
+    /// point buffer.
+    PointViewPtr makeNew() const
+    {
+        return PointViewPtr(new PointView(m_pointTable, m_spatialReference));
+    }
+
+    PointRef point(PointId id)
+        { return PointRef(*this, id); }
+
+    template<class T>
+    T getFieldAs(Dimension::Id dim, PointId pointIndex) const;
+
+    inline void getField(char *pos, Dimension::Id d,
+        Dimension::Type type, PointId id) const;
+
+    template<typename T>
+    void setField(Dimension::Id dim, PointId idx, T val);
+
+    inline void setField(Dimension::Id dim, Dimension::Type type,
+        PointId idx, const void *val);
+
+    template <typename T>
+    bool compare(Dimension::Id dim, PointId id1, PointId id2)
+    {
+        return (getFieldInternal<T>(dim, id1) < getFieldInternal<T>(dim, id2));
+    }
+
+    bool compare(Dimension::Id dim, PointId id1, PointId id2)
+    {
+        const Dimension::Detail *dd = layout()->dimDetail(dim);
+
+        switch (dd->type())
+        {
+            case Dimension::Type::Float:
+                return compare<float>(dim, id1, id2);
+                break;
+            case Dimension::Type::Double:
+                return compare<double>(dim, id1, id2);
+                break;
+            case Dimension::Type::Signed8:
+                return compare<int8_t>(dim, id1, id2);
+                break;
+            case Dimension::Type::Signed16:
+                return compare<int16_t>(dim, id1, id2);
+                break;
+            case Dimension::Type::Signed32:
+                return compare<int32_t>(dim, id1, id2);
+                break;
+            case Dimension::Type::Signed64:
+                return compare<int64_t>(dim, id1, id2);
+                break;
+            case Dimension::Type::Unsigned8:
+                return compare<uint8_t>(dim, id1, id2);
+                break;
+            case Dimension::Type::Unsigned16:
+                return compare<uint16_t>(dim, id1, id2);
+                break;
+            case Dimension::Type::Unsigned32:
+                return compare<uint32_t>(dim, id1, id2);
+                break;
+            case Dimension::Type::Unsigned64:
+                return compare<uint64_t>(dim, id1, id2);
+                break;
+            case Dimension::Type::None:
+            default:
+                return false;
+                break;
+        }
+    }
+
+    void getRawField(Dimension::Id dim, PointId idx, void *buf) const
+    {
+        getFieldInternal(dim, idx, buf);
+    }
+
+    /*! @return a cumulated bounds of all points in the PointView.
+        \verbatim embed:rst
+        .. note::
+
+            This method requires that an `X`, `Y`, and `Z` dimension be
+            available, and that it can be casted into a *double* data
+            type using the :cpp:func:`pdal::Dimension::applyScaling`
+            method. Otherwise, an exception will be thrown.
+        \endverbatim
+    */
+    void calculateBounds(BOX2D& box) const;
+    static void calculateBounds(const PointViewSet&, BOX2D& box);
+    void calculateBounds(BOX3D& box) const;
+    static void calculateBounds(const PointViewSet&, BOX3D& box);
+
+    void dump(std::ostream& ostr) const;
+    bool hasDim(Dimension::Id id) const
+        { return layout()->hasDim(id); }
+    std::string dimName(Dimension::Id id) const
+        { return layout()->dimName(id); }
+    Dimension::IdList dims() const
+        { return layout()->dims(); }
+    std::size_t pointSize() const
+        { return layout()->pointSize(); }
+    std::size_t dimSize(Dimension::Id id) const
+        { return layout()->dimSize(id); }
+    Dimension::Type dimType(Dimension::Id id) const
+         { return layout()->dimType(id);}
+    DimTypeList dimTypes() const
+        { return layout()->dimTypes(); }
+    PointLayoutPtr layout() const
+        { return m_pointTable.layout(); }
+    void setSpatialReference(const SpatialReference& spatialRef)
+        { m_spatialReference = spatialRef; }
+    SpatialReference spatialReference() const
+        { return m_spatialReference; }
+
+    /// Fill a buffer with point data specified by the dimension list.
+    /// \param[in] dims  List of dimensions/types to retrieve.
+    /// \param[in] idx   Index of point to get.
+    /// \param[in] buf   Pointer to buffer to fill.
+    void getPackedPoint(const DimTypeList& dims, PointId idx, char *buf) const
+    {
+        for (auto di = dims.begin(); di != dims.end(); ++di)
+        {
+            getField(buf, di->m_id, di->m_type, idx);
+            buf += Dimension::size(di->m_type);
+        }
+    }
+
+    /// Load the point buffer from memory whose arrangement is specified
+    /// by the dimension list.
+    /// \param[in] dims  Dimension/types of data in packed order
+    /// \param[in] idx   Index of point to write.
+    /// \param[in] buf   Packed data buffer.
+    void setPackedPoint(const DimTypeList& dims, PointId idx, const char *buf)
+    {
+        for (auto di = dims.begin(); di != dims.end(); ++di)
+        {
+            setField(di->m_id, di->m_type, idx, (const void *)buf);
+            buf += Dimension::size(di->m_type);
+        }
+    }
+
+
+    /// Provides access to the memory storing the point data.  Though this
+    /// function is public, other access methods are safer and preferred.
+    char *getPoint(PointId id)
+        { return m_pointTable.getPoint(m_index[id]); }
+
+    /// Provides access to the memory storing the point data.  Though this
+    /// function is public, other access methods are safer and preferred.
+    char *getOrAddPoint(PointId id)
+    {
+        if (id == size())
+        {
+            m_index.push_back(m_pointTable.addPoint());
+            ++m_size;
+            assert(m_temps.empty());
+        }
+
+        return m_pointTable.getPoint(m_index.at(id));
+    }
+
+    // The standard idiom is swapping with a stack-created empty queue, but
+    // that invokes the ctor and probably allocates.  We've probably only got
+    // one or two things in our queue, so just pop until we're empty.
+    void clearTemps()
+    {
+        while (!m_temps.empty())
+            m_temps.pop();
+    }
+    MetadataNode toMetadata() const;
+
+protected:
+    PointTableRef m_pointTable;
+    std::deque<PointId> m_index;
+    // The index might be larger than the size to support temporary point
+    // references.
+    point_count_t m_size;
+    int m_id;
+    std::queue<PointId> m_temps;
+    SpatialReference m_spatialReference;
+
+private:
+    static int m_lastId;
+
+    template<typename T_IN, typename T_OUT>
+    bool convertAndSet(Dimension::Id dim, PointId idx, T_IN in);
+
+    virtual void setFieldInternal(Dimension::Id dim, PointId idx,
+        const void *buf);
+    virtual void getFieldInternal(Dimension::Id dim, PointId idx,
+        void *buf) const
+    { m_pointTable.getFieldInternal(dim, m_index[idx], buf); }
+
+    template<class T>
+    T getFieldInternal(Dimension::Id dim, PointId pointIndex) const;
+    inline PointId getTemp(PointId id);
+    void freeTemp(PointId id)
+        { m_temps.push(id); }
+};
+
+struct PointViewLess
+{
+    bool operator () (const PointViewPtr& p1, const PointViewPtr& p2) const
+        { return p1->m_id < p2->m_id; }
+};
+
+template <class T>
+T PointView::getFieldInternal(Dimension::Id dim, PointId id) const
+{
+    T t;
+
+    getFieldInternal(dim, id, &t);
+    return t;
+}
+
+inline void PointView::getField(char *pos, Dimension::Id d,
+    Dimension::Type type, PointId id) const
+{
+    Everything e;
+
+    switch (type)
+    {
+    case Dimension::Type::Float:
+        e.f = getFieldAs<float>(d, id);
+        break;
+    case Dimension::Type::Double:
+        e.d = getFieldAs<double>(d, id);
+        break;
+    case Dimension::Type::Signed8:
+        e.s8 = getFieldAs<int8_t>(d, id);
+        break;
+    case Dimension::Type::Signed16:
+        e.s16 = getFieldAs<int16_t>(d, id);
+        break;
+    case Dimension::Type::Signed32:
+        e.s32 = getFieldAs<int32_t>(d, id);
+        break;
+    case Dimension::Type::Signed64:
+        e.s64 = getFieldAs<int64_t>(d, id);
+        break;
+    case Dimension::Type::Unsigned8:
+        e.u8 = getFieldAs<uint8_t>(d, id);
+        break;
+    case Dimension::Type::Unsigned16:
+        e.u16 = getFieldAs<uint16_t>(d, id);
+        break;
+    case Dimension::Type::Unsigned32:
+        e.u32 = getFieldAs<uint32_t>(d, id);
+        break;
+    case Dimension::Type::Unsigned64:
+        e.u64 = getFieldAs<uint64_t>(d, id);
+        break;
+    case Dimension::Type::None:
+        break;
+    }
+    memcpy(pos, &e, Dimension::size(type));
+}
+
+inline void PointView::setField(Dimension::Id dim,
+    Dimension::Type type, PointId idx, const void *val)
+{
+    Everything e;
+
+    memcpy(&e, val, Dimension::size(type));
+    switch (type)
+    {
+        case Dimension::Type::Float:
+            setField(dim, idx, e.f);
+            break;
+        case Dimension::Type::Double:
+            setField(dim, idx, e.d);
+            break;
+        case Dimension::Type::Signed8:
+            setField(dim, idx, e.s8);
+            break;
+        case Dimension::Type::Signed16:
+            setField(dim, idx, e.s16);
+            break;
+        case Dimension::Type::Signed32:
+            setField(dim, idx, e.s32);
+            break;
+        case Dimension::Type::Signed64:
+            setField(dim, idx, e.s64);
+            break;
+        case Dimension::Type::Unsigned8:
+            setField(dim, idx, e.u8);
+            break;
+        case Dimension::Type::Unsigned16:
+            setField(dim, idx, e.u16);
+            break;
+        case Dimension::Type::Unsigned32:
+            setField(dim, idx, e.u32);
+            break;
+        case Dimension::Type::Unsigned64:
+            setField(dim, idx, e.u64);
+            break;
+        case Dimension::Type::None:
+            break;
+    }
+}
+
+template <class T>
+inline T PointView::getFieldAs(Dimension::Id dim,
+    PointId pointIndex) const
+{
+    assert(pointIndex < m_size);
+    T retval;
+    const Dimension::Detail *dd = layout()->dimDetail(dim);
+    double val;
+
+    switch (dd->type())
+    {
+    case Dimension::Type::Float:
+        val = getFieldInternal<float>(dim, pointIndex);
+        break;
+    case Dimension::Type::Double:
+        val = getFieldInternal<double>(dim, pointIndex);
+        break;
+    case Dimension::Type::Signed8:
+        val = getFieldInternal<int8_t>(dim, pointIndex);
+        break;
+    case Dimension::Type::Signed16:
+        val = getFieldInternal<int16_t>(dim, pointIndex);
+        break;
+    case Dimension::Type::Signed32:
+        val = getFieldInternal<int32_t>(dim, pointIndex);
+        break;
+    case Dimension::Type::Signed64:
+        val = static_cast<double>(getFieldInternal<int64_t>(dim, pointIndex));
+        break;
+    case Dimension::Type::Unsigned8:
+        val = getFieldInternal<uint8_t>(dim, pointIndex);
+        break;
+    case Dimension::Type::Unsigned16:
+        val = getFieldInternal<uint16_t>(dim, pointIndex);
+        break;
+    case Dimension::Type::Unsigned32:
+        val = getFieldInternal<uint32_t>(dim, pointIndex);
+        break;
+    case Dimension::Type::Unsigned64:
+        val = static_cast<double>(getFieldInternal<uint64_t>(dim, pointIndex));
+        break;
+    case Dimension::Type::None:
+    default:
+        val = 0;
+        break;
+    }
+
+    if (!Utils::numericCast(val, retval))
+    {
+        std::ostringstream oss;
+        oss << "Unable to fetch data and convert as requested: ";
+        oss << Dimension::name(dim) << ":" <<
+            Dimension::interpretationName(dd->type()) <<
+            "(" << (double)val << ") -> " << Utils::typeidName<T>();
+        throw pdal_error(oss.str());
+    }
+
+    return retval;
+}
+
+
+template<typename T_IN, typename T_OUT>
+bool PointView::convertAndSet(Dimension::Id dim, PointId idx, T_IN in)
+{
+    T_OUT out;
+
+    bool success = Utils::numericCast(in, out);
+    if (success)
+        setFieldInternal(dim, idx, &out);
+    return success;
+}
+
+
+template<typename T>
+void PointView::setField(Dimension::Id dim, PointId idx, T val)
+{
+    const Dimension::Detail *dd = layout()->dimDetail(dim);
+
+    bool ok = true;
+    switch (dd->type())
+    {
+    case Dimension::Type::Float:
+        ok = convertAndSet<T, float>(dim, idx, val);
+        break;
+    case Dimension::Type::Double:
+        ok = convertAndSet<T, double>(dim, idx, val);
+        break;
+    case Dimension::Type::Signed8:
+        ok = convertAndSet<T, int8_t>(dim, idx, val);
+        break;
+    case Dimension::Type::Signed16:
+        ok = convertAndSet<T, int16_t>(dim, idx, val);
+        break;
+    case Dimension::Type::Signed32:
+        ok = convertAndSet<T, int32_t>(dim, idx, val);
+        break;
+    case Dimension::Type::Signed64:
+        ok = convertAndSet<T, int64_t>(dim, idx, val);
+        break;
+    case Dimension::Type::Unsigned8:
+        ok = convertAndSet<T, uint8_t>(dim, idx, val);
+        break;
+    case Dimension::Type::Unsigned16:
+        ok = convertAndSet<T, uint16_t>(dim, idx, val);
+        break;
+    case Dimension::Type::Unsigned32:
+        ok = convertAndSet<T, uint32_t>(dim, idx, val);
+        break;
+    case Dimension::Type::Unsigned64:
+        ok = convertAndSet<T, uint64_t>(dim, idx, val);
+        break;
+    case Dimension::Type::None:
+        val = 0;
+        break;
+    }
+    if (!ok)
+    {
+        std::ostringstream oss;
+        oss << "Unable to set data and convert as requested: ";
+        oss << Dimension::name(dim) << ":" << Utils::typeidName<T>() <<
+            "(" << (double)val << ") -> " <<
+            Dimension::interpretationName(dd->type());
+        throw pdal_error(oss.str());
+    }
+}
+
+/**
+void PointView::setFieldInternal(Dimension::Id dim, PointId idx,
+    const void *value)
+{
+    PointId rawId = 0;
+    if (idx == size())
+    {
+        rawId = m_pointTable.addPoint();
+        m_index.push_back(rawId);
+        m_size++;
+        assert(m_temps.empty());
+    }
+    else if (idx > size())
+    {
+        std::cerr << "Point index must increment.\n";
+        //error - throw?
+        return;
+    }
+    else
+    {
+        rawId = m_index[idx];
+    }
+    m_pointTable.setFieldInternal(dim, rawId, value);
+}
+**/
+
+inline void PointView::appendPoint(const PointView& buffer, PointId id)
+{
+    // Invalid 'id' is a programmer error.
+    PointId rawId = buffer.m_index[id];
+    m_index.push_back(rawId);
+    m_size++;
+    assert(m_temps.empty());
+}
+
+
+// Make a temporary copy of a point by adding an entry to the index.
+inline PointId PointView::getTemp(PointId id)
+{
+    PointId newid;
+    if (m_temps.size())
+    {
+        newid = m_temps.front();
+        m_temps.pop();
+        m_index[newid] = m_index[id];
+    }
+    else
+    {
+        newid = (PointId)m_index.size();
+        m_index.push_back(m_index[id]);
+    }
+    return newid;
+}
+
+PDAL_DLL std::ostream& operator<<(std::ostream& ostr, const PointView&);
+
+} // namespace pdal
diff --git a/pdal/PointViewIter.hpp b/pdal/PointViewIter.hpp
new file mode 100644
index 0000000..c519292
--- /dev/null
+++ b/pdal/PointViewIter.hpp
@@ -0,0 +1,173 @@
+/******************************************************************************
+* Copyright (c) 2014, Hobu Inc.
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <iterator>
+
+#include <pdal/PointView.hpp>
+
+namespace pdal
+{
+
+class PointIdxRef
+{
+private:
+    PointView *m_buf;
+    PointId m_id;
+    bool m_tmp;
+
+public:
+    PointIdxRef() : m_buf(NULL), m_id(0), m_tmp(false)
+    {}
+    PointIdxRef(const PointIdxRef& r) : m_buf(r.m_buf)
+    {
+        m_id = m_buf->getTemp(r.m_id);
+        m_tmp = true;
+    }
+    // This is the ctor used to make a PointIdxRef from an iterator.
+    PointIdxRef(PointView *buf, PointId id) : m_buf(buf), m_id(id), m_tmp(false)
+    {}
+
+    ~PointIdxRef()
+    {
+        if (m_tmp)
+            m_buf->freeTemp(m_id);
+    }
+
+    PointIdxRef& operator=(const PointIdxRef& r)
+    {
+        assert(m_buf == NULL || r.m_buf == m_buf);
+        if (!m_buf)
+        {
+            m_buf = r.m_buf;
+            m_id = m_buf->getTemp(r.m_id);
+            m_tmp = true;
+        }
+        else
+            m_buf->m_index[m_id] = r.m_buf->m_index[r.m_id];
+        return *this;
+    }
+
+    bool compare(Dimension::Id dim, const PointIdxRef& p) const
+        { return m_buf->compare(dim, m_id, p.m_id); }
+
+    void swap(PointIdxRef& p)
+    {
+        PointId id = m_buf->m_index[m_id];
+        m_buf->m_index[m_id] = p.m_buf->m_index[p.m_id];
+        p.m_buf->m_index[p.m_id] = id;
+    }
+};
+
+inline void swap(PointIdxRef && p1, PointIdxRef && p2)
+{
+    p1.swap(p2);
+}
+
+class PointViewIter :
+    public std::iterator<std::random_access_iterator_tag, PointIdxRef,
+        point_count_t>
+{
+protected:
+    PointView *m_buf;
+    PointId m_id;
+
+public:
+    typedef std::random_access_iterator_tag iterator_category;
+    typedef std::iterator<iterator_category, PointIdxRef>::value_type
+            value_type;
+    typedef std::iterator<iterator_category, PointIdxRef>::difference_type
+            difference_type;
+    typedef PointIdxRef reference;
+    typedef void * pointer;
+
+
+    PointViewIter(PointView *buf, PointId id) : m_buf(buf), m_id(id)
+    {}
+
+    PointViewIter& operator++()
+        { ++m_id; return *this; }
+    PointViewIter operator++(int)
+        { return PointViewIter(m_buf, m_id++); }
+    PointViewIter& operator--()
+        { --m_id; return *this; }
+    PointViewIter operator--(int)
+        { return PointViewIter(m_buf, m_id--); }
+
+    PointViewIter operator+(const difference_type& n) const
+        { return PointViewIter(m_buf, m_id + n); }
+    PointViewIter operator+=(const difference_type& n)
+        { m_id += n; return *this; }
+    PointViewIter operator-(const difference_type& n) const
+        { return PointViewIter(m_buf, m_id - n); }
+    PointViewIter operator-=(const difference_type& n)
+        { m_id -= n; return *this; }
+    difference_type operator-(const PointViewIter& i)
+        { return m_id - i.m_id; }
+
+    bool operator==(const PointViewIter& i)
+        { return m_id == i.m_id; }
+    bool operator!=(const PointViewIter& i)
+        { return m_id != i.m_id; }
+    bool operator<=(const PointViewIter& i)
+        { return m_id <= i.m_id; }
+    bool operator>=(const PointViewIter& i)
+        { return m_id >= i.m_id; }
+    bool operator<(const PointViewIter& i)
+        { return m_id < i.m_id; }
+    bool operator>(const PointViewIter& i)
+        { return m_id > i.m_id; }
+
+    PointIdxRef operator*() const
+        { return PointIdxRef(m_buf, m_id); }
+    pointer operator->() const
+        { return NULL; }
+    PointIdxRef operator[](const difference_type& /*n*/) const
+        { return PointIdxRef(m_buf, m_id); }
+};
+
+} // namespace pdal
+
+/**
+namespace std
+{
+template<>
+inline void iter_swap(pdal::PointViewIter a, pdal::PointViewIter b)
+{
+    swap(*a, *b);
+}
+}
+**/
+
diff --git a/pdal/Polygon.cpp b/pdal/Polygon.cpp
new file mode 100644
index 0000000..186c0b5
--- /dev/null
+++ b/pdal/Polygon.cpp
@@ -0,0 +1,326 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/Polygon.hpp>
+#include "cpl_string.h"
+
+#include <ogr_geometry.h>
+
+namespace pdal
+{
+
+Polygon::Polygon()
+    : Geometry ()
+{
+}
+
+
+Polygon::Polygon(const std::string& wkt_or_json, SpatialReference ref)
+    : Geometry(wkt_or_json, ref)
+{
+}
+
+
+Polygon::~Polygon()
+{
+}
+
+
+Polygon::Polygon(const Polygon& input)
+    : Geometry(input)
+{
+}
+
+Polygon::Polygon(const Geometry& input)
+    : Geometry(input)
+{
+}
+
+
+Polygon& Polygon::operator=(const Polygon& input)
+{
+
+    if (&input!= this)
+    {
+        m_geoserr = input.m_geoserr;
+        m_srs = input.m_srs;
+        geos::GeometryDeleter geom_del(m_geoserr);
+        GEOSGeomPtr p(GEOSGeom_clone_r(m_geoserr.ctx(), input.m_geom.get()), geom_del);
+        m_geom.swap(p);
+
+        prepare();
+    }
+    return *this;
+}
+
+
+Polygon::Polygon(GEOSGeometry* g, const SpatialReference& srs)
+    : Geometry(g, srs)
+{
+}
+
+
+Polygon::Polygon(OGRGeometryH g, const SpatialReference& srs) : Geometry(g, srs)
+{
+    OGRwkbGeometryType t = OGR_G_GetGeometryType(g);
+
+    if (!(t == wkbPolygon ||
+        t == wkbMultiPolygon ||
+        t == wkbPolygon25D ||
+        t == wkbMultiPolygon25D))
+    {
+        std::ostringstream oss;
+        oss << "pdal::Polygon cannot construct geometry because "
+            "OGR geometry is not Polygon or MultiPolygon!";
+        throw pdal::pdal_error(oss.str());
+    }
+
+    OGRGeometry *ogr_g = (OGRGeometry*)g;
+    //
+    // Convert the the GDAL geom to WKB in order to avoid the version
+    // context issues with exporting directoly to GEOS.
+    OGRwkbByteOrder bo =
+        GEOS_getWKBByteOrder() == GEOS_WKB_XDR ? wkbXDR : wkbNDR;
+    int wkbSize = ogr_g->WkbSize();
+    std::vector<unsigned char> wkb(wkbSize);
+
+    ogr_g->exportToWkb(bo, wkb.data());
+    geos::GeometryDeleter geom_del(m_geoserr);
+    GEOSGeomPtr p(GEOSGeomFromWKB_buf_r(m_geoserr.ctx(), wkb.data(), wkbSize), geom_del);
+    m_geom.swap(p);
+    prepare();
+
+}
+
+Polygon::Polygon(const BOX2D& box) : Geometry ()
+{
+    BOX3D box3(box.minx, box.miny, 0.0,
+               box.maxx, box.maxy, 0.0);
+    initializeFromBounds(box3);
+}
+
+
+Polygon::Polygon(const BOX3D& box) : Geometry ()
+
+{
+    initializeFromBounds(box);
+}
+
+
+void Polygon::initializeFromBounds(const BOX3D& box)
+{
+    GEOSCoordSequence* coords = GEOSCoordSeq_create_r(m_geoserr.ctx(), 5, 3);
+    auto set_coordinate = [coords, this](int pt_num, const double&x,
+        const double& y, const double& z)
+    {
+        if (!GEOSCoordSeq_setX_r(m_geoserr.ctx(), coords, pt_num, x))
+            throw pdal_error("unable to set x for coordinate sequence");
+        if (!GEOSCoordSeq_setY_r(m_geoserr.ctx(), coords, pt_num, y))
+            throw pdal_error("unable to set y for coordinate sequence");
+        if (!GEOSCoordSeq_setZ_r(m_geoserr.ctx(), coords, pt_num, z))
+            throw pdal_error("unable to set z for coordinate sequence");
+    };
+
+    set_coordinate(0, box.minx, box.miny, box.minz);
+    set_coordinate(1, box.minx, box.maxy, box.minz);
+    set_coordinate(2, box.maxx, box.maxy, box.maxz);
+    set_coordinate(3, box.maxx, box.miny, box.maxz);
+    set_coordinate(4, box.minx, box.miny, box.minz);
+
+
+    GEOSGeometry* ring = GEOSGeom_createLinearRing_r(m_geoserr.ctx(), coords);
+    if (!ring)
+        throw pdal_error("unable to create linear ring from BOX2D");
+
+
+    geos::GeometryDeleter geom_del(m_geoserr);
+    GEOSGeomPtr p(GEOSGeom_createPolygon_r(m_geoserr.ctx(), ring, 0, 0), geom_del);
+    m_geom.swap(p);
+    if (!m_geom.get())
+        throw pdal_error("unable to create polygon from linear ring in "
+            "BOX2D constructor");
+    prepare();
+}
+
+
+Polygon Polygon::transform(const SpatialReference& ref) const
+{
+    if (m_srs.empty())
+        throw pdal_error("Polygon::transform failed due to m_srs being empty");
+    if (ref.empty())
+        throw pdal_error("Polygon::transform failed due to ref being empty");
+
+    gdal::SpatialRef fromRef(m_srs.getWKT());
+    gdal::SpatialRef toRef(ref.getWKT());
+    gdal::Geometry geom(wkt(12, true), fromRef);
+    geom.transform(toRef);
+    return Geometry(geom.wkt(), ref);
+}
+
+
+Polygon Polygon::simplify(double distance_tolerance,
+    double area_tolerance) const
+{
+    GEOSGeometry *smoothed =
+        GEOSTopologyPreserveSimplify_r(m_geoserr.ctx(), m_geom.get(), distance_tolerance);
+    if (!smoothed)
+        throw pdal_error("Unable to simplify input geometry!");
+
+    std::vector<GEOSGeometry*> geometries;
+
+    int numGeom = GEOSGetNumGeometries_r(m_geoserr.ctx(), smoothed);
+    for (int n = 0; n < numGeom; ++n)
+    {
+        const GEOSGeometry* m = GEOSGetGeometryN_r(m_geoserr.ctx(), smoothed, n);
+        if (!m)
+            throw pdal::pdal_error("Unable to Get GeometryN");
+
+        const GEOSGeometry* ering = GEOSGetExteriorRing_r(m_geoserr.ctx(), m);
+        if (!ering)
+            throw pdal::pdal_error("Unable to Get Exterior Ring");
+
+        GEOSGeometry* exterior = GEOSGeom_clone_r(m_geoserr.ctx(),
+            GEOSGetExteriorRing_r(m_geoserr.ctx(), m));
+        if (!exterior)
+            throw pdal::pdal_error("Unable to clone exterior ring!");
+
+        std::vector<GEOSGeometry*> keep_rings;
+        int numRings = GEOSGetNumInteriorRings_r(m_geoserr.ctx(), m);
+        for (int i = 0; i < numRings; ++i)
+        {
+            double area(0.0);
+
+            const GEOSGeometry* iring =
+                GEOSGetInteriorRingN_r(m_geoserr.ctx(), m, i);
+            if (!iring)
+                throw pdal::pdal_error("Unable to Get Interior Ring");
+
+            GEOSGeometry* cring = GEOSGeom_clone_r(m_geoserr.ctx(), iring);
+            if (!cring)
+                throw pdal::pdal_error("Unable to clone interior ring!");
+            GEOSGeometry* aring = GEOSGeom_createPolygon_r(m_geoserr.ctx(), cring,
+                NULL, 0);
+
+            int errored = GEOSArea_r(m_geoserr.ctx(), aring, &area);
+            if (errored == 0)
+                throw pdal::pdal_error("Unable to get area of ring!");
+            if (area > area_tolerance)
+            {
+                keep_rings.push_back(cring);
+            }
+        }
+
+        GEOSGeometry* p = GEOSGeom_createPolygon_r(m_geoserr.ctx(), exterior,
+            keep_rings.data(), keep_rings.size());
+        if (p == NULL)
+            throw pdal_error("smooth polygon could not be created!" );
+        geometries.push_back(p);
+    }
+
+    GEOSGeometry* o = GEOSGeom_createCollection_r(m_geoserr.ctx(), GEOS_MULTIPOLYGON,
+        geometries.data(), geometries.size());
+    Geometry p(o, m_srs);
+    GEOSGeom_destroy_r(m_geoserr.ctx(), smoothed);
+    GEOSGeom_destroy_r(m_geoserr.ctx(), o);
+
+    return p;
+}
+
+double Polygon::area() const
+{
+    double output(0.0);
+    int errored = GEOSArea_r(m_geoserr.ctx(), m_geom.get(), &output);
+    if (errored == 0)
+        throw pdal::pdal_error("Unable to get area of ring!");
+    return output;
+}
+
+
+bool Polygon::covers(PointRef& ref) const
+{
+    GEOSCoordSequence* coords = GEOSCoordSeq_create_r(m_geoserr.ctx(), 1, 3);
+    if (!coords)
+        throw pdal_error("Unable to allocate coordinate sequence");
+
+    const double x = ref.getFieldAs<double>(Dimension::Id::X);
+    const double y = ref.getFieldAs<double>(Dimension::Id::Y);
+    const double z = ref.getFieldAs<double>(Dimension::Id::Z);
+
+    if (!GEOSCoordSeq_setX_r(m_geoserr.ctx(), coords, 0, x))
+        throw pdal_error("unable to set x for coordinate sequence");
+    if (!GEOSCoordSeq_setY_r(m_geoserr.ctx(), coords, 0, y))
+        throw pdal_error("unable to set y for coordinate sequence");
+    if (!GEOSCoordSeq_setZ_r(m_geoserr.ctx(), coords, 0, z))
+        throw pdal_error("unable to set z for coordinate sequence");
+    GEOSGeometry* p = GEOSGeom_createPoint_r(m_geoserr.ctx(), coords);
+    if (!p)
+        throw pdal_error("unable to allocate candidate test point");
+
+    bool covers = (bool)(GEOSPreparedCovers_r(m_geoserr.ctx(), m_prepGeom, p));
+    GEOSGeom_destroy_r(m_geoserr.ctx(), p);
+
+    return covers;
+}
+
+bool Polygon::covers(const Polygon& p) const
+{
+    return (bool) GEOSCovers_r(m_geoserr.ctx(), m_geom.get(), p.m_geom.get());
+}
+
+bool Polygon::overlaps(const Polygon& p) const
+{
+    return (bool) GEOSOverlaps_r(m_geoserr.ctx(), m_geom.get(), p.m_geom.get());
+}
+
+bool Polygon::contains(const Polygon& p) const
+{
+    return (bool) GEOSContains_r(m_geoserr.ctx(), m_geom.get(), p.m_geom.get());
+}
+
+bool Polygon::touches(const Polygon& p) const
+{
+    return (bool) GEOSTouches_r(m_geoserr.ctx(), m_geom.get(), p.m_geom.get());
+}
+
+bool Polygon::within(const Polygon& p) const
+{
+    return (bool) GEOSWithin_r(m_geoserr.ctx(), m_geom.get(), p.m_geom.get());
+}
+
+bool Polygon::crosses(const Polygon& p) const
+{
+    return (bool) GEOSCrosses_r(m_geoserr.ctx(), m_geom.get(), p.m_geom.get());
+}
+
+} // namespace geos
diff --git a/pdal/Polygon.hpp b/pdal/Polygon.hpp
new file mode 100644
index 0000000..dcd2487
--- /dev/null
+++ b/pdal/Polygon.hpp
@@ -0,0 +1,92 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+#pragma once
+
+#include <pdal/Log.hpp>
+#include <pdal/PointRef.hpp>
+#include <pdal/SpatialReference.hpp>
+#include <pdal/util/Bounds.hpp>
+#include <pdal/Geometry.hpp>
+
+#include <geos_c.h>
+
+namespace pdal
+{
+
+namespace geos { class ErrorHandler; }
+
+class PDAL_DLL Polygon : public Geometry
+{
+public:
+    Polygon();
+    Polygon(const std::string& wkt_or_json,
+           SpatialReference ref = SpatialReference());
+    Polygon(const BOX2D&);
+    Polygon(const BOX3D&);
+    Polygon(const Polygon&);
+    Polygon(const Geometry&);
+    Polygon(GEOSGeometry* g, const SpatialReference& srs);
+    Polygon(OGRGeometryH g, const SpatialReference& srs);
+    Polygon& operator=(const Polygon&);
+
+    OGRGeometryH getOGRHandle();
+
+    ~Polygon();
+
+    Polygon simplify(double distance_tolerance, double area_tolerance) const;
+    Polygon transform(const SpatialReference& ref) const;
+    double area() const;
+
+    bool covers(PointRef& ref) const;
+    bool equal(const Polygon& p) const;
+    bool covers(const Polygon& p) const;
+    bool overlaps(const Polygon& p) const;
+    bool contains(const Polygon& p) const;
+    bool touches(const Polygon& p) const;
+    bool within(const Polygon& p) const;
+    bool crosses(const Polygon& p) const;
+
+private:
+    void initializeFromBounds(const BOX3D& b);
+
+
+};
+
+//
+// PDAL_DLL std::ostream& operator<<(std::ostream& ostr,
+//     const Polygon& p);
+// PDAL_DLL std::istream& operator>>(std::istream& istr, Polygon& p);
+
+} // namespace pdal
+
diff --git a/src/QuadIndex.cpp b/pdal/QuadIndex.cpp
similarity index 100%
rename from src/QuadIndex.cpp
rename to pdal/QuadIndex.cpp
diff --git a/include/pdal/QuadIndex.hpp b/pdal/QuadIndex.hpp
similarity index 100%
rename from include/pdal/QuadIndex.hpp
rename to pdal/QuadIndex.hpp
diff --git a/include/pdal/QuickInfo.hpp b/pdal/QuickInfo.hpp
similarity index 100%
rename from include/pdal/QuickInfo.hpp
rename to pdal/QuickInfo.hpp
diff --git a/src/Reader.cpp b/pdal/Reader.cpp
similarity index 100%
rename from src/Reader.cpp
rename to pdal/Reader.cpp
diff --git a/include/pdal/Reader.hpp b/pdal/Reader.hpp
similarity index 100%
rename from include/pdal/Reader.hpp
rename to pdal/Reader.hpp
diff --git a/src/Scaling.cpp b/pdal/Scaling.cpp
similarity index 100%
rename from src/Scaling.cpp
rename to pdal/Scaling.cpp
diff --git a/include/pdal/Scaling.hpp b/pdal/Scaling.hpp
similarity index 100%
rename from include/pdal/Scaling.hpp
rename to pdal/Scaling.hpp
diff --git a/pdal/SpatialReference.cpp b/pdal/SpatialReference.cpp
new file mode 100644
index 0000000..8626535
--- /dev/null
+++ b/pdal/SpatialReference.cpp
@@ -0,0 +1,496 @@
+/******************************************************************************
+ * Copyright (c) 2009, Howard Butler
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of the Martin Isenburg or Iowa Department
+ *       of Natural Resources nor the names of its contributors may be
+ *       used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#include <pdal/SpatialReference.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/Metadata.hpp>
+#include <pdal/util/FileUtils.hpp>
+
+// gdal
+#ifdef PDAL_COMPILER_CLANG
+#  pragma clang diagnostic push
+#  pragma clang diagnostic ignored "-Wfloat-equal"
+#endif
+#ifdef PDAL_COMPILER_GCC
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wfloat-equal"
+#endif
+#include <ogr_spatialref.h>
+#ifdef PDAL_COMPILER_GCC
+#  pragma GCC diagnostic pop
+#endif
+#ifdef PDAL_COMPILER_CLANG
+#  pragma clang diagnostic pop
+#endif
+#include <cpl_conv.h>
+
+#include <pdal/util/Utils.hpp>
+
+namespace pdal
+{
+
+SpatialReference::SpatialReference(const std::string& s)
+{
+    set(s);
+}
+
+
+bool SpatialReference::empty() const
+{
+    return m_wkt.empty();
+}
+
+
+bool SpatialReference::valid() const
+{
+    OGRSpatialReferenceH current = OSRNewSpatialReference(m_wkt.c_str());
+    OGRErr err = OSRValidate(current);
+    OSRDestroySpatialReference(current);
+    return err == OGRERR_NONE;
+}
+
+
+std::string SpatialReference::getWKT() const
+{
+    return m_wkt;
+}
+
+
+void SpatialReference::set(std::string v)
+{
+    m_horizontalWkt.clear();
+    if (v.empty())
+    {
+        m_wkt.clear();
+        return;
+    }
+
+    std::string newV = FileUtils::readFileIntoString(v);
+    if (newV.size())
+        v = newV;
+
+    if (isWKT(v))
+    {
+        m_wkt = v;
+        return;
+    }
+
+    OGRSpatialReference srs(NULL);
+
+    CPLErrorReset();
+    const char* input = v.c_str();
+    OGRErr err = srs.SetFromUserInput(const_cast<char *>(input));
+    if (err != OGRERR_NONE)
+    {
+        std::ostringstream oss;
+        std::string msg = CPLGetLastErrorMsg();
+        if (msg.empty())
+            msg = "(unknown reason)";
+        oss << "Could not import coordinate system '" << input << "': " <<
+            msg << ".";
+        throw pdal_error(oss.str());
+    }
+
+    char *poWKT = 0;
+    srs.exportToWkt(&poWKT);
+    m_wkt = poWKT;
+    CPLFree(poWKT);
+}
+
+
+std::string SpatialReference::getProj4() const
+{
+    std::string tmp;
+
+    const char* poWKT = m_wkt.c_str();
+
+    OGRSpatialReference srs(NULL);
+    if (OGRERR_NONE == srs.importFromWkt(const_cast<char **>(&poWKT)))
+    {
+        char* proj4 = nullptr;
+        srs.exportToProj4(&proj4);
+        tmp = proj4;
+        CPLFree(proj4);
+        Utils::trim(tmp);
+    }
+
+    return tmp;
+}
+
+
+std::string SpatialReference::getVertical() const
+{
+    std::string tmp;
+
+    OGRSpatialReference* poSRS =
+        (OGRSpatialReference*)OSRNewSpatialReference(m_wkt.c_str());
+    char *pszWKT = NULL;
+
+    OGR_SRSNode* node = poSRS->GetAttrNode("VERT_CS");
+    if (node && poSRS)
+    {
+        node->exportToWkt(&pszWKT);
+        tmp = pszWKT;
+        CPLFree(pszWKT);
+        OSRDestroySpatialReference(poSRS);
+    }
+
+    return tmp;
+}
+
+
+std::string SpatialReference::getVerticalUnits() const
+{
+    std::string tmp;
+
+    std::string wkt = getVertical();
+    const char* poWKT = wkt.c_str();
+    OGRSpatialReference* poSRS =
+        (OGRSpatialReference*)OSRNewSpatialReference(m_wkt.c_str());
+    if (poSRS)
+    {
+        OGR_SRSNode* node = poSRS->GetAttrNode("VERT_CS");
+        if (node)
+        {
+            char* units(nullptr);
+
+            // 'units' remains internal to the OGRSpatialReference
+            // and should not be freed, or modified. It may be invalidated
+            // on the next OGRSpatialReference call.
+            (void)poSRS->GetLinearUnits(&units);
+            tmp = units;
+            Utils::trim(tmp);
+        }
+    }
+
+    return tmp;
+}
+
+
+std::string SpatialReference::getHorizontal() const
+{
+    if (m_horizontalWkt.empty())
+    {
+        OGRSpatialReference* poSRS =
+            (OGRSpatialReference*)OSRNewSpatialReference(m_wkt.c_str());
+
+        if (poSRS)
+        {
+            char *pszWKT(nullptr);
+            poSRS->StripVertical();
+            poSRS->exportToWkt(&pszWKT);
+            m_horizontalWkt = pszWKT;
+            CPLFree(pszWKT);
+            OSRDestroySpatialReference(poSRS);
+        }
+    }
+    return m_horizontalWkt;
+}
+
+
+std::string SpatialReference::getHorizontalUnits() const
+{
+    std::string wkt = getHorizontal();
+    const char* poWKT = wkt.c_str();
+    OGRSpatialReference* poSRS =
+        (OGRSpatialReference*)OSRNewSpatialReference(m_wkt.c_str());
+
+    if (!poSRS)
+        return std::string();
+
+    char* units(nullptr);
+
+    // The returned value remains internal to the OGRSpatialReference
+    // and should not be freed, or modified. It may be invalidated on
+    // the next OGRSpatialReference call.
+    double u = poSRS->GetLinearUnits(&units);
+    std::string tmp(units);
+    Utils::trim(tmp);
+    return tmp;
+}
+
+
+bool SpatialReference::equals(const SpatialReference& input) const
+{
+    OGRSpatialReferenceH current = OSRNewSpatialReference(getWKT().c_str());
+    OGRSpatialReferenceH other = OSRNewSpatialReference(input.getWKT().c_str());
+
+    int output = OSRIsSame(current, other);
+    OSRDestroySpatialReference(current);
+    OSRDestroySpatialReference(other);
+
+    return (output == 1);
+}
+
+
+bool SpatialReference::operator==(const SpatialReference& input) const
+{
+    return this->equals(input);
+}
+
+
+bool SpatialReference::operator!=(const SpatialReference& input) const
+{
+    return !(this->equals(input));
+}
+
+
+const std::string& SpatialReference::getName() const
+{
+    static std::string name("pdal.spatialreference");
+    return name;
+}
+
+
+bool SpatialReference::isGeographic() const
+{
+    OGRSpatialReferenceH current = OSRNewSpatialReference(m_wkt.c_str());
+    bool output = OSRIsGeographic(current);
+    OSRDestroySpatialReference(current);
+    return output;
+}
+
+
+bool SpatialReference::isGeocentric() const
+{
+    OGRSpatialReferenceH current = OSRNewSpatialReference(m_wkt.c_str());
+    bool output = OSRIsGeocentric(current);
+    OSRDestroySpatialReference(current);
+    return output;
+}
+
+int SpatialReference::calculateZone(double lon, double lat)
+{
+    int zone = 0;
+    lon = Utils::normalizeLongitude(lon);
+
+    // Special Norway processing.
+    if (lat >= 56.0 && lat < 64.0 && lon >= 3.0 && lon < 12.0 )
+        zone = 32;
+    // Special Svalbard processing.
+    else if (lat >= 72.0 && lat < 84.0)
+    {
+        if (lon >= 0.0  && lon < 9.0)
+            zone = 31;
+        else if (lon >= 9.0  && lon < 21.0)
+            zone = 33;
+        else if (lon >= 21.0  && lon < 33.0)
+            zone = 35;
+        else if (lon >= 33.0  && lon < 42.0)
+            zone = 37;
+    }
+    // Everywhere else.
+    else
+    {
+        zone = (int) floor((lon + 180.0) / 6) + 1;
+        if (lat < 0)
+            zone = -zone;
+    }
+
+    return zone;
+}
+
+
+bool SpatialReference::isWKT(const std::string& wkt)
+{
+    // List comes from GDAL.  WKT includes FITTED_CS, but this isn't
+    // included in GDAL list.  Not sure why.
+    StringList leaders { "PROJCS", "GEOGCS", "COMPD_CS", "GEOCCS",
+        "VERT_CS", "LOCAL_CS",
+    // New specification names.
+        "GEODCRS", "GEODETICCRS",
+        "PROJCRS", "PROJECTEDCRS",
+        "VERTCRS", "VERITCALCRS",
+        "ENGCRS", "ENGINEERINGCRS",
+        "IMAGECRS",
+        "PARAMETRICCRS",
+        "TIMECRS",
+        "COMPOUNDCRS"
+    };
+
+    for (const std::string& s : leaders)
+        if (wkt.compare(0, s.size(), s) == 0)
+            return true;
+    return false;
+}
+
+
+std::string SpatialReference::prettyWkt(const std::string& wkt)
+{
+    OGRSpatialReference *srs =
+        (OGRSpatialReference *)OSRNewSpatialReference(wkt.data());
+
+    char *buf = nullptr;
+    srs->exportToPrettyWkt(&buf, FALSE);
+    OSRDestroySpatialReference(srs);
+
+    std::string outWkt(buf);
+    CPLFree(buf);
+    return outWkt;
+}
+
+
+int SpatialReference::computeUTMZone(const BOX3D& box) const
+{
+    // Nothing we can do if we're an empty SRS
+    if (empty())
+        return 0;
+
+    OGRSpatialReferenceH current = OSRNewSpatialReference(m_wkt.c_str());
+    if (! current)
+        throw pdal_error("Could not fetch current SRS");
+
+    OGRSpatialReferenceH wgs84 = OSRNewSpatialReference(0);
+
+    if (OSRSetFromUserInput(wgs84, "EPSG:4326") != OGRERR_NONE)
+    {
+        OSRDestroySpatialReference(current);
+        OSRDestroySpatialReference(wgs84);
+        std::ostringstream msg;
+        msg << "Could not import GDAL input spatial reference for WGS84";
+        throw pdal_error(msg.str());
+    }
+
+    void* transform = OCTNewCoordinateTransformation(current, wgs84);
+
+    if (! transform)
+    {
+        OSRDestroySpatialReference(current);
+        OSRDestroySpatialReference(wgs84);
+        throw pdal_error("Could not compute transform from "
+            "coordinate system to WGS84");
+    }
+
+    double minx(0.0), miny(0.0), minz(0.0);
+    double maxx(0.0), maxy(0.0), maxz(0.0);
+
+    // OCTTransform modifies values in-place
+    minx = box.minx; miny = box.miny; minz = box.minz;
+    maxx = box.maxx; maxy = box.maxy; maxz = box.maxz;
+
+    int ret = OCTTransform(transform, 1, &minx, &miny, &minz);
+    if (ret == 0)
+    {
+        OCTDestroyCoordinateTransformation(transform);
+        OSRDestroySpatialReference(current);
+        OSRDestroySpatialReference(wgs84);
+        std::ostringstream msg;
+        msg << "Could not project minimum point for computeUTMZone::" <<
+            CPLGetLastErrorMsg() << ret;
+        throw pdal_error(msg.str());
+    }
+
+    ret = OCTTransform(transform, 1, &maxx, &maxy, &maxz);
+    if (ret == 0)
+    {
+        OCTDestroyCoordinateTransformation(transform);
+        OSRDestroySpatialReference(current);
+        OSRDestroySpatialReference(wgs84);
+        std::ostringstream msg;
+        msg << "Could not project maximum point for computeUTMZone::" <<
+            CPLGetLastErrorMsg() << ret;
+        throw pdal_error(msg.str());
+    }
+
+    int min_zone(0);
+    int max_zone(0);
+    min_zone = calculateZone(minx, miny);
+    max_zone = calculateZone(maxx, maxy);
+
+    if (min_zone != max_zone)
+    {
+        OCTDestroyCoordinateTransformation(transform);
+        OSRDestroySpatialReference(current);
+        OSRDestroySpatialReference(wgs84);
+        std::ostringstream msg;
+        msg << "Minimum zone is " << min_zone <<"' and maximum zone is '" <<
+            max_zone << "'. They do not match because they cross a "
+            "zone boundary";
+        throw pdal_error(msg.str());
+    }
+
+    OCTDestroyCoordinateTransformation(transform);
+    OSRDestroySpatialReference(current);
+    OSRDestroySpatialReference(wgs84);
+
+    return min_zone;
+}
+
+
+MetadataNode SpatialReference::toMetadata() const
+{
+    MetadataNode root("srs");
+    root.add("horizontal", getHorizontal());
+    root.add("vertical", getVertical());
+    root.add("isgeographic", isGeographic());
+    root.add("isgeocentric", isGeocentric());
+    root.add("proj4", getProj4());
+    root.add("prettywkt", prettyWkt(getHorizontal()));
+    root.add("wkt", getHorizontal());
+    root.add("compoundwkt", getWKT());
+    root.add("prettycompoundwkt", prettyWkt(m_wkt));
+
+    MetadataNode units = root.add("units");
+    units.add("vertical", getVerticalUnits());
+    units.add("horizontal", getHorizontalUnits());
+
+    return root;
+}
+
+
+void SpatialReference::dump() const
+{
+    std::cout << *this;
+}
+
+
+std::ostream& operator<<(std::ostream& ostr, const SpatialReference& srs)
+{
+    ostr << SpatialReference::prettyWkt(srs.m_wkt);
+    return ostr;
+}
+
+
+std::istream& operator>>(std::istream& istr, SpatialReference& srs)
+{
+    SpatialReference ref;
+
+    std::ostringstream oss;
+    oss << istr.rdbuf();
+    srs.set(oss.str());
+
+    return istr;
+}
+
+} // namespace pdal
diff --git a/pdal/SpatialReference.hpp b/pdal/SpatialReference.hpp
new file mode 100644
index 0000000..909e3d9
--- /dev/null
+++ b/pdal/SpatialReference.hpp
@@ -0,0 +1,161 @@
+/******************************************************************************
+ * Copyright (c) 2009, Howard Butler
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of the Martin Isenburg or Iowa Department
+ *       of Natural Resources nor the names of its contributors may be
+ *       used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#pragma once
+
+#include <pdal/pdal_internal.hpp>
+
+namespace pdal
+{
+
+class PDAL_DLL BOX3D;
+class PDAL_DLL MetadataNode;
+
+/// A SpatialReference defines a model of the earth that is used to describe
+/// the location of points.
+/// A SpatialReference is part of input data and is automatically loaded
+/// into PDAL by readers, or it's provided explicitly by a user through an
+/// option.  All points in a point view share a common spatial reference.  When
+/// a stage finishes processing point view, the point view takes on the
+/// spatial reference of that stage, if it had one.
+/// A point table tracks the spatial references of the views currently being
+/// processed by a stage.  If a point table being processed by a stage has
+/// more than one spatial reference, PointTable::spatialReference() will
+/// return an empty spatial reference and PointTable::spatialReferenceUnique()
+/// will return false.
+class PDAL_DLL SpatialReference
+{
+public:
+    /**
+      Constructor.  Create an empty SRS.
+    */
+    SpatialReference()
+    {}
+
+    /**
+      Construct a spatial reference from well-known text.
+
+      \param wkt  Well-known text from which to construct SRS.
+    */
+    SpatialReference(const std::string& wkt);
+
+    /**
+      Determine if this spatial reference is the same as another.
+
+      \param other  SRS to compare with this one.
+      \return  \c true if the SRSs match
+    */
+    bool equals(const SpatialReference& other) const;
+
+    /**
+      See \ref equals.
+    */
+    bool operator==(const SpatialReference& other) const;
+
+    /**
+      Determine if this spatial reference is different from another.
+
+      \param other  SRS to compare with this one.
+      \return \c true if the SRSs don't match.
+    */
+    bool operator!=(const SpatialReference& other) const;
+
+    /**
+       Determine if the well-known text representation of this SRS is
+       lexographically less than that of another.
+
+       \param other  SRS to compare with this one.
+       \return  \c true if this SRS is lexographically less.
+    */
+    bool operator<(const SpatialReference& other) const
+        { return m_wkt < other.m_wkt; }
+
+    /**
+      Returns true iff the object doesn't contain a valid srs.
+
+      \return  Whether the SRS is empty.
+    */
+    bool empty() const;
+
+
+    // Returns true of OSR can validate the SRS
+    bool valid() const;
+
+    std::string getWKT() const;
+
+    /// Sets the SRS from a string representation.  WKT is saved as
+    /// provided.
+    /// \param v - a string containing the definition (filename, proj4,
+    ///    wkt, etc).
+    void set(std::string v);
+
+    /// Returns the Proj.4 string describing the Spatial Reference System.
+    /// If GDAL is linked, it uses GDAL's operations and methods to determine
+    /// the Proj.4 string -- otherwise, if libgeotiff is linked, it uses
+    /// that.  Note that GDAL's operations are much more mature and
+    /// support more coordinate systems and descriptions.
+    std::string getProj4() const;
+
+    std::string getHorizontal() const;
+    std::string getHorizontalUnits() const;
+    std::string getVertical() const;
+    std::string getVerticalUnits() const;
+
+    void dump() const;
+    MetadataNode toMetadata() const;
+
+    bool isGeographic() const;
+    bool isGeocentric() const;
+    int computeUTMZone(const BOX3D& box) const;
+
+    const std::string& getName() const;
+    static int calculateZone(double lon, double lat);
+    static bool isWKT(const std::string& wkt);
+    static std::string prettyWkt(const std::string& wkt);
+
+private:
+    std::string m_wkt;
+    mutable std::string m_horizontalWkt;
+    friend PDAL_DLL std::ostream& operator<<(std::ostream& ostr,
+        const SpatialReference& srs);
+    friend PDAL_DLL std::istream& operator>>(std::istream& istr,
+        SpatialReference& srs);
+};
+
+PDAL_DLL std::ostream& operator<<(std::ostream& ostr,
+    const SpatialReference& srs);
+PDAL_DLL std::istream& operator>>(std::istream& istr, SpatialReference& srs);
+
+} // namespace pdal
+
diff --git a/pdal/Stage.cpp b/pdal/Stage.cpp
new file mode 100644
index 0000000..c1370d8
--- /dev/null
+++ b/pdal/Stage.cpp
@@ -0,0 +1,422 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/GDALUtils.hpp>
+#include <pdal/GEOSUtils.hpp>
+#include <pdal/PipelineManager.hpp>
+#include <pdal/Stage.hpp>
+#include <pdal/SpatialReference.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+#include "private/StageRunner.hpp"
+
+#include <iterator>
+#include <memory>
+
+namespace pdal
+{
+
+Stage::Stage() : m_progressFd(-1), m_debug(false), m_verbose(0)
+{}
+
+
+void Stage::addConditionalOptions(const Options& opts)
+{
+    for (const auto& o : opts.getOptions())
+        m_options.addConditional(o);
+}
+
+
+void Stage::serialize(MetadataNode root, PipelineWriter::TagMap& tags) const
+{
+    for (Stage *s : m_inputs)
+        s->serialize(root, tags);
+
+    auto tagname = [tags](const Stage *s)
+    {
+        const auto ti = tags.find(s);
+        return ti->second;
+    };
+
+    MetadataNode anon("pipeline");
+    anon.add("type", getName());
+    anon.add("tag", tagname(this));
+    m_options.toMetadata(anon);
+    for (Stage *s : m_inputs)
+        anon.addList("inputs", tagname(s));
+    root.addList(anon);
+}
+
+void Stage::addAllArgs(ProgramArgs& args)
+{
+    try
+    {
+        l_addArgs(args);
+        addArgs(args);
+    }
+    catch (arg_error error)
+    {
+        throw pdal_error(getName() + ": " + error.m_error);
+    }
+}
+
+
+void Stage::handleOptions()
+{
+    addAllArgs(*m_args);
+    try
+    {
+        StringList cmdline = m_options.toCommandLine();
+        m_args->parse(cmdline);
+    }
+    catch (arg_error error)
+    {
+        throw pdal_error(getName() + ": " + error.m_error);
+    }
+    setupLog();
+}
+
+
+QuickInfo Stage::preview()
+{
+    m_args.reset(new ProgramArgs);
+    handleOptions();
+    pushLogLeader();
+    QuickInfo qi = inspect();
+    popLogLeader();
+    return qi;
+}
+
+
+void Stage::prepare(PointTableRef table)
+{
+    m_args.reset(new ProgramArgs);
+    for (size_t i = 0; i < m_inputs.size(); ++i)
+    {
+        Stage *prev = m_inputs[i];
+        prev->prepare(table);
+    }
+    handleOptions();
+    pushLogLeader();
+    l_initialize(table);
+    initialize(table);
+    addDimensions(table.layout());
+    prepared(table);
+    popLogLeader();
+}
+
+
+PointViewSet Stage::execute(PointTableRef table)
+{
+    pushLogLeader();
+    table.finalize();
+
+    PointViewSet views;
+
+    // If the inputs are empty, we're a reader.
+    if (m_inputs.empty())
+    {
+        views.insert(PointViewPtr(new PointView(table)));
+    }
+    else
+    {
+        for (size_t i = 0; i < m_inputs.size(); ++i)
+        {
+            Stage *prev = m_inputs[i];
+            PointViewSet temp = prev->execute(table);
+            views.insert(temp.begin(), temp.end());
+        }
+    }
+
+    PointViewSet outViews;
+    std::vector<StageRunnerPtr> runners;
+
+    // Put the spatial references from the views onto the table.
+    // The table's spatial references are only valid as long as the stage
+    // is running.
+    // ABELL - Should we clear the references once the stage run has
+    //   completed?  Wondering if that would break something where a
+    //   writer wants to check a table's SRS.
+    SpatialReference srs;
+    table.clearSpatialReferences();
+    for (auto const& it : views)
+        table.addSpatialReference(it->spatialReference());
+    gdal::ErrorHandler::getGlobalErrorHandler().set(m_log, m_debug);
+
+    // Do the ready operation and then start running all the views
+    // through the stage.
+    ready(table);
+    for (auto const& it : views)
+    {
+        StageRunnerPtr runner(new StageRunner(this, it));
+        runners.push_back(runner);
+        runner->run();
+    }
+
+    // As the stages complete (synchronously at this time), propagate the
+    // spatial reference and merge the output views.
+    srs = getSpatialReference();
+    for (auto const& it : runners)
+    {
+        StageRunnerPtr runner(it);
+        PointViewSet temp = runner->wait();
+
+        // If our stage has a spatial reference, the view takes it on once
+        // the stage has been run.
+        if (!srs.empty())
+            for (PointViewPtr v : temp)
+                v->setSpatialReference(srs);
+        outViews.insert(temp.begin(), temp.end());
+    }
+    l_done(table);
+    popLogLeader();
+    return outViews;
+}
+
+
+// Streamed execution.
+void Stage::execute(StreamPointTable& table)
+{
+    typedef std::list<Stage *> StageList;
+
+    std::list<StageList> lists;
+    StageList stages;
+
+    table.finalize();
+
+    // Walk from the current stage backwards.  As we add each input, copy
+    // the list of stages and push it on a list.  We then pull a list from the
+    // front of list and keep going.  Placing on the back and pulling from the
+    // front insures that the stages will be executed in the order that they
+    // were added.  If we hit stage with no previous stages, we execute
+    // the stage list.
+    // All this often amounts to a bunch of list copying for
+    // no reason, but it's more simple than what we might otherwise do and
+    // this should be a nit in the grand scheme of execution time.
+    //
+    // As an example, if there are four paths from the end stage (writer) to
+    // reader stages, there will be four stage lists and execute(table, stages)
+    // will be called four times.
+    Stage *s = this;
+    stages.push_front(s);
+    while (true)
+    {
+        if (s->m_inputs.empty())
+            execute(table, stages);
+        else
+        {
+            for (auto s2 : s->m_inputs)
+            {
+                StageList newStages(stages);
+                newStages.push_front(s2);
+                lists.push_front(newStages);
+            }
+        }
+        if (lists.empty())
+            break;
+        stages = lists.back();
+        lists.pop_back();
+        s = stages.front();
+    }
+}
+
+
+void Stage::execute(StreamPointTable& table, std::list<Stage *>& stages)
+{
+    std::vector<bool> skips(table.capacity());
+    std::list<Stage *> filters;
+    SpatialReference srs;
+
+    // Separate out the first stage.
+    Stage *reader = stages.front();
+
+    // Build a list of all stages except the first.  We may have a writer in
+    // this list in addition to filters, but we treat them in the same way.
+    auto begin = stages.begin();
+    begin++;
+    std::copy(begin, stages.end(), std::back_inserter(filters));
+
+    for (Stage *s : stages)
+    {
+        s->ready(table);
+        srs = s->getSpatialReference();
+        if (!srs.empty())
+            table.setSpatialReference(srs);
+    }
+
+    // Loop until we're finished.  We handle the number of points up to
+    // the capacity of the StreamPointTable that we've been provided.
+
+    bool finished = false;
+    while (!finished)
+    {
+        // Clear the spatial reference when processing starts.
+        table.clearSpatialReferences();
+        PointId idx = 0;
+        PointRef point(table, idx);
+        point_count_t pointLimit = table.capacity();
+
+        reader->pushLogLeader();
+        // When we get false back from a reader, we're done, so set
+        // the point limit to the number of points processed in this loop
+        // of the table.
+        for (PointId idx = 0; idx < pointLimit; idx++)
+        {
+            point.setPointId(idx);
+            finished = !reader->processOne(point);
+            if (finished)
+                pointLimit = idx;
+        }
+        reader->popLogLeader();
+        srs = reader->getSpatialReference();
+        if (!srs.empty())
+            table.setSpatialReference(srs);
+
+        // When we get a false back from a filter, we're filtering out a
+        // point, so add it to the list of skips so that it doesn't get
+        // processed by subsequent filters.
+        for (Stage *s : filters)
+        {
+            s->pushLogLeader();
+            for (PointId idx = 0; idx < pointLimit; idx++)
+            {
+                if (skips[idx])
+                    continue;
+                point.setPointId(idx);
+                if (!s->processOne(point))
+                    skips[idx] = true;
+            }
+            srs = s->getSpatialReference();
+            if (!srs.empty())
+                table.setSpatialReference(srs);
+            s->popLogLeader();
+        }
+
+        // Yes, vector<bool> is terrible.  Can do something better later.
+        for (size_t i = 0; i < skips.size(); ++i)
+            skips[i] = false;
+        table.reset();
+    }
+
+    for (Stage *s : stages)
+    {
+        s->pushLogLeader();
+        s->l_done(table);
+        s->popLogLeader();
+    }
+}
+
+void Stage::l_done(PointTableRef table)
+{
+    done(table);
+}
+
+void Stage::l_addArgs(ProgramArgs& args)
+{
+    args.add("log", "Debug output filename", m_logname);
+    readerAddArgs(args);
+}
+
+
+void Stage::setupLog()
+{
+    LogLevel l(LogLevel::Error);
+
+    if (m_log)
+    {
+        l = m_log->getLevel();
+        m_logLeader = m_log->leader();
+    }
+
+    if (!m_logname.empty())
+        m_log.reset(new Log("", m_logname));
+    else if (!m_log)
+        m_log.reset(new Log("", "stdlog"));
+    m_log->setLevel(l);
+
+    // Add the stage name to the existing leader.
+    if (m_logLeader.size())
+        m_logLeader += " ";
+    m_logLeader += getName();
+
+    bool debug(l > LogLevel::Debug);
+    gdal::ErrorHandler::getGlobalErrorHandler().set(m_log, debug);
+}
+
+
+void Stage::l_initialize(PointTableRef table)
+{
+    m_metadata = table.metadata().add(getName());
+    writerInitialize(table);
+}
+
+
+void Stage::addSpatialReferenceArg(ProgramArgs& args)
+{
+    args.add("spatialreference", "Spatial reference to apply to data",
+        m_spatialReference);
+}
+
+const SpatialReference& Stage::getSpatialReference() const
+{
+    return m_spatialReference;
+}
+
+
+void Stage::setSpatialReference(const SpatialReference& spatialRef)
+{
+    setSpatialReference(m_metadata, spatialRef);
+}
+
+
+void Stage::setSpatialReference(MetadataNode& m,
+    const SpatialReference& spatialRef)
+{
+    m_spatialReference = spatialRef;
+
+    auto pred = [](MetadataNode m){ return m.name() == "spatialreference"; };
+
+    MetadataNode spatialNode = m.findChild(pred);
+    if (spatialNode.empty())
+    {
+        m.add(spatialRef.toMetadata());
+        m.add("spatialreference", spatialRef.getWKT(), "SRS of this stage");
+        m.add("comp_spatialreference", spatialRef.getWKT(),
+            "SRS of this stage");
+    }
+}
+
+} // namespace pdal
+
diff --git a/pdal/Stage.hpp b/pdal/Stage.hpp
new file mode 100644
index 0000000..bb40116
--- /dev/null
+++ b/pdal/Stage.hpp
@@ -0,0 +1,439 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <list>
+
+#include <pdal/pdal_internal.hpp>
+
+#include <pdal/Dimension.hpp>
+#include <pdal/DimType.hpp>
+#include <pdal/Log.hpp>
+#include <pdal/Metadata.hpp>
+#include <pdal/Options.hpp>
+#include <pdal/PipelineWriter.hpp>
+#include <pdal/PointTable.hpp>
+#include <pdal/PointRef.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/QuickInfo.hpp>
+#include <pdal/SpatialReference.hpp>
+#include <pdal/util/ProgramArgs.hpp>
+
+namespace pdal
+{
+
+class ProgramArgs;
+class StageRunner;
+class StageWrapper;
+
+/**
+  A stage performs the actual processing in PDAL.  Stages may read data,
+  modify or filter read data, create metadata or write processed data.
+
+  Stages are linked with setInput() into a pipeline.  The pipeline is
+  run with by calling in sequence \ref prepare() and \ref execute() on the
+  stage at the end of the pipeline.  PipelineManager can also be used to
+  create and run a pipeline.
+*/
+class PDAL_DLL Stage
+{
+    FRIEND_TEST(OptionsTest, conditional);
+    friend class StageWrapper;
+    friend class StageRunner;
+public:
+    Stage();
+    virtual ~Stage()
+        {}
+
+    /**
+      Add a stage to the input list of this stage.
+
+      \param input  Stage to use as input.
+    */
+    void setInput(Stage& input)
+        { m_inputs.push_back(&input); }
+
+    /**
+      Set a file descriptor to which progress information should be written.
+
+      \param fd  Progress file descriptor.
+    */
+    void setProgressFd(int fd)
+        { m_progressFd = fd; }
+
+    /**
+      Retrieve some basic point information without reading all data when
+      possible.  Usually implemented only by Readers.
+    */
+    QuickInfo preview();
+
+    /**
+      Prepare a stage for execution.  This function needs to be called on the
+      terminal stage of a pipeline (linked set of stages) before \ref execute
+      can be called.  Prepare recurses through all input stages.
+
+      \param table  PointTable being used for stage pipeline.
+    */
+    void prepare(PointTableRef table);
+
+    /**
+      Execute a prepared pipeline (linked set of stages).
+
+      This performs the action associated with the stage by executing the
+      \ref run function of each stage in depth first order.  Each stage is run
+      to completion (all points are processed) before the next stages is run.o
+
+      \param table  Point table being used for stage pipeline.  This must be
+        the same \ref table used in the \ref prepare function.
+    */
+    PointViewSet execute(PointTableRef table);
+
+    /**
+      Execute a prepared pipeline (linked set of stages) in streaming mode.
+
+      This performs the action associated with the stage by executing the
+      \ref processOne function of each stage in depth first order.  Points
+      are processed up to the capacity of the provided StreamPointTable.
+      Not all stages support streaming mode and an exception will be thrown
+      when attempting to \ref execute an unsupported stage.
+
+      Streaming points can reduce memory consumption, but may limit access
+      to algorithms that need to operate on full point sets.
+
+      \param table  Streming point table used for stage pipeline.  This must be
+        the same \ref table used in the \ref prepare function.
+
+    */
+    void execute(StreamPointTable& table);
+
+    /**
+      Set the spatial reference of a stage.
+
+      Set the spatial reference that will override that being carried by the
+      PointView being processed.  This is usually used when reprojecting data
+      to a new spatial reference.  The stage spatial reference will be carried
+      by PointViews processes by this stage to subsequent stages.
+
+      \param srs  Spatial reference to set.
+    */
+    void setSpatialReference(SpatialReference const& srs);
+
+    /**
+      Get the spatial reference of the stage.
+
+      Get the spatial reference that will override that being carried by the
+      PointView being processed.  This is usually used when reprojecting data
+      to a new spatial reference.  The stage spatial reference will be carried
+      by PointViews processes by this stage to subsequent stages.
+
+      \return  The stage's spatial reference.
+    */
+    const SpatialReference& getSpatialReference() const;
+
+    /**
+      Set a stage's options.
+
+      Set the options on a stage, clearing all previously set options.
+
+      \param options  Options to set.
+    */
+    void setOptions(Options options)
+        { m_options = options; }
+
+    /**
+      Add options if an option with the same name doesn't already exist on
+      the stage.
+
+      \param opts  Options to add.
+    */
+    void addConditionalOptions(const Options& opts);
+
+    /**
+      Add a stage's options to a ProgramArgs set.
+
+      \param args  ProgramArgs to add to.
+    */
+    void addAllArgs(ProgramArgs& args);
+
+    /**
+      Add options to the existing option set.
+
+      \param opts  Options to add.
+    */
+    void addOptions(const Options& opts)
+    {
+        for (const auto& o : opts.getOptions())
+            m_options.add(o);
+    }
+
+    /**
+      Remove options from a stage's option set.
+
+      \param opts  Options to remove.
+    */
+    void removeOptions(const Options& opts)
+    {
+        for (const auto& o : opts.getOptions())
+            m_options.remove(o);
+    }
+
+    /**
+      Set the stage's log.
+
+      \param log  Log pointer.
+    */
+    void setLog(LogPtr& log)
+        { m_log = log; }
+
+    /**
+      Return the stage's log pointer.
+
+      \return  Log pointer.
+    */
+    virtual LogPtr log() const
+        { return m_log; }
+
+    /**
+      Push the stage's leader into the log.
+    */
+    void pushLogLeader() const
+        { m_log->pushLeader(m_logLeader); }
+
+    /**
+        Pop the stage's leader from the log.
+    */
+    void popLogLeader() const
+        { m_log->popLeader(); }
+
+    /**
+      Determine whether the stage is in debug mode or not.
+
+      \return  The stage's debug state.
+    */
+    bool isDebug() const
+        { return m_debug; }
+
+    /**
+      Return the name of a stage.
+
+      \return  The stage's name.
+    */
+    virtual std::string getName() const = 0;
+
+    /**
+      Return the tag name of a stage.
+
+      The tag name is used when writing a JSON pipeline.  It is generally
+      the same as the stage name, but a number is appended to maintain
+      uniqueness when stages appear more than once in a pipeline.
+      the same as
+
+      \return  The tag's name.
+    */
+    virtual std::string tagName() const
+        { return getName(); }
+
+    /**
+      Return a list of the stage's inputs.
+
+      \return  A vector pointers to input stages.
+    **/
+    const std::vector<Stage*>& getInputs() const
+        { return m_inputs; }
+
+    /**
+      Get the stage's metadata node.
+
+      \return  Stage's metadata.
+    */
+    MetadataNode getMetadata() const
+        { return m_metadata; }
+
+    /**
+      Serialize a stage by inserting apporpritate data into the provided
+      MetadataNode.  Used to dump a pipeline specification in a portable
+      format.
+
+      \param root  Node to which a stages meatdata should be added.
+      \param tags  Pipeline writer's current list of stage tags.
+    */
+    void serialize(MetadataNode root, PipelineWriter::TagMap& tags) const;
+
+protected:
+    Options m_options;          ///< Stage's options.
+    MetadataNode m_metadata;    ///< Stage's metadata.
+    int m_progressFd;           ///< Descriptor for progress info.
+
+    void setSpatialReference(MetadataNode& m, SpatialReference const&);
+    void addSpatialReferenceArg(ProgramArgs& args);
+
+private:
+    bool m_debug;
+    uint32_t m_verbose;
+    std::string m_logname;
+    std::vector<Stage *> m_inputs;
+    LogPtr m_log;
+    std::string m_logLeader;
+    SpatialReference m_spatialReference;
+    std::unique_ptr<ProgramArgs> m_args;
+
+    Stage& operator=(const Stage&); // not implemented
+    Stage(const Stage&); // not implemented
+
+    void setupLog();
+    void handleOptions();
+
+    virtual void readerAddArgs(ProgramArgs& /*args*/)
+        {}
+    void l_addArgs(ProgramArgs& args);
+    void l_done(PointTableRef table);
+
+    virtual void writerInitialize(PointTableRef /*table*/)
+        {}
+
+    void l_initialize(PointTableRef table);
+
+    /**
+      Get basic metadata (avoids reading points).  Implement in subclass.
+
+      \return  QuickInfo data.
+    */
+    virtual QuickInfo inspect()
+        { return QuickInfo(); }
+
+    /**
+      Add arguments(options) handled by this stage.  Implement in subclass.
+
+      \param args  ProgramArgs object to which arguments should be added.
+    */
+    virtual void addArgs(ProgramArgs& /*args*/)
+    {}
+
+    /**
+      Process options.  Implement in subclass.
+
+      \param options  Options to process.
+    */
+    virtual void processOptions(const Options& /*options*/)
+        {}
+
+    /**
+      Initialize stage after options have been processed.  Implement in
+      subclass.  If you don't require the \ref table argument, you
+      can implement the version of this function that takes no arguments
+      instead of this function.
+
+      \param table  PointTable associated with pipeline.
+    */
+    virtual void initialize(PointTableRef /*table*/)
+        { initialize(); }
+
+    /**
+      Initialize stage after options have been processed.  Implement in
+      subclass.
+    */
+    virtual void initialize()
+        {}
+
+    /**
+      Add dimensions to a layout.
+
+      \param layout  Point layout.
+    */
+    virtual void addDimensions(PointLayoutPtr /*layout*/)
+        {}
+
+    /**
+      Functions called after dimensions have been added.  Implement in
+      subclass.
+
+      \param table  PointTable associated with pipeline.
+    */
+    virtual void prepared(PointTableRef /*table*/)
+        {}
+
+    /**
+      First part of the execute step.  Called after all stages have been
+      prepared.  Implement in subclass.
+
+      \param table  PointTable associated with the pipeline.
+    */
+    virtual void ready(PointTableRef /*table*/)
+        {}
+
+    /**
+      Process a single point (streaming mode).  Implement in sublcass.
+
+      \param point  Point to process.
+      \return  Readers return false when no more points are to be read.
+        Filters return false if a point is to be filtered-out (not passed
+        to subsequent stages).
+    */
+    virtual bool processOne(PointRef& /*point*/)
+    {
+        std::ostringstream oss;
+        oss << "Point streaming not supported for stage " << getName() << ".";
+        throw pdal_error(oss.str());
+    }
+
+    /**
+      Process all points in a view.  Implement in subclass.
+
+      \param view  PointView to process.
+    */
+    virtual PointViewSet run(PointViewPtr /*view*/)
+    {
+        std::cerr << "Can't run stage = " << getName() << "!\n";
+        return PointViewSet();
+    }
+
+    /**
+      Called after all point views have been processed.  Implement in subclass.
+
+      \param table  PointTable associated with pipeline.
+    */
+    virtual void done(PointTableRef /*table*/)
+        {}
+
+    void execute(StreamPointTable& table, std::list<Stage *>& stages);
+
+    /*
+      Test hook.
+    */
+    const Options& getOptions() const
+        { return m_options; }
+};
+
+} // namespace pdal
diff --git a/pdal/StageFactory.cpp b/pdal/StageFactory.cpp
new file mode 100644
index 0000000..debf2f1
--- /dev/null
+++ b/pdal/StageFactory.cpp
@@ -0,0 +1,321 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/StageFactory.hpp>
+#include <pdal/PluginManager.hpp>
+#include <pdal/util/FileUtils.hpp>
+
+#include <filters/ApproximateCoplanarFilter.hpp>
+#include <filters/AttributeFilter.hpp>
+#include <filters/ChipperFilter.hpp>
+#include <filters/ColorizationFilter.hpp>
+#include <filters/ColorinterpFilter.hpp>
+#include <filters/ComputeRangeFilter.hpp>
+#include <filters/CropFilter.hpp>
+#include <filters/DecimationFilter.hpp>
+#include <filters/DividerFilter.hpp>
+#include <filters/EigenvaluesFilter.hpp>
+#include <filters/EstimateRankFilter.hpp>
+#include <filters/FerryFilter.hpp>
+#include <filters/HAGFilter.hpp>
+#include <filters/IQRFilter.hpp>
+#include <filters/KDistanceFilter.hpp>
+#include <filters/LOFFilter.hpp>
+#include <filters/MADFilter.hpp>
+#include <filters/MergeFilter.hpp>
+#include <filters/MongusFilter.hpp>
+#include <filters/MortonOrderFilter.hpp>
+#include <filters/NormalFilter.hpp>
+#include <filters/OutlierFilter.hpp>
+#include <filters/PMFFilter.hpp>
+#include <filters/RadialDensityFilter.hpp>
+#include <filters/RangeFilter.hpp>
+#include <filters/ReprojectionFilter.hpp>
+#include <filters/SampleFilter.hpp>
+#include <filters/SMRFilter.hpp>
+#include <filters/SortFilter.hpp>
+#include <filters/SplitterFilter.hpp>
+#include <filters/StatsFilter.hpp>
+#include <filters/TransformationFilter.hpp>
+
+// readers
+#include <io/BpfReader.hpp>
+#include <io/FauxReader.hpp>
+#include <io/GDALReader.hpp>
+#include <io/Ilvis2Reader.hpp>
+#include <io/LasReader.hpp>
+#include <io/OptechReader.hpp>
+#include <io/BufferReader.hpp>
+#include <io/PlyReader.hpp>
+#include <io/PtsReader.hpp>
+#include <io/QfitReader.hpp>
+#include <io/SbetReader.hpp>
+#include <io/TerrasolidReader.hpp>
+#include <io/TextReader.hpp>
+#include <io/TIndexReader.hpp>
+
+// writers
+#include <io/BpfWriter.hpp>
+#include <io/GDALWriter.hpp>
+#include <io/LasWriter.hpp>
+#include <io/PlyWriter.hpp>
+#include <io/SbetWriter.hpp>
+#include <io/DerivativeWriter.hpp>
+#include <io/TextWriter.hpp>
+#include <io/NullWriter.hpp>
+
+#include <sstream>
+#include <string>
+#include <stdio.h> // for funcptr
+
+namespace pdal
+{
+
+StringList StageFactory::extensions(const std::string& driver)
+{
+    static std::map<std::string, StringList> exts =
+    {
+        { "readers.terrasolid", { "bin" } },
+        { "readers.bpf", { "bpf" }  },
+        { "readers.optech", { "csd" } },
+        { "readers.greyhound", { "greyhound" } },
+        { "readers.icebridge", { "icebridge" } },
+        { "readers.las", { "las", "laz" } },
+        { "readers.nitf", { "nitf", "nsf", "ntf" } },
+        { "readers.pcd", { "pcd" } },
+        { "readers.ply", { "ply" } },
+        { "readers.pts", { "pts" } },
+        { "readers.qfit", { "qi" } },
+        { "readers.rxp", { "rxp" } },
+        { "readers.sbet", { "sbet" } },
+        { "readers.sqlite", { "sqlite" } },
+        { "readers.mrsid", { "sid" } },
+        { "readers.tindex", { "tindex" } },
+        { "readers.text", { "txt" } },
+        { "readers.icebridge", { "h5" } },
+
+        { "writers.bpf", { "bpf" } },
+        { "writers.text", { "csv", "json", "txt", "xyz" } },
+        { "writers.las", { "las", "laz" } },
+        { "writers.matlab", { "mat" } },
+        { "writers.nitf", { "nitf", "nsf", "ntf" } },
+        { "writers.pcd", { "pcd" } },
+        { "writers.pclvisualizer", { "pclvis" } },
+        { "writers.ply", { "ply" } },
+        { "writers.sbet", { "sbet" } },
+        { "writers.derivative", { "derivative" } },
+        { "writers.sqlite", { "sqlite" } },
+    };
+
+    return exts[driver];
+}
+
+std::string StageFactory::inferReaderDriver(const std::string& filename)
+{
+    static std::map<std::string, std::string> drivers =
+    {
+        { "bin", "readers.terrasolid" },
+        { "bpf", "readers.bpf" },
+        { "csd", "readers.optech" },
+        { "greyhound", "readers.greyhound" },
+        { "icebridge", "readers.icebridge" },
+        { "las", "readers.las" },
+        { "laz", "readers.las" },
+        { "nitf", "readers.nitf" },
+        { "nsf", "readers.nitf" },
+        { "ntf", "readers.nitf" },
+        { "pcd", "readers.pcd" },
+        { "ply", "readers.ply" },
+        { "pts", "readers.pts" },
+        { "qi", "readers.qfit" },
+        { "rxp", "readers.rxp" },
+        { "sbet", "readers.sbet" },
+        { "sqlite", "readers.sqlite" },
+        { "sid", "readers.mrsid" },
+        { "tindex", "readers.tindex" },
+        { "txt", "readers.text" },
+        { "h5", "readers.icebridge" }
+    };
+
+    static const std::string ghPrefix("greyhound://");
+
+    std::string ext;
+    // filename may actually be a greyhound uri + pipelineId
+    if (Utils::iequals(filename.substr(0, ghPrefix.size()), ghPrefix))
+        ext = ".greyhound";      // Make it look like an extension.
+    else
+        ext = FileUtils::extension(filename);
+
+    // Strip off '.' and make lowercase.
+    if (ext.length())
+        ext = Utils::tolower(ext.substr(1, ext.length() - 1));
+
+    return drivers[ext]; // will be "" if not found
+}
+
+
+std::string StageFactory::inferWriterDriver(const std::string& filename)
+{
+    std::string ext;
+
+    if (filename == "STDOUT")
+        ext = ".txt";
+    else
+        ext = Utils::tolower(FileUtils::extension(filename));
+
+    static std::map<std::string, std::string> drivers =
+    {
+        { "bpf", "writers.bpf" },
+        { "csv", "writers.text" },
+        { "json", "writers.text" },
+        { "las", "writers.las" },
+        { "laz", "writers.las" },
+        { "mat", "writers.matlab" },
+        { "ntf", "writers.nitf" },
+        { "pcd", "writers.pcd" },
+        { "pclviz", "writers.pclvisualizer" },
+        { "ply", "writers.ply" },
+        { "sbet", "writers.sbet" },
+        { "derivative", "writers.derivative" },
+        { "sqlite", "writers.sqlite" },
+        { "txt", "writers.text" },
+        { "xyz", "writers.text" },
+        { "", "writers.text" },
+        { "tif", "writers.gdal" }
+    };
+
+    // Strip off '.' and make lowercase.
+    if (ext.length())
+        ext = Utils::tolower(ext.substr(1, ext.length() - 1));
+
+    return drivers[ext];
+}
+
+
+StageFactory::StageFactory(bool no_plugins)
+{
+    if (!no_plugins)
+    {
+        PluginManager::loadAll(PF_PluginType_Filter | PF_PluginType_Reader |
+            PF_PluginType_Writer);
+    }
+
+    // filters
+    PluginManager::initializePlugin(ApproximateCoplanarFilter_InitPlugin);
+    PluginManager::initializePlugin(AttributeFilter_InitPlugin);
+    PluginManager::initializePlugin(ChipperFilter_InitPlugin);
+    PluginManager::initializePlugin(ColorizationFilter_InitPlugin);
+    PluginManager::initializePlugin(ColorinterpFilter_InitPlugin);
+    PluginManager::initializePlugin(ComputeRangeFilter_InitPlugin);
+    PluginManager::initializePlugin(CropFilter_InitPlugin);
+    PluginManager::initializePlugin(DecimationFilter_InitPlugin);
+    PluginManager::initializePlugin(DividerFilter_InitPlugin);
+    PluginManager::initializePlugin(EigenvaluesFilter_InitPlugin);
+    PluginManager::initializePlugin(EstimateRankFilter_InitPlugin);
+    PluginManager::initializePlugin(FerryFilter_InitPlugin);
+    PluginManager::initializePlugin(HAGFilter_InitPlugin);
+    PluginManager::initializePlugin(IQRFilter_InitPlugin);
+    PluginManager::initializePlugin(KDistanceFilter_InitPlugin);
+    PluginManager::initializePlugin(LOFFilter_InitPlugin);
+    PluginManager::initializePlugin(MADFilter_InitPlugin);
+    PluginManager::initializePlugin(MergeFilter_InitPlugin);
+    PluginManager::initializePlugin(MongusFilter_InitPlugin);
+    PluginManager::initializePlugin(MortonOrderFilter_InitPlugin);
+    PluginManager::initializePlugin(NormalFilter_InitPlugin);
+    PluginManager::initializePlugin(OutlierFilter_InitPlugin);
+    PluginManager::initializePlugin(PMFFilter_InitPlugin);
+    PluginManager::initializePlugin(RadialDensityFilter_InitPlugin);
+    PluginManager::initializePlugin(RangeFilter_InitPlugin);
+    PluginManager::initializePlugin(ReprojectionFilter_InitPlugin);
+    PluginManager::initializePlugin(SampleFilter_InitPlugin);
+    PluginManager::initializePlugin(SMRFilter_InitPlugin);
+    PluginManager::initializePlugin(SortFilter_InitPlugin);
+    PluginManager::initializePlugin(SplitterFilter_InitPlugin);
+    PluginManager::initializePlugin(StatsFilter_InitPlugin);
+    PluginManager::initializePlugin(TransformationFilter_InitPlugin);
+
+    // readers
+    PluginManager::initializePlugin(BpfReader_InitPlugin);
+    PluginManager::initializePlugin(FauxReader_InitPlugin);
+    PluginManager::initializePlugin(GDALReader_InitPlugin);
+    PluginManager::initializePlugin(Ilvis2Reader_InitPlugin);
+    PluginManager::initializePlugin(LasReader_InitPlugin);
+    PluginManager::initializePlugin(OptechReader_InitPlugin);
+    PluginManager::initializePlugin(PlyReader_InitPlugin);
+    PluginManager::initializePlugin(PtsReader_InitPlugin);
+    PluginManager::initializePlugin(QfitReader_InitPlugin);
+    PluginManager::initializePlugin(SbetReader_InitPlugin);
+    PluginManager::initializePlugin(TerrasolidReader_InitPlugin);
+    PluginManager::initializePlugin(TextReader_InitPlugin);
+    PluginManager::initializePlugin(TIndexReader_InitPlugin);
+
+    // writers
+    PluginManager::initializePlugin(BpfWriter_InitPlugin);
+    PluginManager::initializePlugin(DerivativeWriter_InitPlugin);
+    PluginManager::initializePlugin(GDALWriter_InitPlugin);
+    PluginManager::initializePlugin(LasWriter_InitPlugin);
+    PluginManager::initializePlugin(PlyWriter_InitPlugin);
+    PluginManager::initializePlugin(SbetWriter_InitPlugin);
+    PluginManager::initializePlugin(TextWriter_InitPlugin);
+    PluginManager::initializePlugin(NullWriter_InitPlugin);
+}
+
+
+Stage *StageFactory::createStage(std::string const& stage_name)
+{
+    static_assert(0 < sizeof(Stage), "");
+    Stage *s = static_cast<Stage*>(PluginManager::createObject(stage_name));
+    if (s)
+    {
+        std::lock_guard<std::mutex> lock(m_mutex);
+        m_ownedStages.push_back(std::unique_ptr<Stage>(s));
+    }
+    return s;
+}
+
+
+void StageFactory::destroyStage(Stage *s)
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+    for (auto it = m_ownedStages.begin(); it != m_ownedStages.end(); ++it)
+    {
+        if (s == it->get())
+        {
+            m_ownedStages.erase(it);
+            break;
+        }
+    }
+}
+
+} // namespace pdal
diff --git a/include/pdal/StageFactory.hpp b/pdal/StageFactory.hpp
similarity index 100%
rename from include/pdal/StageFactory.hpp
rename to pdal/StageFactory.hpp
diff --git a/include/pdal/StageWrapper.hpp b/pdal/StageWrapper.hpp
similarity index 100%
rename from include/pdal/StageWrapper.hpp
rename to pdal/StageWrapper.hpp
diff --git a/pdal/Writer.cpp b/pdal/Writer.cpp
new file mode 100644
index 0000000..bc540d8
--- /dev/null
+++ b/pdal/Writer.cpp
@@ -0,0 +1,72 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/Writer.hpp>
+#include <pdal/Stage.hpp>
+
+#include <pdal/util/ProgramArgs.hpp>
+
+#ifdef PDAL_COMPILER_MSVC
+#  pragma warning(disable: 4127)  // conditional expression is constant
+#endif
+
+namespace pdal
+{
+
+std::string::size_type Writer::handleFilenameTemplate(
+    const std::string& filename)
+{
+    std::string::size_type suffixPos = filename.find_last_of('.');
+    std::string::size_type hashPos = filename.find_first_of('#');
+    if (hashPos == std::string::npos)
+        return hashPos;
+
+    if (hashPos > suffixPos)
+    {
+        std::ostringstream oss;
+        oss << getName() << ": Filename template placeholder ('#') is not "
+            "allowed in filename suffix.";
+        throw pdal_error(oss.str());
+    }
+    if (filename.find_first_of('#', hashPos + 1) != std::string::npos)
+    {
+        std::ostringstream oss;
+        oss << getName() << ": Filename specification can only contain "
+            "a single '#' template placeholder.";
+        throw pdal_error(oss.str());
+    }
+    return hashPos;
+}
+
+} // namespace pdal
diff --git a/pdal/Writer.hpp b/pdal/Writer.hpp
new file mode 100644
index 0000000..9618ace
--- /dev/null
+++ b/pdal/Writer.hpp
@@ -0,0 +1,95 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/Options.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/Stage.hpp>
+
+namespace pdal
+{
+
+class Writer;
+class UserCallback;
+
+/**
+  A Writer is a terminal stage for a PDAL pipeline.  It usually writes output
+  to a file, but this isn't a requirement.  The class provides support for
+  some operations common for producing point output.
+*/
+class PDAL_DLL Writer : public Stage
+{
+    friend class WriterWrapper;
+    friend class DbWriter;
+    friend class FlexWriter;
+
+public:
+    /**
+      Construct a writer.
+    */
+    Writer()
+        {}
+
+protected:
+    /**
+      Locate template placeholder ('#') and validate filename with respect
+      to placeholder.
+    */
+    std::string::size_type handleFilenameTemplate(const std::string& filename);
+
+private:
+    virtual PointViewSet run(PointViewPtr view)
+    {
+        PointViewSet viewSet;
+        write(view);
+        viewSet.insert(view);
+        return viewSet;
+    }
+    virtual void writerInitialize(PointTableRef table)
+    {}
+
+    /**
+      Write the point in a PointView.  This is a simplification of the
+      \ref run() interface for convenience.  Impelment in subclass if desired.
+    */
+    virtual void write(const PointViewPtr /*view*/)
+        { std::cerr << "Can't write with stage = " << getName() << "!\n"; }
+
+    Writer& operator=(const Writer&); // not implemented
+    Writer(const Writer&); // not implemented
+};
+
+} // namespace pdal
+
diff --git a/pdal/XMLSchema.cpp b/pdal/XMLSchema.cpp
new file mode 100644
index 0000000..fd0c274
--- /dev/null
+++ b/pdal/XMLSchema.cpp
@@ -0,0 +1,648 @@
+/******************************************************************************
+* Copyright (c) 2011, Howard Butler, hobu.inc at gmail.com *
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/XMLSchema.hpp>
+#include <pdal/PipelineWriter.hpp>
+#include <pdal/PDALUtils.hpp>
+
+#include <sstream>
+#include <iostream>
+#include <iostream>
+#include <list>
+#include <cstdlib>
+#include <map>
+#include <algorithm>
+#include <functional>
+
+#include <string.h>
+#include <stdlib.h>
+
+namespace
+{
+
+/**
+void print_element_names(xmlNode * a_node)
+{
+
+    xmlNode *cur_node = NULL;
+
+    for (cur_node = a_node; cur_node; cur_node = cur_node->next)
+    {
+        if (cur_node->type == XML_ELEMENT_NODE)
+        {
+            printf("node type: Element, name: %s\n", cur_node->name);
+        }
+        print_element_names(cur_node->children);
+    }
+}
+**/
+
+} // anonymous namespace
+
+namespace pdal
+{
+
+void OCISchemaStructuredErrorHandler
+(void * /*userData*/, xmlErrorPtr error)
+{
+    std::ostringstream oss;
+
+    oss << "XML error: '" << error->message <<"' ";
+
+    if (error->str1)
+        oss << " extra info1: '" << error->str1 << "' ";
+    if (error->str2)
+        oss << " extra info2: '" << error->str2 << "' ";
+    if (error->str3)
+        oss << " extra info3: '" << error->str3 << "' ";
+    oss << "on line " << error->line;
+
+    if (error->ctxt)
+    {
+        xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) error->ctxt;
+        xmlParserInputPtr input = ctxt->input;
+
+        xmlParserPrintFileContext(input);
+    }
+    std::cerr << oss.str() << std::endl;
+}
+
+void OCISchemaParserStructuredErrorHandler
+(void * /*userData*/, xmlErrorPtr error)
+{
+    std::cerr << "Schema parsing error: '" << error->message << "' " <<
+        "on line " << error->line << std::endl;
+}
+
+void OCISchemaValidationStructuredErrorHandler
+(void * /*userData*/, xmlErrorPtr error)
+{
+    std::cerr << "Schema validation error: '" << error->message << "' " <<
+        "on line " << error->line << std::endl;
+}
+
+void OCISchemaValidityError
+(void * /*ctx*/, const char* message, ...)
+{
+    const int ERROR_MESSAGE_SIZE = 256;
+    char error[ERROR_MESSAGE_SIZE];
+    va_list arg_ptr;
+
+    va_start(arg_ptr, message);
+    vsnprintf(error, ERROR_MESSAGE_SIZE, message, arg_ptr);
+    va_end(arg_ptr);
+
+    std::cerr << "Schema validity error: '" << error << "' " << std::endl;
+}
+
+void OCISchemaValidityDebug
+(void * /*ctx*/, const char* message, ...)
+{
+    const int ERROR_MESSAGE_SIZE = 256;
+    char error[ERROR_MESSAGE_SIZE];
+    va_list arg_ptr;
+
+    va_start(arg_ptr, message);
+    vsnprintf(error, ERROR_MESSAGE_SIZE, message, arg_ptr);
+    va_end(arg_ptr);
+
+    std::cout << "Schema validity debug: '" << error << "' " << "\n";
+}
+
+
+void OCISchemaGenericErrorHandler
+(void * /*ctx*/, const char* message, ...)
+{
+    const int ERROR_MESSAGE_SIZE = 256;
+    char error[ERROR_MESSAGE_SIZE];
+    va_list arg_ptr;
+
+    va_start(arg_ptr, message);
+    vsnprintf(error, ERROR_MESSAGE_SIZE, message, arg_ptr);
+    va_end(arg_ptr);
+
+    std::ostringstream oss;
+
+    std::cerr << "Generic error: '" << error << "'" << std::endl;
+}
+
+XMLSchema::XMLSchema(std::string xml, std::string xsd,
+    Orientation orientation) : m_orientation(orientation)
+{
+    xmlDocPtr doc = init(xml, xsd);
+    if (doc)
+    {
+        load(doc);
+        xmlFreeDoc(doc);
+    }
+}
+
+
+XMLSchema::XMLSchema(const XMLDimList& dims, MetadataNode m,
+    Orientation orientation) : m_orientation(orientation), m_dims(dims),
+    m_metadata(m)
+{}
+
+
+XMLSchema::XMLSchema(const PointLayoutPtr& layout, MetadataNode m,
+    Orientation orientation) : m_orientation(orientation), m_metadata(m)
+{
+    DimTypeList dimTypes = layout->dimTypes();
+    for (DimType& d : dimTypes)
+        m_dims.push_back(XMLDim(d, layout->dimName(d.m_id)));
+}
+
+
+std::string XMLSchema::xml() const
+{
+    xmlBuffer *b = xmlBufferCreate();
+    xmlTextWriterPtr w = xmlNewTextWriterMemory(b, 0);
+
+    xmlTextWriterSetIndent(w, 1);
+    xmlTextWriterStartDocument(w, NULL, "utf-8", NULL);
+    xmlTextWriterStartElementNS(w, (const xmlChar*)"pc",
+        (const xmlChar*)"PointCloudSchema", NULL);
+    xmlTextWriterWriteAttributeNS(w, (const xmlChar*) "xmlns",
+        (const xmlChar*)"pc", NULL,
+        (const xmlChar*)"http://pointcloud.org/schemas/PC/");
+    xmlTextWriterWriteAttributeNS(w, (const xmlChar*)"xmlns",
+        (const xmlChar*)"xsi", NULL,
+        (const xmlChar*)"http://www.w3.org/2001/XMLSchema-instance");
+
+    writeXml(w);
+
+    xmlTextWriterEndElement(w);
+    xmlTextWriterEndDocument(w);
+
+    std::string output((const char *)b->content, b->use);
+    xmlFreeTextWriter(w);
+    xmlBufferFree(b);
+
+    return output;
+}
+
+
+DimTypeList XMLSchema::dimTypes() const
+{
+    DimTypeList dimTypes;
+
+    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
+        dimTypes.push_back(di->m_dimType);
+    return dimTypes;
+}
+
+
+xmlDocPtr XMLSchema::init(const std::string& xml, const std::string& xsd)
+{
+    xmlParserOption parserOption(XML_PARSE_NONET);
+
+    LIBXML_TEST_VERSION
+
+    xmlSetGenericErrorFunc(m_global_context,
+        (xmlGenericErrorFunc)&OCISchemaGenericErrorHandler);
+    xmlSetStructuredErrorFunc(m_global_context,
+        (xmlStructuredErrorFunc)&OCISchemaStructuredErrorHandler);
+
+    xmlDocPtr doc = xmlReadMemory(xml.c_str(), xml.size(), NULL, NULL,
+        parserOption);
+
+    if (xsd.size() && !validate(doc, xsd))
+    {
+        xmlFreeDoc(doc);
+        doc = NULL;
+        std::cerr << "Document did not validate against schema." << std::endl;
+    }
+    return doc;
+}
+
+
+bool XMLSchema::validate(xmlDocPtr doc, const std::string& xsd)
+{
+    xmlParserOption parserOption(XML_PARSE_NONET);
+
+    xmlDocPtr schemaDoc = xmlReadMemory(xsd.c_str(), xsd.size(),
+        NULL, NULL, parserOption);
+    xmlSchemaParserCtxtPtr parserCtxt = xmlSchemaNewDocParserCtxt(schemaDoc);
+    xmlSchemaSetParserStructuredErrors(parserCtxt,
+        &OCISchemaParserStructuredErrorHandler, m_global_context);
+    xmlSchemaPtr schema = xmlSchemaParse(parserCtxt);
+    xmlSchemaValidCtxtPtr validCtxt = xmlSchemaNewValidCtxt(schema);
+    xmlSchemaSetValidErrors(validCtxt, &OCISchemaValidityError,
+        &OCISchemaValidityDebug, m_global_context);
+    bool valid = (xmlSchemaValidateDoc(validCtxt, doc) == 0);
+
+    xmlFreeDoc(schemaDoc);
+    xmlSchemaFreeParserCtxt(parserCtxt);
+    xmlSchemaFree(schema);
+    xmlSchemaFreeValidCtxt(validCtxt);
+
+    return valid;
+}
+
+
+std::string XMLSchema::remapOldNames(const std::string& input)
+{
+    if (Utils::iequals(input, "Unnamed field 512") ||
+            Utils::iequals(input, "Chipper Point ID"))
+        return std::string("Chipper:PointID");
+
+    if (Utils::iequals(input, "Unnamed field 513") ||
+            Utils::iequals(input, "Chipper Block ID"))
+        return std::string("Chipper:BlockID");
+
+    return input;
+}
+
+
+bool XMLSchema::loadMetadata(xmlNode *startNode, MetadataNode& input)
+{
+//     Expect metadata in the following form
+//     We are going to skip the root element because we are
+//     expecting to be given one with our input
+//     <pc:metadata>
+//         <Metadata name="root" type="">
+//             <Metadata name="compression" type="string">lazperf</Metadata>
+//             <Metadata name="version" type="string">1.0</Metadata>
+//         </Metadata>
+//     </pc:metadata>
+
+    xmlNode *node = startNode;
+    for (node = startNode; node; node = node->next)
+    {
+        if (node->type != XML_ELEMENT_NODE)
+            continue;
+        if (std::string((const char*)node->name) == "Metadata")
+        {
+            const char *fieldname =
+                (const char*)xmlGetProp(node, (const xmlChar*)"name");
+            const char *etype =
+                (const char*)xmlGetProp(node, (const xmlChar*)"type");
+            const char *description =
+                (const char*)xmlGetProp(node, (const xmlChar*) "description");
+            const char *text = (const char*)xmlNodeGetContent(node);
+
+            if (!Utils::iequals(fieldname, "root"))
+            {
+                if (!fieldname)
+                {
+                    std::cerr << "Unable to read metadata for node '" <<
+                        (const char*)node->name << "' no \"name\" was given";
+                    return false;
+                }
+                input.add(fieldname, text ? text : "",
+                    description ? description : "");
+            }
+        }
+        loadMetadata(node->children, input);
+    }
+    return true;
+}
+
+
+bool XMLSchema::load(xmlDocPtr doc)
+{
+    xmlNode* root = xmlDocGetRootElement(doc);
+    // print_element_names(root);
+
+    if (!Utils::iequals((const char*)root->name, "PointCloudSchema"))
+    {
+        std::cerr << "First node of document was not named 'PointCloudSchema'";
+        return false;
+    }
+
+    const unsigned SENTINEL_POS = 100000;
+    unsigned missingPos = SENTINEL_POS + 1;
+
+    xmlNode* dimension = root->children;
+    pdal::Metadata metadata;
+    for (xmlNode *dimension = root->children; dimension;
+        dimension = dimension->next)
+    {
+        // Read off orientation setting
+        if (std::string((const char*)dimension->name) == "orientation")
+        {
+            xmlChar* n = xmlNodeListGetString(doc, dimension->children, 1);
+            if (!n)
+            {
+                std::cerr << "Unable to fetch orientation.\n";
+                return false;
+            }
+            std::string orientation = std::string((const char*)n);
+            xmlFree(n);
+
+            if (Utils::iequals(orientation, "dimension"))
+                m_orientation = Orientation::DimensionMajor;
+            else
+                m_orientation = Orientation::PointMajor;
+            continue;
+        }
+
+        if (std::string((const char*)dimension->name) == "metadata")
+        {
+            m_metadata = MetadataNode("root");
+            if (!loadMetadata(dimension, m_metadata))
+                return false;
+            continue;
+        }
+
+        if (dimension->type != XML_ELEMENT_NODE ||
+            !Utils::iequals((const char*)dimension->name, "dimension"))
+            continue;
+
+        XMLDim dim;
+        dim.m_position = SENTINEL_POS;
+        for (xmlNode *properties = dimension->children; properties;
+            properties = properties->next)
+        {
+            if (properties->type != XML_ELEMENT_NODE)
+                continue;
+
+            std::string propName = (const char *)properties->name;
+            propName = Utils::tolower(propName);
+            if (propName == "name")
+            {
+                xmlChar *n = xmlNodeListGetString(doc, properties->children, 1);
+                if (!n)
+                {
+                    std::cerr << "Unable to fetch name from XML node.";
+                    return false;
+                }
+                dim.m_name = remapOldNames(std::string((const char*)n));
+                xmlFree(n);
+            }
+            if (propName == "description")
+            {
+                xmlChar* n = xmlNodeListGetString(doc, properties->children, 1);
+                if (!n)
+                {
+                    std::cerr << "Unable to fetch description.\n";
+                    return false;
+                }
+                dim.m_description = std::string((const char*)n);
+                xmlFree(n);
+            }
+            if (propName == "interpretation")
+            {
+                xmlChar* n = xmlNodeListGetString(doc, properties->children, 1);
+                if (!n)
+                {
+                    std::cerr << "Unable to fetch interpretation.\n";
+                    return false;
+                }
+                dim.m_dimType.m_type = Dimension::type((const char*)n);
+                xmlFree(n);
+            }
+            if (propName == "minimum")
+            {
+                xmlChar* n = xmlGetProp(properties, (const xmlChar*) "value");
+                if (!n)
+                {
+                    return false;
+                    std::cerr << "Unable to fetch minimum value.\n";
+                }
+                dim.m_min = std::atof((const char*)n);
+                xmlFree(n);
+            }
+            if (propName == "maximum")
+            {
+                xmlChar* n = xmlGetProp(properties, (const xmlChar*) "value");
+                if (!n)
+                {
+                    std::cerr << "Unable to fetch maximum value.\n";
+                    return false;
+                }
+                dim.m_max = std::atof((const char*)n);
+                xmlFree(n);
+            }
+            if (propName == "position")
+            {
+                xmlChar* n = xmlNodeListGetString(doc, properties->children, 1);
+                if (!n)
+                {
+                    std::cerr << "Unable to fetch position value.\n";
+                    return false;
+                }
+                dim.m_position = std::atoi((const char*)n);
+                xmlFree(n);
+            }
+            if (propName == "offset")
+            {
+                xmlChar* n = xmlNodeListGetString(doc, properties->children, 1);
+                if (!n)
+                {
+                    std::cerr << "Unable to fetch offset value!";
+                    return false;
+                }
+                dim.m_dimType.m_xform.m_offset.set((const char*)n);
+                xmlFree(n);
+            }
+            if (propName == "scale")
+            {
+                xmlChar* n = xmlNodeListGetString(doc, properties->children, 1);
+                if (!n)
+                {
+                    std::cerr << "Unable to fetch scale value!";
+                    return false;
+                }
+                dim.m_dimType.m_xform.m_scale.set((const char*)n);
+                xmlFree(n);
+            }
+        }
+        // If we don't have a position, set it to some value larger than all
+        // previous values.
+        if (dim.m_position == SENTINEL_POS)
+            dim.m_position = missingPos++;
+        m_dims.push_back(dim);
+    }
+    std::sort(m_dims.begin(), m_dims.end());
+
+    // Renumber dimension positions to be 1..N
+    for (unsigned pos = 0; pos < m_dims.size(); pos++)
+        m_dims[pos].m_position = pos + 1;
+
+    return true;
+}
+
+
+XMLDim& XMLSchema::xmlDim(Dimension::Id id)
+{
+    static XMLDim nullDim;
+
+    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
+        if (di->m_dimType.m_id == id)
+            return *di;
+    return nullDim;
+}
+
+
+const XMLDim& XMLSchema::xmlDim(Dimension::Id id) const
+{
+    static XMLDim nullDim;
+
+    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
+        if (di->m_dimType.m_id == id)
+            return *di;
+    return nullDim;
+}
+
+
+XMLDim& XMLSchema::xmlDim(const std::string& name)
+{
+    static XMLDim nullDim;
+
+    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
+        if (di->m_name == name)
+            return *di;
+    return nullDim;
+}
+
+
+namespace
+{
+
+void addMetadataEntry(xmlTextWriterPtr w, const MetadataNode& input)
+{
+
+    std::function<void (const MetadataNode&) >  collectMetadata = [&] (const MetadataNode& node)
+    {
+        xmlTextWriterStartElement(w, (const xmlChar*)"Metadata");
+        xmlTextWriterWriteAttribute(w, (const xmlChar*)"name",  (const xmlChar*)node.name().c_str());
+        xmlTextWriterWriteAttribute(w, (const xmlChar*)"type",  (const xmlChar*)node.type().c_str());
+        xmlTextWriterWriteString(w, (const xmlChar*) node.value().c_str());
+        for (auto& m : node.children())
+            if (!m.empty())
+                collectMetadata(m);
+
+        xmlTextWriterEndElement(w);
+    };
+
+    xmlTextWriterStartElementNS(w, (const xmlChar*)"pc",
+        (const xmlChar*)"metadata", NULL);
+
+    for (auto& m : input.children())
+        if (!m.empty())
+            collectMetadata(m);
+
+     xmlTextWriterEndElement(w);
+        xmlTextWriterFlush(w);
+}
+
+
+} // anonymous namespace
+
+
+void XMLSchema::writeXml(xmlTextWriterPtr w) const
+{
+    int pos = 0;
+    for (auto di = m_dims.begin(); di != m_dims.end(); ++di, ++pos)
+    {
+        xmlTextWriterStartElementNS(w, (const xmlChar*)"pc",
+            (const xmlChar*)"dimension", NULL);
+
+        std::ostringstream position;
+        position << (pos + 1);
+        xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
+            (const xmlChar*)"position", NULL,
+            (const xmlChar*)position.str().c_str());
+
+        std::ostringstream size;
+        size << Dimension::size(di->m_dimType.m_type);
+        xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
+            (const xmlChar*)"size", NULL, (const xmlChar*)size.str().c_str());
+
+        std::string description = Dimension::description(di->m_dimType.m_id);
+        if (description.size())
+            xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
+                (const xmlChar*)"description", NULL,
+                (const xmlChar*)description.c_str());
+
+        XForm xform = di->m_dimType.m_xform;
+        if (xform.nonstandard())
+        {
+            std::ostringstream out;
+            out.precision(15);
+
+            out << xform.m_scale.m_val;
+            std::string scale = out.str();
+
+            out.str(std::string());
+            out << xform.m_offset.m_val;
+            std::string offset = out.str();
+
+            out << xform.m_scale.m_val;
+            xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
+                (const xmlChar *)"scale", NULL,
+                (const xmlChar *)scale.data());
+            xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
+                (const xmlChar *)"offset", NULL,
+                (const xmlChar *)offset.data());
+        }
+
+        std::string name = di->m_name;
+        if (name.size())
+            xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
+                (const xmlChar*)"name", NULL, (const xmlChar*)name.c_str());
+
+        xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
+            (const xmlChar*)"interpretation", NULL,
+            (const xmlChar*)
+                Dimension::interpretationName(di->m_dimType.m_type).c_str());
+
+        xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
+            (const xmlChar*)"active", NULL, (const xmlChar*)"true");
+
+        xmlTextWriterEndElement(w);
+        xmlTextWriterFlush(w);
+    }
+    std::ostringstream orientation;
+    if (m_orientation == Orientation::PointMajor)
+        orientation << "point";
+    if (m_orientation == Orientation::DimensionMajor)
+        orientation << "dimension";
+    if (!m_metadata.empty())
+    {
+        addMetadataEntry(w, m_metadata);
+    }
+    xmlTextWriterWriteElementNS(w, (const xmlChar*) "pc",
+        (const xmlChar*)"orientation", NULL,
+        (const xmlChar*)orientation.str().c_str());
+
+    xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
+        (const xmlChar*)"version", NULL,
+        (const xmlChar*)PDAL_XML_SCHEMA_VERSION);
+
+    xmlTextWriterEndElement(w);
+    xmlTextWriterFlush(w);
+}
+
+} // namespace pdal
diff --git a/include/pdal/XMLSchema.hpp b/pdal/XMLSchema.hpp
similarity index 100%
rename from include/pdal/XMLSchema.hpp
rename to pdal/XMLSchema.hpp
diff --git a/pdal/gitsha.cpp b/pdal/gitsha.cpp
new file mode 100644
index 0000000..046be42
--- /dev/null
+++ b/pdal/gitsha.cpp
@@ -0,0 +1,3 @@
+#include <pdal/gitsha.h>
+#define GIT_SHA1 "3851ef74cbef164112b6a3709dc09d59c3b70a05"
+const char g_GIT_SHA1[] = GIT_SHA1;
diff --git a/include/pdal/gitsha.h b/pdal/gitsha.h
similarity index 100%
rename from include/pdal/gitsha.h
rename to pdal/gitsha.h
diff --git a/include/pdal/pdal.hpp b/pdal/pdal.hpp
similarity index 100%
rename from include/pdal/pdal.hpp
rename to pdal/pdal.hpp
diff --git a/pdal/pdal_config.cpp b/pdal/pdal_config.cpp
new file mode 100644
index 0000000..ca45025
--- /dev/null
+++ b/pdal/pdal_config.cpp
@@ -0,0 +1,186 @@
+/******************************************************************************
+ * $Id$
+ *
+ * Project:  libLAS - http://liblas.org - A BSD library for LAS format data.
+ * Purpose:  LAS version related functions.
+ * Author:   Mateusz Loskot, mateusz at loskot.net
+ *           Frank Warmerdam, warmerdam at pobox.com
+ *
+ ******************************************************************************
+ * Copyright (c) 2008, Mateusz Loskot
+ * Copyright (c) 2010, Frank Warmerdam
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of the Martin Isenburg or Iowa Department
+ *       of Natural Resources nor the names of its contributors may be
+ *       used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#include <pdal/pdal_config.hpp>
+
+#include <sstream>
+#include <iomanip>
+
+#include <pdal/pdal_defines.h>
+#include <pdal/gitsha.h>
+
+#include <geotiff.h>
+
+#ifdef PDAL_COMPILER_CLANG
+#  pragma clang diagnostic push
+#  pragma clang diagnostic ignored "-Wfloat-equal"
+#endif
+#include <gdal.h>
+#ifdef PDAL_COMPILER_CLANG
+#  pragma clang diagnostic pop
+#endif
+
+#ifdef PDAL_HAVE_LASZIP
+#include <laszip/laszip.hpp>
+#endif
+
+#include <geos_c.h>
+
+#ifdef PDAL_HAVE_LIBXML2
+#include <libxml/xmlversion.h>
+#endif
+
+#include <pdal/util/Utils.hpp>
+
+namespace pdal
+{
+
+/// Check if GeoTIFF support has been built in to PDAL
+bool IsLibGeoTIFFEnabled()
+{
+    return true;
+}
+
+/// Check if LasZip compression support has been built in to PDAL
+bool IsLasZipEnabled()
+{
+#ifdef PDAL_HAVE_LASZIP
+    return true;
+#else
+    return false;
+#endif
+}
+
+int GetVersionMajor()
+{
+    return PDAL_VERSION_MAJOR;
+}
+
+int GetVersionMinor()
+{
+    return PDAL_VERSION_MINOR;
+}
+
+int GetVersionPatch()
+{
+    return PDAL_VERSION_PATCH;
+}
+
+std::string GetVersionString()
+{
+    return std::string(PDAL_VERSION_STRING);
+}
+
+int GetVersionInteger()
+{
+    return PDAL_VERSION_INTEGER;
+}
+
+std::string GetSHA1()
+{
+	return g_GIT_SHA1;
+}
+
+
+/// Tell the user a bit about PDAL's compilation
+std::string GetFullVersionString()
+{
+    std::ostringstream os;
+
+    std::string sha = GetSHA1();
+    if (!Utils::iequals(sha, "Release"))
+        sha = sha.substr(0,6);
+
+    os << PDAL_VERSION_STRING << " (git-version: " << sha << ")";
+
+    return os.str();
+}
+
+
+std::string getPDALDebugInformation()
+{
+    Utils::screenWidth();
+    std::string headline(Utils::screenWidth(), '-');
+
+    std::ostringstream os;
+
+    os << headline << std::endl;
+    os << "PDAL debug information" << std::endl ;
+    os << headline << std::endl << std::endl;
+
+    os << "Version information" << std::endl;
+    os << headline << std::endl;
+    os << "(" << pdal::GetFullVersionString() << ")" << std::endl;
+    os << std::endl;
+
+    os << "Debug build status" << std::endl;
+    os << headline << std::endl;
+    os << PDAL_BUILD_TYPE << std::endl << std::endl;
+
+    os << "Enabled libraries" << std::endl;
+    os << headline << std::endl << std::endl;
+
+    os << "GEOS (" << GEOS_VERSION << ") - " <<
+        "http://trac.osgeo.org/geos" << std::endl;
+
+    os << "GDAL (" << GDALVersionInfo("RELEASE_NAME") << ") - " <<
+        "http://www.gdal.org" << std::endl;
+
+#ifdef PDAL_HAVE_LASZIP
+    os << "LASzip (" << LASZIP_VERSION_MAJOR << "." << LASZIP_VERSION_MINOR <<
+        "." << LASZIP_VERSION_REVISION << ") - " <<
+        "http://laszip.org" << std::endl;
+#endif
+
+#ifdef PDAL_HAVE_LIBXML2
+    os << "libxml (" << LIBXML_DOTTED_VERSION << ") - " <<
+              "http://www.xmlsoft.org/" << std::endl;
+#endif
+
+    os << "libgeotiff (" << LIBGEOTIFF_VERSION << ") - " <<
+        "http://trac.osgeo.org/geotiff" << std::endl;
+
+    return os.str();
+}
+
+} // namespace pdal
diff --git a/include/pdal/pdal_config.hpp b/pdal/pdal_config.hpp
similarity index 100%
rename from include/pdal/pdal_config.hpp
rename to pdal/pdal_config.hpp
diff --git a/include/pdal/pdal_export.hpp b/pdal/pdal_export.hpp
similarity index 100%
rename from include/pdal/pdal_export.hpp
rename to pdal/pdal_export.hpp
diff --git a/include/pdal/pdal_internal.hpp b/pdal/pdal_internal.hpp
similarity index 100%
rename from include/pdal/pdal_internal.hpp
rename to pdal/pdal_internal.hpp
diff --git a/pdal/pdal_macros.hpp b/pdal/pdal_macros.hpp
new file mode 100644
index 0000000..b9ef9a5
--- /dev/null
+++ b/pdal/pdal_macros.hpp
@@ -0,0 +1,109 @@
+/******************************************************************************
+* Copyright (c) 2012, Howard Butler (hobu.inc at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/pdal_export.hpp>
+#include <pdal/plugin.hpp>
+#include <pdal/PluginManager.hpp>
+
+namespace pdal
+{
+
+struct PluginInfo
+{
+    std::string name;
+    std::string description;
+    std::string link;
+    PluginInfo(const std::string& n, const std::string& d, const std::string& l)
+      : name(n), description(d), link(l)
+    {}
+};
+
+}
+
+#define CREATE_SHARED_PLUGIN(version_major, version_minor, T, type, info) \
+    extern "C" PDAL_DLL int32_t ExitFunc() \
+    { return 0; } \
+    extern "C" PDAL_DLL PF_ExitFunc PF_initPlugin() \
+    { \
+        int res = 0; \
+        PF_RegisterParams rp; \
+        rp.version.major = version_major; \
+        rp.version.minor = version_minor; \
+        rp.createFunc = pdal::T::create; \
+        rp.destroyFunc = pdal::T::destroy; \
+        rp.description = info.description; \
+        rp.link = info.link; \
+        rp.pluginType = PF_PluginType_ ## type; \
+        if (!pdal::PluginManager::registerObject(info.name, &rp)) \
+            return NULL; \
+        return ExitFunc; \
+    } \
+    void * pdal::T::create() { return new pdal::T(); } \
+    int32_t pdal::T::destroy(void *p) \
+    { \
+        if (!p) \
+            return -1; \
+        delete (pdal::T *)p; \
+        return 0; \
+    }
+
+#define CREATE_STATIC_PLUGIN(version_major, version_minor, T, type, info) \
+    extern "C" PDAL_DLL int32_t T ## _ExitFunc() \
+    { return 0; } \
+    extern "C" PDAL_DLL PF_ExitFunc T ## _InitPlugin() \
+    { \
+        int res = 0; \
+        PF_RegisterParams rp; \
+        rp.version.major = version_major; \
+        rp.version.minor = version_minor; \
+        rp.createFunc = pdal::T::create; \
+        rp.destroyFunc = pdal::T::destroy; \
+        rp.description = info.description; \
+        rp.link = info.link; \
+        rp.pluginType = PF_PluginType_ ## type; \
+        if (!pdal::PluginManager::registerObject(info.name, &rp)) \
+            return NULL; \
+        return T ## _ExitFunc; \
+    } \
+    void * pdal::T::create() { return new pdal::T(); } \
+    int32_t pdal::T::destroy(void *p) \
+    { \
+        if (!p) \
+            return -1; \
+        delete (pdal::T *)p; \
+        return 0; \
+    }
+
diff --git a/include/pdal/pdal_test_main.hpp b/pdal/pdal_test_main.hpp
similarity index 100%
rename from include/pdal/pdal_test_main.hpp
rename to pdal/pdal_test_main.hpp
diff --git a/pdal/pdal_types.hpp b/pdal/pdal_types.hpp
new file mode 100644
index 0000000..eb7d487
--- /dev/null
+++ b/pdal/pdal_types.hpp
@@ -0,0 +1,225 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <stdint.h>
+#include <cstdlib>
+#include <istream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <iostream>
+
+#include <pdal/util/Utils.hpp>
+
+namespace pdal
+{
+
+typedef uint64_t PointId;
+typedef uint64_t point_count_t;
+typedef std::vector<std::string> StringList;
+
+typedef union
+{
+    float f;
+    double d;
+    int8_t s8;
+    int16_t s16;
+    int32_t s32;
+    int64_t s64;
+    uint8_t u8;
+    uint16_t u16;
+    uint32_t u32;
+    uint64_t u64;
+} Everything;
+
+struct XForm
+{
+    struct XFormComponent
+    {
+        XFormComponent() : m_val(0.0), m_auto(false)
+        {}
+
+        XFormComponent(double val) : m_val(val), m_auto(false)
+        {}
+
+        double m_val;
+        bool m_auto;
+
+        bool set(const std::string& sval)
+        {
+            if (sval == "auto")
+                m_auto = true;
+            else
+            {
+                size_t pos;
+                try
+                {
+                    m_val = std::stod(sval, &pos);
+                }
+                catch (...)
+                {
+                    // Set error condition.
+                    pos = sval.size() + 1;
+                }
+                if (pos != sval.size())
+                {
+                    m_val = 0;
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        friend std::istream& operator>>(std::istream& in, XFormComponent& xfc);
+        friend std::ostream& operator<<(std::ostream& in,
+            const XFormComponent& xfc);
+    };
+
+    XForm() : m_scale(1.0), m_offset(0.0)
+    {}
+
+    XForm(double scale, double offset) : m_scale(scale), m_offset(offset)
+    {}
+
+    // Scale component of the transform.
+    XFormComponent m_scale;
+    // Offset component of the transform.
+    XFormComponent m_offset;
+
+    double toScaled(double val) const
+        { return (val - m_offset.m_val) / m_scale.m_val; }
+
+    bool nonstandard() const
+    {
+        return m_scale.m_auto || m_offset.m_auto ||
+            m_scale.m_val != 1.0 || m_offset.m_val != 0.0;
+    }
+};
+
+inline std::istream& operator>>(std::istream& in, XForm::XFormComponent& xfc)
+{
+    std::string sval;
+
+    in >> sval;
+    if (!xfc.set(sval))
+        in.setstate(std::ios_base::failbit);
+    return in;
+}
+
+inline std::ostream& operator<<(std::ostream& out,
+    const XForm::XFormComponent& xfc)
+{
+    if (xfc.m_auto)
+        out << "auto";
+    else
+        out << xfc.m_val;
+    return out;
+}
+
+enum class LogLevel
+{
+    Error = 0,
+    Warning,
+    Info,
+    Debug,
+    Debug1,
+    Debug2,
+    Debug3,
+    Debug4,
+    Debug5,
+    None
+};
+
+namespace
+{
+    const StringList logNames { "error", "warning", "info", "debug", "debug1",
+        "debug2", "debug3", "debug4", "debug5" };
+}
+
+inline std::istream& operator>>(std::istream& in, LogLevel& level)
+{
+    std::string sval;
+    level = LogLevel::None;
+
+    in >> sval;
+    try
+    {
+        int val = std::stoi(sval);
+        if (val >= 0 && val < (int)logNames.size())
+            level = (LogLevel)val;
+    }
+    catch (std::exception)
+    {
+        sval = Utils::tolower(sval);
+        for (size_t i = 0; i < logNames.size(); ++i)
+            if (logNames[i] == sval)
+                level = (LogLevel)i;
+    }
+    if (level == LogLevel::None)
+        in.setstate(std::ios_base::failbit);
+    return in;
+}
+
+inline std::ostream& operator<<(std::ostream& out, const LogLevel& level)
+{
+    std::string sval("None");
+
+    if ((size_t)level < logNames.size())
+    {
+        sval = logNames[(size_t)level];
+        sval[0] = toupper(sval[0]);   // Make "Debug", "Error", etc.
+    }
+    out << sval;
+    return out;
+}
+
+
+enum class Orientation
+{
+    PointMajor,
+    DimensionMajor
+};
+
+class pdal_error : public std::runtime_error
+{
+public:
+    inline pdal_error(std::string const& msg) : std::runtime_error(msg)
+        {}
+};
+
+} // namespace pdal
+
diff --git a/src/plang/Array.cpp b/pdal/plang/Array.cpp
similarity index 100%
rename from src/plang/Array.cpp
rename to pdal/plang/Array.cpp
diff --git a/include/pdal/plang/Array.hpp b/pdal/plang/Array.hpp
similarity index 100%
rename from include/pdal/plang/Array.hpp
rename to pdal/plang/Array.hpp
diff --git a/src/plang/BufferedInvocation.cpp b/pdal/plang/BufferedInvocation.cpp
similarity index 100%
rename from src/plang/BufferedInvocation.cpp
rename to pdal/plang/BufferedInvocation.cpp
diff --git a/include/pdal/plang/BufferedInvocation.hpp b/pdal/plang/BufferedInvocation.hpp
similarity index 100%
rename from include/pdal/plang/BufferedInvocation.hpp
rename to pdal/plang/BufferedInvocation.hpp
diff --git a/pdal/plang/CMakeLists.txt b/pdal/plang/CMakeLists.txt
new file mode 100644
index 0000000..f708f37
--- /dev/null
+++ b/pdal/plang/CMakeLists.txt
@@ -0,0 +1,30 @@
+
+set(plang_srcs
+    Array.cpp
+    BufferedInvocation.cpp
+    Invocation.cpp
+    Environment.cpp
+    Redirector.cpp
+    Script.cpp
+)
+
+include(${PDAL_CMAKE_DIR}/python.cmake)
+
+PDAL_ADD_LIBRARY(${PDAL_PLANG_LIB_NAME} ${plang_srcs} )
+set_target_properties(${PDAL_PLANG_LIB_NAME} PROPERTIES
+    VERSION "${PDAL_BUILD_VERSION}"
+    SOVERSION "${PDAL_API_VERSION}"
+    CLEAN_DIRECT_OUTPUT 1)
+target_link_libraries(${PDAL_PLANG_LIB_NAME} PUBLIC
+    ${PDAL_BASE_LIB_NAME}
+    ${PDAL_UTIL_LIB_NAME}
+    ${PYTHON_LIBRARY})
+target_include_directories(${PDAL_PLANG_LIB_NAME} PRIVATE
+    ${PYTHON_INCLUDE_DIR}
+    ${PROJECT_BINARY_DIR}/include)
+install(TARGETS ${PLANG_LIB_NAME}
+    RUNTIME DESTINATION ${PDAL_BIN_INSTALL_DIR}
+    LIBRARY DESTINATION ${PDAL_LIB_INSTALL_DIR}
+    ARCHIVE DESTINATION ${PDAL_LIB_INSTALL_DIR})
+
+
diff --git a/pdal/plang/Environment.cpp b/pdal/plang/Environment.cpp
new file mode 100644
index 0000000..a4ccb0d
--- /dev/null
+++ b/pdal/plang/Environment.cpp
@@ -0,0 +1,348 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#ifndef _WIN32
+#include <dlfcn.h>
+#endif
+
+#include <pdal/plang/Environment.hpp>
+#include <pdal/plang/Redirector.hpp>
+#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
+#define PY_ARRAY_UNIQUE_SYMBOL PDAL_ARRAY_API
+#include <numpy/arrayobject.h>
+
+#include <sstream>
+#include <mutex>
+
+#ifdef PDAL_COMPILER_MSVC
+#  pragma warning(disable: 4127)  // conditional expression is constant
+#endif
+
+#include <Python.h>
+#include <pystate.h>
+#undef toupper
+#undef tolower
+#undef isspace
+
+// See ticket #1010.  This function runs when libplang is loaded.  It makes
+// sure python symbols can be found by extention module .so's since on some
+// platforms (notably Ubuntu), they aren't linked with libpython even though
+// they depend on it.  If a platform doesn't support
+// __attribute__ ((constructor)) this does nothing.  We'll have to deal with
+// those as they come up.
+#ifndef _WIN32
+__attribute__ ((constructor))
+static void loadPython()
+{
+    ::dlopen(PDAL_PYTHON_LIBRARY, RTLD_LAZY | RTLD_GLOBAL);
+}
+#endif
+
+// http://www.linuxjournal.com/article/3641
+// http://www.codeproject.com/Articles/11805/Embedding-Python-in-C-C-Part-I
+// http://stackoverflow.com/questions/6596016/python-threads-in-c
+
+namespace pdal
+{
+namespace plang
+{
+
+static Environment* g_environment=0;
+
+EnvironmentPtr Environment::get()
+{
+    static std::once_flag flag;
+
+    auto init = []()
+    {
+        g_environment = new Environment();
+    };
+
+    std::call_once(flag, init);
+
+    return g_environment;
+}
+
+
+Environment::Environment()
+{
+    // This awfulness is due to the import_array MACRO that returns a value
+    // in some cases and returns nothing at other times.  Defining
+    // NUMPY_IMPORT_ARRAY_RETVAL to nothing makes it so we don't ever return
+    // a value.  The function needs to be stuck in a function to deal with
+    // the return.
+    auto initNumpy = []()
+    {
+#undef NUMPY_IMPORT_ARRAY_RETVAL
+#define NUMPY_IMPORT_ARRAY_RETVAL
+        import_array();
+    };
+
+    if (!Py_IsInitialized())
+    {
+        PyImport_AppendInittab(const_cast<char*>("redirector"), redirector_init);
+        Py_Initialize();
+    } else
+    {
+        m_redirector.init();
+        PyObject* added  = PyImport_AddModule("redirector");
+        if (!added)
+            throw pdal_error("unable to add redirector module!");
+    }
+
+    initNumpy();
+    PyImport_ImportModule("redirector");
+}
+
+
+Environment::~Environment()
+{
+    Py_Finalize();
+}
+
+
+void Environment::set_stdout(std::ostream* ostr)
+{
+    m_redirector.set_stdout(ostr);
+}
+
+
+void Environment::reset_stdout()
+{
+    m_redirector.reset_stdout();
+}
+
+
+std::string getTraceback()
+{
+    // get exception info
+    PyObject *type, *value, *traceback;
+    PyErr_Fetch(&type, &value, &traceback);
+    PyErr_NormalizeException(&type, &value, &traceback);
+
+    std::ostringstream mssg;
+    if (traceback)
+    {
+        PyObject* tracebackModule;
+        PyObject* tracebackDictionary;
+        PyObject* tracebackFunction;
+
+        tracebackModule = PyImport_ImportModule("traceback");
+        if (!tracebackModule)
+            throw pdal::pdal_error("Unable to load traceback module.");
+
+        tracebackDictionary = PyModule_GetDict(tracebackModule);
+        if (!tracebackDictionary)
+            throw pdal::pdal_error("Unable to load traceback dictionary.");
+        tracebackFunction =
+            PyDict_GetItemString(tracebackDictionary, "format_exception");
+        if (!tracebackFunction)
+            throw pdal::pdal_error("Unable to find traceback function.");
+
+        if (!PyCallable_Check(tracebackFunction))
+            throw pdal::pdal_error("Invalid traceback function.");
+
+        // create an argument for "format exception"
+        PyObject* args = PyTuple_New(3);
+        PyTuple_SetItem(args, 0, type);
+        PyTuple_SetItem(args, 1, value);
+        PyTuple_SetItem(args, 2, traceback);
+
+        // get a list of string describing what went wrong
+        PyObject* output = PyObject_CallObject(tracebackFunction, args);
+
+        // print error message
+        Py_ssize_t n = PyList_Size(output);
+
+        for (Py_ssize_t i = 0; i < n; i++)
+        {
+            PyObject* l = PyList_GetItem(output, i);
+            if (!l)
+                throw pdal::pdal_error("unable to get list item in getTraceback");
+            PyObject* r = PyObject_Repr(l);
+            if (!r)
+                throw pdal::pdal_error("unable to get repr in getTraceback");
+#if PY_MAJOR_VERSION >= 3
+            Py_ssize_t size;
+            char *d = PyUnicode_AsUTF8AndSize(r, &size);
+#else
+            char *d = PyString_AsString(r);
+#endif
+            mssg << d;
+        }
+
+        // clean up
+        Py_XDECREF(args);
+        Py_XDECREF(output);
+    }
+    else if (value != NULL)
+    {
+        PyObject* r = PyObject_Repr(value);
+        if (!r)
+            throw pdal::pdal_error("couldn't make string representation of traceback value");
+#if PY_MAJOR_VERSION >= 3
+        Py_ssize_t size;
+        char *d = PyUnicode_AsUTF8AndSize(r, &size);
+#else
+        char *d = PyString_AsString(r);
+#endif
+        mssg << d;
+    }
+    else
+        mssg << "unknown error that we are unable to get a traceback for."
+            "Was it already printed/taken?";
+
+    Py_XDECREF(value);
+    Py_XDECREF(type);
+    Py_XDECREF(traceback);
+
+    return mssg.str();
+}
+
+PyObject *fromMetadata(MetadataNode m)
+{
+    std::string name = m.name();
+    std::string value = m.value();
+    std::string type = m.type();
+    std::string description = m.description();
+
+    MetadataNodeList children = m.children();
+    PyObject *submeta = NULL;
+    if (children.size())
+    {
+        submeta = PyList_New(0);
+        for (MetadataNode& child : children)
+            PyList_Append(submeta, fromMetadata(child));
+    }
+    PyObject *data = PyTuple_New(5);
+    PyTuple_SetItem(data, 0, PyUnicode_FromString(name.data()));
+    PyTuple_SetItem(data, 1, PyUnicode_FromString(value.data()));
+    PyTuple_SetItem(data, 2, PyUnicode_FromString(type.data()));
+    PyTuple_SetItem(data, 3, PyUnicode_FromString(description.data()));
+    PyTuple_SetItem(data, 4, submeta);
+
+    return data;
+}
+
+std::string readPythonString(PyObject* list, Py_ssize_t index)
+{
+    std::stringstream ss;
+
+    PyObject* o = PyTuple_GetItem(list, index);
+    if (!o)
+    {
+        std::stringstream oss;
+        oss << "Unable to get list item number " << index << " for list of length " << PyTuple_Size(list);
+        throw pdal_error(oss.str());
+    }
+    PyObject* r = PyObject_Repr(o);
+    if (!r)
+        throw pdal::pdal_error("unable to get repr in readPythonString");
+#if PY_MAJOR_VERSION >= 3
+    Py_ssize_t size;
+    char *d = PyUnicode_AsUTF8AndSize(r, &size);
+#else
+    char *d = PyString_AsString(r);
+#endif
+    ss << d;
+
+    return ss.str();
+}
+void addMetadata(PyObject *list, MetadataNode m)
+{
+
+    if (!PyList_Check(list))
+        return;
+
+    for (Py_ssize_t i = 0; i < PyList_Size(list); ++i)
+    {
+        PyObject *tuple = PyList_GetItem(list, i);
+        if (!PyTuple_Check(tuple) || PyTuple_Size(tuple) != 5)
+            continue;
+
+        std::string name = readPythonString(tuple, 0);
+        std::string value = readPythonString(tuple, 1);
+
+        std::string type = readPythonString(tuple, 2);
+        if (type.empty())
+            type = Metadata::inferType(value);
+
+        std::string description = readPythonString(tuple, 3);
+
+        PyObject *submeta = PyTuple_GetItem(tuple, 4);
+        MetadataNode child =  m.addWithType(name, value, type, description);
+        if (submeta)
+            addMetadata(submeta, child);
+    }
+}
+
+int Environment::getPythonDataType(Dimension::Type t)
+{
+    using namespace Dimension;
+
+    switch (t)
+    {
+    case Type::Float:
+        return NPY_FLOAT;
+    case Type::Double:
+        return NPY_DOUBLE;
+    case Type::Signed8:
+        return NPY_BYTE;
+    case Type::Signed16:
+        return NPY_SHORT;
+    case Type::Signed32:
+        return NPY_INT;
+    case Type::Signed64:
+        return NPY_LONGLONG;
+    case Type::Unsigned8:
+        return NPY_UBYTE;
+    case Type::Unsigned16:
+        return NPY_USHORT;
+    case Type::Unsigned32:
+        return NPY_UINT;
+    case Type::Unsigned64:
+        return NPY_ULONGLONG;
+    default:
+        return -1;
+    }
+    assert(0);
+
+    return -1;
+}
+
+
+
+} // namespace plang
+} // namespace pdal
+
diff --git a/include/pdal/plang/Environment.hpp b/pdal/plang/Environment.hpp
similarity index 100%
rename from include/pdal/plang/Environment.hpp
rename to pdal/plang/Environment.hpp
diff --git a/pdal/plang/Invocation.cpp b/pdal/plang/Invocation.cpp
new file mode 100644
index 0000000..8eed314
--- /dev/null
+++ b/pdal/plang/Invocation.cpp
@@ -0,0 +1,291 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/plang/Invocation.hpp>
+#include <pdal/plang/Environment.hpp>
+
+#ifdef PDAL_COMPILER_MSVC
+#  pragma warning(disable: 4127) // conditional expression is constant
+#define HAVE_ROUND // inconsistent dll linkage otherwise
+#endif
+
+#include <Python.h>
+#undef toupper
+#undef tolower
+#undef isspace
+
+#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
+
+#define NO_IMPORT_ARRAY
+#define PY_ARRAY_UNIQUE_SYMBOL PDAL_ARRAY_API
+#include <numpy/arrayobject.h>
+
+namespace
+{
+
+int argCount(PyObject *function)
+{
+    PyObject *module = PyImport_ImportModule("inspect");
+    if (!module)
+        return false;
+    PyObject *dictionary = PyModule_GetDict(module);
+    PyObject *getargFunc = PyDict_GetItemString(dictionary, "getargspec");
+    PyObject *inArgs = PyTuple_New(1);
+    PyTuple_SetItem(inArgs, 0, function);
+    PyObject *outArgs = PyObject_CallObject(getargFunc, inArgs);
+    PyObject *arglist = PyTuple_GetItem(outArgs, (Py_ssize_t)0);
+    return (int) PyList_Size(arglist);
+}
+
+}
+
+namespace pdal
+{
+namespace plang
+{
+
+Invocation::Invocation(const Script& script) :
+    m_metaIn(NULL)
+    , m_metaOut(NULL)
+    , m_script(script)
+    , m_bytecode(NULL)
+    , m_module(NULL)
+    , m_dictionary(NULL)
+    , m_function(NULL)
+    , m_varsIn(NULL)
+    , m_varsOut(NULL)
+    , m_scriptArgs(NULL)
+    , m_scriptResult(NULL)
+{
+    plang::Environment::get();
+    resetArguments();
+}
+
+
+Invocation::~Invocation()
+{
+    cleanup();
+}
+
+
+void Invocation::compile()
+{
+    m_bytecode = Py_CompileString(m_script.source(), m_script.module(),
+        Py_file_input);
+    if (!m_bytecode)
+        throw pdal::pdal_error(getTraceback());
+
+    Py_INCREF(m_bytecode);
+
+    m_module = PyImport_ExecCodeModule(const_cast<char*>(m_script.module()),
+        m_bytecode);
+    if (!m_module)
+        throw pdal::pdal_error(getTraceback());
+
+    m_dictionary = PyModule_GetDict(m_module);
+    m_function = PyDict_GetItemString(m_dictionary, m_script.function());
+    if (!m_function)
+    {
+        std::ostringstream oss;
+        oss << "unable to find target function '" << m_script.function() <<
+            "' in module.";
+        throw pdal::pdal_error(oss.str());
+    }
+    if (!PyCallable_Check(m_function))
+        throw pdal::pdal_error(getTraceback());
+}
+
+
+void Invocation::cleanup()
+{
+    Py_XDECREF(m_varsIn);
+    Py_XDECREF(m_varsOut);
+    Py_XDECREF(m_scriptResult);
+    Py_XDECREF(m_scriptArgs); // also decrements script and vars
+    for (size_t i = 0; i < m_pyInputArrays.size(); i++)
+        Py_XDECREF(m_pyInputArrays[i]);
+    m_pyInputArrays.clear();
+    Py_XDECREF(m_bytecode);
+    Py_XDECREF(m_metaIn);
+    Py_XDECREF(m_metaOut);
+}
+
+
+void Invocation::resetArguments()
+{
+    cleanup();
+    m_varsIn = PyDict_New();
+    m_varsOut = PyDict_New();
+    m_metaIn = PyList_New(0);
+    m_metaOut = PyList_New(0);
+}
+
+
+void Invocation::insertArgument(std::string const& name, uint8_t* data,
+    Dimension::Type t, point_count_t count)
+{
+    npy_intp mydims = count;
+    int nd = 1;
+    npy_intp* dims = &mydims;
+    npy_intp stride = Dimension::size(t);
+    npy_intp* strides = &stride;
+
+#ifdef NPY_ARRAY_CARRAY
+    int flags = NPY_ARRAY_CARRAY;
+#else
+    int flags = NPY_CARRAY;
+#endif
+
+    const int pyDataType = plang::Environment::getPythonDataType(t);
+
+    PyObject* pyArray = PyArray_New(&PyArray_Type, nd, dims, pyDataType,
+        strides, data, 0, flags, NULL);
+    m_pyInputArrays.push_back(pyArray);
+    PyDict_SetItemString(m_varsIn, name.c_str(), pyArray);
+}
+
+
+void *Invocation::extractResult(std::string const& name,
+    Dimension::Type t)
+{
+    PyObject* xarr = PyDict_GetItemString(m_varsOut, name.c_str());
+    if (!xarr)
+        throw pdal::pdal_error("plang output variable '" + name + "' not found.");
+    if (!PyArray_Check(xarr))
+        throw pdal::pdal_error("Plang output variable  '" + name +
+            "' is not a numpy array");
+
+    PyArrayObject* arr = (PyArrayObject*)xarr;
+
+    npy_intp one = 0;
+    const int pyDataType = pdal::plang::Environment::getPythonDataType(t);
+    PyArray_Descr *dtype = PyArray_DESCR(arr);
+
+    if (static_cast<uint32_t>(dtype->elsize) != Dimension::size(t))
+    {
+        std::ostringstream oss;
+        oss << "dtype of array has size " << dtype->elsize
+            << " but PDAL dimension '" << name << "' has byte size of "
+            << Dimension::size(t) << " bytes.";
+        throw pdal::pdal_error(oss.str());
+    }
+
+    using namespace Dimension;
+    BaseType b = Dimension::base(t);
+    if (dtype->kind == 'i' && b != BaseType::Signed)
+    {
+        std::ostringstream oss;
+        oss << "dtype of array has a signed integer type but the " <<
+            "dimension data type of '" << name <<
+            "' is not pdal::Signed.";
+        throw pdal::pdal_error(oss.str());
+    }
+
+    if (dtype->kind == 'u' && b != BaseType::Unsigned)
+    {
+        std::ostringstream oss;
+        oss << "dtype of array has a unsigned integer type but the " <<
+            "dimension data type of '" << name <<
+            "' is not pdal::Unsigned.";
+        throw pdal::pdal_error(oss.str());
+    }
+
+    if (dtype->kind == 'f' && b != BaseType::Floating)
+    {
+        std::ostringstream oss;
+        oss << "dtype of array has a float type but the " <<
+            "dimension data type of '" << name << "' is not pdal::Floating.";
+        throw pdal::pdal_error(oss.str());
+    }
+    return PyArray_GetPtr(arr, &one);
+}
+
+
+void Invocation::getOutputNames(std::vector<std::string>& names)
+{
+    names.clear();
+
+    PyObject *key, *value;
+    Py_ssize_t pos = 0;
+
+    while (PyDict_Next(m_varsOut, &pos, &key, &value))
+    {
+        const char* p(0);
+#if PY_MAJOR_VERSION >= 3
+        p = PyBytes_AsString(PyUnicode_AsUTF8String(key));
+#else
+        p = PyString_AsString(key);
+#endif
+        if (p)
+            names.push_back(p);
+    }
+}
+
+
+bool Invocation::hasOutputVariable(const std::string& name) const
+{
+    return (PyDict_GetItemString(m_varsOut, name.c_str()) != NULL);
+}
+
+
+bool Invocation::execute()
+{
+    if (!m_bytecode)
+        throw pdal::pdal_error("No code has been compiled");
+
+    Py_INCREF(m_varsIn);
+    Py_INCREF(m_varsOut);
+    Py_ssize_t numArgs = argCount(m_function);
+    m_scriptArgs = PyTuple_New(numArgs);
+    PyTuple_SetItem(m_scriptArgs, 0, m_varsIn);
+    if (numArgs > 1)
+        PyTuple_SetItem(m_scriptArgs, 1, m_varsOut);
+    if (numArgs > 2)
+        PyTuple_SetItem(m_scriptArgs, 2, m_metaIn);
+    if (numArgs > 3)
+        PyTuple_SetItem(m_scriptArgs, 3, m_metaOut);
+
+    m_scriptResult = PyObject_CallObject(m_function, m_scriptArgs);
+    if (!m_scriptResult)
+        throw pdal::pdal_error(getTraceback());
+
+    if (!PyBool_Check(m_scriptResult))
+        throw pdal::pdal_error("User function return value not a boolean type.");
+
+    return (m_scriptResult == Py_True);
+}
+
+} // namespace plang
+} // namespace pdal
+
diff --git a/include/pdal/plang/Invocation.hpp b/pdal/plang/Invocation.hpp
similarity index 100%
rename from include/pdal/plang/Invocation.hpp
rename to pdal/plang/Invocation.hpp
diff --git a/pdal/plang/Redirector.cpp b/pdal/plang/Redirector.cpp
new file mode 100644
index 0000000..f0014e7
--- /dev/null
+++ b/pdal/plang/Redirector.cpp
@@ -0,0 +1,221 @@
+//
+// Copyright (C) 2011 Mateusz Loskot <mateusz at loskot.net>
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+//
+// Blog article: http://mateusz.loskot.net/?p=2819
+
+#include <pdal/plang/Redirector.hpp>
+
+#ifdef PDAL_COMPILER_MSVC
+#  pragma warning(disable: 4127)  // conditional expression is constant
+#  pragma warning(disable: 4068)  // gcc pragma warnings
+#endif
+
+#include <ostream>
+#include <string>
+
+namespace pdal
+{
+namespace plang
+{
+
+struct Stdout
+{
+    PyObject_HEAD
+    Redirector::stdout_write_type write;
+    Redirector::stdout_flush_type flush;
+};
+
+
+static PyObject* Stdout_write(PyObject* self, PyObject* args)
+{
+    std::size_t written(0);
+    Stdout* selfimpl = reinterpret_cast<Stdout*>(self);
+    if (selfimpl->write)
+    {
+        char* data;
+        if (!PyArg_ParseTuple(args, "s", &data))
+            return 0;
+
+        std::string str(data);
+        selfimpl->write(str);
+        written = str.size();
+    }
+    return PyLong_FromSize_t(written);
+}
+
+
+static PyObject* Stdout_flush(PyObject* self, PyObject* /*args*/)
+{
+    Stdout *selfimpl = reinterpret_cast<Stdout *>(self);
+    if (selfimpl->flush)
+    {
+        selfimpl->flush();
+    }
+    return Py_BuildValue("");
+}
+
+
+static PyMethodDef Stdout_methods[] =
+{
+    {"write", Stdout_write, METH_VARARGS, "sys.stdout.write"},
+    {"flush", Stdout_flush, METH_VARARGS, "sys.stdout.flush"},
+    {0, 0, 0, 0} // sentinel
+};
+
+
+static PyTypeObject StdoutType =
+{
+    PyVarObject_HEAD_INIT(0, 0)
+    "redirector.StdoutType", /* tp_name */
+    sizeof(Stdout), /* tp_basicsize */
+    0, /* tp_itemsize */
+    0, /* tp_dealloc */
+    0, /* tp_print */
+    0, /* tp_getattr */
+    0, /* tp_setattr */
+    0, /* tp_reserved */
+    0, /* tp_repr */
+    0, /* tp_as_number */
+    0, /* tp_as_sequence */
+    0, /* tp_as_mapping */
+    0, /* tp_hash */
+    0, /* tp_call */
+    0, /* tp_str */
+    0, /* tp_getattro */
+    0, /* tp_setattro */
+    0, /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT, /* tp_flags */
+    "redirector.Stdout objects", /* tp_doc */
+    0, /* tp_traverse */
+    0, /* tp_clear */
+    0, /* tp_richcompare */
+    0, /* tp_weaklistoffset */
+    0, /* tp_iter */
+    0, /* tp_iternext */
+    Stdout_methods, /* tp_methods */
+    0, /* tp_members */
+    0, /* tp_getset */
+    0, /* tp_base */
+    0, /* tp_dict */
+    0, /* tp_descr_get */
+    0, /* tp_descr_set */
+    0, /* tp_dictoffset */
+    0, /* tp_init */
+    0, /* tp_alloc */
+    0, /* tp_new */
+    0, /* tp_free */
+    0, /* tp_is_gc */
+    0, /* tp_bases */
+    0, /* tp_mro */
+    0, /* tp_cache */
+    0, /* tp_subclasses */
+    0, /* tp_weaklist */
+    0, /* tp_del */
+    0, /* tp_version_tag */
+#if PY_MAJOR_VERSION >= 3
+    0, /* tp_finalilzer */
+#endif
+};
+
+
+Redirector::Redirector()
+    : m_stdout(NULL)
+    , m_stdout_saved(NULL)
+{
+    return;
+}
+
+
+Redirector::~Redirector()
+{
+    return;
+}
+
+
+PyMODINIT_FUNC redirector_init(void)
+{
+#if PY_MAJOR_VERSION >= 3
+    return Redirector::init();
+#else
+    Redirector::init();
+#endif
+}
+
+#if PY_MAJOR_VERSION >= 3
+    static struct PyModuleDef redirectordef = {
+        PyModuleDef_HEAD_INIT,
+        "redirector",     /* m_name */
+        "redirector.Stdout objects",  /* m_doc */
+        -1,                  /* m_size */
+        Stdout_methods,    /* m_methods */
+        NULL,                /* m_reload */
+        NULL,                /* m_traverse */
+        NULL,                /* m_clear */
+        NULL,                /* m_free */
+    };
+#endif
+
+PyObject* Redirector::init()
+{
+    StdoutType.tp_new = PyType_GenericNew;
+    if (PyType_Ready(&StdoutType) < 0)
+        return NULL;
+#if PY_MAJOR_VERSION >= 3
+    PyObject* m = PyModule_Create(&redirectordef);
+#else
+    PyObject* m = Py_InitModule3("redirector", 0, 0);
+#endif
+    if (m)
+    {
+        //ABELL - This is bad code as the type cast is invalid. (type pun
+        //  warning.)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+        Py_INCREF(reinterpret_cast<PyObject*>(&StdoutType));
+        PyModule_AddObject(m, "Stdout", reinterpret_cast<PyObject*>(&StdoutType));
+#pragma GCC diagnostic pop
+    }
+    return m;
+}
+
+
+void Redirector::set_stdout(std::ostream* ostr)
+{
+    stdout_write_type writeFunc = [ostr](std::string msg) { *ostr << msg; };
+    stdout_flush_type flushFunc = [ostr]{ ostr->flush(); };
+
+    this->set_stdout(writeFunc, flushFunc);
+}
+
+
+void Redirector::set_stdout(stdout_write_type write, stdout_flush_type flush)
+{
+    if (!m_stdout)
+    {
+        m_stdout_saved =
+            PySys_GetObject(const_cast<char*>("stdout")); // borrowed
+        m_stdout = StdoutType.tp_new(&StdoutType, 0, 0);
+    }
+
+    Stdout* impl = reinterpret_cast<Stdout*>(m_stdout);
+    impl->write = write;
+    impl->flush = flush;
+    PySys_SetObject(const_cast<char*>("stdout"), m_stdout);
+}
+
+
+void Redirector::reset_stdout()
+{
+    if (m_stdout_saved)
+        PySys_SetObject(const_cast<char*>("stdout"), m_stdout_saved);
+
+    Py_XDECREF(m_stdout);
+    m_stdout = 0;
+}
+
+} //namespace plang
+} //namespace pdal
+
diff --git a/pdal/plang/Redirector.hpp b/pdal/plang/Redirector.hpp
new file mode 100644
index 0000000..40d1ed1
--- /dev/null
+++ b/pdal/plang/Redirector.hpp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2011 Mateusz Loskot <mateusz at loskot.net>
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+//
+// Blog article: http://mateusz.loskot.net/?p=2819
+
+// http://python3porting.com/cextensions.html
+
+#pragma once
+
+#include <functional>
+#include <pdal/pdal_defines.h>
+
+#ifdef PDAL_COMPILER_MSVC
+#  pragma warning(disable: 4127) // conditional expression is constant
+#define HAVE_ROUND // inconsistent dll linkage otherwise
+#endif
+
+#include <Python.h>
+
+#undef toupper
+#undef tolower
+#undef isspace
+
+namespace pdal
+{
+namespace plang
+{
+
+PyMODINIT_FUNC redirector_init(void);
+
+class Redirector
+{
+public:
+    Redirector();
+    ~Redirector();
+
+    static PyObject* init();
+    void set_stdout(std::ostream* ostr);
+    void reset_stdout();
+
+    typedef std::function<void(std::string)> stdout_write_type;
+    typedef std::function<void()> stdout_flush_type;
+
+private:
+    void set_stdout(stdout_write_type write, stdout_flush_type flush);
+
+    // Internal state
+    PyObject* m_stdout;
+    PyObject* m_stdout_saved;
+};
+
+} // namespace plang
+} // namespace pdal
+
diff --git a/src/plang/Script.cpp b/pdal/plang/Script.cpp
similarity index 100%
rename from src/plang/Script.cpp
rename to pdal/plang/Script.cpp
diff --git a/include/pdal/plang/Script.hpp b/pdal/plang/Script.hpp
similarity index 100%
rename from include/pdal/plang/Script.hpp
rename to pdal/plang/Script.hpp
diff --git a/include/pdal/plugin.hpp b/pdal/plugin.hpp
similarity index 100%
rename from include/pdal/plugin.hpp
rename to pdal/plugin.hpp
diff --git a/src/DynamicLibrary.hpp b/pdal/private/DynamicLibrary.hpp
similarity index 100%
rename from src/DynamicLibrary.hpp
rename to pdal/private/DynamicLibrary.hpp
diff --git a/src/PipelineReaderXML.hpp b/pdal/private/PipelineReaderXML.hpp
similarity index 100%
rename from src/PipelineReaderXML.hpp
rename to pdal/private/PipelineReaderXML.hpp
diff --git a/src/StageRunner.hpp b/pdal/private/StageRunner.hpp
similarity index 100%
rename from src/StageRunner.hpp
rename to pdal/private/StageRunner.hpp
diff --git a/src/prototype.vcxproj b/pdal/prototype.vcxproj
similarity index 100%
rename from src/prototype.vcxproj
rename to pdal/prototype.vcxproj
diff --git a/include/pdal/util/Algorithm.hpp b/pdal/util/Algorithm.hpp
similarity index 100%
rename from include/pdal/util/Algorithm.hpp
rename to pdal/util/Algorithm.hpp
diff --git a/pdal/util/Bounds.cpp b/pdal/util/Bounds.cpp
new file mode 100644
index 0000000..1b62cbe
--- /dev/null
+++ b/pdal/util/Bounds.cpp
@@ -0,0 +1,325 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <iostream>
+#include <limits>
+#include <vector>
+
+#include <pdal/util/Bounds.hpp>
+
+namespace
+{
+
+template <typename PREDICATE>
+void eat(std::istream& in, PREDICATE p)
+{
+    while (p((char)in.get()))
+        ;
+    if (in.eof())
+        in.clear(in.rdstate() & ~std::ios::failbit);
+    else
+        in.unget();
+}
+
+bool eat(std::istream& in, char c)
+{
+    if ((char)in.get() == c)
+        return true;
+    in.unget();
+    return false;
+}
+
+void readpair(std::istream& istr, double& low, double& high)
+{
+    eat(istr, isspace);
+    if (!eat(istr,'['))
+        istr.setstate(std::ios_base::failbit);
+
+    eat(istr, isspace);
+    istr >> low;
+
+    eat(istr, isspace);
+    if (!eat(istr,','))
+        istr.setstate(std::ios_base::failbit);
+
+    eat(istr, isspace);
+    istr >> high;
+
+    if (!eat(istr,']'))
+        istr.setstate(std::ios_base::failbit);
+}
+
+} // unnamed namespace
+
+namespace pdal
+{
+
+namespace
+{
+
+const double LOWEST = (std::numeric_limits<double>::lowest)();
+const double HIGHEST = (std::numeric_limits<double>::max)();
+
+}
+    
+void BOX2D::clear()
+{
+    minx = HIGHEST; miny = HIGHEST;
+    maxx = LOWEST; maxy = LOWEST;
+}
+
+void BOX3D::clear()
+{
+    BOX2D::clear();
+    minz = HIGHEST;
+    maxz = LOWEST;
+}
+
+bool BOX2D::empty() const
+{
+    return  minx == HIGHEST && maxx == LOWEST &&
+        miny == HIGHEST && maxy == LOWEST;
+}
+
+bool BOX3D::empty() const
+{
+    return  BOX2D::empty() && minz == HIGHEST && maxz == LOWEST;
+}
+
+void BOX2D::grow(double x, double y)
+{
+    if (x < minx) minx = x;
+    if (x > maxx) maxx = x;
+
+    if (y < miny) miny = y;
+    if (y > maxy) maxy = y;
+}
+
+void BOX3D::grow(double x, double y, double z)
+{
+    BOX2D::grow(x, y);
+    if (z < minz) minz = z;
+    if (z > maxz) maxz = z;
+}
+
+const BOX2D& BOX2D::getDefaultSpatialExtent()
+{
+    static BOX2D v(LOWEST, LOWEST, HIGHEST, HIGHEST);
+    return v;
+}    
+
+
+const BOX3D& BOX3D::getDefaultSpatialExtent()
+{
+    static BOX3D v(LOWEST, LOWEST, LOWEST, HIGHEST, HIGHEST, HIGHEST);
+    return v;
+}    
+
+Bounds::Bounds(const BOX3D& box) : m_box(box)
+{}
+
+
+Bounds::Bounds(const BOX2D& box) : m_box(box)
+{
+    m_box.minz = HIGHEST;
+    m_box.maxz = LOWEST;
+}
+
+BOX3D Bounds::to3d() const
+{
+    if (m_box.minz == HIGHEST && m_box.maxz == LOWEST)
+        return BOX3D();
+    return m_box;
+}
+
+BOX2D Bounds::to2d() const
+{
+    return m_box.to2d();
+}
+
+bool Bounds::is3d() const
+{
+    return (m_box.minz != HIGHEST || m_box.maxz != LOWEST);
+}
+
+
+void Bounds::set(const BOX3D& box)
+{
+    m_box = box;
+}
+
+
+void Bounds::set(const BOX2D& box)
+{
+    m_box = BOX3D(box);
+    m_box.minz = HIGHEST;
+    m_box.maxz = LOWEST;
+}
+
+std::istream& operator>>(std::istream& istr, BOX2D& bounds)
+{
+    //ABELL - Not sure the point of this.  I get that one can have an "empty"
+    // BOX2D, but when would it be useful to create one from a string?
+    // A really dirty way to check for an empty bounds object right off
+    // the bat
+    char left_paren = (char)istr.get();
+    if (!istr.good())
+    {
+        istr.setstate(std::ios_base::failbit);
+        return istr;
+    }
+    const char right_paren = (char)istr.get();
+
+    if (left_paren == '(' && right_paren == ')')
+    {
+        bounds = BOX2D();
+        return istr;
+    }
+    istr.unget();
+    istr.unget(); // ()
+
+    std::vector<double> v;
+
+    eat(istr, isspace);
+    if (!eat(istr,'('))
+        istr.setstate(std::ios_base::failbit);
+
+    bool done = false;
+    for (int i = 0; i < 2; ++i)
+    {
+        double low, high;
+
+        readpair(istr, low, high);
+
+        eat(istr, isspace);
+        if (!eat(istr, i == 1 ? ')' : ','))
+            istr.setstate(std::ios_base::failbit);
+        v.push_back(low);
+        v.push_back(high);
+    }
+
+    if (istr.good())
+    {
+        bounds.minx = v[0];
+        bounds.maxx = v[1];
+        bounds.miny = v[2];
+        bounds.maxy = v[3];
+    }
+    return istr;
+}
+
+std::istream& operator>>(std::istream& istr, BOX3D& bounds)
+{
+    //ABELL - Not sure the point of this.  I get that one can have an "empty"
+    // BOX3D, but when would it be useful to create one from a string?
+    // A really dirty way to check for an empty bounds object right off
+    // the bat
+    char left_paren = (char)istr.get();
+    if (!istr.good())
+    {
+        istr.setstate(std::ios_base::failbit);
+        return istr;
+    }
+    const char right_paren = (char)istr.get();
+
+    if (left_paren == '(' && right_paren == ')')
+    {
+        BOX3D output;
+        bounds = output;
+        return istr;
+    }
+    istr.unget();
+    istr.unget(); // ()
+
+    std::vector<double> v;
+
+    eat(istr, isspace);
+    if (!eat(istr,'('))
+        istr.setstate(std::ios_base::failbit);
+
+    bool done = false;
+    for (int i = 0; i < 3; ++i)
+    {
+        double low, high;
+
+        readpair(istr, low, high);
+
+        eat(istr, isspace);
+        if (!eat(istr, i == 2 ? ')' : ','))
+            istr.setstate(std::ios_base::failbit);
+        v.push_back(low);
+        v.push_back(high);
+    }
+
+    if (istr.good())
+    {
+        bounds.minx = v[0];
+        bounds.maxx = v[1];
+        bounds.miny = v[2];
+        bounds.maxy = v[3];
+        bounds.minz = v[4];
+        bounds.maxz = v[5];
+    }
+    return istr;
+}
+
+std::istream& operator>>(std::istream& in, Bounds& bounds)
+{
+    std::streampos start = in.tellg();
+    BOX3D b3d;
+    in >> b3d;
+    if (in.fail())
+    {
+        in.clear();
+        in.seekg(start);
+        BOX2D b2d;
+        in >> b2d;
+        if (!in.fail())
+            bounds.set(b2d);
+    }
+    else
+        bounds.set(b3d);
+    return in;
+}
+
+std::ostream& operator<<(std::ostream& out, const Bounds& bounds)
+{
+    if (bounds.is3d())
+        out << bounds.to3d();
+    else
+        out << bounds.to2d();
+    return out;
+}
+
+} // namespace pdal
diff --git a/pdal/util/Bounds.hpp b/pdal/util/Bounds.hpp
new file mode 100644
index 0000000..aaab4df
--- /dev/null
+++ b/pdal/util/Bounds.hpp
@@ -0,0 +1,643 @@
+/******************************************************************************
+ * Copyright (c) 2010, Howard Butler
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of the Martin Isenburg or Iowa Department
+ *       of Natural Resources nor the names of its contributors may be
+ *       used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#pragma once
+
+#include <cstdint>
+#include <sstream>
+
+#include "pdal_util_export.hpp"
+
+namespace pdal
+{
+
+/**
+  BOX2D represents a two-dimensional box with double-precision bounds.
+*/
+class PDAL_DLL BOX2D
+{
+public:
+    double minx;  ///< Minimum X value.
+    double maxx;  ///< Maximum X value.
+    double miny;  ///< Minimum Y value.
+    double maxy;  ///< Maximum Y value.
+
+    /**
+      Construct an "empty" bounds box.
+    */
+    BOX2D()
+        { clear(); }
+
+    /**
+      Construct and initialize a bounds box.
+
+      \param minx  Minimum X value.
+      \param miny  Minimum Y value.
+      \param maxx  Maximum X value.
+      \param maxy  Maximum Y value.
+    */
+    BOX2D(double minx, double miny, double maxx, double maxy) :
+        minx(minx), maxx(maxx), miny(miny), maxy(maxy)
+    {}
+
+    /**
+      Determine whether a bounds box has any bounds set (is in a state
+      as if default-constructed).
+
+      \return  Whether the bounds box is empty.
+    */
+    bool empty() const;
+
+    /**
+      Clear the bounds box to an empty state.
+    */
+    void clear();
+
+    /**
+      Expand the bounds of the box if a value is less than the current
+      minimum or greater than the current maximum.  If the bounds box is
+      currently empty, both minimum and maximum box bounds will be set to
+      the provided value.
+
+      \param x  X dimension value.
+      \param y  Y dimension value.
+    */
+    void grow(double x, double y);
+
+    /**
+      Determine if a bounds box contains a point.
+
+      \param x  X dimension value.
+      \param y  Y dimension value.
+      \return  Whether both dimensions are equal to or less than the maximum
+        box values and equal to or more than the minimum box values.
+    */
+    bool contains(double x, double y) const
+        { return minx <= x && x <= maxx && miny <= y && y <= maxy; }
+
+    /**
+      Determine if the bounds of this box are the same as that of another
+      box.  Empty bounds boxes are always equal.
+
+      \param other  Bounds box to check for equality.
+      \return \c true if the provided box has equal limits to this box,
+        \c false otherwise.
+    */
+    bool equal(const BOX2D& other) const
+    {
+        return  minx == other.minx && maxx == other.maxx &&
+            miny == other.miny && maxy == other.maxy;
+    }
+
+    /**
+      Determine if the bounds of this box are the same as that of another
+      box.  Empty bounds boxes are always equal.
+
+      \param other  Bounds box to check for equality.
+      \return \c true if the provided box has equal limits to this box,
+        \c false otherwise.
+    */
+    bool operator==(BOX2D const& other) const
+    {
+        return equal(other);
+    }
+
+    /**
+      Determine if the bounds of this box are different from that of another
+      box.  Empty bounds boxes are never unequal.
+
+      \param other  Bounds box to check for inequality.
+      \return \c true if the provided box has limits different from this box,
+        \c false otherwise.
+    */
+    bool operator!=(BOX2D const& other) const
+    {
+        return (!equal(other));
+    }
+
+    /**
+      Expand this box to contain another box.
+
+      \param other  Box that this box should contain.
+    */
+    void grow(const BOX2D& other)
+    {
+        if (other.minx < minx) minx = other.minx;
+        if (other.maxx > maxx) maxx = other.maxx;
+
+        if (other.miny < miny) miny = other.miny;
+        if (other.maxy > maxy) maxy = other.maxy;
+    }
+
+    /**
+      Clip this bounds box by another so it will be contained by the
+      other box.
+
+      \param other  Clipping box for this box.
+    */
+    void clip(const BOX2D& other)
+    {
+        if (other.minx > minx) minx = other.minx;
+        if (other.maxx < maxx) maxx = other.maxx;
+
+        if (other.miny > miny) miny = other.miny;
+        if (other.maxy < maxy) maxy = other.maxy;
+    }
+
+    /**
+      Determine if another bounds box is contained in this bounds box.
+      Equal limits are considered to be contained.
+
+      \param other  Bounds box to check for containment.
+      \return  \c true if the provided box is contained in this box,
+        \c false otherwise.
+    **/
+    bool contains(const BOX2D& other) const
+    {
+        return minx <= other.minx && maxx >= other.maxx &&
+            miny <= other.miny && maxy >= other.maxy;
+    }
+
+    /**
+      Determine if another box overlaps this box.
+
+      \param other  Box to test for overlap.
+      \return  Whether the provided box overlaps this box.
+    */
+    bool overlaps(const BOX2D& other)
+    {
+        return minx <= other.maxx && maxx >= other.minx &&
+            miny <= other.maxy && maxy >= other.miny;
+    }
+
+    /**
+      Convert this box to a string suitable for use in SQLite.
+
+      \param precision  Precision for output [default: 8]
+      \return  String format of this box.
+    */
+    std::string toBox(uint32_t precision = 8) const
+    {
+        std::stringstream oss;
+
+        oss.precision(precision);
+        oss.setf(std::ios_base::fixed, std::ios_base::floatfield);
+
+        oss << "box2d(";
+            oss << minx << " " << miny << ", ";
+            oss << maxx << " " << maxy << ")";
+        return oss.str();
+    }
+
+    /**
+      Convert this box to a well-known text string.
+
+      \param precision  Precision for output [default: 8]
+      \return  String format of this box.
+    */
+    std::string toWKT(uint32_t precision = 8) const
+    {
+        if (empty())
+            return std::string();
+
+        std::stringstream oss;
+
+        oss.precision(precision);
+        oss.setf(std::ios_base::fixed, std::ios_base::floatfield);
+
+        oss << "POLYGON ((";
+        oss << minx << " " << miny << ", ";
+        oss << minx << " " << maxy << ", ";
+        oss << maxx << " " << maxy << ", ";
+        oss << maxx << " " << miny << ", ";
+        oss << minx << " " << miny;
+        oss << "))";
+
+        return oss.str();
+    }
+
+    /**
+      Convert this box to a GeoJSON text string.
+
+      \param precision  Precision for output [default: 8]
+      \return  String format of this box.
+    */
+    std::string toGeoJSON(uint32_t precision = 8) const
+    {
+        if (empty())
+            return std::string();
+
+        std::stringstream oss;
+
+        oss.precision(precision);
+        oss.setf(std::ios_base::fixed, std::ios_base::floatfield);
+        oss << "{\"bbox\":[" << minx << ", " << miny << ", " <<
+            maxx <<  "," << maxy << "]}";
+        return oss.str();
+    }
+
+    /**
+      Return a staticly-allocated Bounds extent that represents infinity
+
+      \return  A bounds box with infinite bounds,
+    */
+    static const BOX2D& getDefaultSpatialExtent();
+};
+
+/**
+  BOX3D represents a three-dimensional box with double-precision bounds.
+*/
+class PDAL_DLL BOX3D : private BOX2D
+{
+public:
+    using BOX2D::minx;
+    using BOX2D::maxx;
+    using BOX2D::miny;
+    using BOX2D::maxy;
+    double minz;   ///< Minimum Z value.
+    double maxz;   ///< Maximum Z value.
+
+    /**
+      Clear the bounds box to an empty state.
+    */
+    BOX3D()
+       { clear(); }
+
+    BOX3D(const BOX3D& box) :
+        BOX2D(box), minz(box.minz), maxz(box.maxz)
+    {}
+
+    explicit BOX3D(const BOX2D& box) :
+        BOX2D(box), minz(0), maxz(0)
+    {}
+
+    /**
+      Construct and initialize a bounds box.
+
+      \param minx  Minimum X value.
+      \param miny  Minimum Y value.
+      \param minx  Minimum Z value.
+      \param maxx  Maximum X value.
+      \param maxy  Maximum Y value.
+      \param maxz  Maximum Z value.
+    */
+    BOX3D(double minx, double miny, double minz, double maxx, double maxy,
+        double maxz) : BOX2D(minx, miny, maxx, maxy), minz(minz), maxz(maxz)
+    {}
+
+    /**
+      Determine whether a bounds box has any bounds set (is in a state
+      as if default-constructed).
+
+      \return  Whether the bounds box is empty.
+    */
+    bool empty() const;
+
+    /**
+      Expand the bounds of the box if a value is less than the current
+      minimum or greater than the current maximum.  If the bounds box is
+      currently empty, both minimum and maximum box bounds will be set to
+      the provided value.
+
+      \param x  X dimension value.
+      \param y  Y dimension value.
+      \param z  Z dimension value.
+    */
+    void grow(double x, double y, double z);
+
+    /**
+      Clear the bounds box to an empty state.
+    */
+    void clear();
+
+
+    /**
+      Determine if a bounds box contains a point.
+
+      \param x  X dimension value.
+      \param y  Y dimension value.
+      \param z  Z dimension value.
+      \return  Whether both dimensions are equal to or less than the maximum
+        box values and equal to or more than the minimum box values.
+    */
+    bool contains(double x, double y, double z) const
+    {
+        return BOX2D::contains(x, y) && minz <= z && z <= maxz;
+    }
+
+    /**
+      Determine if another bounds box is contained in this bounds box.
+      Equal limits are considered to be contained.
+
+      \param other  Bounds box to check for containment.
+      \return  \c true if the provided box is contained in this box,
+        \c false otherwise.
+    **/
+    bool contains(const BOX3D& other) const
+    {
+        return BOX2D::contains(other) &&
+            minz <= other.minz && other.maxz <= maxz;
+    }
+
+    /**
+      Determine if the bounds of this box are the same as that of another
+      box.  Empty bounds boxes are always equal.
+
+      \param other  Bounds box to check for equality.
+      \return \c true if the provided box has equal limits to this box,
+        \c false otherwise.
+    */
+    bool equal(const BOX3D& other) const
+    {
+        return  BOX2D::contains(other) &&
+            minz == other.minz && maxz == other.maxz;
+    }
+
+    /**
+      Determine if the bounds of this box are the same as that of another
+      box.  Empty bounds boxes are always equal.
+
+      \param other  Bounds box to check for equality.
+      \return \c true if the provided box has equal limits to this box,
+        \c false otherwise.
+    */
+    bool operator==(BOX3D const& rhs) const
+    {
+        return equal(rhs);
+    }
+
+    /**
+      Determine if the bounds of this box are different from that of another
+      box.  Empty bounds boxes are never unequal.
+
+      \param other  Bounds box to check for inequality.
+      \return \c true if the provided box has limits different from this box,
+        \c false otherwise.
+    */
+    bool operator!=(BOX3D const& rhs) const
+    {
+        return (!equal(rhs));
+    }
+
+    /**
+      Expand this box to contain another box.
+
+      \param other  Box that this box should contain.
+    */
+    void grow(const BOX3D& other)
+    {
+        BOX2D::grow(other);
+        if (other.minz < minz) minz = other.minz;
+        if (other.maxz > maxz) maxz = other.maxz;
+    }
+
+    /**
+      Clip this bounds box by another so it will be contained by the
+      other box.
+
+      \param other  Clipping box for this box.
+    */
+    void clip(const BOX3D& other)
+    {
+        BOX2D::clip(other);
+        if (other.minz < minz) minz = other.minz;
+        if (other.maxz > maxz) maxz = other.maxz;
+    }
+
+    /**
+      Determine if another box overlaps this box.
+
+      \param other  Box to test for overlap.
+      \return  Whether the provided box overlaps this box.
+    */
+    bool overlaps(const BOX3D& other)
+    {
+        return BOX2D::overlaps(other) &&
+           minz <= other.maxz && maxz >= other.minz;
+    }
+
+    /**
+      Convert this box to 2-dimensional bounding box.
+
+      \return  Bounding box with Z dimension stripped.
+    */
+    BOX2D to2d() const
+    {
+        return *this;
+    }
+
+    /**
+      Convert this box to a string suitable for use in SQLite.
+
+      \param precision  Precision for output [default: 8]
+      \return  String format of this box.
+    */
+    std::string toBox(uint32_t precision = 8) const
+    {
+        std::stringstream oss;
+
+        oss.precision(precision);
+        oss.setf(std::ios_base::fixed, std::ios_base::floatfield);
+
+        oss << "box3d(" << minx << " " << miny << " " << minz << ", " <<
+            maxx << " " << maxy << " " << maxz << ")";
+        return oss.str();
+    }
+
+    /**
+      Convert this box to a well-known text string.
+      
+      \param precision  Precision for output [default: 8]
+      \return  String format of this box.
+    */
+    std::string toWKT(uint32_t precision = 8) const
+    {
+        if (empty())
+            return std::string();
+
+        std::stringstream oss;
+
+        oss.precision(precision);
+        oss.setf(std::ios_base::fixed, std::ios_base::floatfield);
+
+        oss << "POLYHEDRON Z ( ";
+
+        oss << "((" << minx << " " << miny << " " << minz << ", " <<
+                       maxx << " " << miny << " " << minz << ", " <<
+                       maxx << " " << maxy << " " << minz << ", " <<
+                       minx << " " << maxy << " " << minz << ", " <<
+                       minx << " " << miny << " " << minz << ", " <<
+               ")), ";
+        oss << "((" << minx << " " << miny << " " << minz << ", " <<
+                       maxx << " " << miny << " " << minz << ", " <<
+                       maxx << " " << miny << " " << maxz << ", " <<
+                       minx << " " << miny << " " << maxz << ", " <<
+                       minx << " " << miny << " " << minz << ", " <<
+               ")), ";
+        oss << "((" << maxx << " " << miny << " " << minz << ", " <<
+                       maxx << " " << maxy << " " << minz << ", " <<
+                       maxx << " " << maxy << " " << maxz << ", " <<
+                       maxx << " " << miny << " " << maxz << ", " <<
+                       maxx << " " << miny << " " << minz << ", " <<
+               ")), ";
+        oss << "((" << maxx << " " << maxy << " " << minz << ", " <<
+                       minx << " " << maxy << " " << minz << ", " <<
+                       minx << " " << maxy << " " << maxz << ", " <<
+                       maxx << " " << maxy << " " << maxz << ", " <<
+                       maxx << " " << maxy << " " << minz << ", " <<
+               ")), ";
+        oss << "((" << minx << " " << maxy << " " << minz << ", " <<
+                       minx << " " << miny << " " << minz << ", " <<
+                       minx << " " << miny << " " << maxz << ", " <<
+                       minx << " " << maxy << " " << maxz << ", " <<
+                       minx << " " << maxy << " " << minz << ", " <<
+               ")), ";
+        oss << "((" << minx << " " << miny << " " << maxz << ", " <<
+                       maxx << " " << miny << " " << maxz << ", " <<
+                       maxx << " " << maxy << " " << maxz << ", " <<
+                       minx << " " << maxy << " " << maxz << ", " <<
+                       minx << " " << miny << " " << maxz << ", " <<
+               "))";
+
+        oss << " )";
+
+        return oss.str();
+    }
+
+    /**
+      Return a staticly-allocated Bounds extent that represents infinity
+
+      \return  A bounds box with infinite bounds,
+    */
+    static const BOX3D& getDefaultSpatialExtent();
+};
+
+/**
+  Wrapper for BOX3D and BOX2D to allow extraction as either.  Typically used
+  to facilitate streaming either a BOX2D or BOX3D 
+*/
+class PDAL_DLL Bounds
+{
+public:
+    Bounds()
+    {}
+
+    Bounds(const BOX3D& box);
+    Bounds(const BOX2D& box);
+
+    BOX3D to3d() const;
+    BOX2D to2d() const;
+    bool is3d() const;
+
+    friend PDAL_DLL std::istream& operator >> (std::istream& in,
+        Bounds& bounds);
+    friend PDAL_DLL std::ostream& operator << (std::ostream& out,
+        const Bounds& bounds);
+
+private:
+    BOX3D m_box;
+
+    void set(const BOX3D& box);
+    void set(const BOX2D& box);
+};
+
+/**
+  Write a 2D bounds box to a stream in a format used by PDAL options.
+
+  \param ostr  Stream to write to.
+  \param bounds  Box to write.
+*/
+inline std::ostream& operator << (std::ostream& ostr, const BOX2D& bounds)
+{
+    if (bounds.empty())
+    {
+        ostr << "()";
+        return ostr;
+    }
+
+    auto savedPrec = ostr.precision();
+    ostr.precision(16); // or..?
+    ostr << "(";
+    ostr << "[" << bounds.minx << ", " << bounds.maxx << "], " <<
+            "[" << bounds.miny << ", " << bounds.maxy << "]";
+    ostr << ")";
+    ostr.precision(savedPrec);
+    return ostr;
+}
+
+/**
+  Write a 3D bounds box to a stream in a format used by PDAL options.
+
+  \param ostr  Stream to write to.
+  \param bounds  Box to write.
+*/
+inline std::ostream& operator << (std::ostream& ostr, const BOX3D& bounds)
+{
+    if (bounds.empty())
+    {
+        ostr << "()";
+        return ostr;
+    }
+
+    auto savedPrec = ostr.precision();
+    ostr.precision(16); // or..?
+    ostr << "(";
+    ostr << "[" << bounds.minx << ", " << bounds.maxx << "], " <<
+            "[" << bounds.miny << ", " << bounds.maxy << "], " <<
+            "[" << bounds.minz << ", " << bounds.maxz << "]";
+    ostr << ")";
+    ostr.precision(savedPrec);
+    return ostr;
+}
+
+/**
+  Read a 2D bounds box from a stream in a format provided by PDAL options.
+
+  \param istr  Stream to read from.
+  \param bounds  Bounds box to populate.
+*/
+extern PDAL_DLL std::istream& operator>>(std::istream& istr, BOX2D& bounds);
+
+/**
+  Read a 3D bounds box from a stream in a format provided by PDAL options.
+
+  \param istr  Stream to read from.
+  \param bounds  Bounds box to populate.
+*/
+extern PDAL_DLL std::istream& operator>>(std::istream& istr, BOX3D& bounds);
+
+PDAL_DLL std::istream& operator >> (std::istream& in, Bounds& bounds);
+PDAL_DLL std::ostream& operator << (std::ostream& in, const Bounds& bounds);
+
+} // namespace pdal
diff --git a/pdal/util/CMakeLists.txt b/pdal/util/CMakeLists.txt
new file mode 100644
index 0000000..498bd41
--- /dev/null
+++ b/pdal/util/CMakeLists.txt
@@ -0,0 +1,37 @@
+#
+# Make sure we don't attempt to add a library more than once
+#
+get_property(EXISTS GLOBAL PROPERTY _UTIL_INCLUDED)
+if(EXISTS)
+    return()
+endif()
+
+set(PDAL_UTIL_SOURCES
+    "${PDAL_UTIL_DIR}/Bounds.cpp"
+    "${PDAL_UTIL_DIR}/Charbuf.cpp"
+    "${PDAL_UTIL_DIR}/FileUtils.cpp"
+    "${PDAL_UTIL_DIR}/Georeference.cpp"
+    "${PDAL_UTIL_DIR}/Utils.cpp"
+    )
+
+PDAL_ADD_FREE_LIBRARY(${PDAL_UTIL_LIB_NAME} SHARED ${PDAL_UTIL_SOURCES})
+target_link_libraries(${PDAL_UTIL_LIB_NAME}
+    PRIVATE
+        ${PDAL_BOOST_LIB_NAME}
+)
+target_include_directories(${PDAL_UTIL_LIB_NAME} PRIVATE
+    ${PDAL_VENDOR_DIR}/pdalboost)
+
+if (UNIX AND NOT APPLE)
+    target_link_libraries(${PDAL_UTIL_LIB_NAME}
+        PRIVATE
+            dl
+    )
+endif()
+
+set_target_properties(${PDAL_UTIL_LIB_NAME} PROPERTIES
+    VERSION "${PDAL_BUILD_VERSION}"
+    SOVERSION "${PDAL_API_VERSION}"
+    CLEAN_DIRECT_OUTPUT 1)
+
+set_property(GLOBAL PROPERTY _UTIL_INCLUDED TRUE)
diff --git a/src/util/Charbuf.cpp b/pdal/util/Charbuf.cpp
similarity index 100%
rename from src/util/Charbuf.cpp
rename to pdal/util/Charbuf.cpp
diff --git a/include/pdal/util/Charbuf.hpp b/pdal/util/Charbuf.hpp
similarity index 100%
rename from include/pdal/util/Charbuf.hpp
rename to pdal/util/Charbuf.hpp
diff --git a/include/pdal/util/Extractor.hpp b/pdal/util/Extractor.hpp
similarity index 100%
rename from include/pdal/util/Extractor.hpp
rename to pdal/util/Extractor.hpp
diff --git a/pdal/util/FileUtils.cpp b/pdal/util/FileUtils.cpp
new file mode 100644
index 0000000..50f9bad
--- /dev/null
+++ b/pdal/util/FileUtils.cpp
@@ -0,0 +1,401 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <sys/stat.h>
+
+#include <iostream>
+#include <sstream>
+#ifndef WIN32
+#include <glob.h>
+#else
+#include <Windows.h>
+#endif
+
+#include <boost/filesystem.hpp>
+
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/util/Utils.hpp>
+#include <pdal/pdal_types.hpp>
+
+namespace pdal
+{
+
+namespace
+{
+
+bool isStdin(std::string filename)
+{
+    return Utils::toupper(filename) == "STDIN";
+}
+
+bool isStdout(std::string filename)
+{
+    return Utils::toupper(filename) == "STOUT" ||
+        Utils::toupper(filename) == "STDOUT";
+}
+
+std::string addTrailingSlash(std::string path)
+{
+    if (path[path.size() - 1] != '/' && path[path.size() - 1] != '\\')
+        path += "/";
+    return path;
+}
+
+} // unnamed namespace
+
+namespace FileUtils
+{
+
+std::istream *openFile(std::string const& filename, bool asBinary)
+{
+    std::ifstream *ifs = nullptr;
+
+    std::string name(filename);
+    if (isStdin(name))
+        return &std::cin;
+
+    if (!FileUtils::fileExists(name))
+        return nullptr;
+
+    std::ios::openmode mode = std::ios::in;
+    if (asBinary)
+        mode |= std::ios::binary;
+
+    ifs = new std::ifstream(name, mode);
+    if (!ifs->good())
+    {
+        delete ifs;
+        return nullptr;
+    }
+    return ifs;
+}
+
+
+std::ostream *createFile(std::string const& name, bool asBinary)
+{
+    if (isStdout(name))
+        return &std::cout;
+
+    std::ios::openmode mode = std::ios::out;
+    if (asBinary)
+        mode |= std::ios::binary;
+
+    std::ostream *ofs = new std::ofstream(name, mode);
+    if (!ofs->good())
+    {
+        delete ofs;
+        return nullptr;
+    }
+    return ofs;
+}
+
+
+bool directoryExists(const std::string& dirname)
+{
+    //ABELL - Seems we should be calling is_directory
+    return pdalboost::filesystem::exists(dirname);
+}
+
+
+bool createDirectory(const std::string& dirname)
+{
+    return pdalboost::filesystem::create_directory(dirname);
+}
+
+
+void deleteDirectory(const std::string& dirname)
+{
+    pdalboost::filesystem::remove_all(dirname);
+}
+
+
+std::vector<std::string> directoryList(const std::string& dir)
+{
+    std::vector<std::string> files;
+
+    pdalboost::filesystem::directory_iterator it(dir);
+    pdalboost::filesystem::directory_iterator end;
+    while (it != end)
+    {
+        files.push_back(it->path().string());
+        it++;
+    }
+    return files;
+}
+
+
+void closeFile(std::ostream *out)
+{
+    // An ofstream is closeable and deletable, but
+    // an ostream like &cout isn't.
+    if (!out)
+        return;
+    std::ofstream *ofs = dynamic_cast<std::ofstream *>(out);
+    if (ofs)
+    {
+        ofs->close();
+        delete ofs;
+    }
+}
+
+
+void closeFile(std::istream* in)
+{
+    // An ifstream is closeable and deletable, but
+    // an istream like &cin isn't.
+    if (!in)
+        return;
+    std::ifstream *ifs = dynamic_cast<std::ifstream *>(in);
+    if (ifs)
+    {
+        ifs->close();
+        delete ifs;
+    }
+}
+
+
+bool deleteFile(const std::string& file)
+{
+    return pdalboost::filesystem::remove(file);
+}
+
+
+void renameFile(const std::string& dest, const std::string& src)
+{
+    pdalboost::filesystem::rename(src, dest);
+}
+
+
+bool fileExists(const std::string& name)
+{
+    if (isStdin(name))
+        return true;
+
+    try
+    {
+        return pdalboost::filesystem::exists(name);
+    }
+    catch (pdalboost::filesystem::filesystem_error)
+    {
+    }
+    return false;
+}
+
+
+uintmax_t fileSize(const std::string& file)
+{
+    return pdalboost::filesystem::file_size(file);
+}
+
+
+std::string readFileIntoString(const std::string& filename)
+{
+    std::string str;
+
+    std::istream* stream = openFile(filename, false);
+    if (stream)
+    {
+        str.assign((std::istreambuf_iterator<char>(*stream)),
+            std::istreambuf_iterator<char>());
+        closeFile(stream);
+    }
+    return str;
+}
+
+
+std::string getcwd()
+{
+    const pdalboost::filesystem::path p = pdalboost::filesystem::current_path();
+    return addTrailingSlash(p.string());
+}
+
+
+/***
+// Non-boost alternative.  Requires file existence.
+std::string toAbsolutePath(const std::string& filename)
+{
+    std::string result;
+
+#ifdef WIN32
+    char buf[MAX_PATH]
+    if (GetFullPathName(filename.c_str(), MAX_PATH, buf, NULL))
+        result = buf;
+#else
+    char buf[PATH_MAX];
+    if (realpath(filename.c_str(), buf))
+        result = buf;
+#endif
+    return result;
+}
+***/
+
+// if the filename is an absolute path, just return it
+// otherwise, make it absolute (relative to current working dir) and return that
+std::string toAbsolutePath(const std::string& filename)
+{
+    return pdalboost::filesystem::absolute(filename).string();
+}
+
+
+// if the filename is an absolute path, just return it
+// otherwise, make it absolute (relative to base dir) and return that
+//
+// note: if base dir is not absolute, first make it absolute via
+// toAbsolutePath(base)
+std::string toAbsolutePath(const std::string& filename, const std::string base)
+{
+    const std::string newbase = toAbsolutePath(base);
+    return pdalboost::filesystem::absolute(filename, newbase).string();
+}
+
+std::string getFilename(const std::string& path)
+{
+#ifdef _WIN32
+    std::string pathsep("\\/");
+#else
+    char pathsep = '/';
+#endif
+
+    std::string::size_type pos = path.find_last_of(pathsep);
+    if (pos == std::string::npos)
+        return path;
+    return path.substr(pos + 1);
+}
+
+
+// Get the directory part of a filename.
+std::string getDirectory(const std::string& path)
+{
+    const pdalboost::filesystem::path dir =
+         pdalboost::filesystem::path(path).parent_path();
+    return addTrailingSlash(dir.string());
+}
+
+
+std::string stem(const std::string& path)
+{
+    std::string f = getFilename(path);
+    if (f != "." && f != "..")
+    {
+        std::string::size_type pos = f.find_last_of(".");
+        if (pos != std::string::npos)
+            f = f.substr(0, pos);
+    }
+    return f;
+}
+
+
+// Determine if the path represents a directory.
+bool isDirectory(const std::string& path)
+{
+    return pdalboost::filesystem::is_directory(path);
+}
+
+// Determine if the path is an absolute path
+bool isAbsolutePath(const std::string& path)
+{
+    return pdalboost::filesystem::path(path).is_absolute();
+}
+
+
+void fileTimes(const std::string& filename, struct tm *createTime,
+    struct tm *modTime)
+{
+#ifdef WIN32
+    struct _stat statbuf;
+    _stat(filename.c_str(), &statbuf);
+
+    if (createTime)
+        *createTime = *gmtime(&statbuf.st_ctime);
+    if (modTime)
+        *modTime = *gmtime(&statbuf.st_mtime);
+#else
+    struct stat statbuf;
+    stat(filename.c_str(), &statbuf);
+
+    if (createTime)
+        gmtime_r(&statbuf.st_ctime, createTime);
+    if (modTime)
+        gmtime_r(&statbuf.st_mtime, modTime);
+#endif
+}
+
+
+std::string extension(const std::string& filename)
+{
+    auto idx = filename.find_last_of('.');
+    if (idx == std::string::npos)
+        return std::string();
+    return filename.substr(idx);
+}
+
+
+std::vector<std::string> glob(std::string path)
+{
+    std::vector<std::string> filenames;
+#ifdef WIN32
+    WIN32_FIND_DATA ffd;
+    HANDLE handle = FindFirstFile(path.c_str(), &ffd);
+
+    if (INVALID_HANDLE_VALUE == handle)
+        return filenames;
+
+    size_t found = path.find_last_of("/\\");
+    do
+    {
+        if (found == std::string::npos)
+            filenames.push_back(ffd.cFileName);
+        else
+            filenames.push_back(path.substr(0, found) + "\\" + ffd.cFileName);
+
+    } while (FindNextFile(handle, &ffd) != 0);
+    FindClose(handle);
+#else
+    glob_t glob_result;
+
+    ::glob(path.c_str(), GLOB_NOSORT, NULL, &glob_result);
+    for (unsigned int i = 0; i < glob_result.gl_pathc; ++i)
+    {
+        std::string filename = glob_result.gl_pathv[i];
+        filenames.push_back(filename);
+    }
+    globfree(&glob_result);
+#endif
+    return filenames;
+}
+
+} // namespace FileUtils
+
+} // namespace pdal
+
diff --git a/pdal/util/FileUtils.hpp b/pdal/util/FileUtils.hpp
new file mode 100644
index 0000000..4cf86ef
--- /dev/null
+++ b/pdal/util/FileUtils.hpp
@@ -0,0 +1,254 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <cassert>
+#include <cmath>
+#include <cstdint>
+#include <istream>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+
+#include "pdal_util_export.hpp"
+
+namespace pdal
+{
+
+namespace FileUtils
+{
+    /**
+      Open an existing file for reading.
+
+      \param filename  Filename.
+      \param asBinary  Read as binary file (don't convert /r/n to /n)
+      \return  Pointer to opened stream.
+    */
+    PDAL_DLL std::istream* openFile(std::string const& filename,
+        bool asBinary=true);
+
+    /**
+      Create a file and open for writing.
+
+      \param filename  Filename.
+      \param asBinary  Write as binary file (don't convert /n to /r/n)
+      \return  Point to opened stream.
+    */
+    PDAL_DLL std::ostream* createFile(std::string const& filename,
+        bool asBinary=true);
+
+    /**
+      Determine if a directory exists.
+
+      \param dirname  Name of directory.
+      \return  Whether a directory exists.
+    */
+    PDAL_DLL bool directoryExists(const std::string& dirname);
+
+    /**
+      Create a directory.
+
+      \param dirname  Directory name.
+      \return  Whether the directory was created.
+    */
+    PDAL_DLL bool createDirectory(const std::string& dirname);
+
+    /**
+      Delete a directory and its contents.
+
+      \param dirname  Directory name.
+    */
+    PDAL_DLL void deleteDirectory(const std::string& dirname);
+
+    /**
+      List the contents of a directory.
+
+      \param dirname  Name of directory to list.
+      \return  List of entries in the directory.
+    */
+    PDAL_DLL std::vector<std::string> directoryList(const std::string& dirname);
+
+    /**
+      Close a file created with createFile.
+
+      \param ofs  Pointer to stream to close.
+    */
+    PDAL_DLL void closeFile(std::ostream* ofs);
+
+    /**
+      Close a file created with openFile.
+
+      \param ifs  Pointer to stream to close.
+    */
+    PDAL_DLL void closeFile(std::istream* ifs);
+
+    /**
+      Delete a file.
+
+      \param filename  Name of file to delete.
+      \return  \c true if successful, \c false otherwise
+    */
+    PDAL_DLL bool deleteFile(const std::string& filename);
+
+    /**
+      Rename a file.
+
+      \param dest  Desired filename.
+      \param src   Source filename.
+    */
+    PDAL_DLL void renameFile(const std::string& dest, const std::string& src);
+
+    /**
+      Determine if a file exists.
+
+      \param  Filename.
+      \return  Whether the file exists.
+    */
+    PDAL_DLL bool fileExists(const std::string& filename);
+
+    /**
+      Get the size of a file.
+
+      \param filename  Filename.
+      \return  Size of file.
+    */
+    PDAL_DLL uintmax_t fileSize(const std::string& filename);
+
+    /**
+      Read a file into a string.
+
+      \param filename  Filename.
+      \return  File contents as a string
+    */
+    PDAL_DLL std::string readFileIntoString(const std::string& filename);
+
+    /**
+      Get the current working directory with trailing separator.
+
+      \return  The current working directory.
+    */
+    PDAL_DLL std::string getcwd();
+
+    /**
+      Return the file component of the given path,
+      e.g. "d:/foo/bar/a.c" -> "a.c"
+
+      \param path  Path from which to extract file component.
+      \return  File part of path.
+    */
+    PDAL_DLL std::string getFilename(const std::string& path);
+
+    /**
+      Return the directory component of the given path,
+      e.g. "d:/foo/bar/a.c" -> "d:/foo/bar/"
+
+      \param path  Path from which to extract directory component.
+      \return  Directory part of path.
+    */
+    PDAL_DLL std::string getDirectory(const std::string& path);
+
+    /**
+      Determine if the path is an absolute path.
+
+      \param path  Path to test.
+      \return  Whether the path is absolute.
+    */
+    PDAL_DLL bool isAbsolutePath(const std::string& path);
+
+    /**
+      Determine if path is a directory.
+
+      \param path  Directory to check.
+      \return  Whether the path represents a directory.
+    */
+    PDAL_DLL bool isDirectory(const std::string& path);
+
+    /**
+      If the filename is an absolute path, just return it otherwise,
+      make it absolute (relative to current working dir) and return it.
+
+      \param filename  Name of file to convert to absolute path.
+      \return  Absolute version of provided filename.
+    */
+    PDAL_DLL std::string toAbsolutePath(const std::string& filename);
+
+    /**
+      If the filename is an absolute path, just return it otherwise,
+      make it absolute (relative to base dir) and return that.
+
+      \param filename  Name of file to convert to absolute path.
+      \param base  Base name to use.
+      \return  Absolute version of provided filename relative to base.
+    */
+    PDAL_DLL std::string toAbsolutePath(const std::string& filename,
+        const std::string base);
+    
+    /**
+      Get the file creation and modification times.
+
+      \param filename  Filename.
+      \param createTime  Pointer to creation time structure.
+      \param modTime  Pointer to modification time structure.
+    */
+    PDAL_DLL void fileTimes(const std::string& filename, struct tm *createTime,
+        struct tm *modTime);
+
+    /**
+      Return the extension of the filename, including the separator (.).
+
+      \param path  File path from which to extract extension.
+      \return  Extension of filename.
+    */
+    PDAL_DLL std::string extension(const std::string& path);
+
+    /**
+      Return the filename stripped of the extension.  . and .. are returned
+      unchanged.
+
+      \param path  File path from which to extract file stem.
+      \return  Stem of filename.
+    */
+    PDAL_DLL std::string stem(const std::string& path);
+
+    /**
+      Expand a filespec to a list of files.
+
+      \param filespec  File specification to expand.
+      \return  List of files that correspond to provided file specification.
+    */
+    PDAL_DLL std::vector<std::string> glob(std::string filespec);
+}
+
+} // namespace pdal
diff --git a/src/util/Georeference.cpp b/pdal/util/Georeference.cpp
similarity index 100%
rename from src/util/Georeference.cpp
rename to pdal/util/Georeference.cpp
diff --git a/include/pdal/util/Georeference.hpp b/pdal/util/Georeference.hpp
similarity index 100%
rename from include/pdal/util/Georeference.hpp
rename to pdal/util/Georeference.hpp
diff --git a/pdal/util/IStream.hpp b/pdal/util/IStream.hpp
new file mode 100644
index 0000000..dd819eb
--- /dev/null
+++ b/pdal/util/IStream.hpp
@@ -0,0 +1,550 @@
+/******************************************************************************
+* Copyright (c) 2014, Andrew Bell
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#include <cassert>
+#include <fstream>
+#include <memory>
+#include <stack>
+#include <vector>
+#include <cstring>
+
+#include "portable_endian.hpp"
+#include "pdal_util_export.hpp"
+
+namespace pdal
+{
+
+class IStreamMarker;
+
+/**
+  Stream wrapper for input of binary data.
+*/
+class IStream
+{
+public:
+    /**
+      Default constructor.
+    */
+    PDAL_DLL IStream() : m_stream(NULL), m_fstream(NULL)
+        {}
+
+    /**
+      Construct an IStream from a filename.
+
+      \param filename  File from which to read.
+    */
+    PDAL_DLL IStream(const std::string& filename) :
+        m_stream(NULL), m_fstream(NULL)
+    { open(filename); }
+
+    /**
+      Construct an IStream from an input stream pointer.
+
+      \param stream  Stream from which to read.
+    */
+    PDAL_DLL IStream(std::istream *stream) : m_stream(stream), m_fstream(NULL)
+        {}
+
+    PDAL_DLL ~IStream()
+        { delete m_fstream; }
+
+    /**
+      Open a file to extract.
+
+      \param filename  Filename.
+      \return  -1 if a stream is already assigned, 0 otherwise.
+    */
+    PDAL_DLL int open(const std::string& filename)
+    {
+        if (m_stream)
+             return -1;
+        m_stream = m_fstream = new std::ifstream(filename,
+            std::ios_base::in | std::ios_base::binary);
+        return 0;
+    }
+
+    /**
+      Close the underlying stream.
+    */
+    PDAL_DLL void close()
+    {
+        delete m_fstream;
+        m_fstream = NULL;
+        m_stream = NULL;
+    }
+
+    /**
+      Return the state of the stream.
+
+      \return  The state of the underlying stream.
+    */
+    PDAL_DLL operator bool ()
+        { return (bool)(*m_stream); }
+
+    /**
+      Seek to a position in the underlying stream.
+
+      \param pos  Position to seek to,
+    */
+    PDAL_DLL void seek(std::streampos pos)
+        { m_stream->seekg(pos, std::istream::beg); }
+
+
+    /**
+      Seek to an offset from a specified position.
+
+      \param off  Offset.
+      \param way  Absolute position for offset (beg, end or cur)
+    */
+    PDAL_DLL void seek(std::streampos off, std::ios_base::seekdir way)
+        { m_stream->seekg(off, way); }
+
+    /**
+      Skip relative to the current position.
+
+      \param offset  Offset from the current position.
+    */
+    PDAL_DLL void skip(std::streamoff offset)
+        { m_stream->seekg(offset, std::istream::cur); }
+
+    /**
+      Determine the position of the get pointer.
+
+      \return  Current get position.
+    */
+    PDAL_DLL std::streampos position() const
+        { return m_stream->tellg(); }
+
+    /**
+      Determine if the underlying stream is good.
+
+      \return  Whether the underlying stream is good.
+    */
+    PDAL_DLL bool good() const
+        { return m_stream->good(); }
+
+    /**
+      Fetch a pointer to the underlying stream.
+
+      \return  Pointer to the underlying stream.
+    */
+    PDAL_DLL std::istream *stream()
+        { return m_stream; }
+
+    /**
+      Temporarily push a stream to read from.
+
+      \param strm  New stream to read from.
+    */
+    PDAL_DLL void pushStream(std::istream *strm)
+    {
+        m_streams.push(m_stream);
+        m_stream = strm;
+    }
+
+    /**
+      Pop the current stream and return it.  The last stream on the stack
+      cannot be popped.
+
+      \return  Pointer to the popped stream.
+    */
+    PDAL_DLL std::istream *popStream()
+    {
+        // Can't pop the last stream for now.
+        if (m_streams.empty())
+            return nullptr;
+        std::istream *strm = m_stream;
+        m_stream = m_streams.top();
+        m_streams.pop();
+        return strm;
+    }
+
+    /**
+      Fetch data from the stream into a string.
+
+      \param s  String to fill.
+      \param size  Number of bytes to extract.
+    */
+    PDAL_DLL void get(std::string& s, size_t size)
+    {
+        // Could do this by appending to a string with a stream, but this
+        // is probably fast enough for now (there's only a simple increment
+        // to advance an istream iterator, which you'd have to call in a loop).
+        std::unique_ptr<char[]> buf(new char[size+1]);
+        m_stream->read(buf.get(), size);
+        buf[size] = '\0';
+        s = buf.get();
+    }
+
+    /**
+      Fetch data from the stream into a vector of char.
+
+      \param buf  Buffer to fill.
+    */
+    PDAL_DLL void get(std::vector<char>& buf) {
+        assert(buf.size() != 0);
+        m_stream->read((char *)&buf[0], buf.size());
+    }
+
+    /**
+      Fetch data from the stream into a vector of unsigned char.
+
+      \param buf  Buffer to fill.
+    */
+    PDAL_DLL void get(std::vector<unsigned char>& buf) {
+        assert(buf.size() != 0);
+        m_stream->read((char *)&buf[0], buf.size());
+    }
+
+    /**
+      Fetch data from the stream into the specified buffer of char.
+
+      \param buf  Buffer to fill.
+      \param size  Number of bytes to extract.
+    */
+    PDAL_DLL void get(char *buf, size_t size)
+        { m_stream->read(buf, size); }
+
+    /**
+      Fetch data from the stream into the specified buffer of unsigned char.
+
+      \param buf  Buffer to fill.
+      \param size  Number of bytes to extract.
+    */
+    PDAL_DLL void get(unsigned char *buf, size_t size)
+        { m_stream->read((char *)buf, size); }
+
+protected:
+    std::istream *m_stream;
+    std::ifstream *m_fstream; // Dup of above to facilitate cleanup.
+
+private:
+    std::stack<std::istream *> m_streams;
+	IStream(const IStream&);
+};
+
+/**
+  Stream wrapper for input of binary data that converts from little-endian
+  to host ordering.
+*/
+class ILeStream : public IStream
+{
+public:
+    /**
+      Default constructor.
+    */
+    PDAL_DLL ILeStream()
+    {}
+
+    /**
+      Constructor that opens the file and maps it to a stream.
+
+      \param filename  Filename.
+    */
+    PDAL_DLL ILeStream(const std::string& filename) : IStream(filename)
+    {}
+
+    /**
+      Constructor that maps to a provided stream.
+
+      \param stream  Stream to extract from.
+    */
+    PDAL_DLL ILeStream(std::istream *stream) : IStream(stream)
+    {}
+
+    /**
+      Extract an unsigned byte from the stream.
+
+      \param v  unsigned byte to populate
+      \return  This stream.
+    */
+    PDAL_DLL ILeStream& operator >> (uint8_t& v)
+    {
+        v = (uint8_t)m_stream->get();
+        return *this;
+    }
+
+    /**
+      Extract an unsigned byte from the stream.
+
+      \param v  unsigned byte to populate
+      \return  This stream.
+    */
+    PDAL_DLL ILeStream& operator >> (int8_t& v)
+    {
+        v = (int8_t)m_stream->get();
+        return *this;
+    }
+
+    /**
+      Extract an unsigned short from the stream.
+
+      \param v  unsigned short to populate
+      \return  This stream.
+    */
+    PDAL_DLL ILeStream& operator >> (uint16_t& v)
+    {
+        m_stream->read((char *)&v, sizeof(v));
+        v = le16toh(v);
+        return *this;
+    }
+
+    /**
+      Extract an short from the stream.
+
+      \param v  short to populate
+      \return  This stream.
+    */
+    PDAL_DLL ILeStream& operator >> (int16_t& v)
+    {
+        m_stream->read((char *)&v, sizeof(v));
+        v = (int16_t)le16toh((uint16_t)v);
+        return *this;
+    }
+
+    /**
+      Extract an unsigned int from the stream.
+
+      \param v  unsigned int to populate
+      \return  This stream.
+    */
+    PDAL_DLL ILeStream& operator >> (uint32_t& v)
+    {
+        m_stream->read((char *)&v, sizeof(v));
+        v = le32toh(v);
+        return *this;
+    }
+
+    /**
+      Extract an int from the stream.
+
+      \param v  int to populate
+      \return  This stream.
+    */
+    PDAL_DLL ILeStream& operator >> (int32_t& v)
+    {
+        m_stream->read((char *)&v, sizeof(v));
+        v = (int32_t)le32toh((uint32_t)v);
+        return *this;
+    }
+
+    /**
+      Extract an unsigned long int from the stream.
+
+      \param v  unsigned long int to populate
+      \return  This stream.
+    */
+    PDAL_DLL ILeStream& operator >> (uint64_t& v)
+    {
+        m_stream->read((char *)&v, sizeof(v));
+        v = le64toh(v);
+        return *this;
+    }
+
+    /**
+      Extract a long int from the stream.
+
+      \param v  long int to populate
+      \return  This stream.
+    */
+    PDAL_DLL ILeStream& operator >> (int64_t& v)
+    {
+        m_stream->read((char *)&v, sizeof(v));
+        v = (int64_t)le64toh((uint64_t)v);
+        return *this;
+    }
+
+    /**
+      Extract a float from the stream.
+
+      \param v  float to populate
+      \return  This stream.
+    */
+    PDAL_DLL ILeStream& operator >> (float& v)
+    {
+        m_stream->read((char *)&v, sizeof(v));
+        uint32_t tmp = le32toh(*(uint32_t *)(&v));
+        std::memcpy(&v, &tmp, sizeof(tmp));
+        return *this;
+    }
+
+    /**
+      Extract a double from the stream.
+
+      \param v  double to populate
+      \return  This stream.
+    */
+    PDAL_DLL ILeStream& operator >> (double& v)
+    {
+        m_stream->read((char *)&v, sizeof(v));
+        uint64_t tmp = le64toh(*(uint64_t *)(&v));
+        std::memcpy(&v, &tmp, sizeof(tmp));
+        return *this;
+    }
+};
+
+
+/**
+  Stream wrapper for input of binary data that converts from either
+  little-endian or big-endian to host ordering, depending on object
+  settings.
+*/
+class ISwitchableStream : public IStream
+{
+public:
+    static const bool DefaultIsLittleEndian = true;
+
+    PDAL_DLL ISwitchableStream() : m_isLittleEndian(DefaultIsLittleEndian)
+    {}
+
+    PDAL_DLL ISwitchableStream(const std::string& filename)
+        : IStream(filename)
+        , m_isLittleEndian(DefaultIsLittleEndian)
+    {}
+    
+    PDAL_DLL ISwitchableStream(std::istream* stream)
+        : IStream(stream)
+        , m_isLittleEndian(DefaultIsLittleEndian)
+    {}
+
+    PDAL_DLL bool isLittleEndian() const
+        { return m_isLittleEndian; }
+    PDAL_DLL void switchToLittleEndian()
+        { m_isLittleEndian = true; }
+    PDAL_DLL void switchToBigEndian()
+        { m_isLittleEndian = false; }
+
+    PDAL_DLL ISwitchableStream& operator>>(uint8_t& v)
+    {
+        v = (uint8_t)m_stream->get();
+        return *this;
+    }
+
+    PDAL_DLL ISwitchableStream& operator>>(int8_t& v)
+    {
+        v = (int8_t)m_stream->get();
+        return *this;
+    }
+
+    PDAL_DLL ISwitchableStream& operator>>(uint16_t& v)
+    {
+        m_stream->read((char*)&v, sizeof(v));
+        v = isLittleEndian() ? le16toh(v) : be16toh(v);
+        return *this;
+    }
+
+    PDAL_DLL ISwitchableStream& operator>>(int16_t& v)
+    {
+        m_stream->read((char*)&v, sizeof(v));
+        v = isLittleEndian() ? (int16_t)le16toh((uint16_t)v)
+                             : (int16_t)be16toh((uint16_t)v);
+        return *this;
+    }
+
+    PDAL_DLL ISwitchableStream& operator>>(uint32_t& v)
+    {
+        m_stream->read((char*)&v, sizeof(v));
+        v = isLittleEndian() ? le32toh(v) : be32toh(v);
+        return *this;
+    }
+
+    PDAL_DLL ISwitchableStream& operator>>(int32_t& v)
+    {
+        m_stream->read((char*)&v, sizeof(v));
+        v = isLittleEndian() ? (int32_t)le32toh((uint32_t)v)
+                             : (int32_t)be32toh((uint32_t)v);
+        return *this;
+    }
+
+    PDAL_DLL ISwitchableStream& operator>>(uint64_t& v)
+    {
+        m_stream->read((char*)&v, sizeof(v));
+        v = isLittleEndian() ? le64toh(v) : be64toh(v);
+        return *this;
+    }
+
+    PDAL_DLL ISwitchableStream& operator>>(int64_t& v)
+    {
+        m_stream->read((char*)&v, sizeof(v));
+        v = isLittleEndian() ? (int64_t)le64toh((uint64_t)v)
+                             : (int64_t)be64toh((uint64_t)v);
+        return *this;
+    }
+
+    PDAL_DLL ISwitchableStream& operator>>(float& v)
+    {
+        m_stream->read((char*)&v, sizeof(v));
+        uint32_t tmp = isLittleEndian() ? le32toh(*(uint32_t*)(&v))
+                                        : be32toh(*(uint32_t*)(&v));
+        std::memcpy(&v, &tmp, sizeof(tmp));
+        return *this;
+    }
+
+    PDAL_DLL ISwitchableStream& operator>>(double& v)
+    {
+        m_stream->read((char*)&v, sizeof(v));
+        uint64_t tmp = isLittleEndian() ? be64toh(*(uint64_t*)(&v))
+                                        : be64toh(*(uint64_t*)(&v));
+        std::memcpy(&v, &tmp, sizeof(tmp));
+        return *this;
+    }
+
+private:
+    bool m_isLittleEndian;
+};
+
+
+/// Stream position marker with rewinding support.
+class IStreamMarker
+{
+public:
+    PDAL_DLL IStreamMarker(IStream& stream) : m_stream(stream)
+        { m_pos = m_stream.position(); }
+
+    PDAL_DLL void rewind()
+        { m_stream.seek(m_pos); }
+
+private:
+    std::streampos m_pos;
+    IStream& m_stream;
+	IStreamMarker(const IStreamMarker&);
+    IStreamMarker& operator=(const IStreamMarker&); // not implemented
+};
+
+} // namespace pdal
diff --git a/include/pdal/util/Inserter.hpp b/pdal/util/Inserter.hpp
similarity index 100%
rename from include/pdal/util/Inserter.hpp
rename to pdal/util/Inserter.hpp
diff --git a/include/pdal/util/OStream.hpp b/pdal/util/OStream.hpp
similarity index 100%
rename from include/pdal/util/OStream.hpp
rename to pdal/util/OStream.hpp
diff --git a/pdal/util/ProgramArgs.hpp b/pdal/util/ProgramArgs.hpp
new file mode 100644
index 0000000..f4cba0e
--- /dev/null
+++ b/pdal/util/ProgramArgs.hpp
@@ -0,0 +1,1591 @@
+/******************************************************************************
+* Copyright (c) 2016, Hobu Inc., hobu at hobu.co
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+****************************************************************************/
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <pdal/util/Utils.hpp>
+
+namespace pdal
+{
+
+class arg_error
+{
+public:
+    arg_error(const std::string& error) : m_error(error)
+    {}
+
+    std::string what() const
+        { return m_error; }
+
+    std::string m_error;
+};
+
+// Specifically, an error in the argument's value.
+class arg_val_error : public arg_error
+{
+public:
+    arg_val_error(const std::string& error) : arg_error(error)
+    {}
+};
+
+namespace
+{
+
+class ArgValList
+{
+    struct ArgVal
+    {
+        std::string m_val;
+        bool m_consumed;
+
+        ArgVal(const std::string& s) :
+            m_val(s), m_consumed(false)
+        {}
+    };
+
+public:
+    ArgValList(const std::vector<std::string>& slist) : m_unconsumedStart(0)
+    {
+        for (const std::string& s : slist)
+            add(s);
+    }
+
+    void add(const std::string& s)
+    {
+        if (s.empty())
+            return;
+
+        // Turn a short arg list into a set of short args: -afv -> -a -f -v
+        // so that each argval represents a single arg.
+        if (s.size() > 1 && s[0] == '-' && s[1] != '-')
+            for (size_t i = 1; i < s.size(); i++)
+                m_vals.push_back({std::string("-") + s[i]});
+        else
+            m_vals.push_back({s});
+    }
+
+    void consume(size_t i)
+    {
+        m_vals[i].m_consumed = true;
+        if (i == m_unconsumedStart)
+            while (i < m_vals.size() - 1 && consumed(++i))
+                m_unconsumedStart++;
+    }
+
+    std::vector<std::string> unconsumedArgs() const
+    {
+        std::vector<std::string> remainingVals;
+
+        for (size_t i = firstUnconsumed(); i < size(); ++i)
+            if (!consumed(i))
+                remainingVals.push_back(m_vals[i].m_val);
+        return remainingVals;
+    }
+
+    size_t size() const
+        { return m_vals.size(); }
+    const std::string& operator[](size_t i) const
+        { return m_vals[i].m_val; }
+    bool consumed(size_t i) const
+        { return m_vals[i].m_consumed; }
+    size_t firstUnconsumed() const
+        { return m_unconsumedStart; }
+private:
+    std::vector<ArgVal> m_vals;
+    size_t m_unconsumedStart;
+};
+
+} // unnamed namespace
+
+
+/**
+   Description of an argument that can be parsed by \class ProgramArgs.
+
+   Stores information about each argument including the required "longname",
+   an optional single-character shortname, a description, and an indicator
+   of the positional-type of the argument.
+*/
+class Arg
+{
+public:
+/**
+  Positional type.  Either None, Optional or Required.
+*/
+enum class PosType
+{
+    None,       ///< Not positional
+    Required,   ///< Required positional
+    Optional    ///< Optional positional
+};
+
+protected:
+    /**
+      Constructor.
+
+      \param longname  Name of argument specified on command line with "--"
+        prefix.
+      \param shortname  Optional name of argument specified on command
+        line with "-" prefix.
+      \param description  Argument description.
+    */
+    Arg(const std::string& longname, const std::string& shortname,
+        const std::string& description) : m_longname(longname),
+        m_shortname(shortname), m_description(description), m_set(false),
+        m_hidden(false), m_positional(PosType::None)
+    {}
+
+public:
+    /**
+      Indicate that the argument shouldn't be shown in help text.
+
+      \param hidden  Whether the argument should be hidden or not
+        [default: true].
+      \return  A reference to this \class Arg, to allow the function
+        call to be chained.
+    */
+    Arg& setHidden(bool hidden = true)
+    {
+        m_hidden = true;
+        return *this;
+    }
+    /**
+      Indicate that the argument is positional.
+
+      Positional arguments may be specified on the command line without
+      any argument name.  Such arguments are required to be specified
+      either with the argument name as a normal option or positionally.
+      Missing positional arguments will raise an exception when the
+      command line is parsed
+    */
+    virtual Arg& setPositional()
+    {
+        m_positional = PosType::Required;
+        return *this;
+    }
+    /**
+      Indicate that the argument is positional and optional.
+
+      Positional arguments may be specified on the command line without
+      any argument name.  Optional positional arguments must be added to
+      \class ProgramArgs after any non-optional arguments.  If optional
+      positional arguments are not found, no exception is raised when
+      the command line is parsed.
+    */
+    virtual Arg& setOptionalPositional()
+    {
+        m_positional = PosType::Optional;
+        return *this;
+    }
+    /**
+      Provide error text for the argument to override the default.
+
+      \param error  Error text.
+    */
+    virtual Arg& setErrorText(const std::string& error)
+    {
+        m_error = error;
+        return *this;
+    }
+    /**
+      Return whether the argument was set during command-line parsing.
+    */
+    bool set() const
+        { return m_set; }
+    /**
+      Return whether a default value was provided for the argument.
+
+      \return  Whether a default was provided.
+    */
+    virtual bool defaultProvided() const
+        { return false; }
+    /**
+      Return a string representation of an Arg's default value, or an
+      empty string if none exists.
+
+      \return  Default value as a string.
+    */
+    virtual std::string defaultVal() const
+        { return std::string(); }
+
+public:
+    /**
+      Return whether an option needs a value to be valid.  Generally true
+      for all options not bound to boolean values.
+      \note  Not intended to be called from user code.
+    */
+    virtual bool needsValue() const
+        { return true; }
+
+    /**
+      Set a an argument's value from a string.
+
+      Throws an arg_error exception if \a s can't be converted to
+      the argument's type.
+      \note  Not intended to be called from user code.
+
+      \param s  Value to set.
+    */
+    virtual void setValue(const std::string& s) = 0;
+
+    /**
+      Reset the argument's state.
+
+      Set the internal state of the argument and it's referenced variable
+      as if no command-line parsing had occurred.
+      \note  For testing.  Not intended to be called from user code.
+    */
+    virtual void reset() = 0;
+
+    /**
+      Set the argument's value from the list of command-line args.
+      \note  Not intended to be called from user code.
+
+      \param vals  The list of command-line argument values.
+    */
+    virtual void assignPositional(ArgValList& vals)
+    {}
+
+    /**
+      Returns the positional type of the argument.
+      \note  Not intended to be called from user code.
+    */
+    PosType positional() const
+        { return m_positional; }
+
+    /**
+      Returns whether the argument is hidden or not.
+      \note  Not intended to be called from user code.
+    */
+    bool hidden() const
+        { return m_hidden; }
+
+    /**
+      Returns the description of the argument.
+      \note  Not intended to be called from user code.
+      \return  Argument description.
+    */
+    std::string description() const
+        { return m_description; }
+
+    /**
+      Returns the longname of the argument.
+      \note  Not intended to be called from user code.
+      \return  Argument long name.
+    */
+    std::string longname() const
+        { return m_longname; }
+
+    /**
+      Returns text indicating the longname and shortname of the option
+      suitable for displaying in help information.
+      \note  Not intended to be called from user code.
+    */
+    std::string nameDescrip() const
+    {
+        std::string s("--");
+        s += m_longname;
+        if (m_shortname.size())
+            s += ", -" + m_shortname;
+        return s;
+    }
+    /**
+      Returns text indicating the name of the option suitable for displaying
+      in "usage" text.
+      \note  Not intended to be called from user code.
+    */
+    std::string commandLine() const
+    {
+        std::string s;
+        if (m_positional == PosType::Required)
+            s =  m_longname;
+        else if (m_positional == PosType::Optional)
+            s += '[' + m_longname + ']';
+        return s;
+    }
+
+protected:
+    std::string m_longname;
+    std::string m_shortname;
+    std::string m_description;
+    std::string m_rawVal;
+    bool m_set;
+    bool m_hidden;
+    PosType m_positional;
+    std::string m_error;
+};
+
+/**
+  Description of an argument.  Boolean arguments and vector (list-based)
+  arguments are handled separately.
+*/
+template <typename T>
+class TArg : public Arg
+{
+public:
+    /**
+      Constructor that takes a default argument.
+
+      \param longname  Name of argument specified on command line with "--"
+        prefix.
+      \param shortname  Optional name of argument specified on command
+        line with "-" prefix.
+      \param description  Argument description.
+      \param variable  Variable to which the value of the argument should
+        be bound.
+      \param def  Default value to be assigned to the bound variable.
+    */
+    TArg(const std::string& longname, const std::string& shortname,
+        const std::string& description, T& variable, T def) :
+        Arg(longname, shortname, description), m_var(variable),
+        m_defaultVal(def), m_defaultProvided(true)
+    { m_var = m_defaultVal; }
+
+    /**
+      Constructor.
+
+      \param longname  Name of argument specified on command line with "--"
+        prefix.
+      \param shortname  Optional name of argument specified on command
+        line with "-" prefix.
+      \param description  Argument description.
+      \param variable  Variable to which the value of the argument should
+        be bound.
+    */
+    TArg(const std::string& longname, const std::string& shortname,
+        const std::string& description, T& variable) :
+        Arg(longname, shortname, description), m_var(variable),
+        m_defaultVal(T()), m_defaultProvided(false)
+    { m_var = m_defaultVal; }
+
+    /**
+      Set a an argument's value from a string.
+
+      Throws an arg_error exception if \a s can't be converted to
+      the argument's type.  Values must be provided for with the
+      option name.
+      \note  Not intended to be called from user code.
+
+      \param s  Value to set.
+    */
+    virtual void setValue(const std::string& s)
+    {
+        if (m_set)
+        {
+            std::ostringstream oss;
+            oss << "Attempted to set value twice for argument '" <<
+                m_longname << "'.";
+            throw arg_val_error(oss.str());
+        }
+        if (s.empty())
+        {
+            std::stringstream oss;
+            oss << "Argument '" << m_longname << "' needs a value and none "
+                "was provided.";
+            throw arg_val_error(oss.str());
+        }
+        m_rawVal = s;
+        if (!Utils::fromString(s, m_var))
+        {
+            std::ostringstream oss;
+            if (m_error.size())
+                throw arg_val_error(m_error);
+            else
+            {
+                oss << "Invalid value '" << s << "' for argument '" <<
+                    m_longname << "'.";
+                throw arg_val_error(oss.str());
+            }
+        }
+        m_set = true;
+    }
+
+    /**
+      Reset the argument's state.
+
+      Set the interval state of the argument and it's referenced variable
+      as if no command-line parsing had occurred.
+      \note  For testing.  Not intended to be called from user code.
+    */
+    virtual void reset()
+    {
+        m_var = m_defaultVal;
+        m_set = false;
+        m_hidden = false;
+    }
+
+    /**
+      Set the argument's value from the command-line args.
+
+      If no value is provided for a required positional option, an arg_error
+      exception is thrown.
+      \note  Not intended to be called from user code.
+
+      \param vals  The list of command-line args.
+    */
+    virtual void assignPositional(ArgValList& vals)
+    {
+        if (m_positional == PosType::None || m_set)
+            return;
+        for (size_t i = vals.firstUnconsumed(); i < vals.size(); ++i)
+        {
+            const std::string& val = vals[i];
+            if ((val.size() && val[0] == '-') || vals.consumed(i))
+                continue;
+            setValue(val);
+            vals.consume(i);
+            return;
+        }
+        if (m_positional == PosType::Required)
+        {
+            std::ostringstream oss;
+
+            oss << "Missing value for positional argument '" <<
+                m_longname << "'.";
+            throw arg_error(oss.str());
+        }
+    }
+
+    /**
+      Return whether a default value was provided for the argument.
+
+      \return  Whether a default was provided.
+    */
+    virtual bool defaultProvided() const
+        { return m_defaultProvided; }
+
+    /**
+      Return a string representation of an Arg's default value.
+
+      \return  Default value as a string.
+    */
+    virtual std::string defaultVal() const
+        { return Utils::toString(m_defaultVal); }
+
+private:
+    T& m_var;
+    T m_defaultVal;
+    bool m_defaultProvided;
+};
+
+/**
+  Description of a boolean argument.  Boolean arguments don't take values.
+  Setting a boolean argument inverts its default value.  Boolean arguments
+  are normally 'false' by default.
+*/
+template <>
+class TArg<bool> : public Arg
+{
+public:
+    /**
+      Constructor for boolean arguments with default value.
+
+      \param longname  Name of argument specified on command line with "--"
+        prefix.
+      \param shortname  Optional name of argument specified on command
+        line with "-" prefix.
+      \param description  Argument description.
+      \param variable  bool variable to which the value of the argument should
+        be bound.
+      \param def  Default value to be assigned to the bound variable.
+    */
+    TArg(const std::string& longname, const std::string& shortname,
+        const std::string& description, bool& variable, bool def) :
+        Arg(longname, shortname, description), m_val(variable),
+        m_defaultVal(def), m_defaultProvided(true)
+    { m_val = m_defaultVal; }
+
+    /**
+      Constructor for boolean arguments without default value.
+
+      \param longname  Name of argument specified on command line with "--"
+        prefix.
+      \param shortname  Optional name of argument specified on command
+        line with "-" prefix.
+      \param description  Argument description.
+      \param variable  bool variable to which the value of the argument should
+        be bound.
+    */
+    TArg(const std::string& longname, const std::string& shortname,
+        const std::string& description, bool& variable) :
+        Arg(longname, shortname, description), m_val(variable),
+        m_defaultVal(false), m_defaultProvided(false)
+    { m_val = m_defaultVal; }
+
+    /**
+      Return whether an option needs a value to be valid.
+
+      \return false  Boolean values don't need a value.
+      \note  Not intended to be called from user code.
+    */
+    virtual bool needsValue() const
+        { return false; }
+
+    /**
+      Set a an argument's value from a string.
+
+      \note  The argumet is either 'true' or 'false'.  True means that we're
+        setting the option, which sets the negative of the default value.
+        False sets the option to the default value (essentially a no-op).
+      \note  Not intended to be called from user code.
+
+      \param s  Value to set [ignored].
+    */
+    virtual void setValue(const std::string& s)
+    {
+        if (s.size() && s[0] == '-')
+        {
+            std::stringstream oss;
+            oss << "Argument '" << m_longname << "' needs a value and none "
+                "was provided.";
+            throw arg_val_error(oss.str());
+        }
+        if (s == "invert")
+            m_val = !m_defaultVal;
+        else if (s == "true")
+            m_val = true;
+        else
+            m_val = false;
+        m_set = true;
+    }
+
+    /**
+      Reset the argument's state.
+
+      Set the internal state of the argument and it's referenced variable
+      as if no command-line parsing had occurred.
+      \note  For testing.  Not intended to be called from user code.
+    */
+    virtual void reset()
+    {
+        m_val = m_defaultVal;
+        m_set = false;
+        m_hidden = false;
+    }
+
+    /**
+      Indicate that the argument is positional.
+
+      Throws an exception to indicate that boolean arguments can't
+      positional.
+    */
+    virtual Arg& setPositional()
+    {
+        std::ostringstream oss;
+        oss << "Boolean argument '" << m_longname << "' can't be positional.";
+        throw arg_error(oss.str());
+        return *this;
+    }
+
+    /**
+      Indicate that the argument is positional and optional.
+
+      Throws an exception to indicate that boolean arguments can't
+      positional.
+    */
+    virtual Arg& setOptionalPositional()
+    {
+        std::ostringstream oss;
+        oss << "Boolean argument '" << m_longname << "' can't be positional.";
+        throw arg_error(oss.str());
+        return *this;
+    }
+    /**
+      Return whether a default value was provided for the argument.
+
+      \return  Whether a default was provided.
+    */
+    virtual bool defaultProvided() const
+        { return m_defaultProvided; }
+    /**
+      Return a string representation of an Arg's default value.
+
+      \return  Default value as a string.
+    */
+    virtual std::string defaultVal() const
+        { return Utils::toString(m_defaultVal); }
+
+private:
+    bool& m_val;
+    bool m_defaultVal;
+    bool m_defaultProvided;
+};
+
+/**
+  Description of a list-based (vector) argument.  List-based arguments can
+  be specified multiple times, taking multiple values.  List-based
+  arguments are necessarily bound to variables that are vectors.
+  \note  Doesn't properly support list-based boolean values.
+*/
+class BaseVArg : public Arg
+{
+public:
+    /**
+      Constructor.
+
+      \param longname  Name of argument specified on command line with "--"
+        prefix.
+      \param shortname  Optional name of argument specified on command
+        line with "-" prefix.
+      \param description  Argument description.
+    */
+    BaseVArg(const std::string& longname, const std::string& shortname,
+        const std::string& description) : Arg(longname, shortname, description),
+        m_defaultProvided(false)
+    {}
+
+    /**
+      Set the argument's value from the command-line args.
+
+      List-based arguments consume ALL positional arguments until
+      one is found that can't be converted to the type of the bound variable.
+      \note  Not intended to be called from user code.
+
+      \param vals  The list of command-line args.
+    */
+    virtual void assignPositional(ArgValList& vals)
+    {
+        if (m_positional == PosType::None || m_set)
+            return;
+
+        int cnt = 0;
+        for (size_t i = vals.firstUnconsumed(); i < vals.size(); ++i)
+        {
+            const std::string& val = vals[i];
+            if ((val.size() && val[0] == '-') || vals.consumed(i))
+                continue;
+            try
+            {
+                setValue(val);
+                vals.consume(i);
+                cnt++;
+            }
+            catch (arg_error&)
+            {
+                break;
+            }
+        }
+        if (cnt == 0 && m_positional == PosType::Required)
+        {
+            std::ostringstream oss;
+
+            oss << "Missing value for positional argument '" <<
+                m_longname << "'.";
+            throw arg_error(oss.str());
+        }
+    }
+
+    /**
+      Return whether a default value was provided for the argument.
+
+      \return  Whether a default was provided.
+    */
+    virtual bool defaultProvided() const
+        { return m_defaultProvided; }
+
+protected:
+    bool m_defaultProvided;
+};
+
+/**
+  Description of a generic list-based (vector) argument.
+  \note  Doesn't properly support list-based boolean values.
+*/
+template <typename T>
+class VArg : public BaseVArg
+{
+public:
+    /**
+      Constructor for arguments with default value.
+
+      \param longname  Name of argument specified on command line with "--"
+        prefix.
+      \param shortname  Optional name of argument specified on command
+        line with "-" prefix.
+      \param description  Argument description.
+      \param variable  Variable to which the argument value(s) should be bound.
+      \param def  Default value.
+    */
+    VArg(const std::string& longname, const std::string& shortname,
+        const std::string& description, std::vector<T>& variable,
+        std::vector<T> def) :
+        BaseVArg(longname, shortname, description), m_var(variable),
+        m_defaultVal(def)
+    {
+        m_var = def;
+        m_defaultProvided = true;
+    }
+
+    /**
+      Constructor for arguments without default value.
+
+      \param longname  Name of argument specified on command line with "--"
+        prefix.
+      \param shortname  Optional name of argument specified on command
+        line with "-" prefix.
+      \param description  Argument description.
+      \param variable  Variable to which the argument value(s) should be bound.
+    */
+    VArg(const std::string& longname, const std::string& shortname,
+        const std::string& description, std::vector<T>& variable) :
+        BaseVArg(longname, shortname, description), m_var(variable)
+    {
+        // Clearing the vector resets to "default" value.
+        m_var.clear();
+    }
+
+    /**
+      Set a an argument's value from a string.
+
+      Throws an arg_error exception if \a s can't be converted to
+      the argument's type.
+      \note  Not intended to be called from user code.
+
+      \param s  Value to set.
+    */
+    virtual void setValue(const std::string& s)
+    {
+        if (s.size() && s[0] == '-')
+        {
+            std::stringstream oss;
+            oss << "Argument '" << m_longname << "' needs a value and none "
+                "was provided.";
+            throw arg_val_error(oss.str());
+        }
+        m_rawVal = s;
+        T var;
+        if (!Utils::fromString(s, var))
+        {
+            std::ostringstream oss;
+            oss << "Invalid value for argument '" << m_longname << "'.";
+            throw arg_val_error(oss.str());
+        }
+        if (!m_set)
+            m_var.clear();
+        m_var.push_back(var);
+        m_set = true;
+    }
+
+    /**
+      Reset the argument's state.
+
+      Set the internal state of the argument and it's referenced variable
+      as if no command-line parsing had occurred.
+      \note  For testing.  Not intended to be called from user code.
+    */
+    virtual void reset()
+    {
+        m_var = m_defaultVal;
+        m_set = false;
+        m_hidden = false;
+    }
+
+    /**
+      Return a string representation of an Arg's default value, or an
+      empty string if none exists.
+
+      \return  Default value as a string.
+    */
+    virtual std::string defaultVal() const
+    {
+        std::string s;
+
+        for (size_t i = 0; i < m_defaultVal.size(); ++i)
+        {
+            if (i > 0)
+                s += ", ";
+            s += Utils::toString(m_defaultVal[i]);
+        }
+        return s;
+    }
+
+private:
+    std::vector<T>& m_var;
+    std::vector<T> m_defaultVal;
+};
+
+/**
+  Description of an argument tied to a string vector.
+*/
+template <>
+class VArg<std::string> : public BaseVArg
+{
+public:
+    /**
+      Constructor for arguments wit default value.
+
+      \param longname  Name of argument specified on command line with "--"
+        prefix.
+      \param shortname  Optional name of argument specified on command
+        line with "-" prefix.
+      \param description  Argument description.
+      \param variable  Variable to which the argument value(s) should be bound.
+      \param def  Default value.
+    */
+    VArg(const std::string& longname, const std::string& shortname,
+        const std::string& description, std::vector<std::string>& variable,
+        std::vector<std::string> def) :
+        BaseVArg(longname, shortname, description), m_var(variable),
+        m_defaultVal(def)
+    {
+        m_var = def;
+        m_defaultProvided = true;
+    }
+
+    /**
+      Constructor for arguments without default value.
+
+      \param longname  Name of argument specified on command line with "--"
+        prefix.
+      \param shortname  Optional name of argument specified on command
+        line with "-" prefix.
+      \param description  Argument description.
+      \param variable  Variable to which the argument value(s) should be bound.
+    */
+    VArg(const std::string& longname, const std::string& shortname,
+        const std::string& description, std::vector<std::string>& variable) :
+        BaseVArg(longname, shortname, description), m_var(variable)
+    {}
+
+    /**
+      Set a an argument's value from a string.
+
+      Throws an arg_error exception if \a s can't be converted to
+      the argument's type.
+      \note  Not intended to be called from user code.
+
+      \param s  Value to set.
+    */
+    virtual void setValue(const std::string& s)
+    {
+        std::vector<std::string> slist = Utils::split2(s, ',');
+        for (auto& ts : slist)
+            Utils::trim(ts);
+
+        if ((s.size() && s[0] == '-') || slist.empty())
+        {
+            std::ostringstream oss;
+
+            oss << "Missing value for argument '" << m_longname << "'.";
+            throw arg_val_error(oss.str());
+        }
+        m_rawVal = s;
+        if (!m_set)
+            m_var.clear();
+        m_var.reserve(m_var.size() + slist.size());
+        m_var.insert(m_var.end(), slist.begin(), slist.end());
+        m_set = true;
+    }
+
+    /**
+      Reset the argument's state.
+
+      Set the internal state of the argument and it's referenced variable
+      as if no command-line parsing had occurred.
+      \note  For testing.  Not intended to be called from user code.
+    */
+    virtual void reset()
+    {
+        m_var = m_defaultVal;
+        m_set = false;
+        m_hidden = false;
+    }
+
+    /**
+      Return a string representation of an Arg's default value, or an
+      empty string if none exists.
+
+      \return  Default value as a string.
+    */
+    virtual std::string defaultVal() const
+    {
+        std::string s;
+
+        for (size_t i = 0; i < m_defaultVal.size(); ++i)
+        {
+            if (i > 0)
+                s += ", ";
+            s += m_defaultVal[i];
+        }
+        return s;
+    }
+
+private:
+    std::vector<std::string>& m_var;
+    std::vector<std::string> m_defaultVal;
+};
+
+/**
+  Parses command lines, provides validation and stores found values in
+  bound variables.  Add arguments with \ref add.  When all arguments
+  have been added, use \ref parse to validate command line and assign
+  values to variables bound with \ref add.
+*/
+class ProgramArgs
+{
+
+public:
+    /**
+      Add a string argument to the list of arguments.
+
+      \param name  Name of argument.  Argument names are specified as
+        "longname[,shortname]", where shortname is an optional one-character
+        abbreviation.
+      \param description  Description of the argument.
+      \param var  Reference to variable to bind to argument.
+      \param def  Default value of argument.
+      \return  Reference to the new argument.
+    */
+    Arg& add(const std::string& name, const std::string description,
+        std::string& var, std::string def)
+    {
+        return add<std::string>(name, description, var, def);
+    }
+
+    /**
+      Add a list-based (vector) string argument
+
+      \param name  Name of argument.  Argument names are specified as
+        "longname[,shortname]", where shortname is an optional one-character
+        abbreviation.
+      \param description  Description of the argument.
+      \param var  Reference to variable to bind to argument.
+      \return  Reference to the new argument.
+    */
+    Arg& add(const std::string& name, const std::string& description,
+        std::vector<std::string>& var)
+    {
+        return add<std::string>(name, description, var);
+    }
+
+    /**
+      Return whether the argument (as specified by it's longname) had
+      its value set during parsing.
+    */
+    bool set(const std::string& name) const
+    {
+        Arg *arg = findLongArg(name);
+        if (arg)
+            return arg->set();
+        return false;
+    }
+
+    /**
+      Add a list-based (vector) argument.
+
+      \param name  Name of argument.  Argument names are specified as
+        "longname[,shortname]", where shortname is an optional one-character
+        abbreviation.
+      \param description  Description of the argument.
+      \param var  Reference to variable to bind to argument.
+      \return  Reference to the new argument.
+    */
+    template<typename T>
+    Arg& add(const std::string& name, const std::string& description,
+        std::vector<T>& var)
+    {
+        std::string longname, shortname;
+        splitName(name, longname, shortname);
+
+        Arg *arg = new VArg<T>(longname, shortname, description, var);
+        addLongArg(longname, arg);
+        addShortArg(shortname, arg);
+        m_args.push_back(std::unique_ptr<Arg>(arg));
+        return *arg;
+    }
+
+    /**
+      Add a list-based (vector) argument with a default.
+
+      \param name  Name of argument.  Argument names are specified as
+        "longname[,shortname]", where shortname is an optional one-character
+        abbreviation.
+      \param description  Description of the argument.
+      \param var  Reference to variable to bind to argument.
+      \return  Reference to the new argument.
+    */
+    template<typename T>
+    Arg& add(const std::string& name, const std::string& description,
+        std::vector<T>& var, std::vector<T> def)
+    {
+        std::string longname, shortname;
+        splitName(name, longname, shortname);
+
+        Arg *arg = new VArg<T>(longname, shortname, description, var, def);
+        addLongArg(longname, arg);
+        addShortArg(shortname, arg);
+        m_args.push_back(std::unique_ptr<Arg>(arg));
+        return *arg;
+    }
+
+    /**
+      Add an argument to the list of arguments with a default.
+
+      \param name  Name of argument.  Argument names are specified as
+        "longname[,shortname]", where shortname is an optional one-character
+        abbreviation.
+      \param description  Description of the argument.
+      \param var  Reference to variable to bind to argument.
+      \param def  Default value of argument.
+      \return  Reference to the new argument.
+    */
+    template<typename T>
+    Arg& add(const std::string& name, const std::string description, T& var,
+        T def)
+    {
+        std::string longname, shortname;
+        splitName(name, longname, shortname);
+
+        Arg *arg = new TArg<T>(longname, shortname, description, var, def);
+        addLongArg(longname, arg);
+        addShortArg(shortname, arg);
+        m_args.push_back(std::unique_ptr<Arg>(arg));
+        return *arg;
+    }
+
+    /**
+      Add an argument to the list of arguments.
+
+      \param name  Name of argument.  Argument names are specified as
+        "longname[,shortname]", where shortname is an optional one-character
+        abbreviation.
+      \param description  Description of the argument.
+      \param var  Reference to variable to bind to argument.
+      \return  Reference to the new argument.
+    */
+    template<typename T>
+    Arg& add(const std::string& name, const std::string description, T& var)
+    {
+        std::string longname, shortname;
+        splitName(name, longname, shortname);
+
+        Arg *arg = new TArg<T>(longname, shortname, description, var);
+        addLongArg(longname, arg);
+        addShortArg(shortname, arg);
+        m_args.push_back(std::unique_ptr<Arg>(arg));
+        return *arg;
+    }
+
+    /**
+      Parse a command line as specified by its argument vector.  No validation
+      occurs and no exceptions are raised, but assignments are made
+      to bound variables where possible.
+
+      \param s  List of strings that constitute the argument list.
+    */
+    void parseSimple(std::vector<std::string>& s)
+    {
+        ArgValList vals(s);
+
+        for (size_t i = 0; i < vals.size();)
+        {
+            const std::string& arg = vals[i];
+            // This may be the value, or it may not.  We're passing it along
+            // just in case.  If there is no value, pass along "" to make
+            // clear that there is none.
+            std::string value((i != vals.size() - 1) ? vals[i + 1] : "");
+            try
+            {
+                int matched = parseArg(arg, value);
+                if (!matched)
+                    i++;
+                else
+                    while (matched--)
+                        vals.consume(i++);
+            }
+            catch (arg_val_error&)
+            {
+                throw;
+            }
+            catch (arg_error&)
+            {
+                i++;
+            }
+        }
+
+        // Go through args and assign those unset to items from the command
+        // line not already consumed.
+        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
+        {
+            Arg *arg = ai->get();
+            try
+            {
+                arg->assignPositional(vals);
+            }
+            catch (arg_val_error&)
+            {
+                throw;
+            }
+            catch (arg_error&)
+            {}
+        }
+        s = vals.unconsumedArgs();
+    }
+
+    /**
+      Parse a command line as specified by its argument list.  Parsing
+      validates the argument vector and assigns values to variables bound
+      to added arguments.
+
+      \param s  List of strings that constitute the argument list.
+    */
+    void parse(std::vector<std::string>& s)
+    {
+        validate();
+        ArgValList vals(s);
+        for (size_t i = 0; i < vals.size();)
+        {
+            const std::string& arg = vals[i];
+            // This may be the value, or it may not.  We're passing it along
+            // just in case.  If there is no value, pass along "" to make
+            // clear that there is none.
+            std::string value((i != vals.size() - 1) ? vals[i + 1] : "");
+            size_t matched = parseArg(arg, value);
+            if (!matched)
+                i++;
+            else
+                while (matched--)
+                    vals.consume(i++);
+        }
+
+        // Go through args and assign those unset to items from the command
+        // line not already consumed.
+        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
+        {
+            Arg *arg = ai->get();
+            arg->assignPositional(vals);
+        }
+    }
+
+    /**
+      Reset the state of all arguments and bound variables as if no parsing
+      had occurred.
+    */
+    void reset()
+    {
+        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
+            (*ai)->reset();
+    }
+
+    /**
+      Return a string suitable for use in a "usage" line for display to
+      users as help.
+    */
+    std::string commandLine() const
+    {
+        std::string s;
+
+        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
+        {
+            Arg *a = ai->get();
+
+            if (a->hidden())
+                continue;
+            std::string o = a->commandLine();
+            if (o.size())
+                s += o + " ";
+        }
+        if (s.size())
+            s = s.substr(0, s.size() - 1);
+        return s;
+    }
+
+    /**
+      Write a formatted description of arguments to an output stream.
+
+      Write a list of the names and descriptions of arguments suitable for
+      display as help information.
+
+      \param out  Stream to which output should be written.
+      \param indent  Number of characters to indent all text.
+      \param totalWidth  Total width to assume for formatting output.
+        Typically this is the width of a terminal window.
+    */
+    void dump(std::ostream& out, size_t indent, size_t totalWidth) const
+    {
+        size_t namelen = 0;
+        std::vector<std::pair<std::string, std::string>> info;
+
+        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
+        {
+            Arg *a = ai->get();
+            if (a->hidden())
+                continue;
+
+            std::string nameDescrip = a->nameDescrip();
+
+            info.push_back(std::make_pair(nameDescrip, a->description()));
+            namelen = std::max(namelen, nameDescrip.size());
+        }
+        size_t secondIndent = indent + 4;
+        int postNameSpacing = 2;
+        size_t leadlen = namelen + indent + postNameSpacing;
+        size_t firstlen = totalWidth - leadlen - 1;
+        size_t secondLen = totalWidth - secondIndent - 1;
+
+        bool skipfirst = (firstlen < 10);
+        if (skipfirst)
+            firstlen = secondLen;
+
+        for (auto i : info)
+        {
+            std::vector<std::string> descrip =
+                Utils::wordWrap(i.second, secondLen, firstlen);
+
+            std::string name = i.first;
+            out << std::string(indent, ' ');
+            if (skipfirst)
+                out << name << std::endl;
+            else
+            {
+                name.resize(namelen, ' ');
+                out << name << std::string(postNameSpacing, ' ') <<
+                    descrip[0] << std::endl;
+            }
+            for (size_t i = 1; i < descrip.size(); ++i)
+                out << std::string(secondIndent, ' ') <<
+                    descrip[i] << std::endl;
+        }
+    }
+
+    /**
+      Write a verbose description of arguments to an output stream.  Each
+      argument is on its own line.  The argument's description follows
+      on subsequent lines.
+
+      \param out  Stream to which output should be written.
+      \param nameIndent  Number of characters to indent argument lines.
+      \param descripIndent  Number of characters to indent description lines.
+      \param totalWidth  Total line width.
+
+    */
+    void dump2(std::ostream& out, size_t nameIndent, size_t descripIndent,
+        size_t totalWidth) const
+    {
+        size_t width = totalWidth - descripIndent;
+        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
+        {
+            Arg *a = ai->get();
+            out << std::string(nameIndent, ' ') << a->longname();
+            if (a->defaultProvided())
+                out << " [" << a->defaultVal() << "]";
+            out << std::endl;
+            std::vector<std::string> descrip =
+                Utils::wordWrap(a->description(), width);
+            if (descrip.empty())
+                descrip.push_back("<no description available>");
+            for (std::string& s : descrip)
+                out << std::string(descripIndent, ' ') << s << std::endl;
+            out << std::endl;
+        }
+    }
+
+    /**
+      Write a JSON array of arguments to an output stream.
+
+      \param out  Stream to which output should be written.
+
+    */
+    void dump3(std::ostream& out) const
+    {
+        out << "[";
+        bool bFirst(true);
+        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
+        {
+            Arg *a = ai->get();
+
+            if (!bFirst)
+                out << ",";
+
+            out << "{\"name\":\"" << a->longname() << "\"";
+
+            if (a->defaultProvided())
+                out << ",\"default\":\"" << a->defaultVal() << "\"";
+
+            out << ",\"description\":\"" << a->description() << "\"}";
+
+            if (bFirst) bFirst = false;
+
+
+        }
+        out << "]";
+    }
+private:
+    /*
+      Split an argument name into longname and shortname.
+
+      \param name  Name of argument specified as "longname[,shortname]".
+      \param[out] longname  Parsed longname.
+      \param[out] shortname  Parsed shortname.
+    */
+    void splitName(const std::string& name, std::string& longname,
+        std::string& shortname)
+    {
+        // Arg names must be specified as "longname[,shortname]" where
+        // shortname is a single character.
+        std::vector<std::string> s = Utils::split(name, ',');
+        if (s.size() > 2)
+            throw arg_error("Invalid program argument specification");
+        if (s.size() == 2 && s[1].size() != 1)
+            throw arg_error("Short argument not specified as single character");
+        if (s.empty())
+            throw arg_error("No program argument provided.");
+        if (s.size() == 1)
+            s.push_back("");
+        longname = s[0];
+        shortname = s[1];
+    }
+
+    /*
+      Add an argument to the list of arguments based on its longname.
+
+      \param name  Argument longname.
+      \param arg   Pointer to argument.
+    */
+    void addLongArg(const std::string& name, Arg *arg)
+    {
+        if (name.empty())
+            return;
+        if (findLongArg(name))
+        {
+            std::ostringstream oss;
+
+            oss << "Argument --" << name << " already exists.";
+            throw arg_error(oss.str());
+        }
+        m_longargs[name] = arg;
+    }
+
+    /*
+      Add an argument to the list of arguments based on its shortname.
+
+      \param name  Argument shortname.
+      \param arg   Pointer to argument.
+    */
+    void addShortArg(const std::string& name, Arg *arg)
+    {
+        if (name.empty())
+            return;
+        if (findShortArg(name[0]))
+        {
+            std::ostringstream oss;
+
+            oss << "Argument -" << name << " already exists.";
+            throw arg_error(oss.str());
+        }
+        m_shortargs[name] = arg;
+    }
+
+    /*
+      Find an argument given its longname.
+
+      \param s  Longname of argument.
+      \return  Pointer to matching argument, or NULL if none was found.
+    */
+    Arg *findLongArg(const std::string& s) const
+    {
+        auto si = m_longargs.find(s);
+        if (si != m_longargs.end())
+            return si->second;
+        return NULL;
+    }
+
+    /*
+      Find an argument given its shortname.
+
+      \param c  Shortnamn of argument.
+      \return  Pointer to matching argument, or NULL if none was found.
+    */
+    Arg *findShortArg(char c) const
+    {
+        std::string s(1, c);
+        auto si = m_shortargs.find(s);
+        if (si != m_shortargs.end())
+            return si->second;
+        return NULL;
+    }
+
+    /*
+      Parse a string-specified argument name and value into its argument.
+
+      \param arg  Name of argument specified on command line.
+      \param value  Potential value assigned to argument.
+      \return  Number of strings consumed (1 for positional arguments or
+        arguments that don't take values or 2 otherwise).
+    */
+    int parseArg(const std::string& arg, const std::string& value)
+    {
+        if (arg.size() > 1 && arg[0] == '-' && arg[1] == '-')
+            return parseLongArg(arg, value);
+        else if (arg.size() && arg[0] == '-')
+            return parseShortArg(arg, value);
+        return 0;
+    }
+
+    /*
+      Parse an argument specified as a long argument (prefixed with "--")
+      Long arguments with values can be specified as
+      "--name=value" or "--name value".
+
+      \param name  Name of argument specified on command line.
+      \param value  Potential value assigned to argument.
+      \return  Number of strings consumed (1 for positional arguments or
+        arguments that don't take values or 2 otherwise).
+    */
+    int parseLongArg(const std::string& inName, const std::string& inValue)
+    {
+        bool attachedValue = false;
+
+        if (inName.size() == 2)
+            throw arg_error("No argument found following '--'.");
+
+        std::string name = inName.substr(2);
+        std::string value = inValue;
+
+        std::size_t pos = name.find_first_of("=");
+        if (pos != std::string::npos)
+        {
+            if (pos < name.size() + 1)
+            {
+                value = name.substr(pos + 1);
+                name = name.substr(0, pos);
+                attachedValue = true;
+            }
+        }
+        else if (value.size() && value[0] == '-')
+        {
+            // If a value starts with a '-' and isn't attached to a name,
+            // we assume it's an option and not a value.
+            value.clear();
+        }
+
+        Arg *arg = findLongArg(name);
+        if (!arg)
+        {
+            std::ostringstream oss;
+            oss << "Unexpected argument '" << name << "'.";
+            throw arg_error(oss.str());
+        }
+
+        if (!arg->needsValue())
+        {
+            if (attachedValue)
+            {
+                if (value != "true" && value != "false")
+                {
+                    std::ostringstream oss;
+                    oss << "Value '" << value << "' provided for argument '" <<
+                        name << "' when none is expected.";
+                    throw arg_error(oss.str());
+                }
+            }
+            else
+                value = "invert";
+            arg->setValue(value);
+            return 1;
+        }
+
+        arg->setValue(value);
+        return (attachedValue ? 1 : 2);
+    }
+
+    /*
+      Parse an argument specified as a short argument (prefixed with "-")
+      Short arguments with values are specified as "-name value".
+
+      \param name  Name of argument specified on command line.
+      \param value  Potential value assigned to argument.
+      \return  Number of strings consumed (1 for positional arguments or
+        arguments that don't take values or 2 otherwise).
+    */
+    int parseShortArg(const std::string& name, const std::string& value)
+    {
+        if (name.size() == 1)
+            throw arg_error("No argument found following '-'.");
+        assert(name.size() == 2);
+
+        Arg *arg = findShortArg(name[1]);
+        if (!arg)
+        {
+            std::ostringstream oss;
+            oss << "Unexpected argument '-" << name[1] << "'.";
+            throw arg_error(oss.str());
+        }
+
+        int cnt;
+        if (arg->needsValue())
+        {
+            // If the value starts with a '-', assume it's an option
+            // rather than a value.
+            if (value.empty() || value[0] == '-')
+            {
+                std::ostringstream oss;
+                oss << "Short option '" << name << "' expects value "
+                    "but none directly follows.";
+                throw arg_error(oss.str());
+            }
+            else
+            {
+                cnt = 2;
+                arg->setValue(value);
+            }
+        }
+        else
+        {
+            arg->setValue("true");
+            cnt = 1;
+        }
+        return cnt;
+    }
+
+    /*
+      Make sure we don't have any required positional args after
+      non-required positional args.
+    */
+    void validate()
+    {
+        bool opt = false;
+        for (auto ai = m_args.begin(); ai != m_args.end(); ++ai)
+        {
+            Arg *arg = ai->get();
+            if (arg->positional() == Arg::PosType::Optional)
+                opt = true;
+            if (opt && (arg->positional() == Arg::PosType::Required))
+            {
+                std::ostringstream oss;
+                oss << "Found required positional argument '" <<
+                    arg->longname() << "' after optional positional argument.";
+                throw arg_error(oss.str());
+            }
+        }
+    }
+
+    std::vector<std::unique_ptr<Arg>> m_args;  /// Storage for arguments
+    std::map<std::string, Arg *> m_shortargs;  /// Map from shortname to args
+    std::map<std::string, Arg *> m_longargs;  /// Map from longname to args
+};
+
+} // namespace pdal
+
diff --git a/pdal/util/Utils.cpp b/pdal/util/Utils.cpp
new file mode 100644
index 0000000..c2a9489
--- /dev/null
+++ b/pdal/util/Utils.cpp
@@ -0,0 +1,698 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/util/Utils.hpp>
+
+#include <cassert>
+#include <cstdlib>
+#include <cctype>
+#include <memory>
+#include <random>
+
+#ifndef _WIN32
+#include <cxxabi.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>  // WIFEXITED, WEXITSTATUS
+#include <execinfo.h> // backtrace
+#include <dlfcn.h> // dladdr
+#endif
+
+#ifdef PDAL_COMPILER_MSVC
+#  pragma warning(disable: 4127)  // conditional expression is constant
+#endif
+
+#include <stdio.h>
+#include <iomanip>
+
+typedef std::vector<std::string> StringList;
+
+namespace pdal
+{
+
+
+void Utils::random_seed(unsigned int seed)
+{
+    srand(seed);
+}
+
+
+double Utils::random(double minimum, double maximum)
+{
+    double r = (double)rand();  // [0..32767]
+    double v = (maximum - minimum) / (double)RAND_MAX;
+    double s = r * v; // [0..(max-min)]
+    double t = minimum + s; // [min..max]
+
+    assert(t >= minimum);
+    assert(t <= maximum);
+
+    return t;
+}
+
+
+double Utils::uniform(const double& minimum, const double& maximum,
+    uint32_t seed)
+{
+    std::mt19937 gen(seed);
+    std::uniform_real_distribution<double> dist(minimum, maximum);
+
+    return dist(gen);
+}
+
+
+double Utils::normal(const double& mean, const double& sigma, uint32_t seed)
+{
+    std::mt19937 gen(seed);
+    std::normal_distribution<double> dist(mean, sigma);
+
+    return dist(gen);
+}
+
+
+int Utils::getenv(const std::string& name, std::string& val)
+{
+    char* value = ::getenv(name.c_str());
+    if (value)
+        val = value;
+    else
+        val.clear();
+    return value ?  0 : -1;
+}
+
+
+int Utils::setenv(const std::string& env, const std::string& val)
+{
+#ifdef _WIN32
+    return ::_putenv_s(env.c_str(), val.c_str()) ? -1 : 0;
+#else
+    return ::setenv(env.c_str(), val.c_str(), 1);
+#endif
+}
+
+
+int Utils::unsetenv(const std::string& env)
+{
+#ifdef _WIN32
+    return ::_putenv_s(env.c_str(), "") ? -1 : 0;
+#else
+    return ::unsetenv(env.c_str());
+#endif
+}
+
+
+void Utils::eatwhitespace(std::istream& s)
+{
+    while (true)
+    {
+        const char c = (char)s.peek();
+        if (!isspace(c))
+            break;
+
+        // throw it away
+        s.get();
+    }
+}
+
+
+void Utils::trimLeading(std::string& s)
+{
+    size_t pos = 0;
+    // Note, that this should be OK in C++11, which guarantees a NULL.
+    while (isspace(s[pos]))
+        pos++;
+    s = s.substr(pos);
+}
+
+
+void Utils::trimTrailing(std::string& s)
+{
+    if (s.empty())
+        return;
+
+    size_t pos = s.size() - 1;
+    while (isspace(s[pos]))
+    {
+        if (pos == 0)
+        {
+            s.clear();
+            return;
+        }
+        else
+            pos--;
+    }
+    s = s.substr(0, pos + 1);
+}
+
+
+bool Utils::eatcharacter(std::istream& s, char x)
+{
+    const char c = (char)s.peek();
+    if (c != x)
+        return false;
+
+    // throw it away
+    s.get();
+
+    return true;
+}
+
+
+std::string Utils::base64_encode(const unsigned char *bytes_to_encode,
+    size_t in_len)
+{
+    /*
+        base64.cpp and base64.h
+
+        Copyright (C) 2004-2008 René Nyffenegger
+
+        This source code is provided 'as-is', without any express or implied
+        warranty. In no event will the author be held liable for any damages
+        arising from the use of this software.
+
+        Permission is granted to anyone to use this software for any purpose,
+        including commercial applications, and to alter it and redistribute it
+        freely, subject to the following restrictions:
+
+        1. The origin of this source code must not be misrepresented;
+           you must not claim that you wrote the original source code. If you
+           use this source code in a product, an acknowledgment in the product
+           documentation would be appreciated but is not required.
+
+        2. Altered source versions must be plainly marked as such, and must
+           not be misrepresented as being the original source code.
+
+        3. This notice may not be removed or altered from any source
+           distribution.
+
+        René Nyffenegger rene.nyffenegger at adp-gmbh.ch
+    */
+
+    if (in_len == 0)
+        return std::string();
+
+    const std::string base64_chars =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        "abcdefghijklmnopqrstuvwxyz"
+        "0123456789+/";
+    std::string ret;
+    int i = 0;
+    int j = 0;
+    uint8_t char_array_3[3];
+    uint8_t char_array_4[4];
+
+    while (in_len--)
+    {
+        char_array_3[i++] = *(bytes_to_encode++);
+        if (i == 3)
+        {
+            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+            char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
+                ((char_array_3[1] & 0xf0) >> 4);
+            char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
+                ((char_array_3[2] & 0xc0) >> 6);
+            char_array_4[3] = char_array_3[2] & 0x3f;
+
+            for (i = 0; (i <4) ; i++)
+                ret += base64_chars[char_array_4[i]];
+            i = 0;
+        }
+    }
+
+    if (i)
+    {
+        for (j = i; j < 3; j++)
+            char_array_3[j] = '\0';
+
+        char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+        char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
+            ((char_array_3[1] & 0xf0) >> 4);
+        char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
+            ((char_array_3[2] & 0xc0) >> 6);
+        char_array_4[3] = char_array_3[2] & 0x3f;
+
+        for (j = 0; (j < i + 1); j++)
+            ret += base64_chars[char_array_4[j]];
+
+        while ((i++ < 3))
+            ret += '=';
+    }
+    return ret;
+}
+
+
+static inline bool is_base64(unsigned char c)
+{
+    return (isalnum(c) || (c == '+') || (c == '/'));
+}
+
+
+std::vector<uint8_t> Utils::base64_decode(std::string const& encoded_string)
+{
+    const std::string base64_chars =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        "abcdefghijklmnopqrstuvwxyz"
+        "0123456789+/";
+
+    std::string::size_type in_len = encoded_string.size();
+    int i = 0;
+    int j = 0;
+    int in_ = 0;
+    unsigned char char_array_4[4], char_array_3[3];
+    std::vector<uint8_t> ret;
+
+    while (in_len-- && (encoded_string[in_] != '=') &&
+        is_base64(encoded_string[in_]))
+    {
+        char_array_4[i++] = encoded_string[in_];
+        in_++;
+        if (i == 4)
+        {
+            for (i = 0; i <4; i++)
+                char_array_4[i] = static_cast<unsigned char>(
+                    base64_chars.find(char_array_4[i]));
+
+            char_array_3[0] = (char_array_4[0] << 2) +
+                ((char_array_4[1] & 0x30) >> 4);
+            char_array_3[1] = ((char_array_4[1] & 0xf) << 4) +
+                ((char_array_4[2] & 0x3c) >> 2);
+            char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+            for (i = 0; (i < 3); i++)
+                ret.push_back(char_array_3[i]);
+            i = 0;
+        }
+    }
+
+    if (i)
+    {
+        for (j = i; j <4; j++)
+            char_array_4[j] = 0;
+
+        for (j = 0; j <4; j++)
+            char_array_4[j] =
+                static_cast<unsigned char>(base64_chars.find(char_array_4[j]));
+
+        char_array_3[0] = (char_array_4[0] << 2) +
+            ((char_array_4[1] & 0x30) >> 4);
+        char_array_3[1] = ((char_array_4[1] & 0xf) << 4) +
+            ((char_array_4[2] & 0x3c) >> 2);
+        char_array_3[2] = ((char_array_4[2] & 0x3) << 6) +
+            char_array_4[3];
+
+        for (j = 0; (j < i - 1); j++)
+            ret.push_back(char_array_3[j]);
+    }
+    return ret;
+}
+
+
+FILE* Utils::portable_popen(const std::string& command, const std::string& mode)
+{
+#ifdef _WIN32
+    const std::string dos_command = Utils::replaceAll(command, "/", "\\");
+    return _popen(dos_command.c_str(), mode.c_str());
+#else
+    return popen(command.c_str(), mode.c_str());
+#endif
+}
+
+
+int Utils::portable_pclose(FILE* fp)
+{
+    int status = 0;
+
+#ifdef _WIN32
+    status = _pclose(fp);
+#else
+    status = pclose(fp);
+    if (status == -1)
+    {
+        throw std::runtime_error("Error closing pipe for subprocess");
+    }
+    if (WIFEXITED(status) != 0)
+    {
+        status = WEXITSTATUS(status);
+    }
+    else
+    {
+        status = 0;
+    }
+#endif
+
+    return status;
+}
+
+
+int Utils::run_shell_command(const std::string& cmd, std::string& output)
+{
+    const int maxbuf = 4096;
+    char buf[maxbuf];
+
+    output = "";
+
+    FILE* fp = portable_popen(cmd.c_str(), "r");
+
+    if (fp == NULL)
+        return 1;
+
+    while (!feof(fp))
+    {
+        if (fgets(buf, maxbuf, fp) == NULL)
+        {
+            if (feof(fp)) break;
+            if (ferror(fp)) break;
+        }
+        output += buf;
+    }
+    return portable_pclose(fp);
+}
+
+
+std::string Utils::replaceAll(std::string result,
+    const std::string& replaceWhat, const std::string& replaceWithWhat)
+{
+    size_t pos = 0;
+    while (1)
+    {
+        pos = result.find(replaceWhat, pos);
+        if (pos == std::string::npos)
+            break;
+        result.replace(pos, replaceWhat.size(), replaceWithWhat);
+        pos += replaceWithWhat.size();
+        if (pos >= result.size())
+            break;
+    }
+    return result;
+}
+
+
+// Adapted from http://stackoverflow.com/a/11969098.
+std::string Utils::escapeJSON(const std::string &str)
+{
+    std::string escaped(str);
+
+    escaped.erase
+    (
+        remove_if(
+            escaped.begin(), escaped.end(), [](const char c)
+            {
+                return (c <= 31);
+            }
+        ),
+        escaped.end()
+    );
+
+    size_t pos(0);
+
+    while((pos = escaped.find_first_of("\"\\/", pos)) != std::string::npos)
+    {
+        escaped.insert(pos, "\\");
+        pos += 2;
+    }
+
+    return escaped;
+}
+
+
+StringList Utils::wordWrap(std::string const& s, size_t lineLength,
+    size_t firstLength)
+{
+    std::vector<std::string> output;
+    if (s.empty())
+        return output;
+
+    if (firstLength == 0)
+        firstLength = lineLength;
+
+    size_t len = firstLength;
+
+    std::istringstream iss(s);
+    std::string line;
+    do
+    {
+        std::string word;
+        iss >> word;
+
+        if ((line.length() + word.length() > len) && line.length())
+        {
+            trimTrailing(line);
+            output.push_back(line);
+            len = lineLength;
+            line.clear();
+        }
+        while (word.length() > len)
+        {
+            output.push_back(word.substr(0, len));
+            word = word.substr(len);
+            len = lineLength;
+        }
+        line += word + " ";
+    } while (iss);
+    trimTrailing(line);
+    if (!line.empty())
+        output.push_back(line);
+    return output;
+}
+
+
+StringList Utils::wordWrap2(std::string const& s, size_t lineLength,
+    size_t firstLength)
+{
+    std::vector<std::string> output;
+    if (s.empty())
+        return output;
+
+    if (firstLength == 0)
+        firstLength = lineLength;
+
+    auto pushWord = [&s, &output](size_t start, size_t end)
+    {
+        if (start != end)
+            output.push_back(s.substr(start, end - start + 1));
+    };
+
+    size_t len = firstLength;
+    size_t startPos = 0;
+    while (true)
+    {
+        size_t endPos = std::min(startPos + len - 1, s.size() - 1);
+        if (endPos + 1 == s.size())
+        {
+            pushWord(startPos, endPos);
+            return output;
+        }
+        size_t pos = endPos;
+        while (pos > startPos)
+        {
+            if (std::isspace(s[pos]) && !std::isspace(s[pos + 1]))
+            {
+                endPos = pos;
+                break;
+            }
+            pos--;
+        }
+        pushWord(startPos, endPos);
+        len = lineLength;
+        startPos = endPos + 1;
+    }
+    return output;
+}
+
+
+/// Demangle strings using the compiler-provided demangle function.
+/// \param[in] s  String to be demangled.
+/// \return  Demangled string
+std::string Utils::demangle(const std::string& s)
+{
+#ifndef _WIN32
+    int status;
+    std::unique_ptr<char[], void (*)(void*)> result(
+            abi::__cxa_demangle(s.c_str(), 0, 0, &status), std::free);
+    if (status == 0)
+        return std::string(result.get());
+#endif
+
+    return s;
+}
+
+
+int Utils::screenWidth()
+{
+#ifdef WIN32
+    return 80;
+#else
+    struct winsize ws;
+    if (ioctl(0, TIOCGWINSZ, &ws))
+        return 80;
+
+    return ws.ws_col;
+#endif
+}
+
+
+std::string Utils::escapeNonprinting(const std::string& s)
+{
+    std::string out;
+
+    for (size_t i = 0; i < s.size(); ++i)
+    {
+        if (s[i] == '\n')
+            out += "\\n";
+        else if (s[i] == '\a')
+            out += "\\a";
+        else if (s[i] == '\b')
+            out += "\\b";
+        else if (s[i] == '\r')
+            out += "\\r";
+        else if (s[i] == '\v')
+            out += "\\v";
+        else if (s[i] < 32)
+        {
+            std::stringstream oss;
+            oss << std::hex << std::setfill('0') << std::setw(2) << (int)s[i];
+            out += "\\x" + oss.str();
+        }
+        else
+            out += s[i];
+    }
+    return out;
+}
+
+
+double Utils::normalizeLongitude(double longitude)
+{
+    longitude = fmod(longitude, 360.0);
+    if (longitude <= -180)
+        longitude += 360;
+    else if (longitude > 180)
+        longitude -= 360;
+    return longitude;
+}
+
+
+std::vector<std::string> Utils::backtrace()
+{
+    std::vector<std::string> lines;
+#ifndef WIN32
+    const int MAX_STACK_SIZE(100);
+    void* buffer[MAX_STACK_SIZE];
+    std::vector<std::string> prefixes;
+    size_t maxPrefix(0);
+
+    std::size_t size(::backtrace(buffer, MAX_STACK_SIZE));
+    char** symbols(backtrace_symbols(buffer, size));
+
+    // Store strings and free symbols.  Start at 1 to remove this function
+    // from the stack.
+    for (std::size_t i(1); i < size; ++i)
+    {
+        lines.push_back(symbols[i]);
+        const std::string& symbol = lines.back();
+        std::string prefix;
+        std::size_t pos = symbol.find("0x");
+        if (pos != std::string::npos)
+            prefix = symbol.substr(0, pos);
+        else
+            prefix = std::to_string(i) + "  ???";
+        trimTrailing(prefix);
+        prefixes.push_back(prefix);
+        maxPrefix = std::max(prefix.size(), maxPrefix);
+    }
+    free(symbols);
+
+    // Replace the simple symbol with a better representation if possible.
+    for (std::size_t i(1); i < size; ++i)
+    {
+        std::string& symbol = lines[i - 1];
+        std::string& prefix = prefixes[i - 1];
+        prefix = prefix + std::string(maxPrefix + 2 - prefix.size(), ' ');
+
+        Dl_info info;
+        if (dladdr(buffer[i], &info))
+        {
+            const std::size_t offset(static_cast<char*>(buffer[i]) -
+                        static_cast<char*>(info.dli_saddr));
+
+            // Replace the address and mangled name with a human-readable
+            // name.
+            symbol = prefix + demangle(info.dli_sname) + " + " +
+                std::to_string(offset);
+        }
+        else
+        {
+            symbol = symbol.substr(maxPrefix + 2);
+            trimLeading(symbol);
+            symbol = prefix + symbol;
+        }
+    }
+#endif
+    return lines;
+}
+
+
+// Useful for debug on occasion.
+std::string Utils::hexDump(const char *buf, size_t count)
+{
+   const unsigned char *cp = reinterpret_cast<const unsigned char *>(buf);
+   char foo[80];
+   int bytes, i, address = 0;
+   std::string out;
+
+   bytes = (count > 16) ? 16 : count;
+
+   while (bytes) {
+      sprintf(foo, "0x%06x ", address);
+      address += 16;
+      for (i = 0; i < 16; i++) {
+         if (i < bytes) {
+            sprintf(foo, "%02X ", cp[i]);
+            out += foo;
+         }
+         else
+            out += "   ";
+      }
+      out += "|";
+      for (i = 0; i < bytes; i++) {
+         sprintf(foo, "%c", isprint(cp[i]) ? cp[i] : '.');
+         out += foo;
+      }
+      out += "|\n";
+      count -= bytes;
+      cp += bytes;
+      bytes = (count > 16) ? 16 : count;
+   }
+   return (out);
+}
+
+} // namespace pdal
diff --git a/pdal/util/Utils.hpp b/pdal/util/Utils.hpp
new file mode 100644
index 0000000..ce55597
--- /dev/null
+++ b/pdal/util/Utils.hpp
@@ -0,0 +1,957 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cctype>
+#include <cmath>
+#include <cstdint>
+#include <cstring>
+#include <fstream>
+#include <iomanip>
+#include <istream>
+#include <limits>
+#include <map>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <typeinfo>
+#include <type_traits>
+#include <vector>
+
+#include "pdal_util_export.hpp"
+
+namespace pdal
+{
+
+namespace Utils
+{
+    /**
+     * \brief Clamp value to given bounds.
+     *
+     * Clamps the input value t to bounds specified by min and max. Used to ensure
+     * that row and column indices remain within valid bounds.
+     *
+     * \param t the input value.
+     * \param min the lower bound.
+     * \param max the upper bound.
+     * \return the value to clamped to the given bounds.
+     */
+    template <class T>
+    PDAL_DLL const T& clamp(const T& t, const T& min, const T& max)
+    {
+        return ((t < min) ? min : ((t > max) ? max : t));
+    }
+
+    /**
+      Set a seed for random number generation.
+
+      \param seed  Seed value.
+    */
+    PDAL_DLL void random_seed(unsigned int seed);
+
+    /**
+      Generate a random value in the range [minimum, maximum].
+
+      \param minimum  Lower value of range for random number generation.
+      \param maximum  Upper value of range for random number generation.
+    */
+    PDAL_DLL double random(double minimum, double maximum);
+
+    /**
+      Generate values in a uniform distribution in the range [minimum, maximum]
+      using the provided seed value.
+
+      \param double  Lower value of range for random number generation.
+      \param double  Upper value of range for random number generation.
+      \param seed    Seed value for random number generation.
+    */
+    PDAL_DLL double uniform(const double& minimum, const double& maximum,
+        uint32_t seed);
+    /**
+      Generate values in a normal distribution in the range [minimum, maximum]
+      using the provided seed value.
+
+      \param double  Lower value of range for random number generation.
+      \param double  Upper value of range for random number generation.
+      \param seed    Seed value for random number generation.
+    */
+    PDAL_DLL double normal(const double& mean, const double& sigma,
+        uint32_t seed);
+
+    /**
+      Determine if two values are within a particular range of each other.
+
+      \param v1  First value to compare.
+      \param v2  Second value to compare.
+      \param tolerance  Maximum difference between \ref v1 and \ref v2
+    */
+    PDAL_DLL inline bool compare_approx(double v1, double v2, double tolerance)
+    {
+        double diff = std::abs(v1 - v2);
+        return diff <= std::abs(tolerance);
+    }
+
+    /**
+      Round double value to nearest integral value.
+
+      \param r  Value to round
+      \return  Rounded value
+    */
+    inline double sround(double r)
+        { return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5); }
+
+    /**
+      Convert a string to lowercase.
+
+      \return  Converted string.
+    **/
+    inline std::string tolower(const std::string& s)
+    {
+        std::string out;
+        for (size_t i = 0; i < s.size(); ++i)
+            out += (char)std::tolower(s[i]);
+        return out;
+    }
+
+    /**
+      Convert a string to uppercase.
+
+      \return  Converted string.
+    */
+    inline std::string toupper(const std::string& s)
+    {
+        std::string out;
+        for (size_t i = 0; i < s.size(); ++i)
+            out += (char)std::toupper(s[i]);
+        return out;
+    }
+
+    /**
+      Compare strings in a case-insensitive manner.
+
+      \param s  First string to compare.
+      \param s2  Second string to compare.
+      \return  Whether the strings are equal.
+    */
+    inline bool iequals(const std::string& s, const std::string& s2)
+    {
+        if (s.length() != s2.length())
+            return false;
+        for (size_t i = 0; i < s.length(); ++i)
+            if (std::toupper(s[i]) != std::toupper(s2[i]))
+                return false;
+        return true;
+    }
+
+    /**
+      Determine if a string starts with a particular prefix.
+
+      \param s  String to check for prefix.
+      \param prefix  Prefix to search for.
+      \return  Whether the string begins with the prefix.
+    */
+    inline bool startsWith(const std::string& s, const std::string& prefix)
+    {
+        if (prefix.size() > s.size())
+            return false;
+        return (strncmp(prefix.data(), s.data(), prefix.size()) == 0);
+    }
+
+    /**
+      Generate a checksum that is the integer sum of the values of the bytes
+      in a buffer.
+
+      \param buf  Pointer to buffer.
+      \param size  Size of buffer.
+      \return  Generated checksum.
+    */
+    inline int cksum(char *buf, size_t size)
+    {
+        int i = 0;
+        while (size--)
+            i += *buf++;
+        return i;
+    }
+
+    /**
+      Fetch the value of an environment variable.
+
+      \param name  Name of environment varaible.
+      \param name  Value of the environemnt variable if it exists, empty
+        otherwise.
+      \return  0 on success, -1 on failure
+    */
+    PDAL_DLL int getenv(std::string const& name, std::string& val);
+
+    /**
+      Set the value of an environment variable.
+
+      \param env  Name of environment variable.
+      \param val  Value of environment variable.
+      \return  0 on success, -1 on failure
+    */
+    PDAL_DLL int setenv(const std::string& env, const std::string& val);
+
+    /**
+      Clear the value of an environment variable.
+
+      \param env  Name of the environment variable to clear.
+      \return  0 on success, -1 on failure
+    */
+    PDAL_DLL int unsetenv(const std::string& env);
+
+    /**
+      Skip stream input until a non-space character is found.
+
+      \param s  Stream to process.
+    */
+    PDAL_DLL void eatwhitespace(std::istream& s);
+
+    /**
+      Remove whitspace from the beginning of a string.
+
+      \param s  String to be trimmed.
+    */
+    PDAL_DLL void trimLeading(std::string& s);
+
+    /**
+      Remove whitspace from the end of a string.
+
+      \param s  String to be trimmed.
+    */
+    PDAL_DLL void trimTrailing(std::string& s);
+
+    /**
+      Remove whitespace from the beginning and end of a string.
+
+      \param s  String to be trimmed.
+    */
+    inline void trim(std::string& s)
+    {
+        trimLeading(s);
+        trimTrailing(s);
+    }
+
+    /**
+      If specified character is at the current stream position, advance the
+      stream position by 1.
+
+      \param s  Stream to insect.
+      \param x  Character to check for.
+      \return \c true if the character is at the current stream position,
+        \c false otherwise.
+    */
+    PDAL_DLL bool eatcharacter(std::istream& s, char x);
+
+    /**
+      Convert a buffer to a string using base64 encoding.
+
+      \param buf  Pointer to buffer to encode.
+      \param size  Size of buffer.
+      \return  Encoded buffer.
+    */
+    PDAL_DLL std::string base64_encode(const unsigned char *buf, size_t size);
+
+    /**
+      Convert a buffer to a string using base64 encoding.
+
+      \param bytes  Pointer to buffer to encode.
+      \return  Encoded buffer.
+    */
+    inline std::string base64_encode(std::vector<uint8_t> const& bytes)
+        { return base64_encode(bytes.data(), bytes.size()); }
+
+    /**
+      Decode a base64-encoded string into a buffer.
+
+      \param input  String to decode.
+      \return  Buffer containing decoded string.
+    */
+    PDAL_DLL std::vector<uint8_t>
+    base64_decode(std::string const& input);
+
+    /**
+      Start a process to run a command and open a pipe to which input can
+      be written and from which output can be read.
+
+      \param command  Command to run in subprocess.
+      \mode  Either 'r', 'w' or 'r+' to specify if the pipe should be opened
+        as read-only, write-only or read-write.
+      \return  Pointer to FILE for input/output from the subprocess.
+    */
+    PDAL_DLL FILE* portable_popen(const std::string& command,
+        const std::string& mode);
+    /**
+      Close file opened with \ref portable_popen.
+
+      \param fp  Pointer to file to close.
+      \return  0 on success, -1 on failure.
+    */
+    PDAL_DLL int portable_pclose(FILE* fp);
+
+    /**
+      Create a subprocess and set the standard output of the command into the
+      provided output string.
+
+      \param cmd  Command to run.
+      \param output  String to which output from the command should be written,
+    */
+    PDAL_DLL int run_shell_command(const std::string& cmd, std::string& output);
+
+    /**
+      Replace all instances of one string found in the input with another value.
+
+      \param input  Input string to modify.
+      \param replaceWhat  Token to locate in input string.
+      \param replaceWithWhat  Replacement for found tokens.
+      \return  Modified version of input string.
+    */
+    PDAL_DLL std::string replaceAll(std::string input,
+        const std::string& replaceWhat , const std::string& replaceWithWhat);
+
+    /**
+      Break a string into a list of strings to not exceed a specified length.
+      Whitespace is condensed to a single space and each string is free of
+      whitespace at the beginning and end when possible.  Optionally, a line
+      length for the first line can be different from subsequent lines.
+
+      \param inputString  String to split into substrings.
+      \param lineLength  Maximum length of substrings.
+      \param firstLength  When non-zero, the maximum length of the first
+        substring.  When zero, the first firstLength is assigned the value
+        provided in lineLength.
+      \return  List of substrings generated from the input string.
+    */
+    PDAL_DLL std::vector<std::string> wordWrap(std::string const& inputString,
+        size_t lineLength, size_t firstLength = 0);
+
+    /**
+      Break a string into a list of strings to not exceed a specified length.
+      The concatanation of the returned substrings will yield the original
+      string.  The algorithm attempts to break the original string such that
+      each substring begins with a word.
+
+      \param inputString  String to split into substrings.
+      \param lineLength  Maximum length of substrings.
+      \param firstLength  When non-zero, the maximum length of the first
+        substring.  When zero, the first firstLength is assigned the value
+        provided in lineLength.
+      \return  List of substrings generated from the input string.
+    */
+    PDAL_DLL std::vector<std::string> wordWrap2(std::string const& inputString,
+        size_t lineLength, size_t firstLength = 0);
+
+    /**
+      Add escape characters or otherwise transform an input string so as to
+      be a valid JSON string.
+
+      \param s  Input string.
+      \return  Valid JSON version of input string.
+    */
+    PDAL_DLL std::string escapeJSON(const std::string &s);
+
+    /**
+      Demangle a C++ symbol into readable form.
+
+      \param s  String to demangle.
+      \return  Demangled symbol.
+    */
+    PDAL_DLL std::string demangle(const std::string& s);
+
+    /**
+      Return the screen width of an associated tty.
+
+      \return  The tty screen width or 80 if on Windows or it can't be
+        determined.
+    */
+    PDAL_DLL int screenWidth();
+
+    /**
+      Escape non-printing characters by using standard notation (i.e. \n)
+      or hex notation (\x10) as as necessary.
+
+      \param s  String to modify.
+      \return  Copy of input string with non-printing characters converted
+        to printable notation.
+    */
+    PDAL_DLL std::string escapeNonprinting(const std::string& s);
+
+    /**
+      Normalize longitude so that it's between (-180, 180].
+
+      \param longitude  Longitude to normalize.
+      \return  Normalized longitude.
+    */
+    PDAL_DLL double normalizeLongitude(double longitude);
+
+    /**
+      Convert an input buffer to a hexadecimal string representation similar
+      to the output of the UNIX command 'od'.  This is mostly used as an
+      occasional debugging aid.
+
+      \param buf  Point to buffer to dump.
+      \param count  Size of buffer.
+      \return  Buffer converted to hex string.
+    */
+    PDAL_DLL std::string hexDump(const char *buf, size_t count);
+
+    /**
+      Generate a backtrace as a list of strings.
+
+      \return  List of functions at the point of the call.
+    */
+    PDAL_DLL std::vector<std::string> backtrace();
+
+    /**
+      Count the number of characters in a string that meet a predicate.
+
+      \param s  String in which to start counting characters.
+      \param p  Position in input string at which to start counting.
+      \param pred  Unary predicte that tests a character.
+      \return  Then number of characters matching the predicate.
+    */
+    template<typename PREDICATE>
+    PDAL_DLL std::string::size_type
+    extract(const std::string& s, std::string::size_type p, PREDICATE pred)
+    {
+        std::string::size_type count = 0;
+        while (pred(s[p++]))
+            count++;
+        return count;
+    }
+
+    /**
+      Split a string into substrings based on a predicate.  Characters
+      matching the predicate are discarded.
+
+      \param s  String to split.
+      \param p  Unary predicate that returns true to indicate that a character
+        is a split location.
+      \return  Substrings.
+    */
+    template<typename PREDICATE>
+    PDAL_DLL std::vector<std::string> split(const std::string& s, PREDICATE p)
+    {
+        std::vector<std::string> result;
+
+        if (s.empty())
+            return result;
+
+        auto it = s.cbegin();
+        auto const end = s.cend();
+        decltype(it) nextIt;
+        do
+        {
+            nextIt = std::find_if(it, end, p);
+            result.push_back(std::string(it, nextIt));
+
+            // Avoid advancing the iterator past the end to avoid UB.
+            if (nextIt != end)
+                it = nextIt + 1;
+        } while (nextIt != end);
+
+        return result;
+    }
+
+    /**
+      Split a string into substrings.  Characters matching the predicate are
+      discarded, as are empty strings otherwise produced by \ref split().
+
+      \param s  String to split.
+      \param p  Predicate returns true if a char in a string is a split
+        location.
+      \return  Vector of substrings.
+    */
+    template<typename PREDICATE>
+    PDAL_DLL std::vector<std::string> split2(const std::string& s, PREDICATE p)
+    {
+        std::vector<std::string> result;
+
+        if (s.empty())
+            return result;
+
+        auto it = s.cbegin();
+        auto const end = s.cend();
+        decltype(it) nextIt;
+        do
+        {
+            nextIt = std::find_if(it, end, p);
+            if (it != nextIt)
+                result.push_back(std::string(it, nextIt));
+
+            // Avoid advancing the iterator past the end to avoid UB.
+            if (nextIt != end)
+                it = nextIt + 1;
+        } while (nextIt != end);
+
+        return result;
+    }
+
+    /**
+      Split a string into substrings based a splitting character.  The
+      splitting characters are discarded.
+
+      \param s  String to split.
+      \param p  Character indicating split positions.
+      \return  Substrings.
+    */
+    inline PDAL_DLL std::vector<std::string>
+    split(const std::string& s, char tChar)
+    {
+        auto pred = [tChar](char c){ return(c == tChar); };
+        return split(s, pred);
+    }
+
+    /**
+      Split a string into substrings based a splitting character.  The
+      splitting characters are discarded as are empty strings otherwise
+      produced by \ref split().
+
+      \param s  String to split.
+      \param p  Character indicating split positions.
+      \return  Substrings.
+    */
+    inline PDAL_DLL std::vector<std::string>
+    split2(const std::string& s, char tChar)
+    {
+        auto pred = [tChar](char c){ return(c == tChar); };
+        return split2(s, pred);
+    }
+
+    /**
+      Return a string representation of a type specified by the template
+      argument.
+
+      \return  String representation of the type.
+    */
+    template<typename T>
+    std::string typeidName()
+        { return Utils::demangle(typeid(T).name()); }
+
+    struct RedirectStream
+    {
+        RedirectStream() : m_out(NULL), m_out2(NULL), m_buf(NULL)
+        {}
+
+        std::ofstream *m_out;
+        std::ostream *m_out2;
+        std::streambuf *m_buf;
+    };
+
+    /**
+      Redirect a stream to some other stream.
+
+      \param out   Stream to redirect.
+      \param dst   Destination stream.
+      \return  Context for stream restoration (see \ref restore()).
+    */
+    inline RedirectStream redirect(std::ostream& out, std::ostream& dst)
+    {
+        RedirectStream redir;
+
+        redir.m_out2 = &dst;
+        redir.m_buf = out.rdbuf();
+        out.rdbuf(redir.m_out2->rdbuf());
+        return redir;
+    }
+
+    /**
+      Redirect a stream to some file, by default /dev/null.
+
+      \param out   Stream to redirect.
+      \param file  Name of file where stream should be redirected.
+      \return  Context for stream restoration (see \ref restore()).
+    */
+    inline RedirectStream redirect(std::ostream& out,
+        const std::string& file = "/dev/null")
+    {
+        RedirectStream redir;
+
+        redir.m_out = new std::ofstream(file);
+        redir.m_buf = out.rdbuf();
+        out.rdbuf(redir.m_out->rdbuf());
+        return redir;
+    }
+
+    /**
+      Restore a stream redirected with redirect().
+
+      \param out  Stream to be restored.
+      \param redir RedirectStream returned from corresponding
+        \ref redirect() call.
+    */
+    inline void restore(std::ostream& out, RedirectStream redir)
+    {
+        out.rdbuf(redir.m_buf);
+        if (redir.m_out)
+            redir.m_out->close();
+        redir.m_out = NULL;
+        redir.m_out2 = NULL;
+        redir.m_buf = NULL;
+    }
+
+    //ABELL - This is certainly not as efficient as boost::numeric_cast, but
+    //  has the advantage of not requiring an exception to indicate an error.
+    //  We should investigate incorporating a version of boost::numeric_cast
+    //  that avoids the exception for an error.
+    /**
+      Determine whether a double value may be safely converted to the given
+      output type without over/underflow.  If the output type is integral the
+      input will be rounded before being tested.
+
+      \param in  Value to range test.
+      \return  Whether value can be safely converted to template type.
+    */
+    template<typename T_OUT>
+    bool inRange(double in)
+    {
+        if (std::is_integral<T_OUT>::value)
+        {
+            in = sround((double)in);
+        }
+
+        return std::is_same<double, T_OUT>::value ||
+           (in >= static_cast<double>(std::numeric_limits<T_OUT>::lowest()) &&
+            in <= static_cast<double>(std::numeric_limits<T_OUT>::max()));
+    }
+
+    /**
+      Determine whether a value may be safely converted to the given
+      output type without over/underflow.  If the output type is integral and
+      different from the input time, the value will be rounded before being
+      tested.
+
+      \param in  Value to range test.
+      \return  Whether value can be safely converted to template type.
+    */
+    template<typename T_IN, typename T_OUT>
+    bool inRange(T_IN in)
+    {
+        return std::is_same<T_IN, T_OUT>::value ||
+            inRange<T_OUT>(static_cast<double>(in));
+    }
+
+    /**
+      Convert a numeric value from one type to another.  Floating point
+      values are rounded to the nearest integer before a conversion is
+      attempted.
+
+      \param in  Value to convert.
+      \param out  Converted value.
+      \return  \c true if the conversion was successful, \c false if the
+        datatypes/input value don't allow conversion.
+    */
+    template<typename T_IN, typename T_OUT>
+    bool numericCast(T_IN in, T_OUT& out)
+    {
+        if (std::is_same<T_IN, T_OUT>::value)
+        {
+            out = static_cast<T_OUT>(in);
+            return true;
+        }
+        if (std::is_integral<T_OUT>::value)
+            in = static_cast<T_IN>(sround((double)in));
+        if ((std::is_same<T_OUT, double>::value) ||
+            (in <= static_cast<double>(std::numeric_limits<T_OUT>::max()) &&
+             in >= static_cast<double>(std::numeric_limits<T_OUT>::lowest())))
+        {
+            out = static_cast<T_OUT>(in);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+      Convert a value to its string representation by writing to a stringstream.
+
+      \param from  Value to convert.
+      \return  String representation.
+    */
+    template<typename T>
+    std::string toString(const T& from)
+    {
+        std::ostringstream oss;
+        oss << from;
+        return oss.str();
+    }
+
+    /**
+      Convert a bool to a string.
+    */
+    inline std::string toString(bool from)
+    {
+        return from ? "true" : "false";
+    }
+
+    /**
+      Convert a double to string with a precision of 10 decimal places.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(double from)
+    {
+        std::ostringstream oss;
+        oss << std::setprecision(10) << from;
+        return oss.str();
+    }
+
+    /**
+      Convert a float to string with a precision of 10 decimal places.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(float from)
+    {
+        std::ostringstream oss;
+        oss << std::setprecision(8) << from;
+        return oss.str();
+    }
+
+    /**
+      Convert a long long int to string.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(long long from)
+        { return std::to_string(from); }
+
+    /**
+      Convert an unsigned long long int to string.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(unsigned long from)
+        { return std::to_string(from); }
+
+    /**
+      Convert a long int to string.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(long from)
+        { return std::to_string(from); }
+
+    /**
+      Convert an unsigned int to string.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(unsigned int from)
+        { return std::to_string(from); }
+
+    /**
+      Convert an int to string.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(int from)
+        { return std::to_string(from); }
+
+    /**
+      Convert an unsigned short to string.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(unsigned short from)
+        { return std::to_string((int)from); }
+
+    /**
+      Convert a short int to string.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(short from)
+        { return std::to_string((int)from); }
+
+    /**
+      Convert a char (treated as numeric) to string.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(char from)
+        { return std::to_string((int)from); }
+
+    /**
+      Convert an unsigned char (treated as numeric) to string.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(unsigned char from)
+        { return std::to_string((int)from); }
+
+    /**
+      Convert a signed char (treated as numeric) to string.
+
+      \param from  Value to convert.
+      \return  String representation of numeric value.
+    */
+    inline std::string toString(signed char from)
+        { return std::to_string((int)from); }
+
+    /**
+      Convert a string to a value by reading from a string stream.
+
+      \param from  String to convert.
+      \param to  Converted value.
+      \return  \c true if the conversion was successful, \c false otherwise.
+    */
+    template<typename T>
+    bool fromString(const std::string& from, T& to)
+    {
+        std::istringstream iss(from);
+
+        iss >> to;
+        return !iss.fail();
+    }
+
+    // Optimization of above.
+    template<>
+    inline bool fromString(const std::string& from, std::string& to)
+    {
+        to = from;
+        return true;
+    }
+
+    /**
+      Convert a numeric string to a char numeric value.
+
+      \parm s  String to convert.
+      \param to  Converted numeric value.
+      \return  \c true if the conversion was successful, \c false otherwise.
+    */
+    template<>
+    inline bool fromString<char>(const std::string& s, char& to)
+    {
+        int i = std::stoi(s);
+        if (i >= std::numeric_limits<char>::lowest() &&
+            i <= std::numeric_limits<char>::max())
+        {
+            to = static_cast<char>(i);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+      Convert a numeric string to an unsigned char numeric value.
+
+      \parm s  String to convert.
+      \param to  Converted numeric value.
+      \return  \c true if the conversion was successful, \c false otherwise.
+    */
+    template<>
+    inline bool fromString<unsigned char>(const std::string& s,
+        unsigned char& to)
+    {
+        int i = std::stoi(s);
+        if (i >= std::numeric_limits<unsigned char>::lowest() &&
+            i <= std::numeric_limits<unsigned char>::max())
+        {
+            to = static_cast<unsigned char>(i);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+      Convert a numeric string to a signed char numeric value.
+
+      \parm s  String to convert.
+      \param to  Converted numeric value.
+      \return  \c true if the conversion was successful, \c false otherwise.
+    */
+    template<>
+    inline bool fromString<signed char>(const std::string& s, signed char& to)
+    {
+        int i = std::stoi(s);
+        if (i >= std::numeric_limits<signed char>::lowest() &&
+            i <= std::numeric_limits<signed char>::max())
+        {
+            to = static_cast<signed char>(i);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+      Specialization conversion from string to double to handle Nan.
+
+      \param s  String to be converted.
+      \param d  Converted value.
+      \return  \c true if the conversion was successful, \c false otherwise.
+    */
+    template<>
+    inline bool fromString<double>(const std::string& s, double& d)
+    {
+        if (s == "nan" || s == "NaN")
+        {
+            d = std::numeric_limits<double>::quiet_NaN();
+            return true;
+        }
+
+        std::istringstream iss(s);
+
+        iss >> d;
+        return !iss.fail();
+    }
+
+    /**
+      Return the argument cast to its underlying type.  Typically used on
+      an enum.
+
+      \param e  Variable for which to find the underlying type.
+      \return  Converted variable.
+    */
+    template<typename E>
+    typename std::underlying_type<E>::type toNative(E e)
+    {
+        return static_cast<typename std::underlying_type<E>::type>(e);
+    }
+
+} // namespace Utils
+} // namespace pdal
diff --git a/include/pdal/util/Uuid.hpp b/pdal/util/Uuid.hpp
similarity index 100%
rename from include/pdal/util/Uuid.hpp
rename to pdal/util/Uuid.hpp
diff --git a/include/pdal/util/pdal_util_export.hpp b/pdal/util/pdal_util_export.hpp
similarity index 100%
rename from include/pdal/util/pdal_util_export.hpp
rename to pdal/util/pdal_util_export.hpp
diff --git a/include/pdal/util/portable_endian.hpp b/pdal/util/portable_endian.hpp
similarity index 100%
rename from include/pdal/util/portable_endian.hpp
rename to pdal/util/portable_endian.hpp
diff --git a/pdal_defines.h.in b/pdal_defines.h.in
index a0b88a6..77dca12 100644
--- a/pdal_defines.h.in
+++ b/pdal_defines.h.in
@@ -30,7 +30,6 @@
 #cmakedefine PDAL_HAVE_LASZIP
 #cmakedefine PDAL_HAVE_LAZPERF
 #cmakedefine PDAL_HAVE_LIBXML2
-#cmakedefine PDAL_HAVE_LIBGEOTIFF
 
 #cmakedefine PDAL_ARBITER_ENABLED
 
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index ea95165..49302e0 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -8,7 +8,7 @@ if(BUILD_PLUGIN_GEOWAVE)
     add_subdirectory(geowave)
 endif(BUILD_PLUGIN_GEOWAVE)
 
-if(BUILD_PLUGIN_GREYHOUND)
+if(BUILD_PLUGIN_GREYHOUND AND CURL_FOUND)
     add_subdirectory(greyhound)
 endif()
 
@@ -17,6 +17,9 @@ if(BUILD_PLUGIN_HEXBIN)
 endif()
 
 if(BUILD_PLUGIN_ICEBRIDGE)
+    if (NOT PDAL_HAVE_LIBXML2)
+        message(FATAL_ERROR "Can't build icebridge plugin without libxml2")
+    endif()
     add_subdirectory(icebridge)
 endif()
 
@@ -33,6 +36,9 @@ if(BUILD_PLUGIN_NITF)
 endif()
 
 if(BUILD_PLUGIN_OCI)
+    if (NOT PDAL_HAVE_LIBXML2)
+        message(FATAL_ERROR "Can't build OCI plugin without libxml2")
+    endif()
     add_subdirectory(oci)
 endif()
 
@@ -45,6 +51,9 @@ if(BUILD_PLUGIN_PCL)
 endif()
 
 if(BUILD_PLUGIN_PGPOINTCLOUD)
+    if (NOT PDAL_HAVE_LIBXML2)
+        message(FATAL_ERROR "Can't build pgpointcloud plugin without libxml2")
+    endif()
     add_subdirectory(pgpointcloud)
 endif()
 
@@ -57,5 +66,8 @@ if(BUILD_PLUGIN_RIVLIB)
 endif(BUILD_PLUGIN_RIVLIB)
 
 if(BUILD_PLUGIN_SQLITE)
+    if (NOT PDAL_HAVE_LIBXML2)
+        message(FATAL_ERROR "Can't build sqlite plugin without libxml2")
+    endif()
     add_subdirectory(sqlite)
 endif()
diff --git a/plugins/cpd/CMakeLists.txt b/plugins/cpd/CMakeLists.txt
index 30d0aa3..671aa5b 100644
--- a/plugins/cpd/CMakeLists.txt
+++ b/plugins/cpd/CMakeLists.txt
@@ -11,20 +11,18 @@ PDAL_ADD_PLUGIN(cpd_kernel_lib_name kernel cpd
     FILES kernel/Cpd.cpp
     LINK_WITH Cpd::Library-C++
     )
-target_include_directories(${cpd_kernel_lib_name}
-    PRIVATE
-    ${CMAKE_CURRENT_LIST_DIR}
-    )
+target_include_directories(${cpd_kernel_lib_name} PRIVATE
+    ${ROOT_DIR}
+    ${CMAKE_CURRENT_LIST_DIR} )
 
-if(WITH_TESTS)
+if (WITH_TESTS)
     PDAL_ADD_TEST(pdal_plugins_cpd_kernel_test
         FILES test/CpdKernelTest.cpp
         LINK_WITH Cpd::Library-C++
         )
     target_include_directories(pdal_plugins_cpd_kernel_test
         PRIVATE
+        ${ROOT_DIR}
         ${CMAKE_CURRENT_LIST_DIR}
-        ${PROJECT_SOURCE_DIR}/io/las
-        ${PROJECT_SOURCE_DIR}/test/unit
         )
 endif()
diff --git a/plugins/cpd/kernel/Cpd.cpp b/plugins/cpd/kernel/Cpd.cpp
index d967088..b9f4658 100644
--- a/plugins/cpd/kernel/Cpd.cpp
+++ b/plugins/cpd/kernel/Cpd.cpp
@@ -40,10 +40,8 @@
 
 #include <pdal/KernelFactory.hpp>
 #include <pdal/StageFactory.hpp>
-
-#include "chipper/ChipperFilter.hpp"
-#include "crop/CropFilter.hpp"
-#include "buffer/BufferReader.hpp"
+#include <filters/CropFilter.hpp>
+#include <io/BufferReader.hpp>
 
 namespace pdal
 {
diff --git a/plugins/cpd/test/CpdKernelTest.cpp b/plugins/cpd/test/CpdKernelTest.cpp
index 4429677..59d6b0f 100644
--- a/plugins/cpd/test/CpdKernelTest.cpp
+++ b/plugins/cpd/test/CpdKernelTest.cpp
@@ -40,7 +40,8 @@
 #include <pdal/PipelineManager.hpp>
 #include <pdal/PluginManager.hpp>
 #include <pdal/Reader.hpp>
-#include "LasReader.hpp"
+#include <pdal/util/FileUtils.hpp>
+#include <io/LasReader.hpp>
 #include "Support.hpp"
 
 namespace pdal
diff --git a/plugins/greyhound/CMakeLists.txt b/plugins/greyhound/CMakeLists.txt
index 2049fad..f6c9370 100644
--- a/plugins/greyhound/CMakeLists.txt
+++ b/plugins/greyhound/CMakeLists.txt
@@ -2,32 +2,25 @@
 # Greyhound plugin CMake configuration
 #
 
-include_directories(${PDAL_JSONCPP_INCLUDE_DIR})
 add_definitions(-DHAVE_JSONCPP=1)
 
-set(srcs
-    io/CompressionStream.cpp
-    io/GreyhoundReader.cpp
-    io/bbox.cpp
-)
-
-set(incs
-    io/CompressionStream.hpp
-    io/GreyhoundReader.hpp
-    io/bbox.hpp
-    io/point.hpp
-)
-
 PDAL_ADD_PLUGIN(libname reader greyhound
-    FILES "${srcs}" "${incs}"
+    FILES
+        io/CompressionStream.cpp
+        io/GreyhoundReader.cpp
+        io/bounds.cpp
+        io/pool.cpp
     LINK_WITH ${PDAL_JSONCPP_LIB_NAME})
+target_include_directories(${libname} PRIVATE
+    ${PDAL_JSONCPP_INCLUDE_DIR}
+    ${PDAL_VENDOR_DIR}) 
 
-set(srcs
-    test/GreyhoundReaderTest.cpp
-)
-
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-PDAL_ADD_TEST(greyhoundreadertest
-    FILES "${srcs}"
-    LINK_WITH ${libname} )
+if (WITH_TESTS)
+    PDAL_ADD_TEST(greyhoundreadertest
+        FILES
+            test/GreyhoundReaderTest.cpp
+        LINK_WITH ${libname} )
+target_include_directories(greyhoundreadertest PRIVATE
+    ${PDAL_JSONCPP_INCLUDE_DIR}
+    ${PDAL_VENDOR_DIR}) 
+endif()
diff --git a/plugins/greyhound/io/GreyhoundReader.cpp b/plugins/greyhound/io/GreyhoundReader.cpp
index 2826b74..54bee77 100644
--- a/plugins/greyhound/io/GreyhoundReader.cpp
+++ b/plugins/greyhound/io/GreyhoundReader.cpp
@@ -33,8 +33,9 @@
 ****************************************************************************/
 
 #include "GreyhoundReader.hpp"
-#include "bbox.hpp"
-#include "dir.hpp"
+
+#include <sstream>
+
 #include <pdal/pdal_macros.hpp>
 #include <pdal/Compression.hpp>
 #include <pdal/util/ProgramArgs.hpp>
@@ -45,510 +46,677 @@ namespace pdal
 static PluginInfo const s_info = PluginInfo(
     "readers.greyhound",
     "Greyhound Reader",
-    "http://pdal.io/stages/readers.greyhound.html" );
+    "http://pdal.io/stages/readers.greyhound.html");
 
 CREATE_SHARED_PLUGIN(1, 0, GreyhoundReader, Reader, s_info)
 
 std::string GreyhoundReader::getName() const { return s_info.name; }
 
-GreyhoundReader::GreyhoundReader()
-    : Reader()
-    , m_url()
-    , m_resource()
-    , m_numPoints(0)
-    , m_index(0)
-    , m_depthBegin(0)
-    , m_depthEnd(std::numeric_limits<uint32_t>::max())
-    , m_baseDepth(0)
-    , m_stopSplittingDepth(0)
-    , m_split(0)
-    , m_retryCount(1)
-    , m_timeout(0)
-    , m_splitCountThreshold(0)
-{ }
-
-GreyhoundReader::~GreyhoundReader()
-{
-}
-
-DimTypeList GreyhoundReader::getSchema(const Json::Value& jsondata) const
+namespace
 {
-    DimTypeList output;
-
-    if (jsondata.isMember("schema") &&
-        jsondata["schema"].isArray())
+    Json::Value parse(const std::string& data)
     {
-        Json::Value jsonDimArray(jsondata["schema"]);
+        Json::Value json;
+        Json::Reader reader;
 
-        for (std::size_t i(0); i < jsonDimArray.size(); ++i)
+        if (data.size())
         {
-            const Json::Value& jsonDim(
-                    jsonDimArray[static_cast<Json::ArrayIndex>(i)]);
-
-            const Dimension::Id id(
-                    Dimension::id(jsonDim["name"].asString()));
-
-            const Dimension::Type type(
-                static_cast<Dimension::Type>(
-                    static_cast<int>(Dimension::fromName(
-                        jsonDim["type"].asString())) |
-                    std::stoi(jsonDim["size"].asString())));
-
-            DimType d;
-            d.m_id = id;
-            d.m_type = type;
-            output.push_back(d);
+            if (!reader.parse(data, json, false))
+            {
+                const std::string jsonError(reader.getFormattedErrorMessages());
+                if (!jsonError.empty())
+                {
+                    throw std::runtime_error(
+                            "Error during parsing: " + jsonError);
+                }
+            }
         }
+
+        return json;
     }
 
-    return output;
-}
-BOX3D GreyhoundReader::getBounds(const Json::Value& jsondata, const std::string& memberName) const
-{
-    BOX3D output;
-    if (jsondata.isMember(memberName) &&
-        jsondata[memberName].isArray())
+    std::string write(const Json::Value& json)
     {
-        Json::Value bounds(jsondata[memberName]);
-
-        output.minx = bounds[0].asDouble();
-        output.miny = bounds[1].asDouble();
-        output.minz = bounds[2].asDouble();
-        output.maxx = bounds[3].asDouble();
-        output.maxy = bounds[4].asDouble();
-        output.maxz = bounds[5].asDouble();
+        Json::StreamWriterBuilder builder;
+        builder.settings_["indentation"] = "";
+        return Json::writeString(builder, json);
     }
-    else
+
+    DimTypeList schemaToDims(const Json::Value& json)
     {
-        throw pdal_error("Greyhound info response has no \"" + memberName + "\" member");
-    }
+        DimTypeList output;
 
-    return output;
-}
+        // XYZ might be natively stored as integral values, typically when the
+        // disk-storage for Entwine is scaled/offset.  Since we're abstracting
+        // this out, always ask for XYZ as doubles.
+        auto isXyz([](Dimension::Id id)
+        {
+            return
+                id == Dimension::Id::X ||
+                id == Dimension::Id::Y ||
+                id == Dimension::Id::Z;
+        });
 
-Json::Value GreyhoundReader::fetch(const std::string& url) const
-{
-    Json::Value config;
-    if (log()->getLevel() > LogLevel::Debug4)
-        config["arbiter"]["verbose"] = true;
-    config["http"]["timeout"] = m_timeout;
-    arbiter::Arbiter a(config);
-    auto response = a.get(url);
+        if (!json.isNull() && json.isArray())
+        {
+            for (const auto& jsonDim : json)
+            {
+                const Dimension::Id id(
+                        Dimension::id(jsonDim["name"].asString()));
 
-    Json::Value jsonResponse;
-    Json::Reader jsonReader;
-    jsonReader.parse(response, jsonResponse);
+                const int baseType(
+                        Utils::toNative(
+                            Dimension::fromName(jsonDim["type"].asString())));
 
-    return jsonResponse;
+                const int size(jsonDim["size"].asUInt64());
 
-}
+                const Dimension::Type type(
+                        isXyz(id) ?
+                            Dimension::Type::Double :
+                            static_cast<Dimension::Type>(baseType | size));
 
+                output.emplace_back(id, type);
+            }
+        }
 
-void GreyhoundReader::initialize(PointTableRef table)
-{
-    std::string info_url = m_url + "/resource/" + m_resource + "/info";
-    log()->get(LogLevel::Info) << "fetching info URL " << info_url << std::endl;
+        return output;
+    }
 
-    m_resourceInfo = fetch(info_url);
+    Json::Value layoutToSchema(const PointLayout& layout)
+    {
+        Json::Value result;
 
-    m_dimData = getSchema(m_resourceInfo);
-    m_conformingBounds= getBounds(m_resourceInfo, "boundsConforming");
-    m_stopSplittingDepth = std::log(m_resourceInfo["numPoints"].asInt64()) / std::log(4);
+        std::map<std::size_t, const Dimension::Detail> details;
 
-    std::string srs = m_resourceInfo["srs"].asString();
-    setSpatialReference(SpatialReference(srs));
+        for (const Dimension::Id id : layout.dims())
+        {
+            const auto& d(*layout.dimDetail(id));
+            details.emplace(d.offset(), d);
+        }
 
-    m_baseDepth = m_resourceInfo["baseDepth"].asUInt();
+        for (const auto& p : details)
+        {
+            const auto& d(p.second);
+            Json::Value j;
+            j["name"] = Dimension::name(d.id());
+            j["type"] = Dimension::toName(base(d.type()));
+            j["size"] = static_cast<int>(Dimension::size(d.type()));
+            result.append(j);
+        }
 
-    PointLayoutPtr layout = table.layout();
+        return result;
+    }
 
-}
+    greyhound::Bounds zoom(
+            const greyhound::Bounds& queryBounds,
+            const greyhound::Bounds& fullBounds,
+            std::size_t& split)
+    {
+        auto currentBounds(fullBounds);
 
-pdal::greyhound::BBox makeBox(BOX3D bounds)
-{
-    pdal::greyhound::Point minimum;
-    pdal::greyhound::Point maximum;
+        greyhound::Dir dir(
+                pdal::greyhound::getDirection(
+                    currentBounds.mid(),
+                    queryBounds.mid()));
 
-    minimum.x = bounds.minx;
-    minimum.y = bounds.miny;
-    minimum.z = bounds.minz;
+        while (currentBounds.get(dir, true).contains(queryBounds))
+        {
+            currentBounds.go(dir, true);
+            ++split;
 
-    maximum.x = bounds.maxx;
-    maximum.y = bounds.maxy;
-    maximum.z = bounds.maxz;
-    pdal::greyhound::BBox box(minimum, maximum, true);
-    return box;
+            dir = pdal::greyhound::getDirection(
+                    currentBounds.mid(),
+                    queryBounds.mid());
+        }
 
-}
-BOX3D zoom(BOX3D query, BOX3D fullBox, int& split)
-{
+        return currentBounds;
+    }
 
-    pdal::greyhound::BBox queryBox = makeBox(query);
-    pdal::greyhound::BBox currentBox = makeBox(fullBox);
+    std::string stringify(const greyhound::Bounds& bounds)
+    {
+        std::stringstream ss;
+        ss << std::fixed;
+        ss <<
+            "[" <<
+            bounds.min().x << "," << bounds.min().y << "," << bounds.min().z <<
+            "," <<
+            bounds.max().x << "," << bounds.max().y << "," << bounds.max().z <<
+            "]";
+        return ss.str();
+    }
 
-    while (currentBox.contains(queryBox))
+    BOX3D toBox(const greyhound::Bounds& bounds)
     {
-        currentBox.go(pdal::greyhound::getDirection(queryBox.mid(), currentBox.mid()));
-        split++;
+        return BOX3D(
+                bounds.min().x, bounds.min().y, bounds.min().z,
+                bounds.max().x, bounds.max().y, bounds.max().z);
     }
 
-    BOX3D output;
-    output.minx = currentBox.min().x; output.maxx = currentBox.max().x;
-    output.miny = currentBox.min().y; output.maxy = currentBox.max().y;
-    output.minz = currentBox.min().z; output.maxz = currentBox.max().z;
+    pdal::greyhound::Bounds toBounds(const BOX3D& box)
+    {
+        return greyhound::Bounds(
+                box.minx, box.miny, box.minz,
+                box.maxx, box.maxy, box.maxz);
+    }
 
-    return output;
+    const double dmin(std::numeric_limits<double>::lowest());
+    const double dmax(std::numeric_limits<double>::max());
+    const BOX3D everythingBox(dmin, dmin, dmin, dmax, dmax, dmax);
+    const greyhound::Bounds everythingBounds(toBounds(everythingBox));
 }
 
-uint64_t sumHierarchy(const Json::Value& tree)
+GreyhoundReader::GreyhoundReader()
+    : Reader()
+    , m_url()
+    , m_resource()
+    , m_depthBegin(0)
+    , m_depthEnd(0)
+    , m_baseDepth(0)
+    , m_sparseDepth(0)
+    , m_numPoints(0)
+    , m_depthBeginArg(0)
+    , m_depthEndArg(0)
+{ }
+
+void GreyhoundReader::initialize(PointTableRef table)
 {
-    uint64_t output(0);
-    if (!tree.isMember("n")) return output;
+    if (m_url.find("http://") == std::string::npos &&
+            m_url.find("https://") == std::string::npos)
+    {
+        m_url = "http://" + m_url;
+    }
 
-    output += tree["n"].asUInt64();
+    Json::Value config;
 
-    auto summarize = [tree](const std::string& name)
+    if (log()->getLevel() > LogLevel::Debug4)
     {
-        uint64_t output(0);
-        if (tree.isMember(name))
-        {
-            output += tree[name]["n"].asUInt64();
-            output += sumHierarchy(tree[name]);
-        }
-        return output;
-    };
-
-    output += summarize("nwu");
-    output += summarize("neu");
-    output += summarize("swu");
-    output += summarize("seu");
-    output += summarize("nwd");
-    output += summarize("ned");
-    output += summarize("swd");
-    output += summarize("sed");
-
-    return output;
-}
+        config["arbiter"]["verbose"] = true;
+    }
 
+    m_arbiter.reset(new arbiter::Arbiter(config));
 
-QuickInfo GreyhoundReader::inspect()
-{
-    QuickInfo qi;
-    std::unique_ptr<PointLayout> layout(new PointLayout());
+    std::string infoUrl = m_url + "/resource/" + m_resource + "/info";
+    log()->get(LogLevel::Debug) << "Fetching info URL: " << infoUrl <<
+        std::endl;
+    m_info = parse(m_arbiter->get(infoUrl));
 
-    PointTable table;
-    initialize(table);
-    addDimensions(layout.get());
+    m_depthBegin = m_depthBeginArg;
+    m_depthEnd = m_depthEndArg;
 
-    Dimension::IdList dims = layout->dims();
-    for (auto di = dims.begin(); di != dims.end(); ++di)
-        qi.m_dimNames.push_back(layout->dimName(*di));
-    qi.m_srs = getSpatialReference();
-    qi.m_valid = true;
+    if (m_info.isMember("scale"))
+    {
+        m_scale.reset(new greyhound::Point(m_info["scale"]));
+    }
 
-    int split(0);
+    if (m_info.isMember("offset"))
+    {
+        m_offset.reset(new greyhound::Point(m_info["offset"]));
+    }
 
-    BOX3D fullBounds = getBounds(m_resourceInfo, "bounds");
-    BOX3D currentBounds = zoom(m_queryBounds, fullBounds, split);
-    m_split = split;
+    if (m_scale && !m_offset) m_offset.reset(new greyhound::Point(0, 0, 0));
+    if (m_offset && !m_scale) m_scale.reset(new greyhound::Point(1, 1, 1));
 
-    uint32_t depthBegin = std::max(m_depthBegin, m_baseDepth + 1 + m_split);
-    uint32_t depthEnd = 28;
+    m_fullBounds = m_info["bounds"];
 
-    Json::Value response = fetchHierarchy(currentBounds, depthBegin, depthEnd);
-    uint64_t count = sumHierarchy(response);
+    if (m_scale)
+    {
+        // Unscale the full bounds.  Since the query bounds will come in as
+        // native coordinates, don't modify those.
+        m_fullBounds = m_fullBounds.unscale(*m_scale, *m_offset);
+
+        // Now inverse our scale/offset.
+        m_scale->x = 1.0 / m_scale->x;
+        m_scale->y = 1.0 / m_scale->y;
+        m_scale->z = 1.0 / m_scale->z;
+
+        m_offset->x = -m_offset->x;
+        m_offset->y = -m_offset->y;
+        m_offset->z = -m_offset->z;
+    }
 
-    qi.m_pointCount = count;
-    qi.m_bounds = getBounds(m_resourceInfo, "boundsConforming");
+    m_queryBounds = toBounds(m_queryBox).intersection(m_fullBounds);
 
-    done(table);
+    if (m_pathsArg.size())
+    {
+        if (m_pathsArg.size() == 1)
+        {
+            m_filter["Path"] = m_pathsArg.front();
+        }
+        else
+        {
+            for (const auto& p : m_pathsArg)
+            {
+                m_filter["Path"].append(p);
+            }
+        }
+    }
 
-    return qi;
+    if (!m_filter.isNull())
+    {
+        log()->get(LogLevel::Debug) << "Filter: " << m_filter << std::endl;
+    }
+
+    m_dims = schemaToDims(m_info["schema"]);
+    m_baseDepth = m_info["baseDepth"].asUInt64();
+    m_sparseDepth = std::log(m_info["numPoints"].asUInt64()) / std::log(4) + 1;
+
+    setSpatialReference(m_info["srs"].asString());
 }
 
 void GreyhoundReader::addArgs(ProgramArgs& args)
 {
     args.add("url", "URL", m_url);
     args.add("resource", "Resource ID", m_resource);
-    args.add("timeout", "Request timeout (milliseconds)", m_timeout, 60000u);
-    args.add("bounds", "Bounding cube", m_queryBounds);
-    args.add("depth_begin", "Beginning depth to query", m_depthBegin);
-    args.add("depth_end", "Ending depth to query", m_depthEnd);
-    args.add("retries", "How many times to retry", m_retryCount, 1u);
-    args.add("split_threshold", "Point count for which to start splitting queries", m_splitCountThreshold, (point_count_t)50000llu);
+    args.add("bounds", "Bounding cube", m_queryBox, everythingBox);
+    args.add("depth_begin", "Beginning depth to query", m_depthBeginArg, 0u);
+    args.add("depth_end", "Ending depth to query", m_depthEndArg, 0u);
+    args.add("tile_path", "Index-optimized tile selection", m_pathsArg);
+    args.add("filter", "Query filter", m_filter);
+    args.add("threads", "Number of threads for HTTP requests", m_threadsArg, 4);
 }
 
+void GreyhoundReader::prepared(PointTableRef table)
+{
+    auto& layout(*table.layout());
+    // Note that we must construct the schema (which drives the formatting of
+    // Greyhound's responses) here, rather than just from the 'info' that comes
+    // back from Greyhound.  This is because other Reader instances may add
+    // dimensions that don't exist in this resource.  Also, the dimensions may
+    // be re-ordered in the layout from what we obtained in 'info'.  Greyhound
+    // will zero-fill non-existent dimentions in the responses, which will
+    // compress to pretty much nothing.
+    m_schema = layoutToSchema(layout);
+    log()->get(LogLevel::Debug) << "Schema: " << m_schema << std::endl;
+
+    m_dims.clear();
+    for (const auto& j : m_schema)
+    {
+        m_dims.push_back(layout.findDimType(j["name"].asString()));
+
+        if (m_dims.back().m_id == Dimension::Id::Unknown)
+        {
+            throw std::runtime_error(
+                    "Could not find dimension " + j["name"].asString());
+        }
+    }
+}
 
 void GreyhoundReader::addDimensions(PointLayoutPtr layout)
 {
-    for (auto& dim: m_dimData)
+    for (auto& dim : m_dims)
     {
         layout->registerDim(dim.m_id, dim.m_type);
     }
 }
 
+QuickInfo GreyhoundReader::inspect()
+{
+    QuickInfo qi;
+    std::unique_ptr<PointLayout> layout(new PointLayout());
 
+    PointTable table;
+    initialize(table);
+    addDimensions(layout.get());
 
+    for (auto di = layout->dims().begin(); di != layout->dims().end(); ++di)
+    {
+        qi.m_dimNames.push_back(layout->dimName(*di));
+    }
 
-std::string stringifyBounds(BOX3D bounds)
-{
-    std::stringstream sbounds;
-    sbounds << std::fixed;
-    sbounds << "[" << bounds.minx << "," << bounds.miny << "," << bounds.minz;
-    sbounds << "," << bounds.maxx << "," << bounds.maxy << "," << bounds.maxz << "]";
-    return sbounds.str();
-}
+    qi.m_srs = getSpatialReference();
+    qi.m_valid = true;
 
-Json::Value GreyhoundReader::fetchHierarchy(BOX3D bounds, uint32_t depthBegin, uint32_t depthEnd)  const
-{
+    std::size_t split(0);
+    const greyhound::Bounds zoomBounds(
+            zoom(m_queryBounds, m_fullBounds, split));
 
-    std::stringstream url;
-    url << m_url << "/resource/" << m_resource;
-    url << "/hierarchy?bounds=" << arbiter::http::sanitize(stringifyBounds(bounds));
-    url << "&depthBegin=" << depthBegin;
-    url << "&depthEnd=" << depthEnd;
+    if (split)
+    {
+        const std::size_t depthBegin(
+                std::max(m_depthBegin, m_baseDepth + split));
+        const std::size_t depthEnd(depthBegin + 32);
+
+        const auto hierarchy(
+                fetchVerticalHierarchy(zoomBounds, depthBegin, depthEnd));
+        qi.m_pointCount =
+            std::accumulate(hierarchy.begin(), hierarchy.end(), 0);
+
+        // The hierarchy doesn't have the resolution we want at the upper
+        // levels.  Estimate what's there based on the top level of information
+        // that we have.
+        if (hierarchy.size())
+        {
+            for (std::size_t i(0); i < split; ++i)
+            {
+                qi.m_pointCount += hierarchy.front() / ((i + 1) * 8);
+            }
+        }
+    }
+    else
+    {
+        qi.m_pointCount = m_info["numPoints"].asUInt64();
+    }
 
-    log()->get(LogLevel::Info) << "fetching hierarchy URL " << url.str() << std::endl;
+    qi.m_bounds = toBox(m_fullBounds.intersection(toBounds(m_queryBox)));
+    done(table);
 
-    Json::Value response = fetch(url.str());
-    return response;
+    return qi;
 }
 
-void GreyhoundReader::ready(PointTableRef)
+point_count_t GreyhoundReader::read(PointViewPtr view, point_count_t count)
 {
+    std::size_t split(0);
+    const greyhound::Bounds zoomBounds(
+            zoom(m_queryBounds, m_fullBounds, split));
 
+    // Greyhound's native chunking is pretty small to accomodate a renderer's
+    // need for very fast response times.  Since we don't really care about
+    // that, we'll let Greyhound do more work per query for better overall
+    // throughput.
+    split += 3;
 
-}
+    const std::size_t depthBegin(m_depthBegin);
+    const std::size_t depthSplit(std::max(m_depthBegin, m_baseDepth + split));
 
+    greyhound::Pool pool(m_threadsArg);
 
-point_count_t GreyhoundReader::readDirection(const greyhound::BBox& currentBox,
-                                            const greyhound::BBox& queryBox,
-                                            uint32_t& depthBegin,
-                                            uint32_t& depthEnd,
-                                            point_count_t count,
-                                            PointViewPtr view,
-                                            const Json::Value& hierarchy)
-{
+    if (depthSplit > depthBegin)
+    {
+        pool.add([this, &view, depthBegin, depthSplit]()
+        {
+            try
+            {
+                inc(fetchData(*view, m_queryBounds, depthBegin, depthSplit));
+            }
+            catch (std::exception& e)
+            {
+                std::lock_guard<std::mutex> lock(m_mutex);
+                m_error.reset(new std::string(e.what()));
+            }
+            catch (...)
+            {
+                std::lock_guard<std::mutex> lock(m_mutex);
+                m_error.reset(new std::string("Unknown error during read"));
+            }
+        });
+    }
 
-    point_count_t output(0);
-    if (!currentBox.overlaps(queryBox))
-        return output;
+    launchPooledReads(*view, zoomBounds, depthSplit, pool);
 
-    using namespace pdal::greyhound;
+    pool.await();
+    if (m_error) throw pdal_error(*m_error);
 
-    auto makeDirBox = [](greyhound::BBox box, greyhound::Dir direction)
-    {
-        BBox dirBox = box.get(direction);
-        return dirBox;
-    };
+    return m_numPoints;
+}
 
+void GreyhoundReader::launchPooledReads(
+        PointView& view,
+        const greyhound::Bounds& bounds,
+        const std::size_t startDepth,
+        greyhound::Pool& pool)
+{
+    Json::Value hierarchy(
+            fetchHierarchy(bounds, startDepth, startDepth + m_hierarchyStep));
 
+    m_tasks.emplace([this, &view, &hierarchy, bounds, startDepth]()
+    {
+        read(view, hierarchy, bounds, startDepth, startDepth);
+    });
 
-    if (currentBox.overlaps(queryBox))
+    while (m_running.size() || m_tasks.size())
     {
-        BOX3D currentBounds;
-        currentBounds.minx = currentBox.min().x; currentBounds.maxx = currentBox.max().x;
-        currentBounds.miny = currentBox.min().y; currentBounds.maxy = currentBox.max().y;
-        currentBounds.minz = currentBox.min().z; currentBounds.maxz = currentBox.max().z;
-
-        Json::Value hierarchy = fetchHierarchy(currentBounds, depthBegin, depthEnd);
-        point_count_t belowUs = sumHierarchy(hierarchy);
-        point_count_t currentLevel(0);
-        if (hierarchy.isMember("n"))
-            currentLevel = hierarchy["n"].asUInt64();
-
-        if (belowUs  > m_splitCountThreshold )
+        std::unique_lock<std::mutex> lock(m_mutex);
+        if (m_tasks.size())
         {
-            Json::Value hierarchy = fetchHierarchy(currentBounds, depthBegin, depthEnd);
-            point_count_t belowUs = sumHierarchy(hierarchy);
-            if (hierarchy.isMember("swd"))
-                output += readDirection(makeDirBox(currentBox, Dir::swd), queryBox, depthBegin, depthEnd, count, view, hierarchy["swd"]);
-            if (hierarchy.isMember("sed"))
-                output += readDirection(makeDirBox(currentBox, Dir::sed), queryBox, depthBegin, depthEnd, count, view, hierarchy["sed"]);
-            if (hierarchy.isMember("nwd"))
-                output += readDirection(makeDirBox(currentBox, Dir::nwd), queryBox, depthBegin, depthEnd, count, view, hierarchy["nwd"]);
-            if (hierarchy.isMember("ned"))
-                output += readDirection(makeDirBox(currentBox, Dir::ned), queryBox, depthBegin, depthEnd, count, view, hierarchy["ned"]);
-            if (hierarchy.isMember("swu"))
-                output += readDirection(makeDirBox(currentBox, Dir::swu), queryBox, depthBegin, depthEnd, count, view, hierarchy["swu"]);
-            if (hierarchy.isMember("seu"))
-                output += readDirection(makeDirBox(currentBox, Dir::seu), queryBox, depthBegin, depthEnd, count, view, hierarchy["seu"]);
-            if (hierarchy.isMember("nwu"))
-                output += readDirection(makeDirBox(currentBox, Dir::nwu), queryBox, depthBegin, depthEnd, count, view, hierarchy["nwu"]);
-            if (hierarchy.isMember("neu"))
-                output += readDirection(makeDirBox(currentBox, Dir::neu), queryBox, depthBegin, depthEnd, count, view, hierarchy["neu"]);
+            const std::size_t taskId(m_taskId);
+            ++m_taskId;
+
+            m_running[taskId] = std::move(m_tasks.front());
+            auto& task(m_running[taskId]);
+            m_tasks.pop();
+
+            lock.unlock();
+
+            pool.add([this, taskId, &task]()
+            {
+                try
+                {
+                    task();
+                }
+                catch (std::runtime_error& e)
+                {
+                    std::lock_guard<std::mutex> lock(m_mutex);
+                    m_error.reset(new std::string(
+                                std::string("Greyhound read failed: ") +
+                                e.what()));
+                }
+                catch (...)
+                {
+                    std::lock_guard<std::mutex> lock(m_mutex);
+                    m_error.reset(new std::string(
+                                "Greyhound read failed: unknown error"));
+                }
+
+                std::lock_guard<std::mutex> lock(m_mutex);
+                m_running.erase(taskId);
+            });
         }
         else
         {
-            if (belowUs)
-                output += this->readLevel(view, count, currentBounds, depthBegin, depthEnd);
+            lock.unlock();
+            std::this_thread::sleep_for(std::chrono::milliseconds(500));
         }
 
+        // If any tasks failed, rethrow in the main thread.
+        lock.lock();
+        if (m_error) throw pdal_error(*m_error);
     }
+}
 
-    return output;
-
-};
-
-
-
-point_count_t GreyhoundReader::read(
-        PointViewPtr view,
-        const point_count_t count)
+void GreyhoundReader::read(
+        PointView& view,
+        Json::Value& hierarchy,
+        const greyhound::Bounds& bounds,
+        const std::size_t startDepth,
+        const std::size_t depth)
 {
-    point_count_t output(0);
-    using namespace pdal::greyhound;
+    // At the end of the query.
+    if (m_depthEnd && depth >= m_depthEnd) return;
+    if (!bounds.overlaps(m_queryBounds)) return;
 
-    // if the base depth is greater than
-    // what the user gave use, we use that
-    // if it isn't, we use base depth + 1 (base depth has 0 points)
-//
+    const greyhound::Bounds intersect(bounds.intersection(m_queryBounds));
 
+    if (depth > m_sparseDepth)
+    {
+        // We're at the sparse depth, so request all remaining depths for the
+        // current bounds.
+        std::lock_guard<std::mutex> lock(m_mutex);
+        m_tasks.emplace([this, &view, intersect, depth]()
+        {
+            inc(fetchData(view, intersect, depth, m_depthEnd));
+        });
+    }
+    else
+    {
+        // If we're at the step size, fetch the next hierarchy range.
+        if (hierarchy.isNull())
+        {
+            const auto diff(depth - startDepth);
+            if (diff && diff % m_hierarchyStep == 0)
+            {
+                hierarchy = fetchHierarchy(
+                        bounds, depth, depth + m_hierarchyStep);
+            }
+            else return;
+        }
 
-    int split(0);
-
-    BOX3D fullBounds = getBounds(m_resourceInfo, "bounds");
-    BOX3D currentBounds = zoom(m_queryBounds, fullBounds, split);
-    m_split = split;
-
-    uint32_t depthBegin = std::max(m_depthBegin, m_baseDepth + 1 + m_split);
-    uint32_t depthEnd = depthBegin + 1;
+        if (hierarchy.isNull() || !hierarchy["n"].asUInt64()) return;
 
-    Json::Value hierarchy = fetchHierarchy(currentBounds, depthBegin, depthEnd);
+        const std::size_t nextDepth(depth + 1);
 
-    point_count_t belowUs = sumHierarchy(hierarchy);
-    if (!belowUs)
-        return output;
+        auto next([this, &view, startDepth, nextDepth, bounds, &hierarchy]()
+        {
+            for (std::size_t d(0); d < greyhound::dirEnd(); ++d)
+            {
+                const auto dir(greyhound::toDir(d));
+                const greyhound::Bounds nextBounds(bounds.get(dir));
+                auto& nextHierarchy(hierarchy[greyhound::dirToString(dir)]);
+
+                read(view, nextHierarchy, nextBounds, startDepth, nextDepth);
+            }
+        });
+
+        // For the first level, kick off the next level without waiting for
+        // the response.
+        if (depth == startDepth) next();
+
+        std::lock_guard<std::mutex> lock(m_mutex);
+        m_tasks.emplace(
+                [this, &view, intersect, depth, nextDepth, startDepth, next]()
+        {
+            const auto inc(fetchData(view, intersect, depth, nextDepth));
+            if (inc && depth != startDepth) next();
+        });
+    }
+}
 
-    BBox queryBox = makeBox(m_queryBounds);
-    BBox currentBox = makeBox(currentBounds);
+std::vector<point_count_t> GreyhoundReader::fetchVerticalHierarchy(
+        const greyhound::Bounds& bounds,
+        std::size_t depthBegin,
+        std::size_t depthEnd) const
+{
+    std::stringstream url;
+    url << m_url << "/resource/" << m_resource;
+    url << "/hierarchy?bounds=" << arbiter::http::sanitize(stringify(bounds));
+    url << "&depthBegin=" << depthBegin;
+    url << "&depthEnd=" << depthEnd;
+    url << "&vertical=true";
 
-    while (depthEnd <= m_depthEnd)
-    {
+    if (m_scale) url << "&scale=" << write(m_scale->toJson());
+    if (m_offset) url << "&offset=" << write(m_offset->toJson());
 
-        if (depthEnd >= m_stopSplittingDepth)
-        {
-            depthEnd = m_depthEnd;
-        }
-        output += readDirection(currentBox, queryBox, depthBegin, depthEnd, count, view, hierarchy);
-        depthBegin++;
-        depthEnd = depthBegin + 1;
-    }
+    log()->get(LogLevel::Debug) << "Hierarchy: " << url.str() << std::endl;
+    const Json::Value json(parse(m_arbiter->get(url.str())));
 
-    return output;
+    std::vector<point_count_t> results;
+    for (const auto& v : json) results.push_back(v.asUInt64());
 
+    return results;
 }
 
-point_count_t GreyhoundReader::readLevel(
-        PointViewPtr view,
-        const point_count_t count,
-        BOX3D bounds,
-        uint32_t depthBegin,
-        uint32_t depthEnd)
+Json::Value GreyhoundReader::fetchHierarchy(
+        const greyhound::Bounds& bounds,
+        std::size_t depthBegin,
+        std::size_t depthEnd) const
 {
+    std::stringstream url;
+    url << m_url << "/resource/" << m_resource;
+    url << "/hierarchy?bounds=" << arbiter::http::sanitize(stringify(bounds));
+    url << "&depthBegin=" << depthBegin;
+    url << "&depthEnd=" << depthEnd;
 
+    if (m_scale) url << "&scale=" << write(m_scale->toJson());
+    if (m_offset) url << "&offset=" << write(m_offset->toJson());
 
-    std::string bounds_str = stringifyBounds(bounds);
+    log()->get(LogLevel::Debug) << "Hierarchy: " << url.str() << std::endl;
+    return parse(m_arbiter->get(url.str()));
+}
+
+point_count_t GreyhoundReader::fetchData(
+        PointView& view,
+        const greyhound::Bounds& bounds,
+        const std::size_t depthBegin,
+        const std::size_t depthEnd)
+{
     std::stringstream url;
     url << m_url << "/resource/" << m_resource;
-    url << "/read?bounds=" << arbiter::http::sanitize(stringifyBounds(bounds));
+    url << "/read?bounds=" << arbiter::http::sanitize(stringify(bounds));
     url << "&depthBegin=" << depthBegin;
     url << "&depthEnd=" << depthEnd;
+    if (m_scale) url << "&scale=" << write(m_scale->toJson());
+    if (m_offset) url << "&offset=" << write(m_offset->toJson());
 
 #ifdef PDAL_HAVE_LAZPERF
     url << "&compress=true";
 #endif
-
-    log()->get(LogLevel::Info) << "fetching read URL " << url.str() << std::endl;
-
-    Json::Value config;
-    if (log()->getLevel() > LogLevel::Debug4)
-        config["arbiter"]["verbose"] = true;
-    config["http"]["timeout"] = 20000;
-    arbiter::Arbiter a(config);
-    uint32_t retries(0);
-    std::vector<char> response;
-    for (uint32_t i = 0; i <= m_retryCount; ++i)
+    if (!m_filter.isNull())
     {
-        try
-        {
-            response = a.getBinary(url.str());
-            break;
-        } catch (arbiter::ArbiterError&)
-        {
-            continue;
-        }
+        url << "&filter=" << arbiter::http::sanitize(write(m_filter));
     }
 
-    PointId nextId = view->size();
-    point_count_t numRead = 0;
-
-    log()->get(LogLevel::Info) << "Fetched "
-                               << response.size()
-                               << " bytes from "
-                               << m_url << std::endl;
-    if (!response.size())
     {
-        return numRead;
+        std::lock_guard<std::mutex> lock(m_mutex);
+        log()->get(LogLevel::Debug) << "Reading: " << url.str() << std::endl;
     }
 
-    const uint32_t numPoints = *reinterpret_cast<const uint32_t*>(response.data() + response.size() - sizeof(uint32_t));
+    url << "&schema=" << arbiter::http::sanitize(write(m_schema));
 
-    log()->get(LogLevel::Info) << "Fetched "
-                               << response.size()
-                               << " bytes and "
-                               << numPoints << " points from"
-                               << m_url << std::endl;
+    auto response(m_arbiter->getBinary(url.str()));
+    const std::size_t pointSize(view.layout()->pointSize());
 
-#ifdef PDAL_HAVE_LAZPERF
-    SignedLazPerfBuf buf(response);
-    LazPerfDecompressor<SignedLazPerfBuf> decompressor(buf, m_dimData);
+    std::unique_lock<std::mutex> lock(m_mutex);
 
-    std::vector<char> ptBuf(decompressor.pointSize());
-    while (numRead < numPoints)
-    {
-        char* outbuf = ptBuf.data();
-        point_count_t numWritten =
-            decompressor.decompress(outbuf, ptBuf.size());
+    const std::size_t numPoints(
+            *reinterpret_cast<const uint32_t*>(
+                response.data() + response.size() - sizeof(uint32_t)));
 
-        double x(0.0); double y(0.0); double z(0.0);
+    log()->get(LogLevel::Debug) <<
+        "Fetched " << numPoints << " points" << std::endl;
+    log()->get(LogLevel::Debug) <<
+        "Fetched " << response.size() << " bytes" << std::endl;
 
-        for (auto di = m_dimData.begin(); di != m_dimData.end(); ++di)
-        {
-            view->setField(di->m_id, di->m_type, nextId, outbuf);
-            outbuf += Dimension::size(di->m_type);
-        }
+    response.resize(response.size() - sizeof(uint32_t));
 
-        x = view->getFieldAs<double>(Dimension::Id::X, nextId);
-        y = view->getFieldAs<double>(Dimension::Id::Y, nextId);
-        z = view->getFieldAs<double>(Dimension::Id::Z, nextId);
+    std::vector<char*> points;
+    points.reserve(numPoints);
 
-        if (m_queryBounds.contains(x,y, z))
-        {
-            // overwrite this point id if we were not inside
-            // the box
-            nextId++;
-        }
+    const PointId startId(view.size());
+    PointId id(startId);
+
+    for (std::size_t i(0); i < numPoints; ++i)
+    {
+        points.push_back(view.getOrAddPoint(id));
+        ++id;
+    }
+
+    lock.unlock();
 
-        numRead++;
+    // Because we are requesting the data in the exact PointLayout of our
+    // PointView, we can just decompress directly into that memory.  Any
+    // non-existent dimensions (for example those added by other readers in the
+    // pipeline) will be zero-filled.
 
-        if (m_cb)
-            m_cb(*view, nextId);
+#ifdef PDAL_HAVE_LAZPERF
+    SignedLazPerfBuf buffer(response);
+    LazPerfDecompressor<SignedLazPerfBuf> decompressor(buffer, m_dims);
+
+    for (std::size_t i(0); i < numPoints; ++i)
+    {
+        decompressor.decompress(points[i], pointSize);
     }
 #else
+    const char* pos(response.data());
+    const char* end(pos + numPoints * pointSize);
+    std::size_t i(0);
 
-    throw pdal_error("uncompressed not implemented!");
+    while (pos < end)
+    {
+        std::copy(pos, pos + pointSize, points[i]);
+        ++i;
+        pos += pointSize;
+    }
 #endif
-    return numRead;
-}
 
-bool GreyhoundReader::eof() const
-{
-    return m_index >= m_numPoints;
-}
+    if (m_cb)
+    {
+        lock.lock();
+        for (std::size_t i(0); i < numPoints; ++i)
+        {
+            m_cb(view, startId + i);
+        }
+    }
 
-void GreyhoundReader::done(PointTableRef)
-{
+    return numPoints;
 }
 
 } // namespace pdal
diff --git a/plugins/greyhound/io/GreyhoundReader.hpp b/plugins/greyhound/io/GreyhoundReader.hpp
index 227371e..6aad81d 100644
--- a/plugins/greyhound/io/GreyhoundReader.hpp
+++ b/plugins/greyhound/io/GreyhoundReader.hpp
@@ -34,72 +34,110 @@
 
 #pragma once
 
+#include <functional>
+#include <queue>
+#include <vector>
+
+#include <arbiter/arbiter.hpp>
+
 #include <pdal/Reader.hpp>
 #include <pdal/StageFactory.hpp>
 #include <pdal/util/Bounds.hpp>
-#include <arbiter.hpp>
 
-#include "dir.hpp"
-#include "bbox.hpp"
+#include "bounds.hpp"
+#include "pool.hpp"
 
 namespace pdal
 {
 
+namespace greyhound = entwine;
+
 class PDAL_DLL GreyhoundReader : public pdal::Reader
 {
 
 public:
     GreyhoundReader();
-    ~GreyhoundReader();
 
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
+    static void* create();
+    static int32_t destroy(void*);
+    std::string getName() const override;
 
 private:
+    std::unique_ptr<arbiter::Arbiter> m_arbiter;
+
     std::string m_url;
     std::string m_resource;
     std::string m_sessionId;
-    point_count_t m_numPoints;
-    point_count_t m_index;
-    BOX3D m_queryBounds;
-    BOX3D m_conformingBounds;
-//     BOX3D m_bounds;
-    uint32_t m_depthBegin;
-    uint32_t m_depthEnd;
-    uint32_t m_baseDepth;
-    uint32_t m_stopSplittingDepth;
-    uint32_t m_split;
-    uint32_t m_retryCount;
-    Json::Value m_resourceInfo;
-    uint32_t m_timeout;
-    point_count_t m_splitCountThreshold;
-
-    virtual void initialize(PointTableRef table);
-    virtual void addArgs(ProgramArgs& args);
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual void ready(PointTableRef table);
-    virtual point_count_t read(PointViewPtr view, point_count_t count);
-    virtual bool eof() const;
-    virtual QuickInfo inspect();
-    virtual void done(PointTableRef table);
-
-    Json::Value fetch(const std::string& url) const;
-    DimTypeList getSchema(const Json::Value& jsondata) const;
-    BOX3D getBounds(const Json::Value& jsondata, const std::string& memberName) const;
-    point_count_t readLevel(PointViewPtr view, point_count_t count, BOX3D bounds, uint32_t readBegin, uint32_t readEnd);
-//     BOX3D zoom(BOX3D bounds, BOX3D fullBox, int& split) const;
-
-    Json::Value fetchHierarchy(BOX3D bounds, uint32_t depthBegin, uint32_t depthEnd)  const;
-
-    point_count_t readDirection(const greyhound::BBox& currentBox,
-                                            const greyhound::BBox& queryBox,
-                                            uint32_t& depthBegin,
-                                            uint32_t& depthEnd,
-                                            point_count_t count,
-                                            PointViewPtr view,
-                                            const Json::Value& hierarchy);
-    DimTypeList m_dimData;
+    BOX3D m_queryBox;
+    greyhound::Bounds m_queryBounds;
+    greyhound::Bounds m_fullBounds;
+    std::size_t m_depthBegin;
+    std::size_t m_depthEnd;
+    std::size_t m_baseDepth;
+    std::size_t m_sparseDepth;
+    Json::Value m_info;
+    Json::Value m_schema;
+    std::unique_ptr<greyhound::Point> m_scale;
+    std::unique_ptr<greyhound::Point> m_offset;
+
+    mutable std::mutex m_mutex;
+    point_count_t m_numPoints = 0;
+    const std::size_t m_hierarchyStep = 6;
+    std::size_t m_taskId = 0;
+    std::queue<std::function<void()>> m_tasks;
+    std::map<std::size_t, std::function<void()>> m_running;
+    std::unique_ptr<std::string> m_error;
+
+    void inc(point_count_t n)
+    {
+        std::lock_guard<std::mutex> lock(m_mutex);
+        m_numPoints += n;
+    }
+
+    DimTypeList m_dims;
+
+    uint32_t m_depthBeginArg;
+    uint32_t m_depthEndArg;
+    std::vector<std::string> m_pathsArg;
+    Json::Value m_filter;
+    int32_t m_threadsArg;
+
+    virtual void initialize(PointTableRef table) override;
+    virtual void addArgs(ProgramArgs& args) override;
+    virtual void addDimensions(PointLayoutPtr layout) override;
+    virtual void prepared(PointTableRef table) override;
+    virtual point_count_t read(PointViewPtr view, point_count_t count) override;
+    virtual QuickInfo inspect() override;
+    virtual void done(PointTableRef table) override { }
+
+    void launchPooledReads(
+            PointView& view,
+            const greyhound::Bounds& bounds,
+            std::size_t depth,
+            greyhound::Pool& pool);
+
+    void read(
+            PointView& view,
+            Json::Value& hierarchy,
+            const greyhound::Bounds& bounds,
+            std::size_t startDepth,
+            std::size_t depth);
+
+    std::vector<point_count_t> fetchVerticalHierarchy(
+            const greyhound::Bounds& bounds,
+            std::size_t depthBegin,
+            std::size_t depthEnd) const;
+
+    Json::Value fetchHierarchy(
+            const greyhound::Bounds& bounds,
+            std::size_t depthBegin,
+            std::size_t depthEnd) const;
+
+    point_count_t fetchData(
+            PointView& view,
+            const greyhound::Bounds& bounds,
+            std::size_t depthBegin,
+            std::size_t depthEnd);
 };
 
 } // namespace pdal
diff --git a/plugins/greyhound/io/bbox.cpp b/plugins/greyhound/io/bbox.cpp
deleted file mode 100644
index 91b0722..0000000
--- a/plugins/greyhound/io/bbox.cpp
+++ /dev/null
@@ -1,318 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Connor Manning (connor at hobu.co)
-*
-* Entwine -- Point cloud indexing
-*
-* Supporting libraries released under PDAL licensing by Hobu, inc.
-*
-******************************************************************************/
-
-#include "bbox.hpp"
-
-#include <cmath>
-#include <limits>
-#include <numeric>
-#include <iostream>
-
-#include "range.hpp"
-#include "point.hpp"
-
-namespace pdal { namespace greyhound {
-
-BBox::BBox() : m_min(), m_max(), m_mid(), m_is3d(false) { }
-
-BBox::BBox(const Point min, const Point max, const bool is3d)
-    : m_min(
-            std::min(min.x, max.x),
-            std::min(min.y, max.y),
-            std::min(min.z, max.z))
-    , m_max(
-            std::max(min.x, max.x),
-            std::max(min.y, max.y),
-            std::max(min.z, max.z))
-    , m_mid()
-    , m_is3d(is3d)
-{
-    setMid();
-    check(min, max);
-}
-
-BBox::BBox(const BBox& other)
-    : m_min(other.min())
-    , m_max(other.max())
-    , m_mid(other.m_mid)
-    , m_is3d(other.m_is3d)
-{ }
-
-BBox::BBox(const Json::Value& json)
-    : m_min()
-    , m_max()
-    , m_mid()
-    , m_is3d(true)
-{
-    if (!json.isArray() || (json.size() != 4 && json.size() != 6))
-    {
-        std::string what(
-                "Invalid JSON BBox specification: " + json.toStyledString());
-        throw std::runtime_error(what);
-    }
-
-    m_is3d = (json.size() == 6);
-
-    if (m_is3d)
-    {
-        m_min = Point(
-                json.get(Json::ArrayIndex(0), 0).asDouble(),
-                json.get(Json::ArrayIndex(1), 0).asDouble(),
-                json.get(Json::ArrayIndex(2), 0).asDouble());
-        m_max = Point(
-                json.get(Json::ArrayIndex(3), 0).asDouble(),
-                json.get(Json::ArrayIndex(4), 0).asDouble(),
-                json.get(Json::ArrayIndex(5), 0).asDouble());
-    }
-    else
-    {
-        m_min = Point(
-                json.get(Json::ArrayIndex(0), 0).asDouble(),
-                json.get(Json::ArrayIndex(1), 0).asDouble());
-        m_max = Point(
-                json.get(Json::ArrayIndex(2), 0).asDouble(),
-                json.get(Json::ArrayIndex(3), 0).asDouble());
-    }
-
-    check(m_min, m_max);
-    setMid();
-}
-
-void BBox::set(const BBox& other)
-{
-    m_min = other.m_min;
-    m_max = other.m_max;
-    m_mid = other.m_mid;
-    m_is3d = other.m_is3d;
-}
-
-void BBox::set(const Point& min, const Point& max, const bool is3d)
-{
-    m_min = min;
-    m_max = max;
-    m_is3d = is3d;
-    setMid();
-}
-
-bool BBox::overlaps(const BBox& other) const
-{
-    Point otherMid(other.mid());
-
-    return
-        std::abs(m_mid.x - otherMid.x) <
-            width() / 2.0  + other.width() / 2.0 &&
-        std::abs(m_mid.y - otherMid.y) <
-            depth() / 2.0 + other.depth() / 2.0 &&
-        (
-            !m_is3d ||
-            !other.m_is3d ||
-            std::abs(m_mid.z - otherMid.z) <
-                height() / 2.0 + other.height() / 2.0);
-}
-
-bool BBox::contains(const BBox& other, const bool force2d) const
-{
-    if (!force2d)
-    {
-        return m_min <= other.m_min && m_max >= other.m_max;
-    }
-    else
-    {
-        return
-            m_min.x <= other.m_min.x &&
-            m_min.y <= other.m_min.y &&
-            m_max.x >= other.m_min.x &&
-            m_max.y >= other.m_min.y;
-    }
-}
-
-bool BBox::contains(const Point& p) const
-{
-    return
-        p.x >= m_min.x && p.x < m_max.x &&
-        p.y >= m_min.y && p.y < m_max.y &&
-        (!m_is3d || (p.z >= m_min.z && p.z < m_max.z));
-
-}
-
-void BBox::goNwu()
-{
-    m_max.x = m_mid.x;
-    m_min.y = m_mid.y;
-    if (m_is3d) m_min.z = m_mid.z;
-    setMid();
-}
-
-void BBox::goNwd(const bool force2d)
-{
-    m_max.x = m_mid.x;
-    m_min.y = m_mid.y;
-    if (!force2d && m_is3d) m_max.z = m_mid.z;
-    setMid();
-}
-
-void BBox::goNeu()
-{
-    m_min.x = m_mid.x;
-    m_min.y = m_mid.y;
-    if (m_is3d) m_min.z = m_mid.z;
-    setMid();
-}
-
-void BBox::goNed(const bool force2d)
-{
-    m_min.x = m_mid.x;
-    m_min.y = m_mid.y;
-    if (!force2d && m_is3d) m_max.z = m_mid.z;
-    setMid();
-}
-
-void BBox::goSwu()
-{
-    m_max.x = m_mid.x;
-    m_max.y = m_mid.y;
-    if (m_is3d) m_min.z = m_mid.z;
-    setMid();
-}
-
-void BBox::goSwd(const bool force2d)
-{
-    m_max.x = m_mid.x;
-    m_max.y = m_mid.y;
-    if (!force2d && m_is3d) m_max.z = m_mid.z;
-    setMid();
-}
-
-void BBox::goSeu()
-{
-    m_min.x = m_mid.x;
-    m_max.y = m_mid.y;
-    if (m_is3d) m_min.z = m_mid.z;
-    setMid();
-}
-
-void BBox::goSed(const bool force2d)
-{
-    m_min.x = m_mid.x;
-    m_max.y = m_mid.y;
-    if (!force2d && m_is3d) m_max.z = m_mid.z;
-    setMid();
-}
-
-Json::Value BBox::toJson() const
-{
-    Json::Value json;
-
-    json.append(m_min.x);
-    json.append(m_min.y);
-    if (m_is3d) json.append(m_min.z);
-
-    json.append(m_max.x);
-    json.append(m_max.y);
-    if (m_is3d) json.append(m_max.z);
-
-    return json;
-}
-
-void BBox::check(const Point& min, const Point& max) const
-{
-    if (min.x > max.x || min.y > max.y || min.z > max.z)
-    {
-        std::cout << "Correcting malformed BBox" << std::endl;
-    }
-}
-
-void BBox::setMid()
-{
-    m_mid.x = m_min.x + (m_max.x - m_min.x) / 2.0;
-    m_mid.y = m_min.y + (m_max.y - m_min.y) / 2.0;
-    /* if (m_is3d) */ m_mid.z = m_min.z + (m_max.z - m_min.z) / 2.0;
-}
-
-void BBox::grow(const BBox& other)
-{
-    grow(other.min());
-    grow(other.max());
-}
-
-void BBox::grow(const Point& p)
-{
-    m_min.x = std::min(m_min.x, p.x);
-    m_min.y = std::min(m_min.y, p.y);
-    m_min.z = std::min(m_min.z, p.z);
-    m_max.x = std::max(m_max.x, p.x);
-    m_max.y = std::max(m_max.y, p.y);
-    m_max.z = std::max(m_max.z, p.z);
-    setMid();
-}
-
-void BBox::growZ(const Range& range)
-{
-    m_min.z = std::min(m_min.z, range.min);
-    m_max.z = std::max(m_max.z, range.max);
-    m_mid.z = m_min.z + (m_max.z - m_min.z) / 2.0;
-}
-
-void BBox::growBy(double ratio)
-{
-    const Point delta(
-            (m_max.x - m_mid.x) * ratio,
-            (m_max.y - m_mid.y) * ratio,
-            (m_max.z - m_mid.z) * ratio);
-
-    m_min -= delta;
-    m_max += delta;
-}
-
-std::vector<BBox> BBox::explode() const
-{
-    return std::vector<BBox> {
-        getNwd(true), getNed(true), getSwd(true), getSed(true)
-    };
-}
-
-std::vector<BBox> BBox::explode(std::size_t delta) const
-{
-    std::vector<BBox> result { *this };
-
-    for (std::size_t i(0); i < delta; ++i)
-    {
-        result = std::accumulate(
-                result.begin(),
-                result.end(),
-                std::vector<BBox>(),
-                [](const std::vector<BBox>& in, const BBox& b)
-                {
-                    auto out(in);
-                    auto next(b.explode());
-                    out.insert(out.end(), next.begin(), next.end());
-                    return out;
-                });
-    }
-
-    return result;
-}
-
-std::ostream& operator<<(std::ostream& os, const BBox& bbox)
-{
-    auto flags(os.flags());
-    auto precision(os.precision());
-
-    os << std::setprecision(2) << std::fixed;
-
-    os << "[" << bbox.min() << ", " << bbox.max() << "]";
-
-    os << std::setprecision(precision);
-    os.flags(flags);
-
-    return os;
-}
-
-} // namespace greyhound
-} // namepace pdal
diff --git a/plugins/greyhound/io/bbox.hpp b/plugins/greyhound/io/bbox.hpp
deleted file mode 100644
index 6c8a6f1..0000000
--- a/plugins/greyhound/io/bbox.hpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Connor Manning (connor at hobu.co)
-*
-* Entwine -- Point cloud indexing
-*
-* Supporting libraries released under PDAL licensing by Hobu, inc.
-*
-******************************************************************************/
-
-#pragma once
-
-#include "dir.hpp"
-#include "point.hpp"
-#include <json/json.h>
-
-
-namespace pdal { namespace greyhound {
-
-class Range;
-
-class BBox
-{
-public:
-    BBox();
-    BBox(Point min, Point max, bool is3d);
-    BBox(const BBox& other);
-    BBox(const Json::Value& json);
-
-    void set(const BBox& other);
-    void set(const Point& min, const Point& max, bool is3d);
-
-    const Point& min() const { return m_min; }
-    const Point& max() const { return m_max; }
-    const Point& mid() const { return m_mid; }
-
-    // Returns true if this BBox shares any area in common with another.
-    bool overlaps(const BBox& other) const;
-
-    // Returns true if the requested BBox is contained within this BBox.
-    bool contains(const BBox& other, bool force2d = false) const;
-
-    // Returns true if the requested point is contained within this BBox.
-    bool contains(const Point& p) const;
-
-    double width()  const { return m_max.x - m_min.x; } // Length in X.
-    double depth()  const { return m_max.y - m_min.y; } // Length in Y.
-    double height() const { return m_max.z - m_min.z; } // Length in Z.
-
-    double area() const { return width() * depth(); }
-    double volume() const { return width() * depth() * height(); }
-
-    void goNwu();
-    void goNwd(bool force2d = false);
-    void goNeu();
-    void goNed(bool force2d = false);
-    void goSwu();
-    void goSwd(bool force2d = false);
-    void goSeu();
-    void goSed(bool force2d = false);
-
-    BBox getNwd(bool force2d = false) const
-    {
-        BBox b(*this); b.goNwd(force2d); return b;
-    }
-
-    BBox getNed(bool force2d = false) const
-    {
-        BBox b(*this); b.goNed(force2d); return b;
-    }
-
-    BBox getSwd(bool force2d = false) const
-    {
-        BBox b(*this); b.goSwd(force2d); return b;
-    }
-
-    BBox getSed(bool force2d = false) const
-    {
-        BBox b(*this); b.goSed(force2d); return b;
-    }
-
-    BBox getNwu() const { BBox b(*this); b.goNwu(); return b; }
-    BBox getNeu() const { BBox b(*this); b.goNeu(); return b; }
-    BBox getSwu() const { BBox b(*this); b.goSwu(); return b; }
-    BBox getSeu() const { BBox b(*this); b.goSeu(); return b; }
-
-    void go(Dir dir, bool force2d = false)
-    {
-        if (force2d) dir = toDir(toIntegral(dir) % 4);
-
-        switch (dir)
-        {
-            case Dir::swd: goSwd(force2d); break;
-            case Dir::sed: goSed(force2d); break;
-            case Dir::nwd: goNwd(force2d); break;
-            case Dir::ned: goNed(force2d); break;
-            case Dir::swu: goSwu(); break;
-            case Dir::seu: goSeu(); break;
-            case Dir::nwu: goNwu(); break;
-            case Dir::neu: goNeu(); break;
-        }
-    }
-
-    BBox get(Dir dir) const
-    {
-        switch (dir)
-        {
-            case Dir::swd: return getSwd(); break;
-            case Dir::sed: return getSed(); break;
-            case Dir::nwd: return getNwd(); break;
-            case Dir::ned: return getNed(); break;
-            case Dir::swu: return getSwu(); break;
-            case Dir::seu: return getSeu(); break;
-            case Dir::nwu: return getNwu(); break;
-            case Dir::neu: return getNeu(); break;
-        }
-
-        throw std::runtime_error(
-                "Invalid Dir to BBox::get: " +
-                std::to_string(static_cast<int>(dir)));
-    }
-
-    bool exists() const { return Point::exists(m_min) && Point::exists(m_max); }
-    bool is3d() const { return m_is3d; }
-
-    Json::Value toJson() const;
-
-    void grow(const BBox& bbox);
-    void grow(const Point& p);
-    void growZ(const Range& range);
-
-    bool isCubic() const
-    {
-        return width() == depth() && (!m_is3d || width() == height());
-    }
-
-    // Bloat all coordinates necessary to form a cube and also to the nearest
-    // integer.
-    void cubeify()
-    {
-        const double xDist(m_max.x - m_min.x);
-        const double yDist(m_max.y - m_min.y);
-        const double zDist(m_max.z - m_min.z);
-
-        const double radius(
-                std::ceil(std::max(std::max(xDist, yDist), zDist) / 2.0 + 10));
-
-        m_min.x = std::floor(m_mid.x) - radius;
-        m_min.y = std::floor(m_mid.y) - radius;
-        m_min.z = std::floor(m_mid.z) - radius;
-
-        m_max.x = std::floor(m_mid.x) + radius;
-        m_max.y = std::floor(m_mid.y) + radius;
-        m_max.z = std::floor(m_mid.z) + radius;
-
-        setMid();
-    }
-
-    void growBy(double ratio);
-
-    std::vector<BBox> explode() const;
-    std::vector<BBox> explode(std::size_t delta) const;
-
-private:
-    Point m_min;
-    Point m_max;
-    Point m_mid;
-
-    bool m_is3d;
-
-    void setMid();
-
-    void check(const Point& min, const Point& max) const;
-};
-
-inline Point& operator+=(Point& lhs, const Point& rhs)
-{
-    lhs.x += rhs.x;
-    lhs.y += rhs.y;
-    lhs.z += rhs.z;
-    return lhs;
-}
-
-inline Point& operator-=(Point& lhs, const Point& rhs)
-{
-    lhs.x -= rhs.x;
-    lhs.y -= rhs.y;
-    lhs.z -= rhs.z;
-    return lhs;
-}
-
-std::ostream& operator<<(std::ostream& os, const BBox& bbox);
-
-// Orders BBoxes by their midpoint.  This is really only useful if the boxes
-// are arranged in a grid and are of equal size (like during a MetaQuery).
-inline bool operator<(const BBox& lhs, const BBox& rhs)
-{
-    const auto& lhsMid(lhs.mid());
-    const auto& rhsMid(rhs.mid());
-
-    return
-        (lhsMid.x < rhsMid.x) ||
-        (lhsMid.x == rhsMid.x && lhsMid.y < rhsMid.y) ||
-        (lhsMid.x == rhsMid.x && lhsMid.y == rhsMid.y && lhsMid.z < rhsMid.z);
-}
-
-inline bool operator==(const BBox& lhs, const BBox& rhs)
-{
-    return lhs.min() == rhs.min() && lhs.max() == rhs.max();
-}
-
-inline bool operator!=(const BBox& lhs, const BBox& rhs)
-{
-    return !(lhs == rhs);
-}
-
-} // namespace greyhound
-} // namespace pdal
diff --git a/plugins/greyhound/io/bounds.cpp b/plugins/greyhound/io/bounds.cpp
new file mode 100644
index 0000000..80b8331
--- /dev/null
+++ b/plugins/greyhound/io/bounds.cpp
@@ -0,0 +1,163 @@
+/******************************************************************************
+* Copyright (c) 2016, Connor Manning (connor at hobu.co)
+*
+* Entwine -- Point cloud indexing
+*
+* Supporting libraries released under PDAL licensing by Hobu, inc.
+*
+******************************************************************************/
+
+#include "bounds.hpp"
+
+#include <cmath>
+#include <limits>
+#include <numeric>
+#include <iostream>
+
+namespace pdal
+{
+namespace entwine
+{
+
+Bounds::Bounds(const Point& min, const Point& max)
+    : m_min(
+            std::min(min.x, max.x),
+            std::min(min.y, max.y),
+            std::min(min.z, max.z))
+    , m_max(
+            std::max(min.x, max.x),
+            std::max(min.y, max.y),
+            std::max(min.z, max.z))
+    , m_mid()
+{
+    setMid();
+    if (min.x > max.x || min.y > max.y || min.z > max.z)
+    {
+        std::cout << "Correcting malformed Bounds" << std::endl;
+    }
+}
+
+Bounds::Bounds(const Json::Value& json) : m_min(), m_max(), m_mid()
+{
+    if (!json.isArray() || (json.size() != 4 && json.size() != 6))
+    {
+        throw std::runtime_error(
+            "Invalid JSON Bounds specification: " + json.toStyledString());
+    }
+
+    const bool is3d(json.size() == 6);
+
+    if (is3d)
+    {
+        m_min = Point(
+                json.get(Json::ArrayIndex(0), 0).asDouble(),
+                json.get(Json::ArrayIndex(1), 0).asDouble(),
+                json.get(Json::ArrayIndex(2), 0).asDouble());
+        m_max = Point(
+                json.get(Json::ArrayIndex(3), 0).asDouble(),
+                json.get(Json::ArrayIndex(4), 0).asDouble(),
+                json.get(Json::ArrayIndex(5), 0).asDouble());
+    }
+    else
+    {
+        m_min = Point(
+                json.get(Json::ArrayIndex(0), 0).asDouble(),
+                json.get(Json::ArrayIndex(1), 0).asDouble());
+        m_max = Point(
+                json.get(Json::ArrayIndex(2), 0).asDouble(),
+                json.get(Json::ArrayIndex(3), 0).asDouble());
+    }
+
+    Bounds self(m_min, m_max);
+    *this = self;
+}
+
+Bounds::Bounds(const Point& center, const double radius)
+    : Bounds(center - radius, center + radius)
+{ }
+
+Bounds::Bounds(
+        const double xMin,
+        const double yMin,
+        const double zMin,
+        const double xMax,
+        const double yMax,
+        const double zMax)
+    : Bounds(Point(xMin, yMin, zMin), Point(xMax, yMax, zMax))
+{ }
+
+Bounds::Bounds(
+        const double xMin,
+        const double yMin,
+        const double xMax,
+        const double yMax)
+    : Bounds(Point(xMin, yMin), Point(xMax, yMax))
+{ }
+
+Json::Value Bounds::toJson() const
+{
+    Json::Value json;
+
+    json.append(m_min.x);
+    json.append(m_min.y);
+    if (is3d()) json.append(m_min.z);
+
+    json.append(m_max.x);
+    json.append(m_max.y);
+    if (is3d()) json.append(m_max.z);
+
+    return json;
+}
+
+void Bounds::grow(const Bounds& other)
+{
+    grow(other.min());
+    grow(other.max());
+}
+
+void Bounds::grow(const Point& p)
+{
+    m_min.x = std::min(m_min.x, p.x);
+    m_min.y = std::min(m_min.y, p.y);
+    m_min.z = std::min(m_min.z, p.z);
+    m_max.x = std::max(m_max.x, p.x);
+    m_max.y = std::max(m_max.y, p.y);
+    m_max.z = std::max(m_max.z, p.z);
+    setMid();
+}
+
+void Bounds::shrink(const Bounds& other)
+{
+    m_min = Point::max(m_min, other.min());
+    m_max = Point::min(m_max, other.max());
+    setMid();
+}
+
+Bounds Bounds::growBy(double ratio) const
+{
+    const Point delta(
+            (m_max.x - m_mid.x) * ratio,
+            (m_max.y - m_mid.y) * ratio,
+            (m_max.z - m_mid.z) * ratio);
+
+    return Bounds(m_min - delta, m_max + delta);
+}
+
+std::ostream& operator<<(std::ostream& os, const Bounds& bounds)
+{
+    auto flags(os.flags());
+    auto precision(os.precision());
+
+    os << std::setprecision(2) << std::fixed;
+
+    os << "[" << bounds.min() << ", " << bounds.max() << "]";
+
+    os << std::setprecision(precision);
+    os.flags(flags);
+
+    return os;
+}
+
+} // namespace entwine
+} // namespace pdal
+
diff --git a/plugins/greyhound/io/bounds.hpp b/plugins/greyhound/io/bounds.hpp
new file mode 100644
index 0000000..97829bb
--- /dev/null
+++ b/plugins/greyhound/io/bounds.hpp
@@ -0,0 +1,309 @@
+/******************************************************************************
+* Copyright (c) 2016, Connor Manning (connor at hobu.co)
+*
+* Entwine -- Point cloud indexing
+*
+* Supporting libraries released under PDAL licensing by Hobu, inc.
+*
+******************************************************************************/
+
+#pragma once
+
+#include <cstdlib>
+#include <iostream>
+
+#include <json/json.h>
+
+#include "dir.hpp"
+#include "point.hpp"
+
+namespace pdal
+{
+namespace entwine
+{
+
+class Bounds
+{
+public:
+    Bounds() = default;
+    Bounds(const Point& min, const Point& max);
+    Bounds(const Point& center, double radius);
+    Bounds(const Json::Value& json);
+    Bounds(double xMin, double yMin, double xMax, double yMax);
+    Bounds(
+            double xMin,
+            double yMin,
+            double zMin,
+            double xMax,
+            double yMax,
+            double zMax);
+
+    void set(const Bounds& other)
+    {
+        m_min = other.m_min;
+        m_max = other.m_max;
+        m_mid = other.m_mid;
+    }
+
+    void set(const Point& min, const Point& max)
+    {
+        m_min = min;
+        m_max = max;
+        setMid();
+    }
+
+    const Point& min() const { return m_min; }
+    const Point& max() const { return m_max; }
+    const Point& mid() const { return m_mid; }
+
+    // Returns true if these Bounds share any area in common with another.
+    bool overlaps(const Bounds& other, bool force2d = false) const
+    {
+        Point otherMid(other.mid());
+
+        return
+            std::abs(m_mid.x - otherMid.x) <=
+                width() / 2.0 + other.width() / 2.0 &&
+            std::abs(m_mid.y - otherMid.y) <=
+                depth() / 2.0 + other.depth() / 2.0 &&
+            (force2d || std::abs(m_mid.z - otherMid.z) <=
+                height() / 2.0 + other.height() / 2.0);
+    }
+
+    // Returns true if the requested Bounds are contained within these Bounds.
+    bool contains(const Bounds& other, bool force2d = false) const
+    {
+        if (!force2d)
+        {
+            return m_min <= other.m_min && m_max >= other.m_max;
+        }
+        else
+        {
+            return
+                m_min.x <= other.m_min.x &&
+                m_min.y <= other.m_min.y &&
+                m_max.x >= other.m_min.x &&
+                m_max.y >= other.m_min.y;
+        }
+    }
+
+    // Returns true if the requested point is contained within these Bounds.
+    bool contains(const Point& p) const { return p >= m_min && p < m_max; }
+
+    double width()  const { return m_max.x - m_min.x; } // Length in X.
+    double depth()  const { return m_max.y - m_min.y; } // Length in Y.
+    double height() const { return m_max.z - m_min.z; } // Length in Z.
+
+    double area() const { return width() * depth(); }
+    double volume() const { return width() * depth() * height(); }
+
+    void goNwu()
+    {
+        m_max.x = m_mid.x;
+        m_min.y = m_mid.y;
+        m_min.z = m_mid.z;
+        setMid();
+    }
+
+    void goNwd(const bool force2d = false)
+    {
+        m_max.x = m_mid.x;
+        m_min.y = m_mid.y;
+        if (!force2d) m_max.z = m_mid.z;
+        setMid();
+    }
+
+    void goNeu()
+    {
+        m_min.x = m_mid.x;
+        m_min.y = m_mid.y;
+        m_min.z = m_mid.z;
+        setMid();
+    }
+
+    void goNed(const bool force2d = false)
+    {
+        m_min.x = m_mid.x;
+        m_min.y = m_mid.y;
+        if (!force2d) m_max.z = m_mid.z;
+        setMid();
+    }
+
+    void goSwu()
+    {
+        m_max.x = m_mid.x;
+        m_max.y = m_mid.y;
+        m_min.z = m_mid.z;
+        setMid();
+    }
+
+    void goSwd(const bool force2d = false)
+    {
+        m_max.x = m_mid.x;
+        m_max.y = m_mid.y;
+        if (!force2d) m_max.z = m_mid.z;
+        setMid();
+    }
+
+    void goSeu()
+    {
+        m_min.x = m_mid.x;
+        m_max.y = m_mid.y;
+        m_min.z = m_mid.z;
+        setMid();
+    }
+
+    void goSed(const bool force2d = false)
+    {
+        m_min.x = m_mid.x;
+        m_max.y = m_mid.y;
+        if (!force2d) m_max.z = m_mid.z;
+        setMid();
+    }
+
+    Bounds getNwd(bool force2d = false) const
+    {
+        Bounds b(*this); b.goNwd(force2d); return b;
+    }
+
+    Bounds getNed(bool force2d = false) const
+    {
+        Bounds b(*this); b.goNed(force2d); return b;
+    }
+
+    Bounds getSwd(bool force2d = false) const
+    {
+        Bounds b(*this); b.goSwd(force2d); return b;
+    }
+
+    Bounds getSed(bool force2d = false) const
+    {
+        Bounds b(*this); b.goSed(force2d); return b;
+    }
+
+    Bounds getNwu() const { Bounds b(*this); b.goNwu(); return b; }
+    Bounds getNeu() const { Bounds b(*this); b.goNeu(); return b; }
+    Bounds getSwu() const { Bounds b(*this); b.goSwu(); return b; }
+    Bounds getSeu() const { Bounds b(*this); b.goSeu(); return b; }
+
+    void go(Dir dir, bool force2d = false)
+    {
+        if (force2d) dir = toDir(toIntegral(dir, true));
+
+        switch (dir)
+        {
+            case Dir::swd: goSwd(force2d); break;
+            case Dir::sed: goSed(force2d); break;
+            case Dir::nwd: goNwd(force2d); break;
+            case Dir::ned: goNed(force2d); break;
+            case Dir::swu: goSwu(); break;
+            case Dir::seu: goSeu(); break;
+            case Dir::nwu: goNwu(); break;
+            case Dir::neu: goNeu(); break;
+        }
+    }
+
+    Bounds get(Dir dir, bool force2d = false) const
+    {
+        if (force2d) dir = toDir(toIntegral(dir, true));
+
+        switch (dir)
+        {
+            case Dir::swd: return getSwd(force2d); break;
+            case Dir::sed: return getSed(force2d); break;
+            case Dir::nwd: return getNwd(force2d); break;
+            case Dir::ned: return getNed(force2d); break;
+            case Dir::swu: return getSwu(); break;
+            case Dir::seu: return getSeu(); break;
+            case Dir::nwu: return getNwu(); break;
+            case Dir::neu: return getNeu(); break;
+        }
+
+        throw std::runtime_error(
+                "Invalid Dir to Bounds::get: " +
+                std::to_string(toIntegral(dir)));
+    }
+
+    bool exists() const { return Point::exists(m_min) || Point::exists(m_max); }
+    bool is3d() const { return m_min.z || m_max.z; }
+
+    Json::Value toJson() const;
+
+    void grow(const Bounds& bounds);
+    void grow(const Point& p);
+    void shrink(const Bounds& bounds);
+
+    bool isCubic() const
+    {
+        return width() == depth() && (!is3d() || width() == height());
+    }
+
+    Bounds growBy(double ratio) const;
+
+    std::vector<Bounds> explode() const;
+
+    Bounds transform(const Transformation& t) const
+    {
+        return Bounds(Point::transform(min(), t), Point::transform(max(), t));
+    }
+
+    Bounds intersection(const Bounds& b) const
+    {
+        return Bounds(Point::max(min(), b.min()), Point::min(max(), b.max()));
+    }
+
+    Bounds scale(const Point& scale, const Point& offset)
+    {
+        return Bounds(
+                Point::scale(min(), scale, offset),
+                Point::scale(max(), scale, offset));
+    }
+
+    Bounds unscale(const Point& scale, const Point& offset)
+    {
+        return Bounds(
+                Point::unscale(min(), scale, offset),
+                Point::unscale(max(), scale, offset));
+    }
+
+private:
+    Point m_min;
+    Point m_max;
+    Point m_mid;
+
+    void setMid()
+    {
+        m_mid.x = m_min.x + (m_max.x - m_min.x) / 2.0;
+        m_mid.y = m_min.y + (m_max.y - m_min.y) / 2.0;
+        m_mid.z = m_min.z + (m_max.z - m_min.z) / 2.0;
+    }
+};
+
+std::ostream& operator<<(std::ostream& os, const Bounds& bounds);
+
+// Orders Bounds by their midpoint.  This is really only useful if the bounds
+// are arranged in a grid and are of equal size (like during a MetaQuery).
+inline bool operator<(const Bounds& lhs, const Bounds& rhs)
+{
+    const auto& lhsMid(lhs.mid());
+    const auto& rhsMid(rhs.mid());
+
+    return
+        (lhsMid.x < rhsMid.x) ||
+        (lhsMid.x == rhsMid.x && lhsMid.y < rhsMid.y) ||
+        (lhsMid.x == rhsMid.x && lhsMid.y == rhsMid.y && lhsMid.z < rhsMid.z);
+}
+
+inline bool operator==(const Bounds& lhs, const Bounds& rhs)
+{
+    return lhs.min() == rhs.min() && lhs.max() == rhs.max();
+}
+
+inline bool operator!=(const Bounds& lhs, const Bounds& rhs)
+{
+    return !(lhs == rhs);
+}
+
+} // namespace entwine
+} // namespace pdal
+
diff --git a/plugins/greyhound/io/dir.hpp b/plugins/greyhound/io/dir.hpp
index e1ec0ee..4bc0a5b 100644
--- a/plugins/greyhound/io/dir.hpp
+++ b/plugins/greyhound/io/dir.hpp
@@ -11,7 +11,10 @@
 
 #include "point.hpp"
 
-namespace pdal { namespace greyhound {
+namespace pdal
+{
+namespace entwine
+{
 
 enum class Dir
 {
@@ -25,8 +28,11 @@ enum class Dir
     neu = 7
 };
 
-// Get the direction of point P in relation to an origin O.
-inline Dir getDirection(const Point& p, const Point& o)
+inline constexpr std::size_t dirHalfEnd() { return 4; }
+inline constexpr std::size_t dirEnd() { return 8; }
+
+// Get the direction from an origin O to a point P.
+inline Dir getDirection(const Point& o, const Point& p)
 {
     return static_cast<Dir>(
             (p.y >= o.y ? 2 : 0) +  // North? +2.
@@ -34,6 +40,20 @@ inline Dir getDirection(const Point& p, const Point& o)
             (p.z >= o.z ? 4 : 0));  // Up? +4.
 }
 
+inline Dir getDirection(const Point& o, const Point& p, bool force2d)
+{
+    if (force2d)
+    {
+        return static_cast<Dir>(
+                (p.y >= o.y ? 2 : 0) +  // North? +2.
+                (p.x >= o.x ? 1 : 0));  // East? +1.
+    }
+    else
+    {
+        return getDirection(o, p);
+    }
+}
+
 inline std::string dirToString(Dir dir)
 {
     switch (dir)
@@ -59,9 +79,11 @@ inline Dir stringToDir(const std::string& s)
             (s[2] == 'u' ? 4 : 0)); // Up? +4.
 }
 
-inline std::size_t toIntegral(Dir dir)
+inline std::size_t toIntegral(Dir dir, bool force2d = false)
 {
-    return static_cast<std::size_t>(dir);
+    std::size_t result(static_cast<std::size_t>(dir));
+    if (force2d) result %= 4;
+    return result;
 }
 
 inline Dir toDir(std::size_t val)
@@ -69,5 +91,20 @@ inline Dir toDir(std::size_t val)
     return static_cast<Dir>(val);
 }
 
+inline bool isSouth(Dir dir) { return toIntegral(dir) % 4 < 2; } // 0, 1, 4, 5
+inline bool isNorth(Dir dir) { return !isSouth(dir); }
+
+inline bool isWest(Dir dir) { return toIntegral(dir) % 2 == 0; } // 0, 2, 4, 6
+inline bool isEast(Dir dir) { return !isWest(dir); }
+
+inline bool isDown(Dir dir) { return toIntegral(dir) < 4; }      // 0, 1, 2, 3
+inline bool isUp(Dir dir) { return !isDown(dir); }
+
+inline std::ostream& operator<<(std::ostream& os, Dir dir)
+{
+    return os << dirToString(dir);
+}
+
 } // namespace entwine
 } // namespace pdal
+
diff --git a/plugins/greyhound/io/point.hpp b/plugins/greyhound/io/point.hpp
index 6aae11a..b8d6ac9 100644
--- a/plugins/greyhound/io/point.hpp
+++ b/plugins/greyhound/io/point.hpp
@@ -3,7 +3,8 @@
 *
 * Entwine -- Point cloud indexing
 *
-* Supporting libraries released under PDAL licensing by Hobu, inc.
+* Entwine is available under the terms of the LGPL2 license. See COPYING
+* for specific license text and more information.
 *
 ******************************************************************************/
 
@@ -13,8 +14,16 @@
 #include <iomanip>
 #include <limits>
 #include <ostream>
+#include <vector>
 
-namespace pdal { namespace greyhound {
+#include <json/json.h>
+
+namespace pdal
+{
+namespace entwine
+{
+
+using Transformation = std::vector<double>;
 
 class Point
 {
@@ -25,9 +34,54 @@ public:
         , z(Point::emptyCoord())
     { }
 
+    Point(double v) noexcept : x(v), y(v), z(v) { }
     Point(double x, double y) noexcept : x(x), y(y), z(Point::emptyCoord()) { }
     Point(double x, double y, double z) noexcept : x(x), y(y), z(z) { }
 
+    Point(const Json::Value& json)
+        : Point()
+    {
+        if (!json.isNull())
+        {
+            if (json.isArray())
+            {
+                x = json[0].asDouble();
+                y = json[1].asDouble();
+                if (json.size() > 2) z = json[2].asDouble();
+            }
+            else if (json.isNumeric())
+            {
+                x = y = z = json.asDouble();
+            }
+            else if (json.isObject())
+            {
+                x = json["x"].asDouble();
+                y = json["y"].asDouble();
+                z = json["z"].asDouble();
+            }
+        }
+    }
+
+    Json::Value toJson() const { return toJsonArray(); }
+
+    Json::Value toJsonArray() const
+    {
+        Json::Value json;
+        json.append(x);
+        json.append(y);
+        json.append(z);
+        return json;
+    }
+
+    Json::Value toJsonObject() const
+    {
+        Json::Value json;
+        json["x"] = x;
+        json["y"] = y;
+        json["z"] = z;
+        return json;
+    }
+
     double sqDist2d(const Point& other) const
     {
         const double xDelta(x - other.x);
@@ -61,11 +115,159 @@ public:
         return 0;
     }
 
+    static Point max(const Point& a, const Point& b)
+    {
+        return Point(
+                std::max(a.x, b.x),
+                std::max(a.y, b.y),
+                std::max(a.z, b.z));
+    }
+
+    static Point min(const Point& a, const Point& b)
+    {
+        return Point(
+                std::min(a.x, b.x),
+                std::min(a.y, b.y),
+                std::min(a.z, b.z));
+    }
+
+    static Point normalize(const Point& p)
+    {
+        const double m(std::sqrt(p.x * p.x + p.y * p.y + p.z * p.z));
+        return Point(p.x / m, p.y / m, p.z / m);
+    }
+
+    static Point cross(const Point& a, const Point& b)
+    {
+        return Point(
+                a.y * b.z - a.z * b.y,
+                a.z * b.x - a.x * b.z,
+                a.x * b.y - a.y * b.x);
+    }
+
+    static double dot(const Point& a, const Point& b)
+    {
+        return a.x * b.x + a.y * b.y + a.z * b.z;
+    }
+
+    static Point transform(const Point& p, const Transformation& t)
+    {
+        return Point(
+            p.x * t[0] + p.y * t[1] + p.z * t[2] + t[3],
+            p.x * t[4] + p.y * t[5] + p.z * t[6] + t[7],
+            p.x * t[8] + p.y * t[9] + p.z * t[10] + t[11]);
+    }
+
+    static Point scale(const Point& p, const Point& scale, const Point& offset)
+    {
+        return Point(
+                Point::scale(p.x, scale.x, offset.x),
+                Point::scale(p.y, scale.y, offset.y),
+                Point::scale(p.z, scale.z, offset.z));
+    }
+
+    static double scale(double d, double scale, double offset)
+    {
+        return (d - offset) / scale;
+    }
+
+    static Point scale(
+            const Point& p,
+            const Point& origin,
+            const Point& scale,
+            const Point& offset)
+    {
+        return Point(
+                Point::scale(p.x, origin.x, scale.x, offset.x),
+                Point::scale(p.y, origin.y, scale.y, offset.y),
+                Point::scale(p.z, origin.z, scale.z, offset.z));
+    }
+
+    static double scale(double d, double origin, double scale, double offset)
+    {
+        return (d - origin) / scale + origin - offset;
+    }
+
+    static Point unscale(
+            const Point& p,
+            const Point& scale,
+            const Point& offset)
+    {
+        return Point(
+                Point::unscale(p.x, scale.x, offset.x),
+                Point::unscale(p.y, scale.y, offset.y),
+                Point::unscale(p.z, scale.z, offset.z));
+    }
+
+    static double unscale(double d, double scale, double offset)
+    {
+        return d * scale + offset;
+    }
+
+    static Point unscale(
+            const Point& p,
+            const Point& origin,
+            const Point& scale,
+            const Point& offset)
+    {
+        return Point(
+                Point::unscale(p.x, origin.x, scale.x, offset.x),
+                Point::unscale(p.y, origin.y, scale.y, offset.y),
+                Point::unscale(p.z, origin.z, scale.z, offset.z));
+    }
+
+    static double unscale(double d, double origin, double scale, double offset)
+    {
+        return (d - origin + offset) * scale + origin;
+    }
+
+    template<typename Op> static Point apply(Op op, const Point& p)
+    {
+        return Point(op(p.x), op(p.y), op(p.z));
+    }
+
+    double& operator[](std::size_t i)
+    {
+        switch (i)
+        {
+            case 0: return x;
+            case 1: return y;
+            case 2: return z;
+            default: throw std::runtime_error("Invalid coordinate index");
+        }
+    }
+
+    double operator[](std::size_t i) const
+    {
+        switch (i)
+        {
+            case 0: return x;
+            case 1: return y;
+            case 2: return z;
+            default: throw std::runtime_error("Invalid coordinate index");
+        }
+    }
+
+    static Point round(const Point& p)
+    {
+        return Point(std::llround(p.x), std::llround(p.y), std::llround(p.z));
+    }
+
     double x;
     double y;
     double z;
 };
 
+inline bool ltChained(const Point& lhs, const Point& rhs)
+{
+    return
+        (lhs.x < rhs.x) ||
+        (lhs.x == rhs.x &&
+            (lhs.y < rhs.y ||
+            (lhs.y == rhs.y &&
+                (lhs.z < rhs.z))));
+}
+
 inline bool operator<(const Point& lhs, const Point& rhs)
 {
     return lhs.x < rhs.x && lhs.y < rhs.y && lhs.z < rhs.z;
@@ -91,30 +293,91 @@ inline bool operator==(const Point& lhs, const Point& rhs)
     return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z;
 }
 
+inline bool operator!=(const Point& lhs, const Point& rhs)
+{
+    return !(lhs == rhs);
+}
+
 inline Point operator+(const Point& in, double offset)
 {
     return Point(in.x + offset, in.y + offset, in.z + offset);
 }
 
+inline Point operator-(const Point& p)
+{
+    return Point(-p.x, -p.y, -p.z);
+}
+
 inline Point operator-(const Point& in, double offset)
 {
     return in + (-offset);
 }
 
+inline Point operator+(const Point& lhs, const Point& rhs)
+{
+    return Point(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z);
+}
+
+inline Point operator-(const Point& lhs, const Point& rhs)
+{
+    return Point(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z);
+}
+
+inline Point& operator+=(Point& lhs, const Point& rhs)
+{
+    lhs.x += rhs.x;
+    lhs.y += rhs.y;
+    lhs.z += rhs.z;
+    return lhs;
+}
+
+inline Point& operator-=(Point& lhs, const Point& rhs)
+{
+    lhs.x -= rhs.x;
+    lhs.y -= rhs.y;
+    lhs.z -= rhs.z;
+    return lhs;
+}
+
+inline Point operator*(const Point& p, double s)
+{
+    return Point(p.x * s, p.y * s, p.z * s);
+}
+
+inline Point operator*(const Point& a, const Point& b)
+{
+    return Point(a.x * b.x, a.y * b.y, a.z * b.z);
+}
+
+inline Point operator/(const Point& a, const Point& b)
+{
+    return Point(a.x / b.x, a.y / b.y, a.z / b.z);
+}
+
 inline std::ostream& operator<<(std::ostream& os, const Point& point)
 {
     auto flags(os.flags());
     auto precision(os.precision());
 
-    os << std::setprecision(2) << std::fixed;
+    auto printCoord([&os](double d)
+    {
+        if (std::trunc(d) == d) os << static_cast<long>(d);
+        else os << d;
+    });
+
+    os << std::setprecision(5) << std::fixed;
+
+    os << "(";
+    printCoord(point.x);
+    os << ", ";
+    printCoord(point.y);
 
-    os << "(" << point.x << ", " << point.y;
     if (
-            point.z != Point::emptyCoord() &&
             point.z != std::numeric_limits<double>::max() &&
             point.z != std::numeric_limits<double>::lowest())
     {
-        os << ", " << point.z;
+        os << ", ";
+        printCoord(point.z);
     }
     os << ")";
 
@@ -124,5 +387,37 @@ inline std::ostream& operator<<(std::ostream& os, const Point& point)
     return os;
 }
 
-} // namespace greyhound
-} // namepsace pdal
+class Color
+{
+public:
+    Color() : r(0), g(0), b(0) { }
+    Color(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) { }
+
+    static Color min(const Color& a, const Color& b)
+    {
+        return Color(
+                std::min(a.r, b.r),
+                std::min(a.g, b.g),
+                std::min(a.b, b.b));
+    }
+
+    static Color max(const Color& a, const Color& b)
+    {
+        return Color(
+                std::max(a.r, b.r),
+                std::max(a.g, b.g),
+                std::max(a.b, b.b));
+    }
+
+    uint8_t r, g, b;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const Color& c)
+{
+    os << "(" << (int)c.r << ", " << (int)c.g << ", " << (int)c.b << ")";
+    return os;
+}
+
+} // namespace entwine
+} // namespace pdal
+
diff --git a/plugins/greyhound/io/pool.cpp b/plugins/greyhound/io/pool.cpp
new file mode 100644
index 0000000..7498d25
--- /dev/null
+++ b/plugins/greyhound/io/pool.cpp
@@ -0,0 +1,167 @@
+/******************************************************************************
+* Copyright (c) 2016, Connor Manning (connor at hobu.co)
+*
+* Entwine -- Point cloud indexing
+*
+* Entwine is available under the terms of the LGPL2 license. See COPYING
+* for specific license text and more information.
+*
+******************************************************************************/
+
+#include "pool.hpp"
+
+#include <cassert>
+#include <iostream>
+
+namespace pdal
+{
+namespace entwine
+{
+
+Pool::Pool(const std::size_t numThreads, const std::size_t queueSize)
+    : m_numThreads(std::max<std::size_t>(numThreads, 1u))
+    , m_queueSize(std::max<std::size_t>(queueSize, 1u))
+    , m_threads()
+    , m_tasks()
+    , m_running(0)
+    , m_stop(true)
+    , m_mutex()
+    , m_produceCv()
+    , m_consumeCv()
+{
+    go();
+}
+
+Pool::~Pool()
+{
+    join();
+}
+
+void Pool::resize(const std::size_t numThreads)
+{
+    join();
+    m_numThreads = numThreads;
+    go();
+}
+
+void Pool::go()
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+
+    if (!stop())
+    {
+        throw std::runtime_error(
+                "Attempted to call Pool::go on an already running Pool");
+    }
+
+    stop(false);
+
+    for (std::size_t i(0); i < m_numThreads; ++i)
+    {
+        m_threads.emplace_back([this]() { work(); });
+    }
+}
+
+void Pool::join()
+{
+    if (!stop())
+    {
+        stop(true);
+
+        for (auto& t : m_threads)
+        {
+            m_consumeCv.notify_all();
+            t.join();
+        }
+
+        std::lock_guard<std::mutex> lock(m_mutex);
+        m_threads.clear();
+        assert(m_tasks.empty());
+    }
+}
+
+void Pool::await()
+{
+    std::unique_lock<std::mutex> lock(m_mutex);
+    m_produceCv.wait(lock, [this]() { return !m_running && m_tasks.empty(); });
+}
+
+void Pool::add(std::function<void()> task)
+{
+    if (stop())
+    {
+        throw std::runtime_error("Attempted to add a task to a stopped Pool");
+    }
+
+    if (!numThreads())
+    {
+        throw std::runtime_error("Attempted to add a task to an empty Pool");
+    }
+
+    std::unique_lock<std::mutex> lock(m_mutex);
+
+    m_produceCv.wait(lock, [this]() { return m_tasks.size() < m_queueSize; });
+    m_tasks.emplace(task);
+
+    lock.unlock();
+
+    // Notify worker that a task is available.
+    m_consumeCv.notify_all();
+}
+
+void Pool::work()
+{
+    std::unique_lock<std::mutex> lock(m_mutex);
+
+    while (!stop() || !m_tasks.empty())
+    {
+        m_consumeCv.wait(lock, [this]() { return !m_tasks.empty() || stop(); });
+
+        if (!m_tasks.empty())
+        {
+            ++m_running;
+            auto task(std::move(m_tasks.front()));
+            m_tasks.pop();
+
+            lock.unlock();
+
+            // Notify add(), which may be waiting for a spot in the queue.
+            m_produceCv.notify_all();
+
+            try
+            {
+                task();
+            }
+            catch (std::exception& e)
+            {
+                std::cout <<
+                    "Exception caught in pool task: " << e.what() << std::endl;
+            }
+            catch (...)
+            {
+                std::cout <<
+                    "Unknown exception caught in pool task." << std::endl;
+            }
+
+            // Notify await(), which may be waiting for a running task.
+            --m_running;
+            m_produceCv.notify_all();
+
+            lock.lock();
+        }
+    }
+}
+
+bool Pool::stop() const
+{
+    return m_stop.load();
+}
+
+void Pool::stop(const bool val)
+{
+    m_stop.store(val);
+}
+
+} // namespace entwine
+} // namespace pdal
+
diff --git a/plugins/greyhound/io/pool.hpp b/plugins/greyhound/io/pool.hpp
new file mode 100644
index 0000000..836ba2c
--- /dev/null
+++ b/plugins/greyhound/io/pool.hpp
@@ -0,0 +1,93 @@
+/******************************************************************************
+* Copyright (c) 2016, Connor Manning (connor at hobu.co)
+*
+* Entwine -- Point cloud indexing
+*
+* Entwine is available under the terms of the LGPL2 license. See COPYING
+* for specific license text and more information.
+*
+******************************************************************************/
+
+#pragma once
+
+#include <atomic>
+#include <condition_variable>
+#include <cstddef>
+#include <functional>
+#include <mutex>
+#include <queue>
+#include <thread>
+#include <vector>
+
+namespace pdal
+{
+namespace entwine
+{
+
+class Pool
+{
+public:
+    // After numThreads tasks are actively running, and queueSize tasks have
+    // been enqueued to wait for an available worker thread, subsequent calls
+    // to Pool::add will block until an enqueued task has been popped from the
+    // queue.
+    Pool(std::size_t numThreads, std::size_t queueSize = 1);
+    ~Pool();
+
+    // Start worker threads
+    void go();
+
+    // Wait for all currently running tasks to complete.
+    void join();
+
+    bool joined() const { return stop(); }
+
+    void cycle() { join(); go(); }
+
+    // Change the number of threads.  Current threads will be joined.
+    void resize(std::size_t numThreads);
+
+    // Wait for all current tasks to complete.  As opposed to join, tasks may
+    // continue to be added while a thread is await()-ing the queue to empty.
+    void await();
+
+    // Not thread-safe, pool should be joined before calling.
+    const std::vector<std::string>& errors() const { return m_errors; }
+
+    // Add a threaded task, blocking until a thread is available.  If join() is
+    // called, add() may not be called again until go() is called and completes.
+    void add(std::function<void()> task);
+
+    std::size_t numThreads() const { return m_numThreads; }
+
+private:
+    // Worker thread function.  Wait for a task and run it - or if stop() is
+    // called, complete any outstanding task and return.
+    void work();
+
+    // Atomically set/get the stop flag.
+    bool stop() const;
+    void stop(bool val);
+
+    std::size_t m_numThreads;
+    std::size_t m_queueSize;
+    std::vector<std::thread> m_threads;
+    std::queue<std::function<void()>> m_tasks;
+    std::atomic_size_t m_running;
+
+    std::vector<std::string> m_errors;
+    std::mutex m_errorMutex;
+
+    std::atomic<bool> m_stop;
+    std::mutex m_mutex;
+    std::condition_variable m_produceCv;
+    std::condition_variable m_consumeCv;
+
+    // Disable copy/assignment.
+    Pool(const Pool& other);
+    Pool& operator=(const Pool& other);
+};
+
+} // namespace entwine
+} // namespace pdal
+
diff --git a/plugins/greyhound/io/range.hpp b/plugins/greyhound/io/range.hpp
deleted file mode 100644
index a8e1209..0000000
--- a/plugins/greyhound/io/range.hpp
+++ /dev/null
@@ -1,37 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Connor Manning (connor at hobu.co)
-*
-* Entwine -- Point cloud indexing
-*
-* Supporting libraries released under PDAL licensing by Hobu, inc.
-*
-******************************************************************************/
-
-#pragma once
-
-#include <limits>
-
-namespace pdal { namespace greyhound {
-
-class Range
-{
-public:
-    Range()
-        : min(std::numeric_limits<double>::max())
-        , max(std::numeric_limits<double>::lowest())
-    { }
-
-    Range(double min, double max) : min(min), max(max) { }
-
-    void grow(double val)
-    {
-        min = std::min(min, val);
-        max = std::max(max, val);
-    }
-
-    double min;
-    double max;
-};
-
-} // namespace entwine
-} // namepsace pdal
diff --git a/plugins/greyhound/test/GreyhoundReaderTest.cpp b/plugins/greyhound/test/GreyhoundReaderTest.cpp
index 38bb415..98df4fc 100644
--- a/plugins/greyhound/test/GreyhoundReaderTest.cpp
+++ b/plugins/greyhound/test/GreyhoundReaderTest.cpp
@@ -32,6 +32,8 @@
 * OF SUCH DAMAGE.
 ****************************************************************************/
 
+#include <sstream>
+
 #include <pdal/pdal_test_main.hpp>
 
 #include <pdal/Writer.hpp>
@@ -40,105 +42,220 @@
 
 #include "Support.hpp"
 #include "../io/GreyhoundReader.hpp"
+#include "../io/bounds.hpp"
 
 using namespace pdal;
 
-namespace { // anonymous
+namespace
+{
 
-std::string ROOT_URL("http://data.greyhound.io");
+const bool fullTest(false);
 
-Options getGreyhoundOptions()
-{
-    Options options;
+const greyhound::Bounds fullBounds(634950,848860,-1830,639650,853560,2870);
+const greyhound::Bounds stadiumBounds(637000,851550,-1830,637800,852300,2870);
+const greyhound::Bounds patchBounds(637000,851550,-1830,637100,851650,2870);
+const greyhound::Bounds originBounds(
+        greyhound::Bounds(-92369, 123812, -11170, -22218, 230745, 2226)
+        .unscale(.01, greyhound::Point(637300, 851210, 520)));
 
-    options.add(Option("url", ROOT_URL));
 
-    // Grab just the stadium area
-    options.add(Option("bounds", "([638404.60,638895.46],[852818.85,853379.15], [-1,1000])"));
-    options.add(Option("resource", "autzen-h"));
-    options.add(Option("depth_begin", 1));
-    options.add(Option("depth_end", 20));
-    options.add(Option("timeout", 50000));
-    options.add(Option("debug", true));
-    options.add(Option("verbose", 8));
+std::string toString(const greyhound::Bounds& b)
+{
+    std::ostringstream ss;
+    ss << "([" <<
+        b.min().x << "," << b.max().x << "],[" <<
+        b.min().y << "," << b.max().y << "],[" <<
+        b.min().z << "," << b.max().z << "])";
+    return ss.str();
+}
+
+const std::string server("http://dev.greyhound.io");
+const std::string resource("autzen-chipped");
 
+Options greyhoundOptions(
+        const greyhound::Bounds* b = nullptr,
+        const std::size_t depthBegin = 0,
+        const std::size_t depthEnd = 0)
+{
+    Options options;
+    options.add("url", server);
+    options.add("resource", resource);
+    if (b) options.add("bounds", toString(*b));
+    if (depthBegin) options.add("depth_begin", depthBegin);
+    if (depthEnd) options.add("depth_end", depthEnd);
     return options;
 }
 
+pdal::greyhound::Bounds toBounds(const BOX3D& b)
+{
+    return greyhound::Bounds(b.minx, b.miny, b.minz, b.maxx, b.maxy, b.maxz);
+}
+
 }
 
 class GreyhoundReaderTest : public testing::Test
 {
 public:
-    GreyhoundReaderTest() : m_bSkipTests(true) {};
-protected:
-    virtual void SetUp()
+    GreyhoundReaderTest()
+        : m_doTests(false)
     {
-        arbiter::Arbiter a;
-        try
-        {
-//             a.get(ROOT_URL+"/resource/nyc-h/info");
-//             m_bSkipTests = false;
-        } catch (arbiter::ArbiterError&)
-        {
-            m_bSkipTests = true;
-        }
+        static std::string path(server + "/resource/" + resource + "/info");
+        static bool good(arbiter::Arbiter().tryGetSize(path));
+        m_doTests = good;
     }
 
+protected:
+    bool doTests() const { return m_doTests; }
+    bool m_doTests;
+};
 
-    virtual void TearDown()
-    {
-    }
+TEST_F(GreyhoundReaderTest, tilePath)
+{
+    if (!doTests()) return;
 
-    bool shouldSkipTests() const { return m_bSkipTests; }
-private:
+    pdal::GreyhoundReader reader;
 
-    bool m_bSkipTests;
-};
+    auto options(greyhoundOptions(nullptr, 14, 15));
+    options.add("tile_path", "c-13.laz");
+    reader.setOptions(options);
 
+    pdal::PointTable table;
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    PointViewPtr view = *viewSet.begin();
+    ASSERT_EQ(view->size(), 2676u);
 
-TEST_F(GreyhoundReaderTest, read)
-{
-    if (shouldSkipTests())
+    greyhound::Point p;
+    for (std::size_t i(0); i < view->size(); ++i)
     {
-        return;
+        p.x = view->getFieldAs<double>(Dimension::Id::X, i);
+        p.y = view->getFieldAs<double>(Dimension::Id::Y, i);
+        p.z = view->getFieldAs<double>(Dimension::Id::Z, i);
+
+        ASSERT_TRUE(originBounds.contains(p));
     }
+}
+
+TEST_F(GreyhoundReaderTest, readPatch)
+{
+    if (!doTests()) return;
 
     pdal::GreyhoundReader reader;
+    reader.setOptions(greyhoundOptions(&stadiumBounds, 14, 15));
 
-    reader.setOptions(getGreyhoundOptions());
     pdal::PointTable table;
     reader.prepare(table);
     PointViewSet viewSet = reader.execute(table);
     PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 13874u);
-//
-    int position (10);
-    EXPECT_DOUBLE_EQ(view->getFieldAs<double>(pdal::Dimension::Id::X, position), 637472.70000000007);
+    ASSERT_EQ(view->size(), 1880u);
+
+    greyhound::Point p;
+    for (std::size_t i(0); i < view->size(); ++i)
+    {
+        p.x = view->getFieldAs<double>(Dimension::Id::X, i);
+        p.y = view->getFieldAs<double>(Dimension::Id::Y, i);
+        p.z = view->getFieldAs<double>(Dimension::Id::Z, i);
 
+        ASSERT_TRUE(stadiumBounds.contains(p));
+    }
 }
 
-TEST_F(GreyhoundReaderTest, quick)
+TEST_F(GreyhoundReaderTest, filter)
 {
-    if (shouldSkipTests())
+    if (!doTests()) return;
+
     {
-        return;
+        pdal::GreyhoundReader reader;
+        auto options(greyhoundOptions(&stadiumBounds, 0, 12));
+        options.add("filter", "{ \"Z\": { \"$gte\": 500 } }");
+
+        reader.setOptions(options);
+
+        pdal::PointTable table;
+        reader.prepare(table);
+        PointViewSet viewSet = reader.execute(table);
+        PointViewPtr view = *viewSet.begin();
+        ASSERT_LT(view->size(), 188260u);
+
+        greyhound::Point p;
+        for (std::size_t i(0); i < view->size(); ++i)
+        {
+            p.x = view->getFieldAs<double>(Dimension::Id::X, i);
+            p.y = view->getFieldAs<double>(Dimension::Id::Y, i);
+            p.z = view->getFieldAs<double>(Dimension::Id::Z, i);
+
+            ASSERT_TRUE(stadiumBounds.contains(p));
+            ASSERT_GE(p.z, 500);
+        }
+    }
+
+    {
+        pdal::GreyhoundReader reader;
+        auto options(greyhoundOptions(&stadiumBounds, 0, 12));
+
+        Json::Value filter;
+        filter["Z"]["$gte"] = 500;
+        options.add("filter", filter);
+
+        reader.setOptions(options);
+
+        pdal::PointTable table;
+        reader.prepare(table);
+        PointViewSet viewSet = reader.execute(table);
+        PointViewPtr view = *viewSet.begin();
+        ASSERT_LT(view->size(), 188260u);
+
+        greyhound::Point p;
+        for (std::size_t i(0); i < view->size(); ++i)
+        {
+            p.x = view->getFieldAs<double>(Dimension::Id::X, i);
+            p.y = view->getFieldAs<double>(Dimension::Id::Y, i);
+            p.z = view->getFieldAs<double>(Dimension::Id::Z, i);
+
+            ASSERT_TRUE(stadiumBounds.contains(p));
+            ASSERT_GE(p.z, 500);
+        }
     }
+}
+
+TEST_F(GreyhoundReaderTest, quickFull)
+{
+    if (!doTests()) return;
 
     pdal::GreyhoundReader reader;
+    reader.setOptions(greyhoundOptions());
 
-    reader.setOptions(getGreyhoundOptions());
     pdal::QuickInfo qi = reader.preview();
-    EXPECT_EQ(qi.m_pointCount, 5183374u);
+    EXPECT_EQ(qi.m_pointCount, 10653336u);
+    EXPECT_EQ(toBounds(qi.m_bounds), fullBounds);
+}
 
-    BOX3D bounds = qi.m_bounds;
-    EXPECT_DOUBLE_EQ(bounds.minx, 635577.79000000004);
-    EXPECT_DOUBLE_EQ(bounds.miny, 848882.15000000002);
-    EXPECT_DOUBLE_EQ(bounds.minz, 406.13999999999999);
-    EXPECT_DOUBLE_EQ(bounds.maxx, 639003.72999999998);
-    EXPECT_DOUBLE_EQ(bounds.maxy, 853537.66000000003);
-    EXPECT_DOUBLE_EQ(bounds.maxz, 615.25999999999999);
+TEST_F(GreyhoundReaderTest, quickSplit)
+{
+    if (!doTests()) return;
 
+    pdal::GreyhoundReader reader;
+    reader.setOptions(greyhoundOptions(&patchBounds));
+
+    // The quick-info point count is an upper bound, so it should be at no less
+    // than the actual query result.
+    pdal::QuickInfo qi = reader.preview();
+    EXPECT_GE(qi.m_pointCount, 5141u);
+}
 
+TEST_F(GreyhoundReaderTest, full)
+{
+    if (!doTests() || !fullTest) return;
+
+    pdal::GreyhoundReader reader;
+    auto options(greyhoundOptions());
+    options.add("threads", 10);
+    reader.setOptions(options);
+
+    pdal::PointTable table;
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    PointViewPtr view = *viewSet.begin();
+    ASSERT_EQ(view->size(), 10653336u);
 }
 
diff --git a/plugins/hexbin/CMakeLists.txt b/plugins/hexbin/CMakeLists.txt
index 02b9dfc..9c02216 100644
--- a/plugins/hexbin/CMakeLists.txt
+++ b/plugins/hexbin/CMakeLists.txt
@@ -2,32 +2,34 @@
 # HexBin plugin CMake configuration
 #
 
-include_directories(${ROOT_DIR}/vendor/pdalboost)
-
 find_package(Hexer REQUIRED)
 if (HEXER_FOUND)
-    include_directories(${HEXER_INCLUDE_DIR})
     add_definitions(-DHAVE_HEXER=1)
 
-    set(srcs filters/HexBin.cpp)
-    set(incs filters/HexBin.hpp)
-
     PDAL_ADD_PLUGIN(libname filter hexbin
-        FILES "${srcs}" "${incs}"
-        LINK_WITH ${HEXER_LIBRARY})
+        FILES
+            filters/HexBin.cpp
+        LINK_WITH
+            ${HEXER_LIBRARY})
+    target_include_directories(${libname} PRIVATE
+        ${PDAL_VENDOR_DIR}/pdalboost
+        ${HEXER_INCLUDE_DIR})
 
     if (WITH_TESTS)
         PDAL_ADD_TEST(hexbintest
-            FILES test/HexbinFilterTest.cpp
-            LINK_WITH ${libname})
+            FILES
+                test/HexbinFilterTest.cpp
+            LINK_WITH
+                ${libname})
     endif()
 
 
-    set(srcs kernel/DensityKernel.cpp kernel/OGR.cpp)
-    set(incs kernel/DensityKernel.hpp kernel/OGR.hpp)
-
     PDAL_ADD_PLUGIN(density_kernel_libname kernel density
-        FILES "${srcs}" "${incs}"
-        LINK_WITH ${HEXER_LIBRARY} ${libname})
+        FILES
+            kernel/DensityKernel.cpp
+            kernel/OGR.cpp
+        LINK_WITH
+            ${HEXER_LIBRARY}
+            ${libname})
 
 endif()
diff --git a/plugins/hexbin/filters/HexBin.cpp b/plugins/hexbin/filters/HexBin.cpp
index 63c77be..e8fb1f7 100644
--- a/plugins/hexbin/filters/HexBin.cpp
+++ b/plugins/hexbin/filters/HexBin.cpp
@@ -64,6 +64,7 @@ void HexBin::addArgs(ProgramArgs& args)
     args.add("precision", "Output precision", m_precision, 8U);
     m_cullArg = &args.add("hole_cull_area_tolerance", "Tolerance area to "
         "apply to holes before cull", m_cullArea);
+    args.add("smooth", "Smooth boundary output", m_doSmooth, true);
 }
 
 
@@ -95,8 +96,21 @@ void HexBin::filter(PointView& view)
 void HexBin::done(PointTableRef table)
 {
     m_grid->processSample();
-    m_grid->findShapes();
-    m_grid->findParentPaths();
+
+    try
+    {
+        m_grid->findShapes();
+        m_grid->findParentPaths();
+    }
+    catch (hexer::hexer_error& e)
+    {
+        m_metadata.add("error", e.what(), "Hexer threw an error and was unable to compute a boundary");
+        m_metadata.add("boundary", "MULTIPOLYGON EMPTY", "Empty polygon -- unable to compute boundary");
+
+        return;
+
+    }
+
 
     std::ostringstream offsets;
     offsets << "MULTIPOINT (";
@@ -193,10 +207,14 @@ void HexBin::done(PointTableRef table)
         SpatialReference utm(makezone(zone));
         density_p = p.transform(utm);
     }
-    pdal::Polygon smooth = p.simplify(tolerance, cull);
-    std::string smooth_text = smooth.wkt(m_precision);
 
-    m_metadata.add("boundary", smooth_text, "Approximated MULTIPOLYGON of domain");
+    if (m_doSmooth)
+        p = p.simplify(tolerance, cull);
+
+    std::string boundary_text = p.wkt(m_precision);
+
+    m_metadata.add("boundary", boundary_text, "Approximated MULTIPOLYGON of domain");
+    m_metadata.addWithType("boundary_json", p.json(), "json", "Approximated MULTIPOLYGON of domain");
     double area = density_p.area();
 
 //    double density = (double) m_grid->densePointCount() / area ;
diff --git a/plugins/hexbin/filters/HexBin.hpp b/plugins/hexbin/filters/HexBin.hpp
index 7258831..02007fb 100644
--- a/plugins/hexbin/filters/HexBin.hpp
+++ b/plugins/hexbin/filters/HexBin.hpp
@@ -67,6 +67,7 @@ private:
     int32_t m_density;
     double m_edgeLength;
     bool m_outputTesselation;
+    bool m_doSmooth;
     point_count_t m_count;
 
     virtual void addArgs(ProgramArgs& args);
diff --git a/plugins/hexbin/kernel/DensityKernel.cpp b/plugins/hexbin/kernel/DensityKernel.cpp
index 0b0dd74..f5a82bb 100644
--- a/plugins/hexbin/kernel/DensityKernel.cpp
+++ b/plugins/hexbin/kernel/DensityKernel.cpp
@@ -39,14 +39,13 @@
 #include <pdal/GDALUtils.hpp>
 #include <pdal/pdal_macros.hpp>
 #include <pdal/plugin.hpp>
+#include <pdal/util/FileUtils.hpp>
 
 namespace pdal
 {
 
-static PluginInfo const s_info = PluginInfo(
-    "kernels.density",
-    "Density Kernel",
-    "http://pdal.io/kernels/kernels.density.html" );
+static PluginInfo const s_info = PluginInfo("kernels.density", "Density Kernel",
+    "http://www.pdal.io/apps/density.html" );
 
 CREATE_SHARED_PLUGIN(1, 0, DensityKernel, Kernel, s_info)
 
@@ -59,6 +58,7 @@ void DensityKernel::addSwitches(ProgramArgs& args)
     args.add("output,o", "output vector data source", m_outputFile);
     args.add("ogrdriver,f", "OGR driver name to use ", m_driverName,
         "ESRI Shapefile");
+    args.add("lyr_name", "OGR layer name to use", m_layerName, "");
 }
 
 
@@ -71,7 +71,7 @@ void DensityKernel::outputDensity(pdal::SpatialReference const& reference)
     hexer::HexGrid* grid = hexbin->grid();
 
     hexdensity::writer::OGR writer(m_outputFile, reference.getWKT(),
-        m_driverName);
+        m_driverName, m_layerName);
     writer.writeDensity(grid);
 //     writer.writeBoundary(grid);
 }
diff --git a/plugins/hexbin/kernel/DensityKernel.hpp b/plugins/hexbin/kernel/DensityKernel.hpp
index 3a59007..5691b87 100644
--- a/plugins/hexbin/kernel/DensityKernel.hpp
+++ b/plugins/hexbin/kernel/DensityKernel.hpp
@@ -56,6 +56,7 @@ private:
     std::string m_inputFile;
     std::string m_outputFile;
     std::string m_driverName;
+    std::string m_layerName;
 
     virtual void addSwitches(ProgramArgs& args);
     void outputDensity(pdal::SpatialReference const& ref);
diff --git a/plugins/hexbin/kernel/OGR.cpp b/plugins/hexbin/kernel/OGR.cpp
index c62fc3c..0bf474c 100644
--- a/plugins/hexbin/kernel/OGR.cpp
+++ b/plugins/hexbin/kernel/OGR.cpp
@@ -39,6 +39,7 @@
 #include <functional>
 
 #include <pdal/GDALUtils.hpp>
+#include <pdal/util/FileUtils.hpp>
 
 #include <hexer/HexGrid.hpp>
 #include <hexer/HexIter.hpp>
@@ -54,12 +55,13 @@ namespace hexdensity
 namespace writer
 {
 
-OGR::OGR(std::string const& filename, std::string srs, std::string driver)
+OGR::OGR(std::string const& filename, std::string srs, std::string driver, std::string layerName)
     : m_filename(filename)
     , m_driver(driver)
     , m_srs(srs)
     , m_ds(0)
     , m_layer(0)
+    , m_layerName(layerName)
 {
     createLayer();
 }
@@ -73,17 +75,28 @@ void OGR::createLayer()
         throw pdal::pdal_error("OGR Driver was null!");
     }
 
-
-    m_ds = OGR_Dr_CreateDataSource(driver, m_filename.c_str(), NULL);
-    if (m_ds == NULL)
+    char **papszDSCO = NULL;
+    if (pdal::FileUtils::fileExists(m_filename))
+    {
+        m_ds = OGR_Dr_Open(driver, m_filename.c_str(), TRUE /*update*/);
+    }
+    else
     {
-        throw pdal::pdal_error("Data source creation was null!");
+
+
+        m_ds = OGR_Dr_CreateDataSource(driver, m_filename.c_str(), NULL);
+        if (m_ds == NULL)
+        {
+            throw pdal::pdal_error("Data source creation was null!");
+        }
     }
 
     pdal::gdal::SpatialRef spatialref(m_srs);
     OGRSpatialReferenceH ref = (OGRSpatialReferenceH)spatialref.get();
 
-    m_layer = OGR_DS_CreateLayer(m_ds, m_filename.c_str(), ref, wkbMultiPolygon, NULL);
+    if (!m_layerName.size())
+        m_layerName = m_filename;
+    m_layer = OGR_DS_CreateLayer(m_ds, m_layerName.c_str(), ref, wkbMultiPolygon, NULL);
     if (m_layer == NULL)
     {
         throw pdal::pdal_error("Layer creation was null!");
diff --git a/plugins/hexbin/kernel/OGR.hpp b/plugins/hexbin/kernel/OGR.hpp
index 52c140c..391d476 100644
--- a/plugins/hexbin/kernel/OGR.hpp
+++ b/plugins/hexbin/kernel/OGR.hpp
@@ -63,7 +63,7 @@ class OGR
 
 
 public:
-    OGR(std::string const& filename, std::string srs, std::string driver = "ESRI Shapefile");
+    OGR(std::string const& filename, std::string srs, std::string driver = "ESRI Shapefile", std::string layerName ="");
     ~OGR();
 
     void writeBoundary(hexer::HexGrid *grid);
@@ -76,6 +76,7 @@ private:
 
     OGRDataSourceH m_ds;
     OGRLayerH m_layer;
+    std::string m_layerName;
 
     void createLayer();
     void collectPath(hexer::Path* path, OGRGeometryH polygon);
diff --git a/plugins/hexbin/test/HexbinFilterTest.cpp b/plugins/hexbin/test/HexbinFilterTest.cpp
index c3473ea..63c26e5 100644
--- a/plugins/hexbin/test/HexbinFilterTest.cpp
+++ b/plugins/hexbin/test/HexbinFilterTest.cpp
@@ -37,6 +37,7 @@
 #include <pdal/SpatialReference.hpp>
 #include <pdal/StageFactory.hpp>
 #include <pdal/PointView.hpp>
+#include <pdal/util/FileUtils.hpp>
 
 #include "Support.hpp"
 
diff --git a/plugins/icebridge/CMakeLists.txt b/plugins/icebridge/CMakeLists.txt
index 3780cbf..aebfa70 100644
--- a/plugins/icebridge/CMakeLists.txt
+++ b/plugins/icebridge/CMakeLists.txt
@@ -4,24 +4,19 @@
 
 include (${PDAL_CMAKE_DIR}/hdf5.cmake)
 
-include_directories(${ROOT_DIR}/vendor/pdalboost)
 
 if (NOT PDAL_HAVE_HDF5)
     message(FATAL "HDF5 not found but is required for Icebridge.")
 else()
-    set(srcs
-        io/IcebridgeReader.cpp
-        io/Hdf5Handler.cpp
-    )
-
-    set(incs
-        io/IcebridgeReader.hpp
-        io/Hdf5Handler.hpp
-    )
-
     PDAL_ADD_PLUGIN(libname reader icebridge
-        FILES "${srcs}" "${incs}"
+        FILES
+            io/IcebridgeReader.cpp
+            io/Hdf5Handler.cpp
         LINK_WITH ${HDF5_LIBRARIES})
+    target_include_directories(${libname}
+        PRIVATE
+            ${ROOT_DIR}
+            ${LIBXML2_INCLUDE_DIR})
 
     if (WITH_TESTS)
         PDAL_ADD_TEST(icetest
diff --git a/plugins/icebridge/io/IcebridgeReader.cpp b/plugins/icebridge/io/IcebridgeReader.cpp
index 720ff34..f2ea00e 100644
--- a/plugins/icebridge/io/IcebridgeReader.cpp
+++ b/plugins/icebridge/io/IcebridgeReader.cpp
@@ -105,6 +105,10 @@ void IcebridgeReader::ready(PointTableRef table)
 {
     m_hdf5Handler.initialize(m_filename, hdf5Columns);
     m_index = 0;
+    if (!m_metadataFile.empty())
+    {
+        m_mdReader.readMetadataFile(m_metadataFile, &m_metadata);
+    }
 }
 
 
@@ -179,7 +183,9 @@ point_count_t IcebridgeReader::read(PointViewPtr view, point_count_t count)
         }
         catch(...)
         {
-            throw icebridge_error("Error fetching column data");
+            std::ostringstream oss;
+            oss << getName() << ": Error fetching column data.";
+            throw pdal_error(oss.str());
         }
     }
     return count;
@@ -209,11 +215,6 @@ void IcebridgeReader::initialize()
 void IcebridgeReader::done(PointTableRef table)
 {
     m_hdf5Handler.close();
-    if (!m_metadataFile.empty())
-    {
-        m_mdReader.readMetadataFile(m_metadataFile, &m_metadata);
-    }
-
 }
 
 
diff --git a/plugins/icebridge/io/IcebridgeReader.hpp b/plugins/icebridge/io/IcebridgeReader.hpp
index fe457c1..d19d509 100644
--- a/plugins/icebridge/io/IcebridgeReader.hpp
+++ b/plugins/icebridge/io/IcebridgeReader.hpp
@@ -48,7 +48,7 @@ namespace pdal
   };
 }
 #else
-    #include "../../io/ilvis2/Ilvis2MetadataReader.hpp"
+    #include <io/Ilvis2MetadataReader.hpp>
 #endif
 
 
@@ -59,13 +59,6 @@ namespace pdal
 namespace pdal
 {
 
-class icebridge_error : public pdal_error
-{
-public:
-    icebridge_error(std::string const& msg) : pdal_error(msg)
-    { }
-};
-
 class PDAL_DLL IcebridgeReader : public pdal::Reader
 {
 public:
diff --git a/plugins/icebridge/test/IcebridgeReaderTest.cpp b/plugins/icebridge/test/IcebridgeReaderTest.cpp
index 34ac750..174b8ad 100644
--- a/plugins/icebridge/test/IcebridgeReaderTest.cpp
+++ b/plugins/icebridge/test/IcebridgeReaderTest.cpp
@@ -38,6 +38,7 @@
 #include <pdal/PointView.hpp>
 #include <pdal/PipelineManager.hpp>
 #include <pdal/StageFactory.hpp>
+#include <pdal/util/FileUtils.hpp>
 
 #include "Support.hpp"
 
@@ -141,7 +142,7 @@ TEST(IcebridgeReaderTest, testPipeline)
 {
     PipelineManager manager;
 
-    manager.readPipeline(Support::configuredpath("icebridge/pipeline.xml"));
+    manager.readPipeline(Support::configuredpath("icebridge/pipeline.json"));
 
     point_count_t numPoints = manager.execute();
     EXPECT_EQ(numPoints, 2u);
diff --git a/plugins/matlab/CMakeLists.txt b/plugins/matlab/CMakeLists.txt
index b0f5609..4de6552 100644
--- a/plugins/matlab/CMakeLists.txt
+++ b/plugins/matlab/CMakeLists.txt
@@ -2,22 +2,21 @@
 
 find_package(Matlab REQUIRED)
 
-include_directories(${MATLAB_INCLUDE_DIR})
-add_definitions(-DHAVE_MATLAB=1)
-
-set(inc io/MatlabWriter.hpp)
-set(src io/MatlabWriter.cpp)
-
 PDAL_ADD_PLUGIN(libname writer matlab
-    FILES "${src}" "${inc}"
-    LINK_WITH ${MATLAB_LIBRARIES}
-    )
+    FILES
+        io/MatlabWriter.cpp
+    LINK_WITH
+        ${MATLAB_LIBRARIES}
+)
+target_compile_definitions(${libname} PRIVATE -DHAVE_MATLAB=1)
+target_include_directories(${libname} PRIVATE ${MATLAB_INCLUDE_DIR})
 
 if (WITH_TESTS)
-    include_directories(io)
     PDAL_ADD_TEST(matlabtest
-        FILES test/MatlabWriterTest.cpp
-        LINK_WITH ${libname}
-        )
+        FILES
+            test/MatlabWriterTest.cpp
+        LINK_WITH
+            ${libname}
+    )
 
 endif()
diff --git a/plugins/matlab/io/MatlabWriter.cpp b/plugins/matlab/io/MatlabWriter.cpp
index 033f3bf..4cca48b 100644
--- a/plugins/matlab/io/MatlabWriter.cpp
+++ b/plugins/matlab/io/MatlabWriter.cpp
@@ -53,6 +53,7 @@ std::string MatlabWriter::getName() const { return s_info.name; }
 
 void MatlabWriter::addArgs(ProgramArgs& args)
 {
+    args.add("filename", "Output filename", m_filename).setPositional();
     args.add("output_dims", "Output dimensions", m_outputDims);
 }
 
@@ -122,7 +123,8 @@ void MatlabWriter::write(const PointViewPtr view)
     mxArray * points = mxCreateDoubleMatrix(nPoints, nDimensions, mxREAL);
     if (!points) {
         std::stringstream ss;
-        ss << "Could not create a points array with dimensions " << nPoints << "x" << nDimensions;
+        ss << "Could not create a points array with dimensions " <<
+            nPoints << "x" << nDimensions;
         throw pdal_error(ss.str());
     }
 
@@ -158,6 +160,7 @@ void MatlabWriter::done(PointTableRef table)
         ss << "Unsuccessful write: " << m_filename;
         throw pdal_error(ss.str());
     }
+    getMetadata().addList("filename", m_filename);
 }
 
 
diff --git a/plugins/matlab/io/MatlabWriter.hpp b/plugins/matlab/io/MatlabWriter.hpp
index 6929d7a..a6e6074 100644
--- a/plugins/matlab/io/MatlabWriter.hpp
+++ b/plugins/matlab/io/MatlabWriter.hpp
@@ -65,6 +65,7 @@ private:
     virtual void write(const PointViewPtr view);
     virtual void done(PointTableRef table);
 
+    std::string m_filename;
     StringList m_outputDims; ///< List of dimensions to write
     // Can't use unique_ptr b/c MATFile is an incomplete type.
     MATFile * m_matfile;
diff --git a/plugins/mrsid/CMakeLists.txt b/plugins/mrsid/CMakeLists.txt
index 68284de..7974259 100644
--- a/plugins/mrsid/CMakeLists.txt
+++ b/plugins/mrsid/CMakeLists.txt
@@ -4,22 +4,22 @@
 
 set(MRSID_ROOT "/Users/hobu/installs/mrsid/Lidar_DSDK" CACHE STRING "Root directory of MrSID install")
 find_package(MrSID)
-if(MRSID_FOUND)
-    message(STATUS "Building with MrSID support")
-
-    include_directories(${MRSID_INCLUDE_DIR})
-    add_definitions(-DHAVE_MRSID=1)
-
-    set(srcs io/MrsidReader.cpp)
-    set(incs io/MrsidReader.hpp)
-
-    PDAL_ADD_PLUGIN(libname reader mrsid
-        FILES "${srcs}" "${incs}"
+if (MRSID_FOUND)
+    PDAL_ADD_PLUGIN(reader_libname reader mrsid
+        FILES
+            io/MrsidReader.cpp
         LINK_WITH ${MRSID_LIBRARY})
+    target_include_directories(${reader_libname} PRIVATE
+        ${PDAL_IO_DIR}
+        ${MRSID_INCLUDE_DIR})
+    target_compile_definitions(${reader_libname} PRIVATE -DHAVE_MRSID=1)
+
     if (WITH_TESTS)
         PDAL_ADD_TEST(mrsidtest
             FILES test/MrsidTest.cpp
-            LINK_WITH ${libname})
+            LINK_WITH ${reader_libname})
+        target_include_directories(mrsidtest PRIVATE
+            ${PDAL_IO_DIR})
     endif()
 else()
     message(STATUS "Building without MrSID support")
diff --git a/plugins/mrsid/io/MrsidReader.cpp b/plugins/mrsid/io/MrsidReader.cpp
index 179a2a8..9a9c235 100644
--- a/plugins/mrsid/io/MrsidReader.cpp
+++ b/plugins/mrsid/io/MrsidReader.cpp
@@ -56,6 +56,10 @@ MrsidReader::MrsidReader()
     : pdal::Reader()
     , m_PS(0), m_iter(NULL)
     , m_initialized(false)
+{}
+
+
+void MrsidReader::addArgs(ProgramArgs& args)
 {
 }
 
@@ -67,6 +71,7 @@ void MrsidReader::done(PointTableRef)
     m_initialized = false;
     m_PS = 0;
     m_iter = 0;
+    getMetadata().addList("filename", m_filename);
 }
 
 pdal::Dimension::Type getPDALType(LizardTech::DataType t)
diff --git a/plugins/mrsid/io/MrsidReader.hpp b/plugins/mrsid/io/MrsidReader.hpp
index ba533c8..bd974aa 100644
--- a/plugins/mrsid/io/MrsidReader.hpp
+++ b/plugins/mrsid/io/MrsidReader.hpp
@@ -75,6 +75,7 @@ public:
 
 protected:
     virtual void addDimensions(PointLayoutPtr layout);
+
 private:
     LizardTech::MG4PointReader *m_PS;
     LizardTech::PointIterator *m_iter;
@@ -87,6 +88,7 @@ private:
     void LayoutToPointInfo(const PointLayout &layout,
         LizardTech::PointInfo &pointInfo) const;
     virtual QuickInfo inspect();
+    virtual void addArgs(ProgramArgs& args);
     virtual void ready(PointTableRef table)
         { ready(table, m_metadata); }
     virtual void ready(PointTableRef table, MetadataNode& m);
diff --git a/plugins/mrsid/test/MrsidTest.cpp b/plugins/mrsid/test/MrsidTest.cpp
index a3d05e3..abd7de4 100644
--- a/plugins/mrsid/test/MrsidTest.cpp
+++ b/plugins/mrsid/test/MrsidTest.cpp
@@ -38,7 +38,7 @@
 #include <pdal/PointView.hpp>
 #include <pdal/PipelineManager.hpp>
 #include <pdal/StageFactory.hpp>
-#include <las/LasWriter.hpp>
+#include <io/LasWriter.hpp>
 
 #include "Support.hpp"
 
diff --git a/plugins/nitf/CMakeLists.txt b/plugins/nitf/CMakeLists.txt
index c6452bc..a4827bf 100644
--- a/plugins/nitf/CMakeLists.txt
+++ b/plugins/nitf/CMakeLists.txt
@@ -1,9 +1,6 @@
 #
 # NITF plugin CMake configuration
 #
-
-include_directories(${ROOT_DIR}/vendor/pdalboost)
-
 include(${PDAL_CMAKE_DIR}/nitro.cmake)
 if (NOT NITRO_FOUND)
     message(FATAL_ERROR "Can't find NITRO support required by NITF.")
@@ -12,58 +9,46 @@ endif()
 #
 # NITF Reader
 #
-set(srcs
-    io/NitfFileReader.cpp
-    io/MetadataReader.cpp
-    io/tre_plugins.cpp
-    io/NitfReader.cpp
-)
-
-set(incs
-    io/NitfFileReader.hpp
-    io/MetadataReader.hpp
-    io/tre_plugins.hpp
-    io/NitfReader.hpp
-)
-
 PDAL_ADD_PLUGIN(reader_libname reader nitf
-    FILES "${srcs}" "${incs}"
-    LINK_WITH ${NITRO_LIBRARIES})
+    FILES
+        io/NitfFileReader.cpp
+        io/MetadataReader.cpp
+        io/tre_plugins.cpp
+        io/NitfReader.cpp
+    LINK_WITH
+        ${NITRO_LIBRARIES})
+target_include_directories(${reader_libname} PRIVATE
+    ${PDAL_VENDOR_DIR}/pdalboost
+    ${ROOT_DIR})
 
 #
 # NITF Writer
 #
-set(srcs
-    io/NitfFileWriter.cpp
-    io/NitfWriter.cpp
-    io/MetadataReader.cpp
-    io/tre_plugins.cpp
-)
-
-set(incs
-    io/NitfFileWriter.hpp
-    io/NitfWriter.hpp
-    io/MetadataReader.hpp
-    io/tre_plugins.hpp
-)
 
 PDAL_ADD_PLUGIN(writer_libname writer nitf
-    FILES "${srcs}" "${incs}"
-    LINK_WITH ${NITRO_LIBRARIES})
+    FILES
+        io/NitfFileWriter.cpp
+        io/NitfWriter.cpp
+        io/MetadataReader.cpp
+        io/tre_plugins.cpp
+    LINK_WITH
+        ${NITRO_LIBRARIES})
+target_include_directories(${writer_libname} PRIVATE
+    ${PDAL_VENDOR_DIR}/pdalboost
+    ${ROOT_DIR})
 
 if (WITH_TESTS)
-	set(srcs
-        test/NitfWriterTest.cpp
-    )
-
     PDAL_ADD_TEST(pdal_io_nitf_writer_test
-        FILES "${srcs}"
+        FILES
+            test/NitfWriterTest.cpp
         LINK_WITH ${writer_libname})
+    target_include_directories(pdal_io_nitf_writer_test PRIVATE
+        ${ROOT_DIR})
 
-    set(srcs
-        test/NitfReaderTest.cpp
-    )
     PDAL_ADD_TEST(pdal_io_nitf_reader_test
-        FILES "${srcs}"
+        FILES
+            test/NitfReaderTest.cpp
         LINK_WITH ${reader_libname})
+    target_include_directories(pdal_io_nitf_reader_test PRIVATE
+        ${ROOT_DIR})
 endif()
diff --git a/plugins/nitf/io/NitfFileWriter.cpp b/plugins/nitf/io/NitfFileWriter.cpp
index 1739f1e..ad94728 100644
--- a/plugins/nitf/io/NitfFileWriter.cpp
+++ b/plugins/nitf/io/NitfFileWriter.cpp
@@ -73,15 +73,30 @@ void NitfFileWriter::addArgs(ProgramArgs& args)
 //
 void NitfFileWriter::write()
 {
+    if (m_fileTitle.empty())
+        m_fileTitle = FileUtils::getFilename(m_filename);
+
     ::nitf::Record record(NITF_VER_21);
     ::nitf::FileHeader header = record.getHeader();
-    header.getFileHeader().set("NITF");
 
+    // This check would be better in an initialize() function, but we can't
+    // because this is a flex writer and we don't know the filename
+    // until the execute() step.
+    if (m_fileTitle.size() > header.getFileTitle().getLength())
+    {
+        std::ostringstream oss;
+
+        oss << "writers.nitf: Can't write file.  " <<
+            "FTITLE field (usually filename) can't be longer than " <<
+            header.getFileTitle().getLength() << ".  Use 'ftitle' option " <<
+            "to set appropriately sized FTITLE.";
+        throw pdal_error(oss.str());
+    }
+
+    header.getFileHeader().set("NITF");
     header.getComplianceLevel().set(m_cLevel);
     header.getSystemType().set(m_sType);
     header.getOriginStationID().set(m_oStationId);
-    if (m_fileTitle.empty())
-        m_fileTitle = FileUtils::getFilename(m_filename);
     header.getFileTitle().set(m_fileTitle);
     header.getClassification().set(m_fileClass);
     header.getMessageCopyNum().set("00000");
@@ -187,9 +202,9 @@ void NitfFileWriter::write()
         if (v.size() != 2)
         {
             std::ostringstream oss;
-            oss << "Invalid name/value for AIMIDB '" << s <<
+            oss << "writers.nitf: Invalid name/value for AIMIDB '" << s <<
                 "'.  Format: <name>:<value>.";
-            throw oss.str();
+            throw pdal_error(oss.str());
         }
         Utils::trim(v[0]);
         Utils::trim(v[1]);
@@ -227,9 +242,9 @@ void NitfFileWriter::write()
         if (v.size() != 2)
         {
             std::ostringstream oss;
-            oss << "Invalid name/value for ACFTB '" << s <<
+            oss << "writers.nitf: Invalid name/value for ACFTB '" << s <<
                 "'.  Format: <name>:<value>.";
-            throw oss.str();
+            throw pdal_error(oss.str());
         }
         Utils::trim(v[0]);
         Utils::trim(v[1]);
diff --git a/plugins/nitf/io/NitfReader.hpp b/plugins/nitf/io/NitfReader.hpp
index b9a25d8..607e477 100644
--- a/plugins/nitf/io/NitfReader.hpp
+++ b/plugins/nitf/io/NitfReader.hpp
@@ -37,8 +37,9 @@
 #include <boost/iostreams/stream.hpp>
 #include <boost/iostreams/restrict.hpp>
 
-#include <las/LasReader.hpp>
+#include <io/LasReader.hpp>
 #include <pdal/StageFactory.hpp>
+#include <pdal/util/FileUtils.hpp>
 
 namespace pdal
 {
diff --git a/plugins/nitf/io/NitfWriter.cpp b/plugins/nitf/io/NitfWriter.cpp
index db7d7e2..7aaf0d0 100644
--- a/plugins/nitf/io/NitfWriter.cpp
+++ b/plugins/nitf/io/NitfWriter.cpp
@@ -46,7 +46,9 @@
 #  pragma clang diagnostic ignored "-Wunused-private-field"
 #endif
 
+#ifndef IMPORT_NITRO_API
 #define IMPORT_NITRO_API
+#endif
 #include <nitro/c++/import/nitf.hpp>
 #include "tre_plugins.hpp"
 
@@ -82,9 +84,7 @@ BOX3D NitfWriter::reprojectBoxToDD(const SpatialReference& reference,
         return BOX3D();
 
     BOX3D output(box);
-    if (!gdal::reprojectBounds(output,
-        reference.getWKT(SpatialReference::eCompoundOK, false),
-        "EPSG:4326"))
+    if (!gdal::reprojectBounds(output, reference.getWKT(), "EPSG:4326"))
     {
         std::ostringstream msg;
 
diff --git a/plugins/nitf/io/NitfWriter.hpp b/plugins/nitf/io/NitfWriter.hpp
index 302cb14..9c486a4 100644
--- a/plugins/nitf/io/NitfWriter.hpp
+++ b/plugins/nitf/io/NitfWriter.hpp
@@ -35,7 +35,7 @@
 #pragma once
 
 #include <pdal/StageFactory.hpp>
-#include <las/LasWriter.hpp>
+#include <io/LasWriter.hpp>
 
 #include "NitfFileWriter.hpp"
 
diff --git a/plugins/nitf/io/tre_plugins.cpp b/plugins/nitf/io/tre_plugins.cpp
index 178f4f1..754f075 100644
--- a/plugins/nitf/io/tre_plugins.cpp
+++ b/plugins/nitf/io/tre_plugins.cpp
@@ -48,7 +48,9 @@
 #  pragma clang diagnostic ignored "-Wunused-private-field"
 #endif
 
+#ifndef IMPORT_NITRO_API
 #define IMPORT_NITRO_API
+#endif
 #include <nitro/c++/import/nitf.hpp>
 #include <nitro/c++/except/Trace.h>
 
@@ -155,7 +157,10 @@ void register_tre_handler(NITF_PLUGIN_INIT_FUNCTION init, NITF_PLUGIN_TRE_HANDLE
 {
   nitf_Error error;
   if (!nitf_PluginRegistry_registerTREHandler(init, handler, &error))
-    throw ::nitf::NITFException(&error);
+  {
+      ::nitf::NITFException ex(&error);
+      throw pdal_error(ex.toString());
+  }
 }
 
 void register_tre_plugins()
diff --git a/plugins/nitf/io/tre_plugins.hpp b/plugins/nitf/io/tre_plugins.hpp
index 486a3a4..33cd4f5 100644
--- a/plugins/nitf/io/tre_plugins.hpp
+++ b/plugins/nitf/io/tre_plugins.hpp
@@ -36,7 +36,9 @@
 
 #include <pdal/pdal_internal.hpp>
 
+#ifndef IMPORT_NITRO_API
 #define IMPORT_NITRO_API
+#endif
 #include <nitro/c++/import/nitf.hpp>
 #include <nitro/c++/except/Trace.h>
 
diff --git a/plugins/nitf/test/NitfReaderTest.cpp b/plugins/nitf/test/NitfReaderTest.cpp
index eb6bb5d..0706fc9 100644
--- a/plugins/nitf/test/NitfReaderTest.cpp
+++ b/plugins/nitf/test/NitfReaderTest.cpp
@@ -37,7 +37,7 @@
 #include <pdal/PointView.hpp>
 #include <pdal/PipelineManager.hpp>
 #include <pdal/StageFactory.hpp>
-#include <las/LasWriter.hpp>
+#include <io/LasWriter.hpp>
 
 #include "Support.hpp"
 
diff --git a/plugins/nitf/test/NitfWriterTest.cpp b/plugins/nitf/test/NitfWriterTest.cpp
index 8900ac5..a3a4c9e 100644
--- a/plugins/nitf/test/NitfWriterTest.cpp
+++ b/plugins/nitf/test/NitfWriterTest.cpp
@@ -36,9 +36,11 @@
 
 #include <array>
 
-#include <buffer/BufferReader.hpp>
 #include <pdal/PointView.hpp>
 #include <pdal/StageFactory.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <io/BufferReader.hpp>
+
 #include "Support.hpp"
 
 using namespace pdal;
@@ -304,3 +306,27 @@ TEST(NitfWriterTest, flex2)
     r->setOptions(ops);
     EXPECT_EQ(r->preview().m_pointCount, v->size());
 }
+
+TEST(NitfWriterTest, longFtitle)
+{
+    StageFactory f;
+
+    Stage *r = f.createStage("readers.las");
+
+    Options ro;
+    ro.add("filename", Support::datapath("nitf/autzen-utm10.las"));
+
+    r->setOptions(ro);
+
+    Stage *w = f.createStage("writers.nitf");
+
+    Options wo;
+    wo.add("filename", "aaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccddddddddddddddeeeeeeeeeeee");
+
+    w->setOptions(wo);
+    w->setInput(*r);
+
+    PointTable t;
+    w->prepare(t);
+    EXPECT_THROW(w->execute(t), pdal_error);
+}
diff --git a/plugins/oci/CMakeLists.txt b/plugins/oci/CMakeLists.txt
index 075da9b..18480f5 100644
--- a/plugins/oci/CMakeLists.txt
+++ b/plugins/oci/CMakeLists.txt
@@ -1,58 +1,46 @@
 #
 # Oracle OCI plugin CMake configuration
 #
-
 find_package(Oracle REQUIRED)
 
-include_directories(${ORACLE_INCLUDE_DIR})
-include_directories(${ROOT_DIR}/vendor/pdalboost)
-
 set(OCI_CONNECTION "scott/tiger at localhost/test" CACHE STRING
     "OCI connection string <username/password at instance>")
-
 #
 # OCI Reader
 #
-set(srcs
-    io/OciCommon.cpp
-    io/OciReader.cpp
-    io/OciWrapper.cpp
-)
-
-set(incs
-    io/OciCommon.hpp
-    io/OciReader.hpp
-    io/OciWrapper.h
-)
-
 PDAL_ADD_PLUGIN(reader_libname reader oci
-    FILES "${srcs}" "${incs}"
-    LINK_WITH ${ORACLE_LIBRARY})
-
+    FILES
+        io/OciCommon.cpp
+        io/OciReader.cpp
+        io/OciWrapper.cpp
+    LINK_WITH
+        ${ORACLE_LIBRARY})
+target_include_directories(${reader_libname} PRIVATE
+    ${ORACLE_INCLUDE_DIR}
+    ${PDAL_VENDOR_DIR}/pdalboost
+    ${LIBXML2_INCLUDE_DIR})
 #
 # OCI Writer
 #
-set(srcs
-    io/OciCommon.cpp
-    io/OciWrapper.cpp
-    io/OciWriter.cpp
-)
-
-set(incs
-    io/OciCommon.hpp
-    io/OciWrapper.h
-    io/OciWriter.hpp
-)
-
 PDAL_ADD_PLUGIN(writer_libname writer oci
-    FILES "${srcs}" "${incs}"
+    FILES
+        io/OciCommon.cpp
+        io/OciWrapper.cpp
+        io/OciWriter.cpp
     LINK_WITH ${ORACLE_LIBRARY})
-
+target_include_directories(${writer_libname} PRIVATE
+    ${ORACLE_INCLUDE_DIR}
+    ${PDAL_VENDOR_DIR}/pdalboost
+    ${LIBXML2_INCLUDE_DIR})
 #
 # OCI tests
 #
 if(BUILD_OCI_TESTS)
     PDAL_ADD_TEST(ocitest
-        FILES test/OCITest.cpp
+        FILES
+            test/OCITest.cpp
         LINK_WITH ${reader_libname} ${writer_libname})
+    target_include_directories(ocitest PRIVATE
+        ${ORACLE_INCLUDE_DIR}
+        ${LIBXML2_INCLUDE_DIR})
 endif()
diff --git a/plugins/oci/io/OciCommon.hpp b/plugins/oci/io/OciCommon.hpp
index 8ecd7f6..de05ae6 100644
--- a/plugins/oci/io/OciCommon.hpp
+++ b/plugins/oci/io/OciCommon.hpp
@@ -113,7 +113,7 @@ public:
     OCILobLocator *locator;
     Connection m_connection;
     sdo_pc* pc;
-    int32_t m_num_remaining;
+    point_count_t m_num_remaining;
     bool m_fetched;  // Set when fetched but not initialized
 };
 typedef std::shared_ptr<Block> BlockPtr;
diff --git a/plugins/oci/io/OciReader.cpp b/plugins/oci/io/OciReader.cpp
index 733df32..54dc996 100644
--- a/plugins/oci/io/OciReader.cpp
+++ b/plugins/oci/io/OciReader.cpp
@@ -36,6 +36,7 @@
 #include <pdal/GDALUtils.hpp>
 #include <pdal/pdal_macros.hpp>
 #include <pdal/PDALUtils.hpp>
+#include <pdal/util/FileUtils.hpp>
 #include <pdal/util/ProgramArgs.hpp>
 
 #include "OciReader.hpp"
diff --git a/plugins/oci/io/OciWriter.cpp b/plugins/oci/io/OciWriter.cpp
index 20609a2..41925be 100644
--- a/plugins/oci/io/OciWriter.cpp
+++ b/plugins/oci/io/OciWriter.cpp
@@ -376,9 +376,10 @@ bool OciWriter::isGeographic(int32_t srid)
     catch (pdal_error const& e)
     {
         std::ostringstream oss;
-        oss << "Failed to fetch geographicness of srid " << srid << std::endl;
+        oss << getName();
+        oss << ": Failed to fetch geographicness of srid " << srid << std::endl;
         oss << e.what() << std::endl;
-        throw std::runtime_error(oss.str());
+        throw pdal_error(oss.str());
     }
 
     std::string k = Utils::toupper(kind.get());
@@ -1021,10 +1022,11 @@ void OciWriter::writeTile(const PointViewPtr view)
     catch (std::runtime_error const& e)
     {
         std::ostringstream oss;
-        oss << "Failed to insert block # into '" << m_blockTableName <<
+        oss << getName();
+        oss << ": Failed to insert block # into '" << m_blockTableName <<
             "' table. Does the table exist? "  << std::endl;
         oss << e.what() << std::endl;
-        throw std::runtime_error(oss.str());
+        throw pdal_error(oss.str());
     }
 
     if (m_streamChunks)
@@ -1127,10 +1129,11 @@ void OciWriter::updatePCExtent()
     catch (std::runtime_error const& e)
     {
         std::ostringstream oss;
-        oss << "Failed to update cloud extent in '" << m_baseTableName <<
+        oss << getName();
+        oss << ": Failed to update cloud extent in '" << m_baseTableName <<
             "' table with id " << m_pc_id << ". Does the table exist? " <<
             std::endl << e.what() << std::endl;
-        throw std::runtime_error(oss.str());
+        throw pdal_error(oss.str());
     }
     m_connection->Commit();
 }
diff --git a/plugins/oci/test/OCITest.cpp b/plugins/oci/test/OCITest.cpp
index b2848a9..ac50e9c 100644
--- a/plugins/oci/test/OCITest.cpp
+++ b/plugins/oci/test/OCITest.cpp
@@ -34,11 +34,9 @@
 
 #include "gtest/gtest.h"
 
-#include <boost/property_tree/ptree.hpp>
-
 #include <pdal/Filter.hpp>
 #include <pdal/StageFactory.hpp>
-#include <las/LasReader.hpp>
+#include <io/LasReader.hpp>
 
 #include "../io/OciCommon.hpp"
 #include "Support.hpp"
diff --git a/plugins/p2g/CMakeLists.txt b/plugins/p2g/CMakeLists.txt
index 756db05..34728fb 100644
--- a/plugins/p2g/CMakeLists.txt
+++ b/plugins/p2g/CMakeLists.txt
@@ -1,16 +1,14 @@
 #
 # Points2grid plugin CMake configuration
 #
-
 find_package(P2G)
 if (P2G_FOUND)
-    include_directories(${P2G_INCLUDE_DIR})
-    add_definitions(-DHAVE_P2G=1)
-
-    set(srcs io/P2gWriter.cpp)
-    set(incs io/P2gWriter.hpp)
-
     PDAL_ADD_PLUGIN(libname writer p2g
-        FILES "${srcs}" "${incs}"
-        LINK_WITH ${P2G_LIBRARY})
+        FILES
+            io/P2gWriter.cpp
+        LINK_WITH
+            ${P2G_LIBRARY}
+    )
+    target_compile_definitions(${libname} PRIVATE -DHAVE_P2G=1)
+    target_include_directories(${libname} PRIVATE ${P2G_INCLUDE_DIR})
 endif()
diff --git a/plugins/p2g/io/P2gWriter.cpp b/plugins/p2g/io/P2gWriter.cpp
index 5cde960..a03b29c 100644
--- a/plugins/p2g/io/P2gWriter.cpp
+++ b/plugins/p2g/io/P2gWriter.cpp
@@ -35,6 +35,7 @@
 #include "P2gWriter.hpp"
 #include <pdal/PointView.hpp>
 #include <pdal/pdal_macros.hpp>
+#include <pdal/util/FileUtils.hpp>
 
 #include <iostream>
 #include <algorithm>
@@ -60,7 +61,7 @@ void P2gWriter::addArgs(ProgramArgs& args)
     args.add("grid_dist_y", "Y grid distance", m_GRID_DIST_Y, 6.0);
     args.add("radius", "Radius", m_RADIUS, 8.4852813742385713);
     args.add("fill_window_size", "Fill window size", m_fill_window_size, 3U);
-    args.add("output_type", "Output type", m_outputTypeSpec);
+    args.add("output_type", "Output type", m_outputTypeSpec, {"all"});
     args.add("output_format", "Output format", m_outputFormatSpec, "grid");
     args.add("bounds", "Output raster bounds", m_bounds);
 
@@ -90,12 +91,10 @@ void P2gWriter::initialize()
         {
             std::ostringstream oss;
 
-            oss << "Unrecognized output type '" << type << "'."; 
-            throw p2g_error(oss.str());
+            oss << getName() << ": Unrecognized output type '" << type << "'."; 
+            throw pdal_error(oss.str());
         }
     }
-    if (m_outputTypes == 0)
-        m_outputTypes = OUTPUT_TYPE_ALL;
 
     std::string fmt = Utils::tolower(m_outputFormatSpec);
     if (fmt == "grid")
@@ -110,8 +109,9 @@ void P2gWriter::initialize()
     {
         std::ostringstream oss;
 
-        oss << "Unrecognized output format '" << m_outputFormatSpec << "'";
-        throw p2g_error(oss.str());
+        oss << getName();
+        oss << ": Unrecognized output format '" << m_outputFormatSpec << "'";
+        throw pdal_error(oss.str());
     }
 }
 
@@ -129,64 +129,45 @@ void P2gWriter::ready(PointTableRef table)
 }
 
 
+// The P2G writer will only work with a single point view at the current time.
+// Merge point views before writing.
 void P2gWriter::write(const PointViewPtr view)
 {
-    for (point_count_t idx = 0; idx < view->size(); idx++)
-    {
-        double x = view->getFieldAs<double>(Dimension::Id::X, idx);
-        double y = view->getFieldAs<double>(Dimension::Id::Y, idx);
-        double z = view->getFieldAs<double>(Dimension::Id::Z, idx);
-        m_coordinates.push_back(Coordinate{x, y, z});
-    }
-
-    if (m_bounds.empty()) {
-        view->calculateBounds(m_bounds);
-    }
-}
-
-void P2gWriter::done(PointTableRef table)
-{
-    // If we never got any points, we're done.
-    if (! m_coordinates.size()) return;
-
-    m_GRID_SIZE_X = (int)(ceil((m_bounds.maxx - m_bounds.minx)/m_GRID_DIST_X)) + 1;
-    m_GRID_SIZE_Y = (int)(ceil((m_bounds.maxy - m_bounds.miny)/m_GRID_DIST_Y)) + 1;
-
-    log()->get(LogLevel::Debug) << "X grid size: " << m_GRID_SIZE_X << std::endl;
-    log()->get(LogLevel::Debug) << "Y grid size: " << m_GRID_SIZE_Y << std::endl;
-
-
+    view->calculateBounds(m_bounds);
+    m_GRID_SIZE_X = (int)(ceil((m_bounds.maxx - m_bounds.minx) /
+        m_GRID_DIST_X)) + 1;
+    m_GRID_SIZE_Y = (int)(ceil((m_bounds.maxy - m_bounds.miny) /
+        m_GRID_DIST_Y)) + 1;
     log()->floatPrecision(6);
     log()->get(LogLevel::Debug) << "X grid distance: " << m_GRID_DIST_X << std::endl;
     log()->get(LogLevel::Debug) << "Y grid distance: " << m_GRID_DIST_Y << std::endl;
     log()->clearFloat();
 
-    std::unique_ptr<OutCoreInterp> p(new OutCoreInterp(m_GRID_DIST_X,
-                                       m_GRID_DIST_Y,
-                                       m_GRID_SIZE_X,
-                                       m_GRID_SIZE_Y,
-                                       m_RADIUS * m_RADIUS,
-                                       m_bounds.minx,
-                                       m_bounds.maxx,
-                                       m_bounds.miny,
-                                       m_bounds.maxy,
-                                       m_fill_window_size));
-    m_interpolator.swap(p);
-
-    if (m_interpolator->init() < 0)
-    {
-        throw p2g_error("unable to initialize interpolator");
-    }
+    m_interpolator.reset(new InCoreInterp(m_GRID_DIST_X, m_GRID_DIST_Y,
+        m_GRID_SIZE_X, m_GRID_SIZE_Y, m_RADIUS * m_RADIUS,
+        m_bounds.minx, m_bounds.maxx, m_bounds.miny, m_bounds.maxy,
+        m_fill_window_size));
+    m_interpolator->init();
 
-    for (auto coord : m_coordinates)
+    for (point_count_t idx = 0; idx < view->size(); idx++)
     {
-        double x = coord.x - m_bounds.minx;
-        double y = coord.y - m_bounds.miny;
-        double z = coord.z;
-
+        double x = view->getFieldAs<double>(Dimension::Id::X, idx) -
+            m_bounds.minx;
+        double y = view->getFieldAs<double>(Dimension::Id::Y, idx) -
+            m_bounds.miny;
+        double z = view->getFieldAs<double>(Dimension::Id::Z, idx);
         if (m_interpolator->update(x, y, z) < 0)
-            throw p2g_error("interp->update() error while processing ");
+        {
+            std::ostringstream oss;
+
+            oss << getName() << ": interp->update() error while processing";
+            throw pdal_error(oss.str());
+        }
     }
+}
+
+void P2gWriter::done(PointTableRef table)
+{
 
     double adfGeoTransform[6];
     adfGeoTransform[0] = m_bounds.minx - 0.5*m_GRID_DIST_X;
@@ -208,12 +189,15 @@ void P2gWriter::done(PointTableRef table)
     if (extension == ".asc" || extension == ".grid" || extension == ".tif")
         m_filename = m_filename.substr(0, m_filename.find_last_of("."));
 
-    if (m_interpolator->finish(m_filename.c_str(),
-        m_outputFormat, m_outputTypes, adfGeoTransform,
-        srs.getWKT().c_str()) < 0)
+    if (m_interpolator->finish(m_filename.c_str(), m_outputFormat,
+        m_outputTypes, adfGeoTransform, srs.getWKT().c_str()) < 0)
     {
-        throw p2g_error("interp->finish() error");
+        ostringstream oss;
+
+        oss << getName() << ": interp->finish() error";
+        throw pdal_error(oss.str());
     }
+    getMetadata().addList("filename", m_filename);
 }
 
 } // namespaces
diff --git a/plugins/p2g/io/P2gWriter.hpp b/plugins/p2g/io/P2gWriter.hpp
index 0c02dde..9021640 100644
--- a/plugins/p2g/io/P2gWriter.hpp
+++ b/plugins/p2g/io/P2gWriter.hpp
@@ -44,22 +44,11 @@
 #include <points2grid/config.h>
 #include <points2grid/Interpolation.hpp>
 #include <points2grid/Global.hpp>
-#include <points2grid/OutCoreInterp.hpp>
+#include <points2grid/InCoreInterp.hpp>
 
 namespace pdal
 {
 
-
-class p2g_error : public pdal_error
-{
-public:
-    p2g_error(std::string const& msg)
-        : pdal_error(msg)
-    {}
-};
-
-
-
 class CoreInterp;
 
 class PDAL_DLL P2gWriter : public Writer
@@ -81,7 +70,7 @@ private:
     virtual void write(const PointViewPtr view);
     virtual void done(PointTableRef table);
 
-    std::unique_ptr<OutCoreInterp> m_interpolator;
+    std::unique_ptr<InCoreInterp> m_interpolator;
 
     uint32_t m_GRID_SIZE_X;
     uint32_t m_GRID_SIZE_Y;
@@ -98,15 +87,6 @@ private:
 
     std::string m_filename;
     int m_outputFormat;
-
-    typedef struct
-    {
-        double x;
-        double y;
-        double z;
-    } Coordinate;
-
-    std::vector<Coordinate> m_coordinates;
 };
 
 } // namespaces
diff --git a/plugins/pcl/CMakeLists.txt b/plugins/pcl/CMakeLists.txt
index ba01626..38a9d4b 100644
--- a/plugins/pcl/CMakeLists.txt
+++ b/plugins/pcl/CMakeLists.txt
@@ -34,12 +34,10 @@ set_package_properties(PCL PROPERTIES DESCRIPTION "Point Cloud Library"
     URL "http://pointclouds.org" TYPE RECOMMENDED
     PURPOSE "Enables PCD reader/writer, PCLVisualizer, PCLBlock filter, and ground, pcl, smooth, and view kernels")
 
-include_directories(${PCL_INCLUDE_DIRS})
-include_directories(${CMAKE_CURRENT_LIST_DIR})
-include_directories(${CMAKE_CURRENT_SOURCE_DIR}/filters)
-include_directories(${CMAKE_CURRENT_SOURCE_DIR}/pipeline)
-include_directories(${CMAKE_CURRENT_SOURCE_DIR}/dartsample)
-link_directories(${PCL_LIBRARY_DIRS})
+set(INCLUDE_DIRS
+    ${ROOT_DIR}
+    ${PCL_INCLUDE_DIRS})
+
 add_definitions(${PCL_DEFINITIONS})
 if (NOT WIN32)
     add_definitions("-fvisibility-inlines-hidden")
@@ -48,177 +46,108 @@ endif()
 # PCL's configuration clobbers Boost find_package - do it again
 find_package(Boost QUIET 1.52 COMPONENTS program_options iostreams filesystem system thread)
 
-if (PCL_VISUALIZATION_FOUND)
-#
-# PCLVisualizer Writer
-#
-    set(srcs io/PCLVisualizer.cpp)
-    set(incs
-        io/PCLVisualizer.hpp
-        PCLConversions.hpp
-    )
-
-    PDAL_ADD_PLUGIN(pclvisualizer_libname writer pclvisualizer
-        FILES "${srcs}" "${incs}"
-        LINK_WITH ${PCL_LIBRARIES})
-
-    #
-    # View Kernel
-    #
-    set(srcs kernel/ViewKernel.cpp)
-    set(incs kernel/ViewKernel.hpp)
-
-    PDAL_ADD_PLUGIN(view_libname kernel view
-        FILES "${srcs}" "${incs}"
-        LINK_WITH ${PCL_LIBRARIES})
-else()
-    message(STATUS "PCLVisualizer disabled because PCL_VISUALIZATION was not found")
-endif()
-
 #
 # PCD Reader
 #
-set(srcs
-    io/PcdCommon.cpp
-    io/PcdReader.cpp
-)
-
-set(incs
-    io/PcdCommon.hpp
-    io/PcdReader.hpp
-    io/point_types.hpp
-    PCLConversions.hpp
-)
-
 PDAL_ADD_PLUGIN(pcd_reader_libname reader pcd
-    FILES "${srcs}" "${incs}"
+    FILES
+        io/PcdCommon.cpp
+        io/PcdReader.cpp
     LINK_WITH ${PCL_LIBRARIES})
+target_include_directories(${pcd_reader_libname} PRIVATE
+    ${INCLUDE_DIRS})
 
 #
 # PCD Writer
 #
-set(srcs
-    io/PcdCommon.cpp
-    io/PcdWriter.cpp
-)
-
-set(incs
-    io/PcdCommon.hpp
-    io/PcdWriter.hpp
-    io/point_types.hpp
-    PCLConversions.hpp
-)
-
 PDAL_ADD_PLUGIN(pcd_writer_libname writer pcd
-    FILES "${srcs}" "${incs}"
+    FILES
+        io/PcdCommon.cpp
+        io/PcdWriter.cpp
     LINK_WITH ${PCL_LIBRARIES})
+target_include_directories(${pcd_writer_libname} PRIVATE
+    ${INCLUDE_DIRS})
 
 #
 # PCLBlock Filter
 #
-set(srcs
-    pipeline/PCLPipeline.cpp
-    filters/PCLBlock.cpp
+PDAL_ADD_PLUGIN(pclblock_libname filter pclblock
+    FILES
+        pipeline/PCLPipeline.cpp
+        filters/PCLBlock.cpp
+    LINK_WITH
+        ${PCL_LIBRARIES}
+        ${PDAL_JSONCPP_LIB_NAME}
 )
-
-set(incs
-   pipeline/PCLPipeline.h
-   pipeline/PCLPipeline.hpp
-   filters/PCLBlock.hpp
-   PCLConversions.hpp
+target_include_directories(${pclblock_libname}
+    PRIVATE
+      ${INCLUDE_DIRS}
+      ${PDAL_JSONCPP_INCLUDE_DIR}
 )
 
-PDAL_ADD_PLUGIN(pclblock_libname filter pclblock
-    FILES "${srcs}" "${incs}"
-    LINK_WITH ${PCL_LIBRARIES})
-
-#
-# Ground Filter
-#
-PDAL_ADD_PLUGIN(ground_filter_libname filter ground
-    FILES filters/GroundFilter.hpp filters/GroundFilter.cpp
-    LINK_WITH ${PCL_LIBRARIES})
-
-PDAL_ADD_PLUGIN(statistical_outlier_filter_libname filter statisticaloutlier
-    FILES filters/StatisticalOutlierFilter.hpp filters/StatisticalOutlierFilter.cpp
-    LINK_WITH ${PCL_LIBRARIES})
-
-PDAL_ADD_PLUGIN(radius_outlier_filter_libname filter radiusoutlier
-    FILES filters/RadiusOutlierFilter.hpp filters/RadiusOutlierFilter.cpp
-    LINK_WITH ${PCL_LIBRARIES})
-
 PDAL_ADD_PLUGIN(voxelgrid_filter_libname filter voxelgrid
-    FILES filters/VoxelGridFilter.hpp filters/VoxelGridFilter.cpp
+    FILES
+        filters/VoxelGridFilter.cpp
     LINK_WITH ${PCL_LIBRARIES})
+target_include_directories(${voxelgrid_filter_libname} PRIVATE
+    ${INCLUDE_DIRS})
 
 PDAL_ADD_PLUGIN(movingleastsquares_filter_libname filter movingleastsquares
-    FILES filters/MovingLeastSquaresFilter.hpp filters/MovingLeastSquaresFilter.cpp
+    FILES
+        filters/MovingLeastSquaresFilter.cpp
     LINK_WITH ${PCL_LIBRARIES})
+target_include_directories(${movingleastsquares_filter_libname} PRIVATE
+    ${INCLUDE_DIRS})
 
 PDAL_ADD_PLUGIN(gridprojection_filter_libname filter gridprojection
-    FILES filters/GridProjectionFilter.hpp filters/GridProjectionFilter.cpp
+    FILES
+        filters/GridProjectionFilter.cpp
     LINK_WITH ${PCL_LIBRARIES})
+target_include_directories(${gridprojection_filter_libname} PRIVATE
+    ${INCLUDE_DIRS})
 
 PDAL_ADD_PLUGIN(poisson_filter_libname filter poisson
-    FILES filters/PoissonFilter.hpp filters/PoissonFilter.cpp
+    FILES
+        filters/PoissonFilter.cpp
     LINK_WITH ${PCL_LIBRARIES})
+target_include_directories(${poisson_filter_libname} PRIVATE
+    ${INCLUDE_DIRS})
 
 PDAL_ADD_PLUGIN(greedyprojection_filter_libname filter greedyprojection
-    FILES filters/GreedyProjectionFilter.hpp filters/GreedyProjectionFilter.cpp
-    LINK_WITH ${PCL_LIBRARIES})
-
-PDAL_ADD_PLUGIN(height_filter_libname filter height
-    FILES filters/HeightFilter.cpp
-    LINK_WITH ${PCL_LIBRARIES})
-
-PDAL_ADD_PLUGIN(dartsample_filter_libname filter dartsample
     FILES
-        filters/DartSampleFilter.cpp
-        dartsample/dart_sample.cpp
+        filters/GreedyProjectionFilter.cpp
     LINK_WITH ${PCL_LIBRARIES})
+target_include_directories(${greedyprojection_filter_libname} PRIVATE
+    ${INCLUDE_DIRS})
 
 #
 # PCL Kernel
 #
-set(srcs kernel/PCLKernel.cpp)
-set(incs kernel/PCLKernel.hpp)
-
 PDAL_ADD_PLUGIN(pcl_libname kernel pcl
-    FILES "${srcs}" "${incs}"
+    FILES
+        kernel/PCLKernel.cpp
     LINK_WITH ${PCL_LIBRARIES} ${pclblock_libname})
-
-#
-# Height Above Ground Kernel
-#
-#PDAL_ADD_PLUGIN(hag_libname kernel height
-#    FILES kernel/HeightAboveGroundKernel.cpp
-#    LINK_WITH ${PCL_LIBRARIES})
-
-#
-# Ground Kernel
-#
-set(srcs kernel/GroundKernel.cpp)
-set(incs kernel/GroundKernel.hpp)
-
-PDAL_ADD_PLUGIN(ground_kernel_libname kernel ground
-    FILES "${srcs}" "${incs}"
-    LINK_WITH ${ground_filter_libname})
+target_include_directories(${pcl_libname} PRIVATE
+    ${PDAL_IO_DIR}
+    ${INCLUDE_DIRS})
 
 #
 # Smooth Kernel
 #
-set(srcs kernel/SmoothKernel.cpp)
-set(incs kernel/SmoothKernel.hpp)
 
 PDAL_ADD_PLUGIN(smooth_libname kernel smooth
-    FILES "${srcs}" "${incs}"
+    FILES
+        kernel/SmoothKernel.cpp
     LINK_WITH ${PCL_LIBRARIES} ${pclblock_libname})
+target_include_directories(${smooth_libname} PRIVATE
+    ${INCLUDE_DIRS}
+    ${PDAL_IO_DIR})
 
 if (WITH_TESTS)
     PDAL_ADD_TEST(pcltest
-    FILES test/PCLBlockFilterTest.cpp
-    LINK_WITH ${pclvisualizer_libname} ${pcd_reader_libname}
-        ${pcd_writer_libname} ${pclblock_libname} ${ground_filter_libname}
-        ${pcl_libname} ${ground_kernel_libname} ${smooth_libname}
+    FILES
+        test/PCLBlockFilterTest.cpp
+    LINK_WITH ${pcd_reader_libname} ${pcd_writer_libname} ${pclblock_libname}
+        ${pcl_libname} ${smooth_libname}
     )
 endif()
diff --git a/plugins/pcl/dartsample/dart_sample.cpp b/plugins/pcl/dartsample/dart_sample.cpp
deleted file mode 100644
index c8bde0f..0000000
--- a/plugins/pcl/dartsample/dart_sample.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "dart_sample.hpp"
-
-#ifndef PCL_NO_PRECOMPILE
-#include <pcl/impl/instantiate.hpp>
-#include <pcl/point_types.h>
-
-// Instantiations of specific point types
-#ifdef PCL_ONLY_CORE_POINT_TYPES
-PCL_INSTANTIATE(DartSample,
-                (pcl::PointXYZ)(pcl::PointXYZI)(pcl::PointXYZRGBA)(pcl::PointXYZRGB))
-#else
-PCL_INSTANTIATE(DartSample, PCL_XYZ_POINT_TYPES)
-#endif
-
-#endif    // PCL_NO_PRECOMPILE
diff --git a/plugins/pcl/dartsample/dart_sample.h b/plugins/pcl/dartsample/dart_sample.h
deleted file mode 100644
index 709f4b1..0000000
--- a/plugins/pcl/dartsample/dart_sample.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pcl/filters/filter_indices.h>
-
-namespace pcl
-{
-  /** \brief @b DartSample performs dart throwing for a given radius.
-    * \author Bradley J Chambers
-    * \ingroup filters
-    */
-  template<typename PointT>
-  class DartSample : public FilterIndices<PointT>
-  {
-    using FilterIndices<PointT>::filter_name_;
-    using FilterIndices<PointT>::getClassName;
-    using FilterIndices<PointT>::indices_;
-    using FilterIndices<PointT>::input_;
-    using FilterIndices<PointT>::negative_;
-    using FilterIndices<PointT>::user_filter_value_;
-    using FilterIndices<PointT>::extract_removed_indices_;
-    using FilterIndices<PointT>::removed_indices_;
-
-    typedef typename FilterIndices<PointT>::PointCloud PointCloud;
-    typedef typename PointCloud::Ptr PointCloudPtr;
-    typedef typename PointCloud::ConstPtr PointCloudConstPtr;
-
-    public:
-
-      typedef boost::shared_ptr< DartSample<PointT> > Ptr;
-      typedef boost::shared_ptr< const DartSample<PointT> > ConstPtr;
-
-      /** \brief Empty constructor. */
-      DartSample (bool extract_removed_indices = false) :
-        FilterIndices<PointT> (extract_removed_indices),
-        radius_ (1.0)
-      {
-        filter_name_ = "DartSample";
-      }
-
-      /** \brief Set minimum distance radius for adding points..
-        * \param radius
-        */
-      inline void
-      setRadius (double radius)
-      {
-        radius_ = radius;
-      }
-
-      /** \brief Get the value of the internal \a radius parameter.
-        */
-      inline double
-      getRadius ()
-      {
-        return (radius_);
-      }
-
-    protected:
-
-      /** \brief Minimum distance radius for adding points. */
-      double radius_;
-
-      /** \brief Sample of point indices into a separate PointCloud
-        * \param output the resultant point cloud
-        */
-      void
-      applyFilter (PointCloud &output);
-
-      /** \brief Sample of point indices
-        * \param indices the resultant point cloud indices
-        */
-      void
-      applyFilter (std::vector<int> &indices);
-  };
-}
-
-#ifdef PCL_NO_PRECOMPILE
-#include "dart_sample.hpp"
-#endif
diff --git a/plugins/pcl/dartsample/dart_sample.hpp b/plugins/pcl/dartsample/dart_sample.hpp
deleted file mode 100644
index a022b3e..0000000
--- a/plugins/pcl/dartsample/dart_sample.hpp
+++ /dev/null
@@ -1,116 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include "dart_sample.h"
-
-#include <algorithm>
-#include <cmath>
-#include <cstdlib>
-#include <ctime>
-#include <vector>
-
-#include <pcl/common/io.h>
-#include <pcl/console/print.h>
-#include <pcl/io/pcd_io.h>
-#include <pcl/octree/octree.h>
-#include <pcl/point_traits.h>
-#include <pcl/point_types.h>
-
-///////////////////////////////////////////////////////////////////////////////
-template<typename PointT> void
-pcl::DartSample<PointT>::applyFilter (PointCloud &output)
-{
-  std::vector<int> indices;
-  output.is_dense = true;
-  applyFilter (indices);
-  copyPointCloud (*input_, indices, output);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-template<typename PointT>
-void
-pcl::DartSample<PointT>::applyFilter (std::vector<int> &indices)
-{
-  unsigned N = static_cast<unsigned> (input_->size ());
-
-  std::srand ( std::time (NULL));
-  std::vector<int> shuffled_indices = (*indices_);
-  std::random_shuffle (shuffled_indices.begin (), shuffled_indices.end ());
-
-  // Reserve N indices/removed_indices_
-  indices.resize (static_cast<size_t> (N));
-  if (extract_removed_indices_)
-    removed_indices_->resize (static_cast<size_t> (N));
-
-  unsigned i = 0;
-  unsigned ri = 0;
-
-  // Create octree, seeded with the first index
-  pcl::octree::OctreePointCloudSearch<PointT> tree (radius_ / std::sqrt (3));
-  typename pcl::PointCloud<PointT>::Ptr cloud_t (new pcl::PointCloud<PointT>);
-  tree.setInputCloud (cloud_t);
-  tree.addPointToCloud (input_->points[shuffled_indices[0]], cloud_t);
-
-  // Keep the first index
-  indices[i++] = shuffled_indices[0];
-
-  // Iterate over remaining points, keeping only those meeting the minimum
-  // distance criteria.
-  for (auto const& j : shuffled_indices)
-  {
-    std::vector<int> neighbors;
-    std::vector<float> sqr_distances;
-    PointT temp_pt = input_->points[j];
-
-    int num = tree.radiusSearch (temp_pt, radius_, neighbors, sqr_distances, 1);
-
-    if (num == 0)
-    {
-      indices[i++] = j;
-      tree.addPointToCloud (temp_pt, cloud_t);
-    }
-    else if (extract_removed_indices_)
-    {
-      (*removed_indices_)[ri++] = j;
-    }
-  }
-
-  indices.resize (static_cast<size_t> (i));
-  removed_indices_->resize (static_cast<size_t> (ri));
-}
-
-#define PCL_INSTANTIATE_DartSample(T) \
-    template class PCL_EXPORTS pcl::DartSample<T>;
diff --git a/plugins/pcl/filters/DartSampleFilter.cpp b/plugins/pcl/filters/DartSampleFilter.cpp
deleted file mode 100644
index 4ee3884..0000000
--- a/plugins/pcl/filters/DartSampleFilter.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "DartSampleFilter.hpp"
-
-#include "dart_sample.h"
-#include "PCLConversions.hpp"
-#include "PCLPipeline.h"
-
-#include <pcl/console/print.h>
-#include <pcl/point_types.h>
-#include <pcl/io/pcd_io.h>
-
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.dartsample", "Dart sample filter",
-               "http://pdal.io/stages/filters.dartsample.html");
-
-CREATE_SHARED_PLUGIN(1, 0, DartSampleFilter, Filter, s_info)
-
-std::string DartSampleFilter::getName() const
-{
-    return s_info.name;
-}
-
-void DartSampleFilter::addArgs(ProgramArgs& args)
-{
-    args.add("radius", "Minimum distance criterion", m_radius, 1.0);
-}
-
-PointViewSet DartSampleFilter::run(PointViewPtr input)
-{
-    PointViewSet viewSet;
-    PointViewPtr output = input->makeNew();
-
-    log()->floatPrecision(2);
-    log()->get(LogLevel::Info) << "DartSampleFilter (radius="
-                               << m_radius << ")\n";
-
-    BOX3D buffer_bounds;
-    input->calculateBounds(buffer_bounds);
-
-    typedef pcl::PointCloud<pcl::PointXYZ> Cloud;
-    Cloud::Ptr cloud(new Cloud);
-    pclsupport::PDALtoPCD(input, *cloud, buffer_bounds);
-
-    pclsupport::setLogLevel(log()->getLevel());
-
-    pcl::DartSample<pcl::PointXYZ> ds;
-    ds.setInputCloud(cloud);
-    ds.setRadius(m_radius);
-
-    std::vector<int> samples;
-    ds.filter(samples);
-
-    if (samples.empty())
-    {
-        log()->get(LogLevel::Warning) << "Filtered cloud has no points!\n";
-        return viewSet;
-    }
-
-    for (const auto& i : samples)
-        output->appendPoint(*input, i);
-
-    double frac = (double)samples.size() / (double)cloud->size();
-    log()->get(LogLevel::Info) << "Retaining " << samples.size() << " of "
-                               << cloud->size() << " points ("
-                               <<  100*frac
-                               << "%)\n";
-
-    viewSet.insert(output);
-    return viewSet;
-}
-
-} // namespace pdal
diff --git a/plugins/pcl/filters/DartSampleFilter.hpp b/plugins/pcl/filters/DartSampleFilter.hpp
deleted file mode 100644
index bb17bec..0000000
--- a/plugins/pcl/filters/DartSampleFilter.hpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2013, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/StageFactory.hpp>
-
-namespace pdal
-{
-
-class PDAL_DLL DartSampleFilter : public Filter
-{
-public:
-    DartSampleFilter() : Filter()
-    {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    double m_radius;
-
-    virtual void addArgs(ProgramArgs& args);
-    virtual PointViewSet run(PointViewPtr view);
-
-    DartSampleFilter& operator=(const DartSampleFilter&); // not implemented
-    DartSampleFilter(const DartSampleFilter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/plugins/pcl/filters/GreedyProjectionFilter.cpp b/plugins/pcl/filters/GreedyProjectionFilter.cpp
index 11ced9d..18c4947 100644
--- a/plugins/pcl/filters/GreedyProjectionFilter.cpp
+++ b/plugins/pcl/filters/GreedyProjectionFilter.cpp
@@ -34,9 +34,6 @@
 
 #include "GreedyProjectionFilter.hpp"
 
-#include "PCLConversions.hpp"
-#include "PCLPipeline.h"
-
 #include <pcl/console/print.h>
 #include <pcl/point_types.h>
 #include <pcl/features/normal_3d.h>
@@ -45,6 +42,8 @@
 
 #include <pdal/pdal_macros.hpp>
 
+#include "../PCLConversions.hpp"
+
 namespace pdal
 {
 
diff --git a/plugins/pcl/filters/GridProjectionFilter.cpp b/plugins/pcl/filters/GridProjectionFilter.cpp
index f21e778..22ac829 100644
--- a/plugins/pcl/filters/GridProjectionFilter.cpp
+++ b/plugins/pcl/filters/GridProjectionFilter.cpp
@@ -34,9 +34,6 @@
 
 #include "GridProjectionFilter.hpp"
 
-#include "PCLConversions.hpp"
-#include "PCLPipeline.h"
-
 #include <pcl/console/print.h>
 #include <pcl/point_types.h>
 #include <pcl/features/normal_3d.h>
@@ -45,6 +42,8 @@
 
 #include <pdal/pdal_macros.hpp>
 
+#include "../PCLConversions.hpp"
+
 namespace pdal
 {
 
diff --git a/plugins/pcl/filters/GroundFilter.cpp b/plugins/pcl/filters/GroundFilter.cpp
deleted file mode 100644
index f2c2630..0000000
--- a/plugins/pcl/filters/GroundFilter.cpp
+++ /dev/null
@@ -1,178 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "GroundFilter.hpp"
-
-#include "PCLConversions.hpp"
-
-#include <pdal/Options.hpp>
-#include <pdal/PointTable.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-
-#include <pcl/point_types.h>
-#include <pcl/console/print.h>
-#include <pcl/filters/extract_indices.h>
-#include <pcl/io/pcd_io.h>
-#include <pcl/segmentation/progressive_morphological_filter.h>
-#include <pcl/segmentation/approximate_progressive_morphological_filter.h>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.ground", "Progressive morphological filter",
-               "http://pdal.io/stages/filters.ground.html");
-
-CREATE_SHARED_PLUGIN(1, 0, GroundFilter, Filter, s_info)
-
-std::string GroundFilter::getName() const
-{
-    return s_info.name;
-}
-
-void GroundFilter::addArgs(ProgramArgs& args)
-{
-    args.add("max_window_size", "Maximum window size", m_maxWindowSize, 33.0);
-    args.add("slope", "Slope", m_slope, 1.0);
-    args.add("max_distance", "Maximum distance", m_maxDistance, 2.5);
-    args.add("initial_distance", "Initial distance", m_initialDistance, 0.15);
-    args.add("cell_size", "Cell size", m_cellSize, 1.0);
-    args.add("classify", "Apply the classification labels?", m_classify, true);
-    args.add("extract", "Extract ground returns?", m_extract);
-    args.add("approximate", "Use approximate algorithm?", m_approximate);
-}
-
-void GroundFilter::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(Dimension::Id::Classification);
-}
-
-PointViewSet GroundFilter::run(PointViewPtr input)
-{
-    bool logOutput = log()->getLevel() > LogLevel::Debug1;
-    if (logOutput)
-        log()->floatPrecision(8);
-    log()->get(LogLevel::Debug2) << "Process GroundFilter...\n";
-
-    // convert PointView to PointXYZ
-    typedef pcl::PointCloud<pcl::PointXYZ> Cloud;
-    Cloud::Ptr cloud(new Cloud);
-    BOX3D bounds;
-    input->calculateBounds(bounds);
-    pclsupport::PDALtoPCD(input, *cloud, bounds);
-
-    pclsupport::setLogLevel(log()->getLevel());
-
-    // setup the PMF filter
-    pcl::PointIndicesPtr idx(new pcl::PointIndices);
-    if (!m_approximate)
-    {
-
-        pcl::ProgressiveMorphologicalFilter<pcl::PointXYZ> pmf;
-        pmf.setInputCloud(cloud);
-        pmf.setMaxWindowSize(m_maxWindowSize);
-        pmf.setSlope(m_slope);
-        pmf.setMaxDistance(m_maxDistance);
-        pmf.setInitialDistance(m_initialDistance);
-        pmf.setCellSize(m_cellSize);
-
-        // run the PMF filter, grabbing indices of ground returns
-        pmf.extract(idx->indices);
-    }
-    else
-    {
-        pcl::ApproximateProgressiveMorphologicalFilter<pcl::PointXYZ> pmf;
-        pmf.setInputCloud(cloud);
-        pmf.setMaxWindowSize(m_maxWindowSize);
-        pmf.setSlope(m_slope);
-        pmf.setMaxDistance(m_maxDistance);
-        pmf.setInitialDistance(m_initialDistance);
-        pmf.setCellSize(m_cellSize);
-
-        // run the PMF filter, grabbing indices of ground returns
-        pmf.extract(idx->indices);
-
-    }
-
-    PointViewSet viewSet;
-    if (!idx->indices.empty() && (m_classify || m_extract))
-    {
-
-        if (m_classify)
-        {
-            log()->get(LogLevel::Debug2) << "Labeled " << idx->indices.size() << " ground returns!\n";
-
-            // set the classification label of ground returns as 2
-            // (corresponding to ASPRS LAS specification)
-            for (const auto& i : idx->indices)
-            {
-                input->setField(Dimension::Id::Classification, i, 2);
-            }
-
-            viewSet.insert(input);
-        }
-
-        if (m_extract)
-        {
-            log()->get(LogLevel::Debug2) << "Extracted " << idx->indices.size() << " ground returns!\n";
-
-            // create new PointView containing only ground returns
-            PointViewPtr output = input->makeNew();
-            for (const auto& i : idx->indices)
-            {
-                output->appendPoint(*input, i);
-            }
-
-            viewSet.erase(input);
-            viewSet.insert(output);
-        }
-    }
-    else
-    {
-        if (idx->indices.empty())
-            log()->get(LogLevel::Debug2) << "Filtered cloud has no ground returns!\n";
-
-        if (!(m_classify || m_extract))
-            log()->get(LogLevel::Debug2) << "Must choose --classify or --extract\n";
-
-        // return the input buffer unchanged
-        viewSet.insert(input);
-    }
-
-    return viewSet;
-}
-
-} // namespace pdal
diff --git a/plugins/pcl/filters/GroundFilter.hpp b/plugins/pcl/filters/GroundFilter.hpp
deleted file mode 100644
index e2e8b41..0000000
--- a/plugins/pcl/filters/GroundFilter.hpp
+++ /dev/null
@@ -1,78 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/Stage.hpp>
-
-#include <memory>
-
-namespace pdal
-{
-
-class Options;
-class PointLayout;
-class PointTable;
-class PointView;
-
-class PDAL_DLL GroundFilter : public Filter
-{
-public:
-    GroundFilter() : Filter()
-    {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    double m_maxWindowSize;
-    double m_slope;
-    double m_maxDistance;
-    double m_initialDistance;
-    double m_cellSize;
-    bool m_classify;
-    bool m_extract;
-    bool m_approximate;
-
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual void addArgs(ProgramArgs& args);
-    virtual PointViewSet run(PointViewPtr view);
-
-    GroundFilter& operator=(const GroundFilter&); // not implemented
-    GroundFilter(const GroundFilter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/plugins/pcl/filters/HeightFilter.cpp b/plugins/pcl/filters/HeightFilter.cpp
deleted file mode 100644
index 19b0da8..0000000
--- a/plugins/pcl/filters/HeightFilter.cpp
+++ /dev/null
@@ -1,181 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "HeightFilter.hpp"
-
-// c++
-#include <memory>
-#include <string>
-#include <vector>
-
-// project
-#include "PCLConversions.hpp"
-#include <pdal/Dimension.hpp>
-#include <pdal/Options.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/PointTable.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-
-// other
-#include <pcl/console/print.h>
-#include <pcl/filters/extract_indices.h>
-#include <pcl/filters/project_inliers.h>
-#include <pcl/io/pcd_io.h>
-#include <pcl/ModelCoefficients.h>
-#include <pcl/point_types.h>
-#include <pcl/search/kdtree.h>
-
-#define DBG log()->get(LogLevel::Debug)
-
-namespace pdal
-{
-
-typedef pcl::PointCloud<pcl::PointXYZ> Cloud;
-
-static PluginInfo const s_info =
-    PluginInfo("filters.height", "Height Filter", "");
-
-CREATE_SHARED_PLUGIN(1, 0, HeightFilter, Filter, s_info)
-
-std::string HeightFilter::getName() const
-{
-    return s_info.name;
-}
-
-void HeightFilter::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(pdal::Dimension::Id::HeightAboveGround);
-}
-
-void HeightFilter::prepared(PointTableRef table)
-{
-    const PointLayoutPtr layout(table.layout());
-    if (!layout->hasDim(Dimension::Id::Classification))
-        throw pdal_error("HeightFilter: missing Classification dimension in input PointView");
-}
-
-void HeightFilter::filter(PointView& view)
-{
-    bool logOutput = log()->getLevel() > LogLevel::Debug1;
-    if (logOutput)
-        log()->floatPrecision(8);
-
-    DBG << "Computing normalized heights...\n";
-
-    BOX3D bounds;
-    view.calculateBounds(bounds);
-
-    Cloud::Ptr cloud_in(new Cloud);
-    pclsupport::PDALtoPCD(std::make_shared<PointView>(view), *cloud_in, bounds);
-
-    pcl::PointIndices::Ptr ground(new pcl::PointIndices());
-    ground->indices.reserve(view.size());
-
-    std::vector<PointId> nonground;
-    nonground.reserve(view.size());
-
-    for (PointId id = 0; id < view.size(); ++id)
-    {
-        double c = view.getFieldAs<double>(Dimension::Id::Classification, id);
-
-        if (c == 2)
-            ground->indices.push_back(id);
-        else
-            nonground.push_back(id);
-    }
-
-    if (ground->indices.size()==0)
-        throw pdal_error("HeightFilter: the input PointView does not appear to have any points classified as ground");
-
-    pcl::ExtractIndices<pcl::PointXYZ> extract;
-    extract.setInputCloud(cloud_in);
-    extract.setIndices(ground);
-
-    Cloud::Ptr cloud_ground(new Cloud);
-    extract.setNegative(false);
-    extract.filter(*cloud_ground);
-
-    Cloud::Ptr cloud_nonground(new Cloud);
-    extract.setNegative(true);
-    extract.filter(*cloud_nonground);
-
-    // project both ground and non-ground into XY plane
-
-    // Create a set of planar coefficients with X=Y=0,Z=1
-    pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients());
-    coefficients->values.resize(4);
-    coefficients->values[0] = coefficients->values[1] = 0;
-    coefficients->values[2] = 1.0;
-    coefficients->values[3] = 0;
-
-    // Create the filtering object
-    pcl::ProjectInliers<pcl::PointXYZ> proj;
-    proj.setModelType(pcl::SACMODEL_PLANE);
-
-    Cloud::Ptr cloud_ground_projected(new Cloud);
-    proj.setInputCloud(cloud_ground);
-    proj.setModelCoefficients(coefficients);
-    proj.filter(*cloud_ground_projected);
-
-    Cloud::Ptr cloud_nonground_projected(new Cloud);
-    proj.setInputCloud(cloud_nonground);
-    proj.setModelCoefficients(coefficients);
-    proj.filter(*cloud_nonground_projected);
-
-    pcl::search::KdTree<pcl::PointXYZ>::Ptr ground_tree;
-    ground_tree.reset(new pcl::search::KdTree<pcl::PointXYZ> (false));
-    ground_tree->setInputCloud(cloud_ground_projected);
-
-    for (size_t i = 0; i < cloud_nonground_projected->size(); ++i)
-    {
-        pcl::PointXYZ nonground_query = cloud_nonground_projected->points[i];
-        std::vector<int> neighbors(1);
-        std::vector<float> sqr_distances(1);
-        ground_tree->nearestKSearch(nonground_query, 1, neighbors, sqr_distances);
-
-        double nonground_Z = view.getFieldAs<double>(Dimension::Id::Z, nonground[i]);
-        double ground_Z = view.getFieldAs<double>(Dimension::Id::Z, ground->indices[neighbors[0]]);
-        double height = nonground_Z - ground_Z;
-
-        view.setField(pdal::Dimension::Id::HeightAboveGround, nonground[i], height);
-    }
-
-    for (auto const& ground_idx : ground->indices)
-        view.setField(pdal::Dimension::Id::HeightAboveGround, ground_idx, 0.0);
-}
-
-
-} // namespace pdal
diff --git a/plugins/pcl/filters/HeightFilter.hpp b/plugins/pcl/filters/HeightFilter.hpp
deleted file mode 100644
index 174eb29..0000000
--- a/plugins/pcl/filters/HeightFilter.hpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <cstdint>
-#include <memory>
-#include <string>
-
-#include <pdal/pdal_export.hpp>
-#include <pdal/Dimension.hpp>
-#include <pdal/Filter.hpp>
-#include <pdal/Stage.hpp>
-
-namespace pdal
-{
-
-class Options;
-class PointLayout;
-class PointView;
-
-class PDAL_DLL HeightFilter : public Filter
-{
-public:
-    HeightFilter() : Filter()
-    {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual void prepared(PointTableRef table);
-    virtual void filter(PointView& view);
-
-    HeightFilter& operator=(const HeightFilter&); // not implemented
-    HeightFilter(const HeightFilter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/plugins/pcl/filters/MovingLeastSquaresFilter.cpp b/plugins/pcl/filters/MovingLeastSquaresFilter.cpp
index ed4eaa4..5f59c99 100644
--- a/plugins/pcl/filters/MovingLeastSquaresFilter.cpp
+++ b/plugins/pcl/filters/MovingLeastSquaresFilter.cpp
@@ -34,9 +34,6 @@
 
 #include "MovingLeastSquaresFilter.hpp"
 
-#include "PCLConversions.hpp"
-#include "PCLPipeline.h"
-
 #include <pcl/console/print.h>
 #include <pcl/point_types.h>
 #include <pcl/io/pcd_io.h>
@@ -44,6 +41,8 @@
 
 #include <pdal/pdal_macros.hpp>
 
+#include "../PCLConversions.hpp"
+
 namespace pdal
 {
 
diff --git a/plugins/pcl/filters/PCLBlock.cpp b/plugins/pcl/filters/PCLBlock.cpp
index a9fe6eb..a2a067b 100644
--- a/plugins/pcl/filters/PCLBlock.cpp
+++ b/plugins/pcl/filters/PCLBlock.cpp
@@ -1,5 +1,5 @@
 /******************************************************************************
-* Copyright (c) 2013-2014, Bradley J Chambers (brad.chambers at gmail.com)
+* Copyright (c) 2013-2016, Bradley J Chambers (brad.chambers at gmail.com)
 *
 * All rights reserved.
 *
@@ -34,9 +34,6 @@
 
 #include "PCLBlock.hpp"
 
-#include "PCLConversions.hpp"
-#include "PCLPipeline.h"
-
 #include <pcl/console/print.h>
 #include <pcl/point_types.h>
 #include <pcl/io/pcd_io.h>
@@ -44,6 +41,9 @@
 #include <pdal/pdal_macros.hpp>
 #include <pdal/util/ProgramArgs.hpp>
 
+#include "../PCLConversions.hpp"
+#include "../pipeline/PCLPipeline.h"
+
 namespace pdal
 {
 
@@ -61,35 +61,26 @@ std::string PCLBlock::getName() const
 void PCLBlock::addArgs(ProgramArgs& args)
 {
     args.add("filename", "Output filename", m_filename);
-    args.add("json", "JSON pipeline", m_json);
+    args.add("methods", "methods", m_methods);
 }
 
 PointViewSet PCLBlock::run(PointViewPtr input)
 {
+    using namespace Dimension;
+    
     PointViewPtr output = input->makeNew();
     PointViewSet viewSet;
     viewSet.insert(output);
 
-    bool logOutput = log()->getLevel() > LogLevel::Debug1;
-    if (logOutput)
-        log()->floatPrecision(8);
-
-    log()->get(LogLevel::Debug2) <<
-         input->getFieldAs<double>(Dimension::Id::X, 0) << ", " <<
-         input->getFieldAs<double>(Dimension::Id::Y, 0) << ", " <<
-         input->getFieldAs<double>(Dimension::Id::Z, 0) << std::endl;
     log()->get(LogLevel::Debug2) << "Process PCLBlock..." << std::endl;
 
-    BOX3D buffer_bounds;
-    input->calculateBounds(buffer_bounds);
+    BOX3D bounds;
+    input->calculateBounds(bounds);
 
     // convert PointView to PointNormal
     typedef pcl::PointCloud<pcl::PointXYZ> Cloud;
     Cloud::Ptr cloud(new Cloud);
-    pclsupport::PDALtoPCD(input, *cloud, buffer_bounds);
-
-    log()->get(LogLevel::Debug2) << cloud->points[0].x << ", " <<
-        cloud->points[0].y << ", " << cloud->points[0].z << std::endl;
+    pclsupport::PDALtoPCD(input, *cloud, bounds);
 
     pclsupport::setLogLevel(log()->getLevel());
 
@@ -97,14 +88,14 @@ PointViewSet PCLBlock::run(PointViewPtr input)
     pipeline.setInputCloud(cloud);
     if (!m_filename.empty())
         pipeline.setFilename(m_filename);
-    else if (!m_json.empty())
-        pipeline.setJSON(m_json);
+    else if (!m_methods.empty())
+        pipeline.setMethods(m_methods);
     else
         throw pdal_error("No PCL pipeline specified!");
     // PDALtoPCD subtracts min values in each XYZ dimension to prevent rounding
     // errors in conversion to float. These offsets need to be conveyed to the
     // pipeline to offset any bounds entered as part of a PassThrough filter.
-    pipeline.setOffsets(buffer_bounds.minx, buffer_bounds.miny, buffer_bounds.minz);
+    pipeline.setOffsets(bounds.minx, bounds.miny, bounds.minz);
 
     // create PointCloud for results
     Cloud::Ptr cloud_f(new Cloud);
@@ -112,18 +103,12 @@ PointViewSet PCLBlock::run(PointViewPtr input)
 
     if (cloud_f->points.empty())
     {
-        log()->get(LogLevel::Debug2) << "Filtered cloud has no points!" << std::endl;
+        log()->get(LogLevel::Debug2) << "Filtered cloud has no points!\n";
         return viewSet;
     }
 
-    pclsupport::PCDtoPDAL(*cloud_f, output, buffer_bounds);
+    pclsupport::PCDtoPDAL(*cloud_f, output, bounds);
 
-    log()->get(LogLevel::Debug2) << cloud->points.size() << " before, " <<
-                                 cloud_f->points.size() << " after" << std::endl;
-    log()->get(LogLevel::Debug2) << output->size() << std::endl;
-    log()->get(LogLevel::Debug2) << output->getFieldAs<double>(Dimension::Id::X, 0) << ", " <<
-                                 output->getFieldAs<double>(Dimension::Id::Y, 0) << ", " <<
-                                 output->getFieldAs<double>(Dimension::Id::Z, 0) << std::endl;
     return viewSet;
 }
 
diff --git a/plugins/pcl/filters/PCLBlock.hpp b/plugins/pcl/filters/PCLBlock.hpp
index 229afb9..fb5f3cf 100644
--- a/plugins/pcl/filters/PCLBlock.hpp
+++ b/plugins/pcl/filters/PCLBlock.hpp
@@ -1,5 +1,5 @@
 /******************************************************************************
-* Copyright (c) 2013, Bradley J Chambers (brad.chambers at gmail.com)
+* Copyright (c) 2013-2016, Bradley J Chambers (brad.chambers at gmail.com)
 *
 * All rights reserved.
 *
@@ -35,10 +35,14 @@
 #pragma once
 
 #include <pdal/Filter.hpp>
-#include <pdal/StageFactory.hpp>
+
+#include <string>
 
 namespace pdal
 {
+  
+class PointView;
+class ProgramArgs;
 
 class PDAL_DLL PCLBlock : public Filter
 {
@@ -52,7 +56,7 @@ public:
 
 private:
     std::string m_filename;
-    std::string m_json;
+    std::string m_methods;
 
     virtual void addArgs(ProgramArgs& args);
     virtual PointViewSet run(PointViewPtr view);
@@ -62,4 +66,3 @@ private:
 };
 
 } // namespace pdal
-
diff --git a/plugins/pcl/filters/PoissonFilter.cpp b/plugins/pcl/filters/PoissonFilter.cpp
index 424c7dd..5cffcb9 100644
--- a/plugins/pcl/filters/PoissonFilter.cpp
+++ b/plugins/pcl/filters/PoissonFilter.cpp
@@ -34,9 +34,6 @@
 
 #include "PoissonFilter.hpp"
 
-#include "PCLConversions.hpp"
-#include "PCLPipeline.h"
-
 #include <pcl/console/print.h>
 #include <pcl/point_types.h>
 #include <pcl/features/normal_3d.h>
@@ -46,6 +43,8 @@
 #include <pdal/pdal_macros.hpp>
 #include <pdal/util/ProgramArgs.hpp>
 
+#include "../PCLConversions.hpp"
+
 namespace pdal
 {
 
diff --git a/plugins/pcl/filters/RadiusOutlierFilter.cpp b/plugins/pcl/filters/RadiusOutlierFilter.cpp
deleted file mode 100644
index c632afa..0000000
--- a/plugins/pcl/filters/RadiusOutlierFilter.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "RadiusOutlierFilter.hpp"
-
-#include "PCLConversions.hpp"
-
-#include <pdal/Options.hpp>
-#include <pdal/PointTable.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <pcl/point_types.h>
-#include <pcl/console/print.h>
-#include <pcl/filters/extract_indices.h>
-#include <pcl/io/pcd_io.h>
-#include <pcl/filters/radius_outlier_removal.h>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.radiusoutlier", "Radius outlier removal",
-               "http://pdal.io/stages/filters.radiusoutlier.html");
-
-CREATE_SHARED_PLUGIN(1, 0, RadiusOutlierFilter, Filter, s_info)
-
-std::string RadiusOutlierFilter::getName() const
-{
-    return s_info.name;
-}
-
-void RadiusOutlierFilter::addArgs(ProgramArgs& args)
-{
-    args.add("min_neighbors", "Minimum number of neighbors in radius",
-        m_min_neighbors, 2);
-    args.add("radius", "Radius", m_radius, 1.0);
-    args.add("classify", "Apply classification labels?", m_classify, true);
-    args.add("extract", "Extract ground returns?", m_extract);
-}
-
-void RadiusOutlierFilter::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(Dimension::Id::Classification);
-}
-
-PointViewSet RadiusOutlierFilter::run(PointViewPtr input)
-{
-    bool logOutput = log()->getLevel() > LogLevel::Debug1;
-    if (logOutput)
-        log()->floatPrecision(8);
-    log()->get(LogLevel::Debug2) << "Process RadiusOutlierFilter...\n";
-
-    // convert PointView to PointXYZ
-    typedef pcl::PointCloud<pcl::PointXYZ> Cloud;
-    Cloud::Ptr cloud(new Cloud);
-    BOX3D bounds;
-    input->calculateBounds(bounds);
-    pclsupport::PDALtoPCD(input, *cloud, bounds);
-
-    pclsupport::setLogLevel(log()->getLevel());
-
-    // setup the outlier filter
-    pcl::RadiusOutlierRemoval<pcl::PointXYZ> ror(true);
-    ror.setInputCloud(cloud);
-    ror.setMinNeighborsInRadius(m_min_neighbors);
-    ror.setRadiusSearch(m_radius);
-
-    pcl::PointCloud<pcl::PointXYZ> output;
-    ror.setNegative(true);
-    ror.filter(output);
-
-    // filtered to return inliers
-    pcl::PointIndicesPtr inliers(new pcl::PointIndices);
-    ror.getRemovedIndices(*inliers);
-
-    PointViewSet viewSet;
-    if (inliers->indices.empty())
-    {
-        log()->get(LogLevel::Warning) << "Requested filter would remove all points. Try a larger radius/smaller minimum neighbors.\n";
-        viewSet.insert(input);
-        return viewSet;
-    }
-
-    // inverse are the outliers
-    std::vector<int> outliers(input->size()-inliers->indices.size());
-    for (PointId i = 0, j = 0, k = 0; i < input->size(); ++i)
-    {
-        if (i == (PointId)inliers->indices[j])
-        {
-            j++;
-            continue;
-        }
-        outliers[k++] = i;
-    }
-
-    if (!outliers.empty() && (m_classify || m_extract))
-    {
-
-        if (m_classify)
-        {
-            log()->get(LogLevel::Debug2) << "Labeled " << outliers.size() << " outliers as noise!\n";
-
-            // set the classification label of outlier returns as 18
-            // (corresponding to ASPRS LAS specification for high noise)
-            for (const auto& i : outliers)
-            {
-                input->setField(Dimension::Id::Classification, i, 18);
-            }
-
-            viewSet.insert(input);
-        }
-
-        if (m_extract)
-        {
-            log()->get(LogLevel::Debug2) << "Extracted " << inliers->indices.size() << " inliers!\n";
-
-            // create new PointView containing only outliers
-            PointViewPtr output = input->makeNew();
-            for (const auto& i : inliers->indices)
-            {
-                output->appendPoint(*input, i);
-            }
-
-            viewSet.erase(input);
-            viewSet.insert(output);
-        }
-    }
-    else
-    {
-        if (outliers.empty())
-            log()->get(LogLevel::Warning) << "Filtered cloud has no outliers!\n";
-
-        if (!(m_classify || m_extract))
-            log()->get(LogLevel::Warning) << "Must choose --classify or --extract\n";
-
-        // return the input buffer unchanged
-        viewSet.insert(input);
-    }
-
-    return viewSet;
-}
-
-} // namespace pdal
diff --git a/plugins/pcl/filters/RadiusOutlierFilter.hpp b/plugins/pcl/filters/RadiusOutlierFilter.hpp
deleted file mode 100644
index 2646b51..0000000
--- a/plugins/pcl/filters/RadiusOutlierFilter.hpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/Stage.hpp>
-
-#include <memory>
-
-namespace pdal
-{
-
-class Options;
-class PointLayout;
-class PointTable;
-class PointView;
-
-class PDAL_DLL RadiusOutlierFilter : public Filter
-{
-public:
-    RadiusOutlierFilter() : Filter()
-    {}
-    RadiusOutlierFilter& operator=(const RadiusOutlierFilter&) = delete;
-    RadiusOutlierFilter(const RadiusOutlierFilter&) = delete;
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    int m_min_neighbors;
-    double m_radius;
-    bool m_classify;
-    bool m_extract;
-
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual void addArgs(ProgramArgs& args);
-    virtual PointViewSet run(PointViewPtr view);
-};
-
-} // namespace pdal
diff --git a/plugins/pcl/filters/StatisticalOutlierFilter.cpp b/plugins/pcl/filters/StatisticalOutlierFilter.cpp
deleted file mode 100644
index a1cce1d..0000000
--- a/plugins/pcl/filters/StatisticalOutlierFilter.cpp
+++ /dev/null
@@ -1,180 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "StatisticalOutlierFilter.hpp"
-
-#include "PCLConversions.hpp"
-
-#include <pdal/Options.hpp>
-#include <pdal/PointTable.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <pcl/point_types.h>
-#include <pcl/console/print.h>
-#include <pcl/filters/extract_indices.h>
-#include <pcl/io/pcd_io.h>
-#include <pcl/filters/statistical_outlier_removal.h>
-
-namespace pdal
-{
-
-static PluginInfo const s_info =
-    PluginInfo("filters.statisticaloutlier", "Statistical outlier removal",
-               "http://pdal.io/stages/filters.statisticaloutlier.html");
-
-CREATE_SHARED_PLUGIN(1, 0, StatisticalOutlierFilter, Filter, s_info)
-
-std::string StatisticalOutlierFilter::getName() const
-{
-    return s_info.name;
-}
-
-
-void StatisticalOutlierFilter::addArgs(ProgramArgs& args)
-{
-    args.add("mean_k", "Mean number of neighbors", m_meanK, 8);
-    args.add("multiplier", "Standard deviation threshold", m_multiplier, 2.0);
-    args.add("classify", "Apply classification labels?", m_classify, true);
-    args.add("extract", "Extrac ground returns?", m_extract);
-}
-
-
-void StatisticalOutlierFilter::addDimensions(PointLayoutPtr layout)
-{
-    layout->registerDim(Dimension::Id::Classification);
-}
-
-PointViewSet StatisticalOutlierFilter::run(PointViewPtr input)
-{
-    bool logOutput = log()->getLevel() > LogLevel::Debug1;
-    if (logOutput)
-        log()->floatPrecision(8);
-    log()->get(LogLevel::Debug2) << "Process StatisticalOutlierFilter...\n";
-
-    // convert PointView to PointXYZ
-    typedef pcl::PointCloud<pcl::PointXYZ> Cloud;
-    Cloud::Ptr cloud(new Cloud);
-    BOX3D bounds;
-    input->calculateBounds(bounds);
-    pclsupport::PDALtoPCD(input, *cloud, bounds);
-
-    pclsupport::setLogLevel(log()->getLevel());
-
-    // setup the outlier filter
-    pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor(true);
-    sor.setInputCloud(cloud);
-    sor.setMeanK(m_meanK);
-    sor.setStddevMulThresh(m_multiplier);
-
-    pcl::PointCloud<pcl::PointXYZ> output;
-    sor.setNegative(true);
-    sor.filter(output);
-
-    // filtered to return inliers
-    pcl::PointIndicesPtr inliers(new pcl::PointIndices);
-    sor.getRemovedIndices(*inliers);
-
-    log()->get(LogLevel::Debug2) << inliers->indices.size() << std::endl;
-
-    PointViewSet viewSet;
-    if (inliers->indices.empty())
-    {
-        log()->get(LogLevel::Warning) << "Requested filter would remove all points. Try increasing the multiplier.\n";
-        viewSet.insert(input);
-        return viewSet;
-    }
-
-    // inverse are the outliers
-    std::vector<int> outliers(input->size()-inliers->indices.size());
-    for (PointId i = 0, j = 0, k = 0; i < input->size(); ++i)
-    {
-        if (i == (PointId)inliers->indices[j])
-        {
-            j++;
-            continue;
-        }
-        outliers[k++] = i;
-    }
-
-    if (!outliers.empty() && (m_classify || m_extract))
-    {
-
-        if (m_classify)
-        {
-            log()->get(LogLevel::Debug2) << "Labeled " << outliers.size() << " outliers as noise!\n";
-
-            // set the classification label of outlier returns as 18
-            // (corresponding to ASPRS LAS specification for high noise)
-            for (const auto& i : outliers)
-            {
-                input->setField(Dimension::Id::Classification, i, 18);
-            }
-
-            viewSet.insert(input);
-        }
-
-        if (m_extract)
-        {
-            log()->get(LogLevel::Debug2) << "Extracted " << inliers->indices.size() << " inliers!\n";
-
-            // create new PointView containing only outliers
-            PointViewPtr output = input->makeNew();
-            for (const auto& i : inliers->indices)
-            {
-                output->appendPoint(*input, i);
-            }
-
-            viewSet.erase(input);
-            viewSet.insert(output);
-        }
-    }
-    else
-    {
-        if (outliers.empty())
-            log()->get(LogLevel::Warning) << "Filtered cloud has no outliers!\n";
-
-        if (!(m_classify || m_extract))
-            log()->get(LogLevel::Warning) << "Must choose --classify or --extract\n";
-
-        // return the input buffer unchanged
-        viewSet.insert(input);
-    }
-
-    return viewSet;
-}
-
-} // namespace pdal
diff --git a/plugins/pcl/filters/StatisticalOutlierFilter.hpp b/plugins/pcl/filters/StatisticalOutlierFilter.hpp
deleted file mode 100644
index 4bb1aa4..0000000
--- a/plugins/pcl/filters/StatisticalOutlierFilter.hpp
+++ /dev/null
@@ -1,74 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Filter.hpp>
-#include <pdal/Stage.hpp>
-
-#include <memory>
-
-namespace pdal
-{
-
-class Options;
-class PointLayout;
-class PointTable;
-class PointView;
-
-class PDAL_DLL StatisticalOutlierFilter : public Filter
-{
-public:
-    StatisticalOutlierFilter() : Filter()
-    {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    int m_meanK;
-    double m_multiplier;
-    bool m_classify;
-    bool m_extract;
-
-    virtual void addDimensions(PointLayoutPtr layout);
-    virtual void addArgs(ProgramArgs& args);
-    virtual PointViewSet run(PointViewPtr view);
-
-    StatisticalOutlierFilter& operator=(const StatisticalOutlierFilter&); // not implemented
-    StatisticalOutlierFilter(const StatisticalOutlierFilter&); // not implemented
-};
-
-} // namespace pdal
diff --git a/plugins/pcl/filters/VoxelGridFilter.cpp b/plugins/pcl/filters/VoxelGridFilter.cpp
index d02c478..e1622c4 100644
--- a/plugins/pcl/filters/VoxelGridFilter.cpp
+++ b/plugins/pcl/filters/VoxelGridFilter.cpp
@@ -34,9 +34,6 @@
 
 #include "VoxelGridFilter.hpp"
 
-#include "PCLConversions.hpp"
-#include "PCLPipeline.h"
-
 #include <pcl/console/print.h>
 #include <pcl/point_types.h>
 #include <pcl/io/pcd_io.h>
@@ -45,6 +42,8 @@
 #include <pdal/pdal_macros.hpp>
 #include <pdal/util/ProgramArgs.hpp>
 
+#include "../PCLConversions.hpp"
+
 namespace pdal
 {
 
diff --git a/plugins/pcl/io/PCLVisualizer.cpp b/plugins/pcl/io/PCLVisualizer.cpp
deleted file mode 100644
index 5da1b1d..0000000
--- a/plugins/pcl/io/PCLVisualizer.cpp
+++ /dev/null
@@ -1,197 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Brad Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "PCLVisualizer.hpp"
-
-#include <pcl/conversions.h>
-#include <pcl/io/pcd_io.h>
-
-#include <pcl/visualization/pcl_visualizer.h>
-#include <pcl/visualization/impl/pcl_visualizer.hpp>
-#include <pcl/visualization/point_cloud_handlers.h>
-#include <pcl/visualization/impl/point_cloud_handlers.hpp>
-
-#include <pdal/PointView.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-
-#include "PCLConversions.hpp"
-#include "point_types.hpp"
-
-#include <chrono>
-#include <memory>
-#include <thread>
-
-bool
-isValidFieldName(const std::string &field)
-{
-    if (field == "_")
-        return (false);
-
-    return (true);
-}
-
-namespace pcl
-{
-namespace visualization
-{
-template <typename PointT>
-class PointCloudColorHandlerIntensity : public PointCloudColorHandler<PointT>
-{
-    using PointCloudColorHandler<PointT>::capable_;
-    using PointCloudColorHandler<PointT>::cloud_;
-
-    typedef typename PointCloudColorHandler<PointT>::PointCloud::ConstPtr PointCloudConstPtr;
-
-public:
-    typedef std::shared_ptr<PointCloudColorHandlerIntensity<PointT> > Ptr;
-    typedef std::shared_ptr<const PointCloudColorHandlerIntensity<PointT> > ConstPtr;
-
-    PointCloudColorHandlerIntensity(const PointCloudConstPtr& cloud) :
-        PointCloudColorHandler<PointT> (cloud)
-    {
-        capable_ = true;
-    }
-
-    virtual bool
-    getColor(vtkSmartPointer<vtkDataArray> &scalars) const
-    {
-        if (!capable_ || !cloud_)
-            return (false);
-
-        if (!scalars)
-            scalars = vtkSmartPointer<vtkUnsignedCharArray>::New();
-        scalars->SetNumberOfComponents(3);
-
-        vtkIdType nr_points = cloud_->width * cloud_->height;
-        reinterpret_cast<vtkUnsignedCharArray*>(&(*scalars))->SetNumberOfTuples(nr_points);
-        unsigned char* colors = reinterpret_cast<vtkUnsignedCharArray*>(&(*scalars))->GetPointer(0);
-
-        int int_idx = pcl::getFieldIndex(*cloud_, "intensity");
-
-        if (int_idx != -1)
-        {
-            float int_data;
-            int int_point_offset = cloud_->fields[int_idx].offset;
-            for (vtkIdType cp = 0; cp < nr_points; ++cp, int_point_offset += cloud_->point_step)
-            {
-                int idx = cp * 3;
-                memcpy(&int_data, &cloud_->data[int_point_offset], sizeof(float));
-                colors[idx + 0] = int_data * 255;
-                colors[idx + 1] = int_data * 255;
-                colors[idx + 2] = int_data * 255;
-            }
-        }
-        return (true);
-    }
-
-private:
-    virtual std::string getFieldName() const
-    {
-        return ("intensity");
-    }
-    virtual inline std::string getName() const
-    {
-        return ("PointCloudColorHandlerIntensity");
-    }
-};
-}
-}
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "writers.pclvisualizer",
-    "PCL Visualizer",
-    "http://pdal.io/stages/writers.pclvisualizer.html" );
-
-CREATE_SHARED_PLUGIN(1, 0, PclVisualizer, Writer, s_info)
-
-std::string PclVisualizer::getName() const { return s_info.name; }
-
-void PclVisualizer::write(const PointViewPtr view)
-{
-    // Determine XYZ bounds
-    BOX3D buffer_bounds;
-
-    view->calculateBounds(buffer_bounds);
-
-    typedef XYZIRGBA PointType;
-
-    // Convert PointView to a PCL PointCloud
-    pcl::PointCloud<PointType>::Ptr cloud(new pcl::PointCloud<PointType>);
-    pclsupport::PDALtoPCD(view, *cloud, buffer_bounds);
-
-    // Create PCLVisualizer
-    std::shared_ptr<pcl::visualization::PCLVisualizer> p(new pcl::visualization::PCLVisualizer("3D Viewer"));
-
-    // Set background to black
-    p->setBackgroundColor(0, 0, 0);
-
-    pcl::PCLPointCloud2::Ptr cloud_blob(new pcl::PCLPointCloud2);
-    toPCLPointCloud2(*cloud, *cloud_blob);
-
-    typedef pcl::visualization::PointCloudColorHandler<pcl::PCLPointCloud2> ColorHandler;
-    typedef ColorHandler::Ptr ColorHandlerPtr;
-    ColorHandlerPtr color_handler;
-    for (size_t f = 0; f < cloud_blob->fields.size(); ++f)
-    {
-        if (cloud_blob->fields[f].name == "rgb" || cloud_blob->fields[f].name == "rgba")
-        {
-            color_handler.reset(new pcl::visualization::PointCloudColorHandlerRGBField<pcl::PCLPointCloud2> (cloud_blob));
-        }
-        else if (cloud_blob->fields[f].name == "intensity")
-        {
-            color_handler.reset(new pcl::visualization::PointCloudColorHandlerIntensity<pcl::PCLPointCloud2> (cloud_blob));
-        }
-        else
-        {
-            if (!isValidFieldName(cloud_blob->fields[f].name))
-                continue;
-            color_handler.reset(new pcl::visualization::PointCloudColorHandlerGenericField<pcl::PCLPointCloud2> (cloud_blob, cloud_blob->fields[f].name));
-        }
-        p->addPointCloud(cloud_blob, color_handler, cloud->sensor_origin_, cloud->sensor_orientation_);
-    }
-    p->updateColorHandlerIndex("cloud", 2);
-
-    while (!p->wasStopped())
-    {
-        p->spinOnce(100);
-        std::this_thread::sleep_for(std::chrono::microseconds(100000));
-    }
-}
-
-
-} // namespaces
diff --git a/plugins/pcl/io/PCLVisualizer.hpp b/plugins/pcl/io/PCLVisualizer.hpp
deleted file mode 100644
index bcb5949..0000000
--- a/plugins/pcl/io/PCLVisualizer.hpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Brad Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Writer.hpp>
-
-#include <string>
-
-namespace pdal
-{
-
-class PDAL_DLL PclVisualizer : public Writer
-{
-public:
-    PclVisualizer()
-    {}
-
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-
-private:
-    virtual void write(const PointViewPtr view);
-
-    PclVisualizer& operator=(const PclVisualizer&); // not implemented
-    PclVisualizer(const PclVisualizer&); // not implemented
-};
-
-} // namespaces
diff --git a/plugins/pcl/io/PcdReader.cpp b/plugins/pcl/io/PcdReader.cpp
index f389026..107642a 100644
--- a/plugins/pcl/io/PcdReader.cpp
+++ b/plugins/pcl/io/PcdReader.cpp
@@ -41,7 +41,7 @@
 #include <pcl/io/impl/pcd_io.hpp>
 
 #include <pdal/PointView.hpp>
-#include "PCLConversions.hpp"
+#include "../PCLConversions.hpp"
 
 #include <pdal/pdal_macros.hpp>
 
diff --git a/plugins/pcl/io/PcdWriter.cpp b/plugins/pcl/io/PcdWriter.cpp
index f3796c8..a44955c 100644
--- a/plugins/pcl/io/PcdWriter.cpp
+++ b/plugins/pcl/io/PcdWriter.cpp
@@ -1,5 +1,6 @@
 /******************************************************************************
 * Copyright (c) 2011, Brad Chambers (brad.chambers at gmail.com)
+* Copytight (c) 2016, Logan Byers (logan.c.byers at gmail.com)
 *
 * All rights reserved.
 *
@@ -42,7 +43,8 @@
 #include <pcl/io/pcd_io.h>
 #include <pcl/io/impl/pcd_io.hpp>
 
-#include "PCLConversions.hpp"
+#include "../PCLConversions.hpp"
+
 #include <pdal/PointView.hpp>
 #include <pdal/pdal_macros.hpp>
 #include <pdal/util/ProgramArgs.hpp>
@@ -62,34 +64,24 @@ std::string PcdWriter::getName() const { return s_info.name; }
 
 void PcdWriter::addArgs(ProgramArgs& args)
 {
-
-    std::string compression;
-    args.add("filename", "Filename to write PCD file to", m_filename);
-    args.add("compression","Level of PCD compression to use (ascii, binary, compressed)", compression, "ascii");
+    args.add("filename", "PCD output filename", m_filename).setPositional();
+    args.add("compression", "Level of PCD compression to use "
+        "(ascii, binary, compressed)", m_compression_string);
     args.add("xyz", "Write only XYZ dimensions?", m_xyz, false);
-    args.add("subtract_minimum", "Set origin to minimum of XYZ dimension", m_subtract_minimum, true);
-    args.add("offset_x", "Offset to be subtracted from XYZ position", m_offset_x, 0.0);
-    args.add("offset_y", "Offset to be subtracted from XYZ position", m_offset_y, 0.0);
-    args.add("offset_z", "Offset to be subtracted from XYZ position", m_offset_z, 0.0);
+    args.add("subtract_minimum", "Set origin to minimum of XYZ dimension",
+        m_subtract_minimum, true);
+    args.add("offset_x", "Offset to be subtracted from XYZ position",
+        m_offset_x, 0.0);
+    args.add("offset_y", "Offset to be subtracted from XYZ position",
+        m_offset_y, 0.0);
+    args.add("offset_z", "Offset to be subtracted from XYZ position",
+        m_offset_z, 0.0);
     args.add("scale_x", "Scale to divide from XYZ dimension", m_scale_x, 1.0);
     args.add("scale_y", "Scale to divide from XYZ dimension", m_scale_y, 1.0);
     args.add("scale_z", "Scale to divide from XYZ dimension", m_scale_z, 1.0);
-
-    if (compression == "binary")
-    {
-      m_compression = 1;
-    }
-    else if (compression == "compressed")
-    {
-      m_compression = 2;
-    }
-    else  // including "ascii"
-    {
-      m_compression = 0;
-    }
-
 }
 
+
 void PcdWriter::write(const PointViewPtr view)
 {
     if (m_xyz)
@@ -103,4 +95,10 @@ void PcdWriter::write(const PointViewPtr view)
 }
 
 
+void PcdWriter::done(PointTableRef)
+{
+    getMetadata().addList("filename", m_filename);
+}
+
+
 } // namespaces
diff --git a/plugins/pcl/io/PcdWriter.hpp b/plugins/pcl/io/PcdWriter.hpp
index e1264ed..b0fe5eb 100644
--- a/plugins/pcl/io/PcdWriter.hpp
+++ b/plugins/pcl/io/PcdWriter.hpp
@@ -1,5 +1,6 @@
 /******************************************************************************
 * Copyright (c) 2014, Brad Chambers (brad.chambers at gmail.com)
+* Copytight (c) 2016, Logan Byers (logan.c.byers at gmail.com)
 *
 * All rights reserved.
 *
@@ -38,7 +39,7 @@
 #include <pdal/util/FileUtils.hpp>
 #include <pdal/StageFactory.hpp>
 
-#include "PCLConversions.hpp"
+#include "../PCLConversions.hpp"
 
 #include <pcl/io/pcd_io.h>
 #include <pcl/io/impl/pcd_io.hpp>
@@ -62,11 +63,13 @@ public:
 private:
     virtual void addArgs(ProgramArgs& args);
     virtual void write(const PointViewPtr view);
+    virtual void done(PointTableRef table);
 
     template<typename CloudT>
     inline void writeView(const PointViewPtr view); // implemented in header
 
     std::string m_filename;
+    std::string m_compression_string;
     uint8_t m_compression;
     bool m_xyz;
     bool m_subtract_minimum;
@@ -101,6 +104,20 @@ void PcdWriter::writeView(const PointViewPtr view)
     }
     pclsupport::PDALtoPCD(view, *cloud, bounds, m_scale_x, m_scale_y, m_scale_z);
     pcl::PCDWriter w;
+
+    if (m_compression_string == "binary")
+    {
+      m_compression = 1;
+    }
+    else if (m_compression_string == "compressed")
+    {
+      m_compression = 2;
+    }
+    else  // including "ascii"
+    {
+      m_compression = 0;
+    }
+
     switch (m_compression)
     {
         case 0 : w.writeASCII<PointT>(m_filename, *cloud); break;
diff --git a/plugins/pcl/kernel/GroundKernel.cpp b/plugins/pcl/kernel/GroundKernel.cpp
deleted file mode 100644
index 899af28..0000000
--- a/plugins/pcl/kernel/GroundKernel.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-* Copyright (c) 2014-2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "GroundKernel.hpp"
-
-#include <pdal/KernelFactory.hpp>
-#include <pdal/Options.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/PointTable.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/Stage.hpp>
-#include <pdal/StageFactory.hpp>
-
-#include <memory>
-#include <string>
-#include <vector>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.ground",
-    "Ground Kernel",
-    "http://pdal.io/kernels/kernels.ground.html" );
-
-CREATE_SHARED_PLUGIN(1, 0, GroundKernel, Kernel, s_info)
-
-std::string GroundKernel::getName() const { return s_info.name; }
-
-GroundKernel::GroundKernel()
-    : Kernel()
-    , m_inputFile("")
-    , m_outputFile("")
-    , m_maxWindowSize(33)
-    , m_slope(1)
-    , m_maxDistance(2.5)
-    , m_initialDistance(0.15)
-    , m_cellSize(1)
-    , m_classify(true)
-    , m_extract(false)
-    , m_approximate(false)
-{}
-
-void GroundKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("input,i", "Input filename", m_inputFile).setPositional();
-    args.add("output,o", "Output filename", m_outputFile).setPositional();
-    args.add("max_window_size", "Max window size", m_maxWindowSize, 33.0);
-    args.add("slope", "Slope", m_slope, 1.0);
-    args.add("max_distance", "Max distance", m_maxDistance, 2.5);
-    args.add("initial_distance", "Initial distance", m_initialDistance, .15);
-    args.add("cell_size", "Cell size", m_cellSize, 1.0);
-    args.add("classify", "Apply classification labels?", m_classify);
-    args.add("extract", "extract ground returns?", m_extract);
-    args.add("approximate,a", "use approximate algorithm? (much faster)",
-        m_approximate);
-}
-
-int GroundKernel::execute()
-{
-    PointTable table;
-
-    Stage& readerStage(makeReader(m_inputFile, ""));
-
-    Options groundOptions;
-    groundOptions.add("max_window_size", m_maxWindowSize);
-    groundOptions.add("slope", m_slope);
-    groundOptions.add("max_distance", m_maxDistance);
-    groundOptions.add("initial_distance", m_initialDistance);
-    groundOptions.add("cell_size", m_cellSize);
-    groundOptions.add("classify", m_classify);
-    groundOptions.add("extract", m_extract);
-    groundOptions.add("approximate", m_approximate);
-
-    Stage& groundStage = makeFilter("filters.ground", readerStage,
-        groundOptions);
-
-    // setup the Writer and write the results
-    Stage& writer(makeWriter(m_outputFile, groundStage, ""));
-
-    writer.prepare(table);
-
-    // process the data, grabbing the PointViewSet for visualization of the
-    // resulting PointView
-    PointViewSet viewSetOut = writer.execute(table);
-
-    if (isVisualize())
-        visualize(*viewSetOut.begin());
-
-    return 0;
-}
-
-} // namespace pdal
diff --git a/plugins/pcl/kernel/GroundKernel.hpp b/plugins/pcl/kernel/GroundKernel.hpp
deleted file mode 100644
index 222b623..0000000
--- a/plugins/pcl/kernel/GroundKernel.hpp
+++ /dev/null
@@ -1,76 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2013, Howard Butler (hobu.inc at gmail.com)
-* Copyright (c) 2014-2015, Brad Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Kernel.hpp>
-#include <pdal/pdal_export.hpp>
-#include <pdal/util/FileUtils.hpp>
-
-#include <memory>
-#include <string>
-
-namespace pdal
-{
-
-class Options;
-class Stage;
-
-class PDAL_DLL GroundKernel : public Kernel
-{
-public:
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-    int execute();
-
-private:
-    GroundKernel();
-    virtual void addSwitches(ProgramArgs& args);
-
-    std::string m_inputFile;
-    std::string m_outputFile;
-    double m_maxWindowSize;
-    double m_slope;
-    double m_maxDistance;
-    double m_initialDistance;
-    double m_cellSize;
-    bool m_classify;
-    bool m_extract;
-    bool m_approximate;
-};
-
-} // namespace pdal
-
diff --git a/plugins/pcl/kernel/HeightAboveGroundKernel.cpp b/plugins/pcl/kernel/HeightAboveGroundKernel.cpp
deleted file mode 100644
index 499f7e5..0000000
--- a/plugins/pcl/kernel/HeightAboveGroundKernel.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Brad Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "HeightAboveGroundKernel.hpp"
-
-#include "PCLConversions.hpp"
-
-#include <pcl/ModelCoefficients.h>
-#include <pcl/filters/project_inliers.h>
-#include <pcl/kdtree/kdtree_flann.h>
-
-#include <pdal/BufferReader.hpp>
-#include <pdal/Filter.hpp>
-#include <pdal/Kernel.hpp>
-#include <pdal/KernelFactory.hpp>
-#include <pdal/Options.hpp>
-#include <pdal/pdal_macros.hpp>
-#include <pdal/PointBuffer.hpp>
-#include <pdal/PointContext.hpp>
-#include <pdal/Reader.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/Writer.hpp>
-
-#include <memory>
-#include <string>
-#include <vector>
-
-CREATE_KERNEL_PLUGIN(height, pdal::HeightAboveGroundKernel)
-
-namespace pdal
-{
-
-void HeightAboveGroundKernel::validateSwitches(ProgramArgs& args)
-{
-    if (m_ground_file == "" && !m_use_classification)
-        throw pdal_error("Either option 'ground' or 'use_classification' "
-            "is required.");
-
-    // should probably verify that the output is BPF
-    // should probably also verify that there is a classification
-    // dimension if --use_classification == true
-}
-
-void HeightAboveGroundKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("use_classification,c", "Use existing classification labels?",
-        m_use_classification);
-    args.add("input,i", "Input filename", m_input_file).setPositional();
-    args.add("ground,g", "Ground filename", m_ground_file).setPositional();
-    args.add("output,o",
-        "Output filename", m_output_file).setOptionalPositional();
-}
-
-int HeightAboveGroundKernel::execute()
-{
-    // we require separate contexts for the input and ground files
-    PointContextRef input_ctx;
-    PointContextRef ground_ctx;
-
-    // because we are appending HeightAboveGround to the input buffer, we must
-    // register it's Dimension
-    input_ctx.registerDim(Dimension::Id::HeightAboveGround);
-
-    // StageFactory will be used to create required stages
-    StageFactory f;
-
-    // setup the reader, inferring driver type from the filename
-    std::string reader_driver = f.inferReaderDriver(m_input_file);
-    std::unique_ptr<Reader> input(f.createReader(reader_driver));
-    Options readerOptions;
-    readerOptions.add("filename", m_input_file);
-    input->setOptions(readerOptions);
-
-    // go ahead and execute to get the PointBuffer
-    input->prepare(input_ctx);
-    PointBufferSet pbSetInput = input->execute(input_ctx);
-    PointBufferPtr input_buf = *pbSetInput.begin();
-
-    PointBufferSet pbSetGround;
-    PointBufferPtr ground_buf;
-
-    if (m_use_classification)
-    {
-        // the user has indicated that the classification dimension exists, so
-        // we will find all ground returns
-        Option source("source",
-                      "import numpy as np\n"
-                      "def yow1(ins,outs):\n"
-                      "  cls = ins['Classification']\n"
-                      "  keep_classes = [2]\n"
-                      "  keep = np.equal(cls, keep_classes[0])\n"
-                      "  outs['Mask'] = keep\n"
-                      "  return True\n"
-                     );
-        Option module("module", "MyModule");
-        Option function("function", "yow1");
-        Options opts;
-        opts.add(source);
-        opts.add(module);
-        opts.add(function);
-
-        // and create a PointBuffer of only ground returns
-        std::unique_ptr<Filter> pred(f.createFilter("filters.predicate"));
-        pred->setOptions(opts);
-        pred->setInput(input.get());
-        pred->prepare(ground_ctx);
-        pbSetGround = pred->execute(ground_ctx);
-        ground_buf = *pbSetGround.begin();
-    }
-    else
-    {
-        // the user has provided a file containing only ground returns, setup
-        // the reader, inferring driver type from the filename
-        std::string ground_driver = f.inferReaderDriver(m_ground_file);
-        std::unique_ptr<Reader> ground(f.createReader(ground_driver));
-        Options ro;
-        ro.add("filename", m_ground_file);
-        ground->setOptions(ro);
-
-        // go ahead and execute to get the PointBuffer
-        ground->prepare(ground_ctx);
-        pbSetGround = ground->execute(ground_ctx);
-        ground_buf = *pbSetGround.begin();
-    }
-
-    typedef pcl::PointXYZ PointT;
-    typedef pcl::PointCloud<PointT> Cloud;
-    typedef Cloud::Ptr CloudPtr;
-
-    // convert the input PointBuffer to a PointCloud
-    CloudPtr cloud(new Cloud);
-    BOX3D const& bounds = input_buf->calculateBounds();
-    pclsupport::PDALtoPCD(*input_buf, *cloud, bounds);
-
-    // convert the ground PointBuffer to a PointCloud
-    CloudPtr cloud_g(new Cloud);
-    // here, we offset the ground cloud by the input bounds so that the two are aligned
-    pclsupport::PDALtoPCD(*ground_buf, *cloud_g, bounds);
-
-    // create a set of planar coefficients with X=Y=0,Z=1
-    pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients());
-    coefficients->values.resize(4);
-    coefficients->values[0] = coefficients->values[1] = 0;
-    coefficients->values[2] = 1.0;
-    coefficients->values[3] = 0;
-
-    // create the filtering object and project ground returns into xy plane
-    pcl::ProjectInliers<PointT> proj;
-    proj.setModelType(pcl::SACMODEL_PLANE);
-    proj.setInputCloud(cloud_g);
-    proj.setModelCoefficients(coefficients);
-    CloudPtr cloud_projected(new Cloud);
-    proj.filter(*cloud_projected);
-
-    // setup the KdTree
-    pcl::KdTreeFLANN<PointT> tree;
-    tree.setInputCloud(cloud_projected);
-
-    // loop over all points in the input cloud, finding the nearest neighbor in
-    // the ground returns (XY plane only), and calculating the difference in z
-    int32_t k = 1;
-    for (size_t idx = 0; idx < cloud->points.size(); ++idx)
-    {
-        // Search for nearesrt neighbor of the query point
-        std::vector<int32_t> neighbors(k);
-        std::vector<float> distances(k);
-        PointT temp_pt = cloud->points[idx];
-        temp_pt.z = 0.0f;
-        int num_neighbors = tree.nearestKSearch(temp_pt, k, neighbors, distances);
-
-        double hag = cloud->points[idx].z - cloud_g->points[neighbors[0]].z;
-        input_buf->setField(Dimension::Id::HeightAboveGround, idx, hag);
-    }
-
-    // populate BufferReader with the input PointBuffer, which now has the
-    // HeightAboveGround dimension
-    BufferReader bufferReader;
-    bufferReader.addBuffer(input_buf);
-
-    // we require that the output be BPF for now, to house our non-standard
-    // dimension
-    Options wo;
-    wo.add("filename", m_output_file);
-    std::unique_ptr<Writer> writer(f.createWriter("writers.bpf"));
-    writer->setOptions(wo);
-    writer->setInput(&bufferReader);
-    writer->prepare(input_ctx);
-    writer->execute(input_ctx);
-
-    return 0;
-}
-
-} // namespace pdal
diff --git a/plugins/pcl/kernel/HeightAboveGroundKernel.hpp b/plugins/pcl/kernel/HeightAboveGroundKernel.hpp
deleted file mode 100644
index 04b308f..0000000
--- a/plugins/pcl/kernel/HeightAboveGroundKernel.hpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Brad Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Kernel.hpp>
-
-#include <string>
-
-namespace pdal
-{
-
-class HeightAboveGroundKernel : public Kernel
-{
-public:
-    SET_KERNEL_NAME("height", "Height above ground")
-    SET_KERNEL_LINK("http://pdal.io/kernels/kernels.height.html")
-
-    int execute();
-
-private:
-    virtual void validateSwitches(ProgramArgs& args);
-    virtual void addSwitches(ProgramArgs& args);
-
-    bool m_use_classification;
-
-    std::string m_input_file;
-    std::string m_ground_file;
-    std::string m_output_file;
-};
-
-} // namespace pdal
diff --git a/plugins/pcl/kernel/PCLKernel.cpp b/plugins/pcl/kernel/PCLKernel.cpp
index c8afd26..f0f371b 100644
--- a/plugins/pcl/kernel/PCLKernel.cpp
+++ b/plugins/pcl/kernel/PCLKernel.cpp
@@ -32,21 +32,18 @@
  * OF SUCH DAMAGE.
  ****************************************************************************/
 
-#include "PCLKernel.hpp"
-
-#include "PCLBlock.hpp"
-
-#include <buffer/BufferReader.hpp>
 #include <pdal/KernelFactory.hpp>
 #include <pdal/pdal_macros.hpp>
+#include <io/BufferReader.hpp>
+
+#include "PCLKernel.hpp"
+#include "../filters/PCLBlock.hpp"
 
 namespace pdal
 {
 
-static PluginInfo const s_info = PluginInfo(
-    "kernels.pcl",
-    "PCL Kernel",
-    "http://pdal.io/kernels/kernels.pcl.html" );
+static PluginInfo const s_info = PluginInfo("kernels.pcl", "PCL Kernel",
+    "http://pdal.io/apps/pcl.html" );
 
 CREATE_SHARED_PLUGIN(1, 0, PCLKernel, Kernel, s_info)
 
diff --git a/plugins/pcl/kernel/SmoothKernel.cpp b/plugins/pcl/kernel/SmoothKernel.cpp
index 5675aa8..64bec59 100644
--- a/plugins/pcl/kernel/SmoothKernel.cpp
+++ b/plugins/pcl/kernel/SmoothKernel.cpp
@@ -35,11 +35,11 @@
 
 #include "SmoothKernel.hpp"
 
-#include "PCLBlock.hpp"
-
-#include <buffer/BufferReader.hpp>
 #include <pdal/KernelFactory.hpp>
 #include <pdal/pdal_macros.hpp>
+#include <io/BufferReader.hpp>
+
+#include "../filters/PCLBlock.hpp"
 
 namespace pdal
 {
diff --git a/plugins/pcl/kernel/ViewKernel.cpp b/plugins/pcl/kernel/ViewKernel.cpp
deleted file mode 100644
index da40c52..0000000
--- a/plugins/pcl/kernel/ViewKernel.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "ViewKernel.hpp"
-
-#include <pdal/KernelFactory.hpp>
-#include <pdal/pdal_macros.hpp>
-
-namespace pdal
-{
-
-static PluginInfo const s_info = PluginInfo(
-    "kernels.view",
-    "View Kernel",
-    "http://pdal.io/kernels/kernels.view.html" );
-
-CREATE_SHARED_PLUGIN(1, 0, ViewKernel, Kernel, s_info)
-
-std::string ViewKernel::getName() const { return s_info.name; }
-
-// Support for parsing point numbers.  Points can be specified singly or as
-// dash-separated ranges.  i.e. 6-7,8,19-20
-namespace {
-
-using namespace std;
-
-vector<string> tokenize(const string s, char c)
-{
-    string::const_iterator begin;
-    string::const_iterator end;
-    vector<string> strings;
-    begin = s.begin();
-    while (true)
-    {
-        end = find(begin, s.end(), c);
-        strings.push_back(string(begin, end));
-        if (end == s.end())
-            break;
-        begin = end + 1;
-    }
-    return strings;
-}
-
-uint32_t parseInt(const string& s)
-{
-    uint32_t val;
-    if (!Utils::fromString(s, val))
-        throw pdal_error(string("Invalid integer: ") + s);
-    return val;
-}
-
-
-void addSingle(const string& s, vector<uint32_t>& points)
-{
-    points.push_back(parseInt(s));
-}
-
-
-void addRange(const string& begin, const string& end,
-    vector<uint32_t>& points)
-{
-    uint32_t low = parseInt(begin);
-    uint32_t high = parseInt(end);
-    if (low > high)
-        throw pdal_error(string("Range invalid: ") + begin + "-" + end);
-    while (low <= high)
-        points.push_back(low++);
-}
-
-
-vector<uint32_t> getListOfPoints(std::string p)
-{
-    vector<uint32_t> output;
-
-    //Remove whitespace from string with awful remove/erase idiom.
-    p.erase(remove_if(p.begin(), p.end(), ::isspace), p.end());
-
-    vector<string> ranges = tokenize(p, ',');
-    for (string s : ranges)
-    {
-        vector<string> limits = tokenize(s, '-');
-        if (limits.size() == 1)
-            addSingle(limits[0], output);
-        else if (limits.size() == 2)
-            addRange(limits[0], limits[1], output);
-        else
-            throw pdal_error(string("Invalid point range: ") + s);
-    }
-    return output;
-}
-
-} //namespace
-
-
-void ViewKernel::addSwitches(ProgramArgs& args)
-{
-    args.add("input,i", "Input filename", m_inputFile).setPositional();
-    args.add("point,p", "Point to dump", m_pointIndexes);
-}
-
-
-int ViewKernel::execute()
-{
-    Stage& readerStage(Kernel::makeReader(m_inputFile, ""));
-
-    PointTable table;
-    readerStage.prepare(table);
-    PointViewSet viewSetIn = readerStage.execute(table);
-
-    PointViewPtr buf = *viewSetIn.begin();
-    if (m_pointIndexes.size())
-    {
-        PointViewPtr outbuf = buf->makeNew();
-
-        std::vector<uint32_t> points = getListOfPoints(m_pointIndexes);
-        for (size_t i = 0; i < points.size(); ++i)
-        {
-            PointId id = (PointId)points[i];
-            if (id < buf->size())
-                outbuf->appendPoint(*buf, id);
-        }
-
-        visualize(outbuf);
-    }
-    else
-    {
-        visualize(buf);
-    }
-    return 0;
-}
-
-} // pdal
diff --git a/plugins/pcl/kernel/ViewKernel.hpp b/plugins/pcl/kernel/ViewKernel.hpp
deleted file mode 100644
index c59e04c..0000000
--- a/plugins/pcl/kernel/ViewKernel.hpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2013, Howard Butler (hobu.inc at gmail.com)
-* Copyright (c) 2014, Brad Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/Kernel.hpp>
-#include <pdal/pdal_export.hpp>
-#include <pdal/util/FileUtils.hpp>
-
-namespace pdal
-{
-
-class PDAL_DLL ViewKernel : public Kernel
-{
-public:
-    static void * create();
-    static int32_t destroy(void *);
-    std::string getName() const;
-    int execute();
-
-private:
-    virtual void addSwitches(ProgramArgs& args);
-
-    std::string m_inputFile;
-    std::string m_pointIndexes;
-};
-
-} // pdal
diff --git a/plugins/pcl/pipeline/PCLPipeline.h b/plugins/pcl/pipeline/PCLPipeline.h
index c98e6ef..150548f 100644
--- a/plugins/pcl/pipeline/PCLPipeline.h
+++ b/plugins/pcl/pipeline/PCLPipeline.h
@@ -4,7 +4,7 @@
  *  Point Cloud Library (PCL) - www.pointclouds.org
  *  Copyright (c) 2009-2012, Willow Garage, Inc.
  *  Copyright (c) 2012-, Open Perception, Inc.
- *  Copyright (c) 2014, RadiantBlue Technologies, Inc.
+ *  Copyright (c) 2014-2016, RadiantBlue Technologies, Inc.
  *
  *  All rights reserved.
  *
@@ -43,11 +43,12 @@
 
 #include <memory>
 
-#include <boost/property_tree/ptree.hpp>
-#include <boost/property_tree/json_parser.hpp>
+#include <pdal/pdal_types.hpp>
 
 #include <pcl/filters/filter_indices.h>
 
+#include <json/json.h>
+
 
 // Pipeline needs to be in the `pcl` namespace in order for the instantiation
 // macros to work
@@ -79,8 +80,8 @@ protected:
 
 public:
 
-    typedef boost::shared_ptr< Pipeline<PointT> > Ptr;
-    typedef boost::shared_ptr< const Pipeline<PointT> > ConstPtr;
+    typedef std::shared_ptr< Pipeline<PointT> > Ptr;
+    typedef std::shared_ptr< const Pipeline<PointT> > ConstPtr;
 
 
     /** \brief Constructor.
@@ -99,18 +100,35 @@ public:
     inline void
     setFilename(const std::string &filename)
     {
+        std::ifstream f(filename.c_str());
+
         filename_set_ = true;
-        boost::property_tree::read_json(filename.c_str(), pt_);
+        Json::Reader jsonReader;
+        if (!jsonReader.parse(f, pt_))
+        {
+            std::string err = "PCL pipeline: Unable to parse pipeline file:\n";
+            err += jsonReader.getFormattedErrorMessages();
+            throw pdal::pdal_error(err);
+        }
         json_set_ = true;
     }
 
+    /** \brief Provide the PCL pipeline as a JSON string.
+      * \param[in] methods the PCL JSON pipeline as a string.
+      */
     inline void
-    setJSON(const std::string &json)
+    setMethods(const std::string &methods)
     {
         if (filename_set_)
-            PCL_WARN("Filename and JSON string have both been specified. Using only the string!\n");
-        std::stringstream ss(json);
-        boost::property_tree::read_json(ss, pt_);
+            PCL_WARN("Filename and methods have both been specified. Using only methods!\n");
+        std::stringstream ss(methods);
+        Json::Reader jsonReader;
+        if (!jsonReader.parse(ss, pt_))
+        {
+            std::string err = "PCL pipeline: Unable to parse pipeline methods:\n";
+            err += jsonReader.getFormattedErrorMessages();
+            throw pdal::pdal_error(err);
+        }
         json_set_ = true;
     }
 
@@ -127,8 +145,6 @@ public:
         z_offset_ = z;
     }
 
-    void dumper(PointCloud &output);
-
 protected:
     using PCLBase<PointT>::input_;
     using PCLBase<PointT>::indices_;
@@ -147,7 +163,7 @@ protected:
      *  \param[in] value_type The parameters.
      */
     void
-    applyPassThrough(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt);
+    applyPassThrough(PointCloudConstPtr cloud, PointCloud &output, Json::Value const& vt);
 
     /** \brief Apply statistical outlier removal filter to input cloud, using parameters specified in property tree.
      *  \param[in] cloud The input point cloud.
@@ -155,7 +171,7 @@ protected:
      *  \param[in] value_type The parameters.
      */
     void
-    applyStatisticalOutlierRemoval(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt);
+    applyStatisticalOutlierRemoval(PointCloudConstPtr cloud, PointCloud &output, Json::Value const& vt);
 
     /** \brief Apply radius outlier removal filter to input cloud, using parameters specified in property tree.
      *  \param[in] cloud The input point cloud.
@@ -163,7 +179,7 @@ protected:
      *  \param[in] value_type The parameters.
      */
     void
-    applyRadiusOutlierRemoval(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt);
+    applyRadiusOutlierRemoval(PointCloudConstPtr cloud, PointCloud &output, Json::Value const& vt);
 
     /** \brief Apply voxel grid filter to input cloud, using parameters specified in property tree.
      *  \param[in] cloud The input point cloud.
@@ -171,7 +187,7 @@ protected:
      *  \param[in] value_type The parameters.
      */
     void
-    applyVoxelGrid(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt);
+    applyVoxelGrid(PointCloudConstPtr cloud, PointCloud &output, Json::Value const& vt);
 
     /** \brief Apply grid minimum filter to input cloud, using parameters specified in property tree.
       *  \param[in] cloud The input point cloud.
@@ -179,7 +195,7 @@ protected:
       *  \param[in] value_type The parameters.
       */
     void
-    applyGridMinimum(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt);
+    applyGridMinimum(PointCloudConstPtr cloud, PointCloud &output, Json::Value const& vt);
 
     /** \brief Apply approximate progressive morphological filter to input cloud, using parameters specified in property tree.
      *  \param[in] cloud The input point cloud.
@@ -187,7 +203,7 @@ protected:
      *  \param[in] value_type The parameters.
      */
     void
-    applyApproximateProgressiveMorphologicalFilter(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt);
+    applyApproximateProgressiveMorphologicalFilter(PointCloudConstPtr cloud, PointCloud &output, Json::Value const& vt);
 
     /** \brief Apply progressive morphological filter to input cloud, using parameters specified in property tree.
      *  \param[in] cloud The input point cloud.
@@ -195,45 +211,12 @@ protected:
      *  \param[in] value_type The parameters.
      */
     void
-    applyProgressiveMorphologicalFilter(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt);
-
-    /** \brief Apply normal estimation to input cloud, using parameters specified in property tree.
-     *  \param[in] cloud The input point cloud.
-     *  \param[out] output The resultant point cloud.
-     *  \param[in] value_type The parameters.
-     */
-    //void
-    //applyNormalEstimation(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt);
-
-    /** \brief Apply conditional removal filter to input cloud, using parameters specified in property tree.
-     *  \param[in] cloud The input point cloud.
-     *  \param[out] output The resultant point cloud.
-     *  \param[in] value_type The parameters.
-     */
-    //void
-    //applyConditionalRemoval(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt);
-
-    /** \brief Apply moving least squares to input cloud, using parameters specified in property tree.
-     *  \param[in] cloud The input point cloud.
-     *  \param[out] output The resultant point cloud.
-     *  \param[in] value_type The parameters.
-     */
-    void
-    applyMovingLeastSquares(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt);
-
-
-    /** \brief Generate tile indices for input point cloud at the given resolution.
-     *  \param[in] cloud The input point cloud.
-     *  \param[in] resolution The tile size (resolution).
-     *  \param[out] tile_indices Vector of tile indices for each point in the cloud.
-     */
-    void
-    generateTileIndices(PointCloudConstPtr cloud, const float &resolution, std::vector<PointIndices> &tile_indices);
+    applyProgressiveMorphologicalFilter(PointCloudConstPtr cloud, PointCloud &output, Json::Value const& vt);
 
 private:
     bool filename_set_, json_set_;
 
-    boost::property_tree::ptree pt_;
+    Json::Value pt_;
 
     /** \brief The offsets to the data in the x, y, and z dimension. */
     double x_offset_, y_offset_, z_offset_;
diff --git a/plugins/pcl/pipeline/PCLPipeline.hpp b/plugins/pcl/pipeline/PCLPipeline.hpp
index caf067a..20932ef 100644
--- a/plugins/pcl/pipeline/PCLPipeline.hpp
+++ b/plugins/pcl/pipeline/PCLPipeline.hpp
@@ -4,7 +4,7 @@
  *  Point Cloud Library (PCL) - www.pointclouds.org
  *  Copyright (c) 2009-2012, Willow Garage, Inc.
  *  Copyright (c) 2012-, Open Perception, Inc.
- *  Copyright (c) 2014, RadiantBlue Technologies, Inc.
+ *  Copyright (c) 2014-2016, RadiantBlue Technologies, Inc.
  *
  *  All rights reserved.
  *
@@ -43,11 +43,6 @@
 
 #include <exception>
 
-#include <boost/algorithm/string.hpp>
-#include <boost/property_tree/ptree.hpp>
-#include <boost/property_tree/json_parser.hpp>
-#include <boost/foreach.hpp>
-
 #include <pcl/for_each_type.h>
 #include <pcl/point_traits.h>
 #include <pcl/common/common.h>
@@ -66,201 +61,56 @@
 
 #include "PCLPipeline.h"
 
+#include <pdal/util/Utils.hpp>
 
-namespace pdal
-{
-namespace pclsupport
-{
-
+#include <json/json.h>
 
-struct tile_point_idx
-{
-    unsigned int tile_idx;
-    unsigned int point_idx;
-
-    tile_point_idx(unsigned int tile_idx_, unsigned int point_idx_) : tile_idx(tile_idx_), point_idx(point_idx_) {}
-    bool operator < (const tile_point_idx &p) const
-    {
-        return (tile_idx < p.tile_idx);
-    }
-};
-
-
-} // namespace pclsupport
-} // namespace pdal
-
-
-template <typename PointT> void
-pcl::Pipeline<PointT>::dumper(PointCloud &cloud)
-{
-    for (size_t i = 0; i < cloud.points.size(); ++i)
-    {
-        if (pcl::traits::has_xyz<PointT>::value)
-        {
-            std::cout << cloud.points[i].x << " "
-                      << cloud.points[i].y << " "
-                      << cloud.points[i].z << " ";
-        }
-        //if (pcl::traits::has_normal<PointT>::value)
-        //{
-        //    std::cout << cloud.points[i].normal[0] << " "
-        //              << cloud.points[i].normal[1] << " "
-        //              << cloud.points[i].normal[2] << " ";
-        //}
-        std::cout << std::endl;
-    }
-}
+using namespace pdal;
 
 
 template <typename PointT> void
-pcl::Pipeline<PointT>::generateTileIndices(PointCloudConstPtr cloud, const float& resolution, std::vector<PointIndices> &tile_indices)
-{
-    Eigen::Vector4f leaf_size;
-    Eigen::Array4f inverse_leaf_size;
-    Eigen::Vector4i min_b, max_b, div_b, divb_mul;
-
-    leaf_size[0] = resolution;
-    leaf_size[1] = resolution;
-    leaf_size[2] = resolution;
-    leaf_size[3] = resolution;
-
-    inverse_leaf_size = Eigen::Array4f::Ones() / leaf_size.array();
-
-    PCL_DEBUG("experimental, tiling with leaf size %f\n", resolution);
-
-    Eigen::Vector4f min_p, max_p;
-    // Get the minimum and maximum dimensions
-    pcl::getMinMax3D<PointT> (*cloud, *indices_, min_p, max_p);
-
-    // Check that the leaf size is not too small, given the size of the data
-    int64_t dx = static_cast<int64_t>((max_p[0] - min_p[0]) * inverse_leaf_size[0])+1;
-    int64_t dy = static_cast<int64_t>((max_p[1] - min_p[1]) * inverse_leaf_size[1])+1;
-
-    if ((dx*dy) > static_cast<int64_t>(std::numeric_limits<int32_t>::max()))
-    {
-        PCL_WARN("[pcl::%s::applyFilter] Leaf size is too small for the input dataset. Integer indices would overflow.", getClassName().c_str());
-        // is this really relevant anymore? don't think so, but maybe something like it
-        //output = *input_;
-        return;
-    }
-
-    // Compute the minimum and maximum bounding box values
-    min_b[0] = static_cast<int>(floor(min_p[0] * inverse_leaf_size[0]));
-    max_b[0] = static_cast<int>(floor(max_p[0] * inverse_leaf_size[0]));
-    min_b[1] = static_cast<int>(floor(min_p[1] * inverse_leaf_size[1]));
-    max_b[1] = static_cast<int>(floor(max_p[1] * inverse_leaf_size[1]));
-
-    // Compute the number of divisions needed along all axis
-    div_b = max_b - min_b + Eigen::Vector4i::Ones();
-    div_b[3] = 0;
-
-    PCL_DEBUG("%d and %d divisions in x and y\n", div_b[0], div_b[1]);
-
-    // Set up the division multiplier
-    divb_mul = Eigen::Vector4i(1, div_b[0], div_b[0] * div_b[1], 0);
-
-    std::vector<pdal::pclsupport::tile_point_idx> index_vector;
-    index_vector.reserve(indices_->size());
-
-    // First pass: go over all points and insert them into the index_vector vector
-    // with calculated idx. Points with the same idx value will contribute to the
-    // same point of resulting CloudPoint
-    for (std::vector<int>::const_iterator it = indices_->begin(); it != indices_->end(); ++it)
-    {
-        if (!input_->is_dense)
-            // Check if the point is invalid
-            if (!pcl_isfinite(input_->points[*it].x) ||
-                    !pcl_isfinite(input_->points[*it].y) ||
-                    !pcl_isfinite(input_->points[*it].z))
-                continue;
-
-        int ijk0 = static_cast<int>(floor(input_->points[*it].x * inverse_leaf_size[0]) - static_cast<float>(min_b[0]));
-        int ijk1 = static_cast<int>(floor(input_->points[*it].y * inverse_leaf_size[1]) - static_cast<float>(min_b[1]));
-
-        // Compute the centroid leaf index
-        int idx = ijk0 * divb_mul[0] + ijk1 * divb_mul[1];
-        index_vector.push_back(pdal::pclsupport::tile_point_idx(static_cast<unsigned int>(idx), *it));
-    }
-
-    // Second pass: sort the index_vector vector using value representing target cell as index
-    // in effect all points belonging to the same output cell will be next to each other
-    std::sort(index_vector.begin(), index_vector.end(), std::less<pdal::pclsupport::tile_point_idx> ());
-
-    // Third pass: count output cells
-    // we need to skip all the same, adjacenent idx values
-    unsigned int index = 0;
-    // first_and_last_indices_vector[i] represents the index in index_vector of the first point in
-    // index_vector belonging to the voxel which corresponds to the i-th output point,
-    // and of the first point not belonging to.
-    std::vector<std::pair<unsigned int, unsigned int> > first_and_last_indices_vector;
-    // Worst case size
-    first_and_last_indices_vector.reserve(index_vector.size());
-    while (index < index_vector.size())
-    {
-        unsigned int i = index + 1;
-        while (i < index_vector.size() && index_vector[i].tile_idx == index_vector[index].tile_idx)
-            ++i;
-        first_and_last_indices_vector.push_back(std::pair<unsigned int, unsigned int> (index, i));
-        index = i;
-    }
-
-    tile_indices.resize(first_and_last_indices_vector.size());
-
-    for (unsigned int cp = 0; cp < first_and_last_indices_vector.size(); ++cp)
-    {
-        // calculate centroid - sum values from all input points, that have the same idx value in index_vector array
-        unsigned int first_index = first_and_last_indices_vector[cp].first;
-        unsigned int last_index = first_and_last_indices_vector[cp].second;
-
-        tile_indices[cp].indices.resize(last_index - first_index);
-
-        PCL_DEBUG("tile %d will have %d points\n", cp, last_index - first_index);
-
-        index = 0;
-
-        for (unsigned int i = first_index + 1; i < last_index; ++i)
-        {
-            tile_indices[cp].indices[index] = index_vector[i].point_idx;
-            ++index;
-        }
-    }
-
-    return;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-template <typename PointT> void
-pcl::Pipeline<PointT>::applyPassThrough(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt)
+pcl::Pipeline<PointT>::applyPassThrough(PointCloudConstPtr cloud,
+    PointCloud &output, Json::Value const& vt)
 {
     // initial setup
     pcl::PassThrough<PointT> pass;
     pass.setInputCloud(cloud);
 
     // parse params
-    std::string field = vt.second.get<std::string> ("setFilterFieldName");
-    float m1 = vt.second.get<float> ("setFilterLimits.min", -std::numeric_limits<float>::max());
-    float m2 = vt.second.get<float> ("setFilterLimits.max", std::numeric_limits<float>::max());
+    std::string field = vt["setFilterFieldName"].asString();
+    float m1 = vt["setFilterLimits"]
+        .get("min", -std::numeric_limits<float>::max())
+        .asFloat();
+    float m2 = vt["setFilterLimits"]
+        .get("max", std::numeric_limits<float>::max())
+        .asFloat();
 
     // summarize settings
-    PCL_DEBUG("      Field name: %s\n", field.c_str());
-    PCL_DEBUG("      Limits: %f, %f\n", m1, m2);
+    PCL_DEBUG("\tField name: %s\n", field.c_str());
+    PCL_DEBUG("\tLimits: %f, %f\n", m1, m2);
 
     if (field.compare("x") == 0)
     {
-        if (m1 != -std::numeric_limits<float>::max()) m1 -= x_offset_;
-        if (m2 != std::numeric_limits<float>::max()) m2 -= x_offset_;
+        if (m1 != -std::numeric_limits<float>::max())
+            m1 -= x_offset_;
+        if (m2 != std::numeric_limits<float>::max())
+            m2 -= x_offset_;
     }
 
     if (field.compare("y") == 0)
     {
-        if (m1 != -std::numeric_limits<float>::max()) m1 -= y_offset_;
-        if (m2 != std::numeric_limits<float>::max()) m2 -= y_offset_;
+        if (m1 != -std::numeric_limits<float>::max())
+            m1 -= y_offset_;
+        if (m2 != std::numeric_limits<float>::max())
+            m2 -= y_offset_;
     }
 
     if (field.compare("z") == 0)
     {
-        if (m1 != -std::numeric_limits<float>::max()) m1 -= z_offset_;
-        if (m2 != std::numeric_limits<float>::max()) m2 -= z_offset_;
+        if (m1 != -std::numeric_limits<float>::max())
+            m1 -= z_offset_;
+        if (m2 != std::numeric_limits<float>::max())
+            m2 -= z_offset_;
     }
 
     // set params and apply filter
@@ -268,76 +118,79 @@ pcl::Pipeline<PointT>::applyPassThrough(PointCloudConstPtr cloud, PointCloud &ou
     pass.setFilterLimits(m1, m2);
     pass.filter(output);
 
-    PCL_DEBUG("%d filtered to %d in passthrough\n", cloud->points.size(), output.points.size());
+    PCL_DEBUG("\t%d filtered to %d in passthrough\n", cloud->points.size(),
+        output.points.size());
 
     return;
 }
 
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 template <typename PointT> void
-pcl::Pipeline<PointT>::applyStatisticalOutlierRemoval(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt)
+pcl::Pipeline<PointT>::applyStatisticalOutlierRemoval(PointCloudConstPtr cloud,
+    PointCloud &output, Json::Value const& vt)
 {
     // initial setup
     pcl::StatisticalOutlierRemoval<PointT> sor;
     sor.setInputCloud(cloud);
 
     // parse params
-    int nr_k = vt.second.get<int> ("setMeanK", 2);
-    double stddev_mult = vt.second.get<double> ("setStddevMulThresh", 0.0);
+    int nr_k = vt.get("setMeanK", 2).asInt();
+    double stddev_mult = vt.get("setStddevMulThresh", 0.0).asDouble();
 
     // summarize settings
-    PCL_DEBUG("      %d neighbors and %f multiplier\n", nr_k, stddev_mult);
+    PCL_DEBUG("\t%d neighbors and %f multiplier\n", nr_k, stddev_mult);
 
     // set params and apply filter
     sor.setMeanK(nr_k);
     sor.setStddevMulThresh(stddev_mult);
     sor.filter(output);
 
-    PCL_DEBUG("      %d points filtered to %d following outlier removal\n", cloud->points.size(), output.points.size());
+    PCL_DEBUG("\t%d points filtered to %d following outlier removal\n",
+        cloud->points.size(), output.points.size());
 
     return;
 }
 
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 template <typename PointT> void
-pcl::Pipeline<PointT>::applyRadiusOutlierRemoval(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt)
+pcl::Pipeline<PointT>::applyRadiusOutlierRemoval(PointCloudConstPtr cloud,
+    PointCloud &output, Json::Value const& vt)
 {
     // initial setup
     pcl::RadiusOutlierRemoval<PointT> ror;
     ror.setInputCloud(cloud);
 
     // parse params
-    int min_neighbors = vt.second.get<int> ("setMinNeighborsInRadius", 2);
-    double radius = vt.second.get<double> ("setRadiusSearch", 1.0);
+    int min_neighbors = vt.get("setMinNeighborsInRadius", 2).asInt();
+    double radius = vt.get("setRadiusSearch", 1.0).asDouble();
 
     // summarize settings
-    PCL_DEBUG("      %d neighbors and %f radius\n", min_neighbors, radius);
+    PCL_DEBUG("\t%d neighbors and %f radius\n", min_neighbors, radius);
 
     // set params and apply filter
     ror.setMinNeighborsInRadius(min_neighbors);
     ror.setRadiusSearch(radius);
     ror.filter(output);
 
-    PCL_DEBUG("      %d points filtered to %d following outlier removal\n", cloud->points.size(), output.points.size());
+    PCL_DEBUG("\t%d points filtered to %d following outlier removal\n",
+        cloud->points.size(), output.points.size());
 
     return;
 }
 
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 template <typename PointT> void
-pcl::Pipeline<PointT>::applyVoxelGrid(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt)
+pcl::Pipeline<PointT>::applyVoxelGrid(PointCloudConstPtr cloud,
+    PointCloud &output, Json::Value const& vt)
 {
     // initial setup
     pcl::VoxelGrid<PointT> vg;
     vg.setInputCloud(cloud);
 
     // parse params
-    float x = vt.second.get<float> ("setLeafSize.x", 1.0);
-    float y = vt.second.get<float> ("setLeafSize.y", 1.0);
-    float z = vt.second.get<float> ("setLeafSize.z", 1.0);
+    float x = vt["setLeafSize"].get("x", 1.0).asFloat();
+    float y = vt["setLeafSize"].get("y", 1.0).asFloat();
+    float z = vt["setLeafSize"].get("z", 1.0).asFloat();
 
     // summarize settings
-    PCL_DEBUG("      leaf size: %f, %f, %f\n", x, y, z);
+    PCL_DEBUG("\tleaf size: %f, %f, %f\n", x, y, z);
 
     // set params and apply filter
     vg.setLeafSize(x, y, z);
@@ -346,15 +199,15 @@ pcl::Pipeline<PointT>::applyVoxelGrid(PointCloudConstPtr cloud, PointCloud &outp
     return;
 }
 
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 template <typename PointT> void
-pcl::Pipeline<PointT>::applyGridMinimum(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt)
+pcl::Pipeline<PointT>::applyGridMinimum(PointCloudConstPtr cloud,
+    PointCloud &output, Json::Value const& vt)
 {
     // parse params
-    float r = vt.second.get<float> ("setResolution", 1.0);
+    float r = vt.get("setResolution", 1.0).asFloat();
 
     // summarize settings
-    PCL_DEBUG("      resolution: %f\n", r);
+    PCL_DEBUG("\tresolution: %f\n", r);
 
     // initial setup
     pcl::GridMinimum<PointT> vgm(r);
@@ -366,31 +219,31 @@ pcl::Pipeline<PointT>::applyGridMinimum(PointCloudConstPtr cloud, PointCloud &ou
     return;
 }
 
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 template <typename PointT> void
-pcl::Pipeline<PointT>::applyApproximateProgressiveMorphologicalFilter(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt)
+pcl::Pipeline<PointT>::applyApproximateProgressiveMorphologicalFilter(
+    PointCloudConstPtr cloud, PointCloud &output, Json::Value const& vt)
 {
     pcl::ApproximateProgressiveMorphologicalFilter<PointT> pmf;
 
     // parse params
-    int w = vt.second.get<int> ("setMaxWindowSize", 33);
-    float s = vt.second.get<float> ("setSlope", 1.0);
-    float md = vt.second.get<float> ("setMaxDistance", 2.5);
-    float id = vt.second.get<float> ("setInitialDistance", 0.15);
-    float c = vt.second.get<float> ("setCellSize", 1.0);
-    float b = vt.second.get<float> ("setBase", 2.0);
-    bool e = vt.second.get<bool> ("setExponential", true);
-    bool n = vt.second.get<bool> ("setNegative", false);
+    int w = vt.get("setMaxWindowSize", 33).asInt();
+    float s = vt.get("setSlope", 1.0).asFloat();
+    float md = vt.get("setMaxDistance", 2.5).asFloat();
+    float id = vt.get("setInitialDistance", 0.15).asFloat();
+    float c = vt.get("setCellSize", 1.0).asFloat();
+    float b = vt.get("setBase", 2.0).asFloat();
+    bool e = vt.get("setExponential", true).asBool();
+    bool n = vt.get("setNegative", false).asBool();
 
     // summarize settings
-    PCL_DEBUG("      max window size: %d\n", w);
-    PCL_DEBUG("      slope: %f\n", s);
-    PCL_DEBUG("      max distance: %f\n", md);
-    PCL_DEBUG("      initial distance: %f\n", id);
-    PCL_DEBUG("      cell size: %f\n", c);
-    PCL_DEBUG("      base: %f\n", b);
-    PCL_DEBUG("      exponential: %s\n", e?"true":"false");
-    PCL_DEBUG("      negative: %s\n", n?"true":"false");
+    PCL_DEBUG("\tmax window size: %d\n", w);
+    PCL_DEBUG("\tslope: %f\n", s);
+    PCL_DEBUG("\tmax distance: %f\n", md);
+    PCL_DEBUG("\tinitial distance: %f\n", id);
+    PCL_DEBUG("\tcell size: %f\n", c);
+    PCL_DEBUG("\tbase: %f\n", b);
+    PCL_DEBUG("\texponential: %s\n", e?"true":"false");
+    PCL_DEBUG("\tnegative: %s\n", n?"true":"false");
 
     pmf.setInputCloud(cloud);
     pmf.setMaxWindowSize(w);
@@ -410,36 +263,37 @@ pcl::Pipeline<PointT>::applyApproximateProgressiveMorphologicalFilter(PointCloud
     extract.setNegative(n);
     extract.filter(output);
 
-    PCL_DEBUG("      %d points filtered to %d following approximate progressive morphological filter\n", cloud->points.size(), output.points.size());
+    PCL_DEBUG("\t%d points filtered to %d following approximate PMF\n",
+        cloud->points.size(), output.points.size());
 
     return;
 }
 
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 template <typename PointT> void
-pcl::Pipeline<PointT>::applyProgressiveMorphologicalFilter(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt)
+pcl::Pipeline<PointT>::applyProgressiveMorphologicalFilter(
+    PointCloudConstPtr cloud, PointCloud &output, Json::Value const& vt)
 {
     pcl::ProgressiveMorphologicalFilter<PointT> pmf;
 
     // parse params
-    int w = vt.second.get<int> ("setMaxWindowSize", 33);
-    float s = vt.second.get<float> ("setSlope", 1.0);
-    float md = vt.second.get<float> ("setMaxDistance", 2.5);
-    float id = vt.second.get<float> ("setInitialDistance", 0.15);
-    float c = vt.second.get<float> ("setCellSize", 1.0);
-    float b = vt.second.get<float> ("setBase", 2.0);
-    bool e = vt.second.get<bool> ("setExponential", true);
-    bool n = vt.second.get<bool> ("setNegative", false);
+    int w = vt.get("setMaxWindowSize", 33).asInt();
+    float s = vt.get("setSlope", 1.0).asFloat();
+    float md = vt.get("setMaxDistance", 2.5).asFloat();
+    float id = vt.get("setInitialDistance", 0.15).asFloat();
+    float c = vt.get("setCellSize", 1.0).asFloat();
+    float b = vt.get("setBase", 2.0).asFloat();
+    bool e = vt.get("setExponential", true).asBool();
+    bool n = vt.get("setNegative", false).asBool();
 
     // summarize settings
-    PCL_DEBUG("      max window size: %d\n", w);
-    PCL_DEBUG("      slope: %f\n", s);
-    PCL_DEBUG("      max distance: %f\n", md);
-    PCL_DEBUG("      initial distance: %f\n", id);
-    PCL_DEBUG("      cell size: %f\n", c);
-    PCL_DEBUG("      base: %f\n", b);
-    PCL_DEBUG("      exponential: %s\n", e?"true":"false");
-    PCL_DEBUG("      negative: %s\n", n?"true":"false");
+    PCL_DEBUG("\tmax window size: %d\n", w);
+    PCL_DEBUG("\tslope: %f\n", s);
+    PCL_DEBUG("\tmax distance: %f\n", md);
+    PCL_DEBUG("\tinitial distance: %f\n", id);
+    PCL_DEBUG("\tcell size: %f\n", c);
+    PCL_DEBUG("\tbase: %f\n", b);
+    PCL_DEBUG("\texponential: %s\n", e?"true":"false");
+    PCL_DEBUG("\tnegative: %s\n", n?"true":"false");
 
     pmf.setInputCloud(cloud);
     pmf.setMaxWindowSize(w);
@@ -459,134 +313,20 @@ pcl::Pipeline<PointT>::applyProgressiveMorphologicalFilter(PointCloudConstPtr cl
     extract.setNegative(n);
     extract.filter(output);
 
-    PCL_DEBUG("      %d points filtered to %d following progressive morphological filter\n", cloud->points.size(), output.points.size());
-
-    return;
-}
-
-/*
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-template <typename PointT> void
-pcl::Pipeline<PointT>::applyNormalEstimation(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt)
-{
-    if (pcl::traits::has_normal<PointT>::value && pcl::traits::has_curvature<PointT>::value)
-    {
-        // parse params
-        float r = vt.second.get<float> ("setRadiusSearch", 1.0);
-        float k = vt.second.get<float> ("setKSearch", 0);
-
-        PCL_DEBUG("      radius: %f\n", r);
-
-        pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
-
-        typename pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT> ());
-        pcl::NormalEstimation<PointT, pcl::Normal> ne;
-        ne.setInputCloud(cloud);
-        //ne.setSearchSurface (cloud); // or maybe not
-        ne.setViewPoint(0.0f, 0.0f, std::numeric_limits<float>::max());
-        ne.setSearchMethod(tree);
-        ne.setRadiusSearch(r);
-        ne.setKSearch(k);
-        ne.compute(*normals);
-
-        pcl::concatenateFields(*cloud, *normals, output);
-    }
-    else
-    {
-        PCL_ERROR("Requested point type does not support NormalEstimation...\n");
-    }
-
-    return;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-template <typename PointT> void
-pcl::Pipeline<PointT>::applyConditionalRemoval(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt)
-{
-    typename pcl::ConditionAnd<PointT>::Ptr cond(new pcl::ConditionAnd<PointT> ());
-
-    if (pcl::traits::has_normal<PointT>::value)
-    {
-        // parse params
-        float m1 = vt.second.get<float> ("normalZ.min", 0);
-        float m2 = vt.second.get<float> ("normalZ.max", std::numeric_limits<float>::max());
-
-        // summarize settings
-        PCL_DEBUG("      Limits: %f, %f\n", m1, m2);
-
-        typedef typename pcl::traits::fieldList<PointT>::type FieldList;
-        float min_normal_z = std::numeric_limits<float>::max();
-        float max_normal_z = -std::numeric_limits<float>::max();
-        for (size_t ii = 0; ii < cloud->points.size(); ++ii)
-        {
-            bool has_normal_z = false;
-            float normal_z_val = 0.0f;
-            pcl::for_each_type<FieldList> (pcl::CopyIfFieldExists<PointT, float> (cloud->points[ii], "normal_z", has_normal_z, normal_z_val));
-            if (has_normal_z)
-            {
-                if (normal_z_val < min_normal_z) min_normal_z = normal_z_val;
-                if (normal_z_val > max_normal_z) max_normal_z = normal_z_val;
-            }
-        }
-        PCL_DEBUG("min/max normal_z [%f, %f]\n", min_normal_z, max_normal_z);
-
-        cond->addComparison(typename pcl::FieldComparison<PointT>::ConstPtr(new pcl::FieldComparison<PointT> ("normal_z", pcl::ComparisonOps::GT, m1)));
-        cond->addComparison(typename pcl::FieldComparison<PointT>::ConstPtr(new pcl::FieldComparison<PointT> ("normal_z", pcl::ComparisonOps::LT, m2)));
-    }
-    else
-    {
-        PCL_WARN("Requested point type does not support ConditionalRemoval by normals...\n");
-    }
-
-    pcl::ConditionalRemoval<PointT> condrem;
-    condrem.setCondition(cond);
-    condrem.setInputCloud(cloud);
-    condrem.filter(output);
+    PCL_DEBUG("\t%d points filtered to %d following PMF\n",
+        cloud->points.size(), output.points.size());
 
     return;
 }
-*/
 
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-template <typename PointT> void
-pcl::Pipeline<PointT>::applyMovingLeastSquares(PointCloudConstPtr cloud, PointCloud &output, boost::property_tree::ptree::value_type &vt)
-{
-        // parse params
-//        float r = vt.second.get<float> ("setRadiusSearch", 1.0);
-//        float k = vt.second.get<float> ("setKSearch", 0);
-
-//        PCL_DEBUG("      radius: %f\n", r);
-
-//        typename pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT> ());
-        pcl::MovingLeastSquares<PointT, PointT> mls;
-        mls.setInputCloud(cloud);
-        //mls.setSearchMethod(tree);
-        mls.setSearchRadius(1);
-        mls.setPolynomialFit(true);
-        mls.setPolynomialOrder(2);
-        mls.setUpsamplingMethod(pcl::MovingLeastSquares<PointT, PointT>::NONE);
-        //mls.setDilationIterations(1);
-        //mls.setDilationVoxelSize(0.5);
-        //mls.setPointDensity(20);
-        //mls.setUpsamplingRadius(2);
-        //mls.setUpsamplingStepSize(1);
-        //PCL_DEBUG("%f radius \n", mls.getUpsamplingRadius());
-        //PCL_DEBUG("%f step\n", mls.getUpsamplingStepSize());
-        mls.process(output);
-
-    PCL_DEBUG("%d filtered to %d in moving least squares\n", cloud->points.size(), output.points.size());
-
-    return;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 template <typename PointT> void
 pcl::Pipeline<PointT>::applyFilter(PointCloud &output)
 {
     // Has the input dataset been set already?
     if (!input_)
     {
-        PCL_WARN("[pcl::%s::applyFilter] No input dataset given!\n", getClassName().c_str());
+        PCL_WARN("[pcl::%s::applyFilter] No input dataset given!\n",
+            getClassName().c_str());
         output.width = output.height = 0;
         output.points.clear();
         return;
@@ -595,7 +335,8 @@ pcl::Pipeline<PointT>::applyFilter(PointCloud &output)
     // Do we have a JSON string to process?
     if (!json_set_)
     {
-        PCL_WARN("[pcl::%s::applyFilter] No input JSON given!\n", getClassName().c_str());
+        PCL_WARN("[pcl::%s::applyFilter] No input JSON given!\n",
+            getClassName().c_str());
         output = *input_;
         return;
     }
@@ -604,89 +345,52 @@ pcl::Pipeline<PointT>::applyFilter(PointCloud &output)
 
     try
     {
-        PCL_DEBUG("\n");
-        PCL_DEBUG("--------------------------------------------------------------------------------\n");
-        PCL_DEBUG("NAME:   %s (%s)\n", pt_.get<std::string> ("pipeline.name","").c_str(), pt_.get<std::string> ("pipeline.version","").c_str());
-        PCL_DEBUG("HELP:   %s\n", pt_.get<std::string> ("pipeline.help","").c_str());
-        PCL_DEBUG("AUTHOR: %s\n", pt_.get<std::string> ("pipeline.author","").c_str());
-        PCL_DEBUG("--------------------------------------------------------------------------------\n");
-
-        // generate tile indices, can use vector of PointIndices (itself a vector of indices belonging to each tile)
-        std::vector<PointIndices> tile_indices;
-        PointIndices foo; // okay, this is totally ugly. i just want to make sure we have a single tile of all indices if no tile_size is specified.
-        foo.indices = *indices_;
-        tile_indices.push_back(foo);
-        float ts = pt_.get<float> ("pipeline.tile_size", 0.0f);
-        if (ts > 0.0f)
-            generateTileIndices(input_, ts, tile_indices);
-
-        // loop over each tile (each PointIndices)
-        #pragma omp parallel for shared(output) num_threads(4)
-        for (size_t tile = 0; tile < tile_indices.size(); ++tile)
-        {
-            typename pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>);
-            typename pcl::PointCloud<PointT>::Ptr cloud_f(new pcl::PointCloud<PointT>);
-
-            // move the copyPointCloud at line 84 here, and only copy the indices belonging to the current tile (and somehow marry that with the *indices_)
-            pcl::copyPointCloud<PointT> (*input_, tile_indices[tile].indices, *cloud);
-
-            PCL_DEBUG("process tile %d through the pipeline\n", tile);
+        typedef pcl::PointCloud<PointT> Cloud;
+        typename Cloud::Ptr cloud(new Cloud);
+        typename Cloud::Ptr cloud_f(new Cloud);
 
-            int step = 1;
+        pcl::copyPointCloud<PointT> (*input_, *cloud);
 
-            BOOST_FOREACH(boost::property_tree::ptree::value_type &vt, pt_.get_child("pipeline.filters"))
+        for (auto const& vt : pt_)
+        {
+            std::string name(vt.get("name", "").asString());
+            
+            using namespace Utils;
+
+            if (iequals(name, "PassThrough"))
+                applyPassThrough(cloud, *cloud_f, vt);
+            else if (iequals(name, "StatisticalOutlierRemoval"))
+                applyStatisticalOutlierRemoval(cloud, *cloud_f, vt);
+            else if (iequals(name, "RadiusOutlierRemoval"))
+                applyRadiusOutlierRemoval(cloud, *cloud_f, vt);
+            else if (iequals(name, "VoxelGrid"))
+                applyVoxelGrid(cloud, *cloud_f, vt);
+            else if (iequals(name, "GridMinimum"))
+                applyGridMinimum(cloud, *cloud_f, vt);
+            else if (iequals(name, "ApproximateProgressiveMorphologicalFilter"))
+                applyApproximateProgressiveMorphologicalFilter(cloud, *cloud_f,
+                    vt);
+            else if (iequals(name, "ProgressiveMorphologicalFilter"))
+                applyProgressiveMorphologicalFilter(cloud, *cloud_f, vt);
+            else
+                PCL_WARN("Requested filter `%s` not implemented! Skipping...\n",
+                    name.c_str());
+
+            cloud.swap(cloud_f);
+
+            if (cloud->points.size() == 0)
             {
-                std::string name = vt.second.get<std::string> ("name", "");
-                std::string help = vt.second.get<std::string> ("help", "");
-
-                PCL_DEBUG("\n");
-                PCL_DEBUG("   Step %d) %s\n", step++, name.c_str());
-                PCL_DEBUG("      %s\n", help.c_str());
-
-                if (boost::iequals(name, "PassThrough"))
-                    applyPassThrough(cloud, *cloud_f, vt);
-                else if (boost::iequals(name, "StatisticalOutlierRemoval"))
-                    applyStatisticalOutlierRemoval(cloud, *cloud_f, vt);
-                else if (boost::iequals(name, "RadiusOutlierRemoval"))
-                    applyRadiusOutlierRemoval(cloud, *cloud_f, vt);
-                else if (boost::iequals(name, "VoxelGrid"))
-                    applyVoxelGrid(cloud, *cloud_f, vt);
-                else if (boost::iequals(name, "GridMinimum"))
-                    applyGridMinimum(cloud, *cloud_f, vt);
-                else if (boost::iequals(name, "ApproximateProgressiveMorphologicalFilter"))
-                    applyApproximateProgressiveMorphologicalFilter(cloud, *cloud_f, vt);
-                else if (boost::iequals(name, "ProgressiveMorphologicalFilter"))
-                    applyProgressiveMorphologicalFilter(cloud, *cloud_f, vt);
-                //else if (boost::iequals(name, "NormalEstimation"))
-                //    applyNormalEstimation(cloud, *cloud_f, vt);
-                //else if (boost::iequals(name, "ConditionalRemoval"))
-                //    applyConditionalRemoval(cloud, *cloud_f, vt);
-                else if (boost::iequals(name, "MovingLeastSquares"))
-                    applyMovingLeastSquares(cloud, *cloud_f, vt);
-                else
-                    PCL_WARN("Requested filter `%s` not implemented! Skipping...\n", name.c_str());
-
-                cloud.swap(cloud_f);
-
-                if (cloud->points.size() == 0)
-                {
-                    PCL_WARN("No points in filtered cloud. Skipping remaining filters...\n");
-                    break;
-                }
+                PCL_WARN("No points in filtered cloud. Aborting!\n");
+                break;
             }
-
-            // after processing each tile, we need to merge them back together into output
-            #pragma omp critical
-            output += *cloud;
-
-            PCL_DEBUG("tile %d filtered to %d points, adding to output, which now has %d points\n", tile, cloud->points.size(), output.points.size());
         }
 
-        PCL_DEBUG("\n");
+        output += *cloud;
     }
     catch (std::exception const& e)
     {
-        PCL_WARN("[pcl::%s::applyFilterIndices] Error parsing JSON and creating pipeline! %s\n", getClassName().c_str(), e.what());
+        PCL_WARN("[pcl::%s] Error parsing JSON and creating pipeline! %s\n",
+            getClassName().c_str(), e.what());
     }
 
     // Resize the output arrays
diff --git a/plugins/pcl/test/PCLBlockFilterTest.cpp b/plugins/pcl/test/PCLBlockFilterTest.cpp
index 26e0947..f18a86e 100644
--- a/plugins/pcl/test/PCLBlockFilterTest.cpp
+++ b/plugins/pcl/test/PCLBlockFilterTest.cpp
@@ -1,5 +1,5 @@
 /******************************************************************************
-* Copyright (c) 2013, Bradley J Chambers (brad.chambers at gmail.com)
+* Copyright (c) 2013-2016, Bradley J Chambers (brad.chambers at gmail.com)
 *
 * All rights reserved.
 *
@@ -35,13 +35,10 @@
 #include <pdal/pdal_test_main.hpp>
 
 #include <pdal/PipelineManager.hpp>
-#include <pdal/PluginManager.hpp>
 #include <pdal/StageFactory.hpp>
 
 #include "Support.hpp"
 
-#undef RUN_SLOW_TESTS
-
 using namespace pdal;
 
 TEST(PCLBlockFilterTest, PCLBlockFilterTest_example_passthrough_json)
@@ -57,22 +54,18 @@ TEST(PCLBlockFilterTest, PCLBlockFilterTest_example_passthrough_json)
     pipeline.execute();
 
     PointViewSet viewSet = pipeline.views();
-    EXPECT_EQ(viewSet.size(), 1u);
+    EXPECT_EQ(1u, viewSet.size());
     PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 795u);
+    EXPECT_EQ(795u, view->size());
 }
 
 static void test_filter(const std::string& jsonFile,
-                        size_t expectedPointCount,
-                        bool useThin=false)
+                        size_t expectedPointCount)
 {
     StageFactory f;
     Options options;
 
-    const std::string& autzenThick = "autzen/autzen-point-format-3.las";
-    const std::string& autzenThin = "autzen/autzen-thin.las";
-    const std::string& autzen = useThin ? autzenThin : autzenThick;
-
+    const std::string& autzen = "autzen/autzen-point-format-3.las";
     options.add("filename", Support::datapath(autzen));
 
     Stage* reader(f.createStage("readers.las"));
@@ -91,9 +84,9 @@ static void test_filter(const std::string& jsonFile,
     pcl_block->prepare(table);
     PointViewSet viewSet = pcl_block->execute(table);
 
-    EXPECT_EQ(viewSet.size(), 1u);
+    EXPECT_EQ(1u, viewSet.size());
     PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), expectedPointCount);
+    EXPECT_EQ(expectedPointCount, view->size());
 }
 
 
@@ -108,62 +101,17 @@ TEST(PCLBlockFilterTest, PCLBlockFilterTest_example_PassThrough_2)
     test_filter("filters/pcl/example_PassThrough_2.json", 50);
 }
 
-TEST(PCLBlockFilterTest, PCLBlockFilterTest_example_PMF_1)
-{
-    test_filter("filters/pcl/example_PMF_1.json", 93);
-}
-
-
-TEST(PCLBlockFilterTest, PCLBlockFilterTest_example_PMF_2)
-{
-    test_filter("filters/pcl/example_PMF_2.json", 94);
-}
-
 //
 // For the filter tests, we attempt to verify that each parameter "works" (as
 // defined by affecting at least one point, using a setting other than the
 // default).
 //
 
-TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_APMF)
-{
-    // BUG: tests still to be developed (seems to either hang or crash inside
-    // Eigen)
-
-    //test_filter("filters/pcl/filter_APMF_1.json", 106, false);
-}
-
-/*
-TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_ConditionalRemoval)
-{
-    // NormalEstimation: KSearch 0, RadiusSearch 50
-    // ConditionalRemoval: (0.0, 0.087156)
-    test_filter("filters/pcl/filter_ConditionalRemoval_1.json", 158, true);
-
-    // NormalEstimation: KSearch 0, RadiusSearch 50
-    // ConditionalRemoval: (0.01, 0.10)
-    test_filter("filters/pcl/filter_ConditionalRemoval_2.json", 160, true);
-}
-*/
-
 TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_GridMinimum)
 {
     test_filter("filters/pcl/filter_GridMinimum.json", 19);
 }
 
-/*
-TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_NormalEstimation)
-{
-    // NormalEstimation: KSearch default (0), RadiusSearch 50
-    test_filter("filters/pcl/filter_NormalEstimation_1.json", 158, true);
-
-    // NormalEstimation: KSearch default (0), RadiusSearch 51
-    test_filter("filters/pcl/filter_NormalEstimation_2.json", 162, true);
-
-    // BUG: need to test KSearch values
-}
-*/
-
 TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_PassThrough)
 {
     // test FilterLimits for Z
@@ -173,61 +121,6 @@ TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_PassThrough)
     test_filter("filters/pcl/filter_PassThrough_2.json", 33);
 }
 
-TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_PMF)
-{
-    // explicitly with all defaults
-    // (with the default autzen file, this isn't a meaningful test: it will just
-    // verify the filter is minimally functioning)
-    test_filter("filters/pcl/filter_PMF_1.json", 106, false);
-
-#if RUN_SLOW_TESTS
-    // explicitly with all defaults
-    test_filter("filters/pcl/filter_PMF_1.json", 9223, true);
-
-    // with CellSize=3
-    test_filter("filters/pcl/filter_PMF_2.json", 8298, true);
-
-    // with WindowSize=50
-    test_filter("filters/pcl/filter_PMF_3.json", 7970, true);
-
-    // with Slope=0.25
-    test_filter("filters/pcl/filter_PMF_4.json", 9206, true);
-
-    // with MaxDistance=5
-    test_filter("filters/pcl/filter_PMF_5.json", 9373, true);
-
-    // with InitialDistance=0.25
-    test_filter("filters/pcl/filter_PMF_6.json", 9229, true);
-
-    // with Base=3
-    test_filter("filters/pcl/filter_PMF_7.json", 8298, true);
-
-    // with Exponential=false
-    test_filter("filters/pcl/filter_PMF_8.json", 9138, true);
-
-    // with Negative=true
-    test_filter("filters/pcl/filter_PMF_9.json", 1430, true);
-#endif
-}
-
-TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_RadiusOutlierRemoval)
-{
-    // test MinNeighbors=1 and RadiusSearch=200
-    test_filter("filters/pcl/filter_RadiusOutlierRemoval_1.json", 60);
-
-    // test MinNeighbors=2 and RadiusSearch=100
-    test_filter("filters/pcl/filter_RadiusOutlierRemoval_2.json", 3);
-}
-
-TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_StatisticalOutlierRemoval)
-{
-    // test StdDev=2, MeanK=1.5
-    test_filter("filters/pcl/filter_StatisticalOutlierRemoval_1.json", 96);
-
-    // test StdDev=5, MeanK=0(default)
-    test_filter("filters/pcl/filter_StatisticalOutlierRemoval_2.json", 63);
-}
-
 TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_VoxelGrid)
 {
     // test LeafSize
diff --git a/plugins/pgpointcloud/CMakeLists.txt b/plugins/pgpointcloud/CMakeLists.txt
index d811a9e..31b5e87 100644
--- a/plugins/pgpointcloud/CMakeLists.txt
+++ b/plugins/pgpointcloud/CMakeLists.txt
@@ -1,36 +1,31 @@
 #
 # PgPointCloud plugin CMake configuration
 #
-
 include(${PDAL_CMAKE_DIR}/postgres.cmake)
-include_directories(${ROOT_DIR}/vendor/pdalboost)
 
 #
 # PgPointCloud Reader
 #
-set(srcs io/PgReader.cpp)
-set(incs
-    io/PgCommon.hpp
-    io/PgReader.hpp
-)
-
 PDAL_ADD_PLUGIN(reader_libname reader pgpointcloud
-    FILES "${srcs}" "${incs}"
-    LINK_WITH ${POSTGRESQL_LIBRARIES})
+    FILES
+        io/PgReader.cpp
+    LINK_WITH
+        ${POSTGRESQL_LIBRARIES})
+target_include_directories(${reader_libname} PRIVATE
+    ${PDAL_VENDOR_DIR}/pdalboost
+    ${LIBXML2_INCLUDE_DIR})
 
 #
 # PgPointCloud Writer
 #
-set(srcs io/PgWriter.cpp)
-set(incs
-    io/PgCommon.hpp
-    io/PgWriter.hpp
-)
-
 PDAL_ADD_PLUGIN(writer_libname writer pgpointcloud
-    FILES "${srcs}" "${incs}"
-    LINK_WITH ${POSTGRESQL_LIBRARIES})
-
+    FILES
+        io/PgWriter.cpp
+    LINK_WITH
+        ${POSTGRESQL_LIBRARIES})
+target_include_directories(${writer_libname} PRIVATE
+    ${PDAL_VENDOR_DIR}/pdalboost
+    ${LIBXML2_INCLUDE_DIR})
 #
 # PgPointCloud tests
 #
@@ -48,17 +43,14 @@ if (BUILD_PGPOINTCLOUD_TESTS)
 
 	configure_file(
         test/Pgtest-Support.hpp.in
-        ${CMAKE_CURRENT_BINARY_DIR}/Pgtest-Support.hpp
+        ${CMAKE_CURRENT_BINARY_DIR}/include/Pgtest-Support.hpp
     )
 
-    set(srcs
-        test/PgpointcloudWriterTest.cpp
-        ${CMAKE_CURRENT_BINARY_DIR}/Pgtest-Support.hpp
-    )
-
-    include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
 	PDAL_ADD_TEST(pgpointcloudtest
-        FILES "${srcs}"
-        LINK_WITH ${reader_libname} ${writer_libname})
+        FILES
+            test/PgpointcloudWriterTest.cpp
+        LINK_WITH
+            ${reader_libname} ${writer_libname})
+    target_include_directories(pgpointcloudtest PRIVATE
+        ${CMAKE_CURRENT_BINARY_DIR}/include)
 endif()
diff --git a/plugins/pgpointcloud/io/PgCommon.hpp b/plugins/pgpointcloud/io/PgCommon.hpp
index c907211..6688b51 100644
--- a/plugins/pgpointcloud/io/PgCommon.hpp
+++ b/plugins/pgpointcloud/io/PgCommon.hpp
@@ -108,21 +108,24 @@ inline void pg_commit(PGconn* session)
     pg_execute(session, sql);
 }
 
-inline char* pg_query_once(PGconn* session, std::string const& sql)
+inline std::string pg_query_once(PGconn* session, std::string const& sql)
 {
     PGresult *result = PQexec(session, sql.c_str());
-
     if ( (!result) ||
          PQresultStatus(result) != PGRES_TUPLES_OK ||
          PQntuples(result) == 0 )
     {
         PQclear(result);
-        return NULL;
+        return std::string();
     }
 
-    char *str = strdup(PQgetvalue(result, 0, 0));
+	int len = PQgetlength(result, 0, 0);
+    char *value = PQgetvalue(result, 0, 0);
+    std::string out;
+    if (value)
+	    out = std::string(value, len);
     PQclear(result);
-    return str;
+    return out;
 }
 
 inline PGresult* pg_query_result(PGconn* session, std::string const& sql)
diff --git a/plugins/pgpointcloud/io/PgReader.cpp b/plugins/pgpointcloud/io/PgReader.cpp
index 2cc65ff..7c29cde 100644
--- a/plugins/pgpointcloud/io/PgReader.cpp
+++ b/plugins/pgpointcloud/io/PgReader.cpp
@@ -157,13 +157,12 @@ uint32_t PgReader::fetchPcid() const
             pg_quote_literal(m_schema_name);
     }
 
-    char *pcid_str = pg_query_once(m_session, oss.str());
+    std::string pcid_str = pg_query_once(m_session, oss.str());
 
     uint32_t pcid = 0;
-    if (pcid_str)
+    if (pcid_str.size())
     {
-       pcid = atoi(pcid_str);
-       free(pcid_str);
+       pcid = atoi(pcid_str.c_str());
     }
 
     if (!pcid) // Are pcid == 0 valid?
@@ -192,12 +191,11 @@ void PgReader::addDimensions(PointLayoutPtr layout)
     std::ostringstream oss;
     oss << "SELECT schema FROM pointcloud_formats WHERE pcid = " << pcid;
 
-    char *xmlStr = pg_query_once(m_session, oss.str());
-    if (!xmlStr)
+    std::string xmlStr = pg_query_once(m_session, oss.str());
+    if (xmlStr.empty())
         throw pdal_error("Unable to fetch schema from `pointcloud_formats`");
 
     loadSchema(layout, xmlStr);
-    free(xmlStr);
 }
 
 
@@ -211,11 +209,11 @@ pdal::SpatialReference PgReader::fetchSpatialReference() const
     std::ostringstream oss;
     oss << "SELECT srid FROM pointcloud_formats WHERE pcid = " << pcid;
 
-    char *srid_str = pg_query_once(m_session, oss.str());
-    if (! srid_str)
+    std::string srid_str = pg_query_once(m_session, oss.str());
+    if (srid_str.empty())
         throw pdal_error("Unable to fetch srid for this table and column");
 
-    int32_t srid = atoi(srid_str);
+    int32_t srid = atoi(srid_str.c_str());
     log()->get(LogLevel::Debug) << "     got SRID = " << srid << std::endl;
 
     oss.str("");
diff --git a/plugins/pgpointcloud/io/PgWriter.cpp b/plugins/pgpointcloud/io/PgWriter.cpp
index 40d702e..e468439 100644
--- a/plugins/pgpointcloud/io/PgWriter.cpp
+++ b/plugins/pgpointcloud/io/PgWriter.cpp
@@ -193,12 +193,11 @@ uint32_t PgWriter::SetupSchema(uint32_t srid)
     {
         oss << "SELECT Count(pcid) FROM pointcloud_formats WHERE pcid = " <<
             m_pcid;
-        char *count_str = pg_query_once(m_session, oss.str());
-        if (!count_str)
+        std::string count_str = pg_query_once(m_session, oss.str());
+        if (count_str.empty())
             throw pdal_error("Unable to count pcid's in table "
                 "`pointcloud_formats`");
-        schema_count = atoi(count_str);
-        free(count_str);
+        schema_count = atoi(count_str.c_str());
         oss.str("");
         if (schema_count == 0)
         {
@@ -212,12 +211,11 @@ uint32_t PgWriter::SetupSchema(uint32_t srid)
     // Do we have any existing schemas in the POINTCLOUD_FORMATS table?
     uint32_t pcid = 0;
     oss << "SELECT Count(pcid) FROM pointcloud_formats";
-    char *schema_count_str = pg_query_once(m_session, oss.str());
-    if (!schema_count_str)
+    std::string schema_count_str = pg_query_once(m_session, oss.str());
+    if (schema_count_str.empty())
         throw pdal_error("Unable to count pcid's in table "
             "`pointcloud_formats`");
-    schema_count = atoi(schema_count_str);
-    free(schema_count_str);
+    schema_count = atoi(schema_count_str.c_str());
     oss.str("");
 
     // Create an XML output schema.
@@ -262,17 +260,17 @@ uint32_t PgWriter::SetupSchema(uint32_t srid)
         // released. See https://github.com/PDAL/PDAL/issues/1101 for
         // SQL to create this sequence on the pointcloud_formats
         // table.
-        char* have_seq = pg_query_once(m_session,
+        std::string have_seq = pg_query_once(m_session,
                 "select count(*) from pg_class where relname = 'pointcloud_formats_pcid_sq'");
-        int seq_count = atoi(have_seq);
+        int seq_count = atoi(have_seq.c_str());
         if (seq_count)
         {
             // We have the sequence, use its nextval
-            char *pcid_str = pg_query_once(m_session,
+            std::string pcid_str = pg_query_once(m_session,
                     "SELECT nextval('pointcloud_formats_pcid_sq')");
-            if (!pcid_str)
+            if (pcid_str.empty())
                 throw pdal_error("Unable to select nextval from pointcloud_formats_pcid_seq");
-            pcid = atoi(pcid_str);
+            pcid = atoi(pcid_str.c_str());
         }
         else
         {
@@ -283,12 +281,12 @@ uint32_t PgWriter::SetupSchema(uint32_t srid)
     }
     else
     {
-        char *pcid_str = pg_query_once(m_session,
+        std::string pcid_str = pg_query_once(m_session,
                 "SELECT Max(pcid)+1 AS pcid FROM pointcloud_formats");
-        if (!pcid_str)
+        if (pcid_str.empty())
             throw pdal_error("Unable to get the max pcid from "
                 "`pointcloud_formats`");
-        pcid = atoi(pcid_str);
+        pcid = atoi(pcid_str.c_str());
     }
 
     const char* paramValues = xml.c_str();
@@ -334,7 +332,7 @@ bool PgWriter::CheckPointCloudExists()
     {
         pg_execute(m_session, q);
     }
-    catch (pdal_error const &e)
+    catch (pdal_error const &)
     {
         return false;
     }
@@ -352,7 +350,7 @@ bool PgWriter::CheckPostGISExists()
     {
         pg_execute(m_session, q);
     }
-    catch (pdal_error const &e)
+    catch (pdal_error const &)
     {
         return false;
     }
@@ -369,11 +367,10 @@ bool PgWriter::CheckTableExists(std::string const& name)
     log()->get(LogLevel::Debug) << "checking for table '" << name <<
         "' existence ... " << std::endl;
 
-    char *count_str = pg_query_once(m_session, oss.str());
-    if (!count_str)
+    std::string count_str = pg_query_once(m_session, oss.str());
+    if (count_str.empty())
         throw pdal_error("Unable to check for the existence of `pg_table`");
-    int count = atoi(count_str);
-    free(count_str);
+    int count = atoi(count_str.c_str());
 
     if (count == 1)
         return true;
diff --git a/plugins/pgpointcloud/test/PgpointcloudWriterTest.cpp b/plugins/pgpointcloud/test/PgpointcloudWriterTest.cpp
index 3f821d7..1ddfe76 100644
--- a/plugins/pgpointcloud/test/PgpointcloudWriterTest.cpp
+++ b/plugins/pgpointcloud/test/PgpointcloudWriterTest.cpp
@@ -112,7 +112,7 @@ protected:
         {
             executeOnMasterDb(createDbSql.str());
         }
-        catch( const pdal_error& error )
+        catch( const pdal_error& )
         {
             m_bSkipTests = true;
             return;
@@ -124,7 +124,7 @@ protected:
         {
             executeOnTestDb("CREATE EXTENSION pointcloud");
         }
-        catch( const pdal_error& error )
+        catch( const pdal_error& )
         {
             m_bSkipTests = true;
             return;
diff --git a/plugins/python/CMakeLists.txt b/plugins/python/CMakeLists.txt
index 559335f..1c4a40a 100644
--- a/plugins/python/CMakeLists.txt
+++ b/plugins/python/CMakeLists.txt
@@ -3,13 +3,18 @@
 #
 
 include(${PDAL_CMAKE_DIR}/python.cmake)
-include_directories(${ROOT_DIR}/vendor/pdalboost)
 
 add_subdirectory(filters)
 if (WITH_TESTS)
-    PDAL_ADD_TEST(plangtest FILES ./test/PLangTest.cpp
+    PDAL_ADD_TEST(plangtest
+        FILES ./test/PLangTest.cpp
         LINK_WITH ${PDAL_PLANG_LIB_NAME})
+    target_include_directories(plangtest PRIVATE
+        ${PDAL_VENDOR_DIR}/pdalboost
+        ${ROOT_DIR})
     if (WITH_APPS)
-      PDAL_ADD_TEST(python_pipeline_test FILES ./test/PythonPipelineTest.cpp LINK_WITH ${PDAL_PLANG_LIB_NAME})
+        PDAL_ADD_TEST(python_pipeline_test
+            FILES ./test/PythonPipelineTest.cpp
+            LINK_WITH ${PDAL_PLANG_LIB_NAME})
     endif()
 endif()
diff --git a/plugins/python/filters/CMakeLists.txt b/plugins/python/filters/CMakeLists.txt
index 57ae073..832fc16 100644
--- a/plugins/python/filters/CMakeLists.txt
+++ b/plugins/python/filters/CMakeLists.txt
@@ -1,31 +1,30 @@
 
 # Predicate Filter
 #
-include_directories(SYSTEM ${PYTHON_INCLUDE_DIR})
-
-set(srcs PredicateFilter.cpp)
-set(incs PredicateFilter.hpp)
-
 PDAL_ADD_PLUGIN(predicate_libname filter predicate
-    FILES "${srcs}" "${incs}"
+    FILES
+        PredicateFilter.cpp
     LINK_WITH ${PDAL_PLANG_LIB_NAME})
+target_include_directories(${predicate_libname} PRIVATE
+    ${PYTHON_INCLUDE_DIR})
 
 #
 # Programmable Filter
 #
-set(srcs ProgrammableFilter.cpp)
-set(incs ProgrammableFilter.hpp)
 
 PDAL_ADD_PLUGIN(programmable_libname filter programmable
-    FILES "${srcs}" "${incs}"
+    FILES
+        ProgrammableFilter.cpp
     LINK_WITH ${PDAL_PLANG_LIB_NAME})
+target_include_directories(${programmable_libname} PRIVATE
+    ${PYTHON_INCLUDE_DIR})
 
 if (WITH_TESTS)
     PDAL_ADD_TEST(python_predicate_test
-            FILES ../test/PredicateFilterTest.cpp
-            LINK_WITH ${predicate_libname} ${PDAL_PLANG_LIB_NAME})
+        FILES ../test/PredicateFilterTest.cpp
+        LINK_WITH ${predicate_libname} ${PDAL_PLANG_LIB_NAME})
 
     PDAL_ADD_TEST(python_programmable_test
-            FILES ../test/ProgrammableFilterTest.cpp
-            LINK_WITH ${programmable_libname} ${PDAL_PLANG_LIB_NAME})
+        FILES ../test/ProgrammableFilterTest.cpp
+        LINK_WITH ${programmable_libname} ${PDAL_PLANG_LIB_NAME})
 endif()
diff --git a/plugins/python/filters/PredicateFilter.cpp b/plugins/python/filters/PredicateFilter.cpp
index 53b9f32..f7a133a 100644
--- a/plugins/python/filters/PredicateFilter.cpp
+++ b/plugins/python/filters/PredicateFilter.cpp
@@ -38,6 +38,7 @@
 #include <pdal/PointView.hpp>
 #include <pdal/StageFactory.hpp>
 #include <pdal/pdal_macros.hpp>
+#include <pdal/util/FileUtils.hpp>
 #include <pdal/util/ProgramArgs.hpp>
 
 namespace pdal
diff --git a/plugins/python/filters/ProgrammableFilter.cpp b/plugins/python/filters/ProgrammableFilter.cpp
index edff7ab..5e4e5f2 100644
--- a/plugins/python/filters/ProgrammableFilter.cpp
+++ b/plugins/python/filters/ProgrammableFilter.cpp
@@ -39,6 +39,7 @@
 #include <pdal/StageFactory.hpp>
 #include <pdal/pdal_macros.hpp>
 #include <pdal/util/ProgramArgs.hpp>
+#include <pdal/util/FileUtils.hpp>
 
 namespace pdal
 {
@@ -72,6 +73,8 @@ void ProgrammableFilter::addDimensions(PointLayoutPtr layout)
 
 void ProgrammableFilter::ready(PointTableRef table)
 {
+    if (m_source.empty())
+        m_source = FileUtils::readFileIntoString(m_scriptFile);
     plang::Environment::get()->set_stdout(log()->getLogStream());
     m_script = new plang::Script(m_source, m_module, m_function);
     m_pythonMethod = new plang::BufferedInvocation(*m_script);
diff --git a/plugins/python/test/PLangTest.cpp b/plugins/python/test/PLangTest.cpp
index 2c5a130..c3f6101 100644
--- a/plugins/python/test/PLangTest.cpp
+++ b/plugins/python/test/PLangTest.cpp
@@ -36,10 +36,9 @@
 
 #include <pdal/plang/Invocation.hpp>
 #include <pdal/plang/Array.hpp>
-
-#include <Support.hpp>
-#include <faux/FauxReader.hpp>
 #include <pdal/StageFactory.hpp>
+#include <io/FauxReader.hpp>
+#include <Support.hpp>
 
 using namespace pdal;
 using namespace pdal::plang;
diff --git a/plugins/python/test/PredicateFilterTest.cpp b/plugins/python/test/PredicateFilterTest.cpp
index 30010bc..7d4dd97 100644
--- a/plugins/python/test/PredicateFilterTest.cpp
+++ b/plugins/python/test/PredicateFilterTest.cpp
@@ -37,13 +37,11 @@
 #include <pdal/PipelineManager.hpp>
 #include <pdal/StageFactory.hpp>
 #include <pdal/StageWrapper.hpp>
-#include <stats/StatsFilter.hpp>
-#include <faux/FauxReader.hpp>
-
+#include <io/FauxReader.hpp>
+#include <filters/StatsFilter.hpp>
 
 #include "Support.hpp"
 
-
 using namespace pdal;
 
 #include <pdal/plang/Environment.hpp>
@@ -352,15 +350,6 @@ TEST_F(PredicateFilterTest, PredicateFilterTest_test5)
     ASSERT_THROW(filter->execute(table), pdal::pdal_error);
 }
 
-TEST_F(PredicateFilterTest, PredicateFilterTest_PipelineXML)
-{
-    PipelineManager mgr;
-
-    mgr.readPipeline(Support::configuredpath("plang/from-module.xml"));
-    point_count_t cnt = mgr.execute();
-    EXPECT_EQ(cnt, 1u);
-}
-
 TEST_F(PredicateFilterTest, PredicateFilterTest_PipelineJSON)
 {
     PipelineManager mgr;
@@ -370,15 +359,6 @@ TEST_F(PredicateFilterTest, PredicateFilterTest_PipelineJSON)
     EXPECT_EQ(cnt, 1u);
 }
 
-TEST_F(PredicateFilterTest, PredicateFilterTest_EmbedXML)
-{
-    PipelineManager mgr;
-
-    mgr.readPipeline(Support::configuredpath("plang/predicate-embed.xml"));
-    point_count_t cnt = mgr.execute();
-    EXPECT_EQ(cnt, 1u);
-}
-
 TEST_F(PredicateFilterTest, PredicateFilterTest_EmbedJSON)
 {
     PipelineManager mgr;
diff --git a/plugins/python/test/ProgrammableFilterTest.cpp b/plugins/python/test/ProgrammableFilterTest.cpp
index 9076da0..4c3f0b4 100644
--- a/plugins/python/test/ProgrammableFilterTest.cpp
+++ b/plugins/python/test/ProgrammableFilterTest.cpp
@@ -36,8 +36,8 @@
 
 #include <pdal/PipelineManager.hpp>
 #include <pdal/StageFactory.hpp>
-#include <stats/StatsFilter.hpp>
-#include <faux/FauxReader.hpp>
+#include <io/FauxReader.hpp>
+#include <filters/StatsFilter.hpp>
 
 #include "Support.hpp"
 
@@ -117,24 +117,6 @@ TEST_F(ProgrammableFilterTest, ProgrammableFilterTest_test1)
     EXPECT_DOUBLE_EQ(statsZ.maximum(), 3.14);
 }
 
-TEST_F(ProgrammableFilterTest, pipelineXML)
-{
-    PipelineManager manager;
-
-    manager.readPipeline(
-        Support::configuredpath("plang/programmable-update-y-dims.xml"));
-    manager.execute();
-    PointViewSet viewSet = manager.views();
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-
-    for (PointId idx = 0; idx < 10; ++idx)
-    {
-        int32_t y = view->getFieldAs<int32_t>(Dimension::Id::Y, idx);
-        EXPECT_EQ(y, 314);
-    }
-}
-
 TEST_F(ProgrammableFilterTest, pipelineJSON)
 {
     PipelineManager manager;
diff --git a/plugins/rxp/CMakeLists.txt b/plugins/rxp/CMakeLists.txt
index 3c7b2ca..0a31fa1 100644
--- a/plugins/rxp/CMakeLists.txt
+++ b/plugins/rxp/CMakeLists.txt
@@ -6,22 +6,13 @@ set_package_properties(RiVLib PROPERTIES
     PURPOSE "Read data from Riegl sensors"
     )
 
-include_directories(${RiVLib_INCLUDE_DIRS})
-message(STATUS "Found RiVLib at: ${RiVLib_INCLUDE_DIRS}")
-
-set(incs
-    io/RxpPointcloud.hpp
-    io/RxpReader.hpp
-    )
-
-set(srcs
-    io/RxpPointcloud.cpp
-    io/RxpReader.cpp
-    )
-
 PDAL_ADD_PLUGIN(libname reader rxp
-    FILES "${srcs}" "${incs}"
-    LINK_WITH ${RiVLib_SCANLIB_LIBRARY})
+    FILES
+        io/RxpPointcloud.cpp
+        io/RxpReader.cpp
+    LINK_WITH
+        ${RiVLib_SCANLIB_LIBRARY})
+target_include_directories(${libname} PRIVATE ${RiVLib_INCLUDE_DIRS})
 
 if (BUILD_RIVLIB_TESTS)
     configure_file(
@@ -29,11 +20,9 @@ if (BUILD_RIVLIB_TESTS)
         "${CMAKE_CURRENT_BINARY_DIR}/test/Config.hpp"
     )
 
-    include_directories("${CMAKE_CURRENT_BINARY_DIR}/test")
-    include_directories(${PROJECT_SOURCE_DIR}/vendor/pdalboost)
-    include_directories(${PROJECT_SOURCE_DIR}/plugins/rxp/io)
-
     PDAL_ADD_TEST(${RXP_TEST_NAME}
         FILES test/RxpReaderTest.cpp
         LINK_WITH ${libname})
-endif (BUILD_RIVLIB_TESTS)
+    target_include_directories(${RXP_TEST_NAME} PRIVATE
+        ${PROJECT_SOURCE_DIR}/plugins/rxp/io)
+endif()
diff --git a/plugins/rxp/io/RxpPointcloud.cpp b/plugins/rxp/io/RxpPointcloud.cpp
index 11406d8..14d8f6e 100644
--- a/plugins/rxp/io/RxpPointcloud.cpp
+++ b/plugins/rxp/io/RxpPointcloud.cpp
@@ -56,12 +56,18 @@ RxpPointcloud::RxpPointcloud(
         const std::string& uri,
         bool syncToPps,
         bool minimal,
+        bool reflectanceAsIntensity,
+        float minReflectance,
+        float maxReflectance,
         PointTableRef table)
     : scanlib::pointcloud(syncToPps)
     , m_view(new PointView(table))
     , m_idx(0)
     , m_syncToPps(syncToPps)
     , m_minimal(minimal)
+    , m_reflectanceAsIntensity(reflectanceAsIntensity)
+    , m_minReflectance(minReflectance)
+    , m_maxReflectance(maxReflectance)
     , m_rc(scanlib::basic_rconnection::create(uri))
     , m_dec(m_rc)
 {}
@@ -138,6 +144,18 @@ void RxpPointcloud::on_echo_transformed(echo_type echo)
             m_view->setField(Id::BackgroundRadiation, idx, t.background_radiation);
             m_view->setField(Id::IsPpsLocked, idx, t.is_pps_locked);
         }
+        if (m_reflectanceAsIntensity) {
+            uint16_t intensity;
+            if (t.reflectance > m_maxReflectance) {
+                intensity = std::numeric_limits<uint16_t>::max();
+            } else if (t.reflectance < m_minReflectance) {
+                intensity = 0;
+            } else {
+                intensity = uint16_t(std::roundf(double(std::numeric_limits<uint16_t>::max()) * 
+                        (t.reflectance - m_minReflectance) / (m_maxReflectance - m_minReflectance)));
+            }
+            m_view->setField(Id::Intensity, idx, intensity);
+        }
         ++idx, ++returnNumber;
     }
 }
diff --git a/plugins/rxp/io/RxpPointcloud.hpp b/plugins/rxp/io/RxpPointcloud.hpp
index 49d7497..3fa2a06 100644
--- a/plugins/rxp/io/RxpPointcloud.hpp
+++ b/plugins/rxp/io/RxpPointcloud.hpp
@@ -62,6 +62,9 @@ public:
             const std::string& uri,
             bool isSyncToPps,
             bool m_minimal,
+            bool m_reflectanceAsIntensity,
+            float m_minReflectance,
+            float m_maxReflectance,
             PointTableRef table);
     virtual ~RxpPointcloud();
 
@@ -82,6 +85,9 @@ private:
     point_count_t m_idx;
     bool m_syncToPps;
     bool m_minimal;
+    bool m_reflectanceAsIntensity;
+    float m_minReflectance;
+    float m_maxReflectance;
     std::shared_ptr<scanlib::basic_rconnection> m_rc;
     scanlib::decoder_rxpmarker m_dec;
     scanlib::buffer m_rxpbuf;
diff --git a/plugins/rxp/io/RxpReader.cpp b/plugins/rxp/io/RxpReader.cpp
index 7c094f9..f768e79 100644
--- a/plugins/rxp/io/RxpReader.cpp
+++ b/plugins/rxp/io/RxpReader.cpp
@@ -56,7 +56,7 @@ CREATE_SHARED_PLUGIN(1, 0, RxpReader, Reader, s_info)
 
 std::string RxpReader::getName() const { return s_info.name; }
 
-Dimension::IdList getRxpDimensions(bool syncToPps, bool minimal)
+Dimension::IdList getRxpDimensions(bool syncToPps, bool minimal, bool reflectanceAsIntensity)
 {
     using namespace Dimension;
     Dimension::IdList ids;
@@ -76,6 +76,9 @@ Dimension::IdList getRxpDimensions(bool syncToPps, bool minimal)
         ids.push_back(Id::BackgroundRadiation);
         ids.push_back(Id::IsPpsLocked);
     }
+    if (reflectanceAsIntensity) {
+        ids.push_back(Id::Intensity);
+    }
 
     return ids;
 }
@@ -85,6 +88,9 @@ void RxpReader::addArgs(ProgramArgs& args)
 {
     args.add("rdtp", "", m_isRdtp, DEFAULT_IS_RDTP);
     args.add("sync_to_pps", "Sync to PPS", m_syncToPps, DEFAULT_SYNC_TO_PPS);
+    args.add("reflectance_as_intensity", "Reflectance as intensity", m_reflectanceAsIntensity, DEFAULT_REFLECTANCE_AS_INTENSITY);
+    args.add("min_reflectance", "Minimum reflectance", m_minReflectance, DEFAULT_MIN_REFLECTANCE);
+    args.add("max_reflectance", "Maximum reflectance", m_maxReflectance, DEFAULT_MAX_REFLECTANCE);
 }
 
 void RxpReader::initialize()
@@ -95,13 +101,13 @@ void RxpReader::initialize()
 
 void RxpReader::addDimensions(PointLayoutPtr layout)
 {
-    layout->registerDims(getRxpDimensions(m_syncToPps, m_minimal));
+    layout->registerDims(getRxpDimensions(m_syncToPps, m_minimal, m_reflectanceAsIntensity));
 }
 
 
 void RxpReader::ready(PointTableRef table)
 {
-    m_pointcloud.reset(new RxpPointcloud(m_uri, m_syncToPps, m_minimal, table));
+    m_pointcloud.reset(new RxpPointcloud(m_uri, m_syncToPps, m_minimal, m_reflectanceAsIntensity, m_minReflectance, m_maxReflectance, table));
 }
 
 
diff --git a/plugins/rxp/io/RxpReader.hpp b/plugins/rxp/io/RxpReader.hpp
index d1559b3..ae79cae 100644
--- a/plugins/rxp/io/RxpReader.hpp
+++ b/plugins/rxp/io/RxpReader.hpp
@@ -55,7 +55,9 @@ namespace pdal
 const bool DEFAULT_SYNC_TO_PPS = true;
 const bool DEFAULT_IS_RDTP = false;
 const bool DEFAULT_MINIMAL = false;
-
+const bool DEFAULT_REFLECTANCE_AS_INTENSITY = true;
+const float DEFAULT_MIN_REFLECTANCE = -25.0;
+const float DEFAULT_MAX_REFLECTANCE = 5.0;
 
 std::string extractRivlibURI(const Options& options);
 Dimension::IdList getRxpDimensions(bool syncToPps, bool minimal);
@@ -68,6 +70,9 @@ public:
         : pdal::Reader()
         , m_syncToPps(DEFAULT_SYNC_TO_PPS)
         , m_minimal(false)
+        , m_reflectanceAsIntensity(DEFAULT_REFLECTANCE_AS_INTENSITY)
+        , m_minReflectance(DEFAULT_MIN_REFLECTANCE)
+        , m_maxReflectance(DEFAULT_MAX_REFLECTANCE)
         , m_pointcloud()
     {}
 
@@ -92,6 +97,9 @@ private:
     bool m_syncToPps;
     bool m_minimal;
     bool m_isRdtp;
+    bool m_reflectanceAsIntensity;
+    float m_minReflectance;
+    float m_maxReflectance;
     std::unique_ptr<RxpPointcloud> m_pointcloud;
 };
 
diff --git a/plugins/rxp/test/RxpReaderTest.cpp b/plugins/rxp/test/RxpReaderTest.cpp
index 984e9f9..e032e18 100644
--- a/plugins/rxp/test/RxpReaderTest.cpp
+++ b/plugins/rxp/test/RxpReaderTest.cpp
@@ -32,6 +32,8 @@
 * OF SUCH DAMAGE.
 ****************************************************************************/
 
+#include <limits>
+
 #include <pdal/pdal_test_main.hpp>
 
 #include <pdal/Options.hpp>
@@ -144,3 +146,18 @@ TEST(RxpReaderTest, testNoPpsSync)
     checkPoint(view, 0, 0.0705248788, -0.0417557284, 0.0304775704, 31.917255942733149,
             0.14050000667339191, 0.689999998, -14.4898596, 3, false, 1, 1);
 }
+
+TEST(RxpReaderTest, testReflectanceAsIntensity)
+{
+    Options options = defaultRxpReaderOptions();
+    options.add("reflectance_as_intensity", true);
+    options.add("max_reflectance", 3.0);
+    RxpReader reader;
+    reader.setOptions(options);
+    PointTable table;
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    PointViewPtr view = *viewSet.begin();
+    uint16_t intensity = view->getFieldAs<uint16_t>(Dimension::Id::Intensity, 0);
+    EXPECT_EQ(std::numeric_limits<uint16_t>::max(), intensity);
+}
diff --git a/plugins/sqlite/CMakeLists.txt b/plugins/sqlite/CMakeLists.txt
index 26aa25c..7699310 100644
--- a/plugins/sqlite/CMakeLists.txt
+++ b/plugins/sqlite/CMakeLists.txt
@@ -4,32 +4,26 @@
 
 include (${PDAL_CMAKE_DIR}/sqlite.cmake)
 include (${PDAL_CMAKE_DIR}/spatialite.cmake)
-include_directories(${ROOT_DIR}/vendor/pdalboost)
 
 # SQLite Reader
 #
-set(srcs io/SQLiteReader.cpp)
-set(incs
-    io/SQLiteCommon.hpp
-    io/SQLiteReader.hpp
-)
 
 PDAL_ADD_PLUGIN(reader_libname reader sqlite
-    FILES "${srcs}" "${incs}"
+    FILES
+        io/SQLiteReader.cpp
     LINK_WITH ${SQLITE3_LIBRARY})
+target_include_directories(${reader_libname} PRIVATE ${LIBXML2_INCLUDE_DIR})
 
 #
 # SQLite Writer
 #
-set(srcs io/SQLiteWriter.cpp)
-set(incs
-    io/SQLiteCommon.hpp
-    io/SQLiteWriter.hpp
-)
 
 PDAL_ADD_PLUGIN(writer_libname writer sqlite
-    FILES "${srcs}" "${incs}"
-    LINK_WITH ${SQLITE3_LIBRARY})
+    FILES
+        io/SQLiteWriter.cpp
+    LINK_WITH
+        ${SQLITE3_LIBRARY})
+target_include_directories(${writer_libname} PRIVATE ${LIBXML2_INCLUDE_DIR})
 
 #
 # SQLite tests
@@ -38,4 +32,7 @@ if(BUILD_SQLITE_TESTS)
 	PDAL_ADD_TEST(sqlitetest
         FILES test/SQLiteTest.cpp
         LINK_WITH ${reader_libname} ${writer_libname})
+    target_include_directories(sqlitetest PRIVATE
+        ${PDAL_IO_DIR}
+        ${LIBXML2_INCLUDE_DIR})
 endif()
diff --git a/plugins/sqlite/io/SQLiteReader.cpp b/plugins/sqlite/io/SQLiteReader.cpp
index f20b0ec..7c1d7c3 100644
--- a/plugins/sqlite/io/SQLiteReader.cpp
+++ b/plugins/sqlite/io/SQLiteReader.cpp
@@ -124,7 +124,7 @@ SQLiteReader::fetchSpatialReference(std::string const& query) const
 void SQLiteReader::addArgs(ProgramArgs& args)
 {
     args.add("spatialreference", "Spatial reference to apply to points if "
-       "one doesnt exist", m_spatialRef);
+       "one doesn't exist", m_spatialRef);
     args.add("query", "SELECT statement that returns point cloud", m_query);
     args.add("connection", "Database connection string", m_connection);
     args.add("module", "Spatialite module name", m_modulename);
diff --git a/plugins/sqlite/io/SQLiteWriter.cpp b/plugins/sqlite/io/SQLiteWriter.cpp
index fba12a7..f77814f 100644
--- a/plugins/sqlite/io/SQLiteWriter.cpp
+++ b/plugins/sqlite/io/SQLiteWriter.cpp
@@ -73,7 +73,7 @@ void SQLiteWriter::addArgs(ProgramArgs& args)
         m_block_table).setPositional();
     args.add("cloud_table_name", "Cloud table name",
         m_cloud_table).setPositional();
-    args.add("connection", "SQL conneciton string",
+    args.add("connection", "SQL connection string",
         m_connection).setPositional();
     args.add("cloud_column_name", "Cloud column name", m_cloud_column, "id");
     args.add("module", "Module name", m_modulename);
@@ -117,7 +117,9 @@ void SQLiteWriter::initialize()
     catch (pdal_error const& e)
     {
         std::stringstream oss;
-        oss << "Unable to connect to database with error '" << e.what() << "'";
+        oss << getName();
+        oss << ": Unable to connect to database with error '" <<
+            e.what() << "'";
         throw pdal_error(oss.str());
     }
 
@@ -343,7 +345,7 @@ SQLiteWriter::loadGeometryWKT(std::string const& filename_or_wkt) const
         if (!IsValidGeometryWKT(filename_or_wkt))
         {
             std::ostringstream oss;
-            oss << "WKT for not valid and '" << filename_or_wkt
+            oss << getName() << ": WKT for not valid and '" << filename_or_wkt
                 << "' doesn't exist as a file";
             throw pdal::pdal_error(oss.str());
         }
@@ -355,7 +357,7 @@ SQLiteWriter::loadGeometryWKT(std::string const& filename_or_wkt) const
         if (!IsValidGeometryWKT(wkt))
         {
             std::ostringstream oss;
-            oss << "WKT for was from file '" << filename_or_wkt
+            oss << getName() << ": WKT for was from file '" << filename_or_wkt
                 << "' is not valid";
             throw pdal::pdal_error(oss.str());
         }
diff --git a/plugins/sqlite/test/SQLiteTest.cpp b/plugins/sqlite/test/SQLiteTest.cpp
index 09a392a..cfd0bbe 100644
--- a/plugins/sqlite/test/SQLiteTest.cpp
+++ b/plugins/sqlite/test/SQLiteTest.cpp
@@ -39,7 +39,7 @@
 
 #include <pdal/PointView.hpp>
 #include <pdal/pdal_defines.h>
-#include <las/LasReader.hpp>
+#include <io/LasReader.hpp>
 
 #include "../io/SQLiteCommon.hpp"
 
@@ -131,7 +131,7 @@ void testReadWrite(bool compression, bool scaling)
     int32_t x = view->getFieldAs<int32_t>(Id::X, 10);
     EXPECT_EQ(x, 636038);
     double xd = view->getFieldAs<double>(Id::X, 10);
-    EXPECT_FLOAT_EQ(xd, 636037.53);
+	EXPECT_DOUBLE_EQ(xd, 636037.53);
     }
 
 //    FileUtils::deleteFile(tempFilename);
diff --git a/python/README.rst b/python/README.rst
index 3ce1d14..84c6d63 100644
--- a/python/README.rst
+++ b/python/README.rst
@@ -2,10 +2,47 @@
 PDAL
 ================================================================================
 
+The PDAL Python extension allows you to process data with PDAL into `Numpy`_
+arrays. Additionally, you can use it to fetch `schema`_ and `metadata`_ from
+PDAL operations.
+
+Usage
+--------------------------------------------------------------------------------
+
+Given the following pipeline, which simply reads an `ASPRS LAS`_ file and
+sorts it by the ``X`` dimension:
+
+.. code-block:: python
+
+
+    json = """
+    {
+      "pipeline": [
+        "1.2-with-color.las",
+        {
+            "type": "filters.sort",
+            "dimension": "X"
+        }
+      ]
+    }"""
+
+    import pdal
+    pipeline = pdal.Pipeline(pipeline)
+    pipeline.validate() # check if our JSON and options were good
+    pipeline.loglevel = 9 #really noisy
+    count = pipeline.execute()
+    arrays = pipeline.arrays
+    metadata = pipeline.metadata
+    log = pipeline.log
+
+
+.. _`Numpy`: http://www.numpy.org/
+.. _`schema`: http://www.pdal.io/dimensions.html
+.. _`metadata`: http://www.pdal.io/development/metadata.html
+
 Requirements
 ================================================================================
 
-PDAL 1.0+ requires
-
+* PDAL 1.4+
 * Python >=2.7 (including Python 3.x)
 
diff --git a/python/VERSION.txt b/python/VERSION.txt
index f0bb29e..88c5fb8 100644
--- a/python/VERSION.txt
+++ b/python/VERSION.txt
@@ -1 +1 @@
-1.3.0
+1.4.0
diff --git a/python/pdal/Pipeline.cpp b/python/pdal/Pipeline.cpp
deleted file mode 100644
index b18e6ee..0000000
--- a/python/pdal/Pipeline.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-#include "Pipeline.hpp"
-#ifdef PDAL_HAVE_LIBXML2
-#include <pdal/XMLSchema.hpp>
-#endif
-
-#include <pdal/plang/Array.hpp>
-
-
-namespace libpdalpython
-{
-
-Pipeline::Pipeline(std::string const& json)
-    : m_json(json)
-    , m_schema("")
-    , m_manager(-1)
-{
-    auto initNumpy = []()
-    {
-#undef NUMPY_IMPORT_ARRAY_RETVAL
-#define NUMPY_IMPORT_ARRAY_RETVAL
-        import_array();
-    };
-    initNumpy();
-}
-
-void Pipeline::execute()
-{
-    std::stringstream strm;
-    strm << m_json;
-    m_manager.readPipeline(strm);
-    m_manager.execute();
-#ifdef PDAL_HAVE_LIBXML2
-    pdal::XMLSchema schema(m_manager.pointTable().layout());
-    m_schema = schema.xml();
-#endif
-
-    strm.str("");
-    pdal::PipelineWriter::writePipeline(m_manager.getStage(), strm);
-
-    m_json = strm.str();
-
-}
-
-std::vector<PArray> Pipeline::getArrays() const
-{
-    std::vector<PArray> output;
-    const pdal::PointViewSet& pvset = m_manager.views();
-
-
-    for (auto i: pvset)
-    {
-        PArray array = new pdal::plang::Array;
-        array->update(i);
-        output.push_back(array);
-    }
-    return output;
-}
-} //namespace libpdalpython
-
diff --git a/python/pdal/Pipeline.hpp b/python/pdal/Pipeline.hpp
deleted file mode 100644
index f832ad1..0000000
--- a/python/pdal/Pipeline.hpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#include <pdal/PipelineManager.hpp>
-#include <pdal/PipelineWriter.hpp>
-#include <pdal/util/FileUtils.hpp>
-#include <pdal/plang/Array.hpp>
-
-#include <string>
-#include <Python.h>
-#undef toupper
-#undef tolower
-#undef isspace
-
-#define PY_ARRAY_UNIQUE_SYMBOL LIBPDALPYTHON_ARRAY_API
-#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
-
-#include <numpy/arrayobject.h>
-
-
-namespace libpdalpython
-{
-//     typedef std::shared_ptr<pdal::plang::Array> PArray;
-    typedef pdal::plang::Array* PArray;
-class Pipeline {
-public:
-    Pipeline(std::string const& xml);
-    ~Pipeline(){};
-
-    void execute();
-    inline const char* getJSON() const { return m_json.c_str(); }
-    inline const char* getSchema() const { return m_schema.c_str(); }
-    std::vector<PArray> getArrays() const;
-
-private:
-    std::string m_json;
-    std::string m_schema;
-    pdal::PipelineManager m_manager; // no progress reporting
-
-};
-
-}
diff --git a/python/pdal/PyPipeline.cpp b/python/pdal/PyPipeline.cpp
new file mode 100644
index 0000000..63f1f17
--- /dev/null
+++ b/python/pdal/PyPipeline.cpp
@@ -0,0 +1,101 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include "PyPipeline.hpp"
+#ifdef PDAL_HAVE_LIBXML2
+#include <pdal/XMLSchema.hpp>
+#endif
+
+
+namespace libpdalpython
+{
+
+Pipeline::Pipeline(std::string const& json)
+    : m_executor(json)
+{
+    auto initNumpy = []()
+    {
+#undef NUMPY_IMPORT_ARRAY_RETVAL
+#define NUMPY_IMPORT_ARRAY_RETVAL
+        import_array();
+    };
+
+    initNumpy();
+}
+
+Pipeline::~Pipeline()
+{
+}
+
+void Pipeline::setLogLevel(int level)
+{
+    m_executor.setLogLevel(level);
+}
+
+int Pipeline::getLogLevel() const
+{
+    return static_cast<int>(m_executor.getLogLevel());
+}
+
+int64_t Pipeline::execute()
+{
+
+    int64_t count = m_executor.execute();
+    return count;
+}
+
+bool Pipeline::validate()
+{
+    return m_executor.validate();
+}
+
+std::vector<PArray> Pipeline::getArrays() const
+{
+    std::vector<PArray> output;
+
+    if (!m_executor.executed())
+        throw python_error("call execute() before fetching arrays");
+
+    const pdal::PointViewSet& pvset = m_executor.getManagerConst().views();
+
+    for (auto i: pvset)
+    {
+        PArray array = new pdal::plang::Array;
+        array->update(i);
+        output.push_back(array);
+    }
+    return output;
+}
+} //namespace libpdalpython
+
diff --git a/python/pdal/PyPipeline.hpp b/python/pdal/PyPipeline.hpp
new file mode 100644
index 0000000..e356f7e
--- /dev/null
+++ b/python/pdal/PyPipeline.hpp
@@ -0,0 +1,102 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#pragma once
+
+#include <pdal/PipelineManager.hpp>
+#include <pdal/PipelineWriter.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/plang/Array.hpp>
+#include <pdal/PipelineExecutor.hpp>
+
+#include <string>
+#include <sstream>
+#undef toupper
+#undef tolower
+#undef isspace
+
+#define PY_ARRAY_UNIQUE_SYMBOL LIBPDALPYTHON_ARRAY_API
+#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
+
+#include <numpy/arrayobject.h>
+
+
+namespace libpdalpython
+{
+
+class python_error : public std::runtime_error
+{
+public:
+    inline python_error(std::string const& msg) : std::runtime_error(msg)
+        {}
+};
+
+    typedef pdal::plang::Array* PArray;
+
+class Pipeline {
+public:
+    Pipeline(std::string const& xml);
+    ~Pipeline();
+
+    int64_t execute();
+    bool validate();
+    inline std::string getPipeline() const
+    {
+        return m_executor.getPipeline();
+    }
+    inline std::string getMetadata() const
+    {
+        return m_executor.getMetadata();
+    }
+    inline std::string getSchema() const
+    {
+        return m_executor.getSchema();
+    }
+    inline std::string getLog() const
+    {
+        return m_executor.getLog();
+    }
+    std::vector<PArray> getArrays() const;
+
+
+    void setLogLevel(int level);
+    int getLogLevel() const;
+
+private:
+
+    pdal::PipelineExecutor m_executor;
+
+};
+
+}
diff --git a/python/pdal/__init__.py b/python/pdal/__init__.py
index 29739b3..0ebc296 100644
--- a/python/pdal/__init__.py
+++ b/python/pdal/__init__.py
@@ -1 +1,3 @@
-__version__='1.3.0'
+__version__='1.4.0'
+
+from .pipeline import Pipeline
diff --git a/python/pdal/libpdalpython.pyx b/python/pdal/libpdalpython.pyx
index d049bee..0728cc1 100644
--- a/python/pdal/libpdalpython.pyx
+++ b/python/pdal/libpdalpython.pyx
@@ -2,6 +2,8 @@
 
 from libcpp.vector cimport vector
 from libcpp.string cimport string
+from libc.stdint cimport uint32_t, int64_t
+from libcpp cimport bool
 from cpython.version cimport PY_MAJOR_VERSION
 cimport numpy as np
 np.import_array()
@@ -9,16 +11,23 @@ np.import_array()
 from cpython cimport PyObject, Py_INCREF
 from cython.operator cimport dereference as deref, preincrement as inc
 
+
 cdef extern from "pdal/plang/Array.hpp" namespace "pdal::plang":
     cdef cppclass Array:
         void* getPythonArray() except+
 
-cdef extern from "Pipeline.hpp" namespace "libpdalpython":
+cdef extern from "PyPipeline.hpp" namespace "libpdalpython":
     cdef cppclass Pipeline:
         Pipeline(const char* ) except +
-        void execute() except +
-        const char* getJSON()
+        int64_t execute() except +
+        bool validate() except +
+        string getPipeline() except +
+        string getMetadata() except +
+        string getSchema() except +
+        string getLog() except +
         vector[Array*] getArrays() except +
+        int getLogLevel()
+        void setLogLevel(int)
 
 cdef class PyPipeline:
     cdef Pipeline *thisptr      # hold a c++ instance which we're wrapping
@@ -33,25 +42,52 @@ cdef class PyPipeline:
     def __dealloc__(self):
         del self.thisptr
 
-    property json:
+    property pipeline:
+        def __get__(self):
+            return self.thisptr.getPipeline().decode('UTF-8')
+
+    property metadata:
+        def __get__(self):
+            return self.thisptr.getMetadata().decode('UTF-8')
+
+    property loglevel:
+        def __get__(self):
+            return self.thisptr.getLogLevel()
+        def __set__(self, v):
+            self.thisptr.setLogLevel(v)
+
+    property log:
         def __get__(self):
-            return self.thisptr.getJSON().decode('UTF-8')
-
-    def arrays(self):
-        v = self.thisptr.getArrays()
-        output = []
-        cdef vector[Array*].iterator it = v.begin()
-        cdef Array* a
-        while it != v.end():
-            ptr = deref(it)
-            a = ptr#.get()
-            o = a.getPythonArray()
-            output.append(<object>o)
-            inc(it)
-        return output
+
+            return self.thisptr.getLog().decode('UTF-8')
+
+    property schema:
+        def __get__(self):
+            import json
+
+            j = self.thisptr.getSchema().decode('UTF-8')
+            return json.loads(j)
+
+    property arrays:
+        def __get__(self):
+            v = self.thisptr.getArrays()
+            output = []
+            cdef vector[Array*].iterator it = v.begin()
+            cdef Array* a
+            while it != v.end():
+                ptr = deref(it)
+                a = ptr#.get()
+                o = a.getPythonArray()
+                output.append(<object>o)
+                inc(it)
+            return output
 
     def execute(self):
         if not self.thisptr:
             raise Exception("C++ Pipeline object not constructed!")
-        self.thisptr.execute()
+        return self.thisptr.execute()
 
+    def validate(self):
+        if not self.thisptr:
+            raise Exception("C++ Pipeline object not constructed!")
+        return self.thisptr.validate()
diff --git a/python/pdal/pipeline.py b/python/pdal/pipeline.py
new file mode 100644
index 0000000..7cd7aef
--- /dev/null
+++ b/python/pdal/pipeline.py
@@ -0,0 +1,46 @@
+
+from pdal import libpdalpython
+
+class Pipeline(object):
+    """A PDAL pipeline object, defined by JSON. See http://www.pdal.io/pipeline.html for more
+    information on how to define one"""
+
+    def __init__(self, json):
+        if isinstance(json, str):
+            data = json
+        else:
+            data = json.decode('UTF-8')
+        self.p = libpdalpython.PyPipeline(data)
+
+    def get_metadata(self):
+        return self.p.metadata
+    metadata = property(get_metadata)
+
+    def get_schema(self):
+        return self.p.schema
+    schema = property(get_schema)
+
+    def get_pipeline(self):
+        return self.p.pipeline
+    pipeline = property(get_pipeline)
+
+    def get_loglevel(self):
+        return self.p.loglevel
+
+    def set_loglevel(self, v):
+        self.p.loglevel = v
+    loglevel = property(get_loglevel, set_loglevel)
+
+    def get_log(self):
+        return self.p.log
+    log = property(get_log)
+
+    def execute(self):
+        return self.p.execute()
+
+    def validate(self):
+        return self.p.validate()
+
+    def get_arrays(self):
+        return self.p.arrays
+    arrays = property(get_arrays)
diff --git a/python/pdal/pipeline_xml.py b/python/pdal/pipeline_xml.py
deleted file mode 100644
index 1b809f0..0000000
--- a/python/pdal/pipeline_xml.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from xml.etree import ElementTree
-import pdal
-
-
-class XMLElement(object):
-
-    def __init__(self):
-        self.source = None
-
-    def xml(self):
-        element = ElementTree.Element(self.tag, self.attrib)
-        if self.source:
-            try:
-                sources = iter(self.source)
-            except TypeError:
-                sources = [self.source]
-            for source in sources:
-                element.append(source.xml())
-        return element
-
-
-class Pipeline(XMLElement):
-
-    tag = 'Pipeline'
-
-    def __init__(self, customversion=None):
-        super(Pipeline, self).__init__()
-        self.attrib = {'version': customversion or pdal.__version__}
-
-    def xml(self):
-        element = super(Pipeline, self).xml()
-        return ElementTree.ElementTree(element)
-
-
-class PipelineComponent(XMLElement):
-
-    def __init__(self, drivertype):
-        super(PipelineComponent, self).__init__()
-        self.attrib = {'type': self.typeformat % drivertype}
-
-
-class Stage(PipelineComponent):
-    pass
-
-
-class Reader(Stage):
-    tag = 'Reader'
-    typeformat = 'readers.%s'
-
-
-class Filter(Stage):
-    tag = 'Filter'
-    typeformat = 'filters.%s'
-
-
-class MultiFilter(Filter):
-    tag = 'MultiFilter'
-
-
-class Writer(PipelineComponent):
-    tag = 'Writer'
-    typeformat = 'writers.%s'
diff --git a/python/setup.py b/python/setup.py
index a74b90e..0542234 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -44,12 +44,15 @@ if 'PDAL_CONFIG' in os.environ:
     log.debug('pdal_config: %s', pdal_config)
 else:
     pdal_config = 'pdal-config'
+    # in case of windows...
+    if os.name in ['nt']:
+        pdal_config += '.bat'
 
 
 def get_pdal_config(option):
     '''Get configuration option from the `pdal-config` development utility
 
-    This code was adapted from Shaply's pdal-config stuff
+    This code was adapted from Shapely's geos-config stuff
     '''
     import subprocess
     pdal_config = globals().get('pdal_config')
@@ -77,9 +80,7 @@ module_version = None
 with open('pdal/__init__.py', 'r') as fp:
     for line in fp:
         if line.startswith("__version__"):
-
-            module_version = Version(
-                line.split("=")[1].strip().strip("\"'"))
+            module_version = Version(line.split("=")[1].strip().strip("\"'"))
             break
 
 if not module_version:
@@ -128,12 +129,17 @@ if pdal_config and "clean" not in sys.argv:
     except ValueError:
         pass
 
+    separator = ':'
+    if os.name in ['nt']:
+        separator = ';'
+
     for item in get_pdal_config('--includes').split():
         if item.startswith("-I"):
-            include_dirs.extend(item[2:].split(":"))
+            include_dirs.extend(item[2:].split(separator))
+
     for item in get_pdal_config('--libs').split():
         if item.startswith("-L"):
-            library_dirs.extend(item[2:].split(":"))
+            library_dirs.extend(item[2:].split(separator))
         elif item.startswith("-l"):
             libraries.append(item[2:])
 
@@ -145,7 +151,7 @@ DEBUG=False
 if DEBUG:
     extra_compile_args += ['-g','-O0']
 
-sources=['pdal/libpdalpython'+ext,"pdal/Pipeline.cpp",  ]
+sources=['pdal/libpdalpython'+ext, "pdal/PyPipeline.cpp"  ]
 extensions = [DistutilsExtension("*",
                                    sources,
                                    include_dirs=include_dirs,
diff --git a/python/test/__init__.py b/python/test/__init__.py
index d5c8c8b..8a3c854 100644
--- a/python/test/__init__.py
+++ b/python/test/__init__.py
@@ -1,5 +1,3 @@
 import sys
 DATADIRECTORY = sys.argv.pop()
-print (DATADIRECTORY)
-from test.test_libpdal import test_suite
 from test.test_pipeline import test_suite
diff --git a/python/test/test_libpdal.py b/python/test/test_libpdal.py
deleted file mode 100644
index ce3c9bc..0000000
--- a/python/test/test_libpdal.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import pdal
-from pdal import libpdalpython
-import unittest
-import os
-
-DATADIRECTORY = os.environ.get('PDAL_TEST_DIR')
-if not DATADIRECTORY:
-    DATADIRECTORY = "../test"
-
-class TestPDALArray(unittest.TestCase):
-
-  DATADIRECTORY = os.environ.get('PDAL_TEST_DIR')
-  if not DATADIRECTORY:
-      DATADIRECTORY = "../test"
-
-  def fetch_json(self, filename):
-    import os
-    fn = DATADIRECTORY + os.path.sep +  filename
-    output = ''
-    with open(fn, 'rb') as f:
-        output = f.read().decode('UTF-8')
-    return output
-
-  @unittest.skipUnless(os.path.exists(os.path.join(DATADIRECTORY, 'data/pipeline/pipeline_read.json')),
-                       os.path.join(DATADIRECTORY, 'data/pipeline/pipeline_read.json'))
-  def test_construction(self):
-    """Can we construct a PDAL pipeline"""
-    json = self.fetch_json('/data/pipeline/pipeline_read.json')
-    r = libpdalpython.PyPipeline(json)
-
-  @unittest.skipUnless(os.path.exists(os.path.join(DATADIRECTORY, 'data/pipeline/pipeline_read.json')),
-                       "missing test data")
-  def test_execution(self):
-    """Can we execute a PDAL pipeline"""
-    x = self.fetch_json('/data/pipeline/pipeline_read.json')
-    r = libpdalpython.PyPipeline(x)
-    r.execute()
-    import sys
-    self.assertGreater(len(r.json), 200)
-
-  @unittest.skipUnless(os.path.exists(os.path.join(DATADIRECTORY, 'data/pipeline/pipeline_read.json')),
-                       "missing test data")
-  def test_array(self):
-    """Can we fetch PDAL data as a numpy array"""
-    json = self.fetch_json('/data/pipeline/pipeline_read.json')
-    r = libpdalpython.PyPipeline(json)
-    r.execute()
-    arrays = r.arrays()
-    self.assertEqual(len(arrays), 1)
-
-    a = arrays[0]
-    self.assertAlmostEqual(a[0][0], 637012.24, 7)
-    self.assertAlmostEqual(a[1064][2], 423.92, 7)
-
-  @unittest.skipUnless(os.path.exists(os.path.join(DATADIRECTORY, 'data/filters/chip.json')),
-                       "missing test data")
-  def test_merged_arrays(self):
-    """Can we fetch merged PDAL data """
-    json = self.fetch_json('/data/filters/chip.json')
-    r = libpdalpython.PyPipeline(json)
-    r.execute()
-    arrays = r.arrays()
-    self.assertEqual(len(arrays), 43)
-    # data are going to all be a little different
-    # due to sorting not being stable
-#     for a in arrays:
-#         self.assertAlmostEqual(a[0][0], 494057.30, 2)
-#         self.assertAlmostEqual(a[0][2], 130.63, 2)
-def test_suite():
-    return unittest.TestSuite(
-        [TestPDALArray])
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/python/test/test_pipeline.py b/python/test/test_pipeline.py
index cd6be3d..e906150 100644
--- a/python/test/test_pipeline.py
+++ b/python/test/test_pipeline.py
@@ -1,47 +1,115 @@
 import unittest
 import pdal
-import pdal.pipeline_xml as pxml
-
-class TestXML(unittest.TestCase):
-
-    def test_simplest_xml(self):
-        p = pxml.Pipeline()
-        p.source = pxml.Reader('foo')
-        xml = p.xml()
-        self.assertEqual(xml.getroot().attrib['version'], pdal.__version__)
-        self.assertEqual(xml.find('Reader').attrib['type'],
-                         'readers.foo')
-
-    def test_writer(self):
-        xml = pxml.Writer('bar').xml()
-        self.assertEqual(xml.tag, 'Writer')
-        self.assertEqual(xml.attrib['type'], 'writers.bar')
-
-    def test_pipeline_version(self):
-        xml = pxml.Pipeline('custom version').xml()
-        self.assertEqual(xml.getroot().attrib['version'], 'custom version')
-
-    def test_filter(self):
-        xml = pxml.Filter('foo').xml()
-        self.assertEqual(xml.tag, 'Filter')
-        self.assertEqual(xml.attrib['type'], 'filters.foo')
-
-    def test_multifilter(self):
-        xml = pxml.MultiFilter('foo').xml()
-        self.assertEqual(xml.tag, 'MultiFilter')
-        self.assertEqual(xml.attrib['type'], 'filters.foo')
-
-    def test_writer_source(self):
-        writer = pxml.Writer('foo')
-        writer.source = pxml.Reader('bar')
-        xml = writer.xml()
-        self.assertTrue(xml.find('Reader') is not None)
-
-    def test_multifilter_source(self):
-        mfilter = pxml.MultiFilter('multi')
-        mfilter.source = [pxml.Reader('foo'), pxml.Reader('bar')]
-        xml = mfilter.xml()
-        self.assertEqual(len(xml.findall('Reader')), 2)
+import os
+
+DATADIRECTORY = os.environ.get('PDAL_TEST_DIR')
+if not DATADIRECTORY:
+    DATADIRECTORY = "../test"
+
+bad_json = u"""
+{
+  "pipeline": [
+    "nofile.las",
+    {
+        "type": "filters.sort",
+        "dimension": "X"
+    }
+  ]
+}
+"""
+
+class TestPipeline(unittest.TestCase):
+
+    DATADIRECTORY = os.environ.get('PDAL_TEST_DIR')
+    if not DATADIRECTORY:
+        DATADIRECTORY = "../test"
+    def fetch_json(self, filename):
+        import os
+        fn = DATADIRECTORY + os.path.sep +  filename
+        output = ''
+        with open(fn, 'rb') as f:
+            output = f.read().decode('UTF-8')
+        return output
+
+    @unittest.skipUnless(os.path.exists(os.path.join(DATADIRECTORY, 'data/pipeline/sort.json')),
+                         os.path.join(DATADIRECTORY, 'data/pipeline/sort.json'))
+    def test_construction(self):
+        """Can we construct a PDAL pipeline"""
+        json = self.fetch_json('/data/pipeline/sort.json')
+        r = pdal.Pipeline(json)
+
+    @unittest.skipUnless(os.path.exists(os.path.join(DATADIRECTORY, 'data/pipeline/sort.json')),
+                           "missing test data")
+    def test_execution(self):
+        """Can we execute a PDAL pipeline"""
+        x = self.fetch_json('/data/pipeline/sort.json')
+        r = pdal.Pipeline(x)
+        r.execute()
+        self.assertGreater(len(r.pipeline), 200)
+
+    def test_validate(self):
+        """Do we complain with bad pipelines"""
+        r = pdal.Pipeline(bad_json)
+        with self.assertRaises(RuntimeError):
+            r.validate()
+
+    @unittest.skipUnless(os.path.exists(os.path.join(DATADIRECTORY, 'data/pipeline/sort.json')),
+                         "missing test data")
+    def test_array(self):
+        """Can we fetch PDAL data as a numpy array"""
+        json = self.fetch_json('/data/pipeline/sort.json')
+        r = pdal.Pipeline(json)
+        r.execute()
+        arrays = r.arrays
+        self.assertEqual(len(arrays), 1)
+
+        a = arrays[0]
+        self.assertAlmostEqual(a[0][0], 635619.85, 7)
+        self.assertAlmostEqual(a[1064][2], 456.92, 7)
+
+    def test_metadata(self):
+        """Can we fetch PDAL metadata"""
+        json = self.fetch_json('/data/pipeline/sort.json')
+        r = pdal.Pipeline(json)
+        r.execute()
+        metadata = r.metadata
+        import json
+        j = json.loads(metadata)
+        self.assertEqual(j["metadata"]["readers.las"]["count"], 1065)
+
+
+    def test_no_execute(self):
+        """Does fetching arrays without executing throw an exception"""
+        json = self.fetch_json('/data/pipeline/sort.json')
+        r = pdal.Pipeline(json)
+        with self.assertRaises(RuntimeError):
+            r.arrays
+
+    def test_logging(self):
+        """Can we fetch log output"""
+        json = self.fetch_json('/data/pipeline/reproject.json')
+        r = pdal.Pipeline(json)
+        r.loglevel = 8
+        count = r.execute()
+        self.assertEqual(count, 789)
+        self.assertEqual(r.log.split()[0], '(pypipeline')
+
+    def test_schema(self):
+        """Fetching a schema works"""
+        json = self.fetch_json('/data/pipeline/sort.json')
+        r = pdal.Pipeline(json)
+        r.execute()
+        self.assertEqual(r.schema['schema']['dimensions'][0]['name'], 'X')
+
+    @unittest.skipUnless(os.path.exists(os.path.join(DATADIRECTORY, 'data/filters/chip.json')),
+                           "missing test data")
+    def test_merged_arrays(self):
+        """Can we fetch multiple point views from merged PDAL data """
+        json = self.fetch_json('/data/filters/chip.json')
+        r = pdal.Pipeline(json)
+        r.execute()
+        arrays = r.arrays
+        self.assertEqual(len(arrays), 43)
 
 def test_suite():
     return unittest.TestSuite(
diff --git a/scripts/appveyor/config.cmd b/scripts/appveyor/config.cmd
index 9113e82..b8d7a58 100644
--- a/scripts/appveyor/config.cmd
+++ b/scripts/appveyor/config.cmd
@@ -17,11 +17,12 @@ cmake -G "Visual Studio 14 2015 Win64" ^
     -DENABLE_CTEST=OFF ^
     -DWITH_APPS=ON ^
     -DWITH_LAZPERF=%PDAL_OPTIONAL_COMPONENTS% ^
-    -DWITH_GEOTIFF=%PDAL_OPTIONAL_COMPONENTS% ^
     -DWITH_LASZIP=%PDAL_OPTIONAL_COMPONENTS% ^
+    -DWITH_PDAL_JNI=%OPTIONAL_COMPONENT_SWITCH% ^
     -DWITH_TESTS=ON ^
     -DNUMPY_INCLUDE_DIR=%OSGEO4W_ROOT%\apps\python27\lib\site-packages\numpy\core\include ^
 	-DNUMPY_VERSION=1.8.1 ^
     -Dgtest_force_shared_crt=ON ^
     -DCMAKE_INSTALL_PREFIX=C:\pdalbin ^
+    -DCMAKE_VERBOSE_MAKEFILE=OFF ^
     .
diff --git a/scripts/ci/common.sh b/scripts/ci/common.sh
index f23781e..6160aa1 100644
--- a/scripts/ci/common.sh
+++ b/scripts/ci/common.sh
@@ -26,9 +26,6 @@ export NUMTHREADS
 
 # pdal_test segfaults when built against external g++-built boost,
 # and I haven't found a good boost package built with clang yet
-if [[ "$CXX" == "clang++" ]]
-then
-    export PDAL_CMAKE_GENERATOR="Ninja"
-else
-    export PDAL_CMAKE_GENERATOR="Unix Makefiles"
-fi
+export PDAL_CMAKE_GENERATOR="Unix Makefiles"
+
+#    export PDAL_CMAKE_GENERATOR="Unix Makefiles"
diff --git a/scripts/ci/script.sh b/scripts/ci/script.sh
index 9ad1714..3aadbc5 100755
--- a/scripts/ci/script.sh
+++ b/scripts/ci/script.sh
@@ -2,6 +2,7 @@
 # Builds and tests PDAL
 
 clang --version
+gcc --version
 
 cd /pdal
 source ./scripts/ci/common.sh
@@ -40,8 +41,8 @@ cmake \
     -DENABLE_CTEST=OFF \
     -DWITH_APPS=ON \
     -DWITH_LAZPERF=$OPTIONAL_COMPONENT_SWITCH \
-    -DWITH_GEOTIFF=$OPTIONAL_COMPONENT_SWITCH \
     -DWITH_LASZIP=$OPTIONAL_COMPONENT_SWITCH \
+    -DWITH_PDAL_JNI=$OPTIONAL_COMPONENT_SWITCH \
     -DLASZIP_INCLUDE_DIR:PATH=/usr/include \
     -DLASZIP_LIBRARY:FILEPATH=/usr/lib/liblaszip.so \
     -DWITH_TESTS=ON \
@@ -50,7 +51,7 @@ cmake \
 
 cmake ..
 
-MAKECMD=ninja
+MAKECMD=make
 
 # Don't use ninja's default number of threads becuase it can
 # saturate Travis's available memory.
@@ -68,7 +69,10 @@ if [ "${OPTIONAL_COMPONENT_SWITCH}" == "ON" ]; then
     echo "current path: " `pwd`
     export PDAL_TEST_DIR=/pdal/_build/test
     python setup.py test
-    
+
+    # JNI tests
+    cd /pdal/java; PDAL_DEPEND_ON_NATIVE=false ./sbt -Djava.library.path=/pdal/_build/lib core/test
+
     # Build all examples
     for EXAMPLE in writing writing-filter writing-kernel writing-reader writing-writer
     do
diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile
index e464d32..a8b7e32 100644
--- a/scripts/docker/Dockerfile
+++ b/scripts/docker/Dockerfile
@@ -1,9 +1,8 @@
 FROM pdal/dependencies:latest
 MAINTAINER Howard Butler <howard at hobu.co>
-#ARG branch=master
 
-ENV CC clang
-ENV CXX clang++
+ENV CC gcc
+ENV CXX g++
 
 RUN apt-get update && apt-get install -y --fix-missing --no-install-recommends \
         libhpdf-dev \
@@ -33,12 +32,12 @@ RUN git clone --depth=1 https://github.com/PDAL/PDAL \
         -DENABLE_CTEST=OFF \
         -DWITH_APPS=ON \
         -DWITH_LAZPERF=ON \
-        -DWITH_GEOTIFF=ON \
         -DWITH_LASZIP=ON \
         -DWITH_TESTS=ON \
+        -DWITH_PDAL_JNI=ON \
         -DCMAKE_BUILD_TYPE=Release \
         .. \
-    && make -j4 \
+    && make -j 2\
     && make install \
     && rm -rf /PDAL
 
@@ -56,7 +55,7 @@ RUN git clone https://github.com/PDAL/PRC.git \
     && cmake \
         -DCMAKE_BUILD_TYPE=Release \
         -DPDAL_DIR=/usr/lib/pdal/cmake \
-        -DCMAKE_BUILD_PREFIX=/usr \
+        -DCMAKE_INSTALL_PREFIX=/usr \
         .. \
     && make \
     && make install \
diff --git a/scripts/docker/Dockerfile.xenial b/scripts/docker/Dockerfile.xenial
index 84b782d..190ffa6 100644
--- a/scripts/docker/Dockerfile.xenial
+++ b/scripts/docker/Dockerfile.xenial
@@ -34,9 +34,9 @@ RUN git clone --depth=1 https://github.com/PDAL/PDAL \
         -DENABLE_CTEST=OFF \
         -DWITH_APPS=ON \
         -DWITH_LAZPERF=ON \
-        -DWITH_GEOTIFF=ON \
         -DWITH_LASZIP=ON \
         -DWITH_TESTS=ON \
+        -DWITH_PDAL_JNI=ON \
         -DCMAKE_BUILD_TYPE=Release \
         .. \
     && make -j4 \
diff --git a/scripts/docker/dependencies/Dockerfile b/scripts/docker/dependencies/Dockerfile
index 0cf895e..7137ed8 100644
--- a/scripts/docker/dependencies/Dockerfile
+++ b/scripts/docker/dependencies/Dockerfile
@@ -1,6 +1,9 @@
 FROM ubuntu:16.04
 MAINTAINER Howard Butler <howard at hobu.co>
 
+ENV CC gcc
+ENV CXX g++
+
 RUN apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 16126D3A3E5C1192
 RUN apt-get update -qq
 RUN apt-get -qq remove postgis
@@ -63,8 +66,10 @@ RUN apt-get update && apt-get install -y --fix-missing --no-install-recommends \
         cython \
         python-pip \
         libgdal1-dev \
+        time \
     && rm -rf /var/lib/apt/lists/*
 
+RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-3.6 20 && update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-3.6 20
 
 
 #RUN git clone --depth=1 https://github.com/OSGeo/gdal.git \
@@ -149,7 +154,7 @@ RUN git clone https://github.com/CRREL/points2grid.git \
     && cd points2grid \
     && mkdir build \
     && cd build \
-    && cmake \
+    && CXXFLAGS="-std=c++11" cmake \
         -DCMAKE_INSTALL_PREFIX=/usr \
         -DCMAKE_BUILD_TYPE="Release" \
         .. \
@@ -287,6 +292,21 @@ RUN apt-get update && apt-get install -y --fix-missing --no-install-recommends \
     && rm -rf /var/lib/apt/lists/*
 
 
+RUN git clone https://github.com/ninja-build/ninja.git \
+    && cd ninja \
+    && ./configure.py --bootstrap \
+    && cp ninja /usr/bin
+
+# Install Java.
+RUN echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \
+    add-apt-repository -y ppa:webupd8team/java && \
+    apt-get update && \
+    apt-get install -y oracle-java8-installer && \
+    rm -rf /var/lib/apt/lists/* && \
+    rm -rf /var/cache/oracle-jdk8-installer
+
+ENV JAVA_HOME /usr/lib/jvm/java-8-oracle
 
 #RUN update-alternatives --install /usr/bin/clang clang /usr/bin/clang-3.6 20 && update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-3.6 20
 
+
diff --git a/scripts/docker/dependencies/Dockerfile.xenial b/scripts/docker/dependencies/Dockerfile.xenial
deleted file mode 100644
index 4e8d8ab..0000000
--- a/scripts/docker/dependencies/Dockerfile.xenial
+++ /dev/null
@@ -1,273 +0,0 @@
-FROM ubuntu:16.04
-MAINTAINER Howard Butler <howard at hobu.co>
-
-RUN apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 16126D3A3E5C1192
-RUN apt-get update -qq
-RUN apt-get -qq remove postgis
-
-RUN apt-get update && apt-get install -y --fix-missing --no-install-recommends \
-        build-essential \
-        ca-certificates \
-        cmake \
-        curl \
-        gfortran \
-        git \
-        libarmadillo-dev \
-        libarpack2-dev \
-        libflann-dev \
-        libhdf5-serial-dev \
-        liblapack-dev \
-        libtiff5-dev \
-        openssh-client \
-        python-dev \
-        python-numpy \
-        python-software-properties \
-        software-properties-common \
-        wget \
-        automake \
-        libtool \
-        libspatialite-dev \
-        libhdf5-dev \
-        subversion \
-        libjsoncpp-dev \
-        libboost-filesystem-dev \
-        libboost-iostreams-dev \
-        libboost-program-options-dev \
-        libboost-system-dev \
-        libboost-thread-dev \
-        subversion \
-        clang \
-        libproj-dev \
-        libc6-dev \
-        libnetcdf-dev \
-        libjasper-dev \
-        libpng-dev \
-        libjpeg-dev \
-        libgif-dev \
-        libwebp-dev \
-        libhdf4-alt-dev \
-        libhdf5-dev \
-        libpq-dev \
-        libxerces-c-dev \
-        unixodbc-dev \
-        libsqlite3-dev \
-        libgeos-dev \
-        libmysqlclient-dev \
-        libltdl-dev \
-        libcurl4-openssl-dev \
-        libspatialite-dev \
-        libdap-dev\
-        ninja \
-        cython \
-        python-pip \
-    && rm -rf /var/lib/apt/lists/*
-
-
-RUN git clone --depth=1 https://github.com/OSGeo/gdal.git \
-    &&    cd gdal/gdal \
-    && ./configure --prefix=/usr \
-            --mandir=/usr/share/man \
-            --includedir=/usr/include/gdal \
-            --with-threads \
-            --with-grass=no \
-            --with-hide-internal-symbols=yes \
-            --with-rename-internal-libtiff-symbols=yes \
-            --with-rename-internal-libgeotiff-symbols=yes \
-            --with-libtiff=internal \
-            --with-geotiff=internal \
-            --with-webp \
-            --with-jasper \
-            --with-netcdf \
-            --with-hdf5=/usr/lib/x86_64-linux-gnu/hdf5/serial/ \
-            --with-xerces \
-            --with-geos \
-            --with-sqlite3 \
-            --with-curl \
-            --with-pg \
-            --with-mysql \
-            --with-python \
-            --with-odbc \
-            --with-ogdi \
-            --with-dods-root=/usr \
-            --with-spatialite=/usr \
-            --with-cfitsio=no \
-            --with-ecw=no \
-            --with-mrsid=no \
-            --with-poppler=yes \
-            --with-openjpeg=yes \
-            --with-freexl=yes \
-            --with-libkml=yes \
-            --with-armadillo=yes \
-            --with-liblzma=yes \
-            --with-epsilon=/usr \
-    && make -j 4 \
-    && make install \
-    && rm -rf /gdal
-
-RUN git clone https://github.com/hobu/nitro \
-    && cd nitro \
-    && mkdir build \
-    && cd build \
-    && cmake \
-        -DCMAKE_INSTALL_PREFIX=/usr \
-        .. \
-    && make \
-    && make install \
-    && rm -rf /nitro
-
-RUN git clone https://github.com/LASzip/LASzip.git laszip \
-    && cd laszip \
-    && git checkout e7065cbc5bdbbe0c6e50c9d93d1cd346e9be6778 \
-    && mkdir build \
-    && cd build \
-    && cmake \
-        -DCMAKE_INSTALL_PREFIX=/usr \
-        -DCMAKE_BUILD_TYPE="Release" \
-        .. \
-    && make \
-    && make install \
-    && rm -rf /laszip
-
-
-RUN git clone https://github.com/hobu/hexer.git \
-    && cd hexer \
-    && mkdir build \
-    && cd build \
-    && cmake \
-        -DCMAKE_INSTALL_PREFIX=/usr \
-        -DCMAKE_BUILD_TYPE="Release" \
-        .. \
-    && make \
-    && make install \
-    && rm -rf /hexer
-
-RUN git clone https://github.com/CRREL/points2grid.git \
-    && cd points2grid \
-    && mkdir build \
-    && cd build \
-    && cmake \
-        -DCMAKE_INSTALL_PREFIX=/usr \
-        -DCMAKE_BUILD_TYPE="Release" \
-        .. \
-    && make \
-    && make install \
-    && rm -rf /points2grid
-
-RUN git clone  https://github.com/hobu/laz-perf.git \
-    && cd laz-perf \
-    && mkdir build \
-    && cd build \
-    && cmake \
-        -DCMAKE_INSTALL_PREFIX=/usr \
-        -DCMAKE_BUILD_TYPE="Release" \
-        .. \
-    && make \
-    && make install \
-    && rm -rf /laz-perf
-
-RUN wget http://bitbucket.org/eigen/eigen/get/3.2.7.tar.gz \
-        && tar -xvf 3.2.7.tar.gz \
-        && cp -R eigen-eigen-b30b87236a1b/Eigen/ /usr/include/Eigen/ \
-        && cp -R eigen-eigen-b30b87236a1b/unsupported/ /usr/include/unsupported/ \
-        && rm -rf /3.2.7.tar.gz \
-        && rm -rf /eigen-eigen-b30b87236a1b
-
-RUN git clone https://github.com/chambbj/pcl.git \
-        && cd pcl \
-        && git checkout pcl-1.7.2-sans-opengl \
-        && mkdir build \
-        && cd build \
-        && CXXFLAGS="-std=c++11"  cmake \
-                -DBUILD_2d=ON \
-                -DBUILD_CUDA=OFF \
-                -DBUILD_GPU=OFF \
-                -DBUILD_apps=OFF \
-                -DBUILD_common=ON \
-                -DBUILD_examples=OFF \
-                -DBUILD_features=ON \
-                -DBUILD_filters=ON \
-                -DBUILD_geometry=ON \
-                -DBUILD_global_tests=OFF \
-                -DBUILD_io=ON \
-                -DBUILD_kdtree=ON \
-                -DBUILD_keypoints=ON \
-                -DBUILD_ml=ON \
-                -DBUILD_octree=ON \
-                -DBUILD_outofcore=OFF \
-                -DBUILD_people=OFF \
-                -DBUILD_recognition=OFF \
-                -DBUILD_registration=ON \
-                -DBUILD_sample_concensus=ON \
-                -DBUILD_search=ON \
-                -DBUILD_segmentation=ON \
-                -DBUILD_simulation=OFF \
-                -DBUILD_stereo=OFF \
-                -DBUILD_surface=ON \
-                -DBUILD_surface_on_nurbs=OFF \
-                -DBUILD_tools=OFF \
-                -DBUILD_tracking=OFF \
-                -DBUILD_visualization=OFF \
-                -DWITH_LIBUSB=OFF \
-                -DWITH_OPENNI=OFF \
-                -DWITH_OPENNI2=OFF \
-                -DWITH_FZAPI=OFF \
-                -DWITH_PXCAPI=OFF \
-                -DWITH_PNG=OFF \
-                -DWITH_QHULL=OFF \
-                -DWITH_QT=OFF \
-                -DWITH_VTK=OFF \
-                -DWITH_PCAP=OFF \
-                -DCMAKE_INSTALL_PREFIX=/usr \
-                -DCMAKE_BUILD_TYPE="Release" \
-                .. \
-        && make \
-        && make install \
-        && rm -rf /pcl
-
-
-
-RUN svn co -r 2691 https://svn.osgeo.org/metacrs/geotiff/trunk/libgeotiff/ \
-    && cd libgeotiff \
-    && ./autogen.sh \
-    && ./configure --prefix=/usr \
-    && make \
-    && make install \
-    && rm -rf /libgeotiff
-
-RUN apt-get update && apt-get install -y --fix-missing --no-install-recommends \
-        ninja-build \
-        libgeos++-dev \
-        unzip \
-    && rm -rf /var/lib/apt/lists/*
-
-RUN mkdir /vdatum \
-    && cd /vdatum \
-    && wget http://download.osgeo.org/proj/vdatum/usa_geoid2012.zip && unzip -j -u usa_geoid2012.zip -d /usr/share/proj \
-    && wget http://download.osgeo.org/proj/vdatum/usa_geoid2009.zip && unzip -j -u usa_geoid2009.zip -d /usr/share/proj \
-    && wget http://download.osgeo.org/proj/vdatum/usa_geoid2003.zip && unzip -j -u usa_geoid2003.zip -d /usr/share/proj \
-    && wget http://download.osgeo.org/proj/vdatum/usa_geoid1999.zip && unzip -j -u usa_geoid1999.zip -d /usr/share/proj \
-    && wget http://download.osgeo.org/proj/vdatum/vertcon/vertconc.gtx && mv vertconc.gtx /usr/share/proj \
-    && wget http://download.osgeo.org/proj/vdatum/vertcon/vertcone.gtx && mv vertcone.gtx /usr/share/proj \
-    && wget http://download.osgeo.org/proj/vdatum/vertcon/vertconw.gtx && mv vertconw.gtx /usr/share/proj \
-    && wget http://download.osgeo.org/proj/vdatum/egm96_15/egm96_15.gtx && mv egm96_15.gtx /usr/share/proj \
-    && wget http://download.osgeo.org/proj/vdatum/egm08_25/egm08_25.gtx && mv egm08_25.gtx /usr/share/proj \
-    && rm -rf /vdatum
-
-RUN git clone https://github.com/gadomski/fgt.git \
-    && cd fgt \
-    && git checkout v0.4.4 \
-    && cmake . -DWITH_TESTS=OFF -DBUILD_SHARED_LIBS=ON -DEIGEN3_INCLUDE_DIR=/usr/include -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release \
-    && make \
-    && make install \
-    && rm -rf /fgt
-
-RUN git clone https://github.com/gadomski/cpd.git \
-    && cd cpd \
-    && git checkout v0.3.2 \
-    && cmake . -DWITH_TESTS=OFF -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release \
-    && make \
-    && make install \
-    && rm -rf /cpd
-
-RUN apt-get clean
-
diff --git a/scripts/docker/docbuild/Dockerfile b/scripts/docker/docbuild/Dockerfile
index e39b881..984c245 100644
--- a/scripts/docker/docbuild/Dockerfile
+++ b/scripts/docker/docbuild/Dockerfile
@@ -1,4 +1,6 @@
-FROM ubuntu:15.10
+FROM ubuntu:16.04
+
+ADD http://www.timeapi.org/utc/now /tmp/bust-cache
 
 RUN apt-get -y update && apt-get install -y \
     python-dev python-pip g++ doxygen dvipng \
@@ -6,7 +8,7 @@ RUN apt-get -y update && apt-get install -y \
     texlive-latex-extra git \
     graphviz python-matplotlib
 
-RUN pip install Sphinx==1.4 breathe \
+RUN pip install Sphinx breathe \
     sphinx_bootstrap_theme awscli sphinxcontrib-bibtex \
     sphinx_rtd_theme
 
diff --git a/scripts/docker/rivlib/Dockerfile b/scripts/docker/rivlib/Dockerfile
index 94afe37..097720e 100644
--- a/scripts/docker/rivlib/Dockerfile
+++ b/scripts/docker/rivlib/Dockerfile
@@ -1,9 +1,10 @@
-FROM pdal/pdal
+FROM pdal/dependencies
 MAINTAINER Pete Gadomski <pete.gadomski at gmail.com>
 
-COPY rivlib-2_2_1-x86_64-linux-gcc44 /
+COPY rivlib-2_3_0-x86_64-linux-gcc55.zip /
+RUN unzip rivlib-2_3_0-x86_64-linux-gcc55.zip
 
-RUN git clone --depth=1 https://github.com/PDAL/PDAL \
+RUN CC="gcc" CXX="g++" git clone --depth=1 https://github.com/PDAL/PDAL \
 	&& cd PDAL \
 	&& git checkout master \
 	&& mkdir build \
@@ -20,14 +21,13 @@ RUN git clone --depth=1 https://github.com/PDAL/PDAL \
 		-DBUILD_PLUGIN_PCL=ON \
 		-DBUILD_PLUGIN_PGPOINTCLOUD=ON \
 		-DBUILD_PLUGIN_SQLITE=ON \
-        -DRiVLib_DIR=/rivlib-2_2_1-x86_64-linux-gcc44/cmake \
+        -DRiVLib_DIR=/rivlib-2_3_0-x86_64-linux-gcc55/cmake \
 		-DBUILD_PLUGIN_RIVLIB=ON \
 		-DBUILD_PLUGIN_PYTHON=ON \
 		-DCMAKE_INSTALL_PREFIX=/usr \
 		-DENABLE_CTEST=OFF \
 		-DWITH_APPS=ON \
 		-DWITH_LAZPERF=ON \
-		-DWITH_GEOTIFF=ON \
 		-DWITH_LASZIP=ON \
 		-DWITH_TESTS=ON \
 		-DCMAKE_BUILD_TYPE=Release \
diff --git a/scripts/linux-install-scripts/pdal.sh b/scripts/linux-install-scripts/pdal.sh
index 18cbb6a..6522b5f 100644
--- a/scripts/linux-install-scripts/pdal.sh
+++ b/scripts/linux-install-scripts/pdal.sh
@@ -17,7 +17,6 @@ cmake   -G "Unix Makefiles"  \
         -DWITH_ICONV=ON \
         -DBUILD_PLUGIN_PCL=ON \
         -DWITH_LASZIP=ON \
-        -DWITH_GEOTIFF=ON \
         -DWITH_LAZPERF=ON \
         -DWITH_LIBXML2=ON \
         -DBUILD_PLUGIN_PYTHON=ON \
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
deleted file mode 100644
index d2699d4..0000000
--- a/src/CMakeLists.txt
+++ /dev/null
@@ -1,230 +0,0 @@
-###############################################################################
-#
-# src/CMakeLists.txt controls building of PDAL library
-#
-# Copyright (c) 2009 Mateusz Loskot <mateusz at loskot.net>
-#
-###############################################################################
-
-###############################################################################
-# Source files specification
-#
-# Naming format:
-#   PDAL_CPP - all the .cpp files
-#   PDAL_HPP - all the .hpp files
-#   PDAL_<dirname>_CPP - all the .cpp files for the given subdir/namespace
-#   ...
-
-set(PDAL_HPP "")
-set(PDAL_CPP "")
-
-if (PDAL_HAVE_LIBXML2)
-    set(PDAL_XML_HEADER ${PDAL_HEADERS_DIR}/XMLSchema.hpp)
-    set(DB_DRIVER_HEADERS
-        "${PDAL_HEADERS_DIR}/DbReader.hpp"
-        "${PDAL_HEADERS_DIR}/DbWriter.hpp"
-    )
-    set(PDAL_XML_SRC XMLSchema.cpp)
-    set(DB_DRIVER_SRCS
-        DbReader.cpp
-        DbWriter.cpp
-    )
-endif()
-
-#
-# base
-#
-set(PDAL_BASE_HPP
-  "${PDAL_HEADERS_DIR}/pdal_types.hpp"
-  "${PDAL_HEADERS_DIR}/Compression.hpp"
-  "${PDAL_HEADERS_DIR}/Eigen.hpp"
-  "${PDAL_HEADERS_DIR}/Filter.hpp"
-  "${PDAL_HEADERS_DIR}/FlexWriter.hpp"
-  "${PDAL_HEADERS_DIR}/GDALUtils.hpp"
-  "${PDAL_HEADERS_DIR}/GEOSUtils.hpp"
-  "${PDAL_HEADERS_DIR}/gitsha.h"
-  "${PDAL_HEADERS_DIR}/KDIndex.hpp"
-  "${PDAL_HEADERS_DIR}/KernelFactory.hpp"
-  "${PDAL_HEADERS_DIR}/Kernel.hpp"
-  "${PDAL_HEADERS_DIR}/Log.hpp"
-  "${PDAL_HEADERS_DIR}/Metadata.hpp"
-  "${PDAL_HEADERS_DIR}/Options.hpp"
-  "${PDAL_HEADERS_DIR}/PipelineManager.hpp"
-  "${PDAL_HEADERS_DIR}/PipelineWriter.hpp"
-  "${PDAL_HEADERS_DIR}/PointContainer.hpp"
-  "${PDAL_HEADERS_DIR}/PointLayout.hpp"
-  "${PDAL_HEADERS_DIR}/PointRef.hpp"
-  "${PDAL_HEADERS_DIR}/PointTable.hpp"
-  "${PDAL_HEADERS_DIR}/PointView.hpp"
-  "${PDAL_HEADERS_DIR}/PointViewIter.hpp"
-  "${PDAL_HEADERS_DIR}/Polygon.hpp"
-  "${PDAL_HEADERS_DIR}/QuadIndex.hpp"
-  "${PDAL_HEADERS_DIR}/Reader.hpp"
-  "${PDAL_HEADERS_DIR}/Scaling.hpp"
-  "${PDAL_HEADERS_DIR}/SpatialReference.hpp"
-  "${PDAL_HEADERS_DIR}/Stage.hpp"
-  "${PDAL_HEADERS_DIR}/StageFactory.hpp"
-  "${PDAL_HEADERS_DIR}/StageWrapper.hpp"
-  "${PDAL_HEADERS_DIR}/Writer.hpp"
-  "${PDAL_SRC_DIR}/PipelineReaderJSON.hpp"
-  "${PDAL_SRC_DIR}/PipelineReaderXML.hpp"
-  "${PDAL_SRC_DIR}/StageRunner.hpp"
-    ${PDAL_XML_HEADER}
-    ${DB_DRIVER_HEADERS}
-)
-
-set(PDAL_BASE_CPP
-  DynamicLibrary.cpp
-  Eigen.cpp
-  gitsha.cpp
-  GDALUtils.cpp
-  GEOSUtils.cpp
-  Kernel.cpp
-  KernelFactory.cpp
-  Log.cpp
-  Metadata.cpp
-  Options.cpp
-  PDALUtils.cpp
-  PointLayout.cpp
-  PointTable.cpp
-  PointView.cpp
-  Polygon.cpp
-  PipelineManager.cpp
-  PipelineReaderJSON.cpp
-  PipelineReaderXML.cpp
-  PipelineWriter.cpp
-  PluginManager.cpp
-  QuadIndex.cpp
-  Reader.cpp
-  Scaling.cpp
-  SpatialReference.cpp
-  Stage.cpp
-  StageFactory.cpp
-  Writer.cpp
-  ${PDAL_XML_SRC}
-  ${PDAL_LAZPERF_SRC}
-  ${DB_DRIVER_SRCS}
-)
-
-list (APPEND PDAL_CPP ${PDAL_BASE_CPP} )
-list (APPEND PDAL_HPP ${PDAL_BASE_HPP} )
-
-#
-# config
-#
-
-set(PDAL_CONFIG_HPP
-  "${PDAL_HEADERS_DIR}/pdal_export.hpp"
-  "${PDAL_HEADERS_DIR}/pdal_internal.hpp"
-  "${PDAL_HEADERS_DIR}/pdal_config.hpp"
-  "${PROJECT_BINARY_DIR}/include/pdal/pdal_defines.h"
-)
-
-set (PDAL_CONFIG_CPP
-  "${PROJECT_SOURCE_DIR}/src/pdal_config.cpp"
-)
-
-list (APPEND PDAL_CPP ${PDAL_CONFIG_CPP} )
-list (APPEND PDAL_HPP ${PDAL_CONFIG_HPP} )
-
-# Standard include directory of PDAL library
-
-###############################################################################
-# Targets settings
-
-set(PDAL_SOURCES
-  ${PDAL_HPP}
-  ${PDAL_CPP}
-  ${PDAL_TARGET_OBJECTS}
-)
-
-# see https://github.com/PDAL/PDAL/issues/108 for discussion on this
-#SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "../pdal_defines.h;../include/pdal/pdal_defines.h")
-
-# NOTE:
-# This hack is required to correctly link static into shared library.
-# Such practice is not recommended as not portable, instead each library,
-# static and shared should be built from sources separately.
-#if(UNIX)
-#  add_definitions("-fPIC")
-#endif()
-
-if(WIN32)
-    if (NOT WITH_STATIC_LASZIP)
-        add_definitions("-DLASZIP_DLL_IMPORT=1")
-    endif()
-endif()
-
-PDAL_ADD_LIBRARY(${PDAL_BASE_LIB_NAME} ${PDAL_SOURCES})
-
-set_target_properties(${PDAL_BASE_LIB_NAME} PROPERTIES VERSION "${PDAL_BUILD_VERSION}"
-    SOVERSION "${PDAL_API_VERSION}"
-    CLEAN_DIRECT_OUTPUT 1)
-
-if (WITH_LASZIP)
-    target_link_libraries(${PDAL_BASE_LIB_NAME} PUBLIC ${LASZIP_LIBRARY})
-endif()
-
-if (PDAL_HAVE_LIBXML2)
-    target_link_libraries(${PDAL_BASE_LIB_NAME} PUBLIC ${LIBXML2_LIBRARIES})
-endif()
-
-#
-# On OSX we reexport the symbols in libpdal_util.dylib into libpdalcpp.dylib
-# so that users only need link libpdalcpp.
-#
-if (APPLE)
-    set(PDAL_REEXPORT "-Wl,-reexport_library,$<TARGET_FILE:${PDAL_UTIL_LIB_NAME}>")
-    #
-    # This allows the rpath reference for the reexported library (above) to
-    # be found.
-    #
-    set(PDAL_LIBDIR "-L$<TARGET_FILE_DIR:${PDAL_UTIL_LIB_NAME}>")
-endif()
-
-target_link_libraries(${PDAL_BASE_LIB_NAME}
-    PUBLIC
-        ${CMAKE_THREAD_LIBS_INIT}
-        ${GDAL_LIBRARY}
-        ${GEOS_LIBRARY}
-        ${ZLIB_LIBRARIES}
-        ${CURL_LIBRARIES}
-    PRIVATE
-        ${PDAL_REEXPORT}
-        ${PDAL_UTIL_LIB_NAME}
-        ${PDAL_ARBITER_LIB_NAME}
-    ${JSON_CPP_LINK_TYPE}
-        ${PDAL_JSONCPP_LIB_NAME}
-    INTERFACE
-        ${PDAL_LIBDIR}
-)
-
-if (WITH_GEOTIFF)
-    target_link_libraries(${PDAL_BASE_LIB_NAME} PUBLIC ${GEOTIFF_LIBRARY})
-endif()
-
-if (WIN32)
-    target_link_libraries(${PDAL_BASE_LIB_NAME} PUBLIC ws2_32)
-endif()
-
-###############################################################################
-# Targets installation
-
-#
-# On Linux, we install a linker script as libpdalcpp.so.  That file
-# specifies linking in libpdal_base.so and libpdal_util.so.  This allows
-# users to link a single library, libpdalcpp
-#
-if (UNIX AND NOT APPLE)
-    set(LIBNAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PDAL_LIB_NAME})
-    install(FILES ${LIBNAME} DESTINATION ${PDAL_LIB_INSTALL_DIR}
-        RENAME ${LIBNAME}${CMAKE_SHARED_LIBRARY_SUFFIX})
-endif()
-
-install(DIRECTORY "${PDAL_HEADERS_DIR}/"
-  DESTINATION "${PDAL_INCLUDE_INSTALL_DIR}"
-  PATTERN "/CMakeFiles/*" EXCLUDE
-  )
-install(FILES ${DIMENSION_OUTFILE} 
-  DESTINATION "${PDAL_INCLUDE_INSTALL_DIR}"
-  )
diff --git a/src/DbWriter.cpp b/src/DbWriter.cpp
deleted file mode 100644
index c4bdec8..0000000
--- a/src/DbWriter.cpp
+++ /dev/null
@@ -1,271 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014,  Hobu Inc., hobu at hobu.co
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. nor the names of its contributors
-*       may be used to endorse or promote products derived from this
-*       software without specific prior written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/DbWriter.hpp>
-#include <pdal/util/Utils.hpp>
-
-namespace pdal
-{
-
-void DbWriter::addArgs(ProgramArgs& args)
-{
-    args.add("output_dims", "Output dimensions", m_outputDims);
-    m_scaling.addArgs(args);
-}
-
-// Build the list of dimensions for the output schema.
-// Placing this here allows validation of dimensions before execution begins.
-void DbWriter::prepared(PointTableRef table)
-{
-    using namespace Dimension;
-
-    PointLayoutPtr layout = table.layout();
-
-    if (m_outputDims.empty())
-    {
-        for (auto& dimType : layout->dimTypes())
-            m_dbDims.push_back(XMLDim(dimType, layout->dimName(dimType.m_id)));
-        return;
-    }
-
-    DimTypeList dims;
-    for (std::string& s : m_outputDims)
-    {
-        DimType dt = layout->findDimType(s);
-        if (dt.m_id == Id::Unknown)
-        {
-            std::ostringstream oss;
-            oss << "Invalid dimension '" << s << "' specified for "
-                "'output_dims' option.";
-            throw pdal_error(oss.str());
-        }
-        m_dbDims.push_back(XMLDim(dt, layout->dimName(dt.m_id)));
-    }
-}
-
-
-void DbWriter::ready(PointTableRef /*table*/)
-{
-    using namespace Dimension;
-
-    // Determine if X, Y and Z values should be written as Signed32 along with
-    // a scale factor and offset instead of being written as Double.
-    m_locationScaling = m_scaling.nonstandard();
-
-    auto cmp = [](const XMLDim& d1, const XMLDim& d2) -> bool
-    {
-        long id1 = Utils::toNative(d1.m_dimType.m_id);
-        long id2 = Utils::toNative(d2.m_dimType.m_id);
-
-        const auto isXyz([](long native)->bool
-        {
-            const Id e(static_cast<Id>(native));
-            return (e == Id::X || e == Id::Y || e == Id::Z);
-        });
-
-        // Put X, Y and Z at the end of the list.
-        if (isXyz(id1))
-            id1 += 1000000;
-        if (isXyz(id2))
-            id2 += 1000000;
-        return id1 < id2;
-    };
-
-    // Sort the dimensions so that X, Y & Z are at the end.
-    std::sort(m_dbDims.begin(), m_dbDims.end(), cmp);
-
-    // Suck the dimTypes out of the dbDims so that they can be used to
-    // retrieve data from the point table.
-    // Set the packed type into the dbDim if necessary and save off the
-    // index of X, Y and Z for location scaling.
-    m_dimTypes.clear();
-    m_xOffsets = std::make_pair(-1, -1);
-    m_yOffsets = std::make_pair(-1, -1);
-    m_zOffsets = std::make_pair(-1, -1);
-    m_packedPointSize = 0;
-    m_dbPointSize = 0;
-    for (auto& xmlDim : m_dbDims)
-    {
-        m_dimTypes.push_back(xmlDim.m_dimType);
-        DimType& dt = m_dimTypes.back();
-        // Dim types are stored in the map to allow fast access in readField.
-        m_dimMap[Utils::toNative(dt.m_id)] = dt;
-
-        if (m_locationScaling)
-        {
-            if (xmlDim.m_dimType.m_id == Id::X)
-            {
-                xmlDim.m_dimType.m_xform = m_scaling.m_xXform;
-                xmlDim.m_dimType.m_type = Type::Signed32;
-                m_xOffsets = std::make_pair(m_packedPointSize, m_dbPointSize);
-            }
-            if (xmlDim.m_dimType.m_id == Id::Y)
-            {
-                xmlDim.m_dimType.m_xform = m_scaling.m_yXform;
-                xmlDim.m_dimType.m_type = Type::Signed32;
-                m_yOffsets = std::make_pair(m_packedPointSize, m_dbPointSize);
-            }
-            if (xmlDim.m_dimType.m_id == Id::Z)
-            {
-                xmlDim.m_dimType.m_xform = m_scaling.m_zXform;
-                xmlDim.m_dimType.m_type = Type::Signed32;
-                m_zOffsets = std::make_pair(m_packedPointSize, m_dbPointSize);
-            }
-        }
-        m_packedPointSize += Dimension::size(dt.m_type);
-        m_dbPointSize += Dimension::size(xmlDim.m_dimType.m_type);
-    }
-}
-
-
-/// Make sure that computed offsets are stored in the schema.
-void DbWriter::setAutoXForm(const PointViewPtr view)
-{
-    using namespace Dimension;
-
-    m_scaling.setAutoXForm(view);
-    for (auto& xmlDim : m_dbDims)
-    {
-        if (xmlDim.m_dimType.m_id == Id::X)
-            xmlDim.m_dimType.m_xform = m_scaling.m_xXform;
-        if (xmlDim.m_dimType.m_id == Id::Y)
-            xmlDim.m_dimType.m_xform = m_scaling.m_yXform;
-        if (xmlDim.m_dimType.m_id == Id::Z)
-            xmlDim.m_dimType.m_xform = m_scaling.m_zXform;
-    }
-}
-
-
-/// Read a field from a PointView and write its value as formatted for output
-/// to the DB schema to the location as requested.
-/// \param[in] view     PointView to read from.
-/// \param[in] pos      Location in which to store field value.
-/// \param[in] id       ID of the dimension to read.
-/// \param[in] idx      Index of point to read.
-/// \return  Size of field as read.
-size_t DbWriter::readField(const PointView& view, char *pos,
-    Dimension::Id id, PointId idx)
-{
-    using namespace Dimension;
-
-    DimType& dt = m_dimMap[(int)id];
-    size_t size = Dimension::size(dt.m_type);
-
-    // Using the ID instead of a dimType as the arugment hides the complication
-    // of the "type" of the dimension to retrieve in the case of location
-    // scaling.
-    view.getField(pos, id, dt.m_type, idx);
-
-    auto iconvert = [pos](const XForm& xform, Dimension::Id dim)
-    {
-        double d;
-        int32_t i;
-
-        memcpy(&d, pos, sizeof(double));
-
-        d = xform.toScaled(d);
-        if (!Utils::numericCast(d, i))
-        {
-            std::ostringstream oss;
-            oss << "Unable to convert double to int32 for packed DB output: ";
-            oss << Dimension::name(dim) << ": (" << d << ").";
-            throw pdal_error(oss.str());
-        }
-        memcpy(pos, &i, sizeof(int32_t));
-    };
-
-    if (m_locationScaling)
-    {
-        // For X, Y or Z.
-        if (id == Id::X)
-        {
-            iconvert(m_scaling.m_xXform, Id::X);
-            size = sizeof(int32_t);
-        }
-        else if (id == Id::Y)
-        {
-            iconvert(m_scaling.m_yXform, Id::Y);
-            size = sizeof(int32_t);
-        }
-        else if (id == Id::Z)
-        {
-            iconvert(m_scaling.m_zXform, Id::Z);
-            size = sizeof(int32_t);
-        }
-    }
-    return size;
-}
-
-
-/// Read a point's data packed into a buffer.
-/// \param[in] view  PointView to read from.
-/// \param[in] idx  Index of point to read.
-/// \param[in] outbuf  Buffer to write to.
-/// \return  Number of bytes written to buffer.
-size_t DbWriter::readPoint(const PointView& view, PointId idx, char *outbuf)
-{
-    using namespace Dimension;
-
-    // Read the data for the output dimensions from the view into the outbuf.
-    view.getPackedPoint(m_dimTypes, idx, outbuf);
-
-    auto iconvert = [](const XForm& xform, Id dim,
-        const char *inpos, char *outpos)
-    {
-        double d;
-        int32_t i;
-
-        memcpy(&d, inpos, sizeof(double));
-        d = xform.toScaled(d);
-        if (!Utils::numericCast(d, i))
-        {
-            std::ostringstream oss;
-            oss << "Unable to convert double to int32 for packed DB output: ";
-            oss << Dimension::name(dim) << ": (" << d << ").";
-            throw pdal_error(oss.str());
-        }
-        memcpy(outpos, &i, sizeof(int32_t));
-    };
-
-    if (m_xOffsets.first >= 0)
-        iconvert(m_scaling.m_xXform, Id::X, outbuf + m_xOffsets.first,
-            outbuf + m_xOffsets.second);
-    if (m_yOffsets.first >= 0)
-        iconvert(m_scaling.m_yXform, Id::Y, outbuf + m_yOffsets.first,
-            outbuf + m_yOffsets.second);
-    if (m_zOffsets.first >= 0)
-        iconvert(m_scaling.m_zXform, Id::Z, outbuf + m_zOffsets.first,
-            outbuf + m_zOffsets.second);
-    return m_dbPointSize;
-}
-
-} // namespace pdal
diff --git a/src/Dimension.json b/src/Dimension.json
deleted file mode 100644
index 5e756a2..0000000
--- a/src/Dimension.json
+++ /dev/null
@@ -1,331 +0,0 @@
-{ "dimensions": [
-    {
-    "name": "X",
-    "type": "double",
-    "description": "X coordinate"
-    },
-    {
-    "name": "Y",
-    "type": "double",
-    "description": "Y coordinate"
-    },
-    {
-    "name": "Z",
-    "type": "double",
-    "description": "Z coordinate"
-    },
-    {
-    "name": "Intensity",
-    "type": "uint16",
-    "description": "Representation of the pulse return magnitude"
-    },
-    {
-    "name": "Amplitude",
-    "type": "float",
-    "description": "This is the ratio of the received power to the power received at the detection limit expressed in dB"
-    },
-    {
-    "name": "Reflectance",
-    "type": "float",
-    "description": "Ratio of the received power to the power that would be received from a white diffuse target at the same distance expressed in dB. The reflectance represents a range independent property of the target.  The surface normal of this target is assumed to be in parallel to the laser beam direction."
-    },
-    {
-    "name": "ReturnNumber",
-    "type": "uint8",
-    "description": "Pulse return number for a given output pulse. A given output laser pulse can have many returns, and they must be marked in order, starting with 1"
-    },
-    {
-    "name": "NumberOfReturns",
-    "type": "uint8",
-    "description": "Total number of returns for a given pulse."
-    },
-    {
-    "name": "ScanDirectionFlag",
-    "type": "uint8",
-    "description": "Direction at which the scanner mirror was traveling at the time of the output pulse. A value of 1 is a positive scan direction, and a bit value of 0 is a negative scan direction, where positive scan direction is a scan moving from the left side of the in-track direction to the right side and negative the opposite"
-    },
-    {
-    "name": "EdgeOfFlightLine",
-    "type": "uint8",
-    "description": "Indicates the end of scanline before a direction change with a value of 1 - 0 otherwise"
-    },
-    {
-    "name": "Classification",
-    "type": "uint8",
-    "description": "ASPRS classification.  0 for no classification.  See LAS specification for details."
-    },
-    {
-    "name": "ScanAngleRank",
-    "alt_names": "ScanAngle",
-    "type": "float",
-    "description": "Angle degree at which the laster point was output from the system, including the roll of the aircraft.  The scan angle is based on being nadir, and -90 the left side of the aircraft in the direction of flight"
-    },
-    {
-    "name": "UserData",
-    "type": "uint8",
-    "description": "Unspecified user data"
-    },
-    {
-    "name": "PointSourceId",
-    "type": "uint16",
-    "description": "File source ID from which the point originated.  Zero indicates that the point originated in the current file"
-    },
-    {
-    "name": "Red",
-    "type": "uint16",
-    "description": "Red image channel value"
-    },
-    {
-    "name": "Green",
-    "type": "uint16",
-    "description": "Green image channel value"
-    },
-    {
-    "name": "Blue",
-    "type": "uint16",
-    "description": "Blue image channel value"
-    },
-    {
-    "name": "GpsTime",
-    "type": "double",
-    "description": "GPS time that the point was acquired"
-    },
-    {
-    "name": "InternalTime",
-    "type": "double",
-    "description": "Scanner's internal time when the point was acquired, in seconds"
-    },
-    {
-    "name": "OffsetTime",
-    "alt_names": "Time",
-    "type": "uint32",
-    "description": "Milliseconds from first acquired point"
-    },
-    {
-    "name": "IsPpsLocked",
-    "type": "uint8",
-    "description": "The external PPS signal was found to be synchronized at the time of the current laser shot."
-    },
-    {
-    "name": "StartPulse",
-    "type": "int32",
-    "description": "Relative pulse signal strength"
-    },
-    {
-    "name": "ReflectedPulse",
-    "type": "int32",
-    "description": "Relative reflected pulse signal strength"
-    },
-    {
-    "name": "Pdop",
-    "type": "float",
-    "description": "GPS PDOP (dilution of precision)"
-    },
-    {
-    "name": "Pitch",
-    "type": "float",
-    "description": "Pitch in degrees"
-    },
-    {
-    "name": "Roll",
-    "type": "float",
-    "description": "Roll in degrees"
-    },
-    {
-    "name": "PulseWidth",
-    "type": "float",
-    "description": "Laser received pulse width (digitizer samples)"
-    },
-    {
-    "name": "Deviation",
-    "type": "float",
-    "description": "A larger value for deviation indicates larger distortion."
-    },
-    {
-    "name": "PassiveSignal",
-    "type": "int32",
-    "description": "Relative passive signal"
-    },
-    {
-    "name": "BackgroundRadiation",
-    "type": "float",
-    "description": "A measure of background radiation."
-    },
-    {
-    "name": "PassiveX",
-    "type": "double",
-    "description": "Passive X footprint"
-    },
-    {
-    "name": "PassiveY",
-    "type": "double",
-    "description": "Passive Y footprint"
-    },
-    {
-    "name": "PassiveZ",
-    "type": "double",
-    "description": "Passive Z footprint"
-    },
-    {
-    "name": "XVelocity",
-    "type": "double",
-    "description": "X Velocity"
-    },
-    {
-    "name": "YVelocity",
-    "type": "double",
-    "description": "Y Velocity"
-    },
-    {
-    "name": "ZVelocity",
-    "type": "double",
-    "description": "Z Velocity"
-    },
-    {
-    "name": "Azimuth",
-    "alt_names": "PlatformHeading",
-    "type": "double",
-    "description": "Scanner azimuth"
-    },
-    {
-    "name": "WanderAngle",
-    "type": "double",
-    "description": "Wander Angle"
-    },
-    {
-    "name": "XBodyAccel",
-    "type": "double",
-    "description": "X Body Acceleration"
-    },
-    {
-    "name": "YBodyAccel",
-    "type": "double",
-    "description": "Y Body Acceleration"
-    },
-    {
-    "name": "ZBodyAccel",
-    "type": "double",
-    "description": "Z Body Acceleration"
-    },
-    {
-    "name": "XBodyAngRate",
-    "type": "double",
-    "description": "X Body Angle Rate"
-    },
-    {
-    "name": "YBodyAngRate",
-    "type": "double",
-    "description": "Y Body Angle Rate"
-    },
-    {
-    "name": "ZBodyAngRate",
-    "type": "double",
-    "description": "Z Body Angle Rate"
-    },
-    {
-    "name": "Flag",
-    "type": "uint8",
-    "description": "Flag"
-    },
-    {
-    "name": "Mark",
-    "type": "uint8",
-    "description": "Mark"
-    },
-    {
-    "name": "Alpha",
-    "type": "uint16",
-    "description": "Alpha"
-    },
-    {
-    "name": "EchoRange",
-    "type": "double",
-    "description": "Echo Range"
-    },
-    {
-    "name": "ScanChannel",
-    "type": "uint8",
-    "description": "Scan Channel"
-    },
-    {
-    "name": "Infrared",
-    "alt_names": "NearInfrared",
-    "type": "uint16",
-    "description": "Infrared"
-    },
-    {
-    "name": "HeightAboveGround",
-    "type": "double",
-    "description": "Height Above Ground"
-    },
-    {
-    "name": "ClassFlags",
-    "type": "uint8",
-    "description": "Class Flags"
-    },
-    {
-    "name": "LvisLfid",
-    "alt_names": "Lvis_Lfid",
-    "type": "uint64",
-    "description": "LVIS_LFID"
-    },
-    {
-    "name": "ShotNumber",
-    "type": "uint64",
-    "description": "Shot Number"
-    },
-    {
-    "name": "LongitudeCentroid",
-    "alt_names": "Longitude_Centroid",
-    "type": "double",
-    "description": "Longitude Centroid"
-    },
-    {
-    "name": "LatitudeCentroid",
-    "alt_names": "Latitude_Centroid",
-    "type": "double",
-    "description": "Latitude Centroid"
-    },
-    {
-    "name": "ElevationCentroid",
-    "alt_names": "Elevation_Centroid",
-    "type": "double",
-    "description": "Elevation Centroid"
-    },
-    {
-    "name": "LongitudeLow",
-    "alt_names": "Longitude_Low",
-    "type": "double",
-    "description": "Longitude Low"
-    },
-    {
-    "name": "LatitudeLow",
-    "alt_names": "Latitude_Low",
-    "type": "double",
-    "description": "Latitude Low"
-    },
-    {
-    "name": "ElevationLow",
-    "alt_names": "Elevation_Low",
-    "type": "double",
-    "description": "Elevation Low"
-    },
-    {
-    "name": "LongitudeHigh",
-    "alt_names": "Longitude_High",
-    "type": "double",
-    "description": "Longitude High"
-    },
-    {
-    "name": "LatitudeHigh",
-    "alt_names": "Latitude_High",
-    "type": "double",
-    "description": "Latitude High"
-    },
-    {
-    "name": "ElevationHigh",
-    "alt_names": "Elevation_High",
-    "type": "double",
-    "description": "Elevation High"
-    }
-] }
diff --git a/src/DynamicLibrary.cpp b/src/DynamicLibrary.cpp
deleted file mode 100755
index 0e9c6ef..0000000
--- a/src/DynamicLibrary.cpp
+++ /dev/null
@@ -1,127 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-// The DynamicLibrary was modeled very closely after the work of Gigi Sayfan in
-// the Dr. Dobbs article:
-// http://www.drdobbs.com/cpp/building-your-own-plugin-framework-part/206503957
-// The original work was released under the Apache License v2.
-
-#ifdef _WIN32
-  #include <Windows.h>
-#else
-  #include <dlfcn.h>
-#endif
-
-#include "DynamicLibrary.hpp"
-#include <sstream>
-#include <iostream>
-
-namespace pdal
-{
-
-DynamicLibrary::~DynamicLibrary()
-{
-    if (m_handle)
-    {
-#ifndef _WIN32
-        ::dlclose(m_handle);
-#else
-        ::FreeLibrary((HMODULE)m_handle);
-#endif
-    }
-}
-
-
-void DynamicLibrary::clear()
-{
-    m_handle = NULL;
-}
-
-
-DynamicLibrary *DynamicLibrary::load(const std::string &name, 
-    std::string &errorString)
-{
-    if (name.empty()) 
-    {
-        errorString = "Empty path.";
-        return NULL;
-    }
-
-    void *handle = NULL;
-
-#ifdef _WIN32
-    handle = ::LoadLibraryA(name.c_str());
-    if (handle == NULL)
-    {
-        DWORD errorCode = ::GetLastError();
-        std::stringstream ss;
-        ss << std::string("LoadLibrary(") << name 
-            << std::string(") Failed. errorCode: ") 
-            << errorCode; 
-        errorString = ss.str();
-    }
-#else
-    handle = ::dlopen(name.c_str(), RTLD_NOW);
-    if (!handle) 
-    {
-        std::string dlErrorString;
-        const char *zErrorString = ::dlerror();
-        if (zErrorString)
-            dlErrorString = zErrorString;
-        errorString += "Failed to load \"" + name + '"';
-        if (dlErrorString.size())
-            errorString += ": " + dlErrorString;
-        return NULL;
-    }
-#endif
-    return new DynamicLibrary(handle);
-}
-
-
-void *DynamicLibrary::getSymbol(const std::string& symbol)
-{
-    if (!m_handle)
-        return NULL;
-
-    void *sym;
-#ifdef _WIN32
-    sym = ::GetProcAddress((HMODULE)m_handle, symbol.c_str());
-#else
-    sym = ::dlsym(m_handle, symbol.c_str());
-#endif
-    return sym;
-}
-
-} // namespace pdal
-
diff --git a/src/Eigen.cpp b/src/Eigen.cpp
deleted file mode 100644
index e9f33e6..0000000
--- a/src/Eigen.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/Eigen.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/util/Bounds.hpp>
-
-#include <Eigen/Dense>
-
-#include <vector>
-
-namespace pdal
-{
-
-Eigen::Vector3f computeCentroid(PointView& view, std::vector<PointId> ids)
-{
-    using namespace Eigen;
-
-    auto n = ids.size();
-
-    double mx, my, mz;
-    mx = my = mz = 0.0;
-    for (auto const& j : ids)
-    {
-        mx += view.getFieldAs<double>(Dimension::Id::X, j);
-        my += view.getFieldAs<double>(Dimension::Id::Y, j);
-        mz += view.getFieldAs<double>(Dimension::Id::Z, j);
-    }
-
-    Vector3f centroid;
-    centroid << mx/n, my/n, mz/n;
-
-    return centroid;
-}
-
-Eigen::Matrix3f computeCovariance(PointView& view, std::vector<PointId> ids)
-{
-    using namespace Eigen;
-
-    auto n = ids.size();
-
-    Vector3f centroid = computeCentroid(view, ids);
-
-    // demean the neighborhood
-    MatrixXf A(3, n);
-    size_t k = 0;
-    for (auto const& j : ids)
-    {
-        A(0, k) = view.getFieldAs<double>(Dimension::Id::X, j) - centroid[0];
-        A(1, k) = view.getFieldAs<double>(Dimension::Id::Y, j) - centroid[1];
-        A(2, k) = view.getFieldAs<double>(Dimension::Id::Z, j) - centroid[2];
-        k++;
-    }
-
-    return A * A.transpose();
-}
-
-uint8_t computeRank(PointView& view, std::vector<PointId> ids, double threshold)
-{
-    using namespace Eigen;
-
-    Matrix3f B = computeCovariance(view, ids);
-
-    JacobiSVD<Matrix3f> svd(B);
-    svd.setThreshold(threshold);
-    
-    return static_cast<uint8_t>(svd.rank());
-}
-
-Eigen::MatrixXd createDSM(PointView& view, int rows, int cols, double cell_size,
-                          BOX2D bounds)
-{
-    using namespace Dimension;
-    using namespace Eigen;
-    
-    MatrixXd ZImin(rows, cols);
-    ZImin.setConstant(std::numeric_limits<double>::quiet_NaN());
-
-    int maxrow = bounds.miny + rows * cell_size;
-    
-    auto clamp = [](int t, int min, int max)
-    {
-        return ((t < min) ? min : ((t > max) ? max : t));
-    };
-    
-    auto getColIndex = [&bounds, &cell_size](double x)
-    {
-        return static_cast<int>(floor((x - bounds.minx) / cell_size));
-    };
-
-    auto getRowIndex = [&maxrow, &cell_size](double y)
-    {
-        return static_cast<int>(floor((maxrow - y) / cell_size));
-    };
-    
-    for (PointId i = 0; i < view.size(); ++i)
-    {
-        double x = view.getFieldAs<double>(Id::X, i);
-        double y = view.getFieldAs<double>(Id::Y, i);
-        double z = view.getFieldAs<double>(Id::Z, i);
-
-        int c = clamp(getColIndex(x), 0, cols-1);
-        int r = clamp(getRowIndex(y), 0, rows-1);
-
-        if (z < ZImin(r, c) || std::isnan(ZImin(r, c)))
-            ZImin(r, c) = z;
-    }
-
-    return ZImin;
-}
-
-} // namespace pdal
diff --git a/src/Filter.cpp b/src/Filter.cpp
deleted file mode 100644
index 4ca7b10..0000000
--- a/src/Filter.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/Filter.hpp>
-#include <pdal/PipelineWriter.hpp>
-
-namespace pdal
-{
-
-pdalboost::property_tree::ptree Filter::serializePipeline() const
-{
-    return serialize(getName(), "Filter");
-}
-
-} // namespace pdal
-
diff --git a/src/GDALUtils.cpp b/src/GDALUtils.cpp
deleted file mode 100644
index bcbeb80..0000000
--- a/src/GDALUtils.cpp
+++ /dev/null
@@ -1,593 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/GDALUtils.hpp>
-#include <pdal/SpatialReference.hpp>
-#include <pdal/util/Utils.hpp>
-
-#include <functional>
-#include <map>
-#include <mutex>
-
-#include <ogr_spatialref.h>
-
-#ifdef PDAL_COMPILER_MSVC
-#  pragma warning(disable: 4127)  // conditional expression is constant
-#endif
-
-namespace pdal
-{
-namespace gdal
-{
-
-/**
-  Reproject a bounds box from a source projection to a destination.
-  \param box  Bounds box to be reprojected in-place.
-  \param srcSrs  String in WKT or other suitable format of box coordinates.
-  \param dstSrs  String in WKT or other suitable format to which
-    coordinates should be projected.
-  \return  Whether the reprojection was successful or not.
-*/
-bool reprojectBounds(BOX3D& box, const std::string& srcSrs,
-    const std::string& dstSrs)
-{
-    OGRSpatialReference src;
-    OGRSpatialReference dst;
-
-    OGRErr srcOk = OSRSetFromUserInput(&src, srcSrs.c_str());
-    OGRErr dstOk = OSRSetFromUserInput(&dst, dstSrs.c_str());
-    if (srcOk != OGRERR_NONE || dstOk != OGRERR_NONE)
-        return false;
-
-    OGRCoordinateTransformationH transform =
-        OCTNewCoordinateTransformation(&src, &dst);
-
-    bool ok = (OCTTransform(transform, 1, &box.minx, &box.miny, &box.minz) &&
-        OCTTransform(transform, 1, &box.maxx, &box.maxy, &box.maxz));
-    OCTDestroyCoordinateTransformation(transform);
-    return ok;
-}
-
-
-std::string lastError()
-{
-    return CPLGetLastErrorMsg();
-}
-
-
-static ErrorHandler* s_gdalErrorHandler= 0;
-
-void registerDrivers()
-{
-    static std::once_flag flag;
-
-    auto init = []() -> void
-    {
-        GDALAllRegister();
-        OGRRegisterAll();
-    };
-
-    std::call_once(flag, init);
-}
-
-
-void unregisterDrivers()
-{
-    GDALDestroyDriverManager();
-}
-
-
-ErrorHandler& ErrorHandler::getGlobalErrorHandler()
-{
-    static std::once_flag flag;
-
-    auto init = []()
-    {
-       s_gdalErrorHandler = new ErrorHandler();
-    };
-
-    std::call_once(flag, init);
-    return *s_gdalErrorHandler;
-}
-
-ErrorHandler::ErrorHandler() : m_errorNum(0)
-{
-    std::string value;
-
-    // Will return thread-local setting
-    const char* set = CPLGetConfigOption("CPL_DEBUG", "");
-    m_cplSet = (bool)set ;
-    m_debug = m_cplSet;
-
-    // Push on a thread-local error handler
-    CPLSetErrorHandler(&ErrorHandler::trampoline);
-}
-
-
-void ErrorHandler::set(LogPtr log, bool debug)
-{
-    setLog(log);
-    setDebug(debug);
-}
-
-
-void ErrorHandler::setLog(LogPtr log)
-{
-    m_log = log;
-}
-
-
-void ErrorHandler::setDebug(bool debug)
-{
-    m_debug = debug;
-
-    if (debug)
-        CPLSetThreadLocalConfigOption("CPL_DEBUG", "ON");
-    else
-        CPLSetThreadLocalConfigOption("CPL_DEBUG", NULL);
-}
-
-
-int ErrorHandler::errorNum()
-{
-    int errorNum = m_errorNum;
-    return errorNum;
-}
-
-void ErrorHandler::handle(::CPLErr level, int num, char const* msg)
-{
-    std::ostringstream oss;
-
-    m_errorNum = num;
-    if (level == CE_Failure || level == CE_Fatal)
-    {
-        oss << "GDAL failure (" << num << ") " << msg;
-        if (m_log)
-            m_log->get(LogLevel::Error) << oss.str() << std::endl;
-    }
-    else if (m_debug && level == CE_Debug)
-    {
-        oss << "GDAL debug: " << msg;
-        if (m_log)
-            m_log->get(LogLevel::Debug) << oss.str() << std::endl;
-    }
-}
-
-
-struct InvalidBand {};
-struct CantReadBlock {};
-
-/*
-  Reads a GDAL band into a vector.
-*/
-class BandReader
-{
-public:
-    /*
-      Constructor that populates various band information.
-
-      \param ds  GDAL dataset handle.
-      \param bandNum  Band number.  Band numbers start at 1.
-    */
-    BandReader(GDALDatasetH ds, int bandNum);
-
-    /*
-      Read the band into the vector.  Reads a block at a time.  Each
-      block is either fully populated with data or a partial block.
-      Partial blocks appear at the X and Y margins when the total size in
-      the doesn't divide evenly by the block size for both the X and Y
-      dimensions.
-
-      \ptData Vector into which the data should be read.  The vector is
-        resized as necessary.
-    */
-    void read(std::vector<uint8_t>& ptData);
-private:
-    GDALDatasetH m_ds;  /// Dataset handle
-    int m_bandNum;  /// Band number.  Band numbers start at 1.
-    GDALRasterBandH m_band;  /// Band handle
-    int m_xTotalSize, m_yTotalSize;  /// Total size (x and y) of the raster
-    int m_xBlockSize, m_yBlockSize;  /// Size (x and y) of blocks
-    int m_xBlockCnt, m_yBlockCnt;    /// Number of blocks in each direction
-    size_t m_eltSize;                /// Size in bytes of each band element.
-    std::vector<uint8_t> m_buf;      /// Block read buffer.
-
-    /*
-      Read a block's worth of data.
-
-      \param x  X coordinate of block to read.
-      \param y  Y coordinate of block to read.
-      \param data  Pointer to vector in which to store data.  Vector must
-        be sufficiently sized to hold all data.
-    */
-    void readBlock(int x, int y, uint8_t *data);
-};
-
-
-/*
-  Create a band reader for a single bad of a GDAL dataset.
-
-  \param ds  GDAL dataset handle.
-  \param bandNum  Band number (1-indexed).
-*/
-BandReader::BandReader(GDALDatasetH ds, int bandNum) : m_ds(ds),
-    m_bandNum(bandNum), m_xBlockSize(0), m_yBlockSize(0)
-{
-    m_band = GDALGetRasterBand(m_ds, m_bandNum);
-    if (!m_band)
-        throw InvalidBand();
-
-    // Perhaps raster bands can have different sizes than the raster itself?
-    m_xTotalSize = GDALGetRasterBandXSize(m_band);
-    m_yTotalSize = GDALGetRasterBandYSize(m_band);
-
-    GDALGetBlockSize(m_band, &m_xBlockSize, &m_yBlockSize);
-
-    m_xBlockCnt = ((m_xTotalSize - 1) / m_xBlockSize) + 1;
-    m_yBlockCnt = ((m_yTotalSize - 1) / m_yBlockSize) + 1;
-}
-
-
-/*
-  Read a raster band into an array.
-
-  \param ptData  Vector to contain raster (Y major - X varies fastest).
-    ptData is resized to fit data in the band.
-*/
-void BandReader::read(std::vector<uint8_t>& ptData)
-{
-    GDALDataType t = GDALGetRasterDataType(m_band);
-    m_eltSize = GDALGetDataTypeSize(t) / CHAR_BIT;
-    m_buf.resize(m_xBlockSize * m_yBlockSize * m_eltSize);
-    ptData.resize(m_xTotalSize * m_yTotalSize * m_eltSize);
-
-    uint8_t *data = ptData.data();
-    for (int y = 0; y < m_yBlockCnt; ++y)
-        for (int x = 0; x < m_xBlockCnt; ++x)
-            readBlock(x, y, data);
-}
-
-
-/*
-  Read a block's worth of data.
-
-  Read data into a block-sized buffer.  Then copy data from the block buffer
-  into the destination array at the proper location to build a complete
-  raster.
-
-  \param x  X coordinate of the block to read.
-  \param y  Y coordinate of the block to read.
-  \param data  Pointer to the data vector that contains the raster information.
-*/
-void BandReader::readBlock(int x, int y, uint8_t *data)
-{
-    if (GDALReadBlock(m_band, x, y, m_buf.data()) != CPLE_None)
-        throw CantReadBlock();
-
-    int xWidth = 0;
-    if (x == m_xBlockCnt - 1)
-        xWidth = m_xTotalSize % m_xBlockSize;
-    if (xWidth == 0)
-        xWidth = m_xBlockSize;
-
-    int yHeight = 0;
-    if (y == m_yBlockCnt - 1)
-        yHeight = m_yTotalSize % m_yBlockSize;
-    if (yHeight == 0)
-        yHeight = m_yBlockSize;
-
-    uint8_t *bp = m_buf.data();
-    // Go through rows copying data.  Increment the buffer pointer by the
-    // width of the row.
-    for (int row = 0; row < yHeight; ++row)
-    {
-        int wholeRows = m_xTotalSize * ((y * m_yBlockSize) + row);
-        int partialRows = m_xBlockSize * x;
-        uint8_t *dp = data + ((wholeRows + partialRows) * m_eltSize);
-        std::copy(bp, bp + (xWidth * m_eltSize), dp);
-
-        // Blocks are always full-sized, even if only some of the data is valid,
-        // so we use m_xBlockSize instead of xWidth.
-        bp += (m_xBlockSize * m_eltSize);
-    }
-}
-
-
-Raster::Raster(const std::string& filename)
-    : m_filename(filename)
-    , m_raster_x_size(0)
-    , m_raster_y_size(0)
-    , m_band_count(0)
-    , m_ds(0)
-{
-    m_forward_transform.fill(0);
-    m_forward_transform[1] = 1;
-    m_forward_transform[5] = 1;
-    m_inverse_transform.fill(0);
-    m_inverse_transform[1] = 1;
-    m_inverse_transform[5] = 1;
-}
-
-
-GDALError Raster::open()
-{
-    GDALError error = GDALError::None;
-    if (m_ds)
-        return error;
-
-    m_ds = GDALOpen(m_filename.c_str(), GA_ReadOnly);
-    if (m_ds == NULL)
-    {
-        m_errorMsg = "Unable to open GDAL datasource '" + m_filename + "'.";
-        return GDALError::CantOpen;
-    }
-
-    // GDAL docs state that we should return an identity transform, even
-    // on error, which should let things work.
-    if (GDALGetGeoTransform(m_ds, &(m_forward_transform.front())) != CE_None)
-    {
-        m_errorMsg = "Unable to get geotransform for raster '" +
-            m_filename + "'.";
-        error = GDALError::NoTransform;
-    }
-
-    if (!GDALInvGeoTransform(&(m_forward_transform.front()),
-        &(m_inverse_transform.front())))
-    {
-        m_errorMsg = "Geotransform for raster '" + m_filename + "' not "
-            "intertible";
-        error = GDALError::NotInvertible;
-    }
-
-    m_raster_x_size = GDALGetRasterXSize(m_ds);
-    m_raster_y_size = GDALGetRasterYSize(m_ds);
-    m_band_count = GDALGetRasterCount(m_ds);
-
-    for (int i = 0; i < m_band_count; ++i)
-    {
-        int fail = 0;
-        GDALRasterBandH band = GDALGetRasterBand(m_ds, i + 1);
-        double v = GDALGetRasterNoDataValue(band, &fail);
-    }
-    if (computePDALDimensionTypes() == GDALError::InvalidBand)
-        error = GDALError::InvalidBand;
-    return error;
-}
-
-
-void Raster::pixelToCoord(int col, int row, std::array<double, 2>& output) const
-{
-    /**
-    double *xform = const_cast<double *>(m_forward_transform.data());
-    GDALApplyGeoTransform(xform, col, row, &output[0], &output[1]);
-    **/
-
-    // from http://gis.stackexchange.com/questions/53617/how-to-find-lat-lon-values-for-every-pixel-in-a-geotiff-file
-    double c = m_forward_transform[0];
-    double a = m_forward_transform[1];
-    double b = m_forward_transform[2];
-    double f = m_forward_transform[3];
-    double d = m_forward_transform[4];
-    double e = m_forward_transform[5];
-
-    //ABELL - Not sure why this is right.  You can think of this like:
-    //   output[0] = a * (col + .5) + b * (row + .5) + c;
-    //   output[1] = d * (col + .5) + e * (row + .5) + f;
-    //   Is there some reason why you want to "move" the points in the raster
-    //   to a location between the rows/columns?  Seems that you would just
-    //   use 'c' and 'f' to shift everything a half-row and half-column if
-    //   that's what you wanted.
-    //   Also, this isn't what GDALApplyGeoTransform does.  And why aren't
-    //   we just calling GDALApplyGeoTransform?
-    output[0] = a*col + b*row + a*0.5 + b*0.5 + c;
-    output[1] = d*col + e*row + d*0.5 + e*0.5 + f;
-}
-
-
-// Determines the pixel/line position given an x/y.
-// No reprojection is done at this time.
-bool Raster::getPixelAndLinePosition(double x, double y,
-    int32_t& pixel, int32_t& line)
-{
-    pixel = (int32_t)std::floor(m_inverse_transform[0] +
-        (m_inverse_transform[1] * x) + (m_inverse_transform[2] * y));
-    line = (int32_t) std::floor(m_inverse_transform[3] +
-        (m_inverse_transform[4] * x) + (m_inverse_transform[5] * y));
-
-    // Return false if we're out of bounds.
-    return (pixel >= 0 && pixel < m_raster_x_size &&
-        line >= 0 && line < m_raster_y_size);
-}
-
-
-Dimension::Type convertGDALtoPDAL(GDALDataType t)
-{
-    switch (t)
-    {
-        case GDT_Byte:
-            return Dimension::Type::Unsigned8;
-        case GDT_UInt16:
-            return Dimension::Type::Unsigned16;
-        case GDT_Int16:
-            return Dimension::Type::Signed16;
-        case GDT_UInt32:
-            return Dimension::Type::Unsigned32;
-        case GDT_Int32:
-            return Dimension::Type::Signed32;
-        case GDT_Float32:
-            return Dimension::Type::Float;
-        case GDT_Float64:
-            return Dimension::Type::Double;
-        case GDT_CInt16:
-        case GDT_CInt32:
-        case GDT_CFloat32:
-        case GDT_CFloat64:
-            throw pdal_error("GDAL complex float type unsupported.");
-        case GDT_Unknown:
-            throw pdal_error("GDAL unknown type unsupported.");
-        case GDT_TypeCount:
-            throw pdal_error("Detected bad GDAL data type.");
-    }
-    return Dimension::Type::None;
-}
-
-
-GDALError Raster::readBand(std::vector<uint8_t>& points, int nBand)
-{
-    try
-    {
-        BandReader(m_ds, nBand).read(points);
-    }
-    catch (InvalidBand)
-    {
-        std::stringstream oss;
-        oss << "Unable to get band " << nBand << " from raster '" <<
-            m_filename << "'.";
-        m_errorMsg = oss.str();
-        return GDALError::InvalidBand;
-    }
-    catch (CantReadBlock)
-    {
-        std::ostringstream oss;
-        oss << "Unable to read block for for raster '" << m_filename << "'.";
-        m_errorMsg = oss.str();
-        return GDALError::CantReadBlock;
-    }
-    return GDALError::None;
-}
-
-
-GDALError Raster::computePDALDimensionTypes()
-{
-    if (!m_ds)
-        return GDALError::NotOpen;
-
-    m_types.clear();
-    for (int i=0; i < m_band_count; ++i)
-    {
-        GDALRasterBandH band = GDALGetRasterBand(m_ds, i+1);
-        if (!band)
-        {
-            std::ostringstream oss;
-
-            oss << "Unable to get band " << (i + 1) <<
-                " from raster data source '" << m_filename << "'.";
-            m_errorMsg = oss.str();
-            return GDALError::InvalidBand;
-        }
-
-        GDALDataType t = GDALGetRasterDataType(band);
-        int x(0), y(0);
-        GDALGetBlockSize(band, &x, &y);
-        m_types.push_back(convertGDALtoPDAL(t));
-    }
-    return GDALError::None;
-}
-
-
-GDALError Raster::read(double x, double y, std::vector<double>& data)
-{
-    if (!m_ds)
-        return GDALError::NotOpen;
-
-    int32_t pixel(0);
-    int32_t line(0);
-    data.resize(m_band_count);
-
-    std::array<double, 2> pix = { {0.0, 0.0} };
-
-    // No data at this x,y if we can't compute a pixel/line location
-    // for it.
-    if (!getPixelAndLinePosition(x, y, pixel, line))
-        return GDALError::NoData;
-
-    for (int i=0; i < m_band_count; ++i)
-    {
-        GDALRasterBandH b = GDALGetRasterBand(m_ds, i+1);
-        if (GDALRasterIO(b, GF_Read, pixel, line, 1, 1,
-            &pix[0], 1, 1, GDT_CFloat64, 0, 0) == CE_None)
-        {
-            // we read a pixel put its values in our vector
-            data[i] = pix[0];
-        }
-    }
-
-    return GDALError::None;
-}
-
-
-SpatialReference Raster::getSpatialRef() const
-{
-    SpatialReference srs;
-
-    if (m_ds)
-        srs = SpatialReference(GDALGetProjectionRef(m_ds));
-    return srs;
-}
-
-
-Raster::~Raster()
-{
-    close();
-}
-
-
-void Raster::close()
-{
-    if (m_ds != 0)
-    {
-        GDALClose(m_ds);
-        m_ds = 0;
-    }
-    m_types.clear();
-}
-
-} // namespace gdal
-
-std::string transformWkt(std::string wkt, const SpatialReference& from,
-    const SpatialReference& to)
-{
-    //ABELL - Should this throw?  Return empty string?
-    if (from.empty() || to.empty())
-        return wkt;
-
-    gdal::SpatialRef fromRef(from.getWKT());
-    gdal::SpatialRef toRef(to.getWKT());
-    gdal::Geometry geom(wkt, fromRef);
-    geom.transform(toRef);
-    return geom.wkt();
-}
-
-} // namespace pdal
-
diff --git a/src/GEOSUtils.cpp b/src/GEOSUtils.cpp
deleted file mode 100644
index 9162bd6..0000000
--- a/src/GEOSUtils.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/GEOSUtils.hpp>
-#include <pdal/Log.hpp>
-
-#include <cstdarg>
-#include <functional>
-#include <map>
-#include <sstream>
-
-#ifdef PDAL_COMPILER_MSVC
-#  pragma warning(disable: 4127)  // conditional expression is constant
-#endif
-
-namespace pdal
-{
-namespace geos
-{
-
-std::unique_ptr<ErrorHandler> ErrorHandler::m_instance;
-
-// Allocating here instead of just making a static because I'm not really
-// sure what GEOS_init_r does and when it can be called.  It doesn't hurt
-// anything.
-ErrorHandler& ErrorHandler::get()
-{
-    if (!m_instance)
-        m_instance.reset(new ErrorHandler);
-    return *m_instance;
-}
-
-
-void ErrorHandler::vaErrorCb(const char *msg, ...)
-{
-    va_list args;
-    va_start(args, msg);
-    char buf[1024];
-    vsnprintf(buf, sizeof(buf), msg, args);
-    ErrorHandler::get().handle(buf, false);
-    va_end(args);
-}
-
-
-void ErrorHandler::vaNoticeCb(const char *msg, ...)
-{
-    va_list args;
-    va_start(args, msg);
-    char buf[1024];
-    vsnprintf(buf, sizeof(buf), msg, args);
-    ErrorHandler::get().handle(buf, true);
-    va_end(args);
-}
-
-
-ErrorHandler::ErrorHandler() : m_debug(false)
-{
-#ifdef GEOS_init_r
-    m_ctx = GEOS_init_r();
-
-    auto errorCb [](const char *msg, void *userData)
-    {
-        get()->handle(msg, false);
-    };
-    GEOSContext_setErrorMessageHandler_r(m_ctx, errorCb, NULL);
-
-    auto noticeCb [](const char *msg, void *userData)
-    {
-        get()->handle(msg, true);
-    };
-    GEOSContext_setNoticeMessageHandler_r(m_ctx, noticeCb, NULL);
-#else
-    m_ctx = initGEOS_r(NULL, NULL);
-    GEOSContext_setErrorHandler_r(m_ctx, vaErrorCb);
-    GEOSContext_setNoticeHandler_r(m_ctx, vaNoticeCb);
-#endif
-}
-
-
-ErrorHandler::~ErrorHandler()
-{
-#ifdef GEOS_finish_r
-    GEOS_finish_r(m_ctx);
-#else
-    finishGEOS_r(m_ctx);
-#endif
-}
-
-
-void ErrorHandler::set(LogPtr log, bool debug)
-{
-    setLog(log);
-    setDebug(debug);
-}
-
-
-void ErrorHandler::setDebug(bool debug)
-{
-    m_debug = debug;
-}
-
-
-void ErrorHandler::setLog(LogPtr log)
-{
-    m_log = log;
-}
-
-
-void ErrorHandler::handle(const char *msg, bool notice)
-{
-    std::ostringstream oss;
-    if (!notice)
-    {
-        oss << "GEOS failure: '" << msg << "'";
-        throw pdal_error(oss.str());
-    }
-    else if (m_debug)
-    {
-        oss << "GEOS debug: " << msg;
-        if (m_log)
-            m_log->get(LogLevel::Debug) << oss.str() << std::endl;
-    }
-}
-
-GEOSContextHandle_t ErrorHandler::ctx() const
-{
-    return m_ctx;
-}
-
-} // namespace geos
-} // namespace pdal
-
diff --git a/src/Kernel.cpp b/src/Kernel.cpp
deleted file mode 100644
index a2a9c96..0000000
--- a/src/Kernel.cpp
+++ /dev/null
@@ -1,515 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <cctype>
-#include <iostream>
-
-#include <pdal/Kernel.hpp>
-#include <pdal/Options.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/pdal_config.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include <pdal/pdal_config.hpp>
-
-#include <buffer/BufferReader.hpp>
-
-#include <memory>
-#include <vector>
-
-namespace pdal
-{
-
-namespace
-{
-
-bool parseOption(std::string o, std::string& stage, std::string& option,
-    std::string& value)
-{
-    value.clear();
-    if (o.size() < 2)
-        return false;
-    if (o[0] != '-' || o[1] != '-')
-        return false;
-
-    o = o.substr(2);
-
-    // Options are stage_type.stage_name.option_name
-    // stage_type is always lowercase stage_names start with lowercase and
-    // then are lowercase or digits.  Option names start with lowercase and
-    // then contain lowercase, digits or underscore.
-
-    // This awfulness is to work around the multiply-defined islower.  Seems
-    // a bit better than the cast solution.
-    auto islc = [](char c)
-        { return std::islower(c); };
-    auto islcOrDigit = [](char c)
-        { return std::islower(c) || std::isdigit(c); };
-
-    std::string::size_type pos = 0;
-    std::string::size_type count = 0;
-
-    // Get stage_type.
-    count = Utils::extract(o, pos, islc);
-    pos += count;
-    std::string stage_type = o.substr(0, pos);
-    if (stage_type != "readers" && stage_type != "writers" &&
-        stage_type != "filters")
-        return false;
-    if (pos >= o.length() || o[pos++] != '.')
-        return false;
-
-    // Get stage_name.
-    count = Utils::extract(o, pos, islcOrDigit);
-    if (std::isdigit(o[pos]))
-        return false;
-    pos += count;
-    stage = o.substr(0, pos);
-    if (pos >= o.length() || o[pos++] != '.')
-        return false;
-
-    // Get option name.
-    std::string::size_type optionStart = pos;
-    count = Option::parse(o, pos);
-    pos += count;
-    option = o.substr(optionStart, count);
-
-    // We've gotten a good option name, so return true, even if the value
-    // is missing.  The caller can handle the missing value if desired.
-    if (pos >= o.length() || o[pos++] != '=')
-        return true;
-
-    // The command-line parser takes care of quotes around an argument
-    // value and such.  May want to do something to handle escaped characters?
-    value = o.substr(pos);
-    return true;
-}
-
-} // unnamed namespace
-
-
-Kernel::Kernel() :
-    m_showTime(false)
-    , m_hardCoreDebug(false)
-    , m_visualize(false)
-{}
-
-
-std::ostream& operator<<(std::ostream& ostr, const Kernel& kernel)
-{
-    ostr << "  Name: " << kernel.getName() << std::endl;
-    return ostr;
-}
-
-
-void Kernel::doSwitches(const StringList& cmdArgs, ProgramArgs& args)
-{
-    OptionsMap& stageOptions = m_manager.stageOptions();
-    StringList stringArgs;
-
-    // Scan the argument vector for extra stage options.  Pull them out and
-    // stick them in the list.  Let the ProgramArgs handle everything else.
-    // NOTE: This depends on the format being "option=value" rather than
-    //   "option value".  This is what we've always expected, so no problem,
-    //   but it would be better to be more flexible.
-    for (size_t i = 0; i < cmdArgs.size(); ++i)
-    {
-        std::string stageName, opName, value;
-
-        if (parseOption(cmdArgs[i], stageName, opName, value))
-        {
-            if (value.empty())
-            {
-                std::ostringstream oss;
-                oss << "Stage option '" << stageName << "." << opName <<
-                    "' must be specified " << " as --" << stageName << "." <<
-                    opName << "=<value>" << ".";
-                throw pdal_error(oss.str());
-            }
-            Option op(opName, value);
-            stageOptions[stageName].add(op);
-        }
-        else
-            stringArgs.push_back(cmdArgs[i]);
-    }
-
-    try
-    {
-        // parseSimple allows us to scan for the help option without
-        // raising exception about missing arguments and so on.
-        // It also removes consumed args from the arg list, so for now,
-        // parse a copy that will be ignored by parse().
-        ProgramArgs hargs;
-        hargs.add("help,h", "Print help message", m_showHelp);
-        hargs.parseSimple(stringArgs);
-
-        addBasicSwitches(args);
-        addSwitches(args);
-        if (!m_showHelp)
-            args.parse(stringArgs);
-    }
-    catch (arg_error& e)
-    {
-        throw pdal_error(e.m_error);
-    }
-}
-
-
-int Kernel::doStartup()
-{
-    return 0;
-}
-
-
-int Kernel::doExecution(ProgramArgs& args)
-{
-    if (m_hardCoreDebug)
-    {
-        int status = innerRun(args);
-        return status;
-    }
-
-    int status = 1;
-
-    try
-    {
-        status = innerRun(args);
-    }
-    catch (pdal::pdal_error const& e)
-    {
-        Utils::printError(e.what());
-        return 1;
-    }
-    catch (std::exception const& e)
-    {
-        Utils::printError(e.what());
-        return 1;
-    }
-    catch (...)
-    {
-        Utils::printError("Caught unexpected exception.");
-        return 1;
-    }
-
-    return status;
-}
-
-
-// this just wraps ALL the code in total catch block
-int Kernel::run(const StringList& cmdArgs, LogPtr& log)
-{
-    m_log = log;
-    m_manager.setLog(m_log);
-
-    ProgramArgs args;
-
-    try
-    {
-        doSwitches(cmdArgs, args);
-    }
-    catch (const pdal_error& e)
-    {
-        Utils::printError(e.what());
-        return 1;
-    }
-
-    if (m_showHelp)
-    {
-        outputHelp(args);
-        return 0;
-    }
-
-    int startup_status = doStartup();
-    if (startup_status)
-        return startup_status;
-
-    return doExecution(args);
-}
-
-
-int Kernel::innerRun(ProgramArgs& args)
-{
-    try
-    {
-        // do any user-level sanity checking
-        validateSwitches(args);
-    }
-    catch (pdal_error e)
-    {
-        Utils::printError(e.what());
-        outputHelp(args);
-        return -1;
-    }
-
-    parseCommonOptions();
-    return execute();
-}
-
-
-bool Kernel::isVisualize() const
-{
-    return m_visualize;
-}
-
-
-void Kernel::visualize(PointViewPtr view)
-{
-    PipelineManager manager;
-
-    manager.commonOptions() = m_manager.commonOptions();
-    manager.stageOptions() = m_manager.stageOptions();
-
-    BufferReader& reader =
-        static_cast<BufferReader&>(manager.makeReader("", "readers.buffer"));
-    reader.addView(view);
-
-    Stage& writer = manager.makeWriter("", "writers.pclvisualizer", reader);
-
-    PointTable table;
-    writer.prepare(table);
-    writer.execute(table);
-}
-
-
-/*
-void Kernel::visualize(PointViewPtr input_view, PointViewPtr output_view) const
-{
-#ifdef PDAL_HAVE_PCL_VISUALIZE
-    int viewport = 0;
-
-    // Determine XYZ bounds
-    BOX3D const& input_bounds = input_view->calculateBounds();
-    BOX3D const& output_bounds = output_view->calculateBounds();
-
-    // Convert PointView to a PCL PointCloud
-    pcl::PointCloud<pcl::PointXYZ>::Ptr input_cloud(
-        new pcl::PointCloud<pcl::PointXYZ>);
-    pclsupport::PDALtoPCD(
-        const_cast<PointViewPtr>(*input_view), *input_cloud, input_bounds);
-    pcl::PointCloud<pcl::PointXYZ>::Ptr output_cloud(
-        new pcl::PointCloud<pcl::PointXYZ>);
-    pclsupport::PDALtoPCD(
-        const_cast<PointViewPtr>(*output_view), *output_cloud, output_bounds);
-
-    // Create PCLVisualizer
-    std::shared_ptr<pcl::visualization::PCLVisualizer> p(
-        new pcl::visualization::PCLVisualizer("3D Viewer"));
-
-    // Set background to black
-    p->setBackgroundColor(0, 0, 0);
-
-    // Use Z dimension to colorize points
-    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZ>
-        input_color(input_cloud, "z");
-    pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZ>
-        output_color(output_cloud, "z");
-
-    // Add point cloud to the viewer with the Z dimension color handler
-    p->createViewPort(0, 0, 0.5, 1, viewport);
-    p->addPointCloud<pcl::PointXYZ> (input_cloud, input_color, "cloud");
-    p->createViewPort(0.5, 0, 1, 1, viewport);
-    p->addPointCloud<pcl::PointXYZ> (output_cloud, output_color, "cloud1");
-
-    p->resetCamera();
-
-    while (!p->wasStopped())
-    {
-        p->spinOnce(100);
-        std::this_thread::sleep_for(std::chrono::microseconds(100000));
-    }
-#endif
-}
-*/
-
-
-void Kernel::parseCommonOptions()
-{
-    Options& options = m_manager.commonOptions();
-
-    if (m_visualize)
-        options.add("visualize", m_visualize);
-
-    auto pred = [](char c){ return (bool)strchr(",| ", c); };
-
-    if (!m_scales.empty())
-    {
-        std::vector<double> scales;
-        StringList scaleTokens = Utils::split2(m_scales, pred);
-        for (std::string s : scaleTokens)
-        {
-            double val;
-
-            if (Utils::fromString(s, val))
-                scales.push_back(val);
-            else
-            {
-                std::ostringstream oss;
-                oss << getName() << ": Invalid scale value '" << s << "'." <<
-                    std::endl;
-                throw pdal_error(oss.str());
-            }
-        }
-        if (scales.size() > 0)
-            options.add("scale_x", scales[0]);
-        if (scales.size() > 1)
-            options.add("scale_y", scales[1]);
-        if (scales.size() > 2)
-            options.add("scale_z", scales[2]);
-    }
-
-    if (!m_offsets.empty())
-    {
-        std::vector<double> offsets;
-        StringList offsetTokens = Utils::split2(m_offsets, pred);
-        for (std::string o : offsetTokens)
-        {
-            double val;
-
-            if (Utils::fromString(o, val))
-                offsets.push_back(val);
-            else
-            {
-                std::ostringstream oss;
-                oss << getName() << ": Invalid offset value '" << o << "'." <<
-                    std::endl;
-                throw pdal_error(oss.str());
-            }
-        }
-        if (offsets.size() > 0)
-            options.add("offset_x", offsets[0]);
-        if (offsets.size() > 1)
-            options.add("offset_y", offsets[1]);
-        if (offsets.size() > 2)
-            options.add("offset_z", offsets[2]);
-    }
-}
-
-
-void Kernel::outputHelp(ProgramArgs& args)
-{
-    std::cout << "usage: " << "pdal " << getShortName() << " [options] " <<
-        args.commandLine() << std::endl;
-
-    std::cout << "options:" << std::endl;
-    args.dump(std::cout, 2, Utils::screenWidth());
-
-    //ABELL - Fix me.
-
-    std::cout <<"\nFor more information, see the full documentation for "
-        "PDAL at http://pdal.io/\n" << std::endl;
-}
-
-
-void Kernel::addBasicSwitches(ProgramArgs& args)
-{
-    args.add("developer-debug",
-        "Enable developer debug (don't trap exceptions)", m_hardCoreDebug);
-    args.add("label", "A string to label the process with", m_label);
-
-    args.add("visualize", "Visualize result", m_visualize);
-    args.add("driver", "Override reader driver", m_driverOverride, "");
-    args.add("scale",
-         "A comma-separated or quoted, space-separated list of scales to "
-         "set on the output file: \n--scale 0.1,0.1,0.00001\n--scale \""
-         "0.1 0.1 0.00001\"", m_scales);
-    args.add("offset",
-         "A comma-separated or quoted, space-separated list of offsets to "
-         "set on the output file: \n--offset 0,0,0\n--offset "
-         "\"1234 5678 91011\"", m_offsets);
-}
-
-/**
-Stage& Kernel::createStage(const std::string& name)
-{
-    Stage *stage = m_factory.createStage(name);
-    if (!stage)
-        throw pdal_error("stage creation failed for " + name);
-    return *stage;
-}
-**/
-
-Stage& Kernel::makeReader(const std::string& inputFile, std::string driver)
-{
-    return m_manager.makeReader(inputFile, driver);
-}
-
-
-Stage& Kernel::makeReader(const std::string& inputFile, std::string driver,
-    Options options)
-{
-    return m_manager.makeReader(inputFile, driver, options);
-}
-
-
-Stage& Kernel::makeFilter(const std::string& driver)
-{
-    return m_manager.makeFilter(driver);
-}
-
-
-Stage& Kernel::makeFilter(const std::string& driver, Stage& parent)
-{
-    return m_manager.makeFilter(driver, parent);
-}
-
-
-Stage& Kernel::makeFilter(const std::string& driver, Stage& parent,
-    Options options)
-{
-    return m_manager.makeFilter(driver, parent, options);
-}
-
-
-Stage& Kernel::makeWriter(const std::string& outputFile, Stage& parent,
-    std::string driver)
-{
-    return m_manager.makeWriter(outputFile, driver, parent);
-}
-
-
-Stage& Kernel::makeWriter(const std::string& outputFile, Stage& parent,
-    std::string driver, Options options)
-{
-    return m_manager.makeWriter(outputFile, driver, parent, options);
-}
-
-
-bool Kernel::test_parseOption(std::string o, std::string& stage,
-    std::string& option, std::string& value)
-{
-    return parseOption(o, stage, option, value);
-}
-
-} // namespace pdal
diff --git a/src/KernelFactory.cpp b/src/KernelFactory.cpp
deleted file mode 100644
index a98bd1c..0000000
--- a/src/KernelFactory.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/KernelFactory.hpp>
-#include <pdal/PluginManager.hpp>
-
-#include <delta/DeltaKernel.hpp>
-#include <diff/DiffKernel.hpp>
-#include <info/InfoKernel.hpp>
-#include <merge/MergeKernel.hpp>
-#include <pipeline/PipelineKernel.hpp>
-#include <random/RandomKernel.hpp>
-#include <sort/SortKernel.hpp>
-#include <split/SplitKernel.hpp>
-#include <tindex/TIndexKernel.hpp>
-#include <translate/TranslateKernel.hpp>
-
-namespace pdal
-{
-
-KernelFactory::KernelFactory(bool no_plugins)
-{
-    if (!no_plugins)
-        PluginManager::loadAll(PF_PluginType_Kernel);
-
-    PluginManager::initializePlugin(DeltaKernel_InitPlugin);
-    PluginManager::initializePlugin(DiffKernel_InitPlugin);
-    PluginManager::initializePlugin(InfoKernel_InitPlugin);
-    PluginManager::initializePlugin(MergeKernel_InitPlugin);
-    PluginManager::initializePlugin(PipelineKernel_InitPlugin);
-    PluginManager::initializePlugin(RandomKernel_InitPlugin);
-    PluginManager::initializePlugin(SortKernel_InitPlugin);
-    PluginManager::initializePlugin(SplitKernel_InitPlugin);
-    PluginManager::initializePlugin(TIndexKernel_InitPlugin);
-    PluginManager::initializePlugin(TranslateKernel_InitPlugin);
-}
-
-} // namespace pdal
diff --git a/src/Log.cpp b/src/Log.cpp
deleted file mode 100644
index 7ef176c..0000000
--- a/src/Log.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/Log.hpp>
-#include <pdal/PDALUtils.hpp>
-
-#include <fstream>
-#include <ostream>
-
-namespace pdal
-{
-
-Log::Log(std::string const& leaderString,
-         std::string const& outputName)
-    : m_level(LogLevel::Error)
-    , m_deleteStreamOnCleanup(false)
-    , m_leader(leaderString)
-{
-
-    makeNullStream();
-    if (Utils::iequals(outputName, "stdlog"))
-        m_log = &std::clog;
-    else if (Utils::iequals(outputName, "stderr"))
-        m_log = &std::cerr;
-    else if (Utils::iequals(outputName, "stdout"))
-        m_log = &std::cout;
-    else if (Utils::iequals(outputName, "devnull"))
-        m_log = m_nullStream;
-    else
-    {
-        m_log = Utils::createFile(outputName);
-        m_deleteStreamOnCleanup = true;
-    }
-}
-
-
-Log::Log(std::string const& leaderString,
-         std::ostream* v)
-    : m_level(LogLevel::Error)
-    , m_deleteStreamOnCleanup(false)
-    , m_leader(leaderString)
-{
-    m_log = v;
-    makeNullStream();
-}
-
-
-Log::~Log()
-{
-
-    if (m_deleteStreamOnCleanup)
-    {
-        m_log->flush();
-        delete m_log;
-    }
-    delete m_nullStream;
-}
-
-
-void Log::makeNullStream()
-{
-#ifdef _WIN32
-    std::string nullFilename = "nul";
-#else
-    std::string nullFilename = "/dev/null";
-#endif
-
-    m_nullStream = new std::ofstream(nullFilename);
-}
-
-
-void Log::floatPrecision(int level)
-{
-    m_log->setf(std::ios_base::fixed, std::ios_base::floatfield);
-    m_log->precision(level);
-}
-
-
-void Log::clearFloat()
-{
-    m_log->unsetf(std::ios_base::fixed);
-    m_log->unsetf(std::ios_base::floatfield);
-}
-
-
-std::ostream& Log::get(LogLevel level)
-{
-    const auto incoming(Utils::toNative(level));
-    const auto stored(Utils::toNative(m_level));
-    const auto nativeDebug(Utils::toNative(LogLevel::Debug));
-    if (incoming <= stored)
-    {
-        *m_log << "(" << m_leader << " "<< getLevelString(level) <<": " <<
-            incoming << "): " <<
-            std::string(incoming < nativeDebug ? 0 : incoming - nativeDebug,
-                    '\t');
-        return *m_log;
-    }
-    return *m_nullStream;
-
-}
-
-
-std::string Log::getLevelString(LogLevel level) const
-{
-    switch (level)
-    {
-        case LogLevel::Error:
-            return "Error";
-            break;
-        case LogLevel::Warning:
-            return "Warning";
-            break;
-        case LogLevel::Info:
-            return "Info";
-            break;
-        default:
-            return "Debug";
-    }
-}
-
-} // namespace
diff --git a/src/PDALUtils.cpp b/src/PDALUtils.cpp
deleted file mode 100644
index d832fa4..0000000
--- a/src/PDALUtils.cpp
+++ /dev/null
@@ -1,338 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <arbiter.hpp>
-
-#include <pdal/PDALUtils.hpp>
-#include <pdal/Options.hpp>
-#include <pdal/util/FileUtils.hpp>
-
-using namespace std;
-
-namespace pdal
-{
-
-namespace {
-
-void toJSON(const MetadataNode& m, std::ostream& o, int level);
-void arrayToJSON(const MetadataNodeList& children, std::ostream& o, int level);
-void arrayEltToJSON(const MetadataNode& m, std::ostream& o, int level);
-void subnodesToJSON(const MetadataNode& parent, std::ostream& o, int level)
-{
-    const std::string indent(level * 2, ' ');
-
-    std::vector<std::string> names = parent.childNames();
-
-    o << indent << "{" << endl;
-    for (auto ni = names.begin(); ni != names.end(); ++ni)
-    {
-        MetadataNodeList children = parent.children(*ni);
-
-        MetadataNode& node = *children.begin();
-        if (node.kind() == MetadataType::Array)
-        {
-            o << indent << "  \"" << node.name() << "\":" << std::endl;
-            arrayToJSON(children, o, level + 1);
-        }
-        else
-            toJSON(node, o, level + 1);
-        if (ni != names.rbegin().base() - 1)
-            o << ",";
-        o << std::endl;
-    }
-    o << indent << "}";
-}
-
-void arrayToJSON(const MetadataNodeList& children, std::ostream& o, int level)
-{
-    const std::string indent(level * 2, ' ');
-
-    o << indent << "[" << std::endl;
-    for (auto ci = children.begin(); ci != children.end(); ++ci)
-    {
-        const MetadataNode& m = *ci;
-
-        arrayEltToJSON(m, o, level + 1);
-        if (ci != children.rbegin().base() - 1)
-            o << ",";
-        o << std::endl;
-    }
-    o << indent << "]";
-}
-
-void arrayEltToJSON(const MetadataNode& m, std::ostream& o, int level)
-{
-    std::string indent(level * 2, ' ');
-    std::string value = m.jsonValue();
-    bool children = m.hasChildren();
-
-    // This is a case from XML.  In JSON, you can't have two values.
-    if (!value.empty() && children)
-    {
-        o << value << "," << std::endl;
-        subnodesToJSON(m, o, level);
-    }
-    else if (!value.empty())
-        o << indent << value;
-    else
-        subnodesToJSON(m, o, level);
-    // There is the case where we have a name and no value to handle.  What
-    // should be done?
-}
-
-void toJSON(const MetadataNode& m, std::ostream& o, int level)
-{
-    std::string indent(level * 2, ' ');
-    std::string name = m.name();
-    std::string value = m.jsonValue();
-    bool children = m.hasChildren();
-
-    if (name.empty())
-        name = "unnamed";
-
-    // This is a case from XML.  In JSON, you can't have two values.
-    if (!value.empty() && children)
-    {
-        o << indent << "\"" << name << "\": " << value << "," << std::endl;
-        o << indent << "\"" << name << "\": ";
-        subnodesToJSON(m, o, level);
-    }
-    else if (!value.empty())
-        o << indent << "\"" << name << "\": " << value;
-    else
-    {
-        o << indent << "\"" << name << "\":" << std::endl;
-        subnodesToJSON(m, o, level);
-    }
-    // There is the case where we have a name and no value to handle.  What
-    // should be done?
-}
-
-} // unnamed namespace
-
-namespace Utils
-{
-
-std::string toJSON(const MetadataNode& m)
-{
-    std::ostringstream o;
-
-    toJSON(m, o);
-    return o.str();
-}
-
-void toJSON(const MetadataNode& m, std::ostream& o)
-{
-    if (m.name().empty())
-        pdal::subnodesToJSON(m, o, 0);
-    else if (m.kind() == MetadataType::Array)
-        pdal::arrayToJSON(m.children(), o, 0);
-    else
-    {
-        o << "{" << std::endl;
-        pdal::toJSON(m, o, 1);
-        o << std::endl;
-        o << "}";
-    }
-    o << std::endl;
-}
-
-namespace
-{
-
-std::string tempFilename(const std::string& path)
-{
-    const std::string tempdir(arbiter::fs::getTempPath());
-    const std::string basename(arbiter::util::getBasename(path));
-
-    return arbiter::util::join(tempdir, basename);
-};
-
-// RAII handling of a temp file to make sure file gets deleted.
-class TempFile
-{
-public:
-    TempFile(const std::string path) : m_filename(path)
-    {}
-
-    virtual ~TempFile()
-        { FileUtils::deleteFile(m_filename); }
-
-    const std::string& filename()
-        { return m_filename; }
-
-private:
-    std::string m_filename;
-};
-
-class ArbiterOutStream : public std::ofstream
-{
-public:
-    ArbiterOutStream(const std::string& localPath,
-            const std::string& remotePath, std::ios::openmode mode) :
-        std::ofstream(localPath, mode), m_remotePath(remotePath),
-        m_localFile(localPath)
-    {}
-
-    virtual ~ArbiterOutStream()
-    {
-        close();
-        arbiter::Arbiter a;
-        a.put(m_remotePath, a.getBinary(m_localFile.filename()));
-    }
-
-private:
-    std::string m_remotePath;
-    TempFile m_localFile;
-};
-
-class ArbiterInStream : public std::ifstream
-{
-public:
-    ArbiterInStream(const std::string& localPath, const std::string& remotePath,
-            std::ios::openmode mode) :
-        m_localFile(localPath)
-    {
-        arbiter::Arbiter a;
-        a.put(localPath, a.getBinary(remotePath));
-        open(localPath, mode);
-    }
-
-private:
-    TempFile m_localFile;
-};
-
-}  // unnamed namespace
-
-/**
-  Create a file (may be on a supported remote filesystem).
-
-  \param path  Path to file to create.
-  \param asBinary  Whether the file should be written in binary mode.
-  \return  Pointer to the created stream, or NULL.
-*/
-std::ostream *createFile(const std::string& path, bool asBinary)
-{
-    arbiter::Arbiter a;
-    const bool remote(a.hasDriver(path) && a.isRemote(path));
-
-    ostream *ofs(nullptr);
-    if (remote)
-    {
-        try
-        {
-            ofs = new ArbiterOutStream(tempFilename(path), path,
-                asBinary ? ios::out | ios::binary : ios::out);
-        }
-        catch (arbiter::ArbiterError)
-        {}
-        if (ofs && !ofs->good())
-        {
-            delete ofs;
-            ofs = nullptr;
-        }
-    }
-    else
-        ofs = FileUtils::createFile(path, asBinary);
-    return ofs;
-}
-
-
-/**
-  Open a file (potentially on a remote filesystem).
-
-  \param path  Path (potentially remote) of file to open.
-  \param asBinary  Whether the file should be opened binary.
-  \return  Pointer to stream opened for input.
-*/
-std::istream *openFile(const std::string& path, bool asBinary)
-{
-    arbiter::Arbiter a;
-    if (a.hasDriver(path) && a.isRemote(path))
-    {
-        try
-        {
-            return new ArbiterInStream(tempFilename(path), path,
-                asBinary ? ios::in | ios::binary : ios::in);
-        }
-        catch (arbiter::ArbiterError)
-        {
-            return nullptr;
-        }
-    }
-    return FileUtils::openFile(path, asBinary);
-}
-
-/**
-  Close an output stream.
-
-  \param out  Stream to close.
-*/
-void closeFile(std::ostream *out)
-{
-    FileUtils::closeFile(out);
-}
-
-
-/**
-  Close an input stream.
-
-  \param out  Stream to close.
-*/
-void closeFile(std::istream *in)
-{
-    FileUtils::closeFile(in);
-}
-
-
-/**
-  Check to see if a file exists.
-
-  \param path  Path to file.
-  \return  Whether the file exists or not.
-*/
-bool fileExists(const std::string& path)
-{
-    arbiter::Arbiter a;
-    if (a.hasDriver(path) && a.isRemote(path) && a.exists(path))
-    {
-        return true;
-    }
-
-    // Arbiter doesn't handle our STDIN hacks.
-    return FileUtils::fileExists(path);
-}
-
-} // namespace Utils
-} // namespace pdal
diff --git a/src/PipelineManager.cpp b/src/PipelineManager.cpp
deleted file mode 100644
index 0da337d..0000000
--- a/src/PipelineManager.cpp
+++ /dev/null
@@ -1,355 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/PipelineManager.hpp>
-#include <pdal/PDALUtils.hpp>
-
-#include "PipelineReaderXML.hpp"
-#include "PipelineReaderJSON.hpp"
-
-namespace pdal
-{
-
-PipelineManager::~PipelineManager()
-{
-    Utils::closeFile(m_input);
-}
-
-
-void PipelineManager::readPipeline(std::istream& input)
-{
-    // Read stream into string.
-    std::string s(std::istreambuf_iterator<char>(input), {});
-
-    std::istringstream ss(s);
-    if (s.find("?xml") != std::string::npos)
-        PipelineReaderXML(*this).readPipeline(ss);
-    else if (s.find("\"pipeline\"") != std::string::npos)
-        PipelineReaderJSON(*this).readPipeline(ss);
-    else
-    {
-        try
-        {
-            PipelineReaderXML(*this).readPipeline(ss);
-        }
-        catch (pdal_error)
-        {
-            // Rewind to make sure the stream is properly positioned after
-            // attempting an XML pipeline.
-            ss.seekg(0);
-            PipelineReaderJSON(*this).readPipeline(ss);
-        }
-    }
-}
-
-
-void PipelineManager::readPipeline(const std::string& filename)
-{
-    if (FileUtils::extension(filename) == ".xml")
-    {
-        PipelineReaderXML pipeReader(*this);
-        return pipeReader.readPipeline(filename);
-    }
-    else if (FileUtils::extension(filename) == ".json")
-    {
-        PipelineReaderJSON pipeReader(*this);
-        return pipeReader.readPipeline(filename);
-    }
-    else
-    {
-        Utils::closeFile(m_input);
-        m_input = Utils::openFile(filename);
-        readPipeline(*m_input);
-    }
-}
-
-
-Stage& PipelineManager::addReader(const std::string& type)
-{
-    Stage *reader = m_factory.createStage(type);
-    if (!reader)
-    {
-        std::ostringstream ss;
-        ss << "Couldn't create reader stage of type '" << type << "'.";
-        throw pdal_error(ss.str());
-    }
-    reader->setLog(m_log);
-    reader->setProgressFd(m_progressFd);
-    m_stages.push_back(reader);
-    return *reader;
-}
-
-
-Stage& PipelineManager::addFilter(const std::string& type)
-{
-    Stage *filter = m_factory.createStage(type);
-    if (!filter)
-    {
-        std::ostringstream ss;
-        ss << "Couldn't create filter stage of type '" << type << "'.";
-        throw pdal_error(ss.str());
-    }
-    filter->setLog(m_log);
-    filter->setProgressFd(m_progressFd);
-    m_stages.push_back(filter);
-    return *filter;
-}
-
-
-Stage& PipelineManager::addWriter(const std::string& type)
-{
-    Stage *writer = m_factory.createStage(type);
-    if (!writer)
-    {
-        std::ostringstream ss;
-        ss << "Couldn't create writer stage of type '" << type << "'.";
-        throw pdal_error(ss.str());
-    }
-    writer->setLog(m_log);
-    writer->setProgressFd(m_progressFd);
-    m_stages.push_back(writer);
-    return *writer;
-}
-
-
-void PipelineManager::validateStageOptions() const
-{
-    // Make sure that the options specified are for relevant stages.
-    for (auto& si : m_stageOptions)
-    {
-        const std::string& stageName = si.first;
-        auto it = std::find_if(m_stages.begin(), m_stages.end(),
-            [stageName](Stage *s)
-            { return (s->getName() == stageName); });
-
-        // If the option stage name matches no created stage, then error.
-        if (it == m_stages.end())
-        {
-            std::ostringstream oss;
-            oss << "Argument references invalid/unused stage: '" <<
-                stageName << "'.";
-            throw pdal_error(oss.str());
-        }
-    }
-}
-
-
-QuickInfo PipelineManager::preview() const
-{
-    QuickInfo qi;
-
-    validateStageOptions();
-    Stage *s = getStage();
-    if (s)
-       qi = s->preview();
-    return qi;
-}
-
-
-void PipelineManager::prepare() const
-{
-    validateStageOptions();
-    Stage *s = getStage();
-    if (s)
-       s->prepare(m_table);
-}
-
-
-point_count_t PipelineManager::execute()
-{
-    prepare();
-
-    Stage *s = getStage();
-    if (!s)
-        return 0;
-    m_viewSet = s->execute(m_table);
-    point_count_t cnt = 0;
-    for (auto pi = m_viewSet.begin(); pi != m_viewSet.end(); ++pi)
-    {
-        PointViewPtr view = *pi;
-        cnt += view->size();
-    }
-    return cnt;
-}
-
-
-MetadataNode PipelineManager::getMetadata() const
-{
-    MetadataNode output("stages");
-
-    for (auto s : m_stages)
-    {
-        output.add(s->getMetadata());
-    }
-    return output;
-}
-
-
-Stage& PipelineManager::makeReader(const std::string& inputFile,
-    std::string driver)
-{
-    static Options nullOpts;
-
-    return makeReader(inputFile, driver, nullOpts);
-}
-
-
-Stage& PipelineManager::makeReader(const std::string& inputFile,
-    std::string driver, Options options)
-{
-    if (driver.empty())
-    {
-        driver = StageFactory::inferReaderDriver(inputFile);
-        if (driver.empty())
-            throw pdal_error("Cannot determine reader for input file: " +
-                inputFile);
-    }
-    if (!inputFile.empty())
-        options.replace("filename", inputFile);
-
-    Stage& reader = addReader(driver);
-    setOptions(reader, options);
-    return reader;
-}
-
-
-Stage& PipelineManager::makeFilter(const std::string& driver)
-{
-    static Options nullOps;
-
-    Stage& filter = addFilter(driver);
-    setOptions(filter, nullOps);
-    return filter;
-}
-
-
-Stage& PipelineManager::makeFilter(const std::string& driver, Options options)
-{
-    Stage& filter = addFilter(driver);
-    setOptions(filter, options);
-    return filter;
-}
-
-
-Stage& PipelineManager::makeFilter(const std::string& driver, Stage& parent)
-{
-    static Options nullOps;
-
-    return makeFilter(driver, parent, nullOps);
-}
-
-
-Stage& PipelineManager::makeFilter(const std::string& driver, Stage& parent,
-    Options options)
-{
-    Stage& filter = addFilter(driver);
-    setOptions(filter, options);
-    filter.setInput(parent);
-    return filter;
-}
-
-
-Stage& PipelineManager::makeWriter(const std::string& outputFile,
-    std::string driver)
-{
-    static Options nullOps;
-
-    return makeWriter(outputFile, driver, nullOps);
-}
-
-Stage& PipelineManager::makeWriter(const std::string& outputFile,
-    std::string driver, Options options)
-{
-    if (driver.empty())
-    {
-        driver = StageFactory::inferWriterDriver(outputFile);
-        if (driver.empty())
-            throw pdal_error("Cannot determine writer for output file: " +
-                outputFile);
-    }
-
-    if (!outputFile.empty())
-        options.replace("filename", outputFile);
-
-    auto& writer = addWriter(driver);
-    setOptions(writer, options);
-    return writer;
-}
-
-
-Stage& PipelineManager::makeWriter(const std::string& outputFile,
-    std::string driver, Stage& parent)
-{
-    static Options nullOps;
-
-    return makeWriter(outputFile, driver, parent, nullOps);
-}
-
-Stage& PipelineManager::makeWriter(const std::string& outputFile,
-    std::string driver, Stage& parent, Options options)
-{
-    Stage& writer = makeWriter(outputFile, driver, options);
-    writer.setInput(parent);
-    return writer;
-}
-
-
-void PipelineManager::setOptions(Stage& stage, const Options& addOps)
-{
-    // First apply common options.
-    stage.setOptions(m_commonOptions);
-
-    // Apply additional reader/writer options, making sure they replace any
-    // common options.
-    stage.removeOptions(addOps);
-    stage.addOptions(addOps);
-
-    // Apply options provided on the command line, overriding others.
-    Options& ops = stageOptions(stage);
-    stage.removeOptions(ops);
-    stage.addOptions(ops);
-}
-
-
-Options& PipelineManager::stageOptions(Stage& stage)
-{
-    static Options nullOpts;
-
-    auto oi = m_stageOptions.find(stage.getName());
-    if (oi == m_stageOptions.end())
-        return nullOpts;
-    return oi->second;
-}
-
-} // namespace pdal
diff --git a/src/PipelineReader.hpp b/src/PipelineReader.hpp
deleted file mode 100644
index 7ae2f83..0000000
--- a/src/PipelineReader.hpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_internal.hpp>
-#include <pdal/StageFactory.hpp>
-
-#include <vector>
-#include <string>
-
-#include <boost/property_tree/ptree.hpp>
-
-namespace pdal
-{
-
-class Options;
-class PipelineManager;
-
-class PDAL_DLL PipelineReader
-{
-    friend class PipelineManager;
-
-private:
-    class StageParserContext;
-
-    PipelineReader(PipelineManager& manager) : m_manager(manager)
-    {}
-
-    // Use this to fill in a pipeline manager with an XML file that
-    // contains a <Writer> as the last pipeline stage.
-    //
-    // returns true iff the xml file is a writer pipeline (otherwise it is
-    // assumed to be a reader pipeline)
-    bool readPipeline(const std::string& filename);
-    bool readPipeline(std::istream& input);
-
-    typedef std::map<std::string, std::string> map_t;
-
-    bool parseElement_Pipeline(const pdalboost::property_tree::ptree&);
-    Stage *parseElement_anystage(const std::string& name,
-        const pdalboost::property_tree::ptree& subtree);
-    Stage *parseElement_Reader(const pdalboost::property_tree::ptree& tree);
-    Stage *parseElement_Filter(const pdalboost::property_tree::ptree& tree);
-    Stage *parseElement_Writer(const pdalboost::property_tree::ptree& tree);
-    Option parseElement_Option(const pdalboost::property_tree::ptree& tree);
-    void collect_attributes(map_t& attrs,
-        const pdalboost::property_tree::ptree& tree);
-    void parse_attributes(map_t& attrs,
-        const pdalboost::property_tree::ptree& tree);
-
-    PipelineManager& m_manager;
-    Options m_baseOptions;
-//    std::string m_inputXmlFile;
-
-    PipelineReader& operator=(const PipelineReader&); // not implemented
-    PipelineReader(const PipelineReader&); // not implemented
-};
-
-} // namespace pdal
-
diff --git a/src/PipelineReaderJSON.cpp b/src/PipelineReaderJSON.cpp
deleted file mode 100644
index ddc5af2..0000000
--- a/src/PipelineReaderJSON.cpp
+++ /dev/null
@@ -1,316 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "PipelineReaderJSON.hpp"
-
-#include <pdal/Filter.hpp>
-#include <pdal/PipelineManager.hpp>
-#include <pdal/PluginManager.hpp>
-#include <pdal/Options.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/util/Utils.hpp>
-
-#include <json/json.h>
-
-#include <memory>
-#include <vector>
-
-namespace pdal
-{
-
-PipelineReaderJSON::PipelineReaderJSON(PipelineManager& manager) :
-    m_manager(manager)
-{}
-
-
-void PipelineReaderJSON::parsePipeline(Json::Value& tree)
-{
-    std::map<std::string, Stage*> tags;
-    std::vector<Stage*> inputs;
-
-    Json::ArrayIndex last = tree.size() - 1;
-    for (Json::ArrayIndex i = 0; i < tree.size(); ++i)
-    {
-        Json::Value& node = tree[i];
-
-        std::string filename;
-        std::string tag;
-        std::string type;
-        std::vector<Stage*> specifiedInputs;
-        Options options;
-
-        // strings are assumed to be filenames
-        if (node.isString())
-        {
-            filename = node.asString();
-        }
-        else
-        {
-            type = extractType(node);
-            filename = extractFilename(node);
-            tag = extractTag(node, tags);
-            specifiedInputs = extractInputs(node, tags);
-            if (!specifiedInputs.empty())
-                inputs = specifiedInputs;
-            options = extractOptions(node);
-        }
-
-        Stage *s = nullptr;
-
-        // The type is inferred from a filename as a reader if it's not
-        // the last stage or if there's only one.
-        if ((type.empty() && (i == 0 || i != last)) ||
-            Utils::startsWith(type, "readers."))
-        {
-            s = &m_manager.makeReader(filename, type, options);
-            if (specifiedInputs.size())
-                throw pdal_error("JSON pipeline: Inputs not permitted for "
-                    " reader: '" + filename + "'.");
-            inputs.push_back(s);
-        }
-        else if (type.empty() || Utils::startsWith(type, "writers."))
-        {
-            s = &m_manager.makeWriter(filename, type, options);
-            for (Stage *ts : inputs)
-                s->setInput(*ts);
-            inputs.clear();
-        }
-        else
-        {
-            if (filename.size())
-                options.add("filename", filename);
-            s = &m_manager.makeFilter(type, options);
-            for (Stage *ts : inputs)
-                s->setInput(*ts);
-            inputs.clear();
-            inputs.push_back(s);
-        }
-        // s should be valid at this point.  makeXXX will throw if the stage
-        // couldn't be constructed.
-        if (tag.size())
-            tags[tag] = s;
-    }
-}
-
-
-void PipelineReaderJSON::readPipeline(std::istream& input)
-{
-    Json::Value root;
-    Json::Reader jsonReader;
-    if (!jsonReader.parse(input, root))
-        throw pdal_error("JSON pipeline: Unable to parse pipeline");
-
-    Json::Value& subtree = root["pipeline"];
-    if (!subtree)
-        throw pdal_error("JSON pipeline: Root element is not a Pipeline");
-    parsePipeline(subtree);
-}
-
-
-void PipelineReaderJSON::readPipeline(const std::string& filename)
-{
-    m_inputJSONFile = filename;
-
-    std::istream* input = Utils::openFile(filename);
-    if (!input)
-    {
-        throw pdal_error("JSON pipeline: Unable to open stream for "
-            "file \"" + filename + "\"");
-    }
-
-    try
-    {
-        readPipeline(*input);
-    }
-    catch (...)
-    {
-        Utils::closeFile(input);
-        throw;
-    }
-
-    Utils::closeFile(input);
-    m_inputJSONFile = "";
-}
-
-
-std::string PipelineReaderJSON::extractType(Json::Value& node)
-{
-    std::string type;
-
-    if (node.isMember("type"))
-    {
-        Json::Value& val = node["type"];
-        if (!val.isNull())
-        {
-            if (val.isString())
-                type = val.asString();
-            else
-                throw pdal_error("JSON pipeline: 'type' must be specified as "
-                        "a string.");
-        }
-        node.removeMember("type");
-        if (node.isMember("type"))
-            throw pdal_error("JSON pipeline: found duplicate 'type' "
-               "entry in stage definition.");
-    }
-    return type;
-}
-
-
-std::string PipelineReaderJSON::extractFilename(Json::Value& node)
-{
-    std::string filename;
-
-    if (node.isMember("filename"))
-    {
-        Json::Value& val = node["filename"];
-        if (!val.isNull())
-        {
-            if (val.isString())
-                filename = val.asString();
-            else
-                throw pdal_error("JSON pipeline: 'filename' must be "
-                    "specified as a string.");
-        }
-        node.removeMember("filename");
-        if (node.isMember("filename"))
-            throw pdal_error("JSON pipeline: found duplicate 'filename' "
-               "entry in stage definition.");
-    }
-    return filename;
-}
-
-
-std::string PipelineReaderJSON::extractTag(Json::Value& node, TagMap& tags)
-{
-    std::string tag;
-
-    if (node.isMember("tag"))
-    {
-        Json::Value& val = node["tag"];
-        if (!val.isNull())
-        {
-            if (val.isString())
-            {
-                tag = val.asString();
-                if (tags.find(tag) != tags.end())
-                    throw pdal_error("JSON pipeline: duplicate tag '" +
-                        tag + "'.");
-            }
-            else
-                throw pdal_error("JSON pipeline: 'tag' must be "
-                    "specified as a string.");
-        }
-        node.removeMember("tag");
-        if (node.isMember("tag"))
-            throw pdal_error("JSON pipeline: found duplicate 'tag' "
-               "entry in stage definition.");
-    }
-    return tag;
-}
-
-
-std::vector<Stage *> PipelineReaderJSON::extractInputs(Json::Value& node,
-    TagMap& tags)
-{
-    std::vector<Stage *> inputs;
-    std::string filename;
-
-    if (node.isMember("inputs"))
-    {
-        Json::Value& val = node["inputs"];
-        if (!val.isNull())
-        {
-            for (const Json::Value& input : node["inputs"])
-            {
-                if (input.isString())
-                {
-                    std::string tag = input.asString();
-                    auto ii = tags.find(tag);
-                    if (ii == tags.end())
-                        throw pdal_error("JSON pipeline: Invalid pipeline: "
-                            "undefined stage tag '" + tag + "'.");
-                    else
-                        inputs.push_back(ii->second);
-                }
-                else
-                    throw pdal_error("JSON pipeline: 'inputs' tag must "
-                        " be specified as a string.");
-            }
-        }
-        node.removeMember("inputs");
-        if (node.isMember("inputs"))
-            throw pdal_error("JSON pipeline: found duplicate 'inputs' "
-               "entry in stage definition.");
-    }
-    return inputs;
-}
-
-
-Options PipelineReaderJSON::extractOptions(Json::Value& node)
-{
-    Options options;
-
-    for (const std::string& name : node.getMemberNames())
-    {
-        if (name == "plugin")
-        {
-            PluginManager::loadPlugin(node[name].asString());
-
-            // Don't actually put a "plugin" option on
-            // any stage
-            continue;
-        }
-
-        if (node[name].isString())
-            options.add(name, node[name].asString());
-        else if (node[name].isInt())
-            options.add(name, node[name].asInt64());
-        else if (node[name].isUInt())
-            options.add(name, node[name].asUInt64());
-        else if (node[name].isDouble())
-            options.add(name, node[name].asDouble());
-        else if (node[name].isBool())
-            options.add(name, node[name].asBool());
-        else if (node[name].isNull())
-            options.add(name, "");
-        else
-            throw pdal_error("JSON pipeline: Value of stage option '" +
-                name + "' cannot be converted.");
-    }
-    node.clear();
-    return options;
-}
-
-} // namespace pdal
diff --git a/src/PipelineReaderJSON.hpp b/src/PipelineReaderJSON.hpp
deleted file mode 100644
index 70ded84..0000000
--- a/src/PipelineReaderJSON.hpp
+++ /dev/null
@@ -1,78 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#pragma once
-
-#include <pdal/pdal_internal.hpp>
-#include <pdal/StageFactory.hpp>
-
-#include <json/json.h>
-
-#include <vector>
-#include <string>
-
-#include <pdal/Options.hpp>
-#include <pdal/StageFactory.hpp>
-
-namespace pdal
-{
-
-class Stage;
-class PipelineManager;
-
-class PDAL_DLL PipelineReaderJSON
-{
-    friend class PipelineManager;
-
-private:
-    typedef std::map<std::string, Stage *> TagMap;
-
-    PipelineReaderJSON(PipelineManager&);
-    void readPipeline(const std::string& filename);
-    void readPipeline(std::istream& input);
-    void parsePipeline(Json::Value&);
-    std::string extractType(Json::Value& node);
-    std::string extractFilename(Json::Value& node);
-    std::string extractTag(Json::Value& node, TagMap& tags);
-    std::vector<Stage *> extractInputs(Json::Value& node, TagMap& tags);
-    Options extractOptions(Json::Value& node);
-
-    PipelineManager& m_manager;
-    std::string m_inputJSONFile;
-
-    PipelineReaderJSON& operator=(const PipelineReaderJSON&); // not implemented
-    PipelineReaderJSON(const PipelineReaderJSON&); // not implemented
-};
-
-} // namespace pdal
diff --git a/src/PipelineReaderXML.cpp b/src/PipelineReaderXML.cpp
deleted file mode 100644
index 120ccba..0000000
--- a/src/PipelineReaderXML.cpp
+++ /dev/null
@@ -1,484 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include "PipelineReaderXML.hpp"
-
-#include <pdal/Filter.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/PipelineManager.hpp>
-#include <pdal/PluginManager.hpp>
-#include <pdal/Options.hpp>
-#include <pdal/util/FileUtils.hpp>
-
-#include <boost/property_tree/xml_parser.hpp>
-
-#ifndef _WIN32
-#include <wordexp.h>
-#endif
-
-namespace pdal
-{
-
-using namespace pdalboost::property_tree;
-
-// ------------------------------------------------------------------------
-
-// this class helps keep tracks of what child nodes we've seen, so we
-// can keep all the error checking in one place
-class PipelineReaderXML::StageParserContext
-{
-public:
-    enum Cardinality { None, One, Many };
-
-    StageParserContext()
-        : m_numTypes(0)
-        , m_cardinality(One)
-        , m_numStages(0)
-    {}
-
-    void setCardinality(Cardinality cardinality)
-    {
-        m_cardinality = cardinality;
-    }
-
-    void addType()
-    {
-        ++m_numTypes;
-    }
-
-    int getNumTypes()
-    {
-        return m_numTypes;
-    }
-
-    void addStage()
-    {
-        ++m_numStages;
-    }
-
-    void addUnknown(const std::string& name)
-    {
-        throw pdal_error("unknown child of element: " + name);
-    }
-
-    void validate()
-    {
-        if (m_numTypes == 0)
-            throw pdal_error("PipelineReaderXML: expected Type element "
-                "missing");
-        if (m_numTypes > 1)
-            throw pdal_error("PipelineReaderXML: extra Type element found");
-
-        if (m_cardinality == None)
-        {
-            if (m_numStages != 0)
-                throw pdal_error("PipelineReaderXML: found child stages where "
-                    "none were expected");
-        }
-        if (m_cardinality == One)
-        {
-            if (m_numStages == 0)
-                throw pdal_error("PipelineReaderXML: "
-                    "expected child stage missing");
-            if (m_numStages > 1)
-                throw pdal_error("PipelineReaderXML: extra child stages found");
-        }
-        if (m_cardinality == Many)
-        {
-            if (m_numStages == 0)
-                throw pdal_error("PipelineReaderXML: expected child stage "
-                    "missing");
-        }
-    }
-
-private:
-    int m_numTypes;
-    Cardinality m_cardinality; // num child stages allowed
-    int m_numStages;
-};
-
-
-PipelineReaderXML::PipelineReaderXML(PipelineManager& manager) :
-    m_manager(manager)
-{}
-
-
-Option PipelineReaderXML::parseElement_Option(const ptree& tree)
-{
-    // cur is an option element, such as this:
-    //     <option>
-    //       <name>myname</name>
-    //       <description>my descr</description>
-    //       <value>17</value>
-    //     </option>
-    // this function will process the element and return an Option from it
-
-    map_t attrs;
-    collect_attributes(attrs, tree);
-
-    std::string name = attrs["name"];
-    std::string value = tree.get_value<std::string>();
-    Utils::trim(value);
-
-    // filenames in the XML are fixed up as follows:
-    //   - if absolute path, leave it alone
-    //   - if relative path, make it absolute using the XML file's directory
-    // The toAbsolutePath function does exactly that magic for us.
-    if (name == "filename")
-    {
-        std::string path = value;
-#ifndef _WIN32
-        wordexp_t result;
-        if (wordexp(path.c_str(), &result, 0) == 0)
-        {
-            if (result.we_wordc == 1)
-                path = result.we_wordv[0];
-        }
-        wordfree(&result);
-#endif
-        if (!FileUtils::isAbsolutePath(path))
-        {
-            std::string abspath = FileUtils::toAbsolutePath(m_inputXmlFile);
-            std::string absdir = FileUtils::getDirectory(abspath);
-            path = FileUtils::toAbsolutePath(path, absdir);
-
-            assert(FileUtils::isAbsolutePath(path));
-        }
-        return Option(name, path);
-    }
-    else if (name == "plugin")
-    {
-       PluginManager::loadPlugin(value);
-    }
-    return Option(name, value);
-}
-
-
-Stage *PipelineReaderXML::parseElement_anystage(const std::string& name,
-    const ptree& subtree)
-{
-    if (name == "Filter")
-    {
-        return parseElement_Filter(subtree);
-    }
-    else if (name == "Reader")
-    {
-        return parseElement_Reader(subtree);
-    }
-    else if (name == "<xmlattr>")
-    {
-        // ignore: will parse later
-    }
-    else
-    {
-        throw pdal_error("PipelineReaderXML: encountered unknown stage type");
-    }
-
-    return NULL;
-}
-
-
-Stage *PipelineReaderXML::parseElement_Reader(const ptree& tree)
-{
-    Options options;
-    StageParserContext context;
-    std::string filename;
-    context.setCardinality(StageParserContext::None);
-
-    map_t attrs;
-    collect_attributes(attrs, tree);
-
-    auto iter = tree.begin();
-    while (iter != tree.end())
-    {
-        const std::string& name = iter->first;
-        const ptree& subtree = iter->second;
-
-        if (name == "<xmlattr>")
-        {
-            // already parsed
-        }
-        else if (name == "Option")
-        {
-            Option option = parseElement_Option(subtree);
-            if (option.getName() == "filename")
-                filename = option.getValue();
-            options.add(option);
-        }
-        else if (name == "Metadata")
-        {
-            // ignored for now
-        }
-        else
-        {
-            context.addUnknown(name);
-        }
-        ++iter;
-    }
-
-    std::string type;
-    if (attrs.count("type"))
-    {
-        type = attrs["type"];
-    }
-
-    Stage& reader = m_manager.makeReader(filename, type, options);
-
-    context.addType();
-    context.validate();
-    return &reader;
-}
-
-
-Stage *PipelineReaderXML::parseElement_Filter(const ptree& tree)
-{
-    Options options;
-
-    StageParserContext context;
-
-    map_t attrs;
-    collect_attributes(attrs, tree);
-
-    std::vector<Stage*> prevStages;
-    for (auto iter = tree.begin(); iter != tree.end(); ++iter)
-    {
-        const std::string& name = iter->first;
-        const ptree& subtree = iter->second;
-
-        if (name == "<xmlattr>")
-        {
-            // already parsed
-        }
-        else if (name == "Option")
-        {
-            Option option = parseElement_Option(subtree);
-            options.add(option);
-        }
-        else if (name == "Metadata")
-        {
-            // ignored
-        }
-        else if (name == "Filter" || name == "Reader")
-        {
-            context.addStage();
-            prevStages.push_back(parseElement_anystage(name, subtree));
-        }
-        else
-        {
-            context.addUnknown(name);
-        }
-    }
-
-    std::string type;
-    if (attrs.count("type"))
-        type = attrs["type"];
-
-    Stage& filter = m_manager.makeFilter(type, options);
-    for (auto sp : prevStages)
-        filter.setInput(*sp);
-    context.setCardinality(StageParserContext::Many);
-    context.addType();
-    context.validate();
-    return &filter;
-}
-
-
-void PipelineReaderXML::parse_attributes(map_t& attrs, const ptree& tree)
-{
-    for (auto iter = tree.begin(); iter != tree.end(); ++iter)
-    {
-        std::string name = iter->first;
-        std::string value = tree.get<std::string>(name);
-        Utils::trim(value);
-
-        attrs[name] = value;
-    }
-}
-
-
-void PipelineReaderXML::collect_attributes(map_t& attrs, const ptree& tree)
-{
-    if (tree.count("<xmlattr>"))
-    {
-        const ptree& subtree = tree.get_child("<xmlattr>");
-        parse_attributes(attrs, subtree);
-    }
-}
-
-
-Stage *PipelineReaderXML::parseElement_Writer(const ptree& tree)
-{
-    Options options;
-    StageParserContext context;
-    std::string filename;
-
-    map_t attrs;
-    collect_attributes(attrs, tree);
-
-    std::vector<Stage *> prevStages;
-    for (auto iter = tree.begin(); iter != tree.end(); ++iter)
-    {
-        const std::string& name = iter->first;
-        const ptree& subtree = iter->second;
-
-        if (name == "<xmlattr>")
-        {
-            // already parsed -- ignore it
-        }
-        else if (name == "Option")
-        {
-            Option option = parseElement_Option(subtree);
-            if (option.getName() == "filename")
-                filename = option.getValue();
-            options.add(option);
-        }
-        else if (name == "Metadata")
-        {
-            // ignored
-        }
-        else if (name == "Filter" || name == "Reader")
-        {
-            context.addStage();
-            prevStages.push_back(parseElement_anystage(name, subtree));
-        }
-        else
-        {
-            context.addUnknown(name);
-        }
-    }
-
-    std::string type;
-    if (attrs.count("type"))
-    {
-        type = attrs["type"];
-        context.addType();
-    }
-
-    context.validate();
-    Stage& writer = m_manager.makeWriter(filename, type, options);
-    for (auto sp : prevStages)
-        writer.setInput(*sp);
-    return &writer;
-}
-
-
-void PipelineReaderXML::parseElement_Pipeline(const ptree& tree)
-{
-    Stage *stage = NULL;
-    Stage *writer = NULL;
-
-    map_t attrs;
-    collect_attributes(attrs, tree);
-
-    std::string version = "";
-    if (attrs.count("version"))
-        version = attrs["version"];
-    if (version != "1.0")
-        throw pdal_error("PipelineReaderXML: unsupported pipeline xml version");
-
-    for (auto iter = tree.begin(); iter != tree.end(); ++iter)
-    {
-        const std::string& name = iter->first;
-        const ptree subtree = iter->second;
-
-        if (name == "Reader" || name == "Filter" )
-        {
-            stage = parseElement_anystage(name, subtree);
-        }
-        else if (name == "Writer")
-        {
-            writer = parseElement_Writer(subtree);
-        }
-        else if (name == "<xmlattr>")
-        {
-            // ignore it, already parsed
-        }
-        else
-        {
-            throw pdal_error("PipelineReaderXML: xml reader invalid child of "
-                "ReaderPipeline element");
-        }
-    }
-
-    if (writer && stage)
-    {
-        throw pdal_error("PipelineReaderXML: extra nodes at front of "
-            "writer pipeline");
-    }
-}
-
-
-void PipelineReaderXML::readPipeline(std::istream& input)
-{
-    ptree tree;
-
-    xml_parser::read_xml(input, tree, xml_parser::no_comments);
-
-    pdalboost::optional<ptree> opt(tree.get_child_optional("Pipeline"));
-    if (!opt.is_initialized())
-        throw pdal_error("PipelineReaderXML: root element is not Pipeline");
-    parseElement_Pipeline(opt.get());
-}
-
-
-void PipelineReaderXML::readPipeline(const std::string& filename)
-{
-    m_inputXmlFile = filename;
-
-    std::istream* input = Utils::openFile(filename);
-
-    try
-    {
-        readPipeline(*input);
-    }
-    catch (const pdal_error& error)
-    {
-        throw error;
-    }
-    catch (...)
-    {
-        Utils::closeFile(input);
-        std::ostringstream oss;
-        oss << "Unable to process pipeline file \"" << filename << "\"." <<
-            "  XML is invalid.";
-        throw pdal_error(oss.str());
-    }
-
-    Utils::closeFile(input);
-
-    m_inputXmlFile = "";
-}
-
-
-} // namespace pdal
diff --git a/src/PluginManager.cpp b/src/PluginManager.cpp
deleted file mode 100644
index 7e83831..0000000
--- a/src/PluginManager.cpp
+++ /dev/null
@@ -1,499 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Bradley J Chambers (brad.chambers at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-// The PluginManager was modeled very closely after the work of Gigi Sayfan in
-// the Dr. Dobbs article:
-// http://www.drdobbs.com/cpp/building-your-own-plugin-framework-part/206503957
-// The original work was released under the Apache License v2.
-
-#include <pdal/PluginManager.hpp>
-
-#include <pdal/pdal_defines.h>
-#include <pdal/util/Algorithm.hpp>
-#include <pdal/util/FileUtils.hpp>
-#include <pdal/util/Utils.hpp>
-
-#include "DynamicLibrary.hpp"
-
-#include <memory>
-#include <sstream>
-#include <string>
-
-namespace pdal
-{
-
-namespace
-{
-
-static PluginManager s_instance;
-
-#if defined(__APPLE__) && defined(__MACH__)
-    const std::string dynamicLibraryExtension(".dylib");
-#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__GNU__)
-    const std::string dynamicLibraryExtension(".so");
-#elif defined _WIN32
-    const std::string dynamicLibraryExtension(".dll");
-#endif
-
-
-bool pluginTypeValid(std::string pathname, PF_PluginType type)
-{
-    return ((Utils::startsWith(pathname, "libpdal_plugin_kernel") &&
-            type & PF_PluginType_Kernel) ||
-        (Utils::startsWith(pathname, "libpdal_plugin_filter") &&
-            type & PF_PluginType_Filter) ||
-        (Utils::startsWith(pathname, "libpdal_plugin_reader") &&
-            type & PF_PluginType_Reader) ||
-        (Utils::startsWith(pathname, "libpdal_plugin_writer") &&
-            type & PF_PluginType_Writer));
-}
-
-StringList pluginSearchPaths()
-{
-    StringList searchPaths;
-    std::string envOverride;
-
-    Utils::getenv("PDAL_DRIVER_PATH", envOverride);
-
-    if (!envOverride.empty())
-        searchPaths = Utils::split2(envOverride, ':');
-    else
-    {
-        StringList standardPaths = { "./lib", "../lib", "../bin" };
-        for (std::string& s : standardPaths)
-        {
-            if (FileUtils::toAbsolutePath(s) !=
-                FileUtils::toAbsolutePath(PDAL_PLUGIN_INSTALL_PATH))
-                searchPaths.push_back(s);
-        }
-        searchPaths.push_back(PDAL_PLUGIN_INSTALL_PATH);
-    }
-    return searchPaths;
-}
-
-
-} // unnamed namespace;
-
-
-bool PluginManager::registerObject(const std::string& name,
-    const PF_RegisterParams *params)
-{
-    return s_instance.l_registerObject(name, params);
-}
-
-
-bool PluginManager::l_registerObject(const std::string& name,
-    const PF_RegisterParams *params)
-{
-    if (params && params->createFunc && params->destroyFunc &&
-        (m_version.major == params->version.major))
-    {
-        auto entry(std::make_pair(name, *params));
-
-        std::lock_guard<std::mutex> lock(m_mutex);
-        return m_plugins.insert(entry).second;
-    }
-    return false;
-}
-
-
-void PluginManager::loadAll(int type)
-{
-    s_instance.l_loadAll(type);
-}
-
-
-void PluginManager::l_loadAll(PF_PluginType type)
-{
-    for (const auto& pluginPath : pluginSearchPaths())
-        loadAll(pluginPath, type);
-}
-
-
-StringList PluginManager::test_pluginSearchPaths()
-{
-    return pluginSearchPaths();
-}
-
-
-StringList PluginManager::names(int typeMask)
-{
-    return s_instance.l_names(typeMask);
-}
-
-
-void PluginManager::setLog(LogPtr& log)
-{
-    s_instance.m_log = log;
-}
-
-
-StringList PluginManager::l_names(int typeMask)
-{
-    StringList l;
-
-    std::lock_guard<std::mutex> lock(m_mutex);
-    for (auto p : m_plugins)
-        if (p.second.pluginType & typeMask)
-            l.push_back(p.first);
-    return l;
-}
-
-std::string PluginManager::link(const std::string& name)
-{
-    return s_instance.l_link(name);
-}
-
-
-std::string PluginManager::l_link(const std::string& name)
-{
-    std::string link;
-
-    std::lock_guard<std::mutex> lock(m_mutex);
-    auto ei = m_plugins.find(name);
-    if (ei != m_plugins.end())
-        link= ei->second.link;
-    return link;
-}
-
-
-std::string PluginManager::description(const std::string& name)
-{
-    return s_instance.l_description(name);
-}
-
-
-std::string PluginManager::l_description(const std::string& name)
-{
-    std::string descrip;
-
-    std::lock_guard<std::mutex> lock(m_mutex);
-    auto ei = m_plugins.find(name);
-    if (ei != m_plugins.end())
-        descrip = ei->second.description;
-    return descrip;
-}
-
-
-void PluginManager::loadAll(const std::string& pluginDirectory, int type)
-{
-    const bool pluginDirectoryValid = pluginDirectory.size() &&
-        (FileUtils::fileExists(pluginDirectory) ||
-            FileUtils::isDirectory(pluginDirectory));
-
-    if (pluginDirectoryValid)
-    {
-        m_log->get(LogLevel::Debug) << "Loading plugins from directory " <<
-            pluginDirectory << std::endl;
-        StringList files = FileUtils::directoryList(pluginDirectory);
-        for (auto file : files)
-        {
-            if ((FileUtils::extension(file) == dynamicLibraryExtension) &&
-                !FileUtils::isDirectory(file))
-                loadByPath(file, type);
-        }
-    }
-}
-
-
-bool PluginManager::initializePlugin(PF_InitFunc initFunc)
-{
-    return s_instance.l_initializePlugin(initFunc);
-}
-
-
-bool PluginManager::l_initializePlugin(PF_InitFunc initFunc)
-{
-    if (PF_ExitFunc exitFunc = initFunc())
-    {
-        std::lock_guard<std::mutex> lock(m_mutex);
-        m_exitFuncVec.push_back(exitFunc);
-        return true;
-    }
-    return false;
-}
-
-
-PluginManager::PluginManager() : m_log(new Log("PDAL", &std::clog))
-{
-    m_version.major = 1;
-    m_version.minor = 0;
-}
-
-
-PluginManager::~PluginManager()
-{
-    if (!shutdown())
-        m_log->get(LogLevel::Error) <<
-            "Error destroying PluginManager" << std::endl;
-}
-
-
-bool PluginManager::shutdown()
-{
-    std::lock_guard<std::mutex> lock(m_mutex);
-
-    bool success(true);
-
-    for (auto const& func : m_exitFuncVec)
-    {
-        try
-        {
-            // Exit functions return 0 if successful.
-            if ((*func)() != 0)
-            {
-                success = false;
-            }
-        }
-        catch (...)
-        {
-            success = false;
-        }
-    }
-
-    // This clears the handles on the dynamic libraries so that they won't
-    // be closed with dlclose().  Depending on the order of dll unloading,
-    // it's possible for a plugin library to be unloaded before this function
-    // (which is usually in a dll) is called, which can raise an error on
-    // some systems when dlclose() is called on a library for which the
-    // reference count is already 0.  Since we're exiting anyway and all the
-    // dlls will be closed, there is no need to call dlclose() on them
-    // explicitly.
-    for (auto l : m_dynamicLibraryMap)
-        l.second->clear();
-
-    m_dynamicLibraryMap.clear();
-    m_plugins.clear();
-    m_exitFuncVec.clear();
-
-    return success;
-}
-
-
-bool PluginManager::loadPlugin(const std::string& driverFileName)
-{
-    return s_instance.l_loadPlugin(driverFileName);
-}
-
-
-bool PluginManager::l_loadPlugin(const std::string& driverFileName)
-{
-    std::vector<std::string> driverPathVec;
-    driverPathVec = Utils::split2(driverFileName, '.');
-
-    if ((FileUtils::extension(driverFileName) != dynamicLibraryExtension) ||
-            FileUtils::isDirectory(driverFileName))
-        return false;
-
-    std::vector<std::string> driverNameVec;
-    driverNameVec = Utils::split2(driverPathVec[0], '_');
-
-    std::string ptype;
-    if (driverNameVec.size() >= 3)
-        ptype = driverNameVec[2];
-
-    PF_PluginType type;
-    if (Utils::iequals(ptype, "reader"))
-        type = PF_PluginType_Reader;
-    else if (Utils::iequals(ptype,"kernel"))
-        type = PF_PluginType_Kernel;
-    else if (Utils::iequals(ptype, "filter"))
-        type = PF_PluginType_Filter;
-    else if (Utils::iequals(ptype, "writer"))
-        type = PF_PluginType_Writer;
-    else
-        throw pdal_error("Unknown plugin type '" + ptype + "'");
-
-    return loadByPath(driverFileName, type);
-}
-
-
-bool PluginManager::guessLoadByPath(const std::string& driverName)
-{
-    // parse the driver name into an expected pluginName, e.g.,
-    // writers.las => libpdal_plugin_writer_las
-
-    std::vector<std::string> driverNameVec;
-    driverNameVec = Utils::split2(driverName, '.');
-
-    for (const auto& pluginPath : pluginSearchPaths())
-    {
-        m_log->get(LogLevel::Debug) <<
-           "Plugin search path: '" << pluginPath << "'" << std::endl;
-        if (!FileUtils::fileExists(pluginPath) ||
-            !FileUtils::isDirectory(pluginPath))
-            continue;
-
-        StringList files = FileUtils::directoryList(pluginPath);
-        for (auto file : files)
-        {
-            if ((FileUtils::extension(file) != dynamicLibraryExtension) ||
-                FileUtils::isDirectory(file))
-                continue;
-
-            std::string stem = FileUtils::stem(file);
-            std::string::size_type pos = stem.find_last_of('_');
-            if (pos == std::string::npos || pos == stem.size() - 1 ||
-                    stem.substr(pos + 1) != driverNameVec[1])
-                continue;
-
-            PF_PluginType type;
-            if (driverNameVec[0] == "readers")
-                type = PF_PluginType_Reader;
-            else if (driverNameVec[0] == "kernels")
-                type = PF_PluginType_Kernel;
-            else if (driverNameVec[0] == "filters")
-                type = PF_PluginType_Filter;
-            else if (driverNameVec[0] == "writers")
-                type = PF_PluginType_Writer;
-            else
-                type = PF_PluginType_Reader;
-
-            if (loadByPath(file, type))
-                return true;
-        }
-    }
-
-    return false;
-}
-
-
-bool PluginManager::loadByPath(const std::string& pluginPath, int type)
-{
-    // Only filenames that start with libpdal_plugin are candidates to be loaded
-    // at runtime.  PDAL plugins are to be named in a specified form:
-
-    // libpdal_plugin_{stagetype}_{name}
-
-    // For example, libpdal_plugin_writer_text or libpdal_plugin_filter_color
-
-    bool loaded(false);
-
-    std::string filename = Utils::tolower(FileUtils::getFilename(pluginPath));
-
-    // If we are a valid type, and we're not yet already
-    // loaded in the LibraryMap, load it.
-
-    if (pluginTypeValid(filename, type) && !libraryLoaded(pluginPath))
-    {
-        std::string errorString;
-        auto completePath(FileUtils::toAbsolutePath(pluginPath));
-
-        m_log->get(LogLevel::Debug) << "Attempting to load plugin '" <<
-            completePath << "'." << std::endl;
-
-        if (DynamicLibrary *d = loadLibrary(completePath, errorString))
-        {
-            m_log->get(LogLevel::Debug) << "Loaded plugin '" << completePath <<
-                "'." << std::endl;
-            if (PF_InitFunc initFunc =
-                    (PF_InitFunc)(d->getSymbol("PF_initPlugin")))
-            {
-                loaded = initializePlugin(initFunc);
-                m_log->get(LogLevel::Debug) << "Initialized plugin '" <<
-                    completePath << "'." << std::endl;
-            }
-            else
-                m_log->get(LogLevel::Error) <<
-                    "Failed to initialize plugin function for plugin '" <<
-                    completePath << "'." << std::endl;
-        }
-        else
-            m_log->get(LogLevel::Error) << "Plugin '" << completePath <<
-                "' found but failed to load: " << errorString << std::endl;
-    }
-
-    return loaded;
-}
-
-
-void *PluginManager::createObject(const std::string& objectType)
-{
-    return s_instance.l_createObject(objectType);
-}
-
-
-void *PluginManager::l_createObject(const std::string& objectType)
-{
-    auto find([this, &objectType]()->bool
-    {
-        std::lock_guard<std::mutex> lock(m_mutex);
-        return m_plugins.count(objectType);
-    });
-
-
-    void *obj(0);
-    if (find() || (guessLoadByPath(objectType) && find()))
-    {
-        PF_CreateFunc f;
-        {
-            std::lock_guard<std::mutex> lock(m_mutex);
-            f = m_plugins[objectType].createFunc;
-        }
-        obj = f();
-    }
-
-    return obj;
-}
-
-
-DynamicLibrary *PluginManager::loadLibrary(const std::string& path,
-    std::string& errorString)
-{
-    DynamicLibrary *d = DynamicLibrary::load(path, errorString);
-
-    if (d)
-    {
-        std::lock_guard<std::mutex> lock(m_mutex);
-        m_dynamicLibraryMap[FileUtils::toAbsolutePath(path)] = DynLibPtr(d);
-    }
-    else
-    {
-        m_log->get(LogLevel::Error) << "Can't load library " << path <<
-            ": " << errorString;
-    }
-
-    return d;
-}
-
-bool PluginManager::libraryLoaded(const std::string& path)
-{
-    std::lock_guard<std::mutex> lock(m_mutex);
-
-    std::string p = FileUtils::toAbsolutePath(path);
-    return Utils::contains(m_dynamicLibraryMap, p);
-}
-
-} // namespace pdal
-
diff --git a/src/PointView.cpp b/src/PointView.cpp
deleted file mode 100644
index c2c3913..0000000
--- a/src/PointView.cpp
+++ /dev/null
@@ -1,244 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Hobu Inc.
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <iomanip>
-
-#include <pdal/PointView.hpp>
-#include <pdal/PointViewIter.hpp>
-
-namespace pdal
-{
-
-int PointView::m_lastId = 0;
-
-PointView::PointView(PointTableRef pointTable) : m_pointTable(pointTable),
-m_size(0), m_id(0)
-{
-	m_id = ++m_lastId;
-}
-
-PointView::PointView(PointTableRef pointTable, const SpatialReference& srs) :
-	m_pointTable(pointTable), m_size(0), m_id(0), m_spatialReference(srs)
-{
-	m_id = ++m_lastId;
-}
-
-PointViewIter PointView::begin()
-{
-    return PointViewIter(this, 0);
-}
-
-
-PointViewIter PointView::end()
-{
-    return PointViewIter(this, size());
-}
-
-
-void PointView::setFieldInternal(Dimension::Id dim, PointId idx,
-    const void *buf)
-{
-    PointId rawId = 0;
-    if (idx == size())
-    {
-        rawId = m_pointTable.addPoint();
-        m_index.push_back(rawId);
-        m_size++;
-        assert(m_temps.empty());
-    }
-    else if (idx > size())
-    {
-        std::cerr << "Point index must increment.\n";
-        //error - throw?
-        return;
-    }
-    else
-    {
-        rawId = m_index[idx];
-    }
-    m_pointTable.setFieldInternal(dim, rawId, buf);
-}
-
-
-void PointView::calculateBounds(BOX2D& output) const
-{
-    for (PointId idx = 0; idx < size(); idx++)
-    {
-        double x = getFieldAs<double>(Dimension::Id::X, idx);
-        double y = getFieldAs<double>(Dimension::Id::Y, idx);
-
-        output.grow(x, y);
-    }
-}
-
-
-void PointView::calculateBounds(const PointViewSet& set, BOX2D& output)
-{
-    for (auto iter = set.begin(); iter != set.end(); ++iter)
-    {
-        PointViewPtr buf = *iter;
-        buf->calculateBounds(output);
-    }
-}
-
-
-void PointView::calculateBounds(BOX3D& output) const
-{
-    for (PointId idx = 0; idx < size(); idx++)
-    {
-        double x = getFieldAs<double>(Dimension::Id::X, idx);
-        double y = getFieldAs<double>(Dimension::Id::Y, idx);
-        double z = getFieldAs<double>(Dimension::Id::Z, idx);
-
-        output.grow(x, y, z);
-    }
-}
-
-
-void PointView::calculateBounds(const PointViewSet& set, BOX3D& output)
-{
-    for (auto iter = set.begin(); iter != set.end(); ++iter)
-    {
-        PointViewPtr buf = *iter;
-        buf->calculateBounds(output);
-    }
-}
-
-
-MetadataNode PointView::toMetadata() const
-{
-    MetadataNode node;
-
-    const Dimension::IdList& dims = layout()->dims();
-
-    for (PointId idx = 0; idx < size(); idx++)
-    {
-        MetadataNode pointnode = node.add(std::to_string(idx));
-        for (auto di = dims.begin(); di != dims.end(); ++di)
-        {
-            double v = getFieldAs<double>(*di, idx);
-            pointnode.add(layout()->dimName(*di), v);
-        }
-    }
-    return node;
-}
-
-
-void PointView::dump(std::ostream& ostr) const
-{
-    using std::endl;
-    PointLayoutPtr layout = m_pointTable.layout();
-    const Dimension::IdList& dims = layout->dims();
-
-    point_count_t numPoints = size();
-    ostr << "Contains " << numPoints << "  points" << endl;
-    for (PointId idx = 0; idx < numPoints; idx++)
-    {
-        ostr << "Point: " << idx << endl;
-
-        for (auto di = dims.begin(); di != dims.end(); ++di)
-        {
-            Dimension::Id d = *di;
-            const Dimension::Detail *dd = layout->dimDetail(d);
-            ostr << Dimension::name(d) << " (" <<
-                Dimension::interpretationName(dd->type()) << ") : ";
-
-            switch (dd->type())
-            {
-            case Dimension::Type::Signed8:
-                {
-                    ostr << (int)(getFieldInternal<int8_t>(d, idx));
-                    break;
-                }
-            case Dimension::Type::Signed16:
-                {
-                    ostr << getFieldInternal<int16_t>(d, idx);
-                    break;
-                }
-            case Dimension::Type::Signed32:
-                {
-                    ostr << getFieldInternal<int32_t>(d, idx);
-                    break;
-                }
-            case Dimension::Type::Signed64:
-                {
-                    ostr << getFieldInternal<int64_t>(d, idx);
-                    break;
-                }
-            case Dimension::Type::Unsigned8:
-                {
-                    ostr << (unsigned)(getFieldInternal<uint8_t>(d, idx));
-                    break;
-                }
-            case Dimension::Type::Unsigned16:
-                {
-                    ostr << getFieldInternal<uint16_t>(d, idx);
-                    break;
-                }
-            case Dimension::Type::Unsigned32:
-                {
-                    ostr << getFieldInternal<uint32_t>(d, idx);
-                    break;
-                }
-            case Dimension::Type::Unsigned64:
-                {
-                    ostr << getFieldInternal<uint64_t>(d, idx);
-                    break;
-                }
-            case Dimension::Type::Float:
-                {
-                    ostr << getFieldInternal<float>(d, idx);
-                    break;
-                }
-            case Dimension::Type::Double:
-                {
-                    ostr << getFieldInternal<double>(d, idx);
-                    break;
-                }
-            default:
-                throw;
-            }
-            ostr << endl;
-        }
-    }
-}
-
-
-std::ostream& operator<<(std::ostream& ostr, const PointView& buf)
-{
-    buf.dump(ostr);
-    return ostr;
-}
-
-} // namespace pdal
diff --git a/src/Polygon.cpp b/src/Polygon.cpp
deleted file mode 100644
index ac85b3e..0000000
--- a/src/Polygon.cpp
+++ /dev/null
@@ -1,515 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/Polygon.hpp>
-#include "cpl_string.h"
-
-#include <ogr_geometry.h>
-
-namespace pdal
-{
-
-Polygon::Polygon()
-    : m_geom(0)
-    , m_prepGeom(0)
-    , m_ctx(geos::ErrorHandler::get().ctx())
-{
-    m_geom = GEOSGeom_createEmptyPolygon_r(m_ctx);
-}
-
-
-Polygon::Polygon(const std::string& wkt_or_json, SpatialReference ref,
-        geos::ErrorHandler& err)
-    : m_geom(0)
-    , m_prepGeom(0)
-    , m_srs(ref)
-    , m_ctx(err.ctx())
-{
-    update(wkt_or_json, ref);
-}
-
-
-Polygon::Polygon(const std::string& wkt_or_json, SpatialReference ref,
-    GEOSContextHandle_t ctx)
-    : m_geom(0)
-    , m_prepGeom(0)
-    , m_srs(ref)
-    , m_ctx(ctx)
-{
-    update(wkt_or_json, ref);
-}
-
-
-Polygon::~Polygon()
-{
-    if (m_geom)
-        GEOSGeom_destroy_r(m_ctx, m_geom);
-    if (m_prepGeom)
-        GEOSPreparedGeom_destroy_r(m_ctx, m_prepGeom);
-    m_geom = 0;
-    m_prepGeom = 0;
-}
-
-
-void Polygon::update(const std::string& wkt_or_json, SpatialReference ref)
-{
-    bool isJson = wkt_or_json.find("{") != wkt_or_json.npos ||
-                  wkt_or_json.find("}") != wkt_or_json.npos;
-
-    if (!isJson)
-    {
-        m_geom = GEOSGeomFromWKT_r(m_ctx, wkt_or_json.c_str());
-        if (!m_geom)
-            throw pdal_error("Unable to create geometry from input WKT");
-    }
-    else
-    {
-        // Assume it is GeoJSON and try constructing from that
-        OGRGeometryH json = OGR_G_CreateGeometryFromJson(wkt_or_json.c_str());
-
-        if (!json)
-            throw pdal_error("Unable to create geometry from "
-                "input GeoJSON");
-
-        char* gdal_wkt(0);
-        OGRErr err = OGR_G_ExportToWkt(json, &gdal_wkt);
-        m_geom = GEOSGeomFromWKT_r(m_ctx, gdal_wkt);
-        //ABELL - Why should this ever throw?  Is it worth catching if
-        //  we don't know why?
-        if (!m_geom)
-            throw pdal_error("Unable to create GEOS geometry from OGR WKT!");
-        OGRFree(gdal_wkt);
-        OGR_G_DestroyGeometry(json);
-    }
-    prepare();
-}
-
-
-void Polygon::prepare()
-{
-    if (m_geom)
-    {
-        m_prepGeom = GEOSPrepare_r(m_ctx, m_geom);
-        if (!m_prepGeom)
-            throw pdal_error("unable to prepare geometry for index-accelerated access");
-    }
-}
-
-Polygon& Polygon::operator=(const Polygon& input)
-{
-
-    if (&input!= this)
-    {
-        m_ctx = input.m_ctx;
-        m_srs = input.m_srs;
-        m_geom = GEOSGeom_clone_r(m_ctx, input.m_geom);
-        prepare();
-    }
-    return *this;
-
-
-}
-
-Polygon::Polygon(const Polygon& input)
-    : m_srs(input.m_srs)
-    , m_ctx(input.m_ctx)
-{
-    assert(input.m_geom != 0);
-    m_geom = GEOSGeom_clone_r(m_ctx, input.m_geom);
-    assert(m_geom != 0);
-    m_prepGeom = 0;
-    prepare();
-}
-
-
-Polygon::Polygon(GEOSGeometry* g, const SpatialReference& srs,
-    geos::ErrorHandler& err)
-    : m_geom(GEOSGeom_clone_r(err.ctx(), g))
-    , m_srs(srs)
-    , m_ctx(err.ctx())
-{
-    prepare();
-}
-
-
-Polygon::Polygon(GEOSGeometry* g, const SpatialReference& srs,
-    GEOSContextHandle_t ctx)
-    : m_geom(GEOSGeom_clone_r(ctx, g))
-    , m_srs(srs)
-    , m_ctx(ctx)
-{
-    prepare();
-}
-
-
-Polygon::Polygon(OGRGeometryH g, const SpatialReference& srs,
-    geos::ErrorHandler& err) : m_srs(srs) , m_ctx(err.ctx())
-{
-    OGRwkbGeometryType t = OGR_G_GetGeometryType(g);
-
-    if (!(t == wkbPolygon ||
-        t == wkbMultiPolygon ||
-        t == wkbPolygon25D ||
-        t == wkbMultiPolygon25D))
-    {
-        std::ostringstream oss;
-        oss << "pdal::Polygon cannot construct geometry because "
-            "OGR geometry is not Polygon or MultiPolygon!";
-        throw pdal::pdal_error(oss.str());
-    }
-
-    OGRGeometry *ogr_g = (OGRGeometry*)g;
-    //
-    // Convert the the GDAL geom to WKB in order to avoid the version
-    // context issues with exporting directoly to GEOS.
-    OGRwkbByteOrder bo =
-        GEOS_getWKBByteOrder() == GEOS_WKB_XDR ? wkbXDR : wkbNDR;
-    int wkbSize = ogr_g->WkbSize();
-    std::vector<unsigned char> wkb(wkbSize);
-
-    ogr_g->exportToWkb(bo, wkb.data());
-    m_geom = GEOSGeomFromWKB_buf_r(m_ctx, wkb.data(), wkbSize);
-    prepare();
-
-}
-
-Polygon::Polygon(const BOX2D& box) : m_ctx(geos::ErrorHandler::get().ctx())
-{
-    BOX3D box3(box.minx, box.miny, 0.0,
-               box.maxx, box.maxy, 0.0);
-    initializeFromBounds(box3);
-}
-
-
-Polygon::Polygon(const BOX3D& box) : m_ctx(geos::ErrorHandler::get().ctx())
-{
-    initializeFromBounds(box);
-}
-
-
-void Polygon::initializeFromBounds(const BOX3D& box)
-{
-    GEOSCoordSequence* coords = GEOSCoordSeq_create_r(m_ctx, 5, 3);
-    auto set_coordinate = [coords, this](int pt_num, const double&x,
-        const double& y, const double& z)
-    {
-        if (!GEOSCoordSeq_setX_r(m_ctx, coords, pt_num, x))
-            throw pdal_error("unable to set x for coordinate sequence");
-        if (!GEOSCoordSeq_setY_r(m_ctx, coords, pt_num, y))
-            throw pdal_error("unable to set y for coordinate sequence");
-        if (!GEOSCoordSeq_setZ_r(m_ctx, coords, pt_num, z))
-            throw pdal_error("unable to set z for coordinate sequence");
-    };
-
-    set_coordinate(0, box.minx, box.miny, box.minz);
-    set_coordinate(1, box.minx, box.maxy, box.minz);
-    set_coordinate(2, box.maxx, box.maxy, box.maxz);
-    set_coordinate(3, box.maxx, box.miny, box.maxz);
-    set_coordinate(4, box.minx, box.miny, box.minz);
-
-
-    GEOSGeometry* ring = GEOSGeom_createLinearRing_r(m_ctx, coords);
-    if (!ring)
-        throw pdal_error("unable to create linear ring from BOX2D");
-
-
-    m_geom = GEOSGeom_createPolygon_r(m_ctx, ring, 0, 0);
-    if (!m_geom)
-        throw pdal_error("unable to create polygon from linear ring in "
-            "BOX2D constructor");
-    prepare();
-}
-
-
-Polygon Polygon::transform(const SpatialReference& ref) const
-{
-    if (m_srs.empty())
-        throw pdal_error("Polygon::transform failed due to m_srs being empty");
-    if (ref.empty())
-        throw pdal_error("Polygon::transform failed due to ref being empty");
-
-    gdal::SpatialRef fromRef(m_srs.getWKT());
-    gdal::SpatialRef toRef(ref.getWKT());
-    gdal::Geometry geom(wkt(12, true), fromRef);
-    geom.transform(toRef);
-    return Polygon(geom.wkt(), ref, m_ctx);
-}
-
-
-Polygon Polygon::simplify(double distance_tolerance,
-    double area_tolerance) const
-{
-    GEOSGeometry *smoothed =
-        GEOSTopologyPreserveSimplify_r(m_ctx, m_geom, distance_tolerance);
-    if (!smoothed)
-        throw pdal_error("Unable to simplify input geometry!");
-
-    std::vector<GEOSGeometry*> geometries;
-
-    int numGeom = GEOSGetNumGeometries_r(m_ctx, smoothed);
-    for (int n = 0; n < numGeom; ++n)
-    {
-        const GEOSGeometry* m = GEOSGetGeometryN_r(m_ctx, smoothed, n);
-        if (!m)
-            throw pdal::pdal_error("Unable to Get GeometryN");
-
-        const GEOSGeometry* ering = GEOSGetExteriorRing_r(m_ctx, m);
-        if (!ering)
-            throw pdal::pdal_error("Unable to Get Exterior Ring");
-
-        GEOSGeometry* exterior = GEOSGeom_clone_r(m_ctx,
-            GEOSGetExteriorRing_r(m_ctx, m));
-        if (!exterior)
-            throw pdal::pdal_error("Unable to clone exterior ring!");
-
-        std::vector<GEOSGeometry*> keep_rings;
-        int numRings = GEOSGetNumInteriorRings_r(m_ctx, m);
-        for (int i = 0; i < numRings; ++i)
-        {
-            double area(0.0);
-
-            const GEOSGeometry* iring =
-                GEOSGetInteriorRingN_r(m_ctx, m, i);
-            if (!iring)
-                throw pdal::pdal_error("Unable to Get Interior Ring");
-
-            GEOSGeometry* cring = GEOSGeom_clone_r(m_ctx, iring);
-            if (!cring)
-                throw pdal::pdal_error("Unable to clone interior ring!");
-            GEOSGeometry* aring = GEOSGeom_createPolygon_r(m_ctx, cring,
-                NULL, 0);
-
-            int errored = GEOSArea_r(m_ctx, aring, &area);
-            if (errored == 0)
-                throw pdal::pdal_error("Unable to get area of ring!");
-            if (area > area_tolerance)
-            {
-                keep_rings.push_back(cring);
-            }
-        }
-
-        GEOSGeometry* p = GEOSGeom_createPolygon_r(m_ctx, exterior,
-            keep_rings.data(), keep_rings.size());
-        if (p == NULL) throw
-            pdal::pdal_error("smooth polygon could not be created!" );
-        geometries.push_back(p);
-    }
-
-    GEOSGeometry* o = GEOSGeom_createCollection_r(m_ctx, GEOS_MULTIPOLYGON,
-        geometries.data(), geometries.size());
-    Polygon p(o, m_srs, m_ctx);
-    GEOSGeom_destroy_r(m_ctx, smoothed);
-    GEOSGeom_destroy_r(m_ctx, o);
-
-    return p;
-}
-
-double Polygon::area() const
-{
-    double output(0.0);
-    int errored = GEOSArea_r(m_ctx, m_geom, &output);
-    if (errored == 0)
-        throw pdal::pdal_error("Unable to get area of ring!");
-    return output;
-}
-
-
-bool Polygon::covers(PointRef& ref) const
-{
-    GEOSCoordSequence* coords = GEOSCoordSeq_create_r(m_ctx, 1, 3);
-    if (!coords)
-        throw pdal_error("Unable to allocate coordinate sequence");
-
-    const double x = ref.getFieldAs<double>(Dimension::Id::X);
-    const double y = ref.getFieldAs<double>(Dimension::Id::Y);
-    const double z = ref.getFieldAs<double>(Dimension::Id::Z);
-
-    if (!GEOSCoordSeq_setX_r(m_ctx, coords, 0, x))
-        throw pdal_error("unable to set x for coordinate sequence");
-    if (!GEOSCoordSeq_setY_r(m_ctx, coords, 0, y))
-        throw pdal_error("unable to set y for coordinate sequence");
-    if (!GEOSCoordSeq_setZ_r(m_ctx, coords, 0, z))
-        throw pdal_error("unable to set z for coordinate sequence");
-    GEOSGeometry* p = GEOSGeom_createPoint_r(m_ctx, coords);
-    if (!p)
-        throw pdal_error("unable to allocate candidate test point");
-
-    bool covers = (bool)(GEOSPreparedCovers_r(m_ctx, m_prepGeom, p));
-    GEOSGeom_destroy_r(m_ctx, p);
-
-    return covers;
-}
-
-
-BOX3D Polygon::bounds() const
-{
-    uint32_t numInputDims;
-    BOX3D output;
-
-    GEOSGeometry* boundary = GEOSGeom_clone_r(m_ctx, m_geom);
-
-    // Smash out multi*
-    if (GEOSGeomTypeId_r(m_ctx, m_geom) > 3)
-        boundary = GEOSEnvelope_r(m_ctx, m_geom);
-
-    GEOSGeometry const* ring = GEOSGetExteriorRing_r(m_ctx, boundary);
-    GEOSCoordSequence const* coords = GEOSGeom_getCoordSeq_r(m_ctx, ring);
-
-    GEOSCoordSeq_getDimensions_r(m_ctx, coords, &numInputDims);
-
-    uint32_t count(0);
-    GEOSCoordSeq_getSize_r(m_ctx, coords, &count);
-
-    double x(0.0);
-    double y(0.0);
-    double z(0.0);
-    for (unsigned i = 0; i < count; ++i)
-    {
-        GEOSCoordSeq_getOrdinate_r(m_ctx, coords, i, 0, &x);
-        GEOSCoordSeq_getOrdinate_r(m_ctx, coords, i, 1, &y);
-        if (numInputDims > 2)
-            GEOSCoordSeq_getOrdinate_r(m_ctx, coords, i, 2, &z);
-        output.grow(x, y, z);
-    }
-    GEOSGeom_destroy_r(m_ctx, boundary);
-
-    return output;
-}
-
-
-bool Polygon::equals(const Polygon& p, double tolerance) const
-{
-    return (bool) GEOSEqualsExact_r(m_ctx, m_geom, p.m_geom, tolerance);
-}
-
-
-
-bool Polygon::operator==(const Polygon& input) const
-{
-    return this->equals(input);
-}
-
-
-bool Polygon::operator!=(const Polygon& input) const
-{
-    return !(this->equals(input));
-}
-
-
-bool Polygon::valid() const
-{
-    int gtype = GEOSGeomTypeId_r(m_ctx, m_geom);
-    if (gtype != GEOS_POLYGON && gtype != GEOS_MULTIPOLYGON)
-        return false;
-
-    return (bool)GEOSisValid_r(m_ctx, m_geom);
-}
-
-
-std::string Polygon::validReason() const
-{
-    int gtype = GEOSGeomTypeId_r(m_ctx, m_geom);
-    if (gtype != GEOS_POLYGON && gtype != GEOS_MULTIPOLYGON)
-        return std::string("Geometry is not Polygon or MultiPolygon");
-
-    char *reason = GEOSisValidReason_r(m_ctx, m_geom);
-    std::string output(reason);
-    GEOSFree_r(m_ctx, reason);
-    return output;
-}
-
-
-std::string Polygon::wkt(double precision, bool bOutputZ) const
-{
-    GEOSWKTWriter *writer = GEOSWKTWriter_create_r(m_ctx);
-    GEOSWKTWriter_setRoundingPrecision_r(m_ctx, writer, precision);
-    if (bOutputZ)
-        GEOSWKTWriter_setOutputDimension_r(m_ctx, writer, 3);
-
-    char *smoothWkt = GEOSWKTWriter_write_r(m_ctx, writer, m_geom);
-    std::string output(smoothWkt);
-    GEOSFree_r(m_ctx, smoothWkt);
-    GEOSWKTWriter_destroy_r(m_ctx, writer);
-    return output;
-}
-
-
-std::string Polygon::json(double precision) const
-{
-    std::ostringstream prec;
-    prec << precision;
-    char **papszOptions = NULL;
-    papszOptions = CSLSetNameValue(papszOptions, "COORDINATE_PRECISION",
-        prec.str().c_str() );
-
-    std::string w(wkt());
-
-    gdal::SpatialRef srs(m_srs.getWKT(pdal::SpatialReference::eCompoundOK));
-    gdal::Geometry g(w, srs);
-
-    char* json = OGR_G_ExportToJsonEx(g.get(), papszOptions);
-
-    std::string output(json);
-    OGRFree(json);
-    return output;
-}
-
-
-std::ostream& operator<<(std::ostream& ostr, const Polygon& p)
-{
-    ostr << p.wkt();
-    return ostr;
-}
-
-
-std::istream& operator>>(std::istream& istr, Polygon& p)
-{
-
-    std::ostringstream oss;
-    oss << istr.rdbuf();
-
-    std::string wkt = oss.str();
-
-    try
-    {
-        p.update(wkt);
-    }
-    catch (pdal_error)
-    {
-        istr.setstate(std::ios::failbit);
-    }
-    return istr;
-}
-
-} // namespace geos
diff --git a/src/SpatialReference.cpp b/src/SpatialReference.cpp
deleted file mode 100644
index e80c2e7..0000000
--- a/src/SpatialReference.cpp
+++ /dev/null
@@ -1,511 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2009, Howard Butler
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of the Martin Isenburg or Iowa Department
- *       of Natural Resources nor the names of its contributors may be
- *       used to endorse or promote products derived from this software
- *       without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#include <pdal/SpatialReference.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/Metadata.hpp>
-
-// gdal
-#ifdef PDAL_COMPILER_CLANG
-#  pragma clang diagnostic push
-#  pragma clang diagnostic ignored "-Wfloat-equal"
-#endif
-#ifdef PDAL_COMPILER_GCC
-#  pragma GCC diagnostic push
-#  pragma GCC diagnostic ignored "-Wfloat-equal"
-#endif
-#include <ogr_spatialref.h>
-#ifdef PDAL_COMPILER_GCC
-#  pragma GCC diagnostic pop
-#endif
-#ifdef PDAL_COMPILER_CLANG
-#  pragma clang diagnostic pop
-#endif
-#include <cpl_conv.h>
-
-#include <pdal/util/Utils.hpp>
-
-namespace pdal
-{
-
-SpatialReference::SpatialReference(const std::string& s)
-{
-    setFromUserInput(s);
-}
-
-
-bool SpatialReference::empty() const
-{
-    return getWKT().empty();
-}
-
-bool SpatialReference::valid() const
-{
-    std::string wkt = getWKT();
-    OGRSpatialReferenceH current =
-        OSRNewSpatialReference(wkt.c_str());
-
-    OGRErr err = OSRValidate(current);
-
-    OSRDestroySpatialReference(current);
-    return err == OGRERR_NONE;
-}
-
-
-std::string SpatialReference::getWKT(WKTModeFlag mode_flag) const
-{
-    return getWKT(mode_flag, false);
-}
-
-
-/// Fetch the SRS as WKT
-std::string SpatialReference::getWKT(WKTModeFlag mode_flag , bool pretty) const
-{
-    std::string result_wkt = m_wkt;
-
-    if ((mode_flag == eHorizontalOnly
-            && strstr(result_wkt.c_str(),"COMPD_CS") != NULL)
-            || pretty)
-    {
-        OGRSpatialReference* poSRS =
-            (OGRSpatialReference*)OSRNewSpatialReference(result_wkt.c_str());
-        char *pszWKT = NULL;
-
-        if (mode_flag == eHorizontalOnly)
-            poSRS->StripVertical();
-        if (pretty)
-            poSRS->exportToPrettyWkt(&pszWKT, FALSE);
-        else
-            poSRS->exportToWkt(&pszWKT);
-
-        OSRDestroySpatialReference(poSRS);
-
-        result_wkt = pszWKT;
-        CPLFree(pszWKT);
-    }
-
-    return result_wkt;
-}
-
-
-void SpatialReference::setFromUserInput(std::string const& v)
-{
-    if (v.empty())
-    {
-        m_wkt.clear();
-        return;
-    }
-
-    OGRSpatialReference srs(NULL);
-
-    CPLErrorReset();
-    const char* input = v.c_str();
-    OGRErr err = srs.SetFromUserInput(const_cast<char *>(input));
-    if (err != OGRERR_NONE)
-    {
-        std::ostringstream oss;
-        std::string msg = CPLGetLastErrorMsg();
-        if (msg.empty())
-            msg = "(unknown reason)";
-        oss << "Could not import coordinate system '" << input << "': " <<
-            msg << ".";
-        throw pdal_error(oss.str());
-    }
-
-    char *poWKT = 0;
-    srs.exportToWkt(&poWKT);
-    std::string tmp(poWKT);
-    CPLFree(poWKT);
-    setWKT(tmp);
-}
-
-
-std::string SpatialReference::getProj4() const
-{
-    std::string tmp;
-
-    std::string wkt = getWKT(eCompoundOK);
-    const char* poWKT = wkt.c_str();
-
-    OGRSpatialReference srs(NULL);
-    if (OGRERR_NONE == srs.importFromWkt(const_cast<char **>(&poWKT)))
-    {
-        char* proj4 = 0;
-        srs.exportToProj4(&proj4);
-        tmp = proj4;
-        CPLFree(proj4);
-
-        Utils::trim(tmp);
-    }
-
-    return tmp;
-}
-
-std::string SpatialReference::getVertical() const
-{
-    std::string tmp("");
-
-    OGRSpatialReference* poSRS =
-        (OGRSpatialReference*)OSRNewSpatialReference(m_wkt.c_str());
-    char *pszWKT = NULL;
-
-    OGR_SRSNode* node = poSRS->GetAttrNode("VERT_CS");
-    if (node && poSRS)
-    {
-        node->exportToWkt(&pszWKT);
-        tmp = pszWKT;
-        CPLFree(pszWKT);
-        OSRDestroySpatialReference(poSRS);
-    }
-
-    return tmp;
-}
-
-std::string SpatialReference::getVerticalUnits() const
-{
-    std::string tmp("");
-
-    std::string wkt = getWKT(eCompoundOK);
-    const char* poWKT = wkt.c_str();
-    OGRSpatialReference* poSRS =
-        (OGRSpatialReference*)OSRNewSpatialReference(m_wkt.c_str());
-    if (poSRS)
-    {
-        OGR_SRSNode* node = poSRS->GetAttrNode("VERT_CS");
-        if (node)
-        {
-            char* units(0);
-
-            // The returned value remains internal to the OGRSpatialReference and should not
-            //        be freed, or modified. It may be invalidated on the next
-            //        OGRSpatialReference call.
-
-            double u = poSRS->GetLinearUnits(&units);
-            tmp = units;
-
-            Utils::trim(tmp);
-
-        }
-    }
-
-    return tmp;
-}
-
-
-std::string SpatialReference::getHorizontal() const
-{
-    std::string tmp("");
-
-    OGRSpatialReference* poSRS =
-        (OGRSpatialReference*)OSRNewSpatialReference(m_wkt.c_str());
-    char *pszWKT = NULL;
-
-    if (poSRS)
-    {
-        poSRS->StripVertical();
-
-        poSRS->exportToWkt(&pszWKT);
-        tmp = pszWKT;
-        CPLFree(pszWKT);
-        OSRDestroySpatialReference(poSRS);
-    }
-
-    return tmp;
-}
-
-std::string SpatialReference::getHorizontalUnits() const
-{
-    std::string tmp("");
-
-    std::string wkt = getWKT(eHorizontalOnly);
-    const char* poWKT = wkt.c_str();
-    OGRSpatialReference* poSRS =
-        (OGRSpatialReference*)OSRNewSpatialReference(m_wkt.c_str());
-    if (poSRS)
-    {
-        char* units(0);
-
-        // The returned value remains internal to the OGRSpatialReference and should not
-        //        be freed, or modified. It may be invalidated on the next
-        //        OGRSpatialReference call.
-
-        double u = poSRS->GetLinearUnits(&units);
-        tmp = units;
-
-        Utils::trim(tmp);
-
-    }
-
-    return tmp;
-}
-
-void SpatialReference::setProj4(std::string const& v)
-{
-    char* poWKT = 0;
-    const char* poProj4 = v.c_str();
-
-    OGRSpatialReference srs(NULL);
-    if (OGRERR_NONE != srs.importFromProj4(const_cast<char *>(poProj4)))
-    {
-        throw std::invalid_argument("could not import proj4 into OSRSpatialReference SetProj4");
-    }
-
-    srs.exportToWkt(&poWKT);
-    m_wkt = poWKT;
-    CPLFree(poWKT);
-}
-
-
-bool SpatialReference::equals(const SpatialReference& input) const
-{
-    OGRSpatialReferenceH current =
-        OSRNewSpatialReference(getWKT(eCompoundOK, false).c_str());
-    OGRSpatialReferenceH other =
-        OSRNewSpatialReference(input.getWKT(eCompoundOK, false).c_str());
-
-    int output = OSRIsSame(current, other);
-    OSRDestroySpatialReference(current);
-    OSRDestroySpatialReference(other);
-
-    return (output == 1);
-}
-
-
-bool SpatialReference::operator==(const SpatialReference& input) const
-{
-    return this->equals(input);
-}
-
-
-bool SpatialReference::operator!=(const SpatialReference& input) const
-{
-    return !(this->equals(input));
-}
-
-
-const std::string& SpatialReference::getName() const
-{
-    static std::string name("pdal.spatialreference");
-    return name;
-}
-
-
-bool SpatialReference::isGeographic() const
-{
-    OGRSpatialReferenceH current =
-        OSRNewSpatialReference(getWKT(eCompoundOK, false).c_str());
-    bool output = OSRIsGeographic(current);
-    OSRDestroySpatialReference(current);
-    return output;
-}
-
-bool SpatialReference::isGeocentric() const
-{
-    OGRSpatialReferenceH current =
-        OSRNewSpatialReference(getWKT(eCompoundOK, false).c_str());
-    bool output = OSRIsGeocentric(current);
-    OSRDestroySpatialReference(current);
-    return output;
-}
-
-int SpatialReference::calculateZone(double lon, double lat)
-{
-    int zone = 0;
-    lon = Utils::normalizeLongitude(lon);
-
-    // Special Norway processing.
-    if (lat >= 56.0 && lat < 64.0 && lon >= 3.0 && lon < 12.0 )
-        zone = 32;
-    // Special Svalbard processing.
-    else if (lat >= 72.0 && lat < 84.0)
-    {
-        if (lon >= 0.0  && lon < 9.0)
-            zone = 31;
-        else if (lon >= 9.0  && lon < 21.0)
-            zone = 33;
-        else if (lon >= 21.0  && lon < 33.0)
-            zone = 35;
-        else if (lon >= 33.0  && lon < 42.0)
-            zone = 37;
-    }
-    // Everywhere else.
-    else
-    {
-        zone = floor((lon + 180.0) / 6) + 1;
-        if (lat < 0)
-            zone = -zone;
-    }
-
-    return zone;
-}
-
-
-int SpatialReference::computeUTMZone(const BOX3D& box) const
-{
-    // Nothing we can do if we're an empty SRS
-    if (empty())
-        return 0;
-
-    OGRSpatialReferenceH current =
-        OSRNewSpatialReference(getWKT(eHorizontalOnly, false).c_str());
-    if (! current)
-        throw std::invalid_argument("Could not fetch current SRS");
-
-    OGRSpatialReferenceH wgs84 = OSRNewSpatialReference(0);
-
-    if (OSRSetFromUserInput(wgs84, "EPSG:4326") != OGRERR_NONE)
-    {
-        OSRDestroySpatialReference(current);
-        OSRDestroySpatialReference(wgs84);
-        std::ostringstream msg;
-        msg << "Could not import GDAL input spatial reference for WGS84";
-        throw std::runtime_error(msg.str());
-    }
-
-    void* transform = OCTNewCoordinateTransformation(current, wgs84);
-
-    if (! transform)
-    {
-        OSRDestroySpatialReference(current);
-        OSRDestroySpatialReference(wgs84);
-        throw std::invalid_argument("could not comput transform from "
-            "coordinate system to WGS84");
-    }
-
-    double minx(0.0), miny(0.0), minz(0.0);
-    double maxx(0.0), maxy(0.0), maxz(0.0);
-
-    // OCTTransform modifies values in-place
-    minx = box.minx; miny = box.miny; minz = box.minz;
-    maxx = box.maxx; maxy = box.maxy; maxz = box.maxz;
-
-    int ret = OCTTransform(transform, 1, &minx, &miny, &minz);
-    if (ret == 0)
-    {
-        OCTDestroyCoordinateTransformation(transform);
-        OSRDestroySpatialReference(current);
-        OSRDestroySpatialReference(wgs84);
-        std::ostringstream msg;
-        msg << "Could not project minimum point for computeUTMZone::" <<
-            CPLGetLastErrorMsg() << ret;
-        throw pdal_error(msg.str());
-    }
-
-    ret = OCTTransform(transform, 1, &maxx, &maxy, &maxz);
-    if (ret == 0)
-    {
-        OCTDestroyCoordinateTransformation(transform);
-        OSRDestroySpatialReference(current);
-        OSRDestroySpatialReference(wgs84);
-        std::ostringstream msg;
-        msg << "Could not project maximum point for computeUTMZone::" <<
-            CPLGetLastErrorMsg() << ret;
-        throw pdal_error(msg.str());
-    }
-
-    int min_zone(0);
-    int max_zone(0);
-    min_zone = calculateZone(minx, miny);
-    max_zone = calculateZone(maxx, maxy);
-
-    if (min_zone != max_zone)
-    {
-        OCTDestroyCoordinateTransformation(transform);
-        OSRDestroySpatialReference(current);
-        OSRDestroySpatialReference(wgs84);
-        std::ostringstream msg;
-        msg << "Minimum zone is " << min_zone <<"' and maximum zone is '" <<
-            max_zone << "'. They do not match because they cross a "
-            "zone boundary";
-        throw pdal_error(msg.str());
-    }
-
-    OCTDestroyCoordinateTransformation(transform);
-    OSRDestroySpatialReference(current);
-    OSRDestroySpatialReference(wgs84);
-
-    return min_zone;
-}
-
-
-MetadataNode SpatialReference::toMetadata() const
-{
-    MetadataNode root("srs");
-    root.add("horizontal", getHorizontal());
-    root.add("vertical", getVertical());
-    root.add("isgeographic", isGeographic());
-    root.add("isgeocentric", isGeocentric());
-    root.add("proj4", getProj4());
-    root.add("prettywkt", getWKT(eHorizontalOnly, true));
-    root.add("wkt", getWKT(eHorizontalOnly, false));
-    root.add("compoundwkt", getWKT(eCompoundOK, false));
-    root.add("prettycompoundwkt", getWKT(eCompoundOK, true));
-
-    MetadataNode units = root.add("units");
-    units.add("vertical", getVerticalUnits());
-    units.add("horizontal", getHorizontalUnits());
-
-    return root;
-}
-
-
-void SpatialReference::dump() const
-{
-    std::cout << *this;
-}
-
-
-std::ostream& operator<<(std::ostream& ostr, const SpatialReference& srs)
-{
-    std::string wkt = srs.getWKT(SpatialReference::eCompoundOK, true);
-    ostr << wkt;
-    return ostr;
-}
-
-
-std::istream& operator>>(std::istream& istr, SpatialReference& srs)
-{
-    SpatialReference ref;
-
-    std::ostringstream oss;
-    oss << istr.rdbuf();
-
-    std::string wkt = oss.str();
-    ref.setFromUserInput(wkt.c_str());
-
-    srs = ref;
-    return istr;
-}
-
-} // namespace pdal
diff --git a/src/Stage.cpp b/src/Stage.cpp
deleted file mode 100644
index a11fd08..0000000
--- a/src/Stage.cpp
+++ /dev/null
@@ -1,398 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/GDALUtils.hpp>
-#include <pdal/GEOSUtils.hpp>
-#include <pdal/PipelineManager.hpp>
-#include <pdal/Stage.hpp>
-#include <pdal/SpatialReference.hpp>
-#include <pdal/PDALUtils.hpp>
-#include <pdal/util/ProgramArgs.hpp>
-
-#include "StageRunner.hpp"
-
-#include <iterator>
-#include <memory>
-
-namespace pdal
-{
-
-Stage::Stage() : m_progressFd(-1), m_debug(false), m_verbose(0)
-{}
-
-
-void Stage::addConditionalOptions(const Options& opts)
-{
-    for (const auto& o : opts.getOptions())
-        m_options.addConditional(o);
-}
-
-
-void Stage::serialize(MetadataNode root, PipelineWriter::TagMap& tags) const
-{
-    for (Stage *s : m_inputs)
-        s->serialize(root, tags);
-
-    auto tagname = [tags](const Stage *s)
-    {
-        const auto ti = tags.find(s);
-        return ti->second;
-    };
-
-    MetadataNode anon("pipeline");
-    anon.add("type", getName());
-    anon.add("tag", tagname(this));
-    m_options.toMetadata(anon);
-    for (Stage *s : m_inputs)
-        anon.addList("inputs", tagname(s));
-    root.addList(anon);
-}
-
-void Stage::addAllArgs(ProgramArgs& args)
-{
-    try
-    {
-        l_addArgs(args);
-        addArgs(args);
-    }
-    catch (arg_error error)
-    {
-        throw pdal_error(error.m_error);
-    }
-}
-
-
-void Stage::handleOptions()
-{
-    addAllArgs(*m_args);
-    try
-    {
-        StringList cmdline = m_options.toCommandLine();
-        m_args->parse(cmdline);
-    }
-    catch (arg_error error)
-    {
-        throw pdal_error(error.m_error);
-    }
-    setupLog();
-}
-
-
-QuickInfo Stage::preview()
-{
-    m_args.reset(new ProgramArgs);
-    handleOptions();
-    return inspect();
-}
-
-
-void Stage::prepare(PointTableRef table)
-{
-    m_args.reset(new ProgramArgs);
-    for (size_t i = 0; i < m_inputs.size(); ++i)
-    {
-        Stage *prev = m_inputs[i];
-        prev->prepare(table);
-    }
-    handleOptions();
-    l_initialize(table);
-    initialize(table);
-    addDimensions(table.layout());
-    prepared(table);
-}
-
-
-PointViewSet Stage::execute(PointTableRef table)
-{
-    table.finalize();
-
-    PointViewSet views;
-
-    // If the inputs are empty, we're a reader.
-    if (m_inputs.empty())
-    {
-        views.insert(PointViewPtr(new PointView(table)));
-    }
-    else
-    {
-        for (size_t i = 0; i < m_inputs.size(); ++i)
-        {
-            Stage *prev = m_inputs[i];
-            PointViewSet temp = prev->execute(table);
-            views.insert(temp.begin(), temp.end());
-        }
-    }
-
-    PointViewSet outViews;
-    std::vector<StageRunnerPtr> runners;
-
-    // Put the spatial references from the views onto the table.
-    // The table's spatial references are only valid as long as the stage
-    // is running.
-    // ABELL - Should we clear the references once the stage run has
-    //   completed?  Wondering if that would break something where a
-    //   writer wants to check a table's SRS.
-    SpatialReference srs;
-    table.clearSpatialReferences();
-    for (auto const& it : views)
-        table.addSpatialReference(it->spatialReference());
-    gdal::ErrorHandler::getGlobalErrorHandler().set(m_log, m_debug);
-
-    // Do the ready operation and then start running all the views
-    // through the stage.
-    ready(table);
-    for (auto const& it : views)
-    {
-        StageRunnerPtr runner(new StageRunner(this, it));
-        runners.push_back(runner);
-        runner->run();
-    }
-
-    // As the stages complete (synchronously at this time), propagate the
-    // spatial reference and merge the output views.
-    srs = getSpatialReference();
-    for (auto const& it : runners)
-    {
-        StageRunnerPtr runner(it);
-        PointViewSet temp = runner->wait();
-
-        // If our stage has a spatial reference, the view takes it on once
-        // the stage has been run.
-        if (!srs.empty())
-            for (PointViewPtr v : temp)
-                v->setSpatialReference(srs);
-        outViews.insert(temp.begin(), temp.end());
-    }
-    done(table);
-    return outViews;
-}
-
-
-// Streamed execution.
-void Stage::execute(StreamPointTable& table)
-{
-    typedef std::list<Stage *> StageList;
-
-    std::list<StageList> lists;
-    StageList stages;
-
-    table.finalize();
-
-    // Walk from the current stage backwards.  As we add each input, copy
-    // the list of stages and push it on a list.  We then pull a list from the
-    // front of list and keep going.  Placing on the back and pulling from the
-    // front insures that the stages will be executed in the order that they
-    // were added.  If we hit stage with no previous stages, we execute
-    // the stage list.
-    // All this often amounts to a bunch of list copying for
-    // no reason, but it's more simple than what we might otherwise do and
-    // this should be a nit in the grand scheme of execution time.
-    //
-    // As an example, if there are four paths from the end stage (writer) to
-    // reader stages, there will be four stage lists and execute(table, stages)
-    // will be called four times.
-    Stage *s = this;
-    stages.push_front(s);
-    while (true)
-    {
-        if (s->m_inputs.empty())
-            execute(table, stages);
-        else
-        {
-            for (auto s2 : s->m_inputs)
-            {
-                StageList newStages(stages);
-                newStages.push_front(s2);
-                lists.push_front(newStages);
-            }
-        }
-        if (lists.empty())
-            break;
-        stages = lists.back();
-        lists.pop_back();
-        s = stages.front();
-    }
-}
-
-
-void Stage::execute(StreamPointTable& table, std::list<Stage *>& stages)
-{
-    std::vector<bool> skips(table.capacity());
-    std::list<Stage *> filters;
-    SpatialReference srs;
-
-    // Separate out the first stage.
-    Stage *reader = stages.front();
-
-    // Build a list of all stages except the first.  We may have a writer in
-    // this list in addition to filters, but we treat them in the same way.
-    auto begin = stages.begin();
-    begin++;
-    std::copy(begin, stages.end(), std::back_inserter(filters));
-
-    for (Stage *s : stages)
-    {
-        s->ready(table);
-        srs = s->getSpatialReference();
-        if (!srs.empty())
-            table.setSpatialReference(srs);
-    }
-
-    // Loop until we're finished.  We handle the number of points up to
-    // the capacity of the StreamPointTable that we've been provided.
-
-    bool finished = false;
-    while (!finished)
-    {
-        // Clear the spatial reference when processing starts.
-        table.clearSpatialReferences();
-        PointId idx = 0;
-        PointRef point(table, idx);
-        point_count_t pointLimit = table.capacity();
-
-        // When we get false back from a reader, we're done, so set
-        // the point limit to the number of points processed in this loop
-        // of the table.
-        for (PointId idx = 0; idx < pointLimit; idx++)
-        {
-            point.setPointId(idx);
-            finished = !reader->processOne(point);
-            if (finished)
-                pointLimit = idx;
-        }
-        srs = reader->getSpatialReference();
-        if (!srs.empty())
-            table.setSpatialReference(srs);
-
-        // When we get a false back from a filter, we're filtering out a
-        // point, so add it to the list of skips so that it doesn't get
-        // processed by subsequent filters.
-        for (Stage *s : filters)
-        {
-            for (PointId idx = 0; idx < pointLimit; idx++)
-            {
-                if (skips[idx])
-                    continue;
-                point.setPointId(idx);
-                if (!s->processOne(point))
-                    skips[idx] = true;
-            }
-            srs = s->getSpatialReference();
-            if (!srs.empty())
-                table.setSpatialReference(srs);
-        }
-
-        // Yes, vector<bool> is terrible.  Can do something better later.
-        for (size_t i = 0; i < skips.size(); ++i)
-            skips[i] = false;
-        table.reset();
-    }
-
-    for (Stage *s : stages)
-        s->done(table);
-}
-
-
-void Stage::l_addArgs(ProgramArgs& args)
-{
-    args.add("log", "Debug output filename", m_logname);
-    readerAddArgs(args);
-}
-
-
-void Stage::setupLog()
-{
-    LogLevel l(LogLevel::Error);
-
-    if (m_log)
-        l = m_log->getLevel();
-
-    if (!m_logname.empty())
-        m_log.reset(new Log(getName(), m_logname));
-    else if (!m_log)
-        m_log.reset(new Log(getName(), "stdlog"));
-    m_log->setLevel(l);
-
-    bool debug(l > LogLevel::Debug);
-    gdal::ErrorHandler::getGlobalErrorHandler().set(m_log, debug);
-}
-
-
-void Stage::l_initialize(PointTableRef table)
-{
-    m_metadata = table.metadata().add(getName());
-    writerInitialize(table);
-}
-
-
-void Stage::addSpatialReferenceArg(ProgramArgs& args)
-{
-    args.add("spatialreference", "Spatial reference to apply to data",
-        m_spatialReference);
-}
-
-const SpatialReference& Stage::getSpatialReference() const
-{
-    return m_spatialReference;
-}
-
-
-void Stage::setSpatialReference(const SpatialReference& spatialRef)
-{
-    setSpatialReference(m_metadata, spatialRef);
-}
-
-
-void Stage::setSpatialReference(MetadataNode& m,
-    const SpatialReference& spatialRef)
-{
-    m_spatialReference = spatialRef;
-
-    auto pred = [](MetadataNode m){ return m.name() == "spatialreference"; };
-
-    MetadataNode spatialNode = m.findChild(pred);
-    if (spatialNode.empty())
-    {
-        m.add(spatialRef.toMetadata());
-        m.add("spatialreference",
-           spatialRef.getWKT(SpatialReference::eHorizontalOnly, false),
-           "SRS of this stage");
-        m.add("comp_spatialreference",
-            spatialRef.getWKT(SpatialReference::eCompoundOK, false),
-            "SRS of this stage");
-    }
-}
-
-} // namespace pdal
-
diff --git a/src/StageFactory.cpp b/src/StageFactory.cpp
deleted file mode 100644
index ad4d1f2..0000000
--- a/src/StageFactory.cpp
+++ /dev/null
@@ -1,305 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/StageFactory.hpp>
-#include <pdal/PluginManager.hpp>
-#include <pdal/util/FileUtils.hpp>
-
-// filters
-#include <approximatecoplanar/ApproximateCoplanarFilter.hpp>
-#include <attribute/AttributeFilter.hpp>
-#include <chipper/ChipperFilter.hpp>
-#include <colorization/ColorizationFilter.hpp>
-#include <crop/CropFilter.hpp>
-#include <decimation/DecimationFilter.hpp>
-#include <divider/DividerFilter.hpp>
-#include <eigenvalues/EigenvaluesFilter.hpp>
-#include <estimaterank/EstimateRankFilter.hpp>
-#include <ferry/FerryFilter.hpp>
-#include <hag/HAGFilter.hpp>
-#include <merge/MergeFilter.hpp>
-#include <mongus/MongusFilter.hpp>
-#include <mortonorder/MortonOrderFilter.hpp>
-#include <normal/NormalFilter.hpp>
-#include <outlier/OutlierFilter.hpp>
-#include <pmf/PMFFilter.hpp>
-#include <range/RangeFilter.hpp>
-#include <reprojection/ReprojectionFilter.hpp>
-#include <sample/SampleFilter.hpp>
-#include <smrf/SMRFilter.hpp>
-#include <sort/SortFilter.hpp>
-#include <splitter/SplitterFilter.hpp>
-#include <stats/StatsFilter.hpp>
-#include <transformation/TransformationFilter.hpp>
-
-// readers
-#include <bpf/BpfReader.hpp>
-#include <faux/FauxReader.hpp>
-#include <gdal/GDALReader.hpp>
-#include <ilvis2/Ilvis2Reader.hpp>
-#include <las/LasReader.hpp>
-#include <optech/OptechReader.hpp>
-#include <buffer/BufferReader.hpp>
-#include <ply/PlyReader.hpp>
-#include <pts/PtsReader.hpp>
-#include <qfit/QfitReader.hpp>
-#include <sbet/SbetReader.hpp>
-#include <terrasolid/TerrasolidReader.hpp>
-#include <text/TextReader.hpp>
-#include <tindex/TIndexReader.hpp>
-
-// writers
-#include <bpf/BpfWriter.hpp>
-#include <las/LasWriter.hpp>
-#include <ply/PlyWriter.hpp>
-#include <sbet/SbetWriter.hpp>
-#include <derivative/DerivativeWriter.hpp>
-#include <text/TextWriter.hpp>
-#include <null/NullWriter.hpp>
-
-#include <sstream>
-#include <string>
-#include <stdio.h> // for funcptr
-
-namespace pdal
-{
-
-StringList StageFactory::extensions(const std::string& driver)
-{
-    static std::map<std::string, StringList> exts =
-    {
-        { "readers.terrasolid", { "bin" } },
-        { "readers.bpf", { "bpf" }  },
-        { "readers.optech", { "csd" } },
-        { "readers.greyhound", { "greyhound" } },
-        { "readers.icebridge", { "icebridge" } },
-        { "readers.las", { "las", "laz" } },
-        { "readers.nitf", { "nitf", "nsf", "ntf" } },
-        { "readers.pcd", { "pcd" } },
-        { "readers.ply", { "ply" } },
-        { "readers.pts", { "pts" } },
-        { "readers.qfit", { "qi" } },
-        { "readers.rxp", { "rxp" } },
-        { "readers.sbet", { "sbet" } },
-        { "readers.sqlite", { "sqlite" } },
-        { "readers.mrsid", { "sid" } },
-        { "readers.tindex", { "tindex" } },
-        { "readers.txt", { "txt" } },
-        { "readers.icebridge", { "h5" } },
-
-        { "writers.bpf", { "bpf" } },
-        { "writers.text", { "csv", "json", "txt", "xyz" } },
-        { "writers.las", { "las", "laz" } },
-        { "writers.matlab", { "mat" } },
-        { "writers.nitf", { "nitf", "nsf", "ntf" } },
-        { "writers.pcd", { "pcd" } },
-        { "writers.pclvisualizer", { "pclvis" } },
-        { "writers.ply", { "ply" } },
-        { "writers.sbet", { "sbet" } },
-        { "writers.derivative", { "derivative" } },
-        { "writers.sqlite", { "sqlite" } },
-    };
-
-    return exts[driver];
-}
-
-std::string StageFactory::inferReaderDriver(const std::string& filename)
-{
-    static std::map<std::string, std::string> drivers =
-    {
-        { "bin", "readers.terrasolid" },
-        { "bpf", "readers.bpf" },
-        { "csd", "readers.optech" },
-        { "greyhound", "readers.greyhound" },
-        { "icebridge", "readers.icebridge" },
-        { "las", "readers.las" },
-        { "laz", "readers.las" },
-        { "nitf", "readers.nitf" },
-        { "nsf", "readers.nitf" },
-        { "ntf", "readers.nitf" },
-        { "pcd", "readers.pcd" },
-        { "ply", "readers.ply" },
-        { "pts", "readers.pts" },
-        { "qi", "readers.qfit" },
-        { "rxp", "readers.rxp" },
-        { "sbet", "readers.sbet" },
-        { "sqlite", "readers.sqlite" },
-        { "sid", "readers.mrsid" },
-        { "tindex", "readers.tindex" },
-        { "txt", "readers.txt" },
-        { "h5", "readers.icebridge" }
-    };
-
-    static const std::string ghPrefix("greyhound://");
-
-    std::string ext;
-    // filename may actually be a greyhound uri + pipelineId
-    if (Utils::iequals(filename.substr(0, ghPrefix.size()), ghPrefix))
-        ext = ".greyhound";      // Make it look like an extension.
-    else
-        ext = FileUtils::extension(filename);
-
-    // Strip off '.' and make lowercase.
-    if (ext.length())
-        ext = Utils::tolower(ext.substr(1, ext.length() - 1));
-
-    return drivers[ext]; // will be "" if not found
-}
-
-
-std::string StageFactory::inferWriterDriver(const std::string& filename)
-{
-    std::string ext;
-
-    if (filename == "STDOUT")
-        ext = ".txt";
-    else
-        ext = Utils::tolower(FileUtils::extension(filename));
-
-    static std::map<std::string, std::string> drivers =
-    {
-        { "bpf", "writers.bpf" },
-        { "csv", "writers.text" },
-        { "json", "writers.text" },
-        { "las", "writers.las" },
-        { "laz", "writers.las" },
-        { "mat", "writers.matlab" },
-        { "ntf", "writers.nitf" },
-        { "pcd", "writers.pcd" },
-        { "pclviz", "writers.pclvisualizer" },
-        { "ply", "writers.ply" },
-        { "sbet", "writers.sbet" },
-        { "derivative", "writers.derivative" },
-        { "sqlite", "writers.sqlite" },
-        { "txt", "writers.text" },
-        { "xyz", "writers.text" },
-        { "", "writers.text" }
-    };
-
-    // Strip off '.' and make lowercase.
-    if (ext.length())
-        ext = Utils::tolower(ext.substr(1, ext.length() - 1));
-
-    return drivers[ext];
-}
-
-
-StageFactory::StageFactory(bool no_plugins)
-{
-    if (!no_plugins)
-    {
-        PluginManager::loadAll(PF_PluginType_Filter | PF_PluginType_Reader |
-            PF_PluginType_Writer);
-    }
-
-    // filters
-    PluginManager::initializePlugin(ApproximateCoplanarFilter_InitPlugin);
-    PluginManager::initializePlugin(AttributeFilter_InitPlugin);
-    PluginManager::initializePlugin(ChipperFilter_InitPlugin);
-    PluginManager::initializePlugin(ColorizationFilter_InitPlugin);
-    PluginManager::initializePlugin(CropFilter_InitPlugin);
-    PluginManager::initializePlugin(DecimationFilter_InitPlugin);
-    PluginManager::initializePlugin(DividerFilter_InitPlugin);
-    PluginManager::initializePlugin(EigenvaluesFilter_InitPlugin);
-    PluginManager::initializePlugin(EstimateRankFilter_InitPlugin);
-    PluginManager::initializePlugin(FerryFilter_InitPlugin);
-    PluginManager::initializePlugin(HAGFilter_InitPlugin);
-    PluginManager::initializePlugin(MergeFilter_InitPlugin);
-    PluginManager::initializePlugin(MongusFilter_InitPlugin);
-    PluginManager::initializePlugin(MortonOrderFilter_InitPlugin);
-    PluginManager::initializePlugin(NormalFilter_InitPlugin);
-    PluginManager::initializePlugin(OutlierFilter_InitPlugin);
-    PluginManager::initializePlugin(PMFFilter_InitPlugin);
-    PluginManager::initializePlugin(RangeFilter_InitPlugin);
-    PluginManager::initializePlugin(ReprojectionFilter_InitPlugin);
-    PluginManager::initializePlugin(SampleFilter_InitPlugin);
-    PluginManager::initializePlugin(SMRFilter_InitPlugin);
-    PluginManager::initializePlugin(SortFilter_InitPlugin);
-    PluginManager::initializePlugin(SplitterFilter_InitPlugin);
-    PluginManager::initializePlugin(StatsFilter_InitPlugin);
-    PluginManager::initializePlugin(TransformationFilter_InitPlugin);
-
-    // readers
-    PluginManager::initializePlugin(BpfReader_InitPlugin);
-    PluginManager::initializePlugin(FauxReader_InitPlugin);
-    PluginManager::initializePlugin(GDALReader_InitPlugin);
-    PluginManager::initializePlugin(Ilvis2Reader_InitPlugin);
-    PluginManager::initializePlugin(LasReader_InitPlugin);
-    PluginManager::initializePlugin(OptechReader_InitPlugin);
-    PluginManager::initializePlugin(PlyReader_InitPlugin);
-    PluginManager::initializePlugin(PtsReader_InitPlugin);
-    PluginManager::initializePlugin(QfitReader_InitPlugin);
-    PluginManager::initializePlugin(SbetReader_InitPlugin);
-    PluginManager::initializePlugin(TerrasolidReader_InitPlugin);
-    PluginManager::initializePlugin(TextReader_InitPlugin);
-    PluginManager::initializePlugin(TIndexReader_InitPlugin);
-
-    // writers
-    PluginManager::initializePlugin(BpfWriter_InitPlugin);
-    PluginManager::initializePlugin(LasWriter_InitPlugin);
-    PluginManager::initializePlugin(PlyWriter_InitPlugin);
-    PluginManager::initializePlugin(SbetWriter_InitPlugin);
-    PluginManager::initializePlugin(DerivativeWriter_InitPlugin);
-    PluginManager::initializePlugin(TextWriter_InitPlugin);
-    PluginManager::initializePlugin(NullWriter_InitPlugin);
-}
-
-
-Stage *StageFactory::createStage(std::string const& stage_name)
-{
-    static_assert(0 < sizeof(Stage), "");
-    Stage *s = static_cast<Stage*>(PluginManager::createObject(stage_name));
-    if (s)
-    {
-        std::lock_guard<std::mutex> lock(m_mutex);
-        m_ownedStages.push_back(std::unique_ptr<Stage>(s));
-    }
-    return s;
-}
-
-
-void StageFactory::destroyStage(Stage *s)
-{
-    std::lock_guard<std::mutex> lock(m_mutex);
-    for (auto it = m_ownedStages.begin(); it != m_ownedStages.end(); ++it)
-    {
-        if (s == it->get())
-        {
-            m_ownedStages.erase(it);
-            break;
-        }
-    }
-}
-
-} // namespace pdal
diff --git a/src/Writer.cpp b/src/Writer.cpp
deleted file mode 100644
index 746aaf4..0000000
--- a/src/Writer.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/Writer.hpp>
-#include <pdal/Stage.hpp>
-
-#include <pdal/util/ProgramArgs.hpp>
-
-#ifdef PDAL_COMPILER_MSVC
-#  pragma warning(disable: 4127)  // conditional expression is constant
-#endif
-
-namespace pdal
-{
-
-void Writer::handleFilenameTemplate()
-{
-    std::string::size_type suffixPos = m_filename.find_last_of('.');
-    m_hashPos = m_filename.find_first_of('#');
-    if (m_hashPos != std::string::npos)
-    {
-        if (m_hashPos > suffixPos)
-        {
-            std::ostringstream oss;
-            oss << getName() << ": Filename template placeholder ('#') is not "
-                "allowed in filename suffix.";
-            throw pdal_error(oss.str());
-        }
-        if (m_filename.find_first_of('#', m_hashPos + 1) !=
-                std::string::npos)
-        {
-            std::ostringstream oss;
-            oss << getName() << ": Filename specification can only contain "
-                "a single '#' template placeholder.";
-            throw pdal_error(oss.str());
-        }
-    }
-}
-
-} // namespace pdal
diff --git a/src/XMLSchema.cpp b/src/XMLSchema.cpp
deleted file mode 100644
index 1ce662f..0000000
--- a/src/XMLSchema.cpp
+++ /dev/null
@@ -1,649 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Howard Butler, hobu.inc at gmail.com *
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/XMLSchema.hpp>
-#include <pdal/PipelineWriter.hpp>
-#include <pdal/PDALUtils.hpp>
-
-#include <sstream>
-#include <iostream>
-#include <iostream>
-#include <list>
-#include <cstdlib>
-#include <map>
-#include <algorithm>
-
-#include <boost/property_tree/xml_parser.hpp>
-
-#include <string.h>
-#include <stdlib.h>
-
-namespace
-{
-
-/**
-void print_element_names(xmlNode * a_node)
-{
-
-    xmlNode *cur_node = NULL;
-
-    for (cur_node = a_node; cur_node; cur_node = cur_node->next)
-    {
-        if (cur_node->type == XML_ELEMENT_NODE)
-        {
-            printf("node type: Element, name: %s\n", cur_node->name);
-        }
-        print_element_names(cur_node->children);
-    }
-}
-**/
-
-} // anonymous namespace
-
-namespace pdal
-{
-
-void OCISchemaStructuredErrorHandler
-(void * /*userData*/, xmlErrorPtr error)
-{
-    std::ostringstream oss;
-
-    oss << "XML error: '" << error->message <<"' ";
-
-    if (error->str1)
-        oss << " extra info1: '" << error->str1 << "' ";
-    if (error->str2)
-        oss << " extra info2: '" << error->str2 << "' ";
-    if (error->str3)
-        oss << " extra info3: '" << error->str3 << "' ";
-    oss << "on line " << error->line;
-
-    if (error->ctxt)
-    {
-        xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) error->ctxt;
-        xmlParserInputPtr input = ctxt->input;
-
-        xmlParserPrintFileContext(input);
-    }
-    std::cerr << oss.str() << std::endl;
-}
-
-void OCISchemaParserStructuredErrorHandler
-(void * /*userData*/, xmlErrorPtr error)
-{
-    std::cerr << "Schema parsing error: '" << error->message << "' " <<
-        "on line " << error->line << std::endl;
-}
-
-void OCISchemaValidationStructuredErrorHandler
-(void * /*userData*/, xmlErrorPtr error)
-{
-    std::cerr << "Schema validation error: '" << error->message << "' " <<
-        "on line " << error->line << std::endl;
-}
-
-void OCISchemaValidityError
-(void * /*ctx*/, const char* message, ...)
-{
-    const int ERROR_MESSAGE_SIZE = 256;
-    char error[ERROR_MESSAGE_SIZE];
-    va_list arg_ptr;
-
-    va_start(arg_ptr, message);
-    vsnprintf(error, ERROR_MESSAGE_SIZE, message, arg_ptr);
-    va_end(arg_ptr);
-
-    std::cerr << "Schema validity error: '" << error << "' " << std::endl;
-}
-
-void OCISchemaValidityDebug
-(void * /*ctx*/, const char* message, ...)
-{
-    const int ERROR_MESSAGE_SIZE = 256;
-    char error[ERROR_MESSAGE_SIZE];
-    va_list arg_ptr;
-
-    va_start(arg_ptr, message);
-    vsnprintf(error, ERROR_MESSAGE_SIZE, message, arg_ptr);
-    va_end(arg_ptr);
-
-    std::cout << "Schema validity debug: '" << error << "' " << "\n";
-}
-
-
-void OCISchemaGenericErrorHandler
-(void * /*ctx*/, const char* message, ...)
-{
-    const int ERROR_MESSAGE_SIZE = 256;
-    char error[ERROR_MESSAGE_SIZE];
-    va_list arg_ptr;
-
-    va_start(arg_ptr, message);
-    vsnprintf(error, ERROR_MESSAGE_SIZE, message, arg_ptr);
-    va_end(arg_ptr);
-
-    std::ostringstream oss;
-
-    std::cerr << "Generic error: '" << error << "'" << std::endl;
-}
-
-XMLSchema::XMLSchema(std::string xml, std::string xsd,
-    Orientation orientation) : m_orientation(orientation)
-{
-    xmlDocPtr doc = init(xml, xsd);
-    if (doc)
-    {
-        load(doc);
-        xmlFreeDoc(doc);
-    }
-}
-
-
-XMLSchema::XMLSchema(const XMLDimList& dims, MetadataNode m,
-    Orientation orientation) : m_orientation(orientation), m_dims(dims),
-    m_metadata(m)
-{}
-
-
-XMLSchema::XMLSchema(const PointLayoutPtr& layout, MetadataNode m,
-    Orientation orientation) : m_orientation(orientation), m_metadata(m)
-{
-    DimTypeList dimTypes = layout->dimTypes();
-    for (DimType& d : dimTypes)
-        m_dims.push_back(XMLDim(d, layout->dimName(d.m_id)));
-}
-
-
-std::string XMLSchema::xml() const
-{
-    xmlBuffer *b = xmlBufferCreate();
-    xmlTextWriterPtr w = xmlNewTextWriterMemory(b, 0);
-
-    xmlTextWriterSetIndent(w, 1);
-    xmlTextWriterStartDocument(w, NULL, "utf-8", NULL);
-    xmlTextWriterStartElementNS(w, (const xmlChar*)"pc",
-        (const xmlChar*)"PointCloudSchema", NULL);
-    xmlTextWriterWriteAttributeNS(w, (const xmlChar*) "xmlns",
-        (const xmlChar*)"pc", NULL,
-        (const xmlChar*)"http://pointcloud.org/schemas/PC/");
-    xmlTextWriterWriteAttributeNS(w, (const xmlChar*)"xmlns",
-        (const xmlChar*)"xsi", NULL,
-        (const xmlChar*)"http://www.w3.org/2001/XMLSchema-instance");
-
-    writeXml(w);
-
-    xmlTextWriterEndElement(w);
-    xmlTextWriterEndDocument(w);
-
-    std::string output((const char *)b->content, b->use);
-    xmlFreeTextWriter(w);
-    xmlBufferFree(b);
-
-    return output;
-}
-
-
-DimTypeList XMLSchema::dimTypes() const
-{
-    DimTypeList dimTypes;
-
-    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
-        dimTypes.push_back(di->m_dimType);
-    return dimTypes;
-}
-
-
-xmlDocPtr XMLSchema::init(const std::string& xml, const std::string& xsd)
-{
-    xmlParserOption parserOption(XML_PARSE_NONET);
-
-    LIBXML_TEST_VERSION
-
-    xmlSetGenericErrorFunc(m_global_context,
-        (xmlGenericErrorFunc)&OCISchemaGenericErrorHandler);
-    xmlSetStructuredErrorFunc(m_global_context,
-        (xmlStructuredErrorFunc)&OCISchemaStructuredErrorHandler);
-
-    xmlDocPtr doc = xmlReadMemory(xml.c_str(), xml.size(), NULL, NULL,
-        parserOption);
-
-    if (xsd.size() && !validate(doc, xsd))
-    {
-        xmlFreeDoc(doc);
-        doc = NULL;
-        std::cerr << "Document did not validate against schema." << std::endl;
-    }
-    return doc;
-}
-
-
-bool XMLSchema::validate(xmlDocPtr doc, const std::string& xsd)
-{
-    xmlParserOption parserOption(XML_PARSE_NONET);
-
-    xmlDocPtr schemaDoc = xmlReadMemory(xsd.c_str(), xsd.size(),
-        NULL, NULL, parserOption);
-    xmlSchemaParserCtxtPtr parserCtxt = xmlSchemaNewDocParserCtxt(schemaDoc);
-    xmlSchemaSetParserStructuredErrors(parserCtxt,
-        &OCISchemaParserStructuredErrorHandler, m_global_context);
-    xmlSchemaPtr schema = xmlSchemaParse(parserCtxt);
-    xmlSchemaValidCtxtPtr validCtxt = xmlSchemaNewValidCtxt(schema);
-    xmlSchemaSetValidErrors(validCtxt, &OCISchemaValidityError,
-        &OCISchemaValidityDebug, m_global_context);
-    bool valid = (xmlSchemaValidateDoc(validCtxt, doc) == 0);
-
-    xmlFreeDoc(schemaDoc);
-    xmlSchemaFreeParserCtxt(parserCtxt);
-    xmlSchemaFree(schema);
-    xmlSchemaFreeValidCtxt(validCtxt);
-
-    return valid;
-}
-
-
-std::string XMLSchema::remapOldNames(const std::string& input)
-{
-    if (Utils::iequals(input, "Unnamed field 512") ||
-            Utils::iequals(input, "Chipper Point ID"))
-        return std::string("Chipper:PointID");
-
-    if (Utils::iequals(input, "Unnamed field 513") ||
-            Utils::iequals(input, "Chipper Block ID"))
-        return std::string("Chipper:BlockID");
-
-    return input;
-}
-
-
-bool XMLSchema::loadMetadata(xmlNode *startNode, MetadataNode& input)
-{
-//     Expect metadata in the following form
-//     We are going to skip the root element because we are
-//     expecting to be given one with our input
-//     <pc:metadata>
-//         <Metadata name="root" type="">
-//             <Metadata name="compression" type="string">lazperf</Metadata>
-//             <Metadata name="version" type="string">1.0</Metadata>
-//         </Metadata>
-//     </pc:metadata>
-
-    xmlNode *node = startNode;
-    for (node = startNode; node; node = node->next)
-    {
-        if (node->type != XML_ELEMENT_NODE)
-            continue;
-        if (std::string((const char*)node->name) == "Metadata")
-        {
-            const char *fieldname =
-                (const char*)xmlGetProp(node, (const xmlChar*)"name");
-            const char *etype =
-                (const char*)xmlGetProp(node, (const xmlChar*)"type");
-            const char *description =
-                (const char*)xmlGetProp(node, (const xmlChar*) "description");
-            const char *text = (const char*)xmlNodeGetContent(node);
-
-            if (!Utils::iequals(fieldname, "root"))
-            {
-                if (!fieldname)
-                {
-                    std::cerr << "Unable to read metadata for node '" <<
-                        (const char*)node->name << "' no \"name\" was given";
-                    return false;
-                }
-                input.add(fieldname, text ? text : "",
-                    description ? description : "");
-            }
-        }
-        loadMetadata(node->children, input);
-    }
-    return true;
-}
-
-
-bool XMLSchema::load(xmlDocPtr doc)
-{
-    xmlNode* root = xmlDocGetRootElement(doc);
-    // print_element_names(root);
-
-    if (!Utils::iequals((const char*)root->name, "PointCloudSchema"))
-    {
-        std::cerr << "First node of document was not named 'PointCloudSchema'";
-        return false;
-    }
-
-    const unsigned SENTINEL_POS = 100000;
-    unsigned missingPos = SENTINEL_POS + 1;
-
-    xmlNode* dimension = root->children;
-    pdal::Metadata metadata;
-    for (xmlNode *dimension = root->children; dimension;
-        dimension = dimension->next)
-    {
-        // Read off orientation setting
-        if (std::string((const char*)dimension->name) == "orientation")
-        {
-            xmlChar* n = xmlNodeListGetString(doc, dimension->children, 1);
-            if (!n)
-            {
-                std::cerr << "Unable to fetch orientation.\n";
-                return false;
-            }
-            std::string orientation = std::string((const char*)n);
-            xmlFree(n);
-
-            if (Utils::iequals(orientation, "dimension"))
-                m_orientation = Orientation::DimensionMajor;
-            else
-                m_orientation = Orientation::PointMajor;
-            continue;
-        }
-
-        if (std::string((const char*)dimension->name) == "metadata")
-        {
-            m_metadata = MetadataNode("root");
-            if (!loadMetadata(dimension, m_metadata))
-                return false;
-            continue;
-        }
-
-        if (dimension->type != XML_ELEMENT_NODE ||
-            !Utils::iequals((const char*)dimension->name, "dimension"))
-            continue;
-
-        XMLDim dim;
-        dim.m_position = SENTINEL_POS;
-        for (xmlNode *properties = dimension->children; properties;
-            properties = properties->next)
-        {
-            if (properties->type != XML_ELEMENT_NODE)
-                continue;
-
-            std::string propName = (const char *)properties->name;
-            propName = Utils::tolower(propName);
-            if (propName == "name")
-            {
-                xmlChar *n = xmlNodeListGetString(doc, properties->children, 1);
-                if (!n)
-                {
-                    std::cerr << "Unable to fetch name from XML node.";
-                    return false;
-                }
-                dim.m_name = remapOldNames(std::string((const char*)n));
-                xmlFree(n);
-            }
-            if (propName == "description")
-            {
-                xmlChar* n = xmlNodeListGetString(doc, properties->children, 1);
-                if (!n)
-                {
-                    std::cerr << "Unable to fetch description.\n";
-                    return false;
-                }
-                dim.m_description = std::string((const char*)n);
-                xmlFree(n);
-            }
-            if (propName == "interpretation")
-            {
-                xmlChar* n = xmlNodeListGetString(doc, properties->children, 1);
-                if (!n)
-                {
-                    std::cerr << "Unable to fetch interpretation.\n";
-                    return false;
-                }
-                dim.m_dimType.m_type = Dimension::type((const char*)n);
-                xmlFree(n);
-            }
-            if (propName == "minimum")
-            {
-                xmlChar* n = xmlGetProp(properties, (const xmlChar*) "value");
-                if (!n)
-                {
-                    return false;
-                    std::cerr << "Unable to fetch minimum value.\n";
-                }
-                dim.m_min = std::atof((const char*)n);
-                xmlFree(n);
-            }
-            if (propName == "maximum")
-            {
-                xmlChar* n = xmlGetProp(properties, (const xmlChar*) "value");
-                if (!n)
-                {
-                    std::cerr << "Unable to fetch maximum value.\n";
-                    return false;
-                }
-                dim.m_max = std::atof((const char*)n);
-                xmlFree(n);
-            }
-            if (propName == "position")
-            {
-                xmlChar* n = xmlNodeListGetString(doc, properties->children, 1);
-                if (!n)
-                {
-                    std::cerr << "Unable to fetch position value.\n";
-                    return false;
-                }
-                dim.m_position = std::atoi((const char*)n);
-                xmlFree(n);
-            }
-            if (propName == "offset")
-            {
-                xmlChar* n = xmlNodeListGetString(doc, properties->children, 1);
-                if (!n)
-                {
-                    std::cerr << "Unable to fetch offset value!";
-                    return false;
-                }
-                dim.m_dimType.m_xform.m_offset.set((const char*)n);
-                xmlFree(n);
-            }
-            if (propName == "scale")
-            {
-                xmlChar* n = xmlNodeListGetString(doc, properties->children, 1);
-                if (!n)
-                {
-                    std::cerr << "Unable to fetch scale value!";
-                    return false;
-                }
-                dim.m_dimType.m_xform.m_scale.set((const char*)n);
-                xmlFree(n);
-            }
-        }
-        // If we don't have a position, set it to some value larger than all
-        // previous values.
-        if (dim.m_position == SENTINEL_POS)
-            dim.m_position = missingPos++;
-        m_dims.push_back(dim);
-    }
-    std::sort(m_dims.begin(), m_dims.end());
-
-    // Renumber dimension positions to be 1..N
-    for (unsigned pos = 0; pos < m_dims.size(); pos++)
-        m_dims[pos].m_position = pos + 1;
-
-    return true;
-}
-
-
-XMLDim& XMLSchema::xmlDim(Dimension::Id id)
-{
-    static XMLDim nullDim;
-
-    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
-        if (di->m_dimType.m_id == id)
-            return *di;
-    return nullDim;
-}
-
-
-const XMLDim& XMLSchema::xmlDim(Dimension::Id id) const
-{
-    static XMLDim nullDim;
-
-    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
-        if (di->m_dimType.m_id == id)
-            return *di;
-    return nullDim;
-}
-
-
-XMLDim& XMLSchema::xmlDim(const std::string& name)
-{
-    static XMLDim nullDim;
-
-    for (auto di = m_dims.begin(); di != m_dims.end(); ++di)
-        if (di->m_name == name)
-            return *di;
-    return nullDim;
-}
-
-
-namespace
-{
-
-pdalboost::property_tree::ptree getMetadataEntry(const MetadataNode& input)
-{
-    pdalboost::property_tree::ptree entry;
-
-    entry.put_value(input.value());
-    entry.put("<xmlattr>.name", input.name());
-    entry.put("<xmlattr>.type", input.type());
-
-    for (auto& m : input.children())
-        entry.add_child("Metadata", getMetadataEntry(m));
-    return entry;
-}
-
-} // anonymous namespace
-
-
-void XMLSchema::writeXml(xmlTextWriterPtr w) const
-{
-    int pos = 0;
-    for (auto di = m_dims.begin(); di != m_dims.end(); ++di, ++pos)
-    {
-        xmlTextWriterStartElementNS(w, (const xmlChar*)"pc",
-            (const xmlChar*)"dimension", NULL);
-
-        std::ostringstream position;
-        position << (pos + 1);
-        xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
-            (const xmlChar*)"position", NULL,
-            (const xmlChar*)position.str().c_str());
-
-        std::ostringstream size;
-        size << Dimension::size(di->m_dimType.m_type);
-        xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
-            (const xmlChar*)"size", NULL, (const xmlChar*)size.str().c_str());
-
-        std::string description = Dimension::description(di->m_dimType.m_id);
-        if (description.size())
-            xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
-                (const xmlChar*)"description", NULL,
-                (const xmlChar*)description.c_str());
-
-        XForm xform = di->m_dimType.m_xform;
-        if (xform.nonstandard())
-        {
-            std::ostringstream out;
-            out.precision(15);
-
-            out << xform.m_scale.m_val;
-            std::string scale = out.str();
-
-            out.str(std::string());
-            out << xform.m_offset.m_val;
-            std::string offset = out.str();
-
-            out << xform.m_scale.m_val;
-            xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
-                (const xmlChar *)"scale", NULL,
-                (const xmlChar *)scale.data());
-            xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
-                (const xmlChar *)"offset", NULL,
-                (const xmlChar *)offset.data());
-        }
-
-        std::string name = di->m_name;
-        if (name.size())
-            xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
-                (const xmlChar*)"name", NULL, (const xmlChar*)name.c_str());
-
-        xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
-            (const xmlChar*)"interpretation", NULL,
-            (const xmlChar*)
-                Dimension::interpretationName(di->m_dimType.m_type).c_str());
-
-        xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
-            (const xmlChar*)"active", NULL, (const xmlChar*)"true");
-
-        xmlTextWriterEndElement(w);
-        xmlTextWriterFlush(w);
-    }
-    std::ostringstream orientation;
-    if (m_orientation == Orientation::PointMajor)
-        orientation << "point";
-    if (m_orientation == Orientation::DimensionMajor)
-        orientation << "dimension";
-    if (!m_metadata.empty())
-    {
-        xmlTextWriterStartElementNS(w, (const xmlChar*) "pc",
-            (const xmlChar*) "metadata", NULL);
-
-        pdalboost::property_tree::ptree output;
-
-        for (auto m : m_metadata.children())
-            output.add_child("Metadata", getMetadataEntry(m));
-        std::ostringstream oss;
-        pdalboost::property_tree::xml_parser::write_xml(oss, output);
-        std::string xml = oss.str();
-
-        // wipe off write_xml's xml declaration
-        xml = Utils::replaceAll(xml,
-            "<?xml version=\"1.0\" encoding=\"utf-8\"?>", "");
-        xmlTextWriterWriteRawLen(w, (const xmlChar*) xml.c_str(), xml.size());
-        xmlTextWriterEndElement(w);
-    }
-    xmlTextWriterWriteElementNS(w, (const xmlChar*) "pc",
-        (const xmlChar*)"orientation", NULL,
-        (const xmlChar*)orientation.str().c_str());
-
-    xmlTextWriterWriteElementNS(w, (const xmlChar*)"pc",
-        (const xmlChar*)"version", NULL,
-        (const xmlChar*)PDAL_XML_SCHEMA_VERSION);
-
-    xmlTextWriterEndElement(w);
-    xmlTextWriterFlush(w);
-}
-
-} // namespace pdal
diff --git a/src/gitsha.cpp b/src/gitsha.cpp
deleted file mode 100644
index 9359b1b..0000000
--- a/src/gitsha.cpp
+++ /dev/null
@@ -1,3 +0,0 @@
-#include <pdal/gitsha.h>
-#define GIT_SHA1 "efca2f77d8cec2025534d12f2b269711fe375471"
-const char g_GIT_SHA1[] = GIT_SHA1;
diff --git a/src/pdal_config.cpp b/src/pdal_config.cpp
deleted file mode 100644
index 2fdee4f..0000000
--- a/src/pdal_config.cpp
+++ /dev/null
@@ -1,194 +0,0 @@
-/******************************************************************************
- * $Id$
- *
- * Project:  libLAS - http://liblas.org - A BSD library for LAS format data.
- * Purpose:  LAS version related functions.
- * Author:   Mateusz Loskot, mateusz at loskot.net
- *           Frank Warmerdam, warmerdam at pobox.com
- *
- ******************************************************************************
- * Copyright (c) 2008, Mateusz Loskot
- * Copyright (c) 2010, Frank Warmerdam
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of the Martin Isenburg or Iowa Department
- *       of Natural Resources nor the names of its contributors may be
- *       used to endorse or promote products derived from this software
- *       without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#include <pdal/pdal_config.hpp>
-
-#include <sstream>
-#include <iomanip>
-
-#include <pdal/pdal_defines.h>
-#include <pdal/gitsha.h>
-
-#ifdef PDAL_HAVE_LIBGEOTIFF
-#include <geotiff.h>
-#endif
-
-#ifdef PDAL_COMPILER_CLANG
-#  pragma clang diagnostic push
-#  pragma clang diagnostic ignored "-Wfloat-equal"
-#endif
-#include <gdal.h>
-#ifdef PDAL_COMPILER_CLANG
-#  pragma clang diagnostic pop
-#endif
-
-#ifdef PDAL_HAVE_LASZIP
-#include <laszip/laszip.hpp>
-#endif
-
-#include <geos_c.h>
-
-#ifdef PDAL_HAVE_LIBXML2
-#include <libxml/xmlversion.h>
-#endif
-
-#include <pdal/util/Utils.hpp>
-
-namespace pdal
-{
-
-/// Check if GeoTIFF support has been built in to PDAL
-bool IsLibGeoTIFFEnabled()
-{
-#ifdef PDAL_HAVE_LIBGEOTIFF
-    return true;
-#else
-    return false;
-#endif
-}
-
-/// Check if LasZip compression support has been built in to PDAL
-bool IsLasZipEnabled()
-{
-#ifdef PDAL_HAVE_LASZIP
-    return true;
-#else
-    return false;
-#endif
-}
-
-int GetVersionMajor()
-{
-    return PDAL_VERSION_MAJOR;
-}
-
-int GetVersionMinor()
-{
-    return PDAL_VERSION_MINOR;
-}
-
-int GetVersionPatch()
-{
-    return PDAL_VERSION_PATCH;
-}
-
-std::string GetVersionString()
-{
-    return std::string(PDAL_VERSION_STRING);
-}
-
-int GetVersionInteger()
-{
-    return PDAL_VERSION_INTEGER;
-}
-
-std::string GetSHA1()
-{
-	return g_GIT_SHA1;
-}
-
-
-/// Tell the user a bit about PDAL's compilation
-std::string GetFullVersionString()
-{
-    std::ostringstream os;
-
-    std::string sha = GetSHA1();
-    if (!Utils::iequals(sha, "Release"))
-        sha = sha.substr(0,6);
-
-    os << PDAL_VERSION_STRING << " (git-version: " << sha << ")";
-
-    return os.str();
-}
-
-
-std::string getPDALDebugInformation()
-{
-    Utils::screenWidth();
-    std::string headline(Utils::screenWidth(), '-');
-
-    std::ostringstream os;
-
-    os << headline << std::endl;
-    os << "PDAL debug information" << std::endl ;
-    os << headline << std::endl << std::endl;
-
-    os << "Version information" << std::endl;
-    os << headline << std::endl;
-    os << "(" << pdal::GetFullVersionString() << ")" << std::endl;
-    os << std::endl;
-
-    os << "Debug build status" << std::endl;
-    os << headline << std::endl;
-    os << PDAL_BUILD_TYPE << std::endl << std::endl;
-
-    os << "Enabled libraries" << std::endl;
-    os << headline << std::endl << std::endl;
-
-    os << "GEOS (" << GEOS_VERSION << ") - " <<
-        "http://trac.osgeo.org/geos" << std::endl;
-
-    os << "GDAL (" << GDALVersionInfo("RELEASE_NAME") << ") - " <<
-        "http://www.gdal.org" << std::endl;
-
-#ifdef PDAL_HAVE_LASZIP
-    os << "LASzip (" << LASZIP_VERSION_MAJOR << "." << LASZIP_VERSION_MINOR <<
-        "." << LASZIP_VERSION_REVISION << ") - " <<
-        "http://laszip.org" << std::endl;
-#endif
-
-#ifdef PDAL_HAVE_LIBXML2
-    os << "libxml (" << LIBXML_DOTTED_VERSION << ") - " <<
-              "http://www.xmlsoft.org/" << std::endl;
-#endif
-
-#ifdef PDAL_HAVE_LIBGEOTIFF
-    os << "libgeotiff (" << LIBGEOTIFF_VERSION << ") - " <<
-        "http://trac.osgeo.org/geotiff" << std::endl;
-#endif
-
-    return os.str();
-}
-
-} // namespace pdal
diff --git a/src/plang/CMakeLists.txt b/src/plang/CMakeLists.txt
deleted file mode 100644
index fc8a46c..0000000
--- a/src/plang/CMakeLists.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-
-set(plang_srcs
-    Array.cpp
-    BufferedInvocation.cpp
-    Invocation.cpp
-    Environment.cpp
-    Redirector.cpp
-    Script.cpp
-)
-
-set(plang_headers
-    "${PDAL_HEADERS_DIR}/plang/Array.hpp"
-    "${PDAL_HEADERS_DIR}/plang/BufferedInvocation.hpp"
-    "${PDAL_HEADERS_DIR}/plang/Invocation.hpp"
-    "${PDAL_HEADERS_DIR}/plang/Environment.hpp"
-    "${PDAL_HEADERS_DIR}/plang/Redirector.hpp"
-    "${PDAL_HEADERS_DIR}/plang/Script.hpp"
-)
-
-include(${PDAL_CMAKE_DIR}/python.cmake)
-include_directories(SYSTEM ${PYTHON_INCLUDE_DIR})
-
-PDAL_ADD_LIBRARY(${PDAL_PLANG_LIB_NAME} ${plang_srcs} )
-set_target_properties(${PDAL_PLANG_LIB_NAME} PROPERTIES
-    VERSION "${PDAL_BUILD_VERSION}"
-    SOVERSION "${PDAL_API_VERSION}"
-    CLEAN_DIRECT_OUTPUT 1)
-
-target_link_libraries(${PDAL_PLANG_LIB_NAME}
-    ${PDAL_BASE_LIB_NAME}
-    ${PDAL_UTIL_LIB_NAME}
-    ${PYTHON_LIBRARY})
-install(TARGETS ${PLANG_LIB_NAME}
-    RUNTIME DESTINATION ${PDAL_BIN_INSTALL_DIR}
-    LIBRARY DESTINATION ${PDAL_LIB_INSTALL_DIR}
-    ARCHIVE DESTINATION ${PDAL_LIB_INSTALL_DIR})
-
-
diff --git a/src/plang/Environment.cpp b/src/plang/Environment.cpp
deleted file mode 100644
index 9b8ece6..0000000
--- a/src/plang/Environment.cpp
+++ /dev/null
@@ -1,339 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#ifndef _WIN32
-#include <dlfcn.h>
-#endif
-
-#include <pdal/plang/Environment.hpp>
-#include <pdal/plang/Redirector.hpp>
-#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
-#define PY_ARRAY_UNIQUE_SYMBOL PDAL_ARRAY_API
-#include <numpy/arrayobject.h>
-
-#include <sstream>
-#include <mutex>
-
-#ifdef PDAL_COMPILER_MSVC
-#  pragma warning(disable: 4127)  // conditional expression is constant
-#endif
-
-#include <Python.h>
-#include <pystate.h>
-#undef toupper
-#undef tolower
-#undef isspace
-
-// See ticket #1010.  This function runs when libplang is loaded.  It makes
-// sure python symbols can be found by extention module .so's since on some
-// platforms (notably Ubuntu), they aren't linked with libpython even though
-// they depend on it.  If a platform doesn't support
-// __attribute__ ((constructor)) this does nothing.  We'll have to deal with
-// those as they come up.
-#ifndef _WIN32
-__attribute__ ((constructor))
-static void loadPython()
-{
-    ::dlopen(PDAL_PYTHON_LIBRARY, RTLD_LAZY | RTLD_GLOBAL);
-}
-#endif
-
-// http://www.linuxjournal.com/article/3641
-// http://www.codeproject.com/Articles/11805/Embedding-Python-in-C-C-Part-I
-// http://stackoverflow.com/questions/6596016/python-threads-in-c
-
-namespace pdal
-{
-namespace plang
-{
-
-static Environment* g_environment=0;
-
-EnvironmentPtr Environment::get()
-{
-    static std::once_flag flag;
-
-    auto init = []()
-    {
-        g_environment = new Environment();
-    };
-
-    std::call_once(flag, init);
-
-    return g_environment;
-}
-
-
-Environment::Environment()
-{
-    // This awfulness is due to the import_array MACRO that returns a value
-    // in some cases and returns nothing at other times.  Defining
-    // NUMPY_IMPORT_ARRAY_RETVAL to nothing makes it so we don't ever return
-    // a value.  The function needs to be stuck in a function to deal with
-    // the return.
-    auto initNumpy = []()
-    {
-#undef NUMPY_IMPORT_ARRAY_RETVAL
-#define NUMPY_IMPORT_ARRAY_RETVAL
-        import_array();
-    };
-
-    PyImport_AppendInittab(const_cast<char*>("redirector"), redirector_init);
-
-    Py_Initialize();
-    initNumpy();
-    PyImport_ImportModule("redirector");
-}
-
-
-Environment::~Environment()
-{
-    Py_Finalize();
-}
-
-
-void Environment::set_stdout(std::ostream* ostr)
-{
-    m_redirector.set_stdout(ostr);
-}
-
-
-void Environment::reset_stdout()
-{
-    m_redirector.reset_stdout();
-}
-
-
-std::string getTraceback()
-{
-    // get exception info
-    PyObject *type, *value, *traceback;
-    PyErr_Fetch(&type, &value, &traceback);
-    PyErr_NormalizeException(&type, &value, &traceback);
-
-    std::ostringstream mssg;
-    if (traceback)
-    {
-        PyObject* tracebackModule;
-        PyObject* tracebackDictionary;
-        PyObject* tracebackFunction;
-
-        tracebackModule = PyImport_ImportModule("traceback");
-        if (!tracebackModule)
-            throw pdal::pdal_error("Unable to load traceback module.");
-
-        tracebackDictionary = PyModule_GetDict(tracebackModule);
-        if (!tracebackDictionary)
-            throw pdal::pdal_error("Unable to load traceback dictionary.");
-        tracebackFunction =
-            PyDict_GetItemString(tracebackDictionary, "format_exception");
-        if (!tracebackFunction)
-            throw pdal::pdal_error("Unable to find traceback function.");
-
-        if (!PyCallable_Check(tracebackFunction))
-            throw pdal::pdal_error("Invalid traceback function.");
-
-        // create an argument for "format exception"
-        PyObject* args = PyTuple_New(3);
-        PyTuple_SetItem(args, 0, type);
-        PyTuple_SetItem(args, 1, value);
-        PyTuple_SetItem(args, 2, traceback);
-
-        // get a list of string describing what went wrong
-        PyObject* output = PyObject_CallObject(tracebackFunction, args);
-
-        // print error message
-        Py_ssize_t n = PyList_Size(output);
-
-        for (Py_ssize_t i = 0; i < n; i++)
-        {
-            PyObject* l = PyList_GetItem(output, i);
-            if (!l)
-                throw pdal::pdal_error("unable to get list item in getTraceback");
-            PyObject* r = PyObject_Repr(l);
-            if (!r)
-                throw pdal::pdal_error("unable to get repr in getTraceback");
-#if PY_MAJOR_VERSION >= 3
-            Py_ssize_t size;
-            char *d = PyUnicode_AsUTF8AndSize(r, &size);
-#else
-            char *d = PyString_AsString(r);
-#endif
-            mssg << d;
-        }
-
-        // clean up
-        Py_XDECREF(args);
-        Py_XDECREF(output);
-    }
-    else if (value != NULL)
-    {
-        PyObject* r = PyObject_Repr(value);
-        if (!r)
-            throw pdal::pdal_error("couldn't make string representation of traceback value");
-#if PY_MAJOR_VERSION >= 3
-        Py_ssize_t size;
-        char *d = PyUnicode_AsUTF8AndSize(r, &size);
-#else
-        char *d = PyString_AsString(r);
-#endif
-        mssg << d;
-    }
-    else
-        mssg << "unknown error that we are unable to get a traceback for."
-            "Was it already printed/taken?";
-
-    Py_XDECREF(value);
-    Py_XDECREF(type);
-    Py_XDECREF(traceback);
-
-    return mssg.str();
-}
-
-PyObject *fromMetadata(MetadataNode m)
-{
-    std::string name = m.name();
-    std::string value = m.value();
-    std::string type = m.type();
-    std::string description = m.description();
-
-    MetadataNodeList children = m.children();
-    PyObject *submeta = NULL;
-    if (children.size())
-    {
-        submeta = PyList_New(0);
-        for (MetadataNode& child : children)
-            PyList_Append(submeta, fromMetadata(child));
-    }
-    PyObject *data = PyTuple_New(5);
-    PyTuple_SetItem(data, 0, PyUnicode_FromString(name.data()));
-    PyTuple_SetItem(data, 1, PyUnicode_FromString(value.data()));
-    PyTuple_SetItem(data, 2, PyUnicode_FromString(type.data()));
-    PyTuple_SetItem(data, 3, PyUnicode_FromString(description.data()));
-    PyTuple_SetItem(data, 4, submeta);
-
-    return data;
-}
-
-std::string readPythonString(PyObject* list, Py_ssize_t index)
-{
-    std::stringstream ss;
-
-    PyObject* o = PyTuple_GetItem(list, index);
-    if (!o)
-    {
-        std::stringstream oss;
-        oss << "Unable to get list item number " << index << " for list of length " << PyTuple_Size(list);
-        throw pdal_error(oss.str());
-    }
-    PyObject* r = PyObject_Repr(o);
-    if (!r)
-        throw pdal::pdal_error("unable to get repr in readPythonString");
-#if PY_MAJOR_VERSION >= 3
-    Py_ssize_t size;
-    char *d = PyUnicode_AsUTF8AndSize(r, &size);
-#else
-    char *d = PyString_AsString(r);
-#endif
-    ss << d;
-
-    return ss.str();
-}
-void addMetadata(PyObject *list, MetadataNode m)
-{
-
-    if (!PyList_Check(list))
-        return;
-
-    for (Py_ssize_t i = 0; i < PyList_Size(list); ++i)
-    {
-        PyObject *tuple = PyList_GetItem(list, i);
-        if (!PyTuple_Check(tuple) || PyTuple_Size(tuple) != 5)
-            continue;
-
-        std::string name = readPythonString(tuple, 0);
-        std::string value = readPythonString(tuple, 1);
-
-        std::string type = readPythonString(tuple, 2);
-        if (type.empty())
-            type = Metadata::inferType(value);
-
-        std::string description = readPythonString(tuple, 3);
-
-        PyObject *submeta = PyTuple_GetItem(tuple, 4);
-        MetadataNode child =  m.addWithType(name, value, type, description);
-        if (submeta)
-            addMetadata(submeta, child);
-    }
-}
-
-int Environment::getPythonDataType(Dimension::Type t)
-{
-    using namespace Dimension;
-
-    switch (t)
-    {
-    case Type::Float:
-        return NPY_FLOAT;
-    case Type::Double:
-        return NPY_DOUBLE;
-    case Type::Signed8:
-        return NPY_BYTE;
-    case Type::Signed16:
-        return NPY_SHORT;
-    case Type::Signed32:
-        return NPY_INT;
-    case Type::Signed64:
-        return NPY_LONGLONG;
-    case Type::Unsigned8:
-        return NPY_UBYTE;
-    case Type::Unsigned16:
-        return NPY_USHORT;
-    case Type::Unsigned32:
-        return NPY_UINT;
-    case Type::Unsigned64:
-        return NPY_ULONGLONG;
-    default:
-        return -1;
-    }
-    assert(0);
-
-    return -1;
-}
-
-
-
-} // namespace plang
-} // namespace pdal
-
diff --git a/src/plang/Invocation.cpp b/src/plang/Invocation.cpp
deleted file mode 100644
index 61620ad..0000000
--- a/src/plang/Invocation.cpp
+++ /dev/null
@@ -1,290 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/plang/Invocation.hpp>
-#include <pdal/plang/Environment.hpp>
-
-#ifdef PDAL_COMPILER_MSVC
-#  pragma warning(disable: 4127) // conditional expression is constant
-#endif
-
-#include <Python.h>
-#undef toupper
-#undef tolower
-#undef isspace
-
-#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
-
-#define NO_IMPORT_ARRAY
-#define PY_ARRAY_UNIQUE_SYMBOL PDAL_ARRAY_API
-#include <numpy/arrayobject.h>
-
-namespace
-{
-
-int argCount(PyObject *function)
-{
-    PyObject *module = PyImport_ImportModule("inspect");
-    if (!module)
-        return false;
-    PyObject *dictionary = PyModule_GetDict(module);
-    PyObject *getargFunc = PyDict_GetItemString(dictionary, "getargspec");
-    PyObject *inArgs = PyTuple_New(1);
-    PyTuple_SetItem(inArgs, 0, function);
-    PyObject *outArgs = PyObject_CallObject(getargFunc, inArgs);
-    PyObject *arglist = PyTuple_GetItem(outArgs, 0);
-    return PyList_Size(arglist);
-}
-
-}
-
-namespace pdal
-{
-namespace plang
-{
-
-Invocation::Invocation(const Script& script) :
-    m_metaIn(NULL)
-    , m_metaOut(NULL)
-    , m_script(script)
-    , m_bytecode(NULL)
-    , m_module(NULL)
-    , m_dictionary(NULL)
-    , m_function(NULL)
-    , m_varsIn(NULL)
-    , m_varsOut(NULL)
-    , m_scriptArgs(NULL)
-    , m_scriptResult(NULL)
-{
-    plang::Environment::get();
-    resetArguments();
-}
-
-
-Invocation::~Invocation()
-{
-    cleanup();
-}
-
-
-void Invocation::compile()
-{
-    m_bytecode = Py_CompileString(m_script.source(), m_script.module(),
-        Py_file_input);
-    if (!m_bytecode)
-        throw pdal::pdal_error(getTraceback());
-
-    Py_INCREF(m_bytecode);
-
-    m_module = PyImport_ExecCodeModule(const_cast<char*>(m_script.module()),
-        m_bytecode);
-    if (!m_module)
-        throw pdal::pdal_error(getTraceback());
-
-    m_dictionary = PyModule_GetDict(m_module);
-    m_function = PyDict_GetItemString(m_dictionary, m_script.function());
-    if (!m_function)
-    {
-        std::ostringstream oss;
-        oss << "unable to find target function '" << m_script.function() <<
-            "' in module.";
-        throw pdal::pdal_error(oss.str());
-    }
-    if (!PyCallable_Check(m_function))
-        throw pdal::pdal_error(getTraceback());
-}
-
-
-void Invocation::cleanup()
-{
-    Py_XDECREF(m_varsIn);
-    Py_XDECREF(m_varsOut);
-    Py_XDECREF(m_scriptResult);
-    Py_XDECREF(m_scriptArgs); // also decrements script and vars
-    for (size_t i = 0; i < m_pyInputArrays.size(); i++)
-        Py_XDECREF(m_pyInputArrays[i]);
-    m_pyInputArrays.clear();
-    Py_XDECREF(m_bytecode);
-    Py_XDECREF(m_metaIn);
-    Py_XDECREF(m_metaOut);
-}
-
-
-void Invocation::resetArguments()
-{
-    cleanup();
-    m_varsIn = PyDict_New();
-    m_varsOut = PyDict_New();
-    m_metaIn = PyList_New(0);
-    m_metaOut = PyList_New(0);
-}
-
-
-void Invocation::insertArgument(std::string const& name, uint8_t* data,
-    Dimension::Type t, point_count_t count)
-{
-    npy_intp mydims = count;
-    int nd = 1;
-    npy_intp* dims = &mydims;
-    npy_intp stride = Dimension::size(t);
-    npy_intp* strides = &stride;
-
-#ifdef NPY_ARRAY_CARRAY
-    int flags = NPY_ARRAY_CARRAY;
-#else
-    int flags = NPY_CARRAY;
-#endif
-
-    const int pyDataType = plang::Environment::getPythonDataType(t);
-
-    PyObject* pyArray = PyArray_New(&PyArray_Type, nd, dims, pyDataType,
-        strides, data, 0, flags, NULL);
-    m_pyInputArrays.push_back(pyArray);
-    PyDict_SetItemString(m_varsIn, name.c_str(), pyArray);
-}
-
-
-void *Invocation::extractResult(std::string const& name,
-    Dimension::Type t)
-{
-    PyObject* xarr = PyDict_GetItemString(m_varsOut, name.c_str());
-    if (!xarr)
-        throw pdal::pdal_error("plang output variable '" + name + "' not found.");
-    if (!PyArray_Check(xarr))
-        throw pdal::pdal_error("Plang output variable  '" + name +
-            "' is not a numpy array");
-
-    PyArrayObject* arr = (PyArrayObject*)xarr;
-
-    npy_intp one = 0;
-    const int pyDataType = pdal::plang::Environment::getPythonDataType(t);
-    PyArray_Descr *dtype = PyArray_DESCR(arr);
-
-    if (static_cast<uint32_t>(dtype->elsize) != Dimension::size(t))
-    {
-        std::ostringstream oss;
-        oss << "dtype of array has size " << dtype->elsize
-            << " but PDAL dimension '" << name << "' has byte size of "
-            << Dimension::size(t) << " bytes.";
-        throw pdal::pdal_error(oss.str());
-    }
-
-    using namespace Dimension;
-    BaseType b = Dimension::base(t);
-    if (dtype->kind == 'i' && b != BaseType::Signed)
-    {
-        std::ostringstream oss;
-        oss << "dtype of array has a signed integer type but the " <<
-            "dimension data type of '" << name <<
-            "' is not pdal::Signed.";
-        throw pdal::pdal_error(oss.str());
-    }
-
-    if (dtype->kind == 'u' && b != BaseType::Unsigned)
-    {
-        std::ostringstream oss;
-        oss << "dtype of array has a unsigned integer type but the " <<
-            "dimension data type of '" << name <<
-            "' is not pdal::Unsigned.";
-        throw pdal::pdal_error(oss.str());
-    }
-
-    if (dtype->kind == 'f' && b != BaseType::Floating)
-    {
-        std::ostringstream oss;
-        oss << "dtype of array has a float type but the " <<
-            "dimension data type of '" << name << "' is not pdal::Floating.";
-        throw pdal::pdal_error(oss.str());
-    }
-    return PyArray_GetPtr(arr, &one);
-}
-
-
-void Invocation::getOutputNames(std::vector<std::string>& names)
-{
-    names.clear();
-
-    PyObject *key, *value;
-    Py_ssize_t pos = 0;
-
-    while (PyDict_Next(m_varsOut, &pos, &key, &value))
-    {
-        const char* p(0);
-#if PY_MAJOR_VERSION >= 3
-        p = PyBytes_AsString(PyUnicode_AsUTF8String(key));
-#else
-        p = PyString_AsString(key);
-#endif
-        if (p)
-            names.push_back(p);
-    }
-}
-
-
-bool Invocation::hasOutputVariable(const std::string& name) const
-{
-    return (PyDict_GetItemString(m_varsOut, name.c_str()) != NULL);
-}
-
-
-bool Invocation::execute()
-{
-    if (!m_bytecode)
-        throw pdal::pdal_error("No code has been compiled");
-
-    Py_INCREF(m_varsIn);
-    Py_INCREF(m_varsOut);
-    Py_ssize_t numArgs = argCount(m_function);
-    m_scriptArgs = PyTuple_New(numArgs);
-    PyTuple_SetItem(m_scriptArgs, 0, m_varsIn);
-    if (numArgs > 1)
-        PyTuple_SetItem(m_scriptArgs, 1, m_varsOut);
-    if (numArgs > 2)
-        PyTuple_SetItem(m_scriptArgs, 2, m_metaIn);
-    if (numArgs > 3)
-        PyTuple_SetItem(m_scriptArgs, 3, m_metaOut);
-
-    m_scriptResult = PyObject_CallObject(m_function, m_scriptArgs);
-    if (!m_scriptResult)
-        throw pdal::pdal_error(getTraceback());
-
-    if (!PyBool_Check(m_scriptResult))
-        throw pdal::pdal_error("User function return value not a boolean type.");
-
-    return (m_scriptResult == Py_True);
-}
-
-} // namespace plang
-} // namespace pdal
-
diff --git a/src/plang/Redirector.cpp b/src/plang/Redirector.cpp
deleted file mode 100644
index 64d5155..0000000
--- a/src/plang/Redirector.cpp
+++ /dev/null
@@ -1,220 +0,0 @@
-//
-// Copyright (C) 2011 Mateusz Loskot <mateusz at loskot.net>
-// Distributed under the Boost Software License, Version 1.0.
-// (See accompanying file LICENSE_1_0.txt or copy at
-// http://www.boost.org/LICENSE_1_0.txt)
-//
-// Blog article: http://mateusz.loskot.net/?p=2819
-
-#include <pdal/plang/Redirector.hpp>
-
-#ifdef PDAL_COMPILER_MSVC
-#  pragma warning(disable: 4127)  // conditional expression is constant
-#endif
-
-#include <ostream>
-#include <string>
-
-namespace pdal
-{
-namespace plang
-{
-
-struct Stdout
-{
-    PyObject_HEAD
-    Redirector::stdout_write_type write;
-    Redirector::stdout_flush_type flush;
-};
-
-
-static PyObject* Stdout_write(PyObject* self, PyObject* args)
-{
-    std::size_t written(0);
-    Stdout* selfimpl = reinterpret_cast<Stdout*>(self);
-    if (selfimpl->write)
-    {
-        char* data;
-        if (!PyArg_ParseTuple(args, "s", &data))
-            return 0;
-
-        std::string str(data);
-        selfimpl->write(str);
-        written = str.size();
-    }
-    return PyLong_FromSize_t(written);
-}
-
-
-static PyObject* Stdout_flush(PyObject* self, PyObject* /*args*/)
-{
-    Stdout *selfimpl = reinterpret_cast<Stdout *>(self);
-    if (selfimpl->flush)
-    {
-        selfimpl->flush();
-    }
-    return Py_BuildValue("");
-}
-
-
-static PyMethodDef Stdout_methods[] =
-{
-    {"write", Stdout_write, METH_VARARGS, "sys.stdout.write"},
-    {"flush", Stdout_flush, METH_VARARGS, "sys.stdout.flush"},
-    {0, 0, 0, 0} // sentinel
-};
-
-
-static PyTypeObject StdoutType =
-{
-    PyVarObject_HEAD_INIT(0, 0)
-    "redirector.StdoutType", /* tp_name */
-    sizeof(Stdout), /* tp_basicsize */
-    0, /* tp_itemsize */
-    0, /* tp_dealloc */
-    0, /* tp_print */
-    0, /* tp_getattr */
-    0, /* tp_setattr */
-    0, /* tp_reserved */
-    0, /* tp_repr */
-    0, /* tp_as_number */
-    0, /* tp_as_sequence */
-    0, /* tp_as_mapping */
-    0, /* tp_hash */
-    0, /* tp_call */
-    0, /* tp_str */
-    0, /* tp_getattro */
-    0, /* tp_setattro */
-    0, /* tp_as_buffer */
-    Py_TPFLAGS_DEFAULT, /* tp_flags */
-    "redirector.Stdout objects", /* tp_doc */
-    0, /* tp_traverse */
-    0, /* tp_clear */
-    0, /* tp_richcompare */
-    0, /* tp_weaklistoffset */
-    0, /* tp_iter */
-    0, /* tp_iternext */
-    Stdout_methods, /* tp_methods */
-    0, /* tp_members */
-    0, /* tp_getset */
-    0, /* tp_base */
-    0, /* tp_dict */
-    0, /* tp_descr_get */
-    0, /* tp_descr_set */
-    0, /* tp_dictoffset */
-    0, /* tp_init */
-    0, /* tp_alloc */
-    0, /* tp_new */
-    0, /* tp_free */
-    0, /* tp_is_gc */
-    0, /* tp_bases */
-    0, /* tp_mro */
-    0, /* tp_cache */
-    0, /* tp_subclasses */
-    0, /* tp_weaklist */
-    0, /* tp_del */
-    0, /* tp_version_tag */
-#if PY_MAJOR_VERSION >= 3
-    0, /* tp_finalilzer */
-#endif
-};
-
-
-Redirector::Redirector()
-    : m_stdout(NULL)
-    , m_stdout_saved(NULL)
-{
-    return;
-}
-
-
-Redirector::~Redirector()
-{
-    return;
-}
-
-
-PyMODINIT_FUNC redirector_init(void)
-{
-#if PY_MAJOR_VERSION >= 3
-    return Redirector::init();
-#else
-    Redirector::init();
-#endif
-}
-
-#if PY_MAJOR_VERSION >= 3
-    static struct PyModuleDef redirectordef = {
-        PyModuleDef_HEAD_INIT,
-        "redirector",     /* m_name */
-        "redirector.Stdout objects",  /* m_doc */
-        -1,                  /* m_size */
-        Stdout_methods,    /* m_methods */
-        NULL,                /* m_reload */
-        NULL,                /* m_traverse */
-        NULL,                /* m_clear */
-        NULL,                /* m_free */
-    };
-#endif
-
-PyObject* Redirector::init()
-{
-    StdoutType.tp_new = PyType_GenericNew;
-    if (PyType_Ready(&StdoutType) < 0)
-        return NULL;
-#if PY_MAJOR_VERSION >= 3
-    PyObject* m = PyModule_Create(&redirectordef);
-#else
-    PyObject* m = Py_InitModule3("redirector", 0, 0);
-#endif
-    if (m)
-    {
-        //ABELL - This is bad code as the type cast is invalid. (type pun
-        //  warning.)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wstrict-aliasing"
-        Py_INCREF(reinterpret_cast<PyObject*>(&StdoutType));
-        PyModule_AddObject(m, "Stdout", reinterpret_cast<PyObject*>(&StdoutType));
-#pragma GCC diagnostic pop
-    }
-    return m;
-}
-
-
-void Redirector::set_stdout(std::ostream* ostr)
-{
-    stdout_write_type writeFunc = [ostr](std::string msg) { *ostr << msg; };
-    stdout_flush_type flushFunc = [ostr]{ ostr->flush(); };
-
-    this->set_stdout(writeFunc, flushFunc);
-}
-
-
-void Redirector::set_stdout(stdout_write_type write, stdout_flush_type flush)
-{
-    if (!m_stdout)
-    {
-        m_stdout_saved =
-            PySys_GetObject(const_cast<char*>("stdout")); // borrowed
-        m_stdout = StdoutType.tp_new(&StdoutType, 0, 0);
-    }
-
-    Stdout* impl = reinterpret_cast<Stdout*>(m_stdout);
-    impl->write = write;
-    impl->flush = flush;
-    PySys_SetObject(const_cast<char*>("stdout"), m_stdout);
-}
-
-
-void Redirector::reset_stdout()
-{
-    if (m_stdout_saved)
-        PySys_SetObject(const_cast<char*>("stdout"), m_stdout_saved);
-
-    Py_XDECREF(m_stdout);
-    m_stdout = 0;
-}
-
-} //namespace plang
-} //namespace pdal
-
diff --git a/src/util/Bounds.cpp b/src/util/Bounds.cpp
deleted file mode 100644
index 9cc74de..0000000
--- a/src/util/Bounds.cpp
+++ /dev/null
@@ -1,316 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <iostream>
-#include <limits>
-#include <vector>
-
-#include <pdal/util/Bounds.hpp>
-
-namespace
-{
-
-template <typename PREDICATE>
-void eat(std::istream& in, PREDICATE p)
-{
-    while (p((char)in.get()))
-        ;
-    if (in.eof())
-        in.clear(in.rdstate() & ~std::ios::failbit);
-    else
-        in.unget();
-}
-
-bool eat(std::istream& in, char c)
-{
-    if ((char)in.get() == c)
-        return true;
-    in.unget();
-    return false;
-}
-
-void readpair(std::istream& istr, double& low, double& high)
-{
-    eat(istr, isspace);
-    if (!eat(istr,'['))
-        istr.setstate(std::ios_base::failbit);
-
-    eat(istr, isspace);
-    istr >> low;
-
-    eat(istr, isspace);
-    if (!eat(istr,','))
-        istr.setstate(std::ios_base::failbit);
-
-    eat(istr, isspace);
-    istr >> high;
-
-    if (!eat(istr,']'))
-        istr.setstate(std::ios_base::failbit);
-}
-
-} // unnamed namespace
-
-namespace pdal
-{
-
-namespace
-{
-
-const double LOWEST = (std::numeric_limits<double>::lowest)();
-const double HIGHEST = (std::numeric_limits<double>::max)();
-
-}
-    
-void BOX2D::clear()
-{
-    minx = HIGHEST; miny = HIGHEST;
-    maxx = LOWEST; maxy = LOWEST;
-}
-
-void BOX3D::clear()
-{
-    BOX2D::clear();
-    minz = HIGHEST;
-    maxz = LOWEST;
-}
-
-bool BOX2D::empty() const
-{
-    return  minx == HIGHEST && maxx == LOWEST &&
-        miny == HIGHEST && maxy == LOWEST;
-}
-
-bool BOX3D::empty() const
-{
-    return  BOX2D::empty() && minz == HIGHEST && maxz == LOWEST;
-}
-
-void BOX2D::grow(double x, double y)
-{
-    if (x < minx) minx = x;
-    if (x > maxx) maxx = x;
-
-    if (y < miny) miny = y;
-    if (y > maxy) maxy = y;
-}
-
-void BOX3D::grow(double x, double y, double z)
-{
-    BOX2D::grow(x, y);
-    if (z < minz) minz = z;
-    if (z > maxz) maxz = z;
-}
-
-const BOX2D& BOX2D::getDefaultSpatialExtent()
-{
-    static BOX2D v(LOWEST, LOWEST, HIGHEST, HIGHEST);
-    return v;
-}    
-
-
-const BOX3D& BOX3D::getDefaultSpatialExtent()
-{
-    static BOX3D v(LOWEST, LOWEST, LOWEST, HIGHEST, HIGHEST, HIGHEST);
-    return v;
-}    
-
-Bounds::Bounds(const BOX3D& box) : m_box(box)
-{}
-
-
-Bounds::Bounds(const BOX2D& box) : m_box(box)
-{
-    m_box.minz = HIGHEST;
-    m_box.maxz = LOWEST;
-}
-
-BOX3D Bounds::to3d() const
-{
-    if (m_box.minz == HIGHEST && m_box.maxz == LOWEST)
-        return BOX3D();
-    return m_box;
-}
-
-BOX2D Bounds::to2d() const
-{
-    return m_box.to2d();
-}
-
-bool Bounds::is3d() const
-{
-    return (m_box.minz != HIGHEST || m_box.maxz != LOWEST);
-}
-
-
-void Bounds::set(const BOX3D& box)
-{
-    m_box = box;
-}
-
-
-void Bounds::set(const BOX2D& box)
-{
-    m_box = BOX3D(box);
-    m_box.minz = HIGHEST;
-    m_box.maxz = LOWEST;
-}
-
-std::istream& operator>>(std::istream& istr, BOX2D& bounds)
-{
-    //ABELL - Not sure the point of this.  I get that one can have an "empty"
-    // BOX2D, but when would it be useful to create one from a string?
-    // A really dirty way to check for an empty bounds object right off
-    // the bat
-    char left_paren = (char)istr.get();
-    if (!istr.good())
-    {
-        istr.setstate(std::ios_base::failbit);
-        return istr;
-    }
-    const char right_paren = (char)istr.get();
-
-    if (left_paren == '(' && right_paren == ')')
-    {
-        bounds = BOX2D();
-        return istr;
-    }
-    istr.unget();
-    istr.unget(); // ()
-
-    std::vector<double> v;
-
-    eat(istr, isspace);
-    if (!eat(istr,'('))
-        istr.setstate(std::ios_base::failbit);
-
-    bool done = false;
-    for (int i = 0; i < 2; ++i)
-    {
-        double low, high;
-
-        readpair(istr, low, high);
-
-        eat(istr, isspace);
-        if (!eat(istr, i == 1 ? ')' : ','))
-            istr.setstate(std::ios_base::failbit);
-        v.push_back(low);
-        v.push_back(high);
-    }
-
-    if (istr.good())
-    {
-        bounds.minx = v[0];
-        bounds.maxx = v[1];
-        bounds.miny = v[2];
-        bounds.maxy = v[3];
-    }
-    return istr;
-}
-
-std::istream& operator>>(std::istream& istr, BOX3D& bounds)
-{
-    //ABELL - Not sure the point of this.  I get that one can have an "empty"
-    // BOX3D, but when would it be useful to create one from a string?
-    // A really dirty way to check for an empty bounds object right off
-    // the bat
-    char left_paren = (char)istr.get();
-    if (!istr.good())
-    {
-        istr.setstate(std::ios_base::failbit);
-        return istr;
-    }
-    const char right_paren = (char)istr.get();
-
-    if (left_paren == '(' && right_paren == ')')
-    {
-        BOX3D output;
-        bounds = output;
-        return istr;
-    }
-    istr.unget();
-    istr.unget(); // ()
-
-    std::vector<double> v;
-
-    eat(istr, isspace);
-    if (!eat(istr,'('))
-        istr.setstate(std::ios_base::failbit);
-
-    bool done = false;
-    for (int i = 0; i < 3; ++i)
-    {
-        double low, high;
-
-        readpair(istr, low, high);
-
-        eat(istr, isspace);
-        if (!eat(istr, i == 2 ? ')' : ','))
-            istr.setstate(std::ios_base::failbit);
-        v.push_back(low);
-        v.push_back(high);
-    }
-
-    if (istr.good())
-    {
-        bounds.minx = v[0];
-        bounds.maxx = v[1];
-        bounds.miny = v[2];
-        bounds.maxy = v[3];
-        bounds.minz = v[4];
-        bounds.maxz = v[5];
-    }
-    return istr;
-}
-
-std::istream& operator>>(std::istream& in, Bounds& bounds)
-{
-    std::streampos start = in.tellg();
-    BOX3D b3d;
-    in >> b3d;
-    if (in.fail())
-    {
-        in.clear();
-        in.seekg(start);
-        BOX2D b2d;
-        in >> b2d;
-        if (!in.fail())
-            bounds.set(b2d);
-    }
-    else
-        bounds.set(b3d);
-    return in;
-}
-
-} // namespace pdal
diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt
deleted file mode 100644
index 24db80c..0000000
--- a/src/util/CMakeLists.txt
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Make sure we don't attempt to add a library more than once
-#
-get_property(EXISTS GLOBAL PROPERTY _UTIL_INCLUDED)
-if(EXISTS)
-    return()
-endif()
-
-set(PDAL_UTIL_HPP
-    "${PDAL_INCLUDE_DIR}/pdal/util/Algorithm.hpp"
-    "${PDAL_INCLUDE_DIR}/pdal/util/Bounds.hpp"
-    "${PDAL_INCLUDE_DIR}/pdal/util/Charbuf.hpp"
-    "${PDAL_INCLUDE_DIR}/pdal/util/Extractor.hpp"
-    "${PDAL_INCLUDE_DIR}/pdal/util/FileUtils.hpp"
-    "${PDAL_INCLUDE_DIR}/pdal/util/Georeference.hpp"
-    "${PDAL_INCLUDE_DIR}/pdal/util/Inserter.hpp"
-    "${PDAL_INCLUDE_DIR}/pdal/util/IStream.hpp"
-    "${PDAL_INCLUDE_DIR}/pdal/util/OStream.hpp"
-    "${PDAL_INCLUDE_DIR}/pdal/util/Utils.hpp"
-    "${PDAL_INCLUDE_DIR}/pdal/util/Uuid.hpp"
-    )
-
-set(PDAL_UTIL_CPP
-    "${PDAL_UTIL_DIR}/Bounds.cpp"
-    "${PDAL_UTIL_DIR}/Charbuf.cpp"
-    "${PDAL_UTIL_DIR}/FileUtils.cpp"
-    "${PDAL_UTIL_DIR}/Georeference.cpp"
-    "${PDAL_UTIL_DIR}/Utils.cpp"
-    )
-
-set(PDAL_UTIL_SOURCES
-    ${PDAL_UTIL_CPP}
-    ${PDAL_UTIL_HPP})
-
-PDAL_ADD_LIBRARY(${PDAL_UTIL_LIB_NAME} SHARED ${PDAL_UTIL_SOURCES})
-target_link_libraries(${PDAL_UTIL_LIB_NAME}
-    PRIVATE
-        ${PDAL_BOOST_LIB_NAME}
-)
-
-set_target_properties(${PDAL_UTIL_LIB_NAME} PROPERTIES
-    VERSION "${PDAL_BUILD_VERSION}"
-    SOVERSION "${PDAL_API_VERSION}"
-    CLEAN_DIRECT_OUTPUT 1)
-
-set_property(GLOBAL PROPERTY _UTIL_INCLUDED TRUE)
diff --git a/src/util/FileUtils.cpp b/src/util/FileUtils.cpp
deleted file mode 100644
index 49d207a..0000000
--- a/src/util/FileUtils.cpp
+++ /dev/null
@@ -1,353 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <sys/stat.h>
-
-#include <iostream>
-#include <sstream>
-
-#include <boost/filesystem.hpp>
-
-#include <pdal/util/FileUtils.hpp>
-#include <pdal/util/Utils.hpp>
-#include <pdal/pdal_types.hpp>
-
-using namespace std;
-
-
-namespace pdal
-{
-
-namespace
-{
-
-bool isStdin(string filename)
-{
-    return Utils::toupper(filename) == "STDIN";
-}
-
-bool isStdout(string filename)
-{
-    return Utils::toupper(filename) == "STOUT" ||
-        Utils::toupper(filename) == "STDOUT";
-}
-
-string addTrailingSlash(string path)
-{
-    if (path[path.size() - 1] != '/' && path[path.size() - 1] != '\\')
-        path += "/";
-    return path;
-}
-
-} // unnamed namespace
-
-namespace FileUtils
-{
-
-istream *openFile(string const& filename, bool asBinary)
-{
-    std::string name(filename);
-    if (isStdin(name))
-        return &cin;
-
-    if (!FileUtils::fileExists(name))
-        return NULL;
-
-    ios::openmode mode = ios::in;
-    if (asBinary)
-        mode |= ios::binary;
-
-    ifstream *ifs = new ifstream(name, mode);
-    if (!ifs->good())
-    {
-        delete ifs;
-        return NULL;
-    }
-    return ifs;
-}
-
-
-ostream *createFile(string const& name, bool asBinary)
-{
-    if (isStdout(name))
-        return &cout;
-
-    ios::openmode mode = ios::out;
-    if (asBinary)
-        mode |= ios::binary;
-
-    ostream *ofs = new ofstream(name, mode);
-    if (!ofs->good())
-    {
-        delete ofs;
-        return NULL;
-    }
-    return ofs;
-}
-
-
-bool directoryExists(const string& dirname)
-{
-    //ABELL - Seems we should be calling is_directory
-    return pdalboost::filesystem::exists(dirname);
-}
-
-
-bool createDirectory(const string& dirname)
-{
-    return pdalboost::filesystem::create_directory(dirname);
-}
-
-
-void deleteDirectory(const string& dirname)
-{
-    pdalboost::filesystem::remove_all(dirname);
-}
-
-
-StringList directoryList(const string& dir)
-{
-    StringList files;
-
-    pdalboost::filesystem::directory_iterator it(dir);
-    pdalboost::filesystem::directory_iterator end;
-    while (it != end)
-    {
-        files.push_back(it->path().string());
-        it++;
-    }
-    return files;
-}
-
-
-void closeFile(ostream *out)
-{
-    // An ofstream is closeable and deletable, but
-    // an ostream like &cout isn't.
-    if (!out)
-        return;
-    ofstream *ofs = dynamic_cast<ofstream *>(out);
-    if (ofs)
-    {
-        ofs->close();
-        delete ofs;
-    }
-}
-
-
-void closeFile(istream* in)
-{
-    // An ifstream is closeable and deletable, but
-    // an istream like &cin isn't.
-    if (!in)
-        return;
-    ifstream *ifs = dynamic_cast<ifstream *>(in);
-    if (ifs)
-    {
-        ifs->close();
-        delete ifs;
-    }
-}
-
-
-bool deleteFile(const string& file)
-{
-    if (!fileExists(file))
-        return false;
-
-    return pdalboost::filesystem::remove(file);
-}
-
-
-void renameFile(const string& dest, const string& src)
-{
-    pdalboost::filesystem::rename(src, dest);
-}
-
-
-bool fileExists(const string& name)
-{
-    pdalboost::system::error_code ec;
-    pdalboost::filesystem::exists(name, ec);
-    return pdalboost::filesystem::exists(name) || isStdin(name);
-}
-
-
-uintmax_t fileSize(const string& file)
-{
-    return pdalboost::filesystem::file_size(file);
-}
-
-
-string readFileIntoString(const string& filename)
-{
-    istream* stream = openFile(filename, false);
-    assert(stream);
-    string str((istreambuf_iterator<char>(*stream)),
-        istreambuf_iterator<char>());
-    closeFile(stream);
-    return str;
-}
-
-
-string getcwd()
-{
-    const pdalboost::filesystem::path p = pdalboost::filesystem::current_path();
-    return addTrailingSlash(p.string());
-}
-
-
-/***
-// Non-boost alternative.  Requires file existence.
-string toAbsolutePath(const string& filename)
-{
-    string result;
-
-#ifdef WIN32
-    char buf[MAX_PATH]
-    if (GetFullPathName(filename.c_str(), MAX_PATH, buf, NULL))
-        result = buf;
-#else
-    char buf[PATH_MAX];
-    if (realpath(filename.c_str(), buf))
-        result = buf;
-#endif
-    return result;
-}
-***/
-
-// if the filename is an absolute path, just return it
-// otherwise, make it absolute (relative to current working dir) and return that
-string toAbsolutePath(const string& filename)
-{
-    return pdalboost::filesystem::absolute(filename).string();
-}
-
-
-// if the filename is an absolute path, just return it
-// otherwise, make it absolute (relative to base dir) and return that
-//
-// note: if base dir is not absolute, first make it absolute via
-// toAbsolutePath(base)
-string toAbsolutePath(const string& filename, const string base)
-{
-    const string newbase = toAbsolutePath(base);
-    return pdalboost::filesystem::absolute(filename, newbase).string();
-}
-
-string getFilename(const string& path)
-{
-#ifdef _WIN32
-    std::string pathsep("\\/");
-#else
-    char pathsep = '/';
-#endif
-
-    string::size_type pos = path.find_last_of(pathsep);
-    if (pos == string::npos)
-        return path;
-    return path.substr(pos + 1);
-}
-
-
-// Get the directory part of a filename.
-string getDirectory(const string& path)
-{
-    const pdalboost::filesystem::path dir =
-         pdalboost::filesystem::path(path).parent_path();
-    return addTrailingSlash(dir.string());
-}
-
-
-string stem(const string& path)
-{
-    std::string f = getFilename(path);
-    if (f != "." && f != "..")
-    {
-        std::string::size_type pos = f.find_last_of(".");
-        if (pos != std::string::npos)
-            f = f.substr(0, pos);
-    }
-    return f;
-}
-
-
-// Determine if the path represents a directory.
-bool isDirectory(const std::string& path)
-{
-    return pdalboost::filesystem::is_directory(path);
-}
-
-// Determine if the path is an absolute path
-bool isAbsolutePath(const string& path)
-{
-    return pdalboost::filesystem::path(path).is_absolute();
-}
-
-
-void fileTimes(const string& filename, struct tm *createTime,
-    struct tm *modTime)
-{
-#ifdef WIN32
-    struct _stat statbuf;
-    _stat(filename.c_str(), &statbuf);
-
-    if (createTime)
-        *createTime = *gmtime(&statbuf.st_ctime);
-    if (modTime)
-        *modTime = *gmtime(&statbuf.st_mtime);
-#else
-    struct stat statbuf;
-    stat(filename.c_str(), &statbuf);
-
-    if (createTime)
-        gmtime_r(&statbuf.st_ctime, createTime);
-    if (modTime)
-        gmtime_r(&statbuf.st_mtime, modTime);
-#endif
-}
-
-
-std::string extension(const std::string& filename)
-{
-    auto idx = filename.find_last_of('.');
-    if (idx == std::string::npos)
-        return std::string();
-    return filename.substr(idx);
-}
-
-} // namespace FileUtils
-
-} // namespace pdal
-
diff --git a/src/util/Utils.cpp b/src/util/Utils.cpp
deleted file mode 100644
index fd2f4bb..0000000
--- a/src/util/Utils.cpp
+++ /dev/null
@@ -1,635 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/util/Utils.hpp>
-
-#include <cassert>
-#include <cstdlib>
-#include <cctype>
-#include <memory>
-#include <random>
-
-#ifndef _WIN32
-#include <cxxabi.h>
-#include <sys/ioctl.h>
-#include <sys/wait.h>  // WIFEXITED, WEXITSTATUS
-#endif
-
-#ifdef PDAL_COMPILER_MSVC
-#  pragma warning(disable: 4127)  // conditional expression is constant
-#endif
-
-#include <stdio.h>
-#include <iomanip>
-
-typedef std::vector<std::string> StringList;
-
-namespace pdal
-{
-
-
-void Utils::random_seed(unsigned int seed)
-{
-    srand(seed);
-}
-
-
-double Utils::random(double minimum, double maximum)
-{
-    double r = (double)rand();  // [0..32767]
-    double v = (maximum - minimum) / (double)RAND_MAX;
-    double s = r * v; // [0..(max-min)]
-    double t = minimum + s; // [min..max]
-
-    assert(t >= minimum);
-    assert(t <= maximum);
-
-    return t;
-}
-
-
-double Utils::uniform(const double& minimum, const double& maximum,
-    uint32_t seed)
-{
-    std::mt19937 gen(seed);
-    std::uniform_real_distribution<double> dist(minimum, maximum);
-
-    return dist(gen);
-}
-
-
-double Utils::normal(const double& mean, const double& sigma, uint32_t seed)
-{
-    std::mt19937 gen(seed);
-    std::normal_distribution<double> dist(mean, sigma);
-
-    return dist(gen);
-}
-
-
-int Utils::getenv(const std::string& name, std::string& val)
-{
-    char* value = ::getenv(name.c_str());
-    if (value)
-        val = value;
-    else
-        val.clear();
-    return value ?  0 : -1;
-}
-
-
-int Utils::setenv(const std::string& env, const std::string& val)
-{
-#ifdef _WIN32
-    return ::_putenv_s(env.c_str(), val.c_str()) ? -1 : 0;
-#else
-    return ::setenv(env.c_str(), val.c_str(), 1);
-#endif
-}
-
-
-int Utils::unsetenv(const std::string& env)
-{
-#ifdef _WIN32
-    return ::_putenv_s(env.c_str(), "") ? -1 : 0;
-#else
-    return ::unsetenv(env.c_str());
-#endif
-}
-
-
-void Utils::eatwhitespace(std::istream& s)
-{
-    while (true)
-    {
-        const char c = (char)s.peek();
-        if (!isspace(c))
-            break;
-
-        // throw it away
-        s.get();
-    }
-}
-
-
-void Utils::trimLeading(std::string& s)
-{
-    size_t pos = 0;
-    // Note, that this should be OK in C++11, which guarantees a NULL.
-    while (isspace(s[pos]))
-        pos++;
-    s = s.substr(pos);
-}
-
-
-void Utils::trimTrailing(std::string& s)
-{
-    if (s.empty())
-        return;
-
-    size_t pos = s.size() - 1;
-    while (isspace(s[pos]))
-    {
-        if (pos == 0)
-        {
-            s.clear();
-            return;
-        }
-        else
-            pos--;
-    }
-    s = s.substr(0, pos + 1);
-}
-
-
-bool Utils::eatcharacter(std::istream& s, char x)
-{
-    const char c = (char)s.peek();
-    if (c != x)
-        return false;
-
-    // throw it away
-    s.get();
-
-    return true;
-}
-
-
-std::string Utils::base64_encode(const unsigned char *bytes_to_encode,
-    size_t in_len)
-{
-    /*
-        base64.cpp and base64.h
-
-        Copyright (C) 2004-2008 René Nyffenegger
-
-        This source code is provided 'as-is', without any express or implied
-        warranty. In no event will the author be held liable for any damages
-        arising from the use of this software.
-
-        Permission is granted to anyone to use this software for any purpose,
-        including commercial applications, and to alter it and redistribute it
-        freely, subject to the following restrictions:
-
-        1. The origin of this source code must not be misrepresented;
-           you must not claim that you wrote the original source code. If you
-           use this source code in a product, an acknowledgment in the product
-           documentation would be appreciated but is not required.
-
-        2. Altered source versions must be plainly marked as such, and must
-           not be misrepresented as being the original source code.
-
-        3. This notice may not be removed or altered from any source
-           distribution.
-
-        René Nyffenegger rene.nyffenegger at adp-gmbh.ch
-    */
-
-    if (in_len == 0)
-        return std::string();
-
-    const std::string base64_chars =
-        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-        "abcdefghijklmnopqrstuvwxyz"
-        "0123456789+/";
-    std::string ret;
-    int i = 0;
-    int j = 0;
-    uint8_t char_array_3[3];
-    uint8_t char_array_4[4];
-
-    while (in_len--)
-    {
-        char_array_3[i++] = *(bytes_to_encode++);
-        if (i == 3)
-        {
-            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
-            char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
-                ((char_array_3[1] & 0xf0) >> 4);
-            char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
-                ((char_array_3[2] & 0xc0) >> 6);
-            char_array_4[3] = char_array_3[2] & 0x3f;
-
-            for (i = 0; (i <4) ; i++)
-                ret += base64_chars[char_array_4[i]];
-            i = 0;
-        }
-    }
-
-    if (i)
-    {
-        for (j = i; j < 3; j++)
-            char_array_3[j] = '\0';
-
-        char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
-        char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
-            ((char_array_3[1] & 0xf0) >> 4);
-        char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
-            ((char_array_3[2] & 0xc0) >> 6);
-        char_array_4[3] = char_array_3[2] & 0x3f;
-
-        for (j = 0; (j < i + 1); j++)
-            ret += base64_chars[char_array_4[j]];
-
-        while ((i++ < 3))
-            ret += '=';
-    }
-    return ret;
-}
-
-
-static inline bool is_base64(unsigned char c)
-{
-    return (isalnum(c) || (c == '+') || (c == '/'));
-}
-
-
-std::vector<uint8_t> Utils::base64_decode(std::string const& encoded_string)
-{
-    const std::string base64_chars =
-        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-        "abcdefghijklmnopqrstuvwxyz"
-        "0123456789+/";
-
-    std::string::size_type in_len = encoded_string.size();
-    int i = 0;
-    int j = 0;
-    int in_ = 0;
-    unsigned char char_array_4[4], char_array_3[3];
-    std::vector<uint8_t> ret;
-
-    while (in_len-- && (encoded_string[in_] != '=') &&
-        is_base64(encoded_string[in_]))
-    {
-        char_array_4[i++] = encoded_string[in_];
-        in_++;
-        if (i == 4)
-        {
-            for (i = 0; i <4; i++)
-                char_array_4[i] = static_cast<unsigned char>(
-                    base64_chars.find(char_array_4[i]));
-
-            char_array_3[0] = (char_array_4[0] << 2) +
-                ((char_array_4[1] & 0x30) >> 4);
-            char_array_3[1] = ((char_array_4[1] & 0xf) << 4) +
-                ((char_array_4[2] & 0x3c) >> 2);
-            char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
-
-            for (i = 0; (i < 3); i++)
-                ret.push_back(char_array_3[i]);
-            i = 0;
-        }
-    }
-
-    if (i)
-    {
-        for (j = i; j <4; j++)
-            char_array_4[j] = 0;
-
-        for (j = 0; j <4; j++)
-            char_array_4[j] =
-                static_cast<unsigned char>(base64_chars.find(char_array_4[j]));
-
-        char_array_3[0] = (char_array_4[0] << 2) +
-            ((char_array_4[1] & 0x30) >> 4);
-        char_array_3[1] = ((char_array_4[1] & 0xf) << 4) +
-            ((char_array_4[2] & 0x3c) >> 2);
-        char_array_3[2] = ((char_array_4[2] & 0x3) << 6) +
-            char_array_4[3];
-
-        for (j = 0; (j < i - 1); j++)
-            ret.push_back(char_array_3[j]);
-    }
-    return ret;
-}
-
-
-FILE* Utils::portable_popen(const std::string& command, const std::string& mode)
-{
-#ifdef _WIN32
-    const std::string dos_command = Utils::replaceAll(command, "/", "\\");
-    return _popen(dos_command.c_str(), mode.c_str());
-#else
-    return popen(command.c_str(), mode.c_str());
-#endif
-}
-
-
-int Utils::portable_pclose(FILE* fp)
-{
-    int status = 0;
-
-#ifdef _WIN32
-    status = _pclose(fp);
-#else
-    status = pclose(fp);
-    if (status == -1)
-    {
-        throw std::runtime_error("error executing command");
-    }
-    if (WIFEXITED(status) != 0)
-    {
-        status = WEXITSTATUS(status);
-    }
-    else
-    {
-        status = 0;
-    }
-#endif
-
-    return status;
-}
-
-
-int Utils::run_shell_command(const std::string& cmd, std::string& output)
-{
-    const int maxbuf = 4096;
-    char buf[maxbuf];
-
-    output = "";
-
-    FILE* fp = portable_popen(cmd.c_str(), "r");
-
-    if (fp == NULL)
-        return 1;
-
-    while (!feof(fp))
-    {
-        if (fgets(buf, maxbuf, fp) == NULL)
-        {
-            if (feof(fp)) break;
-            if (ferror(fp)) break;
-        }
-        output += buf;
-    }
-    return portable_pclose(fp);
-}
-
-
-std::string Utils::replaceAll(std::string result,
-    const std::string& replaceWhat, const std::string& replaceWithWhat)
-{
-    size_t pos = 0;
-    while (1)
-    {
-        pos = result.find(replaceWhat, pos);
-        if (pos == std::string::npos)
-            break;
-        result.replace(pos, replaceWhat.size(), replaceWithWhat);
-        pos += replaceWithWhat.size();
-        if (pos >= result.size())
-            break;
-    }
-    return result;
-}
-
-
-// Adapted from http://stackoverflow.com/a/11969098.
-std::string Utils::escapeJSON(const std::string &str)
-{
-    std::string escaped(str);
-
-    escaped.erase
-    (
-        remove_if(
-            escaped.begin(), escaped.end(), [](const char c)
-            {
-                return (c <= 31);
-            }
-        ),
-        escaped.end()
-    );
-
-    size_t pos(0);
-
-    while((pos = escaped.find_first_of("\"\\/", pos)) != std::string::npos)
-    {
-        escaped.insert(pos, "\\");
-        pos += 2;
-    }
-
-    return escaped;
-}
-
-
-StringList Utils::wordWrap(std::string const& s, size_t lineLength,
-    size_t firstLength)
-{
-    std::vector<std::string> output;
-    if (s.empty())
-        return output;
-
-    if (firstLength == 0)
-        firstLength = lineLength;
-
-    size_t len = firstLength;
-
-    std::istringstream iss(s);
-    std::string line;
-    do
-    {
-        std::string word;
-        iss >> word;
-
-        if ((line.length() + word.length() > len) && line.length())
-        {
-            trimTrailing(line);
-            output.push_back(line);
-            len = lineLength;
-            line.clear();
-        }
-        while (word.length() > len)
-        {
-            output.push_back(word.substr(0, len));
-            word = word.substr(len);
-            len = lineLength;
-        }
-        line += word + " ";
-    } while (iss);
-    trimTrailing(line);
-    if (!line.empty())
-        output.push_back(line);
-    return output;
-}
-
-
-StringList Utils::wordWrap2(std::string const& s, size_t lineLength,
-    size_t firstLength)
-{
-    std::vector<std::string> output;
-    if (s.empty())
-        return output;
-
-    if (firstLength == 0)
-        firstLength = lineLength;
-
-    auto pushWord = [&s, &output](size_t start, size_t end)
-    {
-        if (start != end)
-            output.push_back(s.substr(start, end - start + 1));
-    };
-
-    size_t len = firstLength;
-    size_t startPos = 0;
-    while (true)
-    {
-        size_t endPos = std::min(startPos + len - 1, s.size() - 1);
-        if (endPos + 1 == s.size())
-        {
-            pushWord(startPos, endPos);
-            return output;
-        }
-        size_t pos = endPos;
-        while (pos > startPos)
-        {
-            if (std::isspace(s[pos]) && !std::isspace(s[pos + 1]))
-            {
-                endPos = pos;
-                break;
-            }
-            pos--;
-        }
-        pushWord(startPos, endPos);
-        len = lineLength;
-        startPos = endPos + 1;
-    }
-    return output;
-}
-
-
-/// Demangle strings using the compiler-provided demangle function.
-/// \param[in] s  String to be demangled.
-/// \return  Demangled string
-std::string Utils::demangle(const std::string& s)
-{
-#ifndef _WIN32
-    int status;
-    std::unique_ptr<char[], void (*)(void*)> result(
-            abi::__cxa_demangle(s.c_str(), 0, 0, &status), std::free);
-    return std::string(result.get());
-#else
-    return s;
-#endif
-}
-
-
-int Utils::screenWidth()
-{
-#ifdef WIN32
-    return 80;
-#else
-    struct winsize ws;
-    if (ioctl(0, TIOCGWINSZ, &ws))
-        return 80;
-
-    return ws.ws_col;
-#endif
-}
-
-
-std::string Utils::escapeNonprinting(const std::string& s)
-{
-    std::string out;
-
-    for (size_t i = 0; i < s.size(); ++i)
-    {
-        if (s[i] == '\n')
-            out += "\\n";
-        else if (s[i] == '\a')
-            out += "\\a";
-        else if (s[i] == '\b')
-            out += "\\b";
-        else if (s[i] == '\r')
-            out += "\\r";
-        else if (s[i] == '\v')
-            out += "\\v";
-        else if (s[i] < 32)
-        {
-            std::stringstream oss;
-            oss << std::hex << std::setfill('0') << std::setw(2) << (int)s[i];
-            out += "\\x" + oss.str();
-        }
-        else
-            out += s[i];
-    }
-    return out;
-}
-
-
-double Utils::normalizeLongitude(double longitude)
-{
-    longitude = fmod(longitude, 360.0);
-    if (longitude <= -180)
-        longitude += 360;
-    else if (longitude > 180)
-        longitude -= 360;
-    return longitude;
-}
-
-
-// Useful for debug on occasion.
-std::string Utils::hexDump(const char *buf, size_t count)
-{
-   const unsigned char *cp = reinterpret_cast<const unsigned char *>(buf);
-   char foo[80];
-   int bytes, i, address = 0;
-   std::string out;
-
-   bytes = (count > 16) ? 16 : count;
-
-   while (bytes) {
-      sprintf(foo, "0x%06x ", address);
-      address += 16;
-      for (i = 0; i < 16; i++) {
-         if (i < bytes) {
-            sprintf(foo, "%02X ", cp[i]);
-            out += foo;
-         }
-         else
-            out += "   ";
-      }
-      out += "|";
-      for (i = 0; i < bytes; i++) {
-         sprintf(foo, "%c", isprint(cp[i]) ? cp[i] : '.');
-         out += foo;
-      }
-      out += "|\n";
-      count -= bytes;
-      cp += bytes;
-      bytes = (count > 16) ? 16 : count;
-   }
-   return (out);
-}
-
-} // namespace pdal
diff --git a/test/data/autzen/autzen-interpolate.xml b/test/data/autzen/autzen-interpolate.xml
deleted file mode 100644
index c8391c1..0000000
--- a/test/data/autzen/autzen-interpolate.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.p2g">
-        <Option name="filename">
-            autzen-surface.tif
-        </Option>
-        <Option name="output_type">
-            min
-        </Option>
-        <Option name="output_format">
-            tif
-        </Option>
-        <Option name="grid_dist_x">
-          1.0
-        </Option>
-        <Option name="grid_dist_y">
-          1.0
-        </Option>
-        <Filter type="filters.pclblock">
-            <Option name="filename">
-                ./autzen-APMF.json
-            </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                /Users/hobu/dev/git/pdal/test/data/local/autzen/autzen-full.las
-            </Option>
-        </Reader>
-    </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/autzen/autzen.jpg.aux.xml b/test/data/autzen/autzen.jpg.aux.xml
deleted file mode 100644
index f527a0f..0000000
--- a/test/data/autzen/autzen.jpg.aux.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-<PAMDataset>
-  <SRS>PROJCS["NAD_1983_HARN_Lambert_Conformal_Conic",GEOGCS["GCS_North_American_1983_HARN",DATUM["NAD83_High_Accuracy_Regional_Network",SPHEROID["GRS_1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6152"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",43],PARAME [...]
-  <GeoTransform>  6.3561542786591221e+05,  1.0000000000000000e+00,  0.0000000000000000e+00,  8.5336264308515214e+05,  0.0000000000000000e+00, -1.0000000000000000e+00</GeoTransform>
-  <Metadata domain="IMAGE_STRUCTURE">
-    <MDI key="SOURCE_COLOR_SPACE">YCbCr</MDI>
-    <MDI key="INTERLEAVE">PIXEL</MDI>
-    <MDI key="COMPRESSION">JPEG</MDI>
-  </Metadata>
-  <Metadata>
-    <MDI key="AREA_OR_POINT">Area</MDI>
-  </Metadata>
-  <PAMRasterBand band="1">
-    <NoDataValue>0.00000000000000E+00</NoDataValue>
-    <Metadata domain="IMAGE_STRUCTURE">
-      <MDI key="COMPRESSION">JPEG</MDI>
-    </Metadata>
-    <Metadata>
-      <MDI key="STATISTICS_MINIMUM">35</MDI>
-      <MDI key="STATISTICS_MAXIMUM">255</MDI>
-      <MDI key="STATISTICS_MEAN">123.1901795373</MDI>
-      <MDI key="STATISTICS_MEDIAN">119</MDI>
-      <MDI key="STATISTICS_MODE">76</MDI>
-      <MDI key="STATISTICS_STDDEV">44.35868517657</MDI>
-      <MDI key="STATISTICS_SKIPFACTORX">1</MDI>
-      <MDI key="STATISTICS_SKIPFACTORY">1</MDI>
-      <MDI key="STATISTICS_EXCLUDEDVALUES">0</MDI>
-      <MDI key="LAYER_TYPE">athematic</MDI>
-      <MDI key="STATISTICS_HISTOMIN">0</MDI>
-      <MDI key="STATISTICS_HISTOMAX">255</MDI>
-      <MDI key="STATISTICS_HISTONUMBINS">256</MDI>
-      <MDI key="STATISTICS_HISTOBINVALUES">0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|10|21|9|48|224|385|714|2161|2696|8309|7588|24900|17765|46882|27814|61448|34450|77782|42827|98100|53888|120702|65717|148077|78027|166077|84982|174335|89293|177578|90284|180801|93282|190772|101305|210012|115008|243452|135342|284901|148646|302429|148077|298916|144663|280671|134631|256550|120687|215877|108429|202384|104044|203128|102544|206102|105665|201844|103450|193536|102048|19 [...]
-    </Metadata>
-  </PAMRasterBand>
-  <PAMRasterBand band="2">
-    <NoDataValue>0.00000000000000E+00</NoDataValue>
-    <Metadata domain="IMAGE_STRUCTURE">
-      <MDI key="COMPRESSION">JPEG</MDI>
-    </Metadata>
-    <Metadata>
-      <MDI key="STATISTICS_MINIMUM">49</MDI>
-      <MDI key="STATISTICS_MAXIMUM">255</MDI>
-      <MDI key="STATISTICS_MEAN">126.93814343797</MDI>
-      <MDI key="STATISTICS_MEDIAN">128</MDI>
-      <MDI key="STATISTICS_MODE">138</MDI>
-      <MDI key="STATISTICS_STDDEV">35.775545093364</MDI>
-      <MDI key="STATISTICS_SKIPFACTORX">1</MDI>
-      <MDI key="STATISTICS_SKIPFACTORY">1</MDI>
-      <MDI key="STATISTICS_EXCLUDEDVALUES">0</MDI>
-      <MDI key="LAYER_TYPE">athematic</MDI>
-      <MDI key="STATISTICS_HISTOMIN">0</MDI>
-      <MDI key="STATISTICS_HISTOMAX">255</MDI>
-      <MDI key="STATISTICS_HISTONUMBINS">256</MDI>
-      <MDI key="STATISTICS_HISTOBINVALUES">0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|18|9|28|44|153|455|649|1909|2802|9343|8425|25725|18685|48050|28223|67362|37470|82241|45761|101584|55711|126075|68487|152172|84794|185524|96559|202617|102919|207709|107658|221607|112434|225654|114200|226284|117806|236558|121474|249767|133466|279408|147634|311941|156605|304328|144515|265805|128428|242913|119590|220174|109344|197377|98997|179184|93175| [...]
-    </Metadata>
-  </PAMRasterBand>
-  <PAMRasterBand band="3">
-    <NoDataValue>0.00000000000000E+00</NoDataValue>
-    <Metadata domain="IMAGE_STRUCTURE">
-      <MDI key="COMPRESSION">JPEG</MDI>
-    </Metadata>
-    <Metadata>
-      <MDI key="STATISTICS_MINIMUM">46</MDI>
-      <MDI key="STATISTICS_MAXIMUM">255</MDI>
-      <MDI key="STATISTICS_MEAN">112.16139266402</MDI>
-      <MDI key="STATISTICS_MEDIAN">106</MDI>
-      <MDI key="STATISTICS_MODE">96</MDI>
-      <MDI key="STATISTICS_STDDEV">30.726139906689</MDI>
-      <MDI key="STATISTICS_SKIPFACTORX">1</MDI>
-      <MDI key="STATISTICS_SKIPFACTORY">1</MDI>
-      <MDI key="STATISTICS_EXCLUDEDVALUES">0</MDI>
-      <MDI key="LAYER_TYPE">athematic</MDI>
-      <MDI key="STATISTICS_HISTOMIN">0</MDI>
-      <MDI key="STATISTICS_HISTOMAX">255</MDI>
-      <MDI key="STATISTICS_HISTONUMBINS">256</MDI>
-      <MDI key="STATISTICS_HISTOBINVALUES">0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|9|0|16|69|70|103|397|270|819|995|2190|2835|5936|6759|14962|13403|33486|26770|62511|42952|103342|61300|133575|72938|152680|81852|162560|91043|186667|107722|228069|130398|285507|158372|341260|183090|377369|198397|410282|208868|420529|209239|415785|207731|406405|204345|410580|211696|418652|216557|426333|212853|412247|202892|401270|199707|386659|197533|378993 [...]
-    </Metadata>
-  </PAMRasterBand>
-</PAMDataset>
diff --git a/test/data/autzen/hag.py b/test/data/autzen/hag.py
deleted file mode 100644
index cb0bd8c..0000000
--- a/test/data/autzen/hag.py
+++ /dev/null
@@ -1,53 +0,0 @@
-
-from osgeo import gdal
-gdal.UseExceptions()
-
-import struct
-
-objs = {}
-
-def wakeup(filename):
-
-    objs['ds'] = gdal.Open(filename)
-    objs['gt'] = objs['ds'].GetGeoTransform()
-    objs['band'] = objs['ds'].GetRasterBand(1)
-    return objs
-
-
-def read(x, y, objs):
-
-    # stolen from http://stackoverflow.com/questions/24537450/python-struct-error-unpack-requires-a-string-argument-of-length-2
-    gt = objs['gt']
-    ds = objs['ds']
-    band = objs['band']
-
-    px = int((x - gt[0]) / gt[1])
-    py = int((y - gt[3]) / gt[5])
-
-    val = band.ReadRaster(px,py,1,1,buf_type=gdal.GDT_Float32)
-    z = struct.unpack('f' , val)
-    return z[0]
-
-def filter(ins,outs):
-    HAG = ins['HAG']
-    X = ins['X']
-    Y = ins['Y']
-    Z = ins['Z']
-
-    objs = wakeup('/Users/hobu/dev/git/pdal/test/data/autzen/autzen-surface.tif.min.tif')
-    print (objs)
-    for i in range(len(X)):
-        x = X[i]
-        y = Y[i]
-        z = Z[i]
-        surface_z = read(x, y, objs)
-#        print (x, y, z, surface_z)
-        hag = z - surface_z
-        if (surface_z  != -9999):
-            HAG[i] = hag
-    outs['Z'] = HAG
-    return True
-
-if __name__=='__main__':
-    objs = wakeup('/Users/hobu/dev/git/pdal/test/data/autzen/autzen-surface.tif.min.tif')
-    print (read(636436,850412, objs))
diff --git a/test/data/autzen/hag.xml.in b/test/data/autzen/hag.xml.in
deleted file mode 100644
index 8e57514..0000000
--- a/test/data/autzen/hag.xml.in
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            autzen-hag.las
-        </Option>
-        <Filter type="filters.programmable">
-            <Option name="script">
-                hag.py
-            </Option>
-            <Option name="function">
-                filter
-            </Option>
-            <Option name="module">
-                anything
-            </Option>
-            <Filter type="filters.ferry">
-                <Option name="dimension">
-                    Z
-                    <Options>
-                        <Option name="to">
-                            HAG
-                        </Option>
-                    </Options>
-                </Option>
-                <Reader type="readers.las">
-                    <Option name="filename">
-                        @CMAKE_SOURCE_DIR@/autzen/autzen.las
-                    </Option>
-                </Reader>
-            </Filter>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/autzen/thin-1.las b/test/data/autzen/thin-1.las
new file mode 100644
index 0000000..e058fbb
Binary files /dev/null and b/test/data/autzen/thin-1.las differ
diff --git a/test/data/autzen/thin-2.las b/test/data/autzen/thin-2.las
new file mode 100644
index 0000000..b71a0f0
Binary files /dev/null and b/test/data/autzen/thin-2.las differ
diff --git a/test/data/bpf/bpf.xml.in b/test/data/bpf/bpf.xml.in
deleted file mode 100644
index e7a1ea1..0000000
--- a/test/data/bpf/bpf.xml.in
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out2.las
-        </Option>
-        <Option name="scale_x">0.01</Option>
-        <Option name="offset_x">311898.23</Option>
-        <Option name="scale_y">0.01</Option>
-        <Option name="offset_y">4703909.84</Option>
-        <Option name="scale_z">0.01</Option>
-        <Option name="offset_z">7.385474</Option>
-        <Reader type="readers.bpf">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/bpf/utm15.bpf
-            </Option>
-        </Reader>
-</Writer>
-</Pipeline>
diff --git a/test/data/bpf/bpf2nitf.xml.in b/test/data/bpf/bpf2nitf.xml.in
deleted file mode 100644
index f012de4..0000000
--- a/test/data/bpf/bpf2nitf.xml.in
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.nitf">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/output.ntf
-        </Option>
-        <Option name="scale_x">0.01</Option>
-        <Option name="offset_x">311898.23</Option>
-        <Option name="scale_y">0.01</Option>
-        <Option name="offset_y">4703909.84</Option>
-        <Option name="scale_z">0.01</Option>
-        <Option name="offset_z">7.385474</Option>
-        <Option name="fsclas">U</Option>
-        <Option name="idatim" type="string">20131206140713</Option>
-        <Option name="fscltx" type="string">SIC:0 CH_FO</Option>
-        <Option name="ftitle" type="string">output.ntf</Option>
-        <Option name="forward">software_id, creation_doy, creation_year</Option>
-        <Option name="system_id">PDAL2NTF</Option>
-        <Reader type="readers.bpf">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/bpf/utm15.bpf
-            </Option>
-        </Reader>
-</Writer>
-</Pipeline>
diff --git a/test/data/filters/attribute.xml.in b/test/data/filters/attribute.xml.in
deleted file mode 100644
index 1a361b8..0000000
--- a/test/data/filters/attribute.xml.in
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="2.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/attributed.las
-        </Option>
-        <Option name="scale_x">
-            0.0000001
-        </Option>
-        <Option name="scale_y">
-            0.0000001
-        </Option>
-        <Filter type="filters.attribute">
-            <Option name="dimension">
-                Classification
-                <Options>
-                    <Option name="datasource">
-                        @CMAKE_SOURCE_DIR@/test/data/autzen/attributes.shp
-                    </Option>
-                    <Option name="layer">
-                        attributes
-                    </Option>
-                    <Option name="column">
-                        CLS
-                    </Option>
-                </Options>
-            </Option>
-            <Option name="dimension">
-                Intensity
-                <Options>
-                    <Option name="datasource">
-                        @CMAKE_SOURCE_DIR@/test/data/autzen/attributes.shp
-                    </Option>
-                    <Option name="query">
-                        SELECT CLS FROM attributes where cls!=6
-                    </Option>
-                    <Option name="column">
-                        CLS
-                    </Option>
-                </Options>
-            </Option>
-            <Option name="dimension">
-                PointSourceId
-                <Options>
-                    <Option name="value">
-                        26
-                    </Option>
-                </Options>
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/autzen/autzen-dd.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
-
diff --git a/test/data/filters/chip.xml.in b/test/data/filters/chip.xml.in
deleted file mode 100644
index 3a0badf..0000000
--- a/test/data/filters/chip.xml.in
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/autzen-utm-chipped-25.las
-        </Option>
-        <Filter type="filters.chipper">
-            <Option name="capacity">
-                25
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/autzen/autzen-utm.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/chipper.xml.in b/test/data/filters/chipper.xml.in
deleted file mode 100644
index bda918f..0000000
--- a/test/data/filters/chipper.xml.in
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/autzen-utm-chipped-25.las
-        </Option>
-        <Filter type="filters.merge">
-            <!-- Merge the chips back together into a single file -->
-            <Filter type="filters.chipper">
-                <Option name="capacity">
-                    25
-                </Option>
-
-                <Reader type="readers.las">
-                    <Option name="filename">
-                        @CMAKE_SOURCE_DIR@/test/data/autzen/autzen-utm.las
-                    </Option>
-                </Reader>
-            </Filter>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/colorize-multi.xml.in b/test/data/filters/colorize-multi.xml.in
deleted file mode 100644
index 76af580..0000000
--- a/test/data/filters/colorize-multi.xml.in
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.text">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/sometext.txt
-        </Option>
-        <Filter type="filters.colorization">
-            <Option name="dimension">
-                Red
-                <Options>
-                    <Option name="band">
-                        1
-                    </Option>
-                    <Option name="scale">
-                        1.0
-                    </Option>
-                </Options>
-            </Option>
-            <Option name="dimension">
-                Green
-                <Options>
-                    <Option name="band">
-                        2
-                    </Option>
-                    <Option name="scale">
-                        1.0
-                    </Option>
-                </Options>
-            </Option>
-            <Option name="dimension">
-                Blue
-                <Options>
-                    <Option name="band">
-                        3
-                    </Option>
-                    <Option name="scale">
-                        256
-                    </Option>
-                </Options>
-            </Option>
-            <Option name="raster">
-                ./autzen.tif
-            </Option>
-            <Filter type="filters.colorization">
-                <Option name="dimension">
-                    Red1
-                    <Options>
-                        <Option name="band">
-                            1
-                        </Option>
-                        <Option name="scale">
-                            1.0
-                        </Option>
-                    </Options>
-                </Option>
-                <Option name="dimension">
-                    Green1
-                    <Options>
-                        <Option name="band">
-                            2
-                        </Option>
-                        <Option name="scale">
-                            1.0
-                        </Option>
-                    </Options>
-                </Option>
-                <Option name="dimension">
-                    Blue1
-                    <Options>
-                        <Option name="band">
-                            3
-                        </Option>
-                        <Option name="scale">
-                            256
-                        </Option>
-                    </Options>
-                </Option>
-                <Option name="raster">
-                    ./autzen-warped.tif
-                </Option>
-		<Reader type="readers.las">
-		    <Option name="filename">
-			@CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-		    </Option>
-		    <Option name="spatialreference">
-			EPSG:2993
-		    </Option>
-		</Reader>
-            </Filter>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/colorize.xml.in b/test/data/filters/colorize.xml.in
deleted file mode 100644
index bbcf6cb..0000000
--- a/test/data/filters/colorize.xml.in
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/colorized.las
-        </Option>
-        <Filter type="filters.colorization">
-            <Option name="dimensions">
-                Red, Green, Blue::256
-            </Option>
-            <Option name="raster">
-                @CMAKE_SOURCE_DIR@/test/data/autzen/autzen.jpg
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/crop_reproject.xml.in b/test/data/filters/crop_reproject.xml.in
deleted file mode 100644
index a013c0c..0000000
--- a/test/data/filters/crop_reproject.xml.in
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/cropped.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Filter type="filters.crop">
-	    <Option name="outside">false</Option>
-	    <Option name="y_dim">filters.inplacereprojection.Y</Option>
-	    <Option name="z_dim">filters.inplacereprojection.Z</Option>
-	    <Option name="polygon">POLYGON ((-123.070314549836326 44.057089038234523 128.839019531250017,-123.070271177640294 44.056943121718128 128.768772656250007,-123.070271177640294 44.056943121718128 128.768772656250007,-123.070162496376781 44.056998992754814 128.790203906250014,-123.070162496376781 44.056998992754814 128.790203906250014,-123.070162496376781 44.056998992754814 128.790203906250014,-123.069979815780442 44.057056534650307 129.281039062500014,-123.069979815780442 44.0570565346 [...]
-	    <Option name="x_dim">filters.inplacereprojection.X</Option>             
-	    <!-- <Option name="outside">false</Option>
-	    <Option name="y_dim">drivers.las.reader.Y</Option>
-	    <Option name="z_dim">drivers.las.reader.Z</Option>
-	    <Option name="x_dim">drivers.las.reader.X</Option>            
-	    <Option name="polygon">POLYGON ((636889.412951239268295 851528.512293258565478 422.7001953125,636899.14233423944097 851475.000686757150106 422.4697265625,636899.14233423944097 851475.000686757150106 422.4697265625,636928.33048324030824 851494.459452757611871 422.5400390625,636928.33048324030824 851494.459452757611871 422.5400390625,636928.33048324030824 851494.459452757611871 422.5400390625,636976.977398241520859 851513.918218758190051 424.150390625,636976.977398241520859 851513.918 [...]
-</Option> -->
-	    <Reader type="readers.las">
-		    <Option name="filename">
-                        @CMAKE_SOURCE_DIR@/test/data/autzen/autzen-colorized-1.2-3.las
-		    </Option>
-		    <Option name="spatialreference">
-			EPSG:2994
-		    </Option>
-		    <!-- <Option name="log">
-			oracle-pipeline-write.log
-		    </Option>     -->
-	    </Reader>
-	</Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/crop_wkt.xml.in b/test/data/filters/crop_wkt.xml.in
deleted file mode 100644
index b2d5929..0000000
--- a/test/data/filters/crop_wkt.xml.in
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/crop-wkt.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="polygon">
-                POLYGON ((636889.412951239268295 851528.512293258565478
-                422.7001953125,636899.14233423944097 851475.000686757150106
-                422.4697265625,636899.14233423944097 851475.000686757150106
-                422.4697265625,636928.33048324030824 851494.459452757611871
-                422.5400390625,636928.33048324030824 851494.459452757611871
-                422.5400390625,636928.33048324030824 851494.459452757611871
-                422.5400390625,636976.977398241520859 851513.918218758190051
-                424.150390625,636976.977398241520859 851513.918218758190051
-                424.150390625,637069.406536744092591 851475.000686757150106
-                438.7099609375,637132.647526245797053 851445.812537756282836
-                425.9501953125,637132.647526245797053 851445.812537756282836
-                425.9501953125,637336.964569251285866 851411.759697255445644
-                425.8203125,637336.964569251285866 851411.759697255445644
-                425.8203125,637473.175931254867464 851158.795739248627797
-                435.6298828125,637589.928527257987298 850711.244121236610226
-                420.509765625,637244.535430748714134 850511.791769731207751
-                420.7998046875,636758.066280735656619 850667.461897735483944
-                434.609375,636539.155163229792379 851056.63721774588339
-                422.6396484375,636889.412951239268295 851528.512293258565478
-                422.7001953125))
-            </Option>
-            <Option name="outside">
-                false
-            </Option>
-            <Reader type="readers.las">
-                <!-- <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/autzen-colorized-1.2-3.las
-                </Option> -->
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/crop_wkt_2d.xml.in b/test/data/filters/crop_wkt_2d.xml.in
deleted file mode 100644
index fefadbc..0000000
--- a/test/data/filters/crop_wkt_2d.xml.in
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/autzen-colorized-1.2.3-hole.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="polygon">
-                POLYGON ((636889.4129512392682955 851528.5122932585654780, 636899.1423342394409701 851475.0006867571501061, 636899.1423342394409701 851475.0006867571501061, 636928.3304832403082401 851494.4594527576118708, 636928.3304832403082401 851494.4594527576118708, 636928.3304832403082401 851494.4594527576118708, 636976.9773982415208593 851513.9182187581900507, 636976.9773982415208593 851513.9182187581900507, 637069.4065367440925911 851475.0006867571501061, 637132.6475262457970530 8 [...]
-            </Option>
-            <Option name="outside">
-                true
-            </Option>
-            <Reader type="readers.las">
-                <!-- <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/autzen-colorized-1.2-3.las
-                </Option> -->
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/crop_wkt_2d_classification.xml.in b/test/data/filters/crop_wkt_2d_classification.xml.in
deleted file mode 100644
index e1d8788..0000000
--- a/test/data/filters/crop_wkt_2d_classification.xml.in
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/crop-wkt-2d-classification.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Filter type="filters.predicate"><Option name="function">filter</Option><Option name="source">
-import numpy as np
-
-def filter(ins,outs):
-   cls = ins['Classification']
-
-   keep_classes = [1,2]
-
-   # Use the first test for our base array.
-   keep = np.equal(cls, keep_classes[0])
-
-   # For 1:n, test each predicate and join back
-   # to our existing predicate array
-   for k in range(1,len(keep_classes)):
-       t = np.equal(cls, keep_classes[k])
-       keep = keep + t
-
-   outs['Mask'] = keep
-   return True
-        </Option><Option name="module">anything</Option>
-        <Filter type="filters.crop">
-            <Option name="polygon">
-                POLYGON ((636889.4129512392682955 851528.5122932585654780, 636899.1423342394409701 851475.0006867571501061, 636899.1423342394409701 851475.0006867571501061, 636928.3304832403082401 851494.4594527576118708, 636928.3304832403082401 851494.4594527576118708, 636928.3304832403082401 851494.4594527576118708, 636976.9773982415208593 851513.9182187581900507, 636976.9773982415208593 851513.9182187581900507, 637069.4065367440925911 851475.0006867571501061, 637132.6475262457970530 8 [...]
-
-            </Option>
-            <Option name="outside">
-                false
-            </Option>
-            <Reader type="readers.las">
-                <!-- <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/autzen-colorized-1.2-3.las
-                </Option> -->
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/decimate.xml.in b/test/data/filters/decimate.xml.in
deleted file mode 100644
index 20244a5..0000000
--- a/test/data/filters/decimate.xml.in
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-  <Writer type="writers.text">
-    <Option name="delimiter">,</Option>
-    <Option name="write_header">false</Option>
-    <Option name="filename">@CMAKE_SOURCE_DIR@/test/temp/junk.txt</Option>
-    <Filter type="filters.colorization">
-      <Option name="raster">@CMAKE_SOURCE_DIR@/test/data/autzen/autzen.jpg</Option>
-      <Option name="dimensions">Red:1:1,Green:2:1,Blue:3:1</Option>
-      <Filter type="filters.decimation">
-        <Option name="step">2</Option>
-        <Option name="offset">1</Option>
-        <Reader type="readers.las">
-          <Option name="filename">@CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las</Option>
-          <Option name="spatialreference">EPSG:2993</Option>
-        </Reader>
-      </Filter>
-    </Filter>
-  </Writer>
-</Pipeline>
diff --git a/test/data/filters/ferry.xml.in b/test/data/filters/ferry.xml.in
deleted file mode 100644
index 23b67c9..0000000
--- a/test/data/filters/ferry.xml.in
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/colorized.las
-        </Option>
-        <Filter type="filters.reprojection">
-            <Option name="out_srs">
-                EPSG:4326+4326
-            </Option>
-            <Filter type="filters.ferry">
-                <Option name="dimensions">
-                    X=StatePlaneX, Y=StatePlaneY
-                </Option>
-                <Reader type="readers.las">
-                    <Option name="filename">
-                        @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                    </Option>
-                    <Option name="spatialreference">
-                        EPSG:2993
-                    </Option>
-                </Reader>
-            </Filter>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/hexbin-info.xml.in b/test/data/filters/hexbin-info.xml.in
deleted file mode 100644
index d7aa2f7..0000000
--- a/test/data/filters/hexbin-info.xml.in
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Filter type="filters.hexbin">
-        <Option name="edge_size">
-            0.0
-        </Option>
-        <Option name="threshold">
-            10
-        </Option>
-        <Option name="sample_size">
-            5000
-        </Option>
-        <Option name="x_dim">
-            readers.las.X
-        </Option>
-        <Option name="y_dim">
-            Y
-        </Option>
-        <Option name="precision">
-            4
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-        </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/filters/hexbin.xml.in b/test/data/filters/hexbin.xml.in
deleted file mode 100644
index d42660b..0000000
--- a/test/data/filters/hexbin.xml.in
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/hexbin.las
-        </Option>
-        <Filter type="filters.hexbin">
-            <Option name="edge_size">
-                0.0
-            </Option>
-            <Option name="threshold">
-                10
-            </Option>
-            <Option name="sample_size">
-                5000
-            </Option>
-            <Option name="precision">
-                4
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/merge.xml.in b/test/data/filters/merge.xml.in
deleted file mode 100644
index 66618a6..0000000
--- a/test/data/filters/merge.xml.in
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Filter type="filters.merge">
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-        </Reader>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-        </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/filters/merge2.xml.in b/test/data/filters/merge2.xml.in
deleted file mode 100644
index d8ad037..0000000
--- a/test/data/filters/merge2.xml.in
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Filter type="filters.reprojection">
-        <Option name="out_srs">EPSG:2028</Option>
-        <Filter type="filters.merge">
-            <Reader type="readers.las">
-                <Option name="spatialreference">EPSG:2027</Option>
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-            <Reader type="readers.las">
-                <Option name="spatialreference">EPSG:2027</Option>
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Filter>
-</Pipeline>
diff --git a/test/data/filters/merge3.xml.in b/test/data/filters/merge3.xml.in
deleted file mode 100644
index e35829f..0000000
--- a/test/data/filters/merge3.xml.in
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-        <Filter type="filters.merge">
-            <Reader type="readers.las">
-                <Option name="spatialreference">EPSG:2028</Option>
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-            <Reader type="readers.las">
-                <Option name="spatialreference">EPSG:2027</Option>
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/filters/pcl/example_PMF_1.json b/test/data/filters/pcl/example_PMF_1.json
deleted file mode 100644
index f70886f..0000000
--- a/test/data/filters/pcl/example_PMF_1.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "pipeline":
-    {
-        "name": "ProgressiveMorphologicalFilterExample",
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 200
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/example_PMF_2.json b/test/data/filters/pcl/example_PMF_2.json
deleted file mode 100644
index 4548743..0000000
--- a/test/data/filters/pcl/example_PMF_2.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-    "pipeline":
-    {
-        "name": "ProgressiveMorphologicalFilterAdvancedExample",
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setCellSize": 1.0,
-                "setMaxWindowSize": 200,
-                "setSlope": 1.0,
-                "setInitialDistance": 0.05,
-                "setMaxDistance": 3.0,
-                "setExponential": true
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/example_PassThrough_1.json b/test/data/filters/pcl/example_PassThrough_1.json
index 88bacaf..c6dac72 100644
--- a/test/data/filters/pcl/example_PassThrough_1.json
+++ b/test/data/filters/pcl/example_PassThrough_1.json
@@ -1,18 +1,11 @@
-{
-    "pipeline":
+[
     {
-        "name": "PassThroughExample",
-        "filters":
-        [
-            {
-                "name": "PassThrough",
-                "setFilterFieldName": "z",
-                "setFilterLimits":
-                {
-                    "min": 410.0,
-                    "max": 440.0
-                }
-            }
-        ]
+        "name": "PassThrough",
+        "setFilterFieldName": "z",
+        "setFilterLimits":
+        {
+            "min": 410.0,
+            "max": 440.0
+        }
     }
-}
+]
diff --git a/test/data/filters/pcl/example_PassThrough_2.json b/test/data/filters/pcl/example_PassThrough_2.json
index de32a5c..b26ef63 100644
--- a/test/data/filters/pcl/example_PassThrough_2.json
+++ b/test/data/filters/pcl/example_PassThrough_2.json
@@ -1,28 +1,18 @@
-{
-    "pipeline":
+[
     {
-        "name": "CombinedExample",
-        "help": "Apply passthrough filter followed by statistical outlier removal",
-        "version": 1.0,
-        "author": "Bradley J Chambers",
-        "filters":
-        [
-            {
-                "name": "PassThrough",
-                "help": "filter z values to the range [410,440]",
-                "setFilterFieldName": "z",
-                "setFilterLimits":
-                {
-                    "min": 410.0,
-                    "max": 440.0
-                }
-            },
-            {
-                "name": "StatisticalOutlierRemoval",
-                "help": "apply outlier removal",
-                "setMeanK": 8,
-                "setStddevMulThresh": 0.2
-            }
-        ]
+        "name": "PassThrough",
+        "help": "filter z values to the range [410,440]",
+        "setFilterFieldName": "z",
+        "setFilterLimits":
+        {
+            "min": 410.0,
+            "max": 440.0
+        }
+    },
+    {
+        "name": "StatisticalOutlierRemoval",
+        "help": "apply outlier removal",
+        "setMeanK": 8,
+        "setStddevMulThresh": 0.2
     }
-}
+]
diff --git a/test/data/filters/pcl/filter_APMF_1.json b/test/data/filters/pcl/filter_APMF_1.json
deleted file mode 100644
index 0d90a73..0000000
--- a/test/data/filters/pcl/filter_APMF_1.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "ApproximateProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 33,
-                "setSlope": 1.0,
-                "setMaxDistance": 2.5,
-                "setInitialDistance": 0.15,
-                "setCellSize": 1.0,
-                "setBase": 2.0,
-                "setExponential": true,
-                "setNegative": false
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_ConditionalRemoval_1.json b/test/data/filters/pcl/filter_ConditionalRemoval_1.json
index b4c0d97..7605de8 100644
--- a/test/data/filters/pcl/filter_ConditionalRemoval_1.json
+++ b/test/data/filters/pcl/filter_ConditionalRemoval_1.json
@@ -1,21 +1,15 @@
-{
-    "pipeline":
+[
     {
-        "filters":
-        [
-            {
-                "name": "NormalEstimation",
-                "setKSearch": 0.0,
-                "setRadiusSearch": 50.0
-            },
-            {
-                "name": "ConditionalRemoval",
-                "normalZ":
-                {
-                    "min": 0.0,
-                    "max": 0.087156
-                }
-            }
-        ]
+        "name": "NormalEstimation",
+        "setKSearch": 0.0,
+        "setRadiusSearch": 50.0
+    },
+    {
+        "name": "ConditionalRemoval",
+        "normalZ":
+        {
+            "min": 0.0,
+            "max": 0.087156
+        }
     }
-}
+]
diff --git a/test/data/filters/pcl/filter_ConditionalRemoval_2.json b/test/data/filters/pcl/filter_ConditionalRemoval_2.json
index dde6a94..43761c7 100644
--- a/test/data/filters/pcl/filter_ConditionalRemoval_2.json
+++ b/test/data/filters/pcl/filter_ConditionalRemoval_2.json
@@ -1,21 +1,15 @@
-{
-    "pipeline":
+[
     {
-        "filters":
-        [
-            {
-                "name": "NormalEstimation",
-                "setKSearch": 0.0,
-                "setRadiusSearch": 50.0
-            },
-            {
-                "name": "ConditionalRemoval",
-                "normalZ":
-                {
-                    "min": 0.01,
-                    "max": 0.10
-                }
-            }
-        ]
+        "name": "NormalEstimation",
+        "setKSearch": 0.0,
+        "setRadiusSearch": 50.0
+    },
+    {
+        "name": "ConditionalRemoval",
+        "normalZ":
+        {
+            "min": 0.01,
+            "max": 0.10
+        }
     }
-}
+]
diff --git a/test/data/filters/pcl/filter_GridMinimum.json b/test/data/filters/pcl/filter_GridMinimum.json
index c8aac61..f5e9a1f 100644
--- a/test/data/filters/pcl/filter_GridMinimum.json
+++ b/test/data/filters/pcl/filter_GridMinimum.json
@@ -1,12 +1,6 @@
-{
-    "pipeline":
+[
     {
-        "filters":
-        [
-            {
-                "name": "GridMinimum",
-                "setResolution": 1000.0
-            }
-        ]
+        "name": "GridMinimum",
+        "setResolution": 1000.0
     }
-}
+]
diff --git a/test/data/filters/pcl/filter_NormalEstimation_1.json b/test/data/filters/pcl/filter_NormalEstimation_1.json
index b4c0d97..7605de8 100644
--- a/test/data/filters/pcl/filter_NormalEstimation_1.json
+++ b/test/data/filters/pcl/filter_NormalEstimation_1.json
@@ -1,21 +1,15 @@
-{
-    "pipeline":
+[
     {
-        "filters":
-        [
-            {
-                "name": "NormalEstimation",
-                "setKSearch": 0.0,
-                "setRadiusSearch": 50.0
-            },
-            {
-                "name": "ConditionalRemoval",
-                "normalZ":
-                {
-                    "min": 0.0,
-                    "max": 0.087156
-                }
-            }
-        ]
+        "name": "NormalEstimation",
+        "setKSearch": 0.0,
+        "setRadiusSearch": 50.0
+    },
+    {
+        "name": "ConditionalRemoval",
+        "normalZ":
+        {
+            "min": 0.0,
+            "max": 0.087156
+        }
     }
-}
+]
diff --git a/test/data/filters/pcl/filter_NormalEstimation_2.json b/test/data/filters/pcl/filter_NormalEstimation_2.json
index 74c26bf..e3a5994 100644
--- a/test/data/filters/pcl/filter_NormalEstimation_2.json
+++ b/test/data/filters/pcl/filter_NormalEstimation_2.json
@@ -1,21 +1,15 @@
-{
-    "pipeline":
+[
     {
-        "filters":
-        [
-            {
-                "name": "NormalEstimation",
-                "setKSearch": 0.0,
-                "setRadiusSearch": 51.0
-            },
-            {
-                "name": "ConditionalRemoval",
-                "normalZ":
-                {
-                    "min": 0.0,
-                    "max": 0.087156
-                }
-            }
-        ]
+        "name": "NormalEstimation",
+        "setKSearch": 0.0,
+        "setRadiusSearch": 51.0
+    },
+    {
+        "name": "ConditionalRemoval",
+        "normalZ":
+        {
+            "min": 0.0,
+            "max": 0.087156
+        }
     }
-}
+]
diff --git a/test/data/filters/pcl/filter_PMF_1.json b/test/data/filters/pcl/filter_PMF_1.json
deleted file mode 100644
index 4544a29..0000000
--- a/test/data/filters/pcl/filter_PMF_1.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 33,
-                "setSlope": 1.0,
-                "setMaxDistance": 2.5,
-                "setInitialDistance": 0.15,
-                "setCellSize": 1.0,
-                "setBase": 2.0,
-                "setExponential": true,
-                "setNegative": false
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_PMF_2.json b/test/data/filters/pcl/filter_PMF_2.json
deleted file mode 100644
index 6f368e8..0000000
--- a/test/data/filters/pcl/filter_PMF_2.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 33,
-                "setSlope": 1.0,
-                "setMaxDistance": 2.5,
-                "setInitialDistance": 0.15,
-                "setCellSize": 3.0,
-                "setBase": 2.0,
-                "setExponential": true,
-                "setNegative": false
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_PMF_3.json b/test/data/filters/pcl/filter_PMF_3.json
deleted file mode 100644
index 2eb2115..0000000
--- a/test/data/filters/pcl/filter_PMF_3.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 50,
-                "setSlope": 1.0,
-                "setMaxDistance": 2.5,
-                "setInitialDistance": 0.15,
-                "setCellSize": 1.0,
-                "setBase": 2.0,
-                "setExponential": true,
-                "setNegative": false
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_PMF_4.json b/test/data/filters/pcl/filter_PMF_4.json
deleted file mode 100644
index 74b0244..0000000
--- a/test/data/filters/pcl/filter_PMF_4.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 33,
-                "setSlope": 0.25,
-                "setMaxDistance": 2.5,
-                "setInitialDistance": 0.15,
-                "setCellSize": 1.0,
-                "setBase": 2.0,
-                "setExponential": true,
-                "setNegative": false
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_PMF_5.json b/test/data/filters/pcl/filter_PMF_5.json
deleted file mode 100644
index effb884..0000000
--- a/test/data/filters/pcl/filter_PMF_5.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 33,
-                "setSlope": 1.0,
-                "setMaxDistance": 5,
-                "setInitialDistance": 0.15,
-                "setCellSize": 1.0,
-                "setBase": 2.0,
-                "setExponential": true,
-                "setNegative": false
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_PMF_6.json b/test/data/filters/pcl/filter_PMF_6.json
deleted file mode 100644
index c3d33a2..0000000
--- a/test/data/filters/pcl/filter_PMF_6.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 33,
-                "setSlope": 1.0,
-                "setMaxDistance": 2.5,
-                "setInitialDistance": 0.25,
-                "setCellSize": 1.0,
-                "setBase": 2.0,
-                "setExponential": true,
-                "setNegative": false
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_PMF_7.json b/test/data/filters/pcl/filter_PMF_7.json
deleted file mode 100644
index c1a330f..0000000
--- a/test/data/filters/pcl/filter_PMF_7.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 33,
-                "setSlope": 1.0,
-                "setMaxDistance": 2.5,
-                "setInitialDistance": 0.15,
-                "setCellSize": 1.0,
-                "setBase": 3.0,
-                "setExponential": true,
-                "setNegative": false
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_PMF_8.json b/test/data/filters/pcl/filter_PMF_8.json
deleted file mode 100644
index da7c954..0000000
--- a/test/data/filters/pcl/filter_PMF_8.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 33,
-                "setSlope": 1.0,
-                "setMaxDistance": 2.5,
-                "setInitialDistance": 0.15,
-                "setCellSize": 1.0,
-                "setBase": 2.0,
-                "setExponential": false,
-                "setNegative": false
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_PMF_9.json b/test/data/filters/pcl/filter_PMF_9.json
deleted file mode 100644
index c9fdd72..0000000
--- a/test/data/filters/pcl/filter_PMF_9.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "ProgressiveMorphologicalFilter",
-                "setMaxWindowSize": 33,
-                "setSlope": 1.0,
-                "setMaxDistance": 2.5,
-                "setInitialDistance": 0.15,
-                "setCellSize": 1.0,
-                "setBase": 2.0,
-                "setExponential": true,
-                "setNegative": true
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_PassThrough_1.json b/test/data/filters/pcl/filter_PassThrough_1.json
index e851c88..c6dac72 100644
--- a/test/data/filters/pcl/filter_PassThrough_1.json
+++ b/test/data/filters/pcl/filter_PassThrough_1.json
@@ -1,17 +1,11 @@
-{
-    "pipeline":
+[
     {
-        "filters":
-        [
-            {
-                "name": "PassThrough",
-                "setFilterFieldName": "z",
-                "setFilterLimits":
-                {
-                    "min": 410.0,
-                    "max": 440.0
-                }
-            }
-        ]
+        "name": "PassThrough",
+        "setFilterFieldName": "z",
+        "setFilterLimits":
+        {
+            "min": 410.0,
+            "max": 440.0
+        }
     }
-}
+]
diff --git a/test/data/filters/pcl/filter_PassThrough_2.json b/test/data/filters/pcl/filter_PassThrough_2.json
index 9bb8a17..43c96ed 100644
--- a/test/data/filters/pcl/filter_PassThrough_2.json
+++ b/test/data/filters/pcl/filter_PassThrough_2.json
@@ -1,17 +1,11 @@
-{
-    "pipeline":
+[
     {
-        "filters":
-        [
-            {
-                "name": "PassThrough",
-                "setFilterFieldName": "x",
-                "setFilterLimits":
-                {
-                    "min": 636000,
-                    "max": 637000
-                }
-            }
-        ]
+        "name": "PassThrough",
+        "setFilterFieldName": "x",
+        "setFilterLimits":
+        {
+            "min": 636000,
+            "max": 637000
+        }
     }
-}
+]
diff --git a/test/data/filters/pcl/filter_RadiusOutlierRemoval_1.json b/test/data/filters/pcl/filter_RadiusOutlierRemoval_1.json
deleted file mode 100644
index f46bb24..0000000
--- a/test/data/filters/pcl/filter_RadiusOutlierRemoval_1.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "RadiusOutlierRemoval",
-                "setMinNeighborsInRadius": 1,
-                "setRadiusSearch": 200.0
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_RadiusOutlierRemoval_2.json b/test/data/filters/pcl/filter_RadiusOutlierRemoval_2.json
deleted file mode 100644
index 1a0aa91..0000000
--- a/test/data/filters/pcl/filter_RadiusOutlierRemoval_2.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "RadiusOutlierRemoval",
-                "setMinNeighborsInRadius": 2,
-                "setRadiusSearch": 100.0
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_StatisticalOutlierRemoval_1.json b/test/data/filters/pcl/filter_StatisticalOutlierRemoval_1.json
deleted file mode 100644
index 19301e0..0000000
--- a/test/data/filters/pcl/filter_StatisticalOutlierRemoval_1.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "StatisticalOutlierRemoval",
-                "setMeanK": 2,
-                "setStddevMulThresh": 1.5
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_StatisticalOutlierRemoval_2.json b/test/data/filters/pcl/filter_StatisticalOutlierRemoval_2.json
deleted file mode 100644
index 06f5909..0000000
--- a/test/data/filters/pcl/filter_StatisticalOutlierRemoval_2.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "pipeline":
-    {
-        "filters":
-        [
-            {
-                "name": "StatisticalOutlierRemoval",
-                "setMeanK": 5,
-                "setStddevMulThresh": 0.0
-            }
-        ]
-    }
-}
diff --git a/test/data/filters/pcl/filter_VoxelGrid.json b/test/data/filters/pcl/filter_VoxelGrid.json
index 25dde95..106a1a2 100644
--- a/test/data/filters/pcl/filter_VoxelGrid.json
+++ b/test/data/filters/pcl/filter_VoxelGrid.json
@@ -1,17 +1,11 @@
-{
-    "pipeline":
+[
     {
-        "filters":
-        [
-            {
-                "name": "VoxelGrid",
-                "setLeafSize":
-                {
-                    "x": 500.0,
-                    "y": 500.0,
-                    "z": 10.0
-                }
-            }
-        ]
+        "name": "VoxelGrid",
+        "setLeafSize":
+        {
+            "x": 500.0,
+            "y": 500.0,
+            "z": 10.0
+        }
     }
-}
+]
diff --git a/test/data/filters/pcl/passthrough.xml.in b/test/data/filters/pcl/passthrough.xml.in
deleted file mode 100644
index d5d60da..0000000
--- a/test/data/filters/pcl/passthrough.xml.in
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/foo.las
-        </Option>
-	<Filter type="filters.pclblock">
-	    <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/filters/pcl/example_PassThrough_1.json
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/range_classification.xml.in b/test/data/filters/range_classification.xml.in
deleted file mode 100644
index 6e6d22b..0000000
--- a/test/data/filters/range_classification.xml.in
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="2.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/only_class_equals_2.las
-        </Option>
-        <Filter type="filters.range">
-            <Option name="limits">
-                Classification[2:2]
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
-
diff --git a/test/data/filters/range_z.xml.in b/test/data/filters/range_z.xml.in
deleted file mode 100644
index b59a6fc..0000000
--- a/test/data/filters/range_z.xml.in
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="2.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/only_z_400-500.las
-        </Option>
-        <Filter type="filters.range">
-            <Option name="limits">
-                Z[400:500]
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
-
diff --git a/test/data/filters/range_z_classification.xml.in b/test/data/filters/range_z_classification.xml.in
deleted file mode 100644
index 2c98b6e..0000000
--- a/test/data/filters/range_z_classification.xml.in
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="2.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/only_z_400-500_class_equals_2.las
-        </Option>
-        <Filter type="filters.range">
-            <Option name="limits">
-                Z[400:500], Classification[2:2]
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
-
diff --git a/test/data/filters/reproject.xml.in b/test/data/filters/reproject.xml.in
deleted file mode 100644
index a3ae3ff..0000000
--- a/test/data/filters/reproject.xml.in
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out2.las
-        </Option>
-        <Filter type="filters.predicate">
-            <Option name="function">filter</Option>
-            <Option name="source">
-import numpy as np
-
-def filter(ins,outs):
-    cls = ins['Classification']
-
-    keep_classes = [1]
-
-    # Use the first test for our base array.
-    keep = np.equal(cls, keep_classes[0])
-
-    # For 1:n, test each predicate and join back
-    # to our existing predicate array
-    for k in range(1,len(keep_classes)):
-        t = np.equal(cls, keep_classes[k])
-        keep = keep + t
-
-    outs['Mask'] = keep
-    return True
-            </Option>
-            <Option name="module">anything</Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-                <Option name="spatialreference">
-                    EPSG:2993
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/sort.xml.in b/test/data/filters/sort.xml.in
deleted file mode 100644
index 6b647f3..0000000
--- a/test/data/filters/sort.xml.in
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Filter type="filters.sort">
-        <Option name="dimension">
-            X
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-        </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/filters/splitter.xml.in b/test/data/filters/splitter.xml.in
deleted file mode 100644
index 31b7356..0000000
--- a/test/data/filters/splitter.xml.in
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/tiled.las
-        </Option>
-        <Filter type="filters.splitter">
-            <Option name="length">
-                100
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/filters/stats.xml.in b/test/data/filters/stats.xml.in
deleted file mode 100644
index 825ff49..0000000
--- a/test/data/filters/stats.xml.in
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/pdal-stats.las
-        </Option>
-        <Filter type="filters.stats">
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/gdal/grid.txt b/test/data/gdal/grid.txt
new file mode 100644
index 0000000..d89febe
--- /dev/null
+++ b/test/data/gdal/grid.txt
@@ -0,0 +1,34 @@
+X,Y,Z
+
+0, 0, 0
+.5, .5, 1
+1.5, .5, 2
+2.5, .5, 3
+3.5, .5, 4
+4.5, .5, 5
+3.5, 1, 4.5
+4.5, 1, 5.5
+.5, 1.5, 2
+1.5, 1.5, 3
+2.5, 1.5, 4
+3, 1.5, 4.5
+3.5, 1.5, 5
+4, 1.5, 5.5
+4.5, 1.5, 6
+3.5, 2, 5.5
+4.5, 2, 6.5
+.5, 2.5, 3
+1.5, 2.5, 4
+2.5, 2.5, 5
+3.5, 2.5, 6
+4.5, 2.5, 7
+.5, 3.5, 4
+2.5, 3.5, 6
+3.5, 3.5, 7
+4.5, 3.5, 8
+.5, 4.5, 5
+2.5, 4.5, 7
+3.5, 4.5, 8
+4.5, 4.6, 9.1
+4.7, 4.5, 8.9 
+4.3, 4.5, 8.9 
diff --git a/test/data/hole/crop.xml.in b/test/data/hole/crop.xml.in
deleted file mode 100644
index 574920d..0000000
--- a/test/data/hole/crop.xml.in
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/hole.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="polygon">
-                POLYGON ((-123.070497025496451 44.058766662574463,-123.065624990686828 44.058248360998967,-123.068009177934087 44.057108097532883,-123.066350612892521 44.056486135642295,-123.066661593837807 44.054101948395022,-123.070289704866255 44.054412929340323,-123.070497025496451 44.058766662574463))
-            </Option>
-            <Option name="outside">
-                true
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/hole/spurious.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/icebridge/pipeline.json.in b/test/data/icebridge/pipeline.json.in
new file mode 100644
index 0000000..9fde2c5
--- /dev/null
+++ b/test/data/icebridge/pipeline.json.in
@@ -0,0 +1,13 @@
+{
+  "pipeline":[
+    {
+        "type":"readers.icebridge",
+        "filename":"@CMAKE_SOURCE_DIR@/test/data/icebridge/twoPoints.h5"
+    },
+    {
+        "type":"writers.text",
+        "filename":"@CMAKE_SOURCE_DIR@/test/temp/outfile.txt"
+    }
+  ]
+}
+
diff --git a/test/data/icebridge/pipeline.xml.in b/test/data/icebridge/pipeline.xml.in
deleted file mode 100644
index 246019f..0000000
--- a/test/data/icebridge/pipeline.xml.in
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.text">
-        <Option name="filename">@CMAKE_SOURCE_DIR@/test/temp/outfile.txt</Option>
-        <Reader type="readers.icebridge">
-            <Option name="filename">@CMAKE_SOURCE_DIR@/test/data/icebridge/twoPoints.h5</Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/ilvis2/ilvis.xml b/test/data/ilvis2/ilvis.xml
deleted file mode 100644
index 321c625..0000000
--- a/test/data/ilvis2/ilvis.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            ilvis.las
-        </Option>
-        <Reader type="readers.ilvis2">
-            <Option name="filename">
-                ILVIS2_GL2009_0414_R1401_042504.TXT
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/io/p2g-writer.xml.in b/test/data/io/p2g-writer.xml.in
deleted file mode 100644
index 4177e48..0000000
--- a/test/data/io/p2g-writer.xml.in
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.p2g">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/autzen_grid
-        </Option>
-        <Option name="output_type">
-            min
-        </Option>
-        <Option name="output_format">
-            tif
-        </Option> -->
-	<Reader type="readers.las">
-	    <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-	    </Option>
-	</Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/io/sqlite-reader.xml.in b/test/data/io/sqlite-reader.xml.in
deleted file mode 100644
index 13fac99..0000000
--- a/test/data/io/sqlite-reader.xml.in
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/written-from-sqlite.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Reader type="readers.sqlite">
-            <Option name="query">
-                SELECT b.schema, l.cloud, l.block_id, l.num_points, l.bbox, l.extent, l.points, b.cloud
-                  FROM simple_blocks l, simple_cloud b
-                 WHERE l.cloud = b.cloud and l.cloud in (1)
-                order by l.cloud
-            </Option>
-            <Option name="connection">
-                /Users/hobu/dev/git/pdal/test/data/autzen-dd.sqlite
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/io/sqlite-writer.xml.in b/test/data/io/sqlite-writer.xml.in
deleted file mode 100644
index 35d2a55..0000000
--- a/test/data/io/sqlite-writer.xml.in
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.sqlite">
-        <Option name="connection">
-            autzen-dd.sqlite
-        </Option>
-        <Option name="cloud_table_name">
-            SIMPLE_CLOUD
-        </Option>
-        <Option name="block_table_name">
-            SIMPLE_BLOCKS
-        </Option>
-        
-        <Option name="cloud_column_name">
-            CLOUD
-        </Option>
-        <Option name="is3d">
-            false
-        </Option>
-        <Option name="overwrite">
-            true
-        </Option>
-        <Option name="srid">
-            4326
-        </Option>
-        <Option name="base_table_boundary_wkt">
-        </Option>
-        <Option name="pre_block_sql">
-        </Option>
-        <Option name="pre_sql">
-        </Option>
-        <Option name="post_block_sql">
-        </Option>
-        <Option name="capacity">
-            50
-        </Option>
-        <Option name="stream_output_precision">
-            8
-        </Option>
-        <Option name="pack_ignored_fields">
-            true
-        </Option>
-  
-        <Filter type="filters.chipper">
-        <Option name="capacity">
-            50
-        </Option>
-            <Option name="max_cache_blocks">
-                1
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/autzen/autzen-dd.las
-                </Option>
-                <Option name="spatialreference">
-                    EPSG:4326
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/io/text-writer-csv.xml.in b/test/data/io/text-writer-csv.xml.in
deleted file mode 100644
index a6036f4..0000000
--- a/test/data/io/text-writer-csv.xml.in
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.text">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/autzen-point-format-3.txt
-        </Option>
-        <Option name="order">
-            Red,Green,X,Y,Blue,Z
-        </Option>
-        <Option name="keep_unspecified">
-            false
-        </Option>        
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/autzen/autzen-point-format-3.las
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/io/text-writer-geojson.xml.in b/test/data/io/text-writer-geojson.xml.in
deleted file mode 100644
index 14e91dd..0000000
--- a/test/data/io/text-writer-geojson.xml.in
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.text">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/autzen-point-format-3.txt
-        </Option>
-        <Option name="order">
-            Red,Green,X,Y,Blue,Z
-        </Option>
-        <Option name="keep_unspecified">
-            true    
-        </Option>        
-        <Option name="format">
-            geojson
-        </Option>
-        <Option name="jscallback">
-            pointcloud_callback
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/autzen/autzen-point-format-3.las
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/las/1.2-empty-geotiff-vlrs.las b/test/data/las/1.2-empty-geotiff-vlrs.las
new file mode 100644
index 0000000..a56231c
Binary files /dev/null and b/test/data/las/1.2-empty-geotiff-vlrs.las differ
diff --git a/test/data/nitf/conversion.xml.in b/test/data/nitf/conversion.xml.in
deleted file mode 100644
index 20ef595..0000000
--- a/test/data/nitf/conversion.xml.in
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/nitf.las
-        </Option>
-            <Reader type="readers.nitf">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/nitf/autzen-utm10.ntf
-                </Option>
-            </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/nitf/las2nitf.xml.in b/test/data/nitf/las2nitf.xml.in
deleted file mode 100644
index cef669d..0000000
--- a/test/data/nitf/las2nitf.xml.in
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.nitf">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/output.ntf
-        </Option>
-        <Option name="scale_x">0.01</Option>
-        <Option name="offset_x">311898.23</Option>
-        <Option name="scale_y">0.01</Option>
-        <Option name="offset_y">4703909.84</Option>
-        <Option name="scale_z">0.01</Option>
-        <Option name="offset_z">7.385474</Option>
-        <Option name="fsclas">U</Option>
-        <Option name="idatim" type="string">20131206140713</Option>
-        <Option name="fscltx" type="string">SIC:0 CH_FO</Option>
-        <Option name="ftitle" type="string">output.ntf</Option>
-        <Option name="software_id">
-            forward
-        </Option>
-        <Option name="system_id">
-            PDAL2NTF
-        </Option>
-        <Option name="forward">
-            creation_doy, creation_year
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/utm15.las
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/nitf/reader.xml b/test/data/nitf/reader.xml
deleted file mode 100644
index b1fff9a..0000000
--- a/test/data/nitf/reader.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-            <Reader type="readers.nitf">
-                <Option name="filename">
-                    ../07015_Hamrin_Mountains_Tile_000249_nga.ntf
-                </Option>
-            </Reader>
-</Pipeline>
diff --git a/test/data/nitf/write_laz.xml.in b/test/data/nitf/write_laz.xml.in
deleted file mode 100644
index 3591384..0000000
--- a/test/data/nitf/write_laz.xml.in
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.nitf">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/pdal-compressed.ntf
-        </Option>
-        <Option name="compression">
-            true
-        </Option>
-        <Option name="fsclas">
-            U
-        </Option>
-        <Option name="ophone">
-            5159664628
-        </Option>
-        <Option name="oname">
-            Howard Butler
-        </Option>
-        <Option name="idatim">
-            20110516183337
-        </Option>
-        <Option name="ftitle">
-            This is the title
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/nitf/write_options.xml.in b/test/data/nitf/write_options.xml.in
deleted file mode 100644
index 4b4c15c..0000000
--- a/test/data/nitf/write_options.xml.in
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.nitf">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/pdal-compressed.ntf
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Option name="fsclas">
-            U
-        </Option>
-        <Option name="ophone">
-            5159664628
-        </Option>
-        <Option name="oname">
-            Howard Butler
-        </Option>
-        <Option name="idatim">
-            20110516183337
-        </Option>
-        <Option name="ftitle">
-            This is the title
-        </Option>
-        <Option name="fsctlh">
-            N
-        </Option>
-        <Option name="aimidb">
-            COUNTRY:US,ACQUISITION_DATE:Someday,MISSION_IDENTIFICATION:IDENT,LOCATION:Somewhere
-        </Option>
-        <Option name="acftb">
-            AC_MSN_ID:ID
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-                <Option name="spatialreference"> PROJCS["NAD_1983_Oregon_Statewide_Lambert_Feet_Intl",GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.017453292519943295]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["False_Easting",1312335.958005249],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-120.5],PARAMETER["Standard_Parallel_1",43.0],PARAMETER["Standard_Parall [...]
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/nitf/write_test1.ntf b/test/data/nitf/write_test1.ntf
deleted file mode 100644
index cd46470..0000000
Binary files a/test/data/nitf/write_test1.ntf and /dev/null differ
diff --git a/test/data/oracle/big-read.xml b/test/data/oracle/big-read.xml
deleted file mode 100644
index 05dc88b..0000000
--- a/test/data/oracle/big-read.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            written-from-oracle.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Option name="chunk_size">
-            80000
-        </Option>
-        <Option name="xml_schema_dump">
-            schema-document.xml
-        </Option>
-        <Reader type="readers.oci">
-            <Option name="query">
-                select l."OBJ_ID", l."BLK_ID", l."BLK_EXTENT", l."BLK_DOMAIN", l."PCBLK_MIN_RES", l."PCBLK_MAX_RES", l."NUM_POINTS", l."NUM_UNSORTED_POINTS", l."PT_SORT_DIM", l."POINTS", b.cloud from AUTZEN_COPY_CLOUD b, autzen_COPY_blocks l 
-                                where b.cloud.pc_id = l.obj_id
-                                and b.id = 1
-            </Option>
-            <Option name="connection">
-                grid/grid at localhost/vm
-            </Option>
-            <Option name="debug">
-                true
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/oracle/big-write.xml b/test/data/oracle/big-write.xml
deleted file mode 100644
index 0864b94..0000000
--- a/test/data/oracle/big-write.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.oci">
-        <Option name="connection">
-            grid/grid at localhost/vm
-        </Option>
-        <Option name="base_table_name">
-            AUTZEN_COPY_CLOUD
-        </Option>
-        <Option name="block_table_name">
-            AUTZEN_COPY_BLOCKS
-        </Option>
-        <Option name="cloud_column_name">
-            CLOUD
-        </Option>
-        <Option name="is3d">
-            false
-        </Option>
-        <Option name="solid">
-            false
-        </Option>
-        <Option name="overwrite">
-            false
-        </Option>
-        <Option name="srid">
-            4269
-        </Option>
-        <Option name="base_table_aux_columns">
-        </Option>
-        <Option name="base_table_aux_values">
-        </Option>
-        <Option name="base_table_boundary_column">
-        </Option>
-        <Option name="base_table_boundary_wkt">
-        </Option>
-        <Option name="pre_block_sql">
-        </Option>
-        <Option name="pre_sql">
-        </Option>
-        <Option name="post_block_sql">
-        </Option>
-        <Option name="capacity">
-            25000
-        </Option>
-        <Option name="stream_output_precision">
-            8
-        </Option>
-        <Option name="disable_cloud_trigger">
-            true
-        </Option>
-        <Option name="pack_ignored_fields">
-            false
-        </Option>
-
-                <Filter type="filters.chipper">
-                    <Option name="capacity">
-                        25000
-                    </Option>
-                    <Reader type="readers.las">
-                        <Option name="filename">
-                            ../local/autzen/autzen-colorized-1.2.3-hole.laz
-                        </Option>
-                        <Option name="spatialreference">
-                            EPSG:2926
-                        </Option>
-                    </Reader>
-            </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/oracle/qfit-read.xml b/test/data/oracle/qfit-read.xml
deleted file mode 100644
index 983c88d..0000000
--- a/test/data/oracle/qfit-read.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Reader type="readers.oci">
-        <Option name="query">
-            SELECT CLOUD FROM QFIT_CLOUD where ID=1
-        </Option>
-        <Option name="connection">
-            lidar/lidar at oracle.hobu.biz/lidar
-        </Option>
-        <Option name="debug">
-            true
-        </Option>
-    </Reader>
-</Pipeline>
diff --git a/test/data/oracle/qfit-write.xml b/test/data/oracle/qfit-write.xml
deleted file mode 100644
index 2ad67bc..0000000
--- a/test/data/oracle/qfit-write.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.oci">
-        <Option name="connection">
-            lidar/lidar at oracle.hobu.biz/lidar
-        </Option>
-        <Option name="base_table_name">
-            QFIT_CLOUD
-        </Option>
-        <Option name="block_table_name">
-            QFIT_BLOCKS
-        </Option>
-        <Option name="cloud_column_name">
-            CLOUD
-        </Option>
-        <Option name="is3d">
-            false
-        </Option>
-        <Option name="solid">
-            false
-        </Option>
-        <Option name="overwrite">
-            false
-        </Option>
-        <Option name="srid">
-            4269
-        </Option>
-        <Option name="base_table_aux_columns">
-        </Option>
-        <Option name="base_table_aux_values">
-        </Option>
-        <Option name="base_table_boundary_column">
-        </Option>
-        <Option name="base_table_boundary_wkt">
-        </Option>
-        <Option name="pre_block_sql">
-        </Option>
-        <Option name="pre_sql">
-        </Option>
-        <Option name="post_block_sql">
-        </Option>
-        <Option name="capacity">
-            50
-        </Option>
-        <Option name="stream_output_precision">
-            8
-        </Option>
-  
-        <Filter type="filters.chipper">
-            <Option name="capacity">
-                50
-            </Option>
-	    <Reader type="readers.qfit">
-		<Option name="filename">
-		    ../qfit/14-word.qi
-		</Option>
-		<Option name="flip_coordinates">
-		    true
-		</Option>
-		<Option name="convert_z_units">
-		    true
-		</Option>
-		<Option name="spatialreference">
-		    EPSG:4269+3855
-		</Option>
-	    </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/oracle/read-colorize.xml b/test/data/oracle/read-colorize.xml
deleted file mode 100644
index 48a1ba4..0000000
--- a/test/data/oracle/read-colorize.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            written-from-oracle.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Filter type="filters.colorization">
-            <Option name="dimension">
-                Red
-                <Options>
-                    <Option name="band">
-                        0
-                    </Option>
-                    <Option name="scale">
-                        1.0
-                    </Option>
-                </Options>
-            </Option>
-            <Option name="dimension">
-                Green
-                <Options>
-                    <Option name="band">
-                        0
-                    </Option>
-                    <Option name="scale">
-                        1.0
-                    </Option>
-                </Options>
-            </Option>
-            <Option name="dimension">
-                Blue
-                <Options>
-                    <Option name="band">
-                        0
-                    </Option>
-                    <Option name="scale">
-                        256
-                    </Option>
-                </Options>
-            </Option>
-            <Option name="raster">
-                ../autzen.jpg
-            </Option>
-            <Reader type="readers.oci">
-                <Option name="query">
-                    SELECT CLOUD FROM SIMPLE_CLOUD
-                </Option>
-                <Option name="xml_schema_dump">
-                    oracle-schema.xml
-                </Option>
-                <Option name="connection">
-                    lidar/lidar at lidar.hobu.biz/lidar
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/oracle/read.xml b/test/data/oracle/read.xml
deleted file mode 100644
index a375eb7..0000000
--- a/test/data/oracle/read.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            written-from-oracle.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Reader type="readers.oci">
-            <Option name="query">
-                            
-                SELECT l."OBJ_ID", l."BLK_ID", l."BLK_EXTENT", l."BLK_DOMAIN",
-                       l."PCBLK_MIN_RES", l."PCBLK_MAX_RES", l."NUM_POINTS",
-                       l."NUM_UNSORTED_POINTS", l."PT_SORT_DIM", l."POINTS", b.cloud
-                  FROM SIMPLE_BLOCKS l, SIMPLE_CLOUD b
-                 WHERE l.obj_id = b.id and l.obj_id in (1)
-                order by l.obj_id
-            </Option>
-            <Option name="xml_schema_dump">
-                flipped-dimensions.xml
-            </Option>
-            <Option name="connection">
-                grid/grid at localhost/vm
-            </Option>
-            
-            <!-- <Option name="populate_pointsourceid">
-                true
-            </Option>       -->      
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/oracle/view-read.xml b/test/data/oracle/view-read.xml
deleted file mode 100644
index 693b92e..0000000
--- a/test/data/oracle/view-read.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            written-from-oracle.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-	<Filter type="filters.crop">
-	    <Option name="outside">false</Option>
-	    <Option name="y_dim">readers.oci.Y</Option>
-	    <Option name="z_dim">readers.oci.Z</Option>
-	    <Option name="polygon">POLYGON ((-123.070314549836326 44.057089038234523 128.839019531250017,-123.070271177640294 44.056943121718128 128.768772656250007,-123.070271177640294 44.056943121718128 128.768772656250007,-123.070162496376781 44.056998992754814 128.790203906250014,-123.070162496376781 44.056998992754814 128.790203906250014,-123.070162496376781 44.056998992754814 128.790203906250014,-123.069979815780442 44.057056534650307 129.281039062500014,-123.069979815780442 44.0570565346 [...]
-	    <Option name="x_dim">readers.oci.X</Option>                    
-	    <Reader type="readers.oci">
-		<Option name="query">
-		    
-SELECT l."OBJ_ID", l."BLK_ID", l."BLK_EXTENT", l."BLK_DOMAIN",
-l."PCBLK_MIN_RES", l."PCBLK_MAX_RES", l."NUM_POINTS",
-l."NUM_UNSORTED_POINTS", l."PT_SORT_DIM", l."POINTS", b.cloud
-FROM SIMPLE_BLOCKS l, SIMPLE_CLOUD b
-WHERE l.obj_id = b.id and l.obj_id in (1,2)
-order by l.obj_id
-		</Option>
-		<Option name="connection">
-		    grid/grid at localhost/vm
-		</Option>
-		<Option name="debug">
-		    true
-		</Option>
-		<Option name="populate_pointsourceid">
-		    true
-		</Option>
-	     
-	    </Reader>
-	</Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/oracle/write.xml b/test/data/oracle/write.xml
deleted file mode 100644
index cd72bee..0000000
--- a/test/data/oracle/write.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.oci">
-        <Option name="connection">
-            grid/grid at localhost/vm
-        </Option>
-        <Option name="base_table_name">
-            SIMPLE_CLOUD
-        </Option>
-        <Option name="block_table_name">
-            SIMPLE_BLOCKS
-        </Option>
-        <Option name="store_dimensional_orientation">true</Option>
-        
-        <Option name="cloud_column_name">
-            CLOUD
-        </Option>
-        <Option name="is3d">
-            false
-        </Option>
-        <Option name="solid">
-            false
-        </Option>
-        <Option name="overwrite">
-            false
-        </Option>
-        <Option name="disable_cloud_trigger">
-            true
-        </Option>
-        <Option name="srid">
-            4269
-        </Option>
-        <Option name="base_table_aux_columns">
-        </Option>
-        <Option name="base_table_aux_values">
-        </Option>
-        <Option name="base_table_boundary_column">
-        </Option>
-        <Option name="base_table_boundary_wkt">
-        </Option>
-        <Option name="pre_block_sql">
-        </Option>
-        <Option name="pre_sql">
-        </Option>
-        <Option name="post_block_sql">
-        </Option>
-        <Option name="capacity">
-            50
-        </Option>
-        <Option name="stream_output_precision">
-            8
-        </Option>
-        <Option name="pack_ignored_fields">
-            true
-        </Option>
-        <Filter type="filters.stats">
-           <Option name="dimensions">readers.las.X, readers.las.Y, readers.las.Z, Classification</Option>        
-                <Filter type="filters.chipper">
-                    <Option name="capacity">
-                        50
-                    </Option>
-		    <Reader type="readers.las">
-			    <Option name="filename">
-				../autzen-dd-pf0.las
-			    </Option>
-			    <Option name="spatialreference">
-				EPSG:4326
-			    </Option>
-			    <!-- <Option name="log">
-				oracle-pipeline-write.log
-			    </Option>     -->
-		    </Reader>
-            </Filter>            
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/bad/pipeline_bad01.xml b/test/data/pipeline/bad/pipeline_bad01.xml
deleted file mode 100644
index 9c092bc..0000000
--- a/test/data/pipeline/bad/pipeline_bad01.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer>
-        <!-- bad: no Type element -->
-        <!-- <Type>writers.las</Type> -->
-        <Option name="filename">
-            out.las
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    ../las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/bad/pipeline_bad02.xml b/test/data/pipeline/bad/pipeline_bad02.xml
deleted file mode 100644
index f647911..0000000
--- a/test/data/pipeline/bad/pipeline_bad02.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            out.las
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <!-- bad: no child of Filter -->
-            <!--<Reader type="readers.las">
-                <Option name="filename">
-                    1.2-with-color.las
-                </Option>
-            </Reader>-->
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/bad/pipeline_bad03.xml b/test/data/pipeline/bad/pipeline_bad03.xml
deleted file mode 100644
index 4527ca2..0000000
--- a/test/data/pipeline/bad/pipeline_bad03.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            out.las
-        </Option>
-        <MultiFilter type="filters.mosaic">
-            <!-- bad: no child of MultiFilter -->
-            <!--<Reader type="readers.las">
-                <Option name="filename">
-					1.2-with-color.las
-                </Option>
-            </Reader>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    1.2-with-color.las
-                </Option>
-            </Reader>-->
-        </MultiFilter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/bad/pipeline_bad04.xml b/test/data/pipeline/bad/pipeline_bad04.xml
deleted file mode 100644
index 6bb0a17..0000000
--- a/test/data/pipeline/bad/pipeline_bad04.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            out.las
-        </Option>
-        <!-- bad: no child of Writer -->
-        <!--<Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>-->
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/bad/pipeline_bad05.xml b/test/data/pipeline/bad/pipeline_bad05.xml
deleted file mode 100644
index f538245..0000000
--- a/test/data/pipeline/bad/pipeline_bad05.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            out.las
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    ../las/1.2-with-color.las
-                </Option>
-            </Reader>
-            <!-- bad: extra child of Filter -->
-            <Reader type="readers.las">
-                <Option name="filename">
-                    ../las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/bad/pipeline_bad06.xml b/test/data/pipeline/bad/pipeline_bad06.xml
deleted file mode 100644
index 0cd5e03..0000000
--- a/test/data/pipeline/bad/pipeline_bad06.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            out.las
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    ../las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-        <!-- bad: extra child of Writer -->
-        <Reader type="readers.las">
-            <Option name="filename">
-                ../las/1.2-with-color.las
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/bad/pipeline_bad07.xml b/test/data/pipeline/bad/pipeline_bad07.xml
deleted file mode 100644
index b482729..0000000
--- a/test/data/pipeline/bad/pipeline_bad07.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            out.las
-         </Option>
-        <Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    las/1.2-with-color.las
-                </Option>
-                <!-- bad: child of reader -->
-                <Reader type="readers.las">
-                    <Option name="filename">
-                        ../las/1.2-with-color.las
-                    </Option>
-                </Reader>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/bad/pipeline_bad08.xml b/test/data/pipeline/bad/pipeline_bad08.xml
deleted file mode 100644
index 8b18877..0000000
--- a/test/data/pipeline/bad/pipeline_bad08.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            out.las
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <!-- bad: unknown element type -->
-            <Reafdsadsafdsafdsafder type="readers.las">
-                <Option name="filename">
-                    ../las/1.2-with-color.las
-                </Option>
-            </Reafdsadsafdsafdsafder>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/bad/pipeline_bad09.xml b/test/data/pipeline/bad/pipeline_bad09.xml
deleted file mode 100644
index 9cf91c7..0000000
--- a/test/data/pipeline/bad/pipeline_bad09.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- bad: root element not Pipeline -->
-<!--<Pipeline version="1.0">-->
-    <Writer type="writers.las">
-        <Option name="filename">
-            out.las
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    ../las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-<!--</Pipeline>-->
diff --git a/test/data/pipeline/bad/pipeline_bad10.xml b/test/data/pipeline/bad/pipeline_bad10.xml
deleted file mode 100644
index 37f1aca..0000000
--- a/test/data/pipeline/bad/pipeline_bad10.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- bad: root element not Pipeline -->
-<!--<Pipeline version="1.0">-->
-        <Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    ../1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-<!--</Pipeline>-->
diff --git a/test/data/pipeline/drop_color.xml.in b/test/data/pipeline/drop_color.xml.in
deleted file mode 100644
index a8c429c..0000000
--- a/test/data/pipeline/drop_color.xml.in
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/pdal-compressed.laz
-        </Option>
-        <Option name="compression">
-            true
-        </Option>
-	<Filter type="filters.crop">
-	    <Option name="bounds">
-		([0,1000000],[0,1000000],[0,1000000])
-	    </Option>
-	    <Reader type="readers.las">
-		<Option name="filename">
-		    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-		</Option>
-	    </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/glob.json.in b/test/data/pipeline/glob.json.in
new file mode 100644
index 0000000..6458ac2
--- /dev/null
+++ b/test/data/pipeline/glob.json.in
@@ -0,0 +1,7 @@
+{
+  "pipeline": [
+    "@CMAKE_SOURCE_DIR@/test/data/autzen/thin*.las",
+    "@CMAKE_SOURCE_DIR@/test/temp/globbed.las"
+  ]
+}
+
diff --git a/test/data/pipeline/issue1417.json.in b/test/data/pipeline/issue1417.json.in
new file mode 100644
index 0000000..b25abbb
--- /dev/null
+++ b/test/data/pipeline/issue1417.json.in
@@ -0,0 +1,9 @@
+{
+  "pipeline":[
+    {
+    "filename" : "badfile.reallybad",
+    "type" : "readers.las"
+    },
+    "@CMAKE_SOURCE_DIR@/test/temp/out.las"
+  ]
+}
diff --git a/test/data/pipeline/pipeline_interpolate.xml.in b/test/data/pipeline/pipeline_interpolate.xml.in
deleted file mode 100644
index c9a7ec1..0000000
--- a/test/data/pipeline/pipeline_interpolate.xml.in
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.p2g">
-        <Option name="grid_dist_x">
-            6.0
-        </Option>
-        <Option name="grid_dist_y">
-            6.0
-        </Option>
-        <Option name="radius">
-            8.4852813742385713
-        </Option>
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/autzen_grid
-        </Option>
-        <Option name="output_type">
-            min
-        </Option>
-        <Option name="output_type">
-            max
-        </Option>
-        <Option name="output_type">
-            mean
-        </Option>
-        <Option name="output_type">
-            idw
-        </Option>
-        <Option name="output_type">
-            den
-        </Option>
-        <!-- <Option name="output_type">
-            all
-        </Option> -->
-        <Option name="output_format">
-            asc
-        </Option> -->
-        <!-- <Option name="output_format">
-            grid
-        </Option>     -->
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-            <Option name="spatialreference">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las.wkt
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_metadata_reader.xml.in b/test/data/pipeline/pipeline_metadata_reader.xml.in
deleted file mode 100644
index f2f9a1c..0000000
--- a/test/data/pipeline/pipeline_metadata_reader.xml.in
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Reader type="readers.las">
-        <Option name="filename">
-	    @CMAKE_SOURCE_DIR@/test/data/las/interesting.las
-        </Option>
-    </Reader>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_metadata_writer.xml.in b/test/data/pipeline/pipeline_metadata_writer.xml.in
deleted file mode 100644
index 5c23412..0000000
--- a/test/data/pipeline/pipeline_metadata_writer.xml.in
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/metadata-output.las
-        </Option>
-        <Option name="forward">software_id,creation_doy,creation_year,vlr</Option>
-        <Option name="system_id">SOMEVALUE</Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/interesting.las
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_mississippi.xml.in b/test/data/pipeline/pipeline_mississippi.xml.in
deleted file mode 100644
index ff1f4aa..0000000
--- a/test/data/pipeline/pipeline_mississippi.xml.in
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This file is Mississippi data that has different vertical units (ft) -->
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out2.las
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/mvk-thin.las
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_mississippi_reverse.xml.in b/test/data/pipeline/pipeline_mississippi_reverse.xml.in
deleted file mode 100644
index 0aa10fe..0000000
--- a/test/data/pipeline/pipeline_mississippi_reverse.xml.in
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This file is Mississippi data that has different vertical units (ft) -->
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out3.las
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/temp/out2.las
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_multioptions.xml.in b/test/data/pipeline/pipeline_multioptions.xml.in
deleted file mode 100644
index 5675303..0000000
--- a/test/data/pipeline/pipeline_multioptions.xml.in
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Filter type="filters.crop">
-        <Option name="bounds">
-            ([0,1000000],[0,1000000],[0,1000000])
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                <Options>
-                    <Option name="somemore">
-                        42
-                        <Options>
-                            <Option name="evensomemore">44</Option>
-                        </Options>
-                    </Option>
-                </Options>
-            </Option>
-        </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_read.xml.in b/test/data/pipeline/pipeline_read.xml.in
deleted file mode 100644
index c410b1b..0000000
--- a/test/data/pipeline/pipeline_read.xml.in
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Filter type="filters.crop">
-        <Option name="bounds">
-            ([0,1000000],[0,1000000],[0,1000000])
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-        </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_read_notype.xml.in b/test/data/pipeline/pipeline_read_notype.xml.in
deleted file mode 100644
index 24b3b6c..0000000
--- a/test/data/pipeline/pipeline_read_notype.xml.in
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Filter type="filters.crop">
-        <Option name="bounds">
-            ([0,1000000],[0,1000000],[0,1000000])
-        </Option>
-        <Reader>
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-        </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_readcomments.xml.in b/test/data/pipeline/pipeline_readcomments.xml.in
deleted file mode 100644
index 139b25c..0000000
--- a/test/data/pipeline/pipeline_readcomments.xml.in
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Filter type="filters.crop">
-        <Option name="bounds">
-            ([0,1000000],[0,1000000],[0,1000000])
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-            <!-- This is a comment -->
-        </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_write.xml.in b/test/data/pipeline/pipeline_write.xml.in
deleted file mode 100644
index ab83164..0000000
--- a/test/data/pipeline/pipeline_write.xml.in
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/pdal-compressed.laz
-        </Option>
-        <Option name="compression">
-            true
-        </Option>
-        <Option name="format">
-            2
-        </Option>        
-        <Filter type="filters.stats">
-            <Option name="dimensions">readers.las.X, readers.las.Y, readers.las.Z, Classification</Option>
-            <Filter type="filters.crop">
-                <Option name="bounds">
-                    ([0,1000000],[0,1000000],[0,1000000])
-                </Option>
-                <Reader type="readers.las">
-                    <Option name="filename">
-                        @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                    </Option>
-                </Reader>
-            </Filter>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_write2.xml.in b/test/data/pipeline/pipeline_write2.xml.in
deleted file mode 100644
index a3f4eff..0000000
--- a/test/data/pipeline/pipeline_write2.xml.in
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out2.las
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/utm15.las
-            </Option>              
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/pipeline_writecomments.xml.in b/test/data/pipeline/pipeline_writecomments.xml.in
deleted file mode 100644
index c92ea01..0000000
--- a/test/data/pipeline/pipeline_writecomments.xml.in
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out.las
-        </Option>
-        <Filter type="filters.crop">
-            <Option name="bounds">
-                ([0,1000000],[0,1000000],[0,1000000])
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-    <!-- This is a comment -->
-</Pipeline>
diff --git a/test/data/pipeline/tags.json.in b/test/data/pipeline/tags.json.in
new file mode 100644
index 0000000..a22b4e0
--- /dev/null
+++ b/test/data/pipeline/tags.json.in
@@ -0,0 +1,22 @@
+{
+  "pipeline": [
+    {
+      "filename": "@CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las",
+      "tag": "A"
+    },
+    {
+      "filename": "@CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las",
+      "tag": "B"
+    },
+    {
+      "type":"writers.las",
+      "filename": "out1.las",
+      "inputs": ["A", "B"]
+    },
+    {
+      "type":"writers.las",
+      "filename": "out2.las",
+      "inputs": "A"
+    }
+  ]
+}
diff --git a/test/data/pipeline/tindex.xml b/test/data/pipeline/tindex.xml
deleted file mode 100644
index c55b553..0000000
--- a/test/data/pipeline/tindex.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            out.las
-        </Option>
-        <Reader type="readers.tindex">
-            <Option name="sql">
-                SELECT * from pdal
-            </Option>
-           <Option name="filter_srs">
-                +proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5 +x_0=399999.9999999999 +y_0=0 +ellps=GRS80 +units=ft +no_defs
-            </Option>
-           <Option name="merge">
-               true
-            </Option>
-            <Option name="filename">
-                /Users/hobu/dev/git/pdal/test/temp/index.sqlite
-            </Option>
-            <Option name="where">
-                location LIKE '%nteresting.las%'
-            </Option>
-            <Option name="boundary">
-                ([635629.85, 638982.55], [848999.70 , 853535.43])
-            </Option>
-            <Option name="polygon">
-                POLYGON ((635629.85000000 848999.70000000, 635629.85000000 853535.43000000, 638982.55000000 853535.43000000, 638982.55000000 848999.70000000, 635629.85000000 848999.70000000))
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/pipeline/tindex.xml.in b/test/data/pipeline/tindex.xml.in
deleted file mode 100644
index 050fc97..0000000
--- a/test/data/pipeline/tindex.xml.in
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            out.las
-        </Option>
-        <Reader type="readers.tindex">
-            <Option name="sql">
-                SELECT * from pdal
-            </Option>
-           <Option name="filter_srs">
-                +proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5 +x_0=399999.9999999999 +y_0=0 +ellps=GRS80 +units=ft +no_defs
-            </Option>
-           <Option name="merge">
-               true
-            </Option>
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/temp/index.sqlite
-            </Option>
-            <Option name="where">
-                location LIKE '%nteresting.las%'
-            </Option>
-            <Option name="boundary">
-                ([635629.85, 638982.55], [848999.70 , 853535.43])
-            </Option>
-            <Option name="polygon">
-                POLYGON ((635629.85000000 848999.70000000, 635629.85000000 853535.43000000, 638982.55000000 853535.43000000, 638982.55000000 848999.70000000, 635629.85000000 848999.70000000))
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/plang/from-module.xml.in b/test/data/plang/from-module.xml.in
deleted file mode 100644
index 9829945..0000000
--- a/test/data/plang/from-module.xml.in
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Filter type="filters.predicate">
-        <Option name="script">@CMAKE_SOURCE_DIR@/test/data/plang/test1.py</Option>
-        <Option name="module">anything</Option>
-        <Option name="function">fff</Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-        </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/plang/predicate-embed.xml.in b/test/data/plang/predicate-embed.xml.in
deleted file mode 100644
index faa48bb..0000000
--- a/test/data/plang/predicate-embed.xml.in
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Filter type="filters.predicate">
-        <Option name="module">anything</Option>
-        <Option name="function">fff</Option>
-        <Option name="source">
-import numpy as np
-
-def fff(ins,outs):
-    X = ins['X']
-    Result = np.equal(X, 637501.67)
-    #print X
-    #print Mask
-    outs['Mask'] = Result
-    return True
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/las/1.2-with-color.las
-            </Option>
-        </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/plang/predicate-keep-ground-and-unclass.xml.in b/test/data/plang/predicate-keep-ground-and-unclass.xml.in
deleted file mode 100644
index 0a5560c..0000000
--- a/test/data/plang/predicate-keep-ground-and-unclass.xml.in
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out2.las
-        </Option>    
-        <Filter type="filters.predicate">
-            <Option name="module">anything</Option>
-            <Option name="function">filter</Option>
-            <Option name="source">
-import numpy as np
-
-def filter(ins,outs):
-    cls = ins['Classification']
-    
-    keep_classes = [1,2]
-    
-    # Use the first test for our base array.
-    keep = np.equal(cls, keep_classes[0])
-    
-    # For 1:n, test each predicate and join back 
-    # to our existing predicate array
-    for k in range(1,len(keep_classes)):
-        t = np.equal(cls, keep_classes[k])
-        keep = keep + t
-
-    outs['Mask'] = keep
-    return True
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/autzen/autzen.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/plang/predicate-keep-last-return.xml.in b/test/data/plang/predicate-keep-last-return.xml.in
deleted file mode 100644
index 131348e..0000000
--- a/test/data/plang/predicate-keep-last-return.xml.in
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out2.las
-        </Option>
-        <Filter type="filters.predicate">
-            <Option name="module">anything</Option>
-            <Option name="function">filter</Option>
-            <Option name="source">
-import numpy as np
-
-def filter(ins,outs):
-    ret = ins['ReturnNumber']
-    ret_no = ins['NumberOfReturns']
-
-    # Use the first test for our base array.
-    rets = np.equal(ret, ret_no)
-
-    outs['Mask'] = rets
-    return True
-            </Option>
-            <Filter type="filters.stats">
-                <Reader type="readers.las">
-                    <Option name="filename">
-                        @CMAKE_SOURCE_DIR@/test/data/autzen/autzen.las
-                    </Option>
-                </Reader>
-            </Filter>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/plang/predicate-keep-specified-returns.xml.in b/test/data/plang/predicate-keep-specified-returns.xml.in
deleted file mode 100644
index f5c8f7c..0000000
--- a/test/data/plang/predicate-keep-specified-returns.xml.in
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out2.las
-        </Option>
-        <Filter type="filters.predicate">
-            <Option name="module">anything</Option>
-            <Option name="function">filter</Option>
-            <Option name="source">
-import numpy as np
-
-def filter(ins,outs):
-    ret = ins['ReturnNumber']
-
-    keep_ret = [0, 1,2]
-
-    # Use the first test for our base array.
-    keep = np.equal(ret, keep_ret[0])
-
-    # For 1:n, test each predicate and join back
-    # to our existing predicate array
-    for k in range(1, len(keep_ret)):
-        t = np.equal(ret, keep_ret[k])
-        keep = keep + t
-
-    outs['Mask'] = keep
-    return True
-            </Option>
-            <Reader type="readers.las">
-                <Option name="filename">
-                    @CMAKE_SOURCE_DIR@/test/data/autzen/autzen.las
-                </Option>
-            </Reader>
-        </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/plang/programmable-update-classifications.xml.in b/test/data/plang/programmable-update-classifications.xml.in
deleted file mode 100644
index f2fb780..0000000
--- a/test/data/plang/programmable-update-classifications.xml.in
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">output.las</Option>
-    <Filter type="filters.programmable">
-        <Option name="function">myfunc</Option>
-        <Option name="module">derive</Option>
-        <Option name="source">
-import numpy as np
-def myfunc(ins,outs):
-    kls = ins['Classification']
-    class_map = {2:2, 3:1, 4:1, 5:1, 1:0}
-
-    for value in np.nditer(kls, op_flags=['readwrite']):
-        try:
-            new_value = class_map[int(value)]
-        except KeyError:
-            pass # if we didn't define the key, do nothing
-
-        # see http://docs.scipy.org/doc/numpy/reference/arrays.nditer.html#modifying-array-values
-        value[...] = new_value
-
-    outs['Classification'] = kls
-    return True
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/autzen/autzen-utm.las
-            </Option>
-        </Reader>
-    </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/plang/programmable-update-y-dims.xml.in b/test/data/plang/programmable-update-y-dims.xml.in
deleted file mode 100644
index 5b92a5a..0000000
--- a/test/data/plang/programmable-update-y-dims.xml.in
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Filter type="filters.programmable">
-        <Option name="function">myfunc</Option>
-        <Option name="module">derive</Option>
-        <Option name="source">
-import numpy as np
-def myfunc(ins,outs):
-    X = ins['Y']
-    X1 = np.zeros(X.size, dtype=type(X[0])) + 314
-    outs['Y'] = X1
-    return True
-        </Option>
-        <Reader type="readers.las">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/autzen/autzen-utm.las
-            </Option>
-        </Reader>
-    </Filter>
-</Pipeline>
diff --git a/test/data/qfit/conversion.xml.in b/test/data/qfit/conversion.xml.in
deleted file mode 100644
index aac14bb..0000000
--- a/test/data/qfit/conversion.xml.in
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out2.las
-        </Option>
-        <Option name="debug">
-            true
-        </Option>
-        <Reader type="readers.qfit">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/qfit/14-word.qi
-            </Option>
-            <Option name="flip_coordinates">
-                true
-            </Option>
-            <Option name="scale_z">
-                0.001
-            </Option>
-            <Option name="spatialreference">
-                EPSG:4269+3855
-            </Option>
-            <Option name="log">
-                stdlog
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/qfit/little-endian-conversion.xml.in b/test/data/qfit/little-endian-conversion.xml.in
deleted file mode 100644
index b8e0a17..0000000
--- a/test/data/qfit/little-endian-conversion.xml.in
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/temp/out2.las
-        </Option>
-        <Option name="debug">
-            true
-        </Option>
-        <Reader type="readers.qfit">
-            <Option name="filename">
-                @CMAKE_SOURCE_DIR@/test/data/qfit/20101120_000244.ATM4BT2.qi
-            </Option>
-            <Option name="flip_coordinates">
-                true
-            </Option>
-            <Option name="scale_z">
-                0.001
-            </Option>
-            <Option name="spatialreference">
-                EPSG:4269+3855
-            </Option>
-            <Option name="log">
-                stdlog
-            </Option>
-            <Option name="little_endian">
-                true
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/qfit/pipeline.xml.in b/test/data/qfit/pipeline.xml.in
deleted file mode 100644
index f815262..0000000
--- a/test/data/qfit/pipeline.xml.in
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">@CMAKE_SOURCE_DIR@/test/temp/qfit-foo.las</Option>
-        <Reader type="readers.qfit">
-            <Option name="flip_coordinates">true</Option>
-            <Option name="scale_z">0.001</Option>
-            <Option name="spatialreference">EPSG:4919+3855</Option>
-            <Option name="filename">@CMAKE_SOURCE_DIR@/test/data/qfit/20100515_152839.atm4bT2.qi</Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/qfit/reader.xml.in b/test/data/qfit/reader.xml.in
deleted file mode 100644
index e2b7b85..0000000
--- a/test/data/qfit/reader.xml.in
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Reader type="readers.qfit">
-        <Option name="filename">
-            @CMAKE_SOURCE_DIR@/test/data/qfit/14-word.qi
-        </Option>
-        <Option name="flip_coordinates">
-            true
-        </Option>
-        <Option name="convert_z_units">
-            true
-        </Option>
-    </Reader>
-</Pipeline>
diff --git a/test/data/sbet/pipeline.xml.in b/test/data/sbet/pipeline.xml.in
deleted file mode 100644
index 67de0fb..0000000
--- a/test/data/sbet/pipeline.xml.in
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0"?>
-<Pipeline version="1.0">
-    <Writer type="writers.text">
-        <Option name="filename">@CMAKE_SOURCE_DIR@/test/temp/outfile.txt</Option>
-        <Reader type="readers.sbet">
-            <Option name="filename">@CMAKE_SOURCE_DIR@/test/data/sbet/2-points.sbet</Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/soci/read-cloud.xml b/test/data/soci/read-cloud.xml
deleted file mode 100644
index f318a83..0000000
--- a/test/data/soci/read-cloud.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            written-from-oracle.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Reader type="readers.soci">
-            <Option name="query">
-                select  cloud.cloud_id, 
-                        cloud.schema, 
-                        cloud.block_table,
-                        ST_AsText(cloud.extent) as extent,
-                        ST_SRID(cloud.extent) as srid
-                from 
-                    cloud
-                where 
-                    cloud.cloud_id in (1,2)
-            </Option>
-            <Option name="type">
-                postgresql
-            </Option>
-            <Option name="xml_schema_dump">
-                oracle-schema.xml
-            </Option>
-            <Option name="connection">
-                host='localhost' dbname='lidar' user='hobu'
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/soci/read.xml b/test/data/soci/read.xml
deleted file mode 100644
index 3ce8746..0000000
--- a/test/data/soci/read.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.las">
-        <Option name="filename">
-            written-from-oracle.las
-        </Option>
-        <Option name="compression">
-            false
-        </Option>
-        <Reader type="readers.soci">
-            <Option name="query">
-                select  cloud.cloud_id, 
-                        cloud.schema, 
-                        cloud.block_table, 
-                        blocks.cloud_id, 
-                        blocks.block_id, 
-                        blocks.num_points,
-                        blocks.points,
-                        ST_AsText(cloud.extent) as extent,
-                        ST_SRID(cloud.extent) as srid
-                from 
-                    cloud, blocks 
-                where 
-                    blocks.cloud_id = 1 and
-                    blocks.cloud_id = cloud.cloud_id
-            </Option>
-            <Option name="type">
-                postgresql
-            </Option>
-            <Option name="xml_schema_dump">
-                oracle-schema.xml
-            </Option>
-            <Option name="connection">
-                host='localhost' dbname='lidar' user='hobu'
-            </Option>
-        </Reader>
-    </Writer>
-</Pipeline>
diff --git a/test/data/soci/sthelens-write.xml b/test/data/soci/sthelens-write.xml
deleted file mode 100644
index a6570a7..0000000
--- a/test/data/soci/sthelens-write.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.soci">
-        <Option name="connection">
-            host='localhost' dbname='lidar' user='hobu'
-        </Option>
-        <Option name="type">
-            postgresql
-        </Option>        
-        <Option name="cloud_table">
-            STHELENS_CLOUD
-        </Option>
-        <Option name="block_table">
-            STHELENS_BLOCK
-        </Option>
-        <Option name="kdtreeify">
-            true
-        </Option>        
-        <Option name="cloud_column">
-            CLOUD_ID
-        </Option>
-        <Option name="overwrite">
-            false
-        </Option>
-        <Option name="srid">
-            26910
-        </Option>
-        <Option name="capacity">
-            131072
-        </Option>
-        <Option name="is3d">
-            true
-        </Option>        
-                <Filter type="filters.chipper">
-                    <Option name="capacity">
-                        131072
-                    </Option>
-		    <Reader type="readers.las">
-			    <Option name="filename">
-				/Users/hobu/dev/git/pdal/test/data/local/mtsthelens/st-helens.las
-			    </Option>
-			    <!-- <Option name="spatialreference">
-				EPSG:26910
-			    </Option> -->
-			    <!-- <Option name="log">
-				oracle-pipeline-write.log
-			    </Option>     -->
-		    </Reader>
-            </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/soci/write-utm.xml b/test/data/soci/write-utm.xml
deleted file mode 100644
index 14d7127..0000000
--- a/test/data/soci/write-utm.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.soci">
-        <Option name="connection">
-            host='localhost' dbname='lidar' user='howardbutler'
-        </Option>
-        <Option name="type">
-            postgresql
-        </Option>        
-        <Option name="cloud_table">
-            STHELENS_CLOUD
-        </Option>
-        <Option name="block_table">
-            STHELENS_BLOCK
-        </Option>
-        <Option name="cloud_column">
-            CLOUD_ID
-        </Option>
-        <Option name="overwrite">
-            false
-        </Option>
-        <Option name="srid">
-            2926
-        </Option>
-        <Option name="capacity">
-            80000
-        </Option>
-        <Option name="is3d">
-            true
-        </Option>        
-                <Filter type="filters.chipper">
-                    <Option name="capacity">
-                        80000
-                    </Option>
-		    <Reader type="readers.las">
-			    <Option name="filename">
-				/Users/howardbutler/dev/git/pdal/test/data/local/sthelens/st-helens.las
-			    </Option>
-			    <Option name="spatialreference">
-				EPSG:2926
-			    </Option>
-			    <!-- <Option name="log">
-				oracle-pipeline-write.log
-			    </Option>     -->
-		    </Reader>
-            </Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/soci/write.xml b/test/data/soci/write.xml
deleted file mode 100644
index 46510a8..0000000
--- a/test/data/soci/write.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Pipeline version="1.0">
-    <Writer type="writers.soci">
-        <Option name="connection">
-            host='localhost' dbname='lidar' user='hobu'
-        </Option>
-        <Option name="type">
-            postgresql
-        </Option>        
-        <Option name="cloud_table">
-            CLOUD
-        </Option>
-        <Option name="block_table">
-            BLOCKS
-        </Option>
-        <Option name="cloud_column">
-            CLOUD_ID
-        </Option>
-        <Option name="overwrite">
-            false
-        </Option>
-        <Option name="cloud_boundary_wkt">
-            ./autzen-selection.wkt
-        </Option>
-        <Option name="srid">
-            4326
-        </Option>
-        <Option name="capacity">
-            50
-        </Option>
-        <Option name="is3d">
-            true
-        </Option>        
-	<Filter type="filters.chipper">
-	    <Option name="capacity">
-		50
-	    </Option>
-	    <Reader type="readers.las">
-		    <Option name="filename">
-			../1.2-with-color.las
-		    </Option>
-		    <Option name="spatialreference">
-			EPSG:2926
-		    </Option>
-		    <!-- <Option name="log">
-			oracle-pipeline-write.log
-		    </Option>     -->
-	    </Reader>
-	</Filter>
-    </Writer>
-</Pipeline>
diff --git a/test/data/text/badheader.txt b/test/data/text/badheader.txt
new file mode 100644
index 0000000..67b2233
--- /dev/null
+++ b/test/data/text/badheader.txt
@@ -0,0 +1,10 @@
+X,Y,X
+
+0, 0, 0
+.5, .5, 1
+1.5, .5, 2
+2.5, .5, 3
+3.5, .5, 4
+4.5, .5, 5
+3.5, 1, 4.5
+4.5, 1, 5.5
diff --git a/test/data/text/utm17_2.txt b/test/data/text/utm17_2.txt
index afd67e6..a03955f 100644
--- a/test/data/text/utm17_2.txt
+++ b/test/data/text/utm17_2.txt
@@ -1,4 +1,4 @@
-X Y Z
+X  Y  Z
 289814.15  4320978.61  170.76
 289814.64  4320978.84  170.76
 289815.12  4320979.06  170.75
diff --git a/test/temp/SbetWriterTest.sbet b/test/temp/SbetWriterTest.sbet
new file mode 100644
index 0000000..314f893
Binary files /dev/null and b/test/temp/SbetWriterTest.sbet differ
diff --git a/test/temp/colorized.las b/test/temp/colorized.las
new file mode 100644
index 0000000..cb457cd
Binary files /dev/null and b/test/temp/colorized.las differ
diff --git a/test/temp/crop-wkt-2d-classification.las b/test/temp/crop-wkt-2d-classification.las
new file mode 100644
index 0000000..31e74b1
Binary files /dev/null and b/test/temp/crop-wkt-2d-classification.las differ
diff --git a/test/temp/foo.las b/test/temp/foo.las
new file mode 100644
index 0000000..8f5df92
Binary files /dev/null and b/test/temp/foo.las differ
diff --git a/test/temp/issue895.sqlite b/test/temp/issue895.sqlite
new file mode 100644
index 0000000..4c9efca
Binary files /dev/null and b/test/temp/issue895.sqlite differ
diff --git a/test/temp/meta.json b/test/temp/meta.json
new file mode 100644
index 0000000..9374163
--- /dev/null
+++ b/test/temp/meta.json
@@ -0,0 +1,91 @@
+{
+  "stages":
+  {
+    "readers.las":
+    {
+      "comp_spatialreference": "PROJCS[\"NAD_1983_HARN_Lambert_Conformal_Conic\",GEOGCS[\"GCS_North_American_1983_HARN\",DATUM[\"NAD83_High_Accuracy_Reference_Network\",SPHEROID[\"GRS 1980\",6378137,298.2572221010002,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6152\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],PROJECTION[\"Lambert_Conformal_Conic_2SP\"],PARAMETER[\"standard_parallel_1\",43],PARAMETER[\"standard_parallel_2\",45.5],PARAMETER[\"latitude_of_origin\" [...]
+      "compressed": false,
+      "count": 110000,
+      "creation_doy": 253,
+      "creation_year": 2015,
+      "dataformat_id": 3,
+      "dataoffset": 2038,
+      "filesource_id": 0,
+      "global_encoding": 0,
+      "global_encoding_base64": "AAA=",
+      "header_size": 227,
+      "major_version": 1,
+      "maxx": 637179.22,
+      "maxy": 849497.9,
+      "maxz": 520.51,
+      "minor_version": 2,
+      "minx": 636001.76,
+      "miny": 848935.2,
+      "minz": 406.26,
+      "offset_x": 0,
+      "offset_y": 0,
+      "offset_z": 0,
+      "project_id": "00000000-0000-0000-0000-000000000000",
+      "scale_x": 0.01,
+      "scale_y": 0.01,
+      "scale_z": 0.01,
+      "software_id": "PDAL 1.0.0 (9e8465)",
+      "spatialreference": "PROJCS[\"NAD_1983_HARN_Lambert_Conformal_Conic\",GEOGCS[\"GCS_North_American_1983_HARN\",DATUM[\"NAD83_High_Accuracy_Reference_Network\",SPHEROID[\"GRS 1980\",6378137,298.2572221010002,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6152\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],PROJECTION[\"Lambert_Conformal_Conic_2SP\"],PARAMETER[\"standard_parallel_1\",43],PARAMETER[\"standard_parallel_2\",45.5],PARAMETER[\"latitude_of_origin\",41.7 [...]
+      "srs":
+      {
+        "compoundwkt": "PROJCS[\"NAD_1983_HARN_Lambert_Conformal_Conic\",GEOGCS[\"GCS_North_American_1983_HARN\",DATUM[\"NAD83_High_Accuracy_Reference_Network\",SPHEROID[\"GRS 1980\",6378137,298.2572221010002,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6152\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],PROJECTION[\"Lambert_Conformal_Conic_2SP\"],PARAMETER[\"standard_parallel_1\",43],PARAMETER[\"standard_parallel_2\",45.5],PARAMETER[\"latitude_of_origin\",41.75], [...]
+        "horizontal": "PROJCS[\"NAD_1983_HARN_Lambert_Conformal_Conic\",GEOGCS[\"GCS_North_American_1983_HARN\",DATUM[\"NAD83_High_Accuracy_Reference_Network\",SPHEROID[\"GRS 1980\",6378137,298.2572221010002,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6152\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],PROJECTION[\"Lambert_Conformal_Conic_2SP\"],PARAMETER[\"standard_parallel_1\",43],PARAMETER[\"standard_parallel_2\",45.5],PARAMETER[\"latitude_of_origin\",41.75],P [...]
+        "isgeocentric": false,
+        "isgeographic": false,
+        "prettycompoundwkt": "PROJCS[\"NAD_1983_HARN_Lambert_Conformal_Conic\",    GEOGCS[\"GCS_North_American_1983_HARN\",        DATUM[\"NAD83_High_Accuracy_Reference_Network\",            SPHEROID[\"GRS 1980\",6378137,298.2572221010002,                AUTHORITY[\"EPSG\",\"7019\"]],            AUTHORITY[\"EPSG\",\"6152\"]],        PRIMEM[\"Greenwich\",0],        UNIT[\"degree\",0.0174532925199433]],    PROJECTION[\"Lambert_Conformal_Conic_2SP\"],    PARAMETER[\"standard_parallel_1\",43 [...]
+        "prettywkt": "PROJCS[\"NAD_1983_HARN_Lambert_Conformal_Conic\",    GEOGCS[\"GCS_North_American_1983_HARN\",        DATUM[\"NAD83_High_Accuracy_Reference_Network\",            SPHEROID[\"GRS 1980\",6378137,298.2572221010002,                AUTHORITY[\"EPSG\",\"7019\"]],            AUTHORITY[\"EPSG\",\"6152\"]],        PRIMEM[\"Greenwich\",0],        UNIT[\"degree\",0.0174532925199433]],    PROJECTION[\"Lambert_Conformal_Conic_2SP\"],    PARAMETER[\"standard_parallel_1\",43],    PA [...]
+        "proj4": "+proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5 +x_0=399999.9999999999 +y_0=0 +ellps=GRS80 +units=ft +no_defs",
+        "units":
+        {
+          "horizontal": "foot",
+          "vertical": ""
+        },
+        "vertical": "",
+        "wkt": "PROJCS[\"NAD_1983_HARN_Lambert_Conformal_Conic\",GEOGCS[\"GCS_North_American_1983_HARN\",DATUM[\"NAD83_High_Accuracy_Reference_Network\",SPHEROID[\"GRS 1980\",6378137,298.2572221010002,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6152\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],PROJECTION[\"Lambert_Conformal_Conic_2SP\"],PARAMETER[\"standard_parallel_1\",43],PARAMETER[\"standard_parallel_2\",45.5],PARAMETER[\"latitude_of_origin\",41.75],PARAMETE [...]
+      },
+      "system_id": "PDAL",
+      "vlr_0": "AQABAAAAFQAABAAAAQABAAEEAAABAAEAAgSxhyYAAAAACAAAAQD\/fwEIsYc8ACYAAggAAAEACBgGCAAAAQCOIwkIsIcBAAcACwiwhwEABgANCLCHAQAIAPMLAAABAAEAAAwAAAEA\/38CDAAAAQD\/fwMMAAABAAgABAwAAAEAKiMGDLCHAQACAAcMsIcBAAMADAywhwEAAQANDLCHAQAAAA4MsIcBAAQADwywhwEABQAAAAAAAAAAAA==",
+      "vlr_0":       {
+        "description": "GeoTiff GeoKeyDirectoryTag",
+        "record_id": 34735,
+        "user_id": "LASF_Projection"
+      },
+      "vlr_1": "AAAAAADgREAAAAAAACBewAAAAAAAgEVAAAAAAADARkD+1D\/1TwY0QQAAAAAAAAAAqPnrlB2kckAAAABAplRYQQAAAAAAAAAA",
+      "vlr_1":       {
+        "description": "GeoTiff GeoDoubleParamsTag",
+        "record_id": 34736,
+        "user_id": "LASF_Projection"
+      },
+      "vlr_2": "TkFEXzE5ODNfSEFSTl9MYW1iZXJ0X0NvbmZvcm1hbF9Db25pY3xHQ1MgTmFtZSA9IEdDU19Ob3J0aF9BbWVyaWNhbl8xOTgzX0hBUk58UHJpbWVtID0gR3JlZW53aWNofHwA",
+      "vlr_2":       {
+        "description": "GeoTiff GeoAsciiParamsTag",
+        "record_id": 34737,
+        "user_id": "LASF_Projection"
+      },
+      "vlr_3": "UFJPSkNTWyJOQURfMTk4M19IQVJOX0xhbWJlcnRfQ29uZm9ybWFsX0NvbmljIixHRU9HQ1NbIkdDU19Ob3J0aF9BbWVyaWNhbl8xOTgzX0hBUk4iLERBVFVNWyJOQUQ4M19IaWdoX0FjY3VyYWN5X1JlZ2lvbmFsX05ldHdvcmsiLFNQSEVST0lEWyJHUlNfMTk4MCIsNjM3ODEzNywyOTguMjU3MjIyMTAxLEFVVEhPUklUWVsiRVBTRyIsIjcwMTkiXV0sQVVUSE9SSVRZWyJFUFNHIiwiNjE1MiJdXSxQUklNRU1bIkdyZWVud2ljaCIsMF0sVU5JVFsiZGVncmVlIiwwLjAxNzQ1MzI5MjUxOTk0MzNdXSxQUk9KRUNUSU9OWyJMYW1iZXJ0X0NvbmZvcm1hbF9Db25pY18yU1AiXSxQQVJBTUVURVJbInN0YW5kYXJkX3BhcmFsbGVsXzEiLDQz [...]
+      "vlr_3":       {
+        "description": "OGC Tranformation Record",
+        "record_id": 2112,
+        "user_id": "LASF_Projection"
+      },
+      "vlr_4": "UFJPSkNTWyJOQURfMTk4M19IQVJOX0xhbWJlcnRfQ29uZm9ybWFsX0NvbmljIixHRU9HQ1NbIkdDU19Ob3J0aF9BbWVyaWNhbl8xOTgzX0hBUk4iLERBVFVNWyJOQUQ4M19IaWdoX0FjY3VyYWN5X1JlZ2lvbmFsX05ldHdvcmsiLFNQSEVST0lEWyJHUlNfMTk4MCIsNjM3ODEzNywyOTguMjU3MjIyMTAxLEFVVEhPUklUWVsiRVBTRyIsIjcwMTkiXV0sQVVUSE9SSVRZWyJFUFNHIiwiNjE1MiJdXSxQUklNRU1bIkdyZWVud2ljaCIsMF0sVU5JVFsiZGVncmVlIiwwLjAxNzQ1MzI5MjUxOTk0MzNdXSxQUk9KRUNUSU9OWyJMYW1iZXJ0X0NvbmZvcm1hbF9Db25pY18yU1AiXSxQQVJBTUVURVJbInN0YW5kYXJkX3BhcmFsbGVsXzEiLDQz [...]
+      "vlr_4":       {
+        "description": "OGR variant of OpenGIS WKT SRS",
+        "record_id": 2112,
+        "user_id": "liblas"
+      }
+    },
+    "writers.las":
+    {
+      "filename":
+      [
+        "\/PDAL\/test\/data\/..\/temp\/out.las"
+      ]
+    }
+  }
+}
diff --git a/test/temp/mylog_three.txt b/test/temp/mylog_three.txt
new file mode 100644
index 0000000..586a766
--- /dev/null
+++ b/test/temp/mylog_three.txt
@@ -0,0 +1 @@
+Testing log output through python script.
diff --git a/test/temp/out.las b/test/temp/out.las
new file mode 100644
index 0000000..59b72f9
Binary files /dev/null and b/test/temp/out.las differ
diff --git a/test/temp/out.ply b/test/temp/out.ply
new file mode 100644
index 0000000..a92e8f0
Binary files /dev/null and b/test/temp/out.ply differ
diff --git a/test/temp/out2.las b/test/temp/out2.las
new file mode 100644
index 0000000..6706356
Binary files /dev/null and b/test/temp/out2.las differ
diff --git a/test/temp/outfile.txt b/test/temp/outfile.txt
new file mode 100644
index 0000000..bbc361a
--- /dev/null
+++ b/test/temp/outfile.txt
@@ -0,0 +1,3 @@
+"GpsTime","Y","X","Z","XVelocity","YVelocity","ZVelocity","Roll","Pitch","Azimuth","WanderAngle","XBodyAccel","YBodyAccel","ZBodyAccel","XBodyAngRate","YBodyAngRate","ZBodyAngRate"
+151631.003,0.568,-2.042,107.715,-2.332,-0.334,-0.031,-0.028,-0.024,3.047,-0.022,0.786,0.785,-0.298,0.000,0.009,0.072
+151631.008,0.568,-2.042,107.715,-2.336,-0.332,-0.030,-0.028,-0.024,3.047,-0.022,0.840,0.325,-0.156,0.001,0.007,0.072
diff --git a/test/temp/simple.las b/test/temp/simple.las
new file mode 100644
index 0000000..61b1caa
Binary files /dev/null and b/test/temp/simple.las differ
diff --git a/test/temp/spat.sqlite b/test/temp/spat.sqlite
new file mode 100644
index 0000000..e056423
Binary files /dev/null and b/test/temp/spat.sqlite differ
diff --git a/test/temp/spver.sqlite b/test/temp/spver.sqlite
new file mode 100644
index 0000000..e69de29
diff --git a/test/temp/temp-SqliteWriterTest_test_simple_las.sqlite b/test/temp/temp-SqliteWriterTest_test_simple_las.sqlite
new file mode 100644
index 0000000..2c1ecfa
Binary files /dev/null and b/test/temp/temp-SqliteWriterTest_test_simple_las.sqlite differ
diff --git a/test/temp/temp_nitf.ntf b/test/temp/temp_nitf.ntf
new file mode 100644
index 0000000..1ce08d6
Binary files /dev/null and b/test/temp/temp_nitf.ntf differ
diff --git a/test/temp/test.bpf b/test/temp/test.bpf
new file mode 100644
index 0000000..6766416
Binary files /dev/null and b/test/temp/test.bpf differ
diff --git a/test/temp/test_1.bpf b/test/temp/test_1.bpf
new file mode 100644
index 0000000..9d387f4
Binary files /dev/null and b/test/temp/test_1.bpf differ
diff --git a/test/temp/test_1.las b/test/temp/test_1.las
new file mode 100644
index 0000000..7611b81
Binary files /dev/null and b/test/temp/test_1.las differ
diff --git a/test/temp/test_1.ntf b/test/temp/test_1.ntf
new file mode 100644
index 0000000..09446f1
Binary files /dev/null and b/test/temp/test_1.ntf differ
diff --git a/test/temp/test_2.bpf b/test/temp/test_2.bpf
new file mode 100644
index 0000000..8f0792c
Binary files /dev/null and b/test/temp/test_2.bpf differ
diff --git a/test/temp/test_2.las b/test/temp/test_2.las
new file mode 100644
index 0000000..7adb4c7
Binary files /dev/null and b/test/temp/test_2.las differ
diff --git a/test/temp/test_2.ntf b/test/temp/test_2.ntf
new file mode 100644
index 0000000..d3690c5
Binary files /dev/null and b/test/temp/test_2.ntf differ
diff --git a/test/temp/test_3.bpf b/test/temp/test_3.bpf
new file mode 100644
index 0000000..3d82866
Binary files /dev/null and b/test/temp/test_3.bpf differ
diff --git a/test/temp/test_3.las b/test/temp/test_3.las
new file mode 100644
index 0000000..28005fe
Binary files /dev/null and b/test/temp/test_3.las differ
diff --git a/test/temp/test_3.ntf b/test/temp/test_3.ntf
new file mode 100644
index 0000000..fe68423
Binary files /dev/null and b/test/temp/test_3.ntf differ
diff --git a/test/temp/test_flex.bpf b/test/temp/test_flex.bpf
new file mode 100644
index 0000000..18c90af
Binary files /dev/null and b/test/temp/test_flex.bpf differ
diff --git a/test/temp/test_flex.las b/test/temp/test_flex.las
new file mode 100644
index 0000000..ec61a76
Binary files /dev/null and b/test/temp/test_flex.las differ
diff --git a/test/temp/test_flex.ntf b/test/temp/test_flex.ntf
new file mode 100644
index 0000000..f8ba32d
Binary files /dev/null and b/test/temp/test_flex.ntf differ
diff --git a/test/temp/tmp.bpf b/test/temp/tmp.bpf
new file mode 100644
index 0000000..45fc0fb
Binary files /dev/null and b/test/temp/tmp.bpf differ
diff --git a/test/temp/tmp.las b/test/temp/tmp.las
new file mode 100644
index 0000000..2e09205
Binary files /dev/null and b/test/temp/tmp.las differ
diff --git a/test/temp/tmp.tif b/test/temp/tmp.tif
new file mode 100644
index 0000000..24134d3
Binary files /dev/null and b/test/temp/tmp.tif differ
diff --git a/test/temp/trimtest.las b/test/temp/trimtest.las
new file mode 100644
index 0000000..6aae3c9
Binary files /dev/null and b/test/temp/trimtest.las differ
diff --git a/test/temp/triple.las b/test/temp/triple.las
new file mode 100644
index 0000000..0c6d5ae
Binary files /dev/null and b/test/temp/triple.las differ
diff --git a/test/temp/utm17.txt b/test/temp/utm17.txt
new file mode 100644
index 0000000..a03955f
--- /dev/null
+++ b/test/temp/utm17.txt
@@ -0,0 +1,11 @@
+X  Y  Z
+289814.15  4320978.61  170.76
+289814.64  4320978.84  170.76
+289815.12  4320979.06  170.75
+289815.60  4320979.28  170.74
+289816.08  4320979.50  170.68
+289816.56  4320979.71  170.66
+289817.03  4320979.92  170.63
+289817.53  4320980.16  170.62
+289818.01  4320980.38  170.61
+289818.50  4320980.59  170.58
diff --git a/test/unit/BoundsTest.cpp b/test/unit/BoundsTest.cpp
index 862ea45..7dc52fe 100644
--- a/test/unit/BoundsTest.cpp
+++ b/test/unit/BoundsTest.cpp
@@ -32,8 +32,6 @@
 * OF SUCH DAMAGE.
 ****************************************************************************/
 
-#include <boost/property_tree/xml_parser.hpp>
-
 #include <pdal/pdal_test_main.hpp>
 #include <pdal/util/Bounds.hpp>
 #include <pdal/PDALUtils.hpp>
@@ -86,18 +84,18 @@ TEST(BoundsTest, test_copy)
 TEST(BoundsTest, test_accessor)
 {
     BOX2D b1(1,2,3,4);
-    EXPECT_FLOAT_EQ(b1.minx, 1);
-    EXPECT_FLOAT_EQ(b1.miny, 2);
-    EXPECT_FLOAT_EQ(b1.maxx, 3);
-    EXPECT_FLOAT_EQ(b1.maxy, 4);
+	EXPECT_DOUBLE_EQ(b1.minx, 1.0);
+    EXPECT_DOUBLE_EQ(b1.miny, 2.0);
+	EXPECT_DOUBLE_EQ(b1.maxx, 3.0);
+	EXPECT_DOUBLE_EQ(b1.maxy, 4.0);
 
     BOX3D b2(1,2,3,4,5,6);
-    EXPECT_FLOAT_EQ(b2.minx, 1);
-    EXPECT_FLOAT_EQ(b2.miny, 2);
-    EXPECT_FLOAT_EQ(b2.minz, 3);
-    EXPECT_FLOAT_EQ(b2.maxx, 4);
-    EXPECT_FLOAT_EQ(b2.maxy, 5);
-    EXPECT_FLOAT_EQ(b2.maxz, 6);
+	EXPECT_DOUBLE_EQ(b2.minx, 1.0);
+	EXPECT_DOUBLE_EQ(b2.miny, 2.0);
+	EXPECT_DOUBLE_EQ(b2.minz, 3.0);
+	EXPECT_DOUBLE_EQ(b2.maxx, 4.0);
+	EXPECT_DOUBLE_EQ(b2.maxy, 5.0);
+	EXPECT_DOUBLE_EQ(b2.maxz, 6.0);
 }
 
 TEST(BoundsTest, test_clip)
@@ -121,10 +119,10 @@ TEST(BoundsTest, test_clip)
     // .clip() can make an invalid bounds, this should be fixed.
     BOX2D r6(20,6, 40,8);
 
-    EXPECT_FLOAT_EQ(r1.minx, 20);
-    EXPECT_FLOAT_EQ(r1.maxx, 6);
-    EXPECT_FLOAT_EQ(r1.miny, 40);
-    EXPECT_FLOAT_EQ(r1.maxy, 8);
+	EXPECT_DOUBLE_EQ(r1.minx, 20);
+	EXPECT_DOUBLE_EQ(r1.maxx, 6);
+	EXPECT_DOUBLE_EQ(r1.miny, 40);
+	EXPECT_DOUBLE_EQ(r1.maxy, 8);
 
 //ABELL - Need BOX3D example.
 }
@@ -219,22 +217,6 @@ TEST(BoundsTest, test_output)
     EXPECT_EQ(out3, "([1.1, 101.1], [2.2, 102.2], [3.3, 103.3])");
 }
 
-// TEST(BoundsTest, BoundsTest_ptree)
-// {
-//     const BOX2D b2(1,2,101,102);
-//
-//     std::stringstream ss1(std::stringstream::in | std::stringstream::out);
-//
-//     pdalboost::property_tree::ptree tree = Utils::toPTree(b2);
-//     pdalboost::property_tree::write_xml(ss1, tree);
-//
-//     const std::string out1 = ss1.str();
-//
-//     static std::string xml_header = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-//     const std::string ref = xml_header + "<0><minimum>1</minimum><maximum>101</maximum></0><1><minimum>2</minimum><maximum>102</maximum></1>";
-//
-//     EXPECT_EQ(ref, out1);
-// }
 
 TEST(BoundsTest, test_input)
 {
@@ -348,3 +330,11 @@ TEST(BoundsTest, b2)
     EXPECT_EQ(box3.minz, 0.0);
     EXPECT_EQ(box3.maxz, 2.0);
 }
+
+TEST(BoundsTest, bounds_insertion)
+{
+    std::string s;
+    std::ostringstream oss(s);
+    Bounds b;
+    oss << b;
+}
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 42d6e57..c3b0ed9 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -11,63 +11,33 @@ configure_file(TestConfig.hpp.in "${CMAKE_CURRENT_BINARY_DIR}/TestConfig.hpp")
 file(GLOB_RECURSE inFiles RELATIVE "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/test/data/*.in")
 foreach(infileName ${inFiles})
     string(REGEX REPLACE ".in\$" "" outfileName "${infileName}")
-    configure_file("${CMAKE_SOURCE_DIR}/${infileName}" "${CMAKE_BINARY_DIR}/${outfileName}")
+    configure_file("${CMAKE_SOURCE_DIR}/${infileName}"
+        "${CMAKE_BINARY_DIR}/${outfileName}")
 endforeach()
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-include_directories(
-    ${PROJECT_SOURCE_DIR}/include
-    ${GDAL_INCLUDE_DIR}
-    ${PROJECT_SOURCE_DIR}/io/bpf
-    ${PROJECT_SOURCE_DIR}/io/buffer
-    ${PROJECT_SOURCE_DIR}/io/faux
-    ${PROJECT_SOURCE_DIR}/io/gdal
-    ${PROJECT_SOURCE_DIR}/io/ilvis2
-    ${PROJECT_SOURCE_DIR}/io/las
-    ${PROJECT_SOURCE_DIR}/io/optech
-    ${PROJECT_SOURCE_DIR}/io/ply
-    ${PROJECT_SOURCE_DIR}/io/pts
-    ${PROJECT_SOURCE_DIR}/io/qfit
-    ${PROJECT_SOURCE_DIR}/io/sbet
-    ${PROJECT_SOURCE_DIR}/io/text
-    ${PROJECT_SOURCE_DIR}/io/terrasolid
-    ${PROJECT_SOURCE_DIR}/filters/chipper
-    ${PROJECT_SOURCE_DIR}/filters/colorization
-    ${PROJECT_SOURCE_DIR}/filters/crop
-    ${PROJECT_SOURCE_DIR}/filters/decimation
-    ${PROJECT_SOURCE_DIR}/filters/divider
-    ${PROJECT_SOURCE_DIR}/filters/ferry
-    ${PROJECT_SOURCE_DIR}/filters/merge
-    ${PROJECT_SOURCE_DIR}/filters/mortonorder
-    ${PROJECT_SOURCE_DIR}/filters/randomize
-    ${PROJECT_SOURCE_DIR}/filters/reprojection
-    ${PROJECT_SOURCE_DIR}/filters/range
-    ${PROJECT_SOURCE_DIR}/filters/sort
-    ${PROJECT_SOURCE_DIR}/filters/splitter
-    ${PROJECT_SOURCE_DIR}/filters/stats
-    ${PROJECT_SOURCE_DIR}/filters/streamcallback
-    ${PROJECT_SOURCE_DIR}/filters/transformation
-    ${PROJECT_SOURCE_DIR}/kernels/info
-)
-
-if (WITH_GEOTIFF)
-    include_directories(${GEOTIFF_INCLUDE_DIR})
-endif()
 
 PDAL_ADD_TEST(pdal_bounds_test FILES BoundsTest.cpp)
 PDAL_ADD_TEST(pdal_config_test FILES ConfigTest.cpp)
+PDAL_ADD_TEST(pdal_eigen_test FILES EigenTest.cpp)
+target_include_directories(pdal_eigen_test PRIVATE ${PDAL_VENDOR_DIR}/eigen)
 PDAL_ADD_TEST(pdal_file_utils_test FILES FileUtilsTest.cpp)
 PDAL_ADD_TEST(pdal_georeference_test FILES GeoreferenceTest.cpp)
 PDAL_ADD_TEST(pdal_kdindex_test FILES KDIndexTest.cpp)
+target_include_directories(pdal_kdindex_test PRIVATE ${PDAL_VENDOR_DIR})
 PDAL_ADD_TEST(pdal_kernel_test FILES KernelTest.cpp)
 PDAL_ADD_TEST(pdal_log_test FILES LogTest.cpp)
 PDAL_ADD_TEST(pdal_metadata_test FILES MetadataTest.cpp)
+PDAL_ADD_TEST(pdal_oldpclblock_test FILES OldPCLBlockTest.cpp)
 PDAL_ADD_TEST(pdal_options_test FILES OptionsTest.cpp)
+    target_include_directories(pdal_options_test PRIVATE
+        ${PDAL_JSONCPP_INCLUDE_DIR})
 PDAL_ADD_TEST(pdal_pipeline_manager_test FILES PipelineManagerTest.cpp)
 PDAL_ADD_TEST(pdal_plugin_manager_test FILES PluginManagerTest.cpp)
 PDAL_ADD_TEST(pdal_point_view_test FILES PointViewTest.cpp)
 PDAL_ADD_TEST(pdal_point_table_test FILES PointTableTest.cpp)
 PDAL_ADD_TEST(pdal_program_arg_test FILES ProgramArgsTest.cpp)
+    target_include_directories(pdal_program_arg_test PRIVATE
+        ${PDAL_JSONCPP_INCLUDE_DIR})
+
 PDAL_ADD_TEST(pdal_polygon_test FILES PolygonTest.cpp)
 PDAL_ADD_TEST(pdal_spatial_reference_test FILES SpatialReferenceTest.cpp)
 PDAL_ADD_TEST(pdal_stage_factory_test FILES StageFactoryTest.cpp)
@@ -75,7 +45,6 @@ PDAL_ADD_TEST(pdal_streaming_test FILES StreamingTest.cpp)
 PDAL_ADD_TEST(pdal_support_test FILES SupportTest.cpp)
 PDAL_ADD_TEST(pdal_utils_test FILES UtilsTest.cpp)
 PDAL_ADD_TEST(pdal_uuid_test FILES UuidTest.cpp)
-
 if (PDAL_HAVE_LAZPERF)
     PDAL_ADD_TEST(pdal_lazperf_test FILES CompressionTest.cpp)
 endif()
@@ -83,61 +52,79 @@ endif()
 #
 # sources for the native io
 #
-PDAL_ADD_TEST(pdal_io_bpf_test FILES io/bpf/BPFTest.cpp)
-PDAL_ADD_TEST(pdal_io_buffer_test FILES io/buffer/BufferTest.cpp)
-PDAL_ADD_TEST(pdal_io_faux_test FILES io/faux/FauxReaderTest.cpp)
-PDAL_ADD_TEST(pdal_io_gdal_reader_test FILES io/gdal/GDALReaderTest.cpp)
-PDAL_ADD_TEST(pdal_io_ilvis2_test FILES io/ilvis2/Ilvis2ReaderTest.cpp)
-PDAL_ADD_TEST(pdal_io_las_reader_test FILES io/las/LasReaderTest.cpp)
-PDAL_ADD_TEST(pdal_io_las_writer_test FILES io/las/LasWriterTest.cpp)
-PDAL_ADD_TEST(pdal_io_optech_test FILES io/optech/OptechReaderTest.cpp)
-PDAL_ADD_TEST(pdal_io_ply_reader_test FILES io/ply/PlyReaderTest.cpp)
-PDAL_ADD_TEST(pdal_io_ply_writer_test FILES io/ply/PlyWriterTest.cpp)
-PDAL_ADD_TEST(pdal_io_pts_reader_test FILES io/pts/PtsReaderTest.cpp)
-PDAL_ADD_TEST(pdal_io_qfit_test FILES io/qfit/QFITReaderTest.cpp)
-PDAL_ADD_TEST(pdal_io_sbet_reader_test FILES io/sbet/SbetReaderTest.cpp)
-PDAL_ADD_TEST(pdal_io_sbet_writer_test FILES io/sbet/SbetWriterTest.cpp)
-PDAL_ADD_TEST(pdal_io_terrasolid_test FILES io/terrasolid/TerrasolidReaderTest.cpp)
-PDAL_ADD_TEST(pdal_io_text_test FILES io/text/TextReaderTest.cpp)
+PDAL_ADD_TEST(pdal_io_bpf_test FILES io/BPFTest.cpp)
+PDAL_ADD_TEST(pdal_io_buffer_test FILES io/BufferTest.cpp)
+PDAL_ADD_TEST(pdal_io_faux_test FILES io/FauxReaderTest.cpp)
+PDAL_ADD_TEST(pdal_io_gdal_reader_test FILES io/GDALReaderTest.cpp)
+PDAL_ADD_TEST(pdal_io_gdal_writer_test FILES io/GDALWriterTest.cpp)
+PDAL_ADD_TEST(pdal_io_las_reader_test FILES io/LasReaderTest.cpp)
+PDAL_ADD_TEST(pdal_io_las_writer_test FILES io/LasWriterTest.cpp)
+PDAL_ADD_TEST(pdal_io_optech_test FILES io/OptechReaderTest.cpp)
+PDAL_ADD_TEST(pdal_io_ply_reader_test FILES io/PlyReaderTest.cpp)
+target_include_directories(pdal_io_ply_reader_test PRIVATE ${PDAL_VENDOR_DIR})
+PDAL_ADD_TEST(pdal_io_ply_writer_test FILES io/PlyWriterTest.cpp)
+target_include_directories(pdal_io_ply_writer_test PRIVATE ${PDAL_VENDOR_DIR})
+PDAL_ADD_TEST(pdal_io_pts_reader_test FILES io/PtsReaderTest.cpp)
+PDAL_ADD_TEST(pdal_io_qfit_test FILES io/QFITReaderTest.cpp)
+PDAL_ADD_TEST(pdal_io_sbet_reader_test FILES io/SbetReaderTest.cpp)
+PDAL_ADD_TEST(pdal_io_sbet_writer_test FILES io/SbetWriterTest.cpp)
+PDAL_ADD_TEST(pdal_io_terrasolid_test FILES io/TerrasolidReaderTest.cpp)
+PDAL_ADD_TEST(pdal_io_text_reader_test FILES io/TextReaderTest.cpp)
+PDAL_ADD_TEST(pdal_io_text_writer_test FILES io/TextWriterTest.cpp)
 
 #
 # sources for the native filters
 #
 PDAL_ADD_TEST(pdal_filters_attribute_test FILES filters/AttributeFilterTest.cpp)
 PDAL_ADD_TEST(pdal_filters_chipper_test FILES filters/ChipperTest.cpp)
-PDAL_ADD_TEST(pdal_filters_colorization_test FILES filters/ColorizationFilterTest.cpp)
+PDAL_ADD_TEST(pdal_filters_colorization_test FILES
+    filters/ColorizationFilterTest.cpp)
+PDAL_ADD_TEST(pdal_filters_computerange_test FILES filters/ComputeRangeFilterTest.cpp)
 PDAL_ADD_TEST(pdal_filters_crop_test FILES filters/CropFilterTest.cpp)
-PDAL_ADD_TEST(pdal_filters_decimation_test FILES filters/DecimationFilterTest.cpp)
+PDAL_ADD_TEST(pdal_filters_decimation_test FILES
+    filters/DecimationFilterTest.cpp)
 PDAL_ADD_TEST(pdal_filters_divider_test FILES filters/DividerFilterTest.cpp)
 PDAL_ADD_TEST(pdal_filters_ferry_test FILES filters/FerryFilterTest.cpp)
 PDAL_ADD_TEST(pdal_filters_merge_test FILES filters/MergeTest.cpp)
-PDAL_ADD_TEST(pdal_filters_additional_merge_test FILES filters/AdditionalMergeTest.cpp)
-PDAL_ADD_TEST(pdal_filters_reprojection_test FILES filters/ReprojectionFilterTest.cpp)
+PDAL_ADD_TEST(pdal_filters_additional_merge_test FILES
+    filters/AdditionalMergeTest.cpp)
+PDAL_ADD_TEST(pdal_filters_reprojection_test FILES
+    filters/ReprojectionFilterTest.cpp)
 PDAL_ADD_TEST(pdal_filters_range_test FILES filters/RangeFilterTest.cpp)
 PDAL_ADD_TEST(pdal_filters_randomize_test FILES filters/RandomizeFilterTest.cpp)
 PDAL_ADD_TEST(pdal_filters_sort_test FILES filters/SortFilterTest.cpp)
 PDAL_ADD_TEST(pdal_filters_splitter_test FILES filters/SplitterTest.cpp)
 PDAL_ADD_TEST(pdal_filters_stats_test FILES filters/StatsFilterTest.cpp)
-PDAL_ADD_TEST(pdal_filters_transformation_test FILES filters/TransformationFilterTest.cpp)
+PDAL_ADD_TEST(pdal_filters_transformation_test FILES
+    filters/TransformationFilterTest.cpp)
 
-#
-# conditionally append apps/libxml2 sources
-#
-if (WITH_APPS)
-    if (LASZIP_FOUND)
-        PDAL_ADD_TEST(pdal_merge_test FILES apps/MergeTest.cpp)
-    endif()
-    PDAL_ADD_TEST(pc2pc_test FILES apps/pc2pcTest.cpp)
+PDAL_ADD_TEST(pdal_app_test FILES apps/AppTest.cpp)
+if (LASZIP_FOUND)
+    PDAL_ADD_TEST(pdal_merge_test FILES apps/MergeTest.cpp)
+endif()
+PDAL_ADD_TEST(pc2pc_test FILES apps/pc2pcTest.cpp)
 
-    if (BUILD_PIPELINE_TESTS)
-        PDAL_ADD_TEST(pcpipeline_test FILES apps/pcpipelineTest.cpp)
-    endif()
+if (BUILD_PIPELINE_TESTS)
+    PDAL_ADD_TEST(pcpipeline_test FILES apps/pcpipelineTest.cpp)
     PDAL_ADD_TEST(pcpipeline_test_json FILES apps/pcpipelineTestJSON.cpp)
-    PDAL_ADD_TEST(random_test FILES apps/RandomTest.cpp)
-endif(WITH_APPS)
+endif()
+PDAL_ADD_TEST(hausdorff_test FILES apps/HausdorffTest.cpp)
+PDAL_ADD_TEST(random_test FILES apps/RandomTest.cpp)
+PDAL_ADD_TEST(translate_test FILES apps/TranslateTest.cpp)
 
-if(LIBXML2_FOUND)
-    PDAL_ADD_TEST(pdal_io_ilvis2_metadata_test FILES io/ilvis2/Ilvis2MetadataReaderTest.cpp)
-    PDAL_ADD_TEST(pdal_io_ilvis2_reader_metadata_test FILES io/ilvis2/Ilvis2ReaderWithMDReaderTest.cpp)
+if(PDAL_HAVE_LIBXML2)
+    PDAL_ADD_TEST(pdal_io_ilvis2_metadata_test FILES
+        io/Ilvis2MetadataReaderTest.cpp)
+    target_include_directories(pdal_io_ilvis2_metadata_test PRIVATE
+        ${LIBXML2_INCLUDE_DIR})
+    PDAL_ADD_TEST(pdal_io_ilvis2_reader_metadata_test FILES
+        io/Ilvis2ReaderWithMDReaderTest.cpp)
+    target_include_directories(pdal_io_ilvis2_reader_metadata_test PRIVATE
+        ${LIBXML2_INCLUDE_DIR})
     PDAL_ADD_TEST(xml_schema_test FILES XMLSchemaTest.cpp)
+    target_include_directories(xml_schema_test PRIVATE
+        ${LIBXML2_INCLUDE_DIR})
+    PDAL_ADD_TEST(pdal_io_ilvis2_test FILES io/Ilvis2ReaderTest.cpp)
+    target_include_directories(pdal_io_ilvis2_test PRIVATE
+        ${LIBXML2_INCLUDE_DIR})
 endif()
diff --git a/test/unit/CompressionTest.cpp b/test/unit/CompressionTest.cpp
index 2755b7a..8ade319 100644
--- a/test/unit/CompressionTest.cpp
+++ b/test/unit/CompressionTest.cpp
@@ -43,8 +43,8 @@
 #include "Support.hpp"
 #include <pdal/Options.hpp>
 #include <pdal/PointView.hpp>
-#include <LasReader.hpp>
 #include <pdal/Compression.hpp>
+#include <io/LasReader.hpp>
 
 using namespace pdal;
 
diff --git a/test/unit/ConfigTest.cpp b/test/unit/ConfigTest.cpp
index c4f0570..45394f3 100644
--- a/test/unit/ConfigTest.cpp
+++ b/test/unit/ConfigTest.cpp
@@ -43,11 +43,7 @@ TEST(ConfigTest, test_3rdparty_libs)
     bool geotiff = IsLibGeoTIFFEnabled();
     bool laszip = IsLasZipEnabled();
 
-#ifdef PDAL_HAVE_LIBGEOTIFF
     EXPECT_TRUE(geotiff);
-#else
-    EXPECT_TRUE(!geotiff);
-#endif
 
 #ifdef PDAL_HAVE_LASZIP
     EXPECT_TRUE(laszip);
diff --git a/test/unit/EigenTest.cpp b/test/unit/EigenTest.cpp
new file mode 100644
index 0000000..cedc174
--- /dev/null
+++ b/test/unit/EigenTest.cpp
@@ -0,0 +1,217 @@
+/******************************************************************************
+* Copyright (c) 2016, Peter J. Gadomski <pete.gadomski at gmail.com>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/EigenUtils.hpp>
+#include <pdal/PointView.hpp>
+
+#include <Eigen/Dense>
+
+#include <limits>
+
+using namespace pdal;
+
+TEST(EigenTest, PointViewToEigen)
+{
+    PointTable table;
+    table.layout()->registerDim(Dimension::Id::X);
+    table.layout()->registerDim(Dimension::Id::Y);
+    table.layout()->registerDim(Dimension::Id::Z);
+    PointView pointView(table);
+    pointView.setField(Dimension::Id::X, 0, 1.0);
+    pointView.setField(Dimension::Id::Y, 0, 2.0);
+    pointView.setField(Dimension::Id::Z, 0, 3.0);
+    Eigen::MatrixXd expected(1, 3);
+    expected << 1.0, 2.0, 3.0;
+    Eigen::MatrixXd actual = eigen::pointViewToEigen(pointView);
+    ASSERT_EQ(1, actual.rows());
+    ASSERT_EQ(3, actual.cols());
+    EXPECT_EQ(expected, actual);
+}
+
+TEST(EigenTest, ComputeValues)
+{
+    using namespace Eigen;
+
+    Matrix3d A;
+    A << 1.8339, 0.3188, 0.3426,
+    -2.2588, -1.3077, 3.5784,
+    0.8622, -0.4336, 2.7694;
+
+    double spacing(1.4);
+
+    double zXX = eigen::centralDiffX2(A, spacing);
+    EXPECT_NEAR(2.0076530612, zXX, 0.0001);
+
+    double zYY = eigen::centralDiffY2(A, spacing);
+    EXPECT_NEAR(1.2758163265, zYY, 0.0001);
+
+    double zXY = eigen::centralDiffXY(A, spacing);
+    EXPECT_NEAR(-0.4334821429, zXY, 0.0001);
+
+    double zX = eigen::centralDiffX(A, spacing);
+    EXPECT_NEAR(2.0847142857, zX, 0.0001);
+
+    double zY = eigen::centralDiffY(A, spacing);
+    EXPECT_NEAR(0.2687142857, zY, 0.0001);
+
+    double p = (zX * zX) + (zY * zY);
+    EXPECT_NEAR(4.4182410203, p, 0.0001);
+
+    double q = p + 1;
+    EXPECT_NEAR(5.4182410203, q, 0.0001);
+
+    double contour = eigen::computeContour(A, spacing);
+    EXPECT_NEAR(0.1669520079, contour, 0.0001);
+
+    double profile = eigen::computeProfile(A, spacing);
+    EXPECT_NEAR(0.149520634, profile, 0.0001);
+
+    double tangential = eigen::computeTangential(A, spacing);
+    EXPECT_NEAR(0.6004609473, tangential, 0.0001);
+
+    double total = eigen::computeTotal(A, spacing);
+    EXPECT_NEAR(6.0341916495, total, 0.0001);
+
+    double dZdX = eigen::computeDZDX(A, spacing);
+    EXPECT_NEAR(1.0794910714, dZdX, 0.0001);
+
+    double dZdY = eigen::computeDZDY(A, spacing);
+    EXPECT_NEAR(-0.0044375, dZdY, 0.0001);
+
+    double slope = eigen::computeSlopeRad(dZdX, dZdY);
+    EXPECT_NEAR(0.8236099869, slope, 0.0001);
+
+    double sd8 = eigen::computeSlopeD8(A, spacing);
+    EXPECT_NEAR(48.0378, sd8, 0.0001);
+
+    double sfd = eigen::computeSlopeFD(A, spacing);
+    EXPECT_NEAR(210.1961, sfd, 0.0001);
+
+    double ad8 = eigen::computeAspectD8(A, spacing);
+    EXPECT_NEAR(64.0, ad8, 0.0001);
+
+    double afd = eigen::computeAspectFD(A, spacing);
+    EXPECT_NEAR(269.8718, afd, 0.0001);
+
+    double hs = eigen::computeHillshade(A, spacing, 45.0, 315.0);
+    
+    MatrixXd out = eigen::gradX(A);
+    
+    Matrix3d gx;
+    gx << -1.5151, -0.7457, 0.0238,
+    0.9511, 2.9186, 4.8861,
+    -1.2958, 0.9536, 3.2030;
+    
+    for (size_t i = 0; i < 9; ++i)
+        EXPECT_NEAR(gx(i), out(i), 0.0001);
+    
+    MatrixXd out2 = eigen::gradY(A);
+    
+    Matrix3d gy;
+    gy << -4.0927, -1.6265, 3.2358,
+    -0.4859, -0.3762, 1.2134,
+    3.1210, 0.8741, -0.8090;
+    
+    for (size_t i = 0; i < 9; ++i)
+        EXPECT_NEAR(gy(i), out2(i), 0.0001);
+
+    A(0, 0) = std::numeric_limits<double>::quiet_NaN();
+    Matrix3d B = eigen::replaceNaNs(A);
+    EXPECT_NEAR(0.4839, B(0, 0), 0.0001);
+}
+
+TEST(EigenTest, CheckThrow)
+{
+    using namespace Eigen;
+
+    MatrixXd A = MatrixXd::Zero(3, 4);
+
+    EXPECT_THROW(eigen::computeSlopeD8(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::computeSlopeFD(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::computeAspectD8(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::computeAspectFD(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::computeContour(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::computeProfile(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::computeTangential(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::computeHillshade(A, 1.0, 45.0, 315.0), pdal_error);
+    EXPECT_THROW(eigen::computeTotal(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::centralDiffX2(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::centralDiffY2(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::centralDiffXY(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::centralDiffX(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::centralDiffY(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::computeDZDX(A, 1.0), pdal_error);
+    EXPECT_THROW(eigen::computeDZDY(A, 1.0), pdal_error);
+
+    MatrixXd B = MatrixXd::Zero(3, 3);
+
+    EXPECT_THROW(eigen::computeSlopeD8(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::computeSlopeFD(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::computeAspectD8(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::computeAspectFD(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::computeContour(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::computeProfile(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::computeTangential(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::computeHillshade(B, -1.0, 45.0, 315.0), pdal_error);
+    EXPECT_THROW(eigen::computeTotal(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::centralDiffX2(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::centralDiffY2(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::centralDiffXY(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::centralDiffX(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::centralDiffY(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::computeDZDX(B, -1.0), pdal_error);
+    EXPECT_THROW(eigen::computeDZDY(B, -1.0), pdal_error);
+}
+
+TEST(EigenTest, Padding)
+{
+    using namespace Eigen;
+
+    Matrix3d A;
+    A << 1.8339, 0.3188, 0.3426,
+    -2.2588, -1.3077, 3.5784,
+    0.8622, -0.4336, 2.7694;
+
+    MatrixXd B = eigen::padMatrix(A, 1);
+
+    EXPECT_EQ(5, B.rows());
+    EXPECT_EQ(5, B.cols());
+
+    EXPECT_EQ(-2.2588, B(2, 0));
+    EXPECT_EQ(0.3188, B(0, 2));
+    EXPECT_EQ(3.5784, B(2, 4));
+    EXPECT_EQ(-0.4336, B(4, 2));
+}
diff --git a/test/unit/FileUtilsTest.cpp b/test/unit/FileUtilsTest.cpp
index 657e56e..a634b3e 100644
--- a/test/unit/FileUtilsTest.cpp
+++ b/test/unit/FileUtilsTest.cpp
@@ -202,3 +202,48 @@ TEST(FileUtilsTest, stem)
     EXPECT_EQ(FileUtils::stem("."), ".");
     EXPECT_EQ(FileUtils::stem(".."), "..");
 }
+
+TEST(FileUtilsTest, glob)
+{
+    auto TP = [](const std::string s)
+    {
+        return Support::temppath(s);
+    };
+
+    auto filenames = [&TP]()
+    {
+        std::vector<std::string> names;
+        std::string name;
+        for (int i = 0; i < 5; ++i)
+        {
+            std::string name;
+            name = TP(std::string("foo") + std::to_string(i) + ".glob");
+            names.push_back(name);
+            name = TP(std::string("bar") + std::to_string(i) + ".glob");
+            names.push_back(name);
+        }
+        return names;
+    };
+
+    for (std::string& file : filenames())
+        FileUtils::deleteFile(file);
+
+    for (std::string& file : filenames()) {
+        auto f = FileUtils::createFile(file);
+        FileUtils::closeFile(f);
+    }
+
+    EXPECT_EQ(FileUtils::glob(TP("*.glob")).size(), 10u);
+    EXPECT_EQ(FileUtils::glob(TP("foo1.glob")).size(), 1u);
+
+    for (std::string& file : filenames())
+        FileUtils::deleteFile(file);
+
+    EXPECT_EQ(FileUtils::glob(TP("*.glob")).size(), 0u);
+    EXPECT_EQ(FileUtils::glob(TP("foo1.glob")).size(), 0u);
+
+    FileUtils::deleteFile("temp.glob");
+    FileUtils::closeFile(FileUtils::createFile("temp.glob"));
+    EXPECT_EQ(FileUtils::glob("temp.glob").size(), 1u);
+    FileUtils::deleteFile("temp.glob");
+}
diff --git a/test/unit/KDIndexTest.cpp b/test/unit/KDIndexTest.cpp
index 608ca37..f050c47 100644
--- a/test/unit/KDIndexTest.cpp
+++ b/test/unit/KDIndexTest.cpp
@@ -94,6 +94,25 @@ TEST(KDIndex, neighbors2D)
     EXPECT_EQ(ids[2], 3u);
     EXPECT_EQ(ids[3], 0u);
     EXPECT_EQ(ids[4], 4u);
+
+    // Search by PointId
+    ids = index.neighbors(0, 5);
+    EXPECT_EQ(ids.size(), 5u);
+    EXPECT_EQ(ids[0], 0u);
+    EXPECT_EQ(ids[1], 1u);
+    EXPECT_EQ(ids[2], 2u);
+    EXPECT_EQ(ids[3], 3u);
+    EXPECT_EQ(ids[4], 4u);
+
+    // Search by PointRef
+    PointRef point = view.point(0);
+    ids = index.neighbors(point, 5);
+    EXPECT_EQ(ids.size(), 5u);
+    EXPECT_EQ(ids[0], 0u);
+    EXPECT_EQ(ids[1], 1u);
+    EXPECT_EQ(ids[2], 2u);
+    EXPECT_EQ(ids[3], 3u);
+    EXPECT_EQ(ids[4], 4u);
 }
 
 TEST(KDIndex, neighbors3D)
@@ -159,6 +178,25 @@ TEST(KDIndex, neighbors3D)
     EXPECT_EQ(ids[2], 3u);
     EXPECT_EQ(ids[3], 0u);
     EXPECT_EQ(ids[4], 4u);
+
+    // Search by PointId
+    ids = index.neighbors(0, 5);
+    EXPECT_EQ(ids.size(), 5u);
+    EXPECT_EQ(ids[0], 0u);
+    EXPECT_EQ(ids[1], 1u);
+    EXPECT_EQ(ids[2], 2u);
+    EXPECT_EQ(ids[3], 3u);
+    EXPECT_EQ(ids[4], 4u);
+
+    // Search by PointRef
+    PointRef point = view.point(0);
+    ids = index.neighbors(point, 5);
+    EXPECT_EQ(ids.size(), 5u);
+    EXPECT_EQ(ids[0], 0u);
+    EXPECT_EQ(ids[1], 1u);
+    EXPECT_EQ(ids[2], 2u);
+    EXPECT_EQ(ids[3], 3u);
+    EXPECT_EQ(ids[4], 4u);
 }
 
 TEST(KDIndex, neighbordims)
@@ -236,6 +274,21 @@ TEST(KDIndex, radius2D)
     EXPECT_EQ(ids[2], 3u);
     EXPECT_EQ(ids[3], 0u);
     EXPECT_EQ(ids[4], 4u);
+
+    // Search by PointId
+    ids = index.radius(0, 4.25);
+    EXPECT_EQ(ids.size(), 3u);
+    EXPECT_EQ(ids[0], 0u);
+    EXPECT_EQ(ids[1], 1u);
+    EXPECT_EQ(ids[2], 2u);
+
+    // Search by PointRef
+    PointRef point = view.point(0);
+    ids = index.radius(point, 4.25);
+    EXPECT_EQ(ids.size(), 3u);
+    EXPECT_EQ(ids[0], 0u);
+    EXPECT_EQ(ids[1], 1u);
+    EXPECT_EQ(ids[2], 2u);
 }
 
 TEST(KDIndex, radius3D)
@@ -286,5 +339,20 @@ TEST(KDIndex, radius3D)
     EXPECT_EQ(ids[2], 3u);
     EXPECT_EQ(ids[3], 0u);
     EXPECT_EQ(ids[4], 4u);
+
+    // Search by PointId
+    ids = index.radius(0, 5.2);
+    EXPECT_EQ(ids.size(), 3u);
+    EXPECT_EQ(ids[0], 0u);
+    EXPECT_EQ(ids[1], 1u);
+    EXPECT_EQ(ids[2], 2u);
+
+    // Search by PointRef
+    PointRef point = view.point(0);
+    ids = index.radius(point, 5.2);
+    EXPECT_EQ(ids.size(), 3u);
+    EXPECT_EQ(ids[0], 0u);
+    EXPECT_EQ(ids[1], 1u);
+    EXPECT_EQ(ids[2], 2u);
 }
 
diff --git a/test/unit/LogTest.cpp b/test/unit/LogTest.cpp
index 16ab983..aa78e92 100644
--- a/test/unit/LogTest.cpp
+++ b/test/unit/LogTest.cpp
@@ -36,7 +36,7 @@
 #include <pdal/Options.hpp>
 #include <pdal/PointView.hpp>
 #include <pdal/StageFactory.hpp>
-#include <FauxReader.hpp>
+#include <io/FauxReader.hpp>
 #include "Support.hpp"
 
 using namespace pdal;
diff --git a/test/unit/OldPCLBlockTest.cpp b/test/unit/OldPCLBlockTest.cpp
new file mode 100644
index 0000000..d358be2
--- /dev/null
+++ b/test/unit/OldPCLBlockTest.cpp
@@ -0,0 +1,296 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <string>
+
+#include <pdal/pdal_test_main.hpp>
+#include <pdal/StageFactory.hpp>
+
+#include "Support.hpp"
+
+using namespace pdal;
+
+TEST(OldPCLBlockTests, StatisticalOutliers1)
+{
+    StageFactory f;
+    
+    Options ro;
+    ro.add("filename", Support::datapath("autzen/autzen-point-format-3.las"));
+    
+    Stage* r(f.createStage("readers.las"));
+    EXPECT_TRUE(r);
+    r->setOptions(ro);
+    
+    Options fo;
+    fo.add("method", "statistical");
+    fo.add("multiplier", 1.5);
+    fo.add("mean_k", 2);
+    fo.add("extract", true);
+    
+    Stage* outlier(f.createStage("filters.outlier"));
+    EXPECT_TRUE(outlier);
+    outlier->setOptions(fo);
+    outlier->setInput(*r);
+    
+    PointTable table;
+    outlier->prepare(table);
+    PointViewSet viewSet = outlier->execute(table);
+    
+    EXPECT_EQ(1u, viewSet.size());
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(96u, view->size());
+}
+
+TEST(OldPCLBlockTests, StatisticalOutliers2)
+{
+    StageFactory f;
+    
+    Options ro;
+    ro.add("filename", Support::datapath("autzen/autzen-point-format-3.las"));
+    
+    Stage* r(f.createStage("readers.las"));
+    EXPECT_TRUE(r);
+    r->setOptions(ro);
+    
+    Options fo;
+    fo.add("method", "statistical");
+    fo.add("multiplier", 0.0);
+    fo.add("mean_k", 5);
+    fo.add("extract", true);
+    
+    Stage* outlier(f.createStage("filters.outlier"));
+    EXPECT_TRUE(outlier);
+    outlier->setOptions(fo);
+    outlier->setInput(*r);
+    
+    PointTable table;
+    outlier->prepare(table);
+    PointViewSet viewSet = outlier->execute(table);
+    
+    EXPECT_EQ(1u, viewSet.size());
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(63u, view->size());
+}
+
+TEST(OldPCLBlockTests, RadiusOutliers1)
+{
+    StageFactory f;
+    
+    Options ro;
+    ro.add("filename", Support::datapath("autzen/autzen-point-format-3.las"));
+    
+    Stage* r(f.createStage("readers.las"));
+    EXPECT_TRUE(r);
+    r->setOptions(ro);
+    
+    Options fo;
+    fo.add("method", "radius");
+    fo.add("radius", 200.0);
+    fo.add("min_k", 1);
+    fo.add("extract", true);
+    
+    Stage* outlier(f.createStage("filters.outlier"));
+    EXPECT_TRUE(outlier);
+    outlier->setOptions(fo);
+    outlier->setInput(*r);
+    
+    PointTable table;
+    outlier->prepare(table);
+    PointViewSet viewSet = outlier->execute(table);
+    
+    EXPECT_EQ(1u, viewSet.size());
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(60u, view->size());
+}
+
+TEST(OldPCLBlockTests, RadiusOutliers2)
+{
+    StageFactory f;
+    
+    Options ro;
+    ro.add("filename", Support::datapath("autzen/autzen-point-format-3.las"));
+    
+    Stage* r(f.createStage("readers.las"));
+    EXPECT_TRUE(r);
+    r->setOptions(ro);
+    
+    Options fo;
+    fo.add("method", "radius");
+    fo.add("radius", 100.0);
+    fo.add("min_k", 2);
+    fo.add("extract", true);
+    
+    Stage* outlier(f.createStage("filters.outlier"));
+    EXPECT_TRUE(outlier);
+    outlier->setOptions(fo);
+    outlier->setInput(*r);
+    
+    PointTable table;
+    outlier->prepare(table);
+    PointViewSet viewSet = outlier->execute(table);
+    
+    EXPECT_EQ(1u, viewSet.size());
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(3u, view->size());
+}
+
+TEST(OldPCLBlockTests, PMF1)
+{
+    StageFactory f;
+    
+    Options ro;
+    ro.add("filename", Support::datapath("autzen/autzen-point-format-3.las"));
+    
+    Stage* r(f.createStage("readers.las"));
+    EXPECT_TRUE(r);
+    r->setOptions(ro);
+    
+    Options fo;
+    fo.add("max_window_size", 200);
+    fo.add("extract", true);
+    
+    Stage* outlier(f.createStage("filters.pmf"));
+    EXPECT_TRUE(outlier);
+    outlier->setOptions(fo);
+    outlier->setInput(*r);
+    
+    PointTable table;
+    outlier->prepare(table);
+    PointViewSet viewSet = outlier->execute(table);
+    
+    EXPECT_EQ(1u, viewSet.size());
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(93u, view->size());
+}
+
+TEST(OldPCLBlockTests, PMF2)
+{
+    StageFactory f;
+    
+    Options ro;
+    ro.add("filename", Support::datapath("autzen/autzen-point-format-3.las"));
+    
+    Stage* r(f.createStage("readers.las"));
+    EXPECT_TRUE(r);
+    r->setOptions(ro);
+    
+    Options fo;
+    fo.add("max_window_size", 200);
+    fo.add("cell_size", 1.0);
+    fo.add("slope", 1.0);
+    fo.add("initial_distance", 0.05);
+    fo.add("max_distance", 3.0);
+    fo.add("extract", true);
+    
+    Stage* outlier(f.createStage("filters.pmf"));
+    EXPECT_TRUE(outlier);
+    outlier->setOptions(fo);
+    outlier->setInput(*r);
+    
+    PointTable table;
+    outlier->prepare(table);
+    PointViewSet viewSet = outlier->execute(table);
+    
+    EXPECT_EQ(1u, viewSet.size());
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(94u, view->size());
+}
+
+TEST(OldPCLBlockTests, PMF3)
+{
+    StageFactory f;
+    
+    Options ro;
+    ro.add("filename", Support::datapath("autzen/autzen-point-format-3.las"));
+    
+    Stage* r(f.createStage("readers.las"));
+    EXPECT_TRUE(r);
+    r->setOptions(ro);
+    
+    Options fo;
+    fo.add("max_window_size", 33);
+    fo.add("cell_size", 1.0);
+    fo.add("slope", 1.0);
+    fo.add("initial_distance", 0.15);
+    fo.add("max_distance", 2.5);
+    fo.add("extract", true);
+    
+    Stage* outlier(f.createStage("filters.pmf"));
+    EXPECT_TRUE(outlier);
+    outlier->setOptions(fo);
+    outlier->setInput(*r);
+    
+    PointTable table;
+    outlier->prepare(table);
+    PointViewSet viewSet = outlier->execute(table);
+    
+    EXPECT_EQ(1u, viewSet.size());
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(106u, view->size());
+}
+
+/*
+// Test these with "autzen/autzen-thin.las"
+TEST(PCLBlockFilterTest, PCLBlockFilterTest_filter_PMF)
+{
+#if RUN_SLOW_TESTS
+    // explicitly with all defaults
+    test_filter("filters/pcl/filter_PMF_1.json", 9223, true);
+
+    // with CellSize=3
+    test_filter("filters/pcl/filter_PMF_2.json", 8298, true);
+
+    // with WindowSize=50
+    test_filter("filters/pcl/filter_PMF_3.json", 7970, true);
+
+    // with Slope=0.25
+    test_filter("filters/pcl/filter_PMF_4.json", 9206, true);
+
+    // with MaxDistance=5
+    test_filter("filters/pcl/filter_PMF_5.json", 9373, true);
+
+    // with InitialDistance=0.25
+    test_filter("filters/pcl/filter_PMF_6.json", 9229, true);
+
+    // with Base=3
+    test_filter("filters/pcl/filter_PMF_7.json", 8298, true);
+
+    // with Exponential=false
+    test_filter("filters/pcl/filter_PMF_8.json", 9138, true);
+
+    // with Negative=true
+    test_filter("filters/pcl/filter_PMF_9.json", 1430, true);
+#endif
+}
+*/
diff --git a/test/unit/OptionsTest.cpp b/test/unit/OptionsTest.cpp
index 03ad0d7..19e7a90 100644
--- a/test/unit/OptionsTest.cpp
+++ b/test/unit/OptionsTest.cpp
@@ -34,10 +34,12 @@
 
 #include <pdal/pdal_test_main.hpp>
 
+#include <json/json.h>
+
 #include <pdal/Options.hpp>
 #include <pdal/PDALUtils.hpp>
-#include <CropFilter.hpp>
-#include <FauxReader.hpp>
+#include <io/FauxReader.hpp>
+#include <filters/CropFilter.hpp>
 
 #include "Support.hpp"
 
@@ -53,11 +55,6 @@ namespace pdal
 
 TEST(OptionsTest, test_option_writing)
 {
-    std::ostringstream ostr_i;
-    const std::string ref_i = xml_header + xml_int_ref;
-    std::ostringstream ostr_s;
-    const std::string ref_s = xml_header + xml_str_ref;
-
     const Option option_i("my_int", (uint16_t)17);
     EXPECT_TRUE(option_i.getName() == "my_int");
     EXPECT_TRUE(option_i.getValue() == "17");
@@ -69,6 +66,24 @@ TEST(OptionsTest, test_option_writing)
 }
 
 
+TEST(OptionsTest, json)
+{
+    // Test that a JSON option will be stringified into the option's underlying
+    // value.
+    Json::Value inJson;
+    inJson["key"] = 42;
+    const Option option_j("my_json", inJson);
+    EXPECT_TRUE(option_j.getName() == "my_json");
+
+    // Don't string-compare, test JSON-equality, since we don't care exactly
+    // how it's stringified.
+    Json::Value outJson;
+    Json::Reader().parse(option_j.getValue(), outJson);
+
+    EXPECT_EQ(inJson, outJson) << inJson << " != " << outJson;
+}
+
+
 TEST(OptionsTest, conditional)
 {
     CropFilter s;
@@ -139,4 +154,18 @@ TEST(OptionsTest, programargs)
     EXPECT_EQ(trueDef, true);
 }
 
+TEST(OptionsTest, nan)
+{
+    ProgramArgs args;
+    double value;
+
+    args.add("value", "Not a number", value);
+
+    Options ops;
+    ops.add("value", std::numeric_limits<double>::quiet_NaN());
+
+    StringList cmdline = ops.toCommandLine();
+    EXPECT_NO_THROW(args.parse(cmdline));
+}
+
 } // namespace pdal
diff --git a/test/unit/PipelineManagerTest.cpp b/test/unit/PipelineManagerTest.cpp
index 3b7ebf5..3538103 100644
--- a/test/unit/PipelineManagerTest.cpp
+++ b/test/unit/PipelineManagerTest.cpp
@@ -85,7 +85,7 @@ TEST(PipelineManagerTest, OptionOrder)
     Options o;
     o.add("filename", Support::temppath("sorted.las"));
     r->setOptions(o);
-    
+
     PointTable t;
     r->prepare(t);
     PointViewSet s = r->execute(t);
@@ -124,3 +124,34 @@ TEST(PipelineManagerTest, OptionOrder)
     FileUtils::deleteFile(Support::temppath("sorted.las"));
 }
 
+// Make sure that when we add an option at the command line, it overrides
+// a pipeline option.
+TEST(PipelineManagerTest, InputGlobbing)
+{
+    std::string cmd = Support::binpath(Support::exename("pdal") +
+        " pipeline");
+
+    std::string file(Support::configuredpath("pipeline/glob.json"));
+
+    std::string output;
+    int stat = Utils::run_shell_command(cmd + " " + file, output);
+    EXPECT_EQ(stat, 0);
+
+    StageFactory f;
+    Stage *r = f.createStage("readers.las");
+
+    Options o;
+    o.add("filename", Support::temppath("globbed.las"));
+    r->setOptions(o);
+
+    PointTable t;
+    r->prepare(t);
+    PointViewSet s = r->execute(t);
+    EXPECT_EQ(s.size(), 1U);
+    PointViewPtr v = *(s.begin());
+
+    EXPECT_EQ(v->size(), 10653U);
+
+    FileUtils::deleteFile(Support::temppath("globbed.las"));
+}
+
diff --git a/test/unit/PointTableTest.cpp b/test/unit/PointTableTest.cpp
index 1e8f0a6..aa4ebe1 100644
--- a/test/unit/PointTableTest.cpp
+++ b/test/unit/PointTableTest.cpp
@@ -35,7 +35,7 @@
 #include <pdal/pdal_test_main.hpp>
 
 #include <pdal/PointTable.hpp>
-#include <las/LasReader.hpp>
+#include <io/LasReader.hpp>
 #include "Support.hpp"
 
 using namespace pdal;
diff --git a/test/unit/ProgramArgsTest.cpp b/test/unit/ProgramArgsTest.cpp
index 55631ca..6ca8fbb 100644
--- a/test/unit/ProgramArgsTest.cpp
+++ b/test/unit/ProgramArgsTest.cpp
@@ -34,6 +34,8 @@
 
 #include <pdal/pdal_test_main.hpp>
 
+#include <json/json.h>
+
 // No wordexp() on windows and I don't feel like doing something special.
 #ifndef _WIN32
 
@@ -268,13 +270,13 @@ TEST(ProgramArgsTest, vector)
 
     std::string m_foo;
     std::vector<int> m_bar;
+    std::vector<int> m_flub;
     bool m_baz;
 
     args.add("foo,f", "Foo description", m_foo, "foo").setPositional();
     args.add("bar", "Foo description", m_bar).setOptionalPositional();
     args.add("baz,z", "Foo description", m_baz);
-
-    // Go through exceptions procedurally.
+    args.add("flub", "Flub description", m_flub, {1, 3, 5});
 
     StringList s = toStringList("--bar 23 --bar 45 Foo -z");
     args.parse(s);
@@ -283,6 +285,10 @@ TEST(ProgramArgsTest, vector)
     EXPECT_EQ(m_baz, true);
     EXPECT_EQ(m_bar[0], 23);
     EXPECT_EQ(m_bar[1], 45);
+    EXPECT_EQ(m_flub.size(), 3u);
+    EXPECT_EQ(m_flub[0], 1);
+    EXPECT_EQ(m_flub[1], 3);
+    EXPECT_EQ(m_flub[2], 5);
 
     args.reset();
     s = toStringList("Foo");
@@ -290,6 +296,7 @@ TEST(ProgramArgsTest, vector)
     EXPECT_EQ(m_bar.size(), 0u);
     EXPECT_EQ(m_foo, "Foo");
     EXPECT_EQ(m_baz, false);
+    EXPECT_EQ(m_flub.size(), 3u);
 
     args.reset();
     s = toStringList("Fool 44 55 66");
@@ -300,6 +307,18 @@ TEST(ProgramArgsTest, vector)
     EXPECT_EQ(m_bar[1], 55);
     EXPECT_EQ(m_bar[2], 66);
     EXPECT_EQ(m_baz, false);
+    EXPECT_EQ(m_flub.size(), 3u);
+
+    args.reset();
+    s = toStringList("--bar 23 --flub 2 Foo -z --flub 4");
+    args.parse(s);
+    EXPECT_EQ(m_foo, "Foo");
+    EXPECT_EQ(m_bar.size(), 1u);
+    EXPECT_EQ(m_baz, true);
+    EXPECT_EQ(m_bar[0], 23);
+    EXPECT_EQ(m_flub.size(), 2u);
+    EXPECT_EQ(m_flub[0], 2);
+    EXPECT_EQ(m_flub[1], 4);
 }
 
 TEST(ProgramArgsTest, stringvector)
@@ -410,4 +429,102 @@ TEST(ProgramArgsTest, parseSimple)
     EXPECT_THROW(args.parse(s), arg_error);
 }
 
+TEST(ProgramArgsTest, json)
+{
+    // Test JSON arguments.  If the arg refers to an underlying Json::Value,
+    // it should be parsed into that Json::Value.  If a string, then that
+    // string should contain the stringified JSON (which should be parsable
+    // into equivalent JSON).
+    Json::Value m_json;
+    std::string m_string;
+    Json::Value verify;
+    verify["key"] = 42;
+
+    const std::string stringified("\"{ \\\"key\\\": 42 }\"");
+
+    ProgramArgs args;
+    args.add("json,j", "JSON description", m_json);
+    args.add("string,s", "String description", m_string);
+
+    StringList s;
+    Json::Reader reader;
+
+    // An unset Json::Value should be null.
+    {
+        s = toStringList("");
+        EXPECT_NO_THROW(args.parse(s));
+        EXPECT_TRUE(m_json.isNull());
+    }
+
+    // Test an object.
+    {
+        StringList s = toStringList(
+                "--json " + stringified + " --string " + stringified);
+        EXPECT_NO_THROW(args.parse(s));
+
+        // Verify the JSON value.
+        EXPECT_EQ(m_json, verify) << m_json << " != " << verify;
+
+        // Verify that the string value parses into the proper JSON value.
+        Json::Value fromString;
+        EXPECT_TRUE(reader.parse(m_string, fromString));
+        EXPECT_EQ(fromString, verify) << fromString << " != " << verify;
+    }
+
+    // Test non-objects.
+    {
+        args.reset();
+        s = toStringList("--json [1,2,3]");
+        EXPECT_NO_THROW(args.parse(s));
+        ASSERT_TRUE(reader.parse("[1, 2, 3]", verify));
+        EXPECT_EQ(m_json, verify);
+
+        args.reset();
+        s = toStringList("--json 2");
+        EXPECT_NO_THROW(args.parse(s));
+        verify = 2;
+        EXPECT_TRUE(m_json.isNumeric());
+        EXPECT_EQ(m_json.asInt(), 2);
+
+        args.reset();
+        s = toStringList("--json 3.14");
+        EXPECT_NO_THROW(args.parse(s));
+        verify = 3.14;
+        EXPECT_TRUE(m_json.isDouble());
+        EXPECT_EQ(m_json.asDouble(), 3.14);
+    }
+}
+
+TEST(ProgramArgsTest, invalidJson)
+{
+    Json::Value m_json;
+    std::string m_string;
+    Json::Value verify;
+    verify["key"] = 42;
+
+    ProgramArgs args;
+    args.add("json,j", "JSON description", m_json);
+    args.add("string,s", "String description", m_string);
+
+    // If the underlying is a string, then invalid JSON is fine during argument
+    // parsing, but will fail later if parsed as JSON.
+    args.reset();
+    StringList s = toStringList("--string \"{ invalid JSON here }\"");
+    EXPECT_NO_THROW(args.parse(s));
+
+    // The string was successfully parsed, but contains invalid JSON.
+    Json::Value invalidParse;
+    EXPECT_FALSE(Json::Reader().parse(m_string, invalidParse));
+
+    // If the underlying is a Json::Value, then argument parsing should fail
+    // up front.
+    args.reset();
+    s = toStringList("--json \"{ invalid JSON here }\"");
+    EXPECT_ANY_THROW(args.parse(s));
+
+    args.reset();
+    s = toStringList("--json");
+    EXPECT_ANY_THROW(args.parse(s));
+}
+
 #endif
diff --git a/test/unit/SpatialReferenceTest.cpp b/test/unit/SpatialReferenceTest.cpp
index 6c26575..2ea9cc1 100644
--- a/test/unit/SpatialReferenceTest.cpp
+++ b/test/unit/SpatialReferenceTest.cpp
@@ -37,10 +37,10 @@
 #include <pdal/SpatialReference.hpp>
 #include <pdal/Polygon.hpp>
 #include <pdal/util/FileUtils.hpp>
-#include <ReprojectionFilter.hpp>
-#include <MergeFilter.hpp>
-#include <LasWriter.hpp>
-#include <LasReader.hpp>
+#include <filters/ReprojectionFilter.hpp>
+#include <filters/MergeFilter.hpp>
+#include <io/LasWriter.hpp>
+#include <io/LasReader.hpp>
 
 #include <gdal_version.h>
 
@@ -83,22 +83,19 @@ TEST(SpatialReferenceTest, test_proj4_roundtrip)
     };
 
     {
-        SpatialReference ref;
-        ref.setProj4(proj4);
+        SpatialReference ref(proj4);
         EXPECT_TRUE(!ref.empty());
         const std::string ret = ref.getProj4();
     }
 
     {
-        SpatialReference ref;
-        ref.setProj4(proj4_ellps);
+        SpatialReference ref(proj4_ellps);
         const std::string ret = ref.getProj4();
         EXPECT_TRUE(Utils::contains(proj4_out, ret));
     }
 
     {
-        SpatialReference ref;
-        ref.setProj4(proj4_out.front());
+        SpatialReference ref(proj4_out.front());
         const std::string ret = ref.getProj4();
         EXPECT_TRUE(Utils::contains(proj4_out, ret));
     }
@@ -108,14 +105,12 @@ TEST(SpatialReferenceTest, test_proj4_roundtrip)
 // Test setting EPSG:4326 from User string
 TEST(SpatialReferenceTest, test_userstring_roundtrip)
 {
-    SpatialReference ref;
-
     std::string code = "EPSG:4326";
     std::string proj4 = "+proj=longlat +datum=WGS84 +no_defs";
     std::string proj4_ellps =
         "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs";
     const std::string wkt = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]";
-    ref.setFromUserInput(code);
+    SpatialReference ref(code);
 
     std::string ret_proj = ref.getProj4();
     std::string ret_wkt = ref.getWKT();
@@ -128,11 +123,9 @@ TEST(SpatialReferenceTest, test_userstring_roundtrip)
 // Test fetching UTM zone
 TEST(SpatialReferenceTest, test_get_utmzone)
 {
-    SpatialReference ref;
-
     // from test/data/autzen-srs.wkt
     std::string code = "+proj=lcc +lat_1=43 +lat_2=45.5 +lat_0=41.75 +lon_0=-120.5 +x_0=399999.9999999999 +y_0=0 +ellps=GRS80 +units=ft +no_defs";
-    ref.setFromUserInput(code);
+    SpatialReference ref(code);
 
     BOX3D box(635589.01, 848886.45, 638994.75, 853535.43, 0, 0);
 
@@ -161,7 +154,6 @@ TEST(SpatialReferenceTest, calcZone)
 }
 
 
-#if defined(PDAL_HAVE_LIBGEOTIFF)
 // Test fetching SRS from an existing file
 TEST(SpatialReferenceTest, test_read_srs)
 {
@@ -189,10 +181,8 @@ TEST(SpatialReferenceTest, test_read_srs)
     std::string proj4 = "+proj=utm +zone=17 +datum=WGS84 +units=m +no_defs";
     EXPECT_EQ(ret_proj4, proj4);
 }
-#endif
 
 
-#ifdef PDAL_HAVE_LIBGEOTIFF
 
 // Try writing a compound coordinate system to file and ensure we get back
 // WKT with the geoidgrids (from the WKT VLR).
@@ -208,9 +198,8 @@ TEST(SpatialReferenceTest, test_vertical_datums)
 
     const std::string wkt = "COMPD_CS[\"unknown\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433],AUTHORITY[\"EPSG\",\"4326\"]],VERT_CS[\"NAVD88 height\",VERT_DATUM[\"North American Vertical Datum 1988\",2005,AUTHORITY[\"EPSG\",\"5103\"],EXTENSION[\"PROJ4_GRIDS\",\"g2012a_conus.gtx,g2012a_alaska.gtx,g2012a_guam.gtx,g2012a_hawaii.gtx,g2012a [...]
 
-    SpatialReference ref;
-    ref.setFromUserInput(wkt);
-    const std::string wktCheck = ref.getWKT(SpatialReference::eCompoundOK);
+    SpatialReference ref(wkt);
+    const std::string wktCheck = ref.getWKT();
     EXPECT_EQ(wkt, wktCheck); // just to make sure
 
     PointTable table;
@@ -239,7 +228,7 @@ TEST(SpatialReferenceTest, test_vertical_datums)
     reader2.execute(table2);
 
     const SpatialReference ref2 = reader2.getSpatialReference();
-    const std::string wkt2 = ref2.getWKT(SpatialReference::eCompoundOK);
+    const std::string wkt2 = ref2.getWKT();
 
     EXPECT_EQ(wkt, wkt2);
 
@@ -247,20 +236,17 @@ TEST(SpatialReferenceTest, test_vertical_datums)
     FileUtils::deleteFile(tmpfile);
 }
 **/
-#endif //PDAL_HAVE_LIBGEOTIFF
 
 
-#if defined(PDAL_HAVE_LIBGEOTIFF)
 // Try writing only the WKT VLR to a file, and see if the resulting
 // file still works ok.
 TEST(SpatialReferenceTest, test_writing_vlr)
 {
     std::string tmpfile(Support::temppath("tmp_srs_9.las"));
-    SpatialReference ref;
 
     const std::string reference_wkt = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]";
 
-    ref.setFromUserInput("EPSG:4326");
+    SpatialReference ref("EPSG:4326");
     std::string wkt = ref.getWKT();
     EXPECT_EQ(wkt, reference_wkt);
 
@@ -308,15 +294,13 @@ TEST(SpatialReferenceTest, test_writing_vlr)
     // Cleanup
     FileUtils::deleteFile(tmpfile);
 }
-#endif
 
 
 TEST(SpatialReferenceTest, test_io)
 {
     const std::string wkt = "COMPD_CS[\"WGS 84 + VERT_CS\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],VERT_CS[\"NAVD88 height\",VERT_DATUM[\"North American Vertical Datum 1988\",2005,AUTHORITY[\"EPSG\",\"5103\"],EXTENSION[\"PROJ4_GRIDS\",\"g2003con [...]
 
-    SpatialReference ref;
-    ref.setFromUserInput(wkt);
+    SpatialReference ref(wkt);
 
     std::stringstream ss(std::stringstream::in | std::stringstream::out);
 
@@ -344,7 +328,6 @@ TEST(SpatialReferenceTest, test_vertical_and_horizontal)
 
 }
 
-#if defined(PDAL_HAVE_LIBGEOTIFF)
 TEST(SpatialReferenceTest, merge)
 {
     Options o1;
@@ -392,7 +375,6 @@ TEST(SpatialReferenceTest, merge)
         Support::datapath("las/test_epsg_4326x3.las"));
 }
 
-#endif
 
 TEST(SpatialReferenceTest, test_bounds)
 {
diff --git a/test/unit/StreamingTest.cpp b/test/unit/StreamingTest.cpp
index 25c05a6..8994d36 100644
--- a/test/unit/StreamingTest.cpp
+++ b/test/unit/StreamingTest.cpp
@@ -36,9 +36,9 @@
 
 #include <pdal/Filter.hpp>
 #include <pdal/PointTable.hpp>
-#include <FauxReader.hpp>
-#include <MergeFilter.hpp>
-#include <StreamCallbackFilter.hpp>
+#include <io/FauxReader.hpp>
+#include <filters/MergeFilter.hpp>
+#include <filters/StreamCallbackFilter.hpp>
 #include "Support.hpp"
 
 using namespace pdal;
diff --git a/test/unit/XMLSchemaTest.cpp b/test/unit/XMLSchemaTest.cpp
index 6d77e64..9d042be 100644
--- a/test/unit/XMLSchemaTest.cpp
+++ b/test/unit/XMLSchemaTest.cpp
@@ -91,43 +91,43 @@ TEST(XMLSchemaTest, read)
     XMLDim dim = getDim(dims, "X");
     DimType dt = dim.m_dimType;
     EXPECT_EQ(dim.m_name,  "X");
-    EXPECT_FLOAT_EQ(dt.m_xform.m_scale.m_val, .01);
-    EXPECT_FLOAT_EQ(dt.m_xform.m_offset.m_val, 0.0);
+    EXPECT_DOUBLE_EQ(dt.m_xform.m_scale.m_val, .01);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_offset.m_val, 0.0);
     EXPECT_EQ(dt.m_type, Dimension::Type::Signed32);
 
     dim = getDim(dims, "Y");
     dt = dim.m_dimType;
     EXPECT_EQ(dim.m_name, "Y");
-    EXPECT_FLOAT_EQ(dt.m_xform.m_scale.m_val, .01);
-    EXPECT_FLOAT_EQ(dt.m_xform.m_offset.m_val, 0.0);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_scale.m_val, .01);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_offset.m_val, 0.0);
     EXPECT_EQ(dt.m_type, Dimension::Type::Signed32);
 
     dim = getDim(dims, "Z");
     dt = dim.m_dimType;
     EXPECT_EQ(dim.m_name, "Z");
-    EXPECT_FLOAT_EQ(dt.m_xform.m_scale.m_val, .01);
-    EXPECT_FLOAT_EQ(dt.m_xform.m_offset.m_val, 0.0);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_scale.m_val, .01);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_offset.m_val, 0.0);
     EXPECT_EQ(dt.m_type, Dimension::Type::Signed32);
 
     dim = getDim(dims, "Intensity");
     dt = dim.m_dimType;
     EXPECT_EQ(dim.m_name, "Intensity");
-    EXPECT_FLOAT_EQ(dt.m_xform.m_scale.m_val, 1.0);
-    EXPECT_FLOAT_EQ(dt.m_xform.m_offset.m_val, 0.0);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_scale.m_val, 1.0);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_offset.m_val, 0.0);
     EXPECT_EQ(dt.m_type, Dimension::Type::Unsigned16);
 
     dim = getDim(dims, "ReturnNumber");
     dt = dim.m_dimType;
     EXPECT_EQ(dim.m_name, "ReturnNumber");
-    EXPECT_FLOAT_EQ(dt.m_xform.m_scale.m_val, 1.0);
-    EXPECT_FLOAT_EQ(dt.m_xform.m_offset.m_val, 0.0);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_scale.m_val, 1.0);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_offset.m_val, 0.0);
     EXPECT_EQ(dt.m_type, Dimension::Type::Unsigned8);
 
     dim = getDim(dims, "NumberOfReturns");
     dt = dim.m_dimType;
     EXPECT_EQ(dim.m_name, "NumberOfReturns");
-    EXPECT_FLOAT_EQ(dt.m_xform.m_scale.m_val, 1.0);
-    EXPECT_FLOAT_EQ(dt.m_xform.m_offset.m_val, 0.0);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_scale.m_val, 1.0);
+	EXPECT_DOUBLE_EQ(dt.m_xform.m_offset.m_val, 0.0);
     EXPECT_EQ(dt.m_type, Dimension::Type::Unsigned8);
 }
 
diff --git a/test/unit/apps/AppTest.cpp b/test/unit/apps/AppTest.cpp
new file mode 100644
index 0000000..af1be0a
--- /dev/null
+++ b/test/unit/apps/AppTest.cpp
@@ -0,0 +1,67 @@
+/******************************************************************************
+* Copyright (c) 2016, Hobu Inc., (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the names of contributors
+*       may be used to endorse or promote products derived from this
+*       software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <string>
+
+#include <pdal/pdal_test_main.hpp>
+
+#include "Support.hpp"
+
+namespace
+{
+std::string appName()
+{
+    return Support::binpath("pdal");
+}
+} // unnamed namespace
+
+namespace pdal
+{
+
+TEST(PdalApp, log)
+{
+    std::string output;
+
+    Utils::run_shell_command(appName() + " -v Debug 2>&1", output);
+    EXPECT_TRUE(output.find("PDAL Debug: 3") != std::string::npos);
+
+    output.clear();
+    Utils::run_shell_command(appName() + " --verbose=3 2>&1", output);
+    EXPECT_TRUE(output.find("PDAL Debug: 3") != std::string::npos);
+
+    output.clear();
+    Utils::run_shell_command(appName() + " 2>&1", output);
+    EXPECT_TRUE(output.find("PDAL Debug: 3") == std::string::npos);
+}
+
+} // unnamed namespace
diff --git a/test/unit/apps/HausdorffTest.cpp b/test/unit/apps/HausdorffTest.cpp
new file mode 100644
index 0000000..04483cf
--- /dev/null
+++ b/test/unit/apps/HausdorffTest.cpp
@@ -0,0 +1,94 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <string>
+
+#include <pdal/pdal_test_main.hpp>
+#include <pdal/PDALUtils.hpp>
+#include <pdal/PointView.hpp>
+
+#include "Support.hpp"
+
+using namespace pdal;
+
+TEST(Hausdorff, kernel)
+{
+    std::string A = Support::datapath("autzen/autzen-thin.las");
+    std::string B = Support::datapath("las/autzen_trim.las");
+    std::string output;
+
+    const std::string cmd = Support::binpath(Support::exename("pdal")) +
+                            " hausdorff " + A + " " + B;
+
+    EXPECT_EQ(Utils::run_shell_command(cmd, output), 0);
+    EXPECT_TRUE(output.find("\"hausdorff\": 4416.968175") != std::string::npos);
+}
+
+TEST(Hausdorff, distance)
+{
+    PointTable table;
+    PointLayoutPtr layout(table.layout());
+
+    layout->registerDim(Dimension::Id::X);
+    layout->registerDim(Dimension::Id::Y);
+    layout->registerDim(Dimension::Id::Z);
+
+    PointViewPtr src(new PointView(table));
+    src->setField(Dimension::Id::X, 0, 0.0);
+    src->setField(Dimension::Id::Y, 0, 0.0);
+    src->setField(Dimension::Id::Z, 0, 0.0);
+
+    PointViewPtr cand(new PointView(table));
+    cand->setField(Dimension::Id::X, 0, 1.0);
+    cand->setField(Dimension::Id::Y, 0, 0.0);
+    cand->setField(Dimension::Id::Z, 0, 0.0);
+
+    cand->setField(Dimension::Id::X, 1, 0.0);
+    cand->setField(Dimension::Id::Y, 1, 2.0);
+    cand->setField(Dimension::Id::Z, 1, 0.0);
+
+    EXPECT_EQ(2.0, Utils::computeHausdorff(src, cand));
+
+    cand->setField(Dimension::Id::X, 1, 0.0);
+    cand->setField(Dimension::Id::Y, 1, 0.0);
+    cand->setField(Dimension::Id::Z, 1, 3.0);
+
+    EXPECT_EQ(3.0, Utils::computeHausdorff(src, cand));
+
+    src->setField(Dimension::Id::X, 0, 1.0);
+    src->setField(Dimension::Id::Y, 0, 1.0);
+    src->setField(Dimension::Id::Z, 0, 1.0);
+
+    EXPECT_EQ(std::sqrt(6.0), Utils::computeHausdorff(src, cand));
+}
diff --git a/test/unit/apps/MergeTest.cpp b/test/unit/apps/MergeTest.cpp
index b9404da..2d0c3b5 100644
--- a/test/unit/apps/MergeTest.cpp
+++ b/test/unit/apps/MergeTest.cpp
@@ -57,7 +57,7 @@ TEST(Merge, pdalinfoTest_no_input)
     std::string output;
     EXPECT_EQ(Utils::run_shell_command(cmd, output), 1);
 
-    const std::string expected = "PDAL: Missing value for positional "
+    const std::string expected = "PDAL: kernels.merge: Missing value for positional "
         "argument 'files'.";
     EXPECT_EQ(output.substr(0, expected.length()), expected);
 }
diff --git a/test/unit/apps/RandomTest.cpp b/test/unit/apps/RandomTest.cpp
index f967fdd..f26a852 100644
--- a/test/unit/apps/RandomTest.cpp
+++ b/test/unit/apps/RandomTest.cpp
@@ -38,7 +38,7 @@
 #include <pdal/util/FileUtils.hpp>
 #include <pdal/util/Utils.hpp>
 #include <pdal/PDALUtils.hpp>
-#include <LasReader.hpp>
+#include <io/LasReader.hpp>
 
 #include "Support.hpp"
 
diff --git a/test/unit/apps/TranslateTest.cpp b/test/unit/apps/TranslateTest.cpp
new file mode 100644
index 0000000..ddf925b
--- /dev/null
+++ b/test/unit/apps/TranslateTest.cpp
@@ -0,0 +1,121 @@
+/******************************************************************************
+* Copyright (c) 2016, Hobu Inc.
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/StageFactory.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/util/Utils.hpp>
+#include "Support.hpp"
+
+#include <iostream>
+#include <sstream>
+#include <string>
+
+using namespace pdal;
+
+static int runTranslate(std::string const& cmdline, std::string& output)
+{
+    const std::string cmd = Support::binpath(Support::exename("pdal")) +
+        " translate";
+
+    return Utils::run_shell_command(cmd + " " + cmdline, output);
+}
+
+TEST(translateTest, t1)
+{
+    std::string output;
+
+    std::string in = Support::datapath("las/autzen_trim.las");
+    std::string out = Support::temppath("out.las");
+
+    EXPECT_EQ(runTranslate(in + " " + out, output), 0);
+    EXPECT_EQ(runTranslate(in + " -f filters.stats " + out, output), 0);
+    EXPECT_EQ(runTranslate(in + " " + out + " stats", output), 0);
+    EXPECT_EQ(runTranslate(in + " " + out + " filters.stats", output), 0);
+    EXPECT_NE(runTranslate(in + " " + out + " foobar", output), 0);
+    EXPECT_NE(runTranslate(in + " " + out +
+        " filters.stats --json filters.stats", output), 0);
+    EXPECT_NE(runTranslate(in + " " + out +
+        " --json filters.stats", output), 0);
+}
+
+TEST(translateTest, t2)
+{
+    std::string output;
+
+    std::string in = Support::datapath("las/autzen_trim.las");
+    std::string out = Support::temppath("out.las");
+
+    const char *json = " \
+        [ \
+        { \\\"type\\\":\\\"filters.stats\\\" }, \
+        { \\\"type\\\":\\\"filters.range\\\", \
+          \\\"limits\\\":\\\"Z[0:100]\\\" } \
+        ]";
+
+    EXPECT_EQ(runTranslate(in + " " + out +
+        " --json=\"" + json + "\"", output), 0);
+    EXPECT_EQ(runTranslate(in + " " + out + " -r readers.las "
+        " --json=\"" + json + "\"", output), 0);
+    EXPECT_EQ(runTranslate(in + " " + out + " -w writers.las "
+        " --json=\"" + json + "\"", output), 0);
+    EXPECT_EQ(runTranslate(in + " " + out + " -r readers.las -w writers.las "
+        " --json=\"" + json + "\"", output), 0);
+
+    const char *json2 = " \
+        { \\\"type\\\":\\\"filters.stats\\\" }, \
+        { \\\"type\\\":\\\"filters.range\\\", \
+          \\\"limits\\\":\\\"Z[0:100]\\\" }";
+
+    EXPECT_NE(runTranslate(in + " " + out +
+        " --json=\"" + json2 + "\"", output), 0);
+}
+
+TEST(translateTest, t3)
+{
+    std::string output;
+
+    std::string in = Support::datapath("las/autzen_trim.las");
+    std::string out = Support::temppath("out.las");
+    std::string meta = Support::temppath("meta.json");
+
+    EXPECT_EQ(runTranslate(in + " " + out + " --metadata " + meta, output), 0);
+#ifndef WIN32
+    Utils::run_shell_command("grep -c readers.las " + meta, output);
+    EXPECT_EQ(std::stoi(output), 1);
+    Utils::run_shell_command("grep -c writers.las " + meta, output);
+    EXPECT_EQ(std::stoi(output), 1);
+#endif
+}
diff --git a/test/unit/apps/pc2pcTest.cpp b/test/unit/apps/pc2pcTest.cpp
index 66219fc..c74a61b 100644
--- a/test/unit/apps/pc2pcTest.cpp
+++ b/test/unit/apps/pc2pcTest.cpp
@@ -32,17 +32,16 @@
 * OF SUCH DAMAGE.
 ****************************************************************************/
 
+#include <iostream>
+#include <sstream>
+#include <string>
+
 #include <pdal/pdal_test_main.hpp>
 
 #include <pdal/util/FileUtils.hpp>
-#include <LasReader.hpp>
-
+#include <io/LasReader.hpp>
 #include "Support.hpp"
 
-#include <iostream>
-#include <sstream>
-#include <string>
-
 using namespace pdal;
 
 static std::string appName()
@@ -61,7 +60,7 @@ TEST(pc2pcTest, pc2pcTest_test_no_input)
     int stat = Utils::run_shell_command(cmd, output);
     EXPECT_EQ(stat, 1);
 
-    const std::string expected = "PDAL: Missing value for positional argument 'input'.";
+    const std::string expected = "PDAL: kernels.translate: Missing value for positional argument 'input'.";
     EXPECT_EQ(output.substr(0, expected.length()), expected);
 }
 #endif
@@ -145,11 +144,7 @@ TEST(pc2pcTest, pc2pc_test_switches)
     EXPECT_EQ(stat, 0);
     EXPECT_TRUE(fileIsOkay(outputLas));
     EXPECT_TRUE(!fileIsCompressed(outputLas));
-#ifdef PDAL_HAVE_LIBGEOTIFF
     EXPECT_TRUE(!fileHasSrs(outputLas));
-#else
-    (void)fileHasSrs(outputLas);
-#endif
 
 #ifdef PDAL_HAVE_LASZIP
     // does --compress make a compressed file?
@@ -175,9 +170,7 @@ TEST(pc2pcTest, pc2pc_test_switches)
     stat = Utils::run_shell_command(fullCmd, output);
     EXPECT_EQ(stat, 0);
     EXPECT_TRUE(fileIsOkay(outputLas));
-#ifdef PDAL_HAVE_LIBGEOTIFF
     EXPECT_TRUE(fileHasSrs(outputLas));
-#endif
 
     FileUtils::deleteFile(outputLas);
     FileUtils::deleteFile(outputLaz);
diff --git a/test/unit/apps/pcpipelineTestJSON.cpp b/test/unit/apps/pcpipelineTestJSON.cpp
index 9eb6e69..dbee2ad 100644
--- a/test/unit/apps/pcpipelineTestJSON.cpp
+++ b/test/unit/apps/pcpipelineTestJSON.cpp
@@ -34,6 +34,7 @@
 
 #include <pdal/pdal_test_main.hpp>
 
+#include <pdal/PipelineManager.hpp>
 #include <pdal/StageFactory.hpp>
 #include <pdal/util/FileUtils.hpp>
 #include <pdal/util/Utils.hpp>
@@ -43,7 +44,10 @@
 #include <sstream>
 #include <string>
 
-static std::string appName()
+namespace
+{
+
+std::string appName()
 {
     const std::string app = Support::binpath(Support::exename("pdal") +
         " pipeline");
@@ -51,25 +55,27 @@ static std::string appName()
 }
 
 // most pipelines (those with a writer) will be invoked via `pdal pipeline`
-static void run_pipeline(std::string const& pipeline)
+void run_pipeline(std::string const& pipelineFile,
+    const std::string options = std::string())
 {
     const std::string cmd = appName();
 
     std::string output;
-    std::string file(Support::configuredpath(pipeline));
-    int stat = pdal::Utils::run_shell_command(cmd + " " + file, output);
+    std::string file(Support::configuredpath(pipelineFile));
+    int stat = pdal::Utils::run_shell_command(cmd + " " + file + " " + options,
+        output);
     EXPECT_EQ(0, stat);
     if (stat)
         std::cerr << output << std::endl;
 }
 
 // most pipelines (those with a writer) will be invoked via `pdal pipeline`
-static void run_pipeline_stdin(std::string const& pipeline)
+void run_pipeline_stdin(std::string const& pipelineFile)
 {
     const std::string cmd = appName();
 
     std::string output;
-    std::string file(Support::configuredpath(pipeline));
+    std::string file(Support::configuredpath(pipelineFile));
     int stat = pdal::Utils::run_shell_command(cmd + " --stdin < " + file,
         output);
     EXPECT_EQ(0, stat);
@@ -77,6 +83,11 @@ static void run_pipeline_stdin(std::string const& pipeline)
         std::cerr << output << std::endl;
 }
 
+} // unnamed namespace
+
+namespace pdal
+{
+
 TEST(pipelineBaseTest, no_input)
 {
     const std::string cmd = appName();
@@ -219,30 +230,32 @@ INSTANTIATE_TEST_CASE_P(plugins, jsonWithLAZ,
                             "pipeline/crop-stats.json"
                         ));
 
-// TEST(pipelineFiltersTest, DISABLED_crop_reproject)
-// { run_pipeline("filters/crop_reproject.xml"); }
-
-// TEST(pipelineIcebridgeTest, DISABLED_icebridge)
-// { run_pipeline("icebridge/pipeline.xml"); }
-
-// TEST(pipelineNitfTest, DISABLED_reader)
-// { run_info("nitf/reader.xml"); }
-
-// skip oracle tests for now
-
-// TEST(pipelineQfitTest, DISABLED_little_endian_conversion)
-// { run_pipeline("qfit/little-endian-conversion.xml"); }
-
-// TEST(pipelineQfitTest, DISABLED_pipeline)
-// { run_pipeline("qfit/pipeline.xml"); }
-
-// TEST(pipelineQfitTest, DISABLED_reader)
-// { run_info("qfit/reader.xml"); }
-
-// skip soci tests for now
+TEST(json, tags)
+{
+    PipelineManager manager;
+
+    manager.readPipeline(Support::configuredpath("pipeline/tags.json"));
+    std::vector<Stage *> stages = manager.m_stages;
+
+    EXPECT_EQ(stages.size(), 4u);
+    size_t totalInputs(0);
+    for (Stage *s : stages)
+    {
+        std::vector<Stage *> inputs = s->getInputs();
+        if (inputs.empty())
+            EXPECT_EQ(s->getName(), "readers.las");
+        if (inputs.size() == 1 || inputs.size() == 2)
+            EXPECT_EQ(s->getName(), "writers.las");
+        totalInputs += inputs.size();
+    }
+    EXPECT_EQ(totalInputs, 3U);
+}
 
-// TEST(pipelineSQLiteTest, DISABLED_reader)
-// { run_pipeline("io/sqlite-reader.xml"); }
+TEST(json, issue1417)
+{
+    std::string options = "--readers.las.filename=" +
+        Support::datapath("las/utm15.las");
+    run_pipeline("pipeline/issue1417.json", options);
+}
 
-// TEST(pipelineSQLiteTest, DISABLED_writer)
-// { run_pipeline("io/sqlite-writer.xml"); }
+} // namespace pdal
diff --git a/test/unit/filters/AdditionalMergeTest.cpp b/test/unit/filters/AdditionalMergeTest.cpp
index aca15c8..1f2af4a 100644
--- a/test/unit/filters/AdditionalMergeTest.cpp
+++ b/test/unit/filters/AdditionalMergeTest.cpp
@@ -38,10 +38,10 @@
 
 #include <pdal/PipelineManager.hpp>
 #include <pdal/util/FileUtils.hpp>
-#include <LasReader.hpp>
-#include <LasWriter.hpp>
-#include <DecimationFilter.hpp>
-#include <MergeFilter.hpp>
+#include <io/LasReader.hpp>
+#include <io/LasWriter.hpp>
+#include <filters/DecimationFilter.hpp>
+#include <filters/MergeFilter.hpp>
 
 using namespace pdal;
 
diff --git a/test/unit/filters/ChipperTest.cpp b/test/unit/filters/ChipperTest.cpp
index 7bc3293..772bafc 100644
--- a/test/unit/filters/ChipperTest.cpp
+++ b/test/unit/filters/ChipperTest.cpp
@@ -34,11 +34,11 @@
 
 #include <pdal/pdal_test_main.hpp>
 
-#include <ChipperFilter.hpp>
-#include <LasWriter.hpp>
-#include <LasReader.hpp>
 #include <pdal/Options.hpp>
 #include <pdal/StageWrapper.hpp>
+#include <filters/ChipperFilter.hpp>
+#include <io/LasWriter.hpp>
+#include <io/LasReader.hpp>
 
 #include "Support.hpp"
 
diff --git a/test/unit/filters/ColorizationFilterTest.cpp b/test/unit/filters/ColorizationFilterTest.cpp
index 6cdcc39..9a5c856 100644
--- a/test/unit/filters/ColorizationFilterTest.cpp
+++ b/test/unit/filters/ColorizationFilterTest.cpp
@@ -34,10 +34,10 @@
 
 #include <pdal/pdal_test_main.hpp>
 
-#include <LasReader.hpp>
-#include <ColorizationFilter.hpp>
-#include <StreamCallbackFilter.hpp>
 #include <pdal/PointView.hpp>
+#include <io/LasReader.hpp>
+#include <filters/ColorizationFilter.hpp>
+#include <filters/StreamCallbackFilter.hpp>
 
 #include "Support.hpp"
 
diff --git a/test/unit/filters/ComputeRangeFilterTest.cpp b/test/unit/filters/ComputeRangeFilterTest.cpp
new file mode 100644
index 0000000..5c52421
--- /dev/null
+++ b/test/unit/filters/ComputeRangeFilterTest.cpp
@@ -0,0 +1,99 @@
+/******************************************************************************
+* Copyright (c) 2016, Bradley J Chambers (brad.chambers at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/util/FileUtils.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/StageFactory.hpp>
+#include <io/BufferReader.hpp>
+#include <filters/ComputeRangeFilter.hpp>
+
+#include "Support.hpp"
+
+using namespace pdal;
+
+TEST(ComputeRangeFilterTest, create)
+{
+    StageFactory f;
+    Stage* filter(f.createStage("filters.computerange"));
+    EXPECT_TRUE(filter);
+}
+
+TEST(ComputeRangeFilterTest, compute)
+{
+    using namespace Dimension;
+
+    PointTable table;
+    PointLayoutPtr layout(table.layout());
+
+    layout->registerDim(Id::X);
+    layout->registerDim(Id::Y);
+    layout->registerDim(Id::Z);
+    Id pn = layout->registerOrAssignDim("Pixel Number", Type::Double);
+    Id fn = layout->registerOrAssignDim("Frame Number", Type::Double);
+
+    PointViewPtr view(new PointView(table));
+
+    BufferReader r;
+    r.addView(view);
+
+    ComputeRangeFilter crop;
+    crop.setInput(r);
+    crop.prepare(table);
+
+    view->setField(Id::X, 0, 0.0);
+    view->setField(Id::Y, 0, 0.0);
+    view->setField(Id::Z, 0, 0.0);
+    view->setField(pn, 0, 0.0);
+    view->setField(fn, 0, 0.0);
+
+    view->setField(Id::X, 1, 0.0);
+    view->setField(Id::Y, 1, 3.0);
+    view->setField(Id::Z, 1, 4.0);
+    view->setField(pn, 1, -5.0);
+    view->setField(fn, 1, 0.0);
+
+    PointViewSet s = crop.execute(table);
+    EXPECT_EQ(1u, s.size());
+
+    Id range = layout->findDim("Range");
+    EXPECT_NE(Id::Unknown, range);
+
+    PointViewPtr out = *s.begin();
+    EXPECT_EQ(2u, out->size());
+
+    EXPECT_EQ(5.0, out->getFieldAs<double>(range, 0));
+    EXPECT_EQ(0.0, out->getFieldAs<double>(range, 1));
+}
diff --git a/test/unit/filters/CropFilterTest.cpp b/test/unit/filters/CropFilterTest.cpp
index 9bd68d6..217e8e7 100644
--- a/test/unit/filters/CropFilterTest.cpp
+++ b/test/unit/filters/CropFilterTest.cpp
@@ -37,13 +37,13 @@
 #include <pdal/util/FileUtils.hpp>
 #include <pdal/PointView.hpp>
 #include <pdal/StageFactory.hpp>
-#include <BufferReader.hpp>
-#include <CropFilter.hpp>
-#include <FauxReader.hpp>
-#include <LasReader.hpp>
-#include <ReprojectionFilter.hpp>
-#include <StatsFilter.hpp>
-#include <StreamCallbackFilter.hpp>
+#include <io/BufferReader.hpp>
+#include <io/FauxReader.hpp>
+#include <io/LasReader.hpp>
+#include <filters/ReprojectionFilter.hpp>
+#include <filters/CropFilter.hpp>
+#include <filters/StatsFilter.hpp>
+#include <filters/StreamCallbackFilter.hpp>
 #include "Support.hpp"
 
 using namespace pdal;
@@ -350,3 +350,42 @@ TEST(CropFilterTest, stream)
     f.execute(table);
 }
 
+
+TEST(CropFilterTest, test_sphere)
+{
+    BOX3D srcBounds(0.0, 0.0, 0.0, 10.0, 100.0, 1000.0);
+    Options opts;
+    opts.add("bounds", srcBounds);
+    opts.add("count", 1000);
+    opts.add("mode", "ramp");
+    FauxReader reader;
+    reader.setOptions(opts);
+
+    // crop the window to 1/3rd the size in each dimension
+    BOX2D dstBounds(3.33333, 33.33333, 6.66666, 66.66666);
+    Options cropOpts;
+    cropOpts.add("distance", 10.0);
+    cropOpts.add("point", "POINT (4.3 43.0 500)");
+
+    CropFilter filter;
+    filter.setOptions(cropOpts);
+    filter.setInput(reader);
+
+    Options statOpts;
+
+    StatsFilter stats;
+    stats.setOptions(statOpts);
+    stats.setInput(filter);
+
+    PointTable table;
+    stats.prepare(table);
+    PointViewSet viewSet = stats.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr buf = *viewSet.begin();
+
+    const stats::Summary& statsX = stats.getStats(Dimension::Id::X);
+    const stats::Summary& statsY = stats.getStats(Dimension::Id::Y);
+    const stats::Summary& statsZ = stats.getStats(Dimension::Id::Z);
+    EXPECT_EQ(buf->size(), 14u);
+
+}
diff --git a/test/unit/filters/DecimationFilterTest.cpp b/test/unit/filters/DecimationFilterTest.cpp
index 33332ca..66e766b 100644
--- a/test/unit/filters/DecimationFilterTest.cpp
+++ b/test/unit/filters/DecimationFilterTest.cpp
@@ -36,9 +36,9 @@
 
 #include <pdal/PointView.hpp>
 #include <pdal/StageFactory.hpp>
-#include <DecimationFilter.hpp>
-#include <FauxReader.hpp>
-#include <StreamCallbackFilter.hpp>
+#include <io/FauxReader.hpp>
+#include <filters/DecimationFilter.hpp>
+#include <filters/StreamCallbackFilter.hpp>
 
 using namespace pdal;
 
diff --git a/test/unit/filters/DividerFilterTest.cpp b/test/unit/filters/DividerFilterTest.cpp
index 2f85e0c..2854d68 100644
--- a/test/unit/filters/DividerFilterTest.cpp
+++ b/test/unit/filters/DividerFilterTest.cpp
@@ -34,8 +34,8 @@
 
 #include <pdal/pdal_test_main.hpp>
 
-#include <FauxReader.hpp>
-#include <DividerFilter.hpp>
+#include <io/FauxReader.hpp>
+#include <filters/DividerFilter.hpp>
 
 using namespace pdal;
 
diff --git a/test/unit/filters/FerryFilterTest.cpp b/test/unit/filters/FerryFilterTest.cpp
index faec3af..d6c53c2 100644
--- a/test/unit/filters/FerryFilterTest.cpp
+++ b/test/unit/filters/FerryFilterTest.cpp
@@ -37,10 +37,10 @@
 #include <pdal/PointView.hpp>
 #include <pdal/StageFactory.hpp>
 #include <pdal/PipelineManager.hpp>
-#include <FauxReader.hpp>
-#include <FerryFilter.hpp>
-#include <LasReader.hpp>
-#include <StreamCallbackFilter.hpp>
+#include <io/FauxReader.hpp>
+#include <io/LasReader.hpp>
+#include <filters/FerryFilter.hpp>
+#include <filters/StreamCallbackFilter.hpp>
 #include "Support.hpp"
 
 using namespace pdal;
@@ -52,34 +52,6 @@ TEST(FerryFilterTest, create)
     EXPECT_TRUE(filter);
 }
 
-TEST(FerryFilterTest, test_ferry_copy_xml)
-{
-    PipelineManager mgr;
-    mgr.readPipeline(Support::configuredpath("filters/ferry.xml"));
-
-    mgr.execute();
-    ConstPointTableRef table(mgr.pointTable());
-
-    PointViewSet viewSet = mgr.views();
-
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 1065u);
-
-    Dimension::Id state_plane_x = table.layout()->findDim("StatePlaneX");
-    Dimension::Id state_plane_y = table.layout()->findDim("StatePlaneY");
-
-    double lon = view->getFieldAs<double>(Dimension::Id::X, 0);
-    double lat = view->getFieldAs<double>(Dimension::Id::Y, 0);
-
-    double x = view->getFieldAs<double>(state_plane_x, 0);
-    double y = view->getFieldAs<double>(state_plane_y, 0);
-
-    EXPECT_DOUBLE_EQ(-117.2501328350574, lon);
-    EXPECT_DOUBLE_EQ(49.341077824192915, lat);
-    EXPECT_DOUBLE_EQ(637012.24, x);
-    EXPECT_DOUBLE_EQ(849028.31, y);
-}
 
 TEST(FerryFilterTest, stream)
 {
diff --git a/test/unit/filters/MergeTest.cpp b/test/unit/filters/MergeTest.cpp
index abcca8b..8b013ec 100644
--- a/test/unit/filters/MergeTest.cpp
+++ b/test/unit/filters/MergeTest.cpp
@@ -38,61 +38,6 @@
 
 #include "Support.hpp"
 
-TEST(MergeTest, test1)
-{
-    using namespace pdal;
-
-    PipelineManager mgr;
-    mgr.readPipeline(Support::configuredpath("filters/merge.xml"));
-    mgr.execute();
-
-    PointViewSet viewSet = mgr.views();
-
-    EXPECT_EQ(1u, viewSet.size());
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(2130u, view->size());
-}
-
-TEST(MergeTest, test2)
-{
-    using namespace pdal;
-
-    PipelineManager mgr;
-    mgr.readPipeline(Support::configuredpath("filters/merge2.xml"));
-    mgr.execute();
-
-    PointViewSet viewSet = mgr.views();
-
-    EXPECT_EQ(1u, viewSet.size());
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(2130u, view->size());
-}
-
-TEST(MergeTest, test3)
-{
-    using namespace pdal;
-
-    LogPtr log(new Log("pdal merge", &std::clog));
-    log->setLevel((LogLevel)5);
-    PipelineManager mgr;
-    mgr.setLog(log);
-    mgr.readPipeline(Support::configuredpath("filters/merge3.xml"));
-
-    std::ostringstream oss;
-    std::ostream& o = std::clog;
-    auto ctx = Utils::redirect(o, oss);
-
-    mgr.execute();
-    std::string s = oss.str();
-    EXPECT_TRUE(s.find("inconsistent spatial references") != s.npos);
-    Utils::restore(o, ctx);
-
-    PointViewSet viewSet = mgr.views();
-
-    EXPECT_EQ(1u, viewSet.size());
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(2130u, view->size());
-}
 
 TEST(MergeTest, test4)
 {
diff --git a/test/unit/filters/RandomizeFilterTest.cpp b/test/unit/filters/RandomizeFilterTest.cpp
index 8d1a372..b70b6bb 100644
--- a/test/unit/filters/RandomizeFilterTest.cpp
+++ b/test/unit/filters/RandomizeFilterTest.cpp
@@ -34,8 +34,8 @@
 
 #include <pdal/pdal_test_main.hpp>
 
-#include <FauxReader.hpp>
-#include <RandomizeFilter.hpp>
+#include <io/FauxReader.hpp>
+#include <filters/RandomizeFilter.hpp>
 
 using namespace pdal;
 
diff --git a/test/unit/filters/RangeFilterTest.cpp b/test/unit/filters/RangeFilterTest.cpp
index cdec6b8..38aef6f 100644
--- a/test/unit/filters/RangeFilterTest.cpp
+++ b/test/unit/filters/RangeFilterTest.cpp
@@ -36,9 +36,9 @@
 
 #include <pdal/PointView.hpp>
 #include <pdal/StageFactory.hpp>
-#include <FauxReader.hpp>
-#include <RangeFilter.hpp>
-#include <StreamCallbackFilter.hpp>
+#include <io/FauxReader.hpp>
+#include <filters/RangeFilter.hpp>
+#include <filters/StreamCallbackFilter.hpp>
 
 using namespace pdal;
 
diff --git a/test/unit/filters/ReprojectionFilterTest.cpp b/test/unit/filters/ReprojectionFilterTest.cpp
index bcc1e29..ca29e45 100644
--- a/test/unit/filters/ReprojectionFilterTest.cpp
+++ b/test/unit/filters/ReprojectionFilterTest.cpp
@@ -36,9 +36,9 @@
 
 #include <pdal/SpatialReference.hpp>
 #include <pdal/PointView.hpp>
-#include <LasReader.hpp>
-#include <ReprojectionFilter.hpp>
-#include <StreamCallbackFilter.hpp>
+#include <io/LasReader.hpp>
+#include <filters/ReprojectionFilter.hpp>
+#include <filters/StreamCallbackFilter.hpp>
 
 #include "Support.hpp"
 
@@ -47,19 +47,16 @@ using namespace pdal;
 namespace
 {
 
-#if defined(PDAL_HAVE_LIBGEOTIFF)
 void getPoint(const PointView& data, double& x, double& y, double& z)
 {
     x = data.getFieldAs<double>(Dimension::Id::X, 0);
     y = data.getFieldAs<double>(Dimension::Id::Y, 0);
     z = data.getFieldAs<double>(Dimension::Id::Z, 0);
 }
-#endif
 
 } // unnamed namespace
 
 
-#if defined(PDAL_HAVE_LIBGEOTIFF)
 // Test reprojecting UTM 15 to DD with a filter
 TEST(ReprojectionFilterTest, ReprojectionFilterTest_test_1)
 {
@@ -99,9 +96,7 @@ TEST(ReprojectionFilterTest, ReprojectionFilterTest_test_1)
         EXPECT_FLOAT_EQ(z, postZ);
     }
 }
-#endif
 
-#if defined(PDAL_HAVE_LIBGEOTIFF)
 // Test reprojecting UTM 15 to DD with a filter
 TEST(ReprojectionFilterTest, stream_test_1)
 {
@@ -146,5 +141,4 @@ TEST(ReprojectionFilterTest, stream_test_1)
     stream.prepare(table);
     stream.execute(table);
 }
-#endif
 
diff --git a/test/unit/filters/SortFilterTest.cpp b/test/unit/filters/SortFilterTest.cpp
index 3785971..dd6d273 100644
--- a/test/unit/filters/SortFilterTest.cpp
+++ b/test/unit/filters/SortFilterTest.cpp
@@ -36,9 +36,11 @@
 
 #include <random>
 
-#include <SortFilter.hpp>
 #include <pdal/PipelineManager.hpp>
 #include <pdal/StageWrapper.hpp>
+#include <io/LasReader.hpp>
+#include <io/LasWriter.hpp>
+#include <filters/SortFilter.hpp>
 #include "Support.hpp"
 
 using namespace pdal;
@@ -89,11 +91,11 @@ TEST(SortFilterTest, simple)
         doSort(count);
 }
 
-TEST(SortFilterTest, pipelineXML)
+TEST(SortFilterTest, pipelineJSON)
 {
     PipelineManager mgr;
 
-    mgr.readPipeline(Support::configuredpath("filters/sort.xml"));
+    mgr.readPipeline(Support::configuredpath("filters/sort.json"));
     mgr.execute();
 
     PointViewSet viewSet = mgr.views();
@@ -109,22 +111,38 @@ TEST(SortFilterTest, pipelineXML)
     }
 }
 
-TEST(SortFilterTest, pipelineJSON)
+TEST(SortFilterTest, issue1382)
 {
-    PipelineManager mgr;
+    LasReader r;
+    Options ro;
 
-    mgr.readPipeline(Support::configuredpath("filters/sort.json"));
-    mgr.execute();
+    ro.add("filename", Support::datapath("autzen/autzen-utm.las"));
+    r.setOptions(ro);
 
-    PointViewSet viewSet = mgr.views();
+    SortFilter f;
+    Options fo;
 
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
+    fo.add("dimension", "Z");
+    f.setOptions(fo);
+    f.setInput(r);
 
-    for (PointId i = 1; i < view->size(); ++i)
+    LasWriter w;
+    Options wo;
+
+    wo.add("filename", Support::temppath("out.las"));
+    w.setOptions(wo);
+    w.setInput(f);
+
+    PointTable t;
+
+    w.prepare(t);
+    PointViewSet s = w.execute(t);
+    PointViewPtr v = *s.begin();
+
+    for (PointId i = 1; i < v->size(); ++i)
     {
-        double d1 = view->getFieldAs<double>(Dimension::Id::X, i - 1);
-        double d2 = view->getFieldAs<double>(Dimension::Id::X, i);
+        double d1 = v->getFieldAs<double>(Dimension::Id::Z, i - 1);
+        double d2 = v->getFieldAs<double>(Dimension::Id::Z, i);
         EXPECT_TRUE(d1 <= d2);
     }
 }
diff --git a/test/unit/filters/SplitterTest.cpp b/test/unit/filters/SplitterTest.cpp
index ff067bc..6177093 100644
--- a/test/unit/filters/SplitterTest.cpp
+++ b/test/unit/filters/SplitterTest.cpp
@@ -36,8 +36,8 @@
 
 #include <pdal/StageFactory.hpp>
 #include <pdal/StageWrapper.hpp>
-#include <LasReader.hpp>
-#include <SplitterFilter.hpp>
+#include <io/LasReader.hpp>
+#include <filters/SplitterFilter.hpp>
 #include "Support.hpp"
 
 using namespace pdal;
@@ -76,15 +76,27 @@ TEST(SplitterTest, test_tile_filter)
     StageWrapper::done(s, table);
 
     std::vector<PointViewPtr> views;
-    for (auto it = viewSet.begin(); it != viewSet.end(); ++it)
-        views.push_back(*it);
+    std::map<PointViewPtr, BOX2D> bounds;
 
-    auto sorter = [](PointViewPtr p1, PointViewPtr p2)
+    for (auto it = viewSet.begin(); it != viewSet.end(); ++it)
     {
-        BOX2D b1, b2;
+        BOX2D b;
+        PointViewPtr v = *it;
+        v->calculateBounds(b);
+        EXPECT_TRUE(b.maxx - b.minx <= 1000);    
+        EXPECT_TRUE(b.maxy - b.miny <= 1000);    
+
+        for (auto& p : bounds)
+            EXPECT_FALSE(p.second.overlaps(b));
 
-        p1->calculateBounds(b1);
-        p2->calculateBounds(b2);
+        bounds[v] = b;
+        views.push_back(v);
+    }
+
+    auto sorter = [&bounds](PointViewPtr p1, PointViewPtr p2)
+    {
+        BOX2D b1 = bounds[p1];
+        BOX2D b2 = bounds[p2];
 
         return b1.minx < b2.minx ?  true :
             b1.minx > b2.minx ? false :
@@ -92,9 +104,9 @@ TEST(SplitterTest, test_tile_filter)
     };
     std::sort(views.begin(), views.end(), sorter);
 
-    EXPECT_EQ(views.size(), 15u);
-    size_t counts[] = {24, 27, 26, 27, 10, 166, 142, 76, 141, 132, 63, 70, 67,
-        34, 60 };
+    EXPECT_EQ(views.size(), 24u);
+    size_t counts[] = {24, 25, 2, 26, 27, 10, 82, 68, 43, 57, 7, 71, 73,
+        61, 33, 84, 74, 4, 59, 70, 67, 34, 60, 4 };
     for (size_t i = 0; i < views.size(); ++i)
     {
         PointViewPtr view = views[i];
diff --git a/test/unit/filters/StatsFilterTest.cpp b/test/unit/filters/StatsFilterTest.cpp
index a9a35cb..0553a69 100644
--- a/test/unit/filters/StatsFilterTest.cpp
+++ b/test/unit/filters/StatsFilterTest.cpp
@@ -36,8 +36,8 @@
 
 #include <pdal/PDALUtils.hpp>
 #include <pdal/StageFactory.hpp>
-#include <FauxReader.hpp>
-#include <StatsFilter.hpp>
+#include <filters/StatsFilter.hpp>
+#include <io/FauxReader.hpp>
 
 #include "Support.hpp"
 
@@ -271,3 +271,36 @@ TEST(Stats, enum)
         d += (100.0 / 9);
     }
 }
+
+TEST(Stats, global)
+{
+    BOX3D bounds(1.0, 0.0, 0.0, 10.0, 100.0, 1000.0);
+    Options ops;
+    ops.add("bounds", bounds);
+    ops.add("count", 10);
+    ops.add("mode", "ramp");
+
+    FauxReader reader;
+    reader.setOptions(ops);
+
+    Options filterOps;
+    filterOps.add("dimensions", "X, Y, Z");
+    filterOps.add("global", "Z, Y, X");
+    filterOps.add("count", "Y");
+
+    StatsFilter filter;
+    filter.setInput(reader);
+    filter.setOptions(filterOps);
+
+    PointTable table;
+    filter.prepare(table);
+    filter.execute(table);
+
+    const stats::Summary& statsZ = filter.getStats(Dimension::Id::Z);
+
+    EXPECT_DOUBLE_EQ(statsZ.median(), 555.55555555555554);
+	EXPECT_DOUBLE_EQ(statsZ.mad(), 333.33333333333331);
+	EXPECT_DOUBLE_EQ(statsZ.minimum(), 0.0);
+	EXPECT_DOUBLE_EQ(statsZ.maximum(), 1000.0);
+
+}
diff --git a/test/unit/filters/TransformationFilterTest.cpp b/test/unit/filters/TransformationFilterTest.cpp
index 4a5f802..4589048 100644
--- a/test/unit/filters/TransformationFilterTest.cpp
+++ b/test/unit/filters/TransformationFilterTest.cpp
@@ -33,11 +33,10 @@
 ****************************************************************************/
 
 #include <pdal/pdal_test_main.hpp>
-#include <FauxReader.hpp>
-#include <TransformationFilter.hpp>
 
 #include <pdal/StageFactory.hpp>
-
+#include <io/FauxReader.hpp>
+#include <filters/TransformationFilter.hpp>
 
 namespace pdal
 {
diff --git a/test/unit/io/BPFTest.cpp b/test/unit/io/BPFTest.cpp
new file mode 100644
index 0000000..d1709fc
--- /dev/null
+++ b/test/unit/io/BPFTest.cpp
@@ -0,0 +1,710 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <array>
+
+#include <pdal/Filter.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/util/Utils.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <io/BpfReader.hpp>
+#include <io/BpfWriter.hpp>
+#include <io/BufferReader.hpp>
+
+#include "Support.hpp"
+
+using namespace pdal;
+
+namespace
+{
+
+template<typename LeftIter, typename RightIter>
+::testing::AssertionResult CheckEqualCollections(
+    LeftIter left_begin, LeftIter left_end, RightIter right_begin)
+{
+    bool equal(true);
+    std::string message;
+    size_t index(0);
+    while (left_begin != left_end)
+    {
+        if (*left_begin++ != *right_begin++)
+        {
+            equal = false;
+            message += "\n\tMismatch at index " + std::to_string(index);
+        }
+        ++index;
+    }
+    if (message.size())
+        message += "\n\t";
+    return equal ? ::testing::AssertionSuccess() :
+        ::testing::AssertionFailure() << message;
+}
+
+
+
+void test_file_type_view(const std::string& filename)
+{
+    PointTable table;
+
+    struct PtData
+    {
+        float x;
+        float y;
+        float z;
+    };
+
+    Options ops;
+
+    ops.add("filename", filename);
+    ops.add("count", 506);
+    std::shared_ptr<BpfReader> reader(new BpfReader);
+    reader->setOptions(ops);
+
+    reader->prepare(table);
+    PointViewSet viewSet = reader->execute(table);
+
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 506u);
+
+    PtData pts2[3] = { {494057.312f, 4877433.5f, 130.630005f},
+                       {494133.812f, 4877440.0f, 130.440002f},
+                       {494021.094f, 4877440.0f, 130.460007f} };
+
+    for (int i = 0; i < 3; ++i)
+    {
+        float x = view->getFieldAs<float>(Dimension::Id::X, i);
+        float y = view->getFieldAs<float>(Dimension::Id::Y, i);
+        float z = view->getFieldAs<float>(Dimension::Id::Z, i);
+
+        EXPECT_FLOAT_EQ(x, pts2[i].x);
+        EXPECT_FLOAT_EQ(y, pts2[i].y);
+        EXPECT_FLOAT_EQ(z, pts2[i].z);
+    }
+
+    PtData pts[3] = { {494915.25f, 4878096.5f, 128.220001f},
+                      {494917.062f, 4878124.5f, 128.539993f},
+                      {494920.781f, 4877914.5f, 127.43f} };
+
+    for (int i = 503; i < 3; ++i)
+    {
+        float x = view->getFieldAs<float>(Dimension::Id::X, i);
+        float y = view->getFieldAs<float>(Dimension::Id::Y, i);
+        float z = view->getFieldAs<float>(Dimension::Id::Z, i);
+
+        EXPECT_FLOAT_EQ(x, pts[i].x);
+        EXPECT_FLOAT_EQ(y, pts[i].y);
+        EXPECT_FLOAT_EQ(z, pts[i].z);
+    }
+}
+
+void test_file_type_stream(const std::string& filename)
+{
+    class Checker : public Filter
+    {
+    public:
+        Checker() : m_cnt(0)
+        {}
+
+        struct PtData
+        {
+            float x;
+            float y;
+            float z;
+        };
+
+        std::string getName() const
+        { return "checker"; }
+
+        bool processOne(PointRef& p)
+        {
+            PtData pts0[3] = { {494057.312f, 4877433.5f, 130.630005f},
+                {494133.812f, 4877440.0f, 130.440002f},
+                {494021.094f, 4877440.0f, 130.460007f} };
+
+            PtData pts503[3] = { {494915.25f, 4878096.5f, 128.220001f},
+                {494917.062f, 4878124.5f, 128.539993f},
+                {494920.781f, 4877914.5f, 127.43f} };
+
+            PtData d;
+
+            if (m_cnt < 3)
+               d = pts0[0 + m_cnt];
+            else if (m_cnt >= 503 && m_cnt < 506)
+               d = pts503[m_cnt - 503];
+            else
+            {
+                m_cnt++;
+                return true;
+            }
+
+            float x = p.getFieldAs<float>(Dimension::Id::X);
+            float y = p.getFieldAs<float>(Dimension::Id::Y);
+            float z = p.getFieldAs<float>(Dimension::Id::Z);
+
+            EXPECT_FLOAT_EQ(x, d.x);
+            EXPECT_FLOAT_EQ(y, d.y);
+            EXPECT_FLOAT_EQ(z, d.z);
+            EXPECT_TRUE(m_cnt < 506) << "Count exceeded amount requested "
+                "in 'count' option.";
+
+            m_cnt++;
+            return true;
+        }
+
+    private:
+        size_t m_cnt;
+    };
+
+    FixedPointTable table(50);
+
+    Options ops;
+
+    ops.add("filename", filename);
+    ops.add("count", 506);
+    BpfReader reader;
+    reader.setOptions(ops);
+
+    Checker c;
+    c.setInput(reader);
+
+    c.prepare(table);
+    c.execute(table);
+}
+
+
+void test_file_type(const std::string& filename)
+{
+    test_file_type_view(filename);
+    test_file_type_stream(filename);
+}
+
+
+void test_roundtrip(Options& writerOps)
+{
+    std::string infile(
+        Support::datapath("bpf/autzen-utm-chipped-25-v3-interleaved.bpf"));
+    std::string outfile(Support::temppath("tmp.bpf"));
+
+    PointTable table;
+
+    Options readerOps;
+
+    readerOps.add("filename", infile);
+    BpfReader reader;
+    reader.setOptions(readerOps);
+
+    writerOps.add("filename", outfile);
+    BpfWriter writer;
+    writer.setOptions(writerOps);
+    writer.setInput(reader);
+
+    FileUtils::deleteFile(outfile);
+    writer.prepare(table);
+    writer.execute(table);
+
+    test_file_type(outfile);
+}
+
+
+} //namespace
+
+TEST(BPFTest, test_point_major)
+{
+    test_file_type(
+        Support::datapath("bpf/autzen-utm-chipped-25-v3-interleaved.bpf"));
+}
+
+TEST(BPFTest, test_dim_major)
+{
+    test_file_type(
+        Support::datapath("bpf/autzen-utm-chipped-25-v3.bpf"));
+}
+
+TEST(BPFTest, test_byte_major)
+{
+    test_file_type(
+        Support::datapath("bpf/autzen-utm-chipped-25-v3-segregated.bpf"));
+}
+
+TEST(BPFTest, test_point_major_zlib)
+{
+    test_file_type(
+        Support::datapath("bpf/"
+            "autzen-utm-chipped-25-v3-deflate-interleaved.bpf"));
+}
+
+TEST(BPFTest, test_dim_major_zlib)
+{
+    test_file_type(
+        Support::datapath("bpf/autzen-utm-chipped-25-v3-deflate.bpf"));
+}
+
+TEST(BPFTest, test_byte_major_zlib)
+{
+    test_file_type(
+        Support::datapath("bpf/"
+            "autzen-utm-chipped-25-v3-deflate-segregated.bpf"));
+}
+
+TEST(BPFTest, roundtrip_byte)
+{
+    Options ops;
+
+    ops.add("format", "BYTE");
+    test_roundtrip(ops);
+}
+
+TEST(BPFTest, roundtrip_dimension)
+{
+    Options ops;
+
+    ops.add("format", "DIMENSION");
+    test_roundtrip(ops);
+}
+
+TEST(BPFTest, roundtrip_point)
+{
+    Options ops;
+
+    ops.add("format", "POINT");
+    test_roundtrip(ops);
+}
+
+TEST(BPFTest, roundtrip_byte_compression)
+{
+    Options ops;
+
+    ops.add("format", "BYTE");
+    ops.add("compression", true);
+    test_roundtrip(ops);
+}
+
+TEST(BPFTest, roundtrip_dimension_compression)
+{
+    Options ops;
+
+    ops.add("format", "DIMENSION");
+    ops.add("compression", true);
+    test_roundtrip(ops);
+}
+
+TEST(BPFTest, roundtrip_point_compression)
+{
+    Options ops;
+
+    ops.add("format", "POINT");
+    ops.add("compression", true);
+    test_roundtrip(ops);
+}
+
+TEST(BPFTest, roundtrip_scaling)
+{
+    Options ops;
+
+    ops.add("format", "POINT");
+    ops.add("offset_x", 494000.0);
+    ops.add("offset_y", 487000.0);
+    ops.add("offset_z", 130.0);
+    ops.add("scale_x", .001);
+    ops.add("scale_y", .01);
+    ops.add("scale_z", 10.0);
+    test_roundtrip(ops);
+}
+
+TEST(BPFTest, extra_bytes)
+{
+    std::string infile(
+        Support::datapath("bpf/autzen-utm-chipped-25-v3-interleaved.bpf"));
+    std::string outfile(Support::temppath("tmp.bpf"));
+
+    PointTable table;
+
+    Options readerOps;
+    readerOps.add("filename", infile);
+    BpfReader reader;
+    reader.setOptions(readerOps);
+
+    const unsigned char buf[] = "This is a test.";
+
+    Options writerOps;
+    writerOps.add("filename", outfile);
+    writerOps.add("header_data", Utils::base64_encode(buf, sizeof(buf)));
+    BpfWriter writer;
+    writer.setOptions(writerOps);
+    writer.setInput(reader);
+
+    FileUtils::deleteFile(outfile);
+    writer.prepare(table);
+    writer.execute(table);
+
+    test_file_type(outfile);
+
+    Options readerOps2;
+    readerOps2.add("filename", outfile);
+
+    PointTable table2;
+    BpfReader reader2;
+    reader2.setOptions(readerOps2);
+    reader2.prepare(table2);
+    reader2.execute(table2);
+    MetadataNode n = reader2.getMetadata();
+    std::vector<uint8_t> outbuf =
+        Utils::base64_decode(n.findChild("header_data").value());
+    EXPECT_EQ(memcmp(outbuf.data(), buf, sizeof(buf)), 0);
+}
+
+TEST(BPFTest, bundled)
+{
+    std::string infile(
+        Support::datapath("bpf/autzen-utm-chipped-25-v3-interleaved.bpf"));
+    std::string outfile(Support::temppath("tmp.bpf"));
+
+    PointTable table;
+
+    Options readerOps;
+    readerOps.add("filename", infile);
+    BpfReader reader;
+    reader.setOptions(readerOps);
+
+    Options writerOps;
+    writerOps.add("filename", outfile);
+    writerOps.add("bundledfile", Support::datapath("bpf/bundle1"));
+    writerOps.add("bundledfile", Support::datapath("bpf/bundle2"));
+
+    BpfWriter writer;
+    writer.setOptions(writerOps);
+    writer.setInput(reader);
+
+    FileUtils::deleteFile(outfile);
+    writer.prepare(table);
+    writer.execute(table);
+
+    test_file_type(outfile);
+
+    Options readerOps2;
+    readerOps2.add("filename", outfile);
+
+    PointTable table2;
+    BpfReader reader2;
+    reader2.setOptions(readerOps2);
+    reader2.prepare(table2);
+    reader2.execute(table2);
+    MetadataNode n = reader2.getMetadata();
+    std::vector<uint8_t> outbuf;
+    auto findbundle = [](MetadataNode& m)
+        { return m.name() == "bundled_file"; };
+    MetadataNodeList nodes = n.findChildren(findbundle);
+    EXPECT_EQ(nodes.size(), 2u);
+    auto findbundle1 = [](const MetadataNode& m)
+        { return m.name() == "bundle1"; };
+    outbuf = Utils::base64_decode(n.find(findbundle1).value());
+    EXPECT_EQ(memcmp(outbuf.data(), "This is a test",
+        outbuf.size() - 1), 0);
+    auto findbundle2 = [](const MetadataNode& m)
+        { return m.name() == "bundle2"; };
+    outbuf = Utils::base64_decode(n.find(findbundle2).value());
+    EXPECT_EQ(memcmp(outbuf.data(), "This is another test",
+        outbuf.size() - 1), 0);
+}
+
+TEST(BPFTest, inspect)
+{
+    Options ops;
+    ops.add("filename", Support::datapath("bpf/autzen-dd.bpf"));
+
+    BpfReader reader;
+    reader.setOptions(ops);
+
+    QuickInfo qi = reader.preview();
+
+    std::string testWkt = "PROJCS[\"WGS 84 / UTM zone 1N\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-177],PARAMETER[\"scale_factor\",0.9996],PA [...]
+    EXPECT_EQ(qi.m_srs.getWKT(), testWkt);
+
+    EXPECT_EQ(qi.m_pointCount, 1065u);
+
+    BOX3D bounds(
+        -13676090.610841721296, 4894836.9556098170578, 123.93000030517578125,
+        -13674705.011110275984, 4896224.6888861842453, 178.7299957275390625);
+    EXPECT_EQ(qi.m_bounds, bounds);
+
+    const char *dims[] =
+    {
+        "Blue",
+        "Classification",
+        "GPSTime",
+        "Green",
+        "Intensity",
+        "Number of Returns",
+        "Red",
+        "Return Information",
+        "Return Number",
+        "X",
+        "Y",
+        "Z"
+    };
+
+    std::sort(qi.m_dimNames.begin(), qi.m_dimNames.end());
+    EXPECT_TRUE(CheckEqualCollections(qi.m_dimNames.begin(),
+        qi.m_dimNames.end(), std::begin(dims)));
+}
+
+TEST(BPFTest, mueller)
+{
+    BpfMuellerMatrix xform;
+
+    double x = 12.345;
+    double y = 45.345;
+    double z = 999.341;
+
+    double xp = x;
+    double yp = y;
+    double zp = z;
+    xform.apply(xp, yp, zp);
+    EXPECT_DOUBLE_EQ(x, xp);
+    EXPECT_DOUBLE_EQ(y, yp);
+    EXPECT_DOUBLE_EQ(z, zp);
+
+    // Test translation.
+    xp = 1.0;
+    yp = 1.0;
+    zp = 1.0;
+
+    BpfMuellerMatrix translate;
+
+    translate.m_vals[3] = 2.0;
+    translate.m_vals[7] = 2.0;
+    translate.m_vals[11] = 1.0;
+    translate.apply(xp, yp, zp);
+    EXPECT_DOUBLE_EQ(xp, 3.0);
+    EXPECT_DOUBLE_EQ(yp, 3.0);
+    EXPECT_DOUBLE_EQ(zp, 2.0);
+
+    BpfMuellerMatrix scale;
+    xp = 1.0;
+    yp = 1.0;
+    zp = 1.0;
+    scale.m_vals[0] = 2.0;
+    scale.m_vals[5] = 7.0;
+    scale.m_vals[10] = -3.0;
+    scale.apply(xp, yp, zp);
+    EXPECT_DOUBLE_EQ(xp, 2.0);
+    EXPECT_DOUBLE_EQ(yp, 7.0);
+    EXPECT_DOUBLE_EQ(zp, -3.0);
+}
+
+
+TEST(BPFTest, flex)
+{
+    std::array<std::string, 3> outname =
+        {{ "test_1.bpf", "test_2.bpf", "test_3.bpf" }};
+
+    Options readerOps;
+    readerOps.add("filename",
+        Support::datapath("bpf/autzen-utm-chipped-25-v3.bpf"));
+
+    PointTable table;
+
+    BpfReader reader;
+    reader.setOptions(readerOps);
+
+    reader.prepare(table);
+    PointViewSet views = reader.execute(table);
+    PointViewPtr v = *(views.begin());
+
+    PointViewPtr v1(new PointView(table));
+    PointViewPtr v2(new PointView(table));
+    PointViewPtr v3(new PointView(table));
+
+    std::vector<PointViewPtr> vs;
+    vs.push_back(v1);
+    vs.push_back(v2);
+    vs.push_back(v3);
+
+    for (PointId i = 0; i < v->size(); ++i)
+        vs[i % 3]->appendPoint(*v, i);
+
+    for (size_t i = 0; i < outname.size(); ++i)
+        FileUtils::deleteFile(Support::temppath(outname[i]));
+
+    BufferReader reader2;
+    reader2.addView(v1);
+    reader2.addView(v2);
+    reader2.addView(v3);
+
+    Options writerOps;
+    writerOps.add("filename", Support::temppath("test_#.bpf"));
+
+    BpfWriter writer;
+    writer.setOptions(writerOps);
+    writer.setInput(reader2);
+
+    writer.prepare(table);
+    writer.execute(table);
+
+    for (size_t i = 0; i < outname.size(); ++i)
+    {
+        std::string filename = Support::temppath(outname[i]);
+        EXPECT_TRUE(FileUtils::fileExists(filename));
+
+        Options ops;
+        ops.add("filename", filename);
+
+        BpfReader r;
+        r.setOptions(ops);
+        EXPECT_EQ(r.preview().m_pointCount, 355u);
+    }
+}
+
+TEST(BPFTest, flex2)
+{
+    Options readerOps;
+    readerOps.add("filename",
+        Support::datapath("bpf/autzen-utm-chipped-25-v3.bpf"));
+
+    PointTable table;
+
+    BpfReader reader;
+    reader.setOptions(readerOps);
+
+    reader.prepare(table);
+    PointViewSet views = reader.execute(table);
+    PointViewPtr v = *(views.begin());
+
+    PointViewPtr v1(new PointView(table));
+    PointViewPtr v2(new PointView(table));
+    PointViewPtr v3(new PointView(table));
+
+    std::vector<PointViewPtr> vs;
+    vs.push_back(v1);
+    vs.push_back(v2);
+    vs.push_back(v3);
+
+    for (PointId i = 0; i < v->size(); ++i)
+        vs[i % 3]->appendPoint(*v, i);
+
+    std::string outfile(Support::temppath("test_flex.bpf"));
+    FileUtils::deleteFile(outfile);
+
+    BufferReader reader2;
+    reader2.addView(v1);
+    reader2.addView(v2);
+    reader2.addView(v3);
+
+    Options writerOps;
+    writerOps.add("filename", outfile);
+
+    BpfWriter writer;
+    writer.setOptions(writerOps);
+    writer.setInput(reader2);
+
+    writer.prepare(table);
+    writer.execute(table);
+
+    EXPECT_TRUE(FileUtils::fileExists(outfile));
+
+    Options ops;
+    ops.add("filename", outfile);
+
+    BpfReader r;
+    r.setOptions(ops);
+    EXPECT_EQ(r.preview().m_pointCount, 1065u);
+}
+
+TEST(BPFTest, outputdims)
+{
+    Options ops;
+    ops.add("filename", Support::datapath("bpf/autzen-dd.bpf"));
+
+    BpfReader reader;
+    reader.setOptions(ops);
+
+    std::string testfile(Support::temppath("test.bpf"));
+
+    FileUtils::deleteFile(testfile);
+    Options writerOps;
+    writerOps.add("filename", testfile);
+    writerOps.add("output_dims", "X, Y, Z, Red, Green");
+
+    BpfWriter writer;
+    writer.setInput(reader);
+    writer.setOptions(writerOps);
+    
+    PointTable t;
+    writer.prepare(t);
+    writer.execute(t);
+
+    Options o2;
+    o2.add("filename", testfile);
+
+    BpfReader r;
+    r.setOptions(o2);
+
+    PointTable t2;
+    r.prepare(t2);
+
+    StringList dimNames;
+    Dimension::IdList dimList = t2.layout()->dims(); 
+    EXPECT_EQ(dimList.size(), 5u);
+    for (auto di : dimList)
+        dimNames.push_back(t2.layout()->dimName(di));
+
+    const char *dims[] =
+    {
+        "Green",
+        "Red",
+        "X",
+        "Y",
+        "Z"
+    };
+   
+    std::sort(dimNames.begin(), dimNames.end());
+    EXPECT_TRUE(CheckEqualCollections(dimNames.begin(),
+        dimNames.end(), std::begin(dims)));
+
+    Options o3;
+    o3.add("filename", testfile);
+    o3.add("output_dims", "Y, Z, Red, Green");
+
+    BpfWriter w3;
+    w3.setOptions(o3);
+    
+    // Missing X dimension.
+    PointTable t3;
+    EXPECT_THROW(w3.prepare(t3), pdal_error);
+    
+}
+
diff --git a/test/unit/io/BufferTest.cpp b/test/unit/io/BufferTest.cpp
new file mode 100644
index 0000000..bd10088
--- /dev/null
+++ b/test/unit/io/BufferTest.cpp
@@ -0,0 +1,97 @@
+/******************************************************************************
+* Copyright (c) 2014, Hobu Inc. (hobu at hobu.inc)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/PointView.hpp>
+#include <pdal/StageFactory.hpp>
+#include <io/BufferReader.hpp>
+#include <filters/StatsFilter.hpp>
+
+using namespace pdal;
+
+namespace
+{
+
+TEST(ViewTest, test_basic)
+{
+    PointTable table;
+    PointViewPtr view(new PointView(table));
+
+    table.layout()->registerDim(Dimension::Id::X);
+    table.layout()->registerDim(Dimension::Id::Y);
+    table.layout()->registerDim(Dimension::Id::Z);
+
+    for (int i = 0; i < 20; ++i)
+    {
+        view->setField(Dimension::Id::X, i, i);
+        view->setField(Dimension::Id::Y, i, 2 * i);
+        view->setField(Dimension::Id::Z, i, -i);
+    }
+
+    Options ops;
+    BufferReader r;
+    r.setOptions(ops);
+    r.addView(view);
+
+    StatsFilter s;
+    s.setOptions(ops);
+    s.setInput(r);
+
+    s.prepare(table);
+    PointViewSet viewSet = s.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 20u);
+
+    stats::Summary xSummary = s.getStats(Dimension::Id::X);
+    EXPECT_FLOAT_EQ(xSummary.minimum(), 0);
+    EXPECT_FLOAT_EQ(xSummary.maximum(), 19);
+    EXPECT_EQ(xSummary.count(), 20u);
+    EXPECT_FLOAT_EQ(xSummary.average(), 9.5);
+
+    stats::Summary ySummary = s.getStats(Dimension::Id::Y);
+    EXPECT_FLOAT_EQ(ySummary.minimum(), 0);
+    EXPECT_FLOAT_EQ(ySummary.maximum(), 38);
+    EXPECT_EQ(ySummary.count(), 20u);
+    EXPECT_FLOAT_EQ(ySummary.average(), 19);
+
+    stats::Summary zSummary = s.getStats(Dimension::Id::Z);
+    EXPECT_FLOAT_EQ(zSummary.minimum(), -19);
+    EXPECT_FLOAT_EQ(zSummary.maximum(), 0);
+    EXPECT_EQ(zSummary.count(), 20u);
+    EXPECT_FLOAT_EQ(zSummary.average(), -9.5);
+}
+
+}
diff --git a/test/unit/io/FauxReaderTest.cpp b/test/unit/io/FauxReaderTest.cpp
new file mode 100644
index 0000000..a2a6280
--- /dev/null
+++ b/test/unit/io/FauxReaderTest.cpp
@@ -0,0 +1,239 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <io/FauxReader.hpp>
+
+using namespace pdal;
+
+TEST(FauxReaderTest, test_constant_mode_sequential_iter)
+{
+    Options ops;
+
+    BOX3D bounds(1.0, 2.0, 3.0, 101.0, 102.0, 103.0);
+    ops.add("bounds", bounds);
+    ops.add("count", 750);
+    ops.add("mode", "constant");
+    std::shared_ptr<FauxReader> reader(new FauxReader);
+    reader->setOptions(ops);
+
+    PointTable table;
+    reader->prepare(table);
+    PointViewSet viewSet = reader->execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 750u);
+    for (point_count_t i = 0; i < view->size(); i++)
+    {
+        double x = view->getFieldAs<double>(Dimension::Id::X, i);
+        double y = view->getFieldAs<double>(Dimension::Id::Y, i);
+        double z = view->getFieldAs<double>(Dimension::Id::Z, i);
+        uint64_t t = view->getFieldAs<uint64_t>(Dimension::Id::OffsetTime, i);
+
+        EXPECT_FLOAT_EQ(x, 1.0);
+        EXPECT_FLOAT_EQ(y, 2.0);
+        EXPECT_FLOAT_EQ(z, 3.0);
+        EXPECT_EQ(t, i);
+    }
+}
+
+
+TEST(FauxReaderTest, test_random_mode)
+{
+    BOX3D bounds(1.0, 2.0, 3.0, 101.0, 102.0, 103.0);
+    Options ops;
+    ops.add("bounds", bounds);
+    ops.add("count", 750);
+    ops.add("mode", "constant");
+    std::shared_ptr<FauxReader> reader(new FauxReader);
+    reader->setOptions(ops);
+
+    PointTable table;
+    reader->prepare(table);
+    PointViewSet viewSet = reader->execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 750u);
+
+    for (point_count_t i = 0; i < view->size(); ++i)
+    {
+        double x = view->getFieldAs<double>(Dimension::Id::X, i);
+        double y = view->getFieldAs<double>(Dimension::Id::Y, i);
+        double z = view->getFieldAs<double>(Dimension::Id::Z, i);
+        uint64_t t = view->getFieldAs<uint64_t>(Dimension::Id::OffsetTime, i);
+
+        EXPECT_GE(x, 1.0);
+        EXPECT_LE(x, 101.0);
+
+        EXPECT_GE(y, 2.0);
+        EXPECT_LE(y, 102.0);
+
+        EXPECT_GE(z, 3.0);
+        EXPECT_LE(z, 103.0);
+
+        EXPECT_EQ(t, i);
+    }
+}
+
+
+TEST(FauxReaderTest, test_ramp_mode_1)
+{
+    BOX3D bounds(0, 0, 0, 4, 4, 4);
+    Options ops;
+    ops.add("bounds", bounds);
+    ops.add("count", 2);
+    ops.add("mode", "ramp");
+
+    std::shared_ptr<FauxReader> reader(new FauxReader);
+    reader->setOptions(ops);
+
+    PointTable table;
+    reader->prepare(table);
+    PointViewSet viewSet = reader->execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 2u);
+
+    double x0 = view->getFieldAs<double>(Dimension::Id::X, 0);
+    double y0 = view->getFieldAs<double>(Dimension::Id::Y, 0);
+    double z0 = view->getFieldAs<double>(Dimension::Id::Z, 0);
+    uint64_t t0 = view->getFieldAs<uint64_t>(Dimension::Id::OffsetTime, 0);
+
+    double x1 = view->getFieldAs<double>(Dimension::Id::X, 1);
+    double y1 = view->getFieldAs<double>(Dimension::Id::Y, 1);
+    double z1 = view->getFieldAs<double>(Dimension::Id::Z, 1);
+    uint64_t t1 = view->getFieldAs<uint64_t>(Dimension::Id::OffsetTime, 1);
+
+    EXPECT_FLOAT_EQ(x0, 0.0);
+    EXPECT_FLOAT_EQ(y0, 0.0);
+    EXPECT_FLOAT_EQ(z0, 0.0);
+    EXPECT_EQ(t0, 0u);
+
+    EXPECT_FLOAT_EQ(x1, 4.0);
+    EXPECT_FLOAT_EQ(y1, 4.0);
+    EXPECT_FLOAT_EQ(z1, 4.0);
+    EXPECT_EQ(t1, 1u);
+}
+
+
+TEST(FauxReaderTest, test_ramp_mode_2)
+{
+    BOX3D bounds(1.0, 2.0, 3.0, 101.0, 152.0, 203.0);
+    Options ops;
+    ops.add("bounds", bounds);
+    ops.add("count", 750);
+    ops.add("mode", "ramp");
+    std::shared_ptr<FauxReader> reader(new FauxReader);
+    reader->setOptions(ops);
+
+    PointTable table;
+    reader->prepare(table);
+    PointViewSet viewSet = reader->execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 750u);
+
+    double delX = (101.0 - 1.0) / (750.0 - 1.0);
+    double delY = (152.0 - 2.0) / (750.0 - 1.0);
+    double delZ = (203.0 - 3.0) / (750.0 - 1.0);
+
+    for (point_count_t i = 0; i < view->size(); ++i)
+    {
+        double x = view->getFieldAs<double>(Dimension::Id::X, i);
+        double y = view->getFieldAs<double>(Dimension::Id::Y, i);
+        double z = view->getFieldAs<double>(Dimension::Id::Z, i);
+        uint64_t t = view->getFieldAs<uint64_t>(Dimension::Id::OffsetTime, i);
+
+        EXPECT_FLOAT_EQ(x, 1.0 + delX * i);
+        EXPECT_FLOAT_EQ(y, 2.0 + delY * i);
+        EXPECT_FLOAT_EQ(z, 3.0 + delZ * i);
+        EXPECT_EQ(t, i);
+    }
+}
+
+
+TEST(FauxReaderTest, test_return_number)
+{
+    Options ops;
+
+    BOX3D bounds(1.0, 2.0, 3.0, 101.0, 102.0, 103.0);
+    ops.add("bounds", bounds);
+    ops.add("count", 100);
+    ops.add("mode", "constant");
+    ops.add("number_of_returns", 9);
+    std::shared_ptr<FauxReader> reader(new FauxReader);
+    reader->setOptions(ops);
+
+    PointTable table;
+    reader->prepare(table);
+    PointViewSet viewSet = reader->execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 100u);
+
+    for (point_count_t i = 0; i < view->size(); i++)
+    {
+        uint8_t returnNumber = view->getFieldAs<uint8_t>(
+            Dimension::Id::ReturnNumber, i);
+        uint8_t numberOfReturns =
+            view->getFieldAs<uint8_t>(Dimension::Id::NumberOfReturns, i);
+
+        EXPECT_EQ(returnNumber, (i % 9) + 1);
+        EXPECT_EQ(numberOfReturns, 9);
+    }
+}
+
+
+TEST(FauxReaderTest, one_point)
+{
+    Options ops;
+
+    ops.add("bounds", BOX3D(1, 2, 3, 1, 2, 3));
+    ops.add("count", 1);
+    ops.add("mode", "ramp");
+    FauxReader reader;
+    reader.setOptions(ops);
+
+    PointTable table;
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 1u);
+
+    EXPECT_EQ(1, view->getFieldAs<int>(Dimension::Id::X, 0));
+    EXPECT_EQ(2, view->getFieldAs<int>(Dimension::Id::Y, 0));
+    EXPECT_EQ(3, view->getFieldAs<int>(Dimension::Id::Z, 0));
+}
diff --git a/test/unit/io/GDALReaderTest.cpp b/test/unit/io/GDALReaderTest.cpp
new file mode 100644
index 0000000..1b2db1a
--- /dev/null
+++ b/test/unit/io/GDALReaderTest.cpp
@@ -0,0 +1,201 @@
+/******************************************************************************
+* Copyright (c) 2015, Hobu Inc. (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <fstream>
+#include <iostream>
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <io/GDALReader.hpp>
+#include "Support.hpp"
+
+using namespace pdal;
+
+TEST(GDALReaderTest, badfile)
+{
+    Options ro;
+    ro.add("filename", Support::datapath("png/autzen-foo.png"));
+
+    GDALReader gr;
+    gr.setOptions(ro);
+
+    PointTable t;
+    gr.prepare(t);
+    EXPECT_THROW(gr.execute(t), pdal_error);
+}
+
+
+TEST(GDALReaderTest, simple)
+{
+    Options ro;
+    ro.add("filename", Support::datapath("png/autzen-height.png"));
+
+    GDALReader gr;
+    gr.setOptions(ro);
+
+    PointTable t;
+    gr.prepare(t);
+    PointViewSet s = gr.execute(t);
+    PointViewPtr v = *s.begin();
+    PointLayoutPtr l = t.layout();
+    Dimension::Id id1 = l->findDim("band-1");
+    Dimension::Id id2 = l->findDim("band-2");
+    Dimension::Id id3 = l->findDim("band-3");
+    EXPECT_EQ(v->size(), (size_t)(735 * 973));
+
+    auto verify = [v, id1, id2, id3]
+        (PointId idx, double xx, double xy, double xr, double xg, double xb)
+    {
+        double r, g, b, x, y;
+        x = v->getFieldAs<double>(Dimension::Id::X, idx);
+        y = v->getFieldAs<double>(Dimension::Id::Y, idx);
+        r = v->getFieldAs<double>(id1, idx);
+        g = v->getFieldAs<double>(id2, idx);
+        b = v->getFieldAs<double>(id3, idx);
+        EXPECT_DOUBLE_EQ(x, xx);
+        EXPECT_DOUBLE_EQ(y, xy);
+        EXPECT_DOUBLE_EQ(r, xr);
+        EXPECT_DOUBLE_EQ(g, xg);
+        EXPECT_DOUBLE_EQ(b, xb);
+    };
+
+    verify(0, .5, .5, 0, 0, 0);
+    verify(120000, 195.5, 163.5, 255, 213, 0);
+    verify(290000, 410.5, 394.5, 0, 255, 206);
+    verify(715154, 734.5, 972.5, 0, 0, 0);
+}
+
+struct Point
+{
+    double m_x;
+    double m_y;
+    double m_z;
+};
+
+std::ostream& operator << (std::ostream& o, const Point& p)
+{
+    o << p.m_x << "/" << p.m_y << "/" << p.m_z;
+    return o;
+}
+
+bool operator < (const Point& p1, const Point& p2)
+{
+    return (p1.m_x < p2.m_x ? true :
+            p1.m_x > p2.m_x ? false :
+            p1.m_y < p2.m_y ? true :
+            p1.m_y > p2.m_y ? false :
+            p1.m_z < p2.m_z ? true : false);
+}
+
+class GDALReaderTypeTest : public ::testing::Test
+{
+protected:
+    GDALReaderTypeTest()
+    {
+        std::string xyzFilename = Support::datapath("gdal/data.xyz");
+
+        std::ifstream in(xyzFilename);
+
+        while (true)
+        {
+            Point p;
+            in >> p.m_x >> p.m_y >> p.m_z;
+            if (in.eof())
+                break;
+            m_xyzPoints.push_back(p);
+        }
+    }
+
+    void compare(const std::string& path)
+    {
+        Options ro;
+        ro.add("filename", path);
+
+        GDALReader gr;
+        gr.setOptions(ro);
+
+        PointTable t;
+        gr.prepare(t);
+        Dimension::Id b1 = t.layout()->findDim("band-1");
+        PointViewSet s = gr.execute(t);
+        PointViewPtr v = *s.begin();
+
+        EXPECT_EQ(v->size(), m_xyzPoints.size());
+        for (PointId idx = 0; idx < v->size(); ++idx)
+        {
+            Point p;
+            p.m_x = v->getFieldAs<double>(Dimension::Id::X, idx);
+            p.m_y = v->getFieldAs<double>(Dimension::Id::Y, idx);
+            p.m_z = v->getFieldAs<double>(b1, idx);
+            m_gdalPoints.push_back(p);
+        }
+        std::sort(m_xyzPoints.begin(), m_xyzPoints.end());
+        std::sort(m_gdalPoints.begin(), m_gdalPoints.end());
+        for (size_t i = 0; i < m_gdalPoints.size(); ++i)
+        {
+            EXPECT_DOUBLE_EQ(m_xyzPoints[i].m_x, m_gdalPoints[i].m_x);
+            EXPECT_DOUBLE_EQ(m_xyzPoints[i].m_y, m_gdalPoints[i].m_y);
+            EXPECT_DOUBLE_EQ(m_xyzPoints[i].m_z, m_gdalPoints[i].m_z);
+        }
+    }
+
+private:
+    std::vector<Point> m_xyzPoints;    
+    std::vector<Point> m_gdalPoints;
+};
+
+TEST_F(GDALReaderTypeTest, byte)
+{
+    compare(Support::datapath("gdal/byte.tif"));
+}
+
+TEST_F(GDALReaderTypeTest, int16)
+{
+    compare(Support::datapath("gdal/int16.tif"));
+}
+
+TEST_F(GDALReaderTypeTest, int32)
+{
+    compare(Support::datapath("gdal/int32.tif"));
+}
+
+TEST_F(GDALReaderTypeTest, float32)
+{
+    compare(Support::datapath("gdal/float32.tif"));
+}
+
+TEST_F(GDALReaderTypeTest, float64)
+{
+    compare(Support::datapath("gdal/float64.tif"));
+}
diff --git a/test/unit/io/GDALWriterTest.cpp b/test/unit/io/GDALWriterTest.cpp
new file mode 100644
index 0000000..4f40339
--- /dev/null
+++ b/test/unit/io/GDALWriterTest.cpp
@@ -0,0 +1,374 @@
+/******************************************************************************
+* Copyright (c) 2016, Hobu Inc. (info at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+#include <pdal/GDALUtils.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <io/GDALWriter.hpp>
+#include <io/LasReader.hpp>
+#include <io/TextReader.hpp>
+#include "Support.hpp"
+
+#include <iostream>
+#include <sstream>
+
+using namespace pdal;
+
+namespace
+{
+
+void runGdalWriter(const Options& wo, const std::string& outfile,
+    const std::string& values)
+{
+    FileUtils::deleteFile(outfile);
+
+    Options ro;
+    ro.add("filename", Support::datapath("gdal/grid.txt"));
+
+    TextReader r;
+    r.setOptions(ro);
+
+    GDALWriter w;
+    w.setOptions(wo);
+    w.setInput(r);
+
+    PointTable t;
+
+    w.prepare(t);
+    w.execute(t);
+
+    using namespace gdal;
+
+    std::istringstream iss(values);
+
+    std::vector<double> arr;
+    while (true)
+    {
+        double d;
+        iss >> d;
+        if (!iss)
+            break;
+        arr.push_back(d);
+    }
+
+    registerDrivers();
+    Raster raster(outfile, "GTiff");
+    if (raster.open() != GDALError::None)
+    {
+        throw pdal_error(raster.errorMsg());
+    }
+    std::vector<uint8_t> data;
+    raster.readBand(data, 1);
+    double *d = reinterpret_cast<double *>(data.data());
+    for (size_t i = 0; i < arr.size(); ++i)
+        EXPECT_NEAR(arr[i], *d++, .001);
+}
+
+}
+
+TEST(GDALWriterTest, min)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "min");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+
+    const std::string output =
+        "5.000 -9999.000     7.000     8.000     8.900 "
+        "4.000 -9999.000     6.000     7.000     8.000 "
+        "3.000     4.000     5.000     5.500     6.500 "
+        "2.000     3.000     4.000     4.500     5.500 "
+        "1.000     2.000     3.000     4.000     5.000 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, minWindow)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "min");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+    wo.add("window_size", 2);
+
+    const std::string output =
+        "5.000     5.464     7.000     8.000     8.900 "
+        "4.000     4.857     6.000     7.000     8.000 "
+        "3.000     4.000     5.000     5.500     6.500 "
+        "2.000     3.000     4.000     4.500     5.500 "
+        "1.000     2.000     3.000     4.000     5.000 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, max)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "max");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+
+    const std::string output =
+        "5.000 -9999.000     7.000     8.000     9.100 "
+        "4.000 -9999.000     6.000     7.000     8.000 "
+        "3.000     4.000     5.000     6.000     7.000 "
+        "2.000     3.000     4.000     5.500     6.500 "
+        "1.000     2.000     3.000     4.500     5.500 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, maxWindow)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "max");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+    wo.add("window_size", 2);
+
+    const std::string output =
+        "5.000     5.500     7.000     8.000     9.100 "
+        "4.000     4.929     6.000     7.000     8.000 "
+        "3.000     4.000     5.000     6.000     7.000 "
+        "2.000     3.000     4.000     5.500     6.500 "
+        "1.000     2.000     3.000     4.500     5.500 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, mean)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "mean");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+
+    const std::string output =
+        "5.000 -9999.000     7.000     8.000     8.967 "
+        "4.000 -9999.000     6.000     7.000     8.000 "
+        "3.000     4.000     5.000     5.750     6.750 "
+        "2.000     3.000     4.000     4.875     5.875 "
+        "1.000     2.000     3.000     4.250     5.250 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, meanWindow)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "mean");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+    wo.add("window_size", 2);
+
+    const std::string output =
+        "5.000     5.482     7.000     8.000     8.967 "
+        "4.000     4.887     6.000     7.000     8.000 "
+        "3.000     4.000     5.000     5.750     6.750 "
+        "2.000     3.000     4.000     4.875     5.875 "
+        "1.000     2.000     3.000     4.250     5.250 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, idw)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "idw");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+
+    const std::string output =
+        "5.000 -9999.000     7.000     8.000     9.000 "
+        "4.000 -9999.000     6.000     7.000     8.000 "
+        "3.000     4.000     5.000     6.000     7.000 "
+        "2.000     3.000     4.000     5.000     6.000 "
+        "1.000     2.000     3.000     4.000     5.000 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, idwWindow)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "idw");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+    wo.add("window_size", 2);
+
+    const std::string output =
+        "5.000     5.500     7.000     8.000     9.000 "
+        "4.000     4.905     6.000     7.000     8.000 "
+        "3.000     4.000     5.000     6.000     7.000 "
+        "2.000     3.000     4.000     5.000     6.000 "
+        "1.000     2.000     3.000     4.000     5.000 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, count)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "count");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+
+    const std::string output =
+        "1.000     0.000     1.000     1.000     3.000 "
+        "1.000     0.000     1.000     1.000     1.000 "
+        "1.000     1.000     1.000     2.000     2.000 "
+        "1.000     1.000     1.000     4.000     4.000 "
+        "1.000     1.000     1.000     2.000     2.000 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, stdev)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "stdev");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+
+    const std::string output =
+        "0.000 -9999.000     0.000     0.000     0.094 "
+        "0.000 -9999.000     0.000     0.000     0.000 "
+        "0.000     0.000     0.000     0.250     0.250 "
+        "0.000     0.000     0.000     0.415     0.415 "
+        "0.000     0.000     0.000     0.250     0.250 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, stdevWindow)
+{
+    std::string outfile = Support::temppath("tmp.tif");
+
+    Options wo;
+    wo.add("gdaldriver", "GTiff");
+    wo.add("output_type", "stdev");
+    wo.add("resolution", 1);
+    wo.add("radius", .7071);
+    wo.add("filename", outfile);
+    wo.add("window_size", 2);
+
+    const std::string output =
+        "0.000     0.018     0.000     0.000     0.094 "
+        "0.000     0.032     0.000     0.000     0.000 "
+        "0.000     0.000     0.000     0.250     0.250 "
+        "0.000     0.000     0.000     0.415     0.415 "
+        "0.000     0.000     0.000     0.250     0.250 ";
+
+    runGdalWriter(wo, outfile, output);
+}
+
+TEST(GDALWriterTest, additionalDim)
+{
+    std::string outfile(Support::temppath("out.tif"));
+
+    FileUtils::deleteFile(outfile);
+
+    LasReader r;
+    Options ro;
+
+    ro.add("filename", Support::datapath("las/extrabytes.las"));
+
+    GDALWriter w;
+    Options wo;
+
+    wo.add("dimension", "Flags1");
+    wo.add("resolution", 2);
+    wo.add("radius", 2.7);
+    wo.add("filename", outfile);
+
+    r.setOptions(ro);
+    w.setOptions(wo);
+    w.setInput(r);
+
+    PointTable t;
+
+    EXPECT_NO_THROW(w.prepare(t));
+
+    Options wo2;
+
+    wo2.add("dimension", "Flag55");
+    wo2.add("resolution", 2);
+    wo2.add("radius", 2.7);
+    wo2.add("filename", outfile);
+
+    w.setOptions(wo2);
+
+    PointTable t2;
+    EXPECT_THROW(w.prepare(t2), pdal_error);
+}
diff --git a/test/unit/io/Ilvis2MetadataReaderTest.cpp b/test/unit/io/Ilvis2MetadataReaderTest.cpp
new file mode 100644
index 0000000..7711144
--- /dev/null
+++ b/test/unit/io/Ilvis2MetadataReaderTest.cpp
@@ -0,0 +1,97 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <io/Ilvis2MetadataReader.hpp>
+#include "Support.hpp"
+
+#include <iostream>
+#include <fstream>
+
+using namespace pdal;
+
+
+TEST(Ilvis2MetadataReaderTest, testReadMetadata)
+{
+    Ilvis2MetadataReader reader;
+    MetadataNode *m, n;
+    MetadataNodeList l,l1,l2,l3;
+    std::ofstream outfile;
+
+    m = new MetadataNode();
+    reader.readMetadataFile(Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT.xml"), m);
+    
+    n = m->children("GranuleUR")[0];
+    EXPECT_EQ("SC:ILVIS2.001:51203496", n.value());
+
+    n = m->children("DbID")[0];
+    EXPECT_EQ(51203496L, n.value<long>());
+
+    l = m->children("DataFile");
+    EXPECT_EQ(std::size_t{2}, l.size());
+    EXPECT_EQ("SHA1", l[1].children("ChecksumType")[0].value());
+
+    l = m->children("Campaign");
+    EXPECT_EQ(std::size_t{2}, l.size());
+
+    l = m->children("PSA");
+    EXPECT_EQ(std::size_t{3}, l.size());
+    EXPECT_EQ("SIPSMetGenVersion", l[0].children("PSAName")[0].value());
+    EXPECT_EQ("N426NA", l[2].children("PSAValue")[0].value());
+
+    l = m->children("BrowseProductGranuleId");
+    EXPECT_EQ(std::size_t{2}, l.size());
+
+    l = m->children("PHProductGranuleId");
+    EXPECT_EQ(std::size_t{1}, l.size());
+    EXPECT_EQ("PH_ID", l[0].value());
+
+    l = m->children("Platform");
+    EXPECT_EQ(std::size_t{1}, l.size());
+    l1 = l[0].children("Instrument");
+    EXPECT_EQ(std::size_t{1}, l1.size());
+    EXPECT_EQ(std::size_t{2}, l1[0].children("OperationMode").size());
+    EXPECT_EQ("Safe", l1[0].children("OperationMode")[1].value());
+    l2 = l1[0].children("Sensor");
+    EXPECT_EQ(std::size_t{1}, l2.size());
+    l3 = l2[0].children("SensorCharacteristic");
+    EXPECT_EQ(std::size_t{2}, l3.size());
+    EXPECT_EQ("CharName1", l3[0].children("CharacteristicName")[0].value());
+    EXPECT_EQ("MyValue", l3[1].children("CharacteristicValue")[0].value());
+
+    l = m->children("ConvexHull");
+    EXPECT_EQ(std::size_t{1}, l.size());
+    EXPECT_EQ(std::size_t{0}, l[0].value().find("POLYGON"));
+}
diff --git a/test/unit/io/Ilvis2ReaderTest.cpp b/test/unit/io/Ilvis2ReaderTest.cpp
new file mode 100644
index 0000000..c5bdc1f
--- /dev/null
+++ b/test/unit/io/Ilvis2ReaderTest.cpp
@@ -0,0 +1,126 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/Options.hpp>
+#include <pdal/PointView.hpp>
+
+#include <io/Ilvis2Reader.hpp>
+
+#include "Support.hpp"
+
+using namespace pdal;
+
+void checkPoint(const PointView& data, PointId index, double time,
+    double latitude, double longitude, double altitude)
+{
+    auto checkDimension = [&data,index](Dimension::Id dim,
+        double expected)
+    {
+        double actual = data.getFieldAs<double>(dim, index);
+        EXPECT_FLOAT_EQ(expected, actual);
+    };
+
+    checkDimension(Dimension::Id::Y, latitude);
+    checkDimension(Dimension::Id::X, longitude);
+    checkDimension(Dimension::Id::Z, altitude);
+    checkDimension(Dimension::Id::GpsTime, time);
+}
+
+TEST(Ilvis2ReaderTest, testReadDefault)
+{
+    Option filename("filename",
+        Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT"));
+    Options options(filename);
+    std::shared_ptr<Ilvis2Reader> reader(new Ilvis2Reader);
+    reader->setOptions(options);
+
+    PointTable table;
+
+    reader->prepare(table);
+    PointViewSet viewSet = reader->execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+
+    EXPECT_EQ(view->size(), 4u);
+
+    checkPoint(*view.get(), 0, 42504.48313,
+             78.307672,-58.785213,1956.777
+            );
+
+    checkPoint(*view.get(), 1, 42504.48512,
+             78.307592, 101.215097, 1956.588
+            );
+
+    checkPoint(*view.get(), 2, 42504.48712,
+             78.307512, -58.78459, 1956.667
+            );
+
+    checkPoint(*view.get(), 3, 42504.48712,
+             78.307512, -58.78459, 2956.667
+            );
+}
+
+
+TEST(Ilvis2ReaderTest, testReadHigh)
+{
+    Option filename("filename",
+        Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT"));
+    Options options(filename);
+    options.add("mapping","high");
+    std::shared_ptr<Ilvis2Reader> reader(new Ilvis2Reader);
+    reader->setOptions(options);
+
+    PointTable table;
+
+    reader->prepare(table);
+    PointViewSet viewSet = reader->execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+
+    EXPECT_EQ(view->size(), 3u);
+
+    checkPoint(*view.get(), 0, 42504.48313,
+             78.307672,-58.785213,1956.777
+            );
+
+    checkPoint(*view.get(), 1, 42504.48512,
+             78.307592, 101.215097, 1956.588
+            );
+
+    checkPoint(*view.get(), 2, 42504.48712,
+             78.307512, -58.78459, 2956.667
+            );
+}
diff --git a/test/unit/io/Ilvis2ReaderWithMDReaderTest.cpp b/test/unit/io/Ilvis2ReaderWithMDReaderTest.cpp
new file mode 100644
index 0000000..24b8b9d
--- /dev/null
+++ b/test/unit/io/Ilvis2ReaderWithMDReaderTest.cpp
@@ -0,0 +1,126 @@
+/******************************************************************************
+* Copyright (c) 2015, Howard Butler (howard at hobu.co)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/Options.hpp>
+#include <pdal/PointView.hpp>
+#include <io/Ilvis2Reader.hpp>
+
+#include "Support.hpp"
+
+using namespace pdal;
+
+TEST(Ilvis2ReaderWithMDReaderTest, testInvalidMetadataFile)
+{
+    Option filename("filename",
+        Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT"));
+    Options options(filename);
+    options.add("metadata", "invalidfile");
+    std::shared_ptr<Ilvis2Reader> reader(new Ilvis2Reader);
+    reader->setOptions(options);
+
+    PointTable table;
+    try
+    {
+        reader->prepare(table);
+        reader->execute(table);
+        FAIL() << "Expected an exception for an invalid file";
+    }
+    catch (pdal_error const & err)
+    {
+        EXPECT_EQ("Invalid metadata file: 'invalidfile'", std::string(err.what()));
+    }
+}
+
+
+TEST(Ilvis2ReaderWithMDReaderTest, testValidMetadataFile)
+{
+    Option filename("filename",
+        Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT"));
+    Options options(filename);
+    options.add("metadata", Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT.xml"));
+    std::shared_ptr<Ilvis2Reader> reader(new Ilvis2Reader);
+    reader->setOptions(options);
+
+    PointTable table;
+    reader->prepare(table);
+    reader->execute(table);
+
+    MetadataNode m, n;
+    MetadataNodeList l;
+    m = reader->getMetadata();
+
+    n = m.children("GranuleUR")[0];
+    EXPECT_EQ("SC:ILVIS2.001:51203496", n.value());
+
+    l = m.children("DataFile");
+    EXPECT_EQ(std::size_t{2}, l.size());
+    EXPECT_EQ("SHA1", l[1].children("ChecksumType")[0].value());
+
+    l = m.children("Platform")[0].children("Instrument")[0].children("Sensor")[0].children("SensorCharacteristic");
+    EXPECT_EQ(std::size_t{2}, l.size());
+    EXPECT_EQ("CharName1", l[0].children("CharacteristicName")[0].value());
+    EXPECT_EQ("MyValue", l[1].children("CharacteristicValue")[0].value());
+}
+
+
+TEST(Ilvis2ReaderWithMDReaderTest, testNoMetadataFile)
+{
+    Option filename("filename",
+        Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT"));
+    Options options(filename);
+    std::shared_ptr<Ilvis2Reader> reader(new Ilvis2Reader);
+    reader->setOptions(options);
+
+    PointTable table;
+    reader->prepare(table);
+    reader->execute(table);
+
+    MetadataNode m;
+    MetadataNodeList l;
+    m = reader->getMetadata();
+
+    l = m.children("GranuleUR");
+    EXPECT_EQ(std::size_t{0}, l.size());
+
+    l = m.children("DataFile");
+    EXPECT_EQ(std::size_t{0}, l.size());
+
+    l = m.children("Platform");
+    EXPECT_EQ(std::size_t{0}, l.size());
+
+    l = m.children("ConvexHull");
+    EXPECT_EQ(std::size_t{0}, l.size());
+}
diff --git a/test/unit/io/LasReaderTest.cpp b/test/unit/io/LasReaderTest.cpp
new file mode 100644
index 0000000..63981b6
--- /dev/null
+++ b/test/unit/io/LasReaderTest.cpp
@@ -0,0 +1,509 @@
+/******************************************************************************
+ * Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+ *       names of its contributors may be used to endorse or promote
+ *       products derived from this software without specific prior
+ *       written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/Filter.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/StageFactory.hpp>
+#include <io/LasReader.hpp>
+#include "Support.hpp"
+
+using namespace pdal;
+
+namespace {
+template<typename LeftIter, typename RightIter>
+::testing::AssertionResult CheckEqualCollections(LeftIter left_begin,
+    LeftIter left_end, RightIter right_begin)
+{
+    bool equal(true);
+
+    std::string message;
+    size_t index(0);
+    while (left_begin != left_end)
+    {
+        if (*left_begin++ != *right_begin++)
+        {
+            equal = false;
+            message += "\n\tMismatch at index " + std::to_string(index);
+        }
+        ++index;
+    }
+    if (message.size())
+        message += "\n\t";
+    return equal ? ::testing::AssertionSuccess() :
+        ::testing::AssertionFailure() << message;
+}
+
+} // unnamed namespace
+
+TEST(LasReaderTest, create)
+{
+    StageFactory f;
+
+    auto s = f.createStage("readers.las");
+    EXPECT_TRUE(s);
+}
+
+
+TEST(LasReaderTest, header)
+{
+    PointTable table;
+    Options ops;
+    ops.add("filename", Support::datapath("las/simple.las"));
+
+    LasReader reader;
+    reader.setOptions(ops);
+
+    reader.prepare(table);
+    // This tests the copy ctor, too.
+    LasHeader h = reader.header();
+
+    EXPECT_EQ(h.fileSignature(), "LASF");
+    EXPECT_EQ(h.fileSourceId(), 0);
+    EXPECT_TRUE(h.projectId().isNull());
+    EXPECT_EQ(h.versionMajor(), 1);
+    EXPECT_EQ(h.versionMinor(), 2);
+    EXPECT_EQ(h.creationDOY(), 0);
+    EXPECT_EQ(h.creationYear(), 0);
+    EXPECT_EQ(h.vlrOffset(), 227);
+    EXPECT_EQ(h.pointFormat(), 3);
+    EXPECT_EQ(h.pointCount(), 1065u);
+    EXPECT_DOUBLE_EQ(h.scaleX(), .01);
+    EXPECT_DOUBLE_EQ(h.scaleY(), .01);
+    EXPECT_DOUBLE_EQ(h.scaleZ(), .01);
+    EXPECT_DOUBLE_EQ(h.offsetX(), 0);
+    EXPECT_DOUBLE_EQ(h.offsetY(), 0);
+    EXPECT_DOUBLE_EQ(h.offsetZ(), 0);
+    EXPECT_DOUBLE_EQ(h.maxX(), 638982.55);
+    EXPECT_DOUBLE_EQ(h.maxY(), 853535.43);
+    EXPECT_DOUBLE_EQ(h.maxZ(), 586.38);
+    EXPECT_DOUBLE_EQ(h.minX(), 635619.85);
+    EXPECT_DOUBLE_EQ(h.minY(), 848899.70);
+    EXPECT_DOUBLE_EQ(h.minZ(), 406.59);
+    EXPECT_EQ(h.compressed(), false);
+    EXPECT_EQ(h.compressionInfo(), "");
+    EXPECT_EQ(h.pointCountByReturn(0), 925u);
+    EXPECT_EQ(h.pointCountByReturn(1), 114u);
+    EXPECT_EQ(h.pointCountByReturn(2), 21u);
+    EXPECT_EQ(h.pointCountByReturn(3), 5u);
+    EXPECT_EQ(h.pointCountByReturn(4), 0u);
+}
+
+
+TEST(LasReaderTest, test_sequential)
+{
+    PointTable table;
+
+    Options ops1;
+    ops1.add("filename", Support::datapath("las/1.2-with-color.las"));
+    ops1.add("count", 103);
+    LasReader reader;
+    reader.setOptions(ops1);
+
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    Support::check_p0_p1_p2(*view);
+    PointViewPtr view2 = view->makeNew();
+    view2->appendPoint(*view, 100);
+    view2->appendPoint(*view, 101);
+    view2->appendPoint(*view, 102);
+    Support::check_p100_p101_p102(*view2);
+}
+
+
+static void test_a_format(const std::string& file, uint8_t majorVersion,
+    uint8_t minorVersion, int pointFormat,
+    double xref, double yref, double zref, double tref,
+    uint16_t rref,  uint16_t gref,  uint16_t bref)
+{
+    PointTable table;
+
+    Options ops1;
+    ops1.add("filename", Support::datapath(file));
+    ops1.add("count", 1);
+    LasReader reader;
+    reader.setOptions(ops1);
+    reader.prepare(table);
+
+    EXPECT_EQ(reader.header().pointFormat(), pointFormat);
+    EXPECT_EQ(reader.header().versionMajor(), majorVersion);
+    EXPECT_EQ(reader.header().versionMinor(), minorVersion);
+
+    PointViewSet viewSet = reader.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 1u);
+
+    Support::check_pN(*view, 0, xref, yref, zref, tref, rref, gref, bref);
+}
+
+TEST(LasReaderTest, test_different_formats)
+{
+    test_a_format("las/permutations/1.0_0.las", 1, 0, 0, 470692.440000, 4602888.900000, 16.000000, 0, 0, 0, 0);
+    test_a_format("las/permutations/1.0_1.las", 1, 0, 1, 470692.440000, 4602888.900000, 16.000000, 1205902800.000000, 0, 0, 0);
+
+    test_a_format("las/permutations/1.1_0.las", 1, 1, 0, 470692.440000, 4602888.900000, 16.000000, 0, 0, 0, 0);
+    test_a_format("las/permutations/1.1_1.las", 1, 1, 1, 470692.440000, 4602888.900000, 16.000000, 1205902800.000000, 0, 0, 0);
+
+    test_a_format("las/permutations/1.2_0.las", 1, 2, 0, 470692.440000, 4602888.900000, 16.000000, 0, 0, 0, 0);
+    test_a_format("las/permutations/1.2_1.las", 1, 2, 1, 470692.440000, 4602888.900000, 16.000000, 1205902800.000000, 0, 0, 0);
+    test_a_format("las/permutations/1.2_2.las", 1, 2, 2, 470692.440000, 4602888.900000, 16.000000, 0, 255, 12, 234);
+    test_a_format("las/permutations/1.2_3.las", 1, 2, 3, 470692.440000, 4602888.900000, 16.000000, 1205902800.000000, 255, 12, 234);
+}
+
+
+TEST(LasReaderTest, inspect)
+{
+    Options ops;
+    ops.add("filename", Support::datapath("las/epsg_4326.las"));
+
+    LasReader reader;
+    reader.setOptions(ops);
+
+    QuickInfo qi = reader.preview();
+
+    std::string testWkt = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433],AUTHORITY[\"EPSG\",\"4326\"]]";
+
+    EXPECT_EQ(qi.m_srs.getWKT(), testWkt);
+    EXPECT_EQ(qi.m_pointCount, 5380u);
+
+    BOX3D bounds(-94.683465399999989, 31.0367341, 39.081000199999998,
+        -94.660631099999989, 31.047329099999999, 78.119000200000002);
+    EXPECT_EQ(qi.m_bounds, bounds);
+
+    const char *dims[] =
+    {
+        "Classification",
+        "EdgeOfFlightLine",
+        "Intensity",
+        "NumberOfReturns",
+        "PointSourceId",
+        "ReturnNumber",
+        "ScanAngleRank",
+        "ScanDirectionFlag",
+        "UserData",
+        "X",
+        "Y",
+        "Z"
+    };
+
+    std::sort(qi.m_dimNames.begin(), qi.m_dimNames.end());
+    EXPECT_TRUE(CheckEqualCollections(qi.m_dimNames.begin(),
+        qi.m_dimNames.end(), std::begin(dims)));
+}
+
+TEST(LasReaderTest, test_vlr)
+{
+    PointTable table;
+
+    Options ops1;
+    ops1.add("filename", Support::datapath("las/lots_of_vlr.las"));
+    LasReader reader;
+    reader.setOptions(ops1);
+    reader.prepare(table);
+    reader.execute(table);
+
+    MetadataNode root = reader.getMetadata();
+    for (size_t i = 0; i < 390; ++i)
+    {
+        std::string name("vlr_");
+        name += std::to_string(i);
+        MetadataNode m = root.findChild(name);
+        EXPECT_TRUE(!m.value().empty()) << "No node " << i;
+    }
+}
+
+
+TEST(LasReaderTest, testInvalidFileSignature)
+{
+    PointTable table;
+
+    Options ops1;
+    ops1.add("filename", Support::datapath("las/1.2-with-color.las.wkt"));
+    LasReader reader;
+    reader.setOptions(ops1);
+
+    EXPECT_TRUE(reader.header().valid());
+}
+
+TEST(LasReaderTest, extraBytes)
+{
+    PointTable table;
+    PointLayoutPtr layout(table.layout());
+
+    Options readOps;
+    readOps.add("filename", Support::datapath("las/extrabytes.las"));
+    LasReader reader;
+    reader.setOptions(readOps);
+
+    reader.prepare(table);
+
+    DimTypeList dimTypes = layout->dimTypes();
+    EXPECT_EQ(dimTypes.size(), (size_t)25);
+
+    Dimension::Id color0 = layout->findProprietaryDim("Colors0");
+    EXPECT_EQ(layout->dimType(color0), Dimension::Type::Unsigned16);
+    Dimension::Id color1 = layout->findProprietaryDim("Colors1");
+    EXPECT_EQ(layout->dimType(color1), Dimension::Type::Unsigned16);
+    Dimension::Id color2 = layout->findProprietaryDim("Colors2");
+    EXPECT_EQ(layout->dimType(color2), Dimension::Type::Unsigned16);
+
+    Dimension::Id flag0 = layout->findProprietaryDim("Flags0");
+    EXPECT_EQ(layout->dimType(flag0), Dimension::Type::Signed8);
+    Dimension::Id flag1 = layout->findProprietaryDim("Flags1");
+    EXPECT_EQ(layout->dimType(flag1), Dimension::Type::Signed8);
+
+    Dimension::Id intense2 = layout->findProprietaryDim("Intensity");
+    EXPECT_EQ(layout->dimType(intense2), Dimension::Type::Unsigned32);
+
+    Dimension::Id time2 = layout->findProprietaryDim("Time");
+    EXPECT_EQ(layout->dimType(time2), Dimension::Type::Unsigned64);
+
+    PointViewSet viewSet = reader.execute(table);
+    EXPECT_EQ(viewSet.size(), (size_t)1);
+    PointViewPtr view = *viewSet.begin();
+
+    Dimension::Id red = layout->findDim("Red");
+    Dimension::Id green = layout->findDim("Green");
+    Dimension::Id blue = layout->findDim("Blue");
+
+    Dimension::Id returnNum = layout->findDim("ReturnNumber");
+    Dimension::Id numReturns = layout->findDim("NumberOfReturns");
+
+    Dimension::Id intensity = layout->findDim("Intensity");
+    Dimension::Id time = layout->findDim("GpsTime");
+
+    for (PointId idx = 0; idx < view->size(); ++idx)
+    {
+        EXPECT_EQ(view->getFieldAs<uint16_t>(red, idx),
+            view->getFieldAs<uint16_t>(color0, idx));
+        EXPECT_EQ(view->getFieldAs<uint16_t>(green, idx),
+            view->getFieldAs<uint16_t>(color1, idx));
+        EXPECT_EQ(view->getFieldAs<uint16_t>(blue, idx),
+            view->getFieldAs<uint16_t>(color2, idx));
+
+        EXPECT_EQ(view->getFieldAs<uint16_t>(flag0, idx),
+            view->getFieldAs<uint16_t>(returnNum, idx));
+        EXPECT_EQ(view->getFieldAs<uint16_t>(flag1, idx),
+            view->getFieldAs<uint16_t>(numReturns, idx));
+
+        EXPECT_EQ(view->getFieldAs<uint16_t>(intensity, idx),
+            view->getFieldAs<uint16_t>(intense2, idx));
+
+        // Time was written truncated rather than rounded.
+        EXPECT_NEAR(view->getFieldAs<double>(time, idx),
+            view->getFieldAs<double>(time2, idx), 1.0);
+    }
+}
+
+TEST(LasReaderTest, callback)
+{
+    PointTable table;
+    point_count_t count = 0;
+
+    Options ops;
+    ops.add("filename", Support::datapath("las/simple.las"));
+
+    Reader::PointReadFunc cb = [&count](PointView& view, PointId id)
+    {
+        count++;
+    };
+    LasReader reader;
+    reader.setOptions(ops);
+    reader.setReadCb(cb);
+
+    reader.prepare(table);
+    reader.execute(table);
+    EXPECT_EQ(count, (point_count_t)1065);
+}
+
+#ifdef PDAL_HAVE_LAZPERF
+// LAZ files are normally written in chunks of 50,000, so a file of size
+// 110,000 ensures we read some whole chunks and a partial.
+TEST(LasReaderTest, lazperf)
+{
+    Options ops1;
+    ops1.add("filename", Support::datapath("laz/autzen_trim.laz"));
+    ops1.add("compression", "lazperf");
+
+    LasReader lazReader;
+    lazReader.setOptions(ops1);
+
+    PointTable t1;
+    lazReader.prepare(t1);
+    PointViewSet pbSet = lazReader.execute(t1);
+    EXPECT_EQ(pbSet.size(), 1UL);
+    PointViewPtr view1 = *pbSet.begin();
+    EXPECT_EQ(view1->size(), (point_count_t)110000);
+
+    Options ops2;
+    ops2.add("filename", Support::datapath("las/autzen_trim.las"));
+
+    LasReader lasReader;
+    lasReader.setOptions(ops2);
+
+    PointTable t2;
+    lasReader.prepare(t2);
+    pbSet = lasReader.execute(t2);
+    EXPECT_EQ(pbSet.size(), 1UL);
+    PointViewPtr view2 = *pbSet.begin();
+    EXPECT_EQ(view2->size(), (point_count_t)110000);
+
+    DimTypeList dims = view1->dimTypes();
+    size_t pointSize = view1->pointSize();
+    EXPECT_EQ(view1->pointSize(), view2->pointSize());
+    // Validate some point data.
+    std::unique_ptr<char> buf1(new char[pointSize]);
+    std::unique_ptr<char> buf2(new char[pointSize]);
+    for (PointId i = 0; i < 110000; i += 100)
+    {
+       view1->getPackedPoint(dims, i, buf1.get());
+       view2->getPackedPoint(dims, i, buf2.get());
+       EXPECT_EQ(memcmp(buf1.get(), buf2.get(), pointSize), 0);
+    }
+}
+#endif
+
+void streamTest(const std::string src, const std::string compression)
+{
+    Options ops1;
+    ops1.add("filename", src);
+    ops1.add("compression", compression);
+
+    LasReader lasReader;
+    lasReader.setOptions(ops1);
+
+    PointTable t;
+    lasReader.prepare(t);
+    PointViewSet s = lasReader.execute(t);
+    PointViewPtr p = *s.begin();
+
+    class Checker : public Filter
+    {
+    public:
+        std::string getName() const
+            { return "checker"; }
+        Checker(PointViewPtr v) : m_cnt(0), m_view(v),
+            m_bulkBuf(v->pointSize()), m_buf(v->pointSize()),
+            m_dims(v->dimTypes())
+            {}
+    private:
+        point_count_t m_cnt;
+        PointViewPtr m_view;
+        std::vector<char> m_bulkBuf;
+        std::vector<char> m_buf;
+        DimTypeList m_dims;
+
+        bool processOne(PointRef& point)
+        {
+            PointRef bulkPoint = m_view->point(m_cnt);
+
+            bulkPoint.getPackedData(m_dims, m_bulkBuf.data());
+            point.getPackedData(m_dims, m_buf.data());
+            EXPECT_EQ(memcmp(m_buf.data(), m_bulkBuf.data(),
+                m_view->pointSize()), 0);
+            m_cnt++;
+            return true;
+        }
+
+        void done(PointTableRef)
+        {
+            EXPECT_EQ(m_cnt, 110000u);
+        }
+    };
+
+    Options ops2;
+    ops2.add("filename", Support::datapath("las/autzen_trim.las"));
+
+    LasReader lazReader;
+    lazReader.setOptions(ops2);
+
+    Checker c(p);
+    c.setInput(lazReader);
+
+    FixedPointTable fixed(100);
+    c.prepare(fixed);
+    c.execute(fixed);
+}
+
+TEST(LasReaderTest, stream)
+{
+    // Compression option is ignored for non-compressed file.
+    streamTest(Support::datapath("las/autzen_trim.las"), "laszip");
+#ifdef PDAL_HAVE_LASZIP
+    streamTest(Support::datapath("laz/autzen_trim.laz"), "laszip");
+#endif
+#ifdef PDAL_HAVE_LAZPERF
+    streamTest(Support::datapath("laz/autzen_trim.laz"), "lazperf");
+#endif
+}
+
+
+// The header of 1.2-with-color-clipped says that it has 1065 points,
+// but it really only has 1064.
+TEST(LasReaderTest, LasHeaderIncorrentPointcount)
+{
+    PointTable table;
+
+    Options readOps;
+    readOps.add("filename", Support::datapath("las/1.2-with-color-clipped.las"));
+    LasReader reader;
+    reader.setOptions(readOps);
+
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    PointViewPtr view = *viewSet.begin();
+
+    EXPECT_EQ(1064u, view->size());
+}
+
+TEST(LasReaderTest, EmptyGeotiffVlr)
+{
+    PointTable table;
+
+    Options readOps;
+    readOps.add("filename", Support::datapath("las/1.2-empty-geotiff-vlrs.las"));
+    LasReader reader;
+    reader.setOptions(readOps);
+
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    PointViewPtr view = *viewSet.begin();
+
+    EXPECT_EQ(43u, view->size());
+
+}
diff --git a/test/unit/io/LasWriterTest.cpp b/test/unit/io/LasWriterTest.cpp
new file mode 100644
index 0000000..9a0af87
--- /dev/null
+++ b/test/unit/io/LasWriterTest.cpp
@@ -0,0 +1,756 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <stdlib.h>
+
+#include <pdal/PointView.hpp>
+#include <pdal/StageFactory.hpp>
+#include <pdal/StageWrapper.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <io/BufferReader.hpp>
+#include <io/LasHeader.hpp>
+#include <io/LasReader.hpp>
+#include <io/LasWriter.hpp>
+#include <io/BpfReader.hpp>
+
+#include "Support.hpp"
+
+namespace pdal
+{
+
+//ABELL - Should probably be moved to its own file.
+class LasTester
+{
+public:
+    LasHeader *header(LasWriter& w)
+        { return &w.m_lasHeader; }
+    SpatialReference srs(LasWriter& w)
+        { return w.m_srs; }
+};
+
+} // namespace pdal
+
+using namespace pdal;
+
+TEST(LasWriterTest, srs)
+{
+    Options readerOps;
+    readerOps.add("filename", Support::datapath("las/utm15.las"));
+
+    LasReader reader;
+    reader.setOptions(readerOps);
+
+    Options writerOps;
+    writerOps.add("filename", Support::temppath("out.las"));
+    LasWriter writer;
+    writer.setInput(reader);
+    writer.setOptions(writerOps);
+
+    PointTable table;
+    writer.prepare(table);
+    writer.execute(table);
+
+    LasTester tester;
+    SpatialReference srs = tester.srs(writer);
+    EXPECT_EQ(srs, SpatialReference("EPSG:26915"));
+}
+
+
+TEST(LasWriterTest, srs2)
+{
+    Options readerOps;
+    readerOps.add("filename", Support::datapath("las/utm15.las"));
+
+    LasReader reader;
+    reader.setOptions(readerOps);
+
+    Options writerOps;
+    writerOps.add("filename", Support::temppath("out.las"));
+    writerOps.add("a_srs", "EPSG:32615");
+    LasWriter writer;
+    writer.setInput(reader);
+    writer.setOptions(writerOps);
+
+    PointTable table;
+    writer.prepare(table);
+    writer.execute(table);
+
+    LasTester tester;
+    SpatialReference srs = tester.srs(writer);
+    EXPECT_EQ(srs, SpatialReference("EPSG:32615"));
+}
+
+
+TEST(LasWriterTest, auto_offset)
+{
+    using namespace Dimension;
+
+    const std::string FILENAME(Support::temppath("offset_test.las"));
+    PointTable table;
+
+    table.layout()->registerDim(Id::X);
+
+    BufferReader bufferReader;
+
+    PointViewPtr view(new PointView(table));
+    view->setField(Id::X, 0, 125000.00);
+    view->setField(Id::X, 1, 74529.00);
+    view->setField(Id::X, 2, 523523.02);
+    bufferReader.addView(view);
+
+    Options writerOps;
+    writerOps.add("filename", FILENAME);
+    writerOps.add("offset_x", "auto");
+    writerOps.add("scale_x", "auto");
+
+    LasWriter writer;
+    writer.setOptions(writerOps);
+    writer.setInput(bufferReader);
+
+    writer.prepare(table);
+    writer.execute(table);
+
+    Options readerOps;
+    readerOps.add("filename", FILENAME);
+
+    PointTable readTable;
+
+    LasReader reader;
+    reader.setOptions(readerOps);
+
+    reader.prepare(readTable);
+    EXPECT_DOUBLE_EQ(74529.00, reader.header().offsetX());
+    PointViewSet viewSet = reader.execute(readTable);
+    EXPECT_EQ(viewSet.size(), 1u);
+    view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 3u);
+    EXPECT_NEAR(125000.00, view->getFieldAs<double>(Id::X, 0), .0001);
+    EXPECT_NEAR(74529.00, view->getFieldAs<double>(Id::X, 1), .0001);
+    EXPECT_NEAR(523523.02, view->getFieldAs<double>(Id::X, 2), .0001);
+    FileUtils::deleteFile(FILENAME);
+}
+
+TEST(LasWriterTest, extra_dims)
+{
+    Options readerOps;
+
+    readerOps.add("filename", Support::datapath("las/1.2-with-color.las"));
+    LasReader reader;
+    reader.setOptions(readerOps);
+
+    Options writerOps;
+    writerOps.add("extra_dims", "Red=int32, Blue = int16, Green = int32_t");
+    writerOps.add("filename", Support::temppath("simple.las"));
+    LasWriter writer;
+    writer.setInput(reader);
+    writer.setOptions(writerOps);
+
+    PointTable table;
+    writer.prepare(table);
+    PointViewSet viewSet = writer.execute(table);
+
+    LasTester tester;
+    LasHeader *header = tester.header(writer);
+    EXPECT_EQ(header->pointLen(), header->basePointLen() + 10);
+    PointViewPtr pb = *viewSet.begin();
+
+    uint16_t colors[][3] = {
+        { 68, 77, 88 },
+        { 92, 100, 110 },
+        { 79, 87, 87 },
+        { 100, 102, 116 },
+        { 162, 114, 145 },
+        { 163, 137, 155 },
+        { 154, 131, 144 },
+        { 104, 111, 126 },
+        { 164, 136, 156 },
+        { 72, 87, 82 },
+        { 117, 117, 136 }
+    };
+
+    Options reader2Ops;
+    reader2Ops.add("filename", Support::temppath("simple.las"));
+    reader2Ops.add("extra_dims", "R1 =int32, B1= int16 ,G1=int32_t");
+
+    LasReader reader2;
+    reader2.setOptions(reader2Ops);
+
+    PointTable readTable;
+    reader2.prepare(readTable);
+    viewSet = reader2.execute(readTable);
+    pb = *viewSet.begin();
+    Dimension::Id r1 = readTable.layout()->findDim("R1");
+    EXPECT_TRUE(r1 != Dimension::Id::Unknown);
+    Dimension::Id b1 = readTable.layout()->findDim("B1");
+    EXPECT_TRUE(b1 != Dimension::Id::Unknown);
+    Dimension::Id g1 = readTable.layout()->findDim("G1");
+    EXPECT_TRUE(g1 != Dimension::Id::Unknown);
+    EXPECT_EQ(pb->size(), (size_t)1065);
+    size_t j = 0;
+    for (PointId i = 0; i < pb->size(); i += 100)
+    {
+        EXPECT_EQ(pb->getFieldAs<int16_t>(r1, i), colors[j][0]);
+        EXPECT_EQ(pb->getFieldAs<int16_t>(g1, i), colors[j][1]);
+        EXPECT_EQ(pb->getFieldAs<int16_t>(b1, i), colors[j][2]);
+        j++;
+    }
+}
+
+TEST(LasWriterTest, all_extra_dims)
+{
+    Options readerOps;
+
+    readerOps.add("filename", Support::datapath("bpf/simple-extra.bpf"));
+    BpfReader reader;
+    reader.setOptions(readerOps);
+
+    FileUtils::deleteFile(Support::temppath("simple.las"));
+
+    Options writerOps;
+    writerOps.add("extra_dims", "all");
+    writerOps.add("filename", Support::temppath("simple.las"));
+    writerOps.add("minor_version", 4);
+    LasWriter writer;
+    writer.setInput(reader);
+    writer.setOptions(writerOps);
+
+    PointTable table;
+    writer.prepare(table);
+    writer.execute(table);
+
+    Options ops;
+    ops.add("filename", Support::temppath("simple.las"));
+
+    LasReader r;
+    r.setOptions(ops);
+
+    PointTable t2;
+    r.prepare(t2);
+    Dimension::Id foo = t2.layout()->findDim("Foo");
+    Dimension::Id bar = t2.layout()->findDim("Bar");
+    Dimension::Id baz = t2.layout()->findDim("Baz");
+
+    PointViewSet s = r.execute(t2);
+    EXPECT_EQ(s.size(), 1u);
+    PointViewPtr v = *s.begin();
+
+    // We test for floats instead of doubles because when X, Y and Z
+    // get written, they are written scaled, which loses precision.  The
+    // foo, bar and baz values are written as full-precision doubles.
+    for (PointId i = 0; i < v->size(); ++i)
+    {
+        using namespace Dimension;
+
+        ASSERT_FLOAT_EQ(v->getFieldAs<float>(Id::X, i),
+            v->getFieldAs<float>(foo, i));
+        ASSERT_FLOAT_EQ(v->getFieldAs<float>(Id::Y, i),
+            v->getFieldAs<float>(bar, i));
+        ASSERT_FLOAT_EQ(v->getFieldAs<float>(Id::Z, i),
+            v->getFieldAs<float>(baz, i));
+    }
+}
+
+// Merge a couple of 1.4 LAS files with point formats 1 and 6.  Write the
+// output with version 3.  Verify that you get point format 3 (default),
+// LAS 1.3 output and other header data forwarded.
+TEST(LasWriterTest, forward)
+{
+    Options readerOps1;
+
+    readerOps1.add("filename", Support::datapath("las/4_1.las"));
+
+    Options readerOps2;
+
+    readerOps2.add("filename", Support::datapath("las/4_6.las"));
+
+    LasReader r1;
+    r1.addOptions(readerOps1);
+
+    LasReader r2;
+    r2.addOptions(readerOps2);
+
+    StageFactory sf;
+    Stage *m = sf.createStage("filters.merge");
+    m->setInput(r1);
+    m->setInput(r2);
+
+    std::string testfile = Support::temppath("tmp.las");
+    FileUtils::deleteFile(testfile);
+
+    Options writerOps;
+    writerOps.add("forward", "header");
+    writerOps.add("minor_version", 3);
+    writerOps.add("filename", testfile);
+
+    LasWriter w;
+    w.setInput(*m);
+    w.addOptions(writerOps);
+
+    PointTable t;
+
+    w.prepare(t);
+    w.execute(t);
+
+    Options readerOps;
+    readerOps.add("filename", testfile);
+
+    LasReader r;
+
+    r.setOptions(readerOps);
+
+    PointTable t2;
+
+    r.prepare(t2);
+    r.execute(t2);
+
+    MetadataNode n1 = r.getMetadata();
+    EXPECT_EQ(n1.findChild("major_version").value<uint8_t>(), 1);
+    EXPECT_EQ(n1.findChild("minor_version").value<uint8_t>(), 3);
+    EXPECT_EQ(n1.findChild("dataformat_id").value<uint8_t>(), 3);
+    EXPECT_EQ(n1.findChild("filesource_id").value<uint8_t>(), 0);
+    // Global encoding doesn't match because 4_1.las has a bad value, so we
+    // get the default.
+    EXPECT_EQ(n1.findChild("global_encoding").value<uint8_t>(), 0);
+    EXPECT_EQ(n1.findChild("project_id").value<Uuid>(), Uuid());
+    EXPECT_EQ(n1.findChild("system_id").value(), "");
+    EXPECT_EQ(n1.findChild("software_id").value(), "TerraScan");
+    EXPECT_EQ(n1.findChild("creation_doy").value<uint16_t>(), 142);
+    EXPECT_EQ(n1.findChild("creation_year").value<uint16_t>(), 2014);
+}
+
+TEST(LasWriterTest, forwardvlr)
+{
+    Options readerOps1;
+
+    readerOps1.add("filename", Support::datapath("las/lots_of_vlr.las"));
+    LasReader r1;
+    r1.addOptions(readerOps1);
+
+    std::string testfile = Support::temppath("tmp.las");
+    FileUtils::deleteFile(testfile);
+
+    Options writerOps;
+    writerOps.add("forward", "vlr");
+    writerOps.add("filename", testfile);
+
+    LasWriter w;
+    w.setInput(r1);
+    w.addOptions(writerOps);
+
+    PointTable t;
+
+    w.prepare(t);
+    w.execute(t);
+
+    Options readerOps;
+    readerOps.add("filename", testfile);
+
+    LasReader r;
+
+    r.setOptions(readerOps);
+
+    PointTable t2;
+
+    r.prepare(t2);
+    r.execute(t2);
+
+    MetadataNode forward = t2.privateMetadata("lasforward");
+
+    auto pred = [](MetadataNode temp)
+        { return Utils::startsWith(temp.name(), "vlr_"); };
+    MetadataNodeList nodes = forward.findChildren(pred);
+    EXPECT_EQ(nodes.size(), 388UL);
+}
+
+// Test that data from three input views gets written to separate output files.
+TEST(LasWriterTest, flex)
+{
+    std::array<std::string, 3> outname =
+        {{ "test_1.las", "test_2.las", "test_3.las" }};
+
+    Options readerOps;
+    readerOps.add("filename", Support::datapath("las/simple.las"));
+
+    PointTable table;
+
+    LasReader reader;
+    reader.setOptions(readerOps);
+
+    reader.prepare(table);
+    PointViewSet views = reader.execute(table);
+    PointViewPtr v = *(views.begin());
+
+    PointViewPtr v1(new PointView(table));
+    PointViewPtr v2(new PointView(table));
+    PointViewPtr v3(new PointView(table));
+
+    std::vector<PointViewPtr> vs;
+    vs.push_back(v1);
+    vs.push_back(v2);
+    vs.push_back(v3);
+
+    for (PointId i = 0; i < v->size(); ++i)
+        vs[i % 3]->appendPoint(*v, i);
+
+    for (size_t i = 0; i < outname.size(); ++i)
+        FileUtils::deleteFile(Support::temppath(outname[i]));
+
+    BufferReader reader2;
+    reader2.addView(v1);
+    reader2.addView(v2);
+    reader2.addView(v3);
+
+    Options writerOps;
+    writerOps.add("filename", Support::temppath("test_#.las"));
+
+    LasWriter writer;
+    writer.setOptions(writerOps);
+    writer.setInput(reader2);
+
+    writer.prepare(table);
+    writer.execute(table);
+
+    for (size_t i = 0; i < outname.size(); ++i)
+    {
+        std::string filename = Support::temppath(outname[i]);
+        EXPECT_TRUE(FileUtils::fileExists(filename));
+
+        Options ops;
+        ops.add("filename", filename);
+
+        LasReader r;
+        r.setOptions(ops);
+        EXPECT_EQ(r.preview().m_pointCount, 355u);
+    }
+}
+
+// Test that data from three input views gets written to a single output file.
+TEST(LasWriterTest, flex2)
+{
+    Options readerOps;
+    readerOps.add("filename", Support::datapath("las/simple.las"));
+
+    PointTable table;
+
+    LasReader reader;
+    reader.setOptions(readerOps);
+
+    reader.prepare(table);
+    PointViewSet views = reader.execute(table);
+    PointViewPtr v = *(views.begin());
+
+    PointViewPtr v1(new PointView(table));
+    PointViewPtr v2(new PointView(table));
+    PointViewPtr v3(new PointView(table));
+
+    std::vector<PointViewPtr> vs;
+    vs.push_back(v1);
+    vs.push_back(v2);
+    vs.push_back(v3);
+
+    for (PointId i = 0; i < v->size(); ++i)
+        vs[i % 3]->appendPoint(*v, i);
+
+    std::string outfile(Support::temppath("test_flex.las"));
+    FileUtils::deleteFile(outfile);
+
+    BufferReader reader2;
+    reader2.addView(v1);
+    reader2.addView(v2);
+    reader2.addView(v3);
+
+    Options writerOps;
+    writerOps.add("filename", outfile);
+
+    LasWriter writer;
+    writer.setOptions(writerOps);
+    writer.setInput(reader2);
+
+    writer.prepare(table);
+    writer.execute(table);
+
+    EXPECT_TRUE(FileUtils::fileExists(outfile));
+
+    Options ops;
+    ops.add("filename", outfile);
+
+    LasReader r;
+    r.setOptions(ops);
+    EXPECT_EQ(r.preview().m_pointCount, 1065u);
+}
+
+#if defined(PDAL_HAVE_LAZPERF) && defined(PDAL_HAVE_LASZIP)
+// LAZ files are normally written in chunks of 50,000, so a file of size
+// 110,000 ensures we read some whole chunks and a partial.
+TEST(LasWriterTest, lazperf)
+{
+    Options readerOps;
+    readerOps.add("filename", Support::datapath("las/autzen_trim.las"));
+
+    LasReader lazReader;
+    lazReader.setOptions(readerOps);
+
+    std::string testfile(Support::temppath("temp.laz"));
+
+    FileUtils::deleteFile(testfile);
+
+    Options writerOps;
+    writerOps.add("filename", testfile);
+    writerOps.add("compression", "lazperf");
+
+    LasWriter lazWriter;
+    lazWriter.setOptions(writerOps);
+    lazWriter.setInput(lazReader);
+
+    PointTable t;
+    lazWriter.prepare(t);
+    lazWriter.execute(t);
+
+    // Now test the points were properly written.  Use laszip.
+    Options ops1;
+    ops1.add("filename", testfile);
+
+    LasReader r1;
+    r1.setOptions(ops1);
+
+    PointTable t1;
+    r1.prepare(t1);
+    PointViewSet set1 = r1.execute(t1);
+    PointViewPtr view1 = *set1.begin();
+
+    Options ops2;
+    ops2.add("filename", Support::datapath("las/autzen_trim.las"));
+
+    LasReader r2;
+    r2.setOptions(ops2);
+
+    PointTable t2;
+    r2.prepare(t2);
+    PointViewSet set2 = r2.execute(t2);
+    PointViewPtr view2 = *set2.begin();
+
+    EXPECT_EQ(view1->size(), view2->size());
+    EXPECT_EQ(view1->size(), (point_count_t)110000);
+
+    DimTypeList dims = view1->dimTypes();
+    size_t pointSize = view1->pointSize();
+    EXPECT_EQ(view1->pointSize(), view2->pointSize());
+
+   // Validate some point data.
+    std::unique_ptr<char> buf1(new char[pointSize]);
+    std::unique_ptr<char> buf2(new char[pointSize]);
+    for (PointId i = 0; i < view1->pointSize(); i += 100)
+    {
+       view1->getPackedPoint(dims, i, buf1.get());
+       view2->getPackedPoint(dims, i, buf2.get());
+       EXPECT_EQ(memcmp(buf1.get(), buf2.get(), pointSize), 0);
+    }
+}
+#endif
+
+void compareFiles(const std::string& name1, const std::string& name2,
+    size_t increment = 100)
+{
+    Options o1;
+    o1.add("filename", name1);
+
+    Options o2;
+    o2.add("filename", name2);
+
+    LasReader r1;
+    r1.setOptions(o1);
+
+    LasReader r2;
+    r2.setOptions(o2);
+
+    PointTable t1;
+    r1.prepare(t1);
+    PointViewSet s1 = r1.execute(t1);
+    EXPECT_EQ(s1.size(), 1u);
+    PointViewPtr v1 = *s1.begin();
+    DimTypeList d1 = v1->dimTypes();
+    size_t size1 = v1->pointSize();
+    std::vector<char> buf1(size1);
+
+    PointTable t2;
+    r2.prepare(t2);
+    PointViewSet s2 = r2.execute(t2);
+    EXPECT_EQ(s2.size(), 1u);
+    PointViewPtr v2 = *s2.begin();
+    DimTypeList d2 = v2->dimTypes();
+    size_t size2 = v2->pointSize();
+    std::vector<char> buf2(size2);
+
+    EXPECT_EQ(v1->size(), v2->size());
+    EXPECT_EQ(d1.size(), d2.size());
+    EXPECT_EQ(size1, size2);
+
+    for (PointId i = 0; i < std::min(size1, size2); i += increment)
+    {
+       v1->getPackedPoint(d1, i, buf1.data());
+       v2->getPackedPoint(d2, i, buf2.data());
+       EXPECT_EQ(memcmp(buf1.data(), buf2.data(), std::min(size1, size2)), 0);
+    }
+}
+
+TEST(LasWriterTest, stream)
+{
+    std::string infile(Support::datapath("las/autzen_trim.las"));
+    std::string outfile(Support::temppath("trimtest.las"));
+
+    FileUtils::deleteFile(outfile);
+
+    Options ops1;
+    ops1.add("filename", infile);
+
+    LasReader r;
+    r.setOptions(ops1);
+
+    Options ops2;
+    ops2.add("filename", outfile);
+    ops2.add("forward", "all");
+    LasWriter w;
+    w.setOptions(ops2);
+    w.setInput(r);
+
+    FixedPointTable t(100);
+    w.prepare(t);
+    w.execute(t);
+
+    compareFiles(infile, outfile);
+}
+
+TEST(LasWriterTest, fix1063_1064_1065)
+{
+    std::string outfile = Support::temppath("out.las");
+    std::string infile = Support::datapath("las/test1_4.las");
+
+    FileUtils::deleteFile(outfile);
+
+    std::string cmd = "pdal translate --writers.las.forward=all "
+        "--writers.las.a_srs=\"EPSG:4326\" " + infile + " " + outfile;
+    std::string output;
+    std::cerr << "*** Shell command = " <<
+        Support::binpath(cmd) << "!\n";
+    Utils::run_shell_command(Support::binpath(cmd), output);
+
+    Options o;
+    o.add("filename", outfile);
+
+    LasReader r;
+    r.setOptions(o);
+
+    PointTable t;
+    r.prepare(t);
+    PointViewSet s = r.execute(t);
+    EXPECT_EQ(s.size(), 1u);
+    PointViewPtr v = *s.begin();
+    EXPECT_EQ(v->size(), 1000u);
+
+    // https://github.com/PDAL/PDAL/issues/1063
+    for (PointId idx = 0; idx < v->size(); ++idx)
+        EXPECT_EQ(8, v->getFieldAs<int>(Dimension::Id::ClassFlags, idx));
+
+    // https://github.com/PDAL/PDAL/issues/1064
+    MetadataNode m = r.getMetadata();
+    m = m.findChild("global_encoding");
+    EXPECT_EQ(17, m.value<int>());
+
+    // https://github.com/PDAL/PDAL/issues/1065
+    SpatialReference ref = v->spatialReference();
+    std::string wkt = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]";
+    EXPECT_EQ(ref.getWKT(), wkt);
+}
+
+/**
+namespace
+{
+
+bool diffdump(const std::string& f1, const std::string& f2)
+{
+    auto dump = [](const std::string& temp, const std::string& in)
+    {
+        std::stringstream ss;
+        ss << "lasdump -o " << temp << " " << in;
+        system(ss.str().c_str());
+    };
+
+    std::string t1 = Support::temppath("lasdump1.tmp");
+    std::string t2 = Support::temppath("lasdump2.tmp");
+
+    dump(t1, f1);
+    dump(t2, f2);
+
+    std::string diffFile = Support::temppath("dumpdiff.tmp");
+    std::stringstream ss;
+    ss << "diff " << t1 << " " << t2 << " > " << diffFile;
+    system(ss.str().c_str());
+
+    return true;
+}
+
+} // Unnamed namespace
+
+TEST(LasWriterTest, simple)
+{
+    PointTable table;
+
+    std::string infile(Support::datapath("las/1.2-with-color.las"));
+    std::string outfile(Support::temppath("simple.las"));
+
+    // remove file from earlier run, if needed
+    FileUtils::deleteFile(outfile);
+
+    Options readerOpts;
+    readerOpts.add("filename", infile);
+
+    Options writerOpts;
+    writerOpts.add("creation_year", 2014);
+    writerOpts.add("filename", outfile);
+
+    LasReader reader;
+    reader.setOptions(readerOpts);
+
+    LasWriter writer;
+    writer.setOptions(writerOpts);
+    writer.setInput(reader);
+    writer.prepare(table);
+    writer.execute(table);
+
+    diffdump(infile, outfile);
+}
+**/
+
diff --git a/test/unit/io/OptechReaderTest.cpp b/test/unit/io/OptechReaderTest.cpp
new file mode 100644
index 0000000..7049d0e
--- /dev/null
+++ b/test/unit/io/OptechReaderTest.cpp
@@ -0,0 +1,141 @@
+/******************************************************************************
+* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/StageFactory.hpp>
+#include <io/OptechReader.hpp>
+#include "Support.hpp"
+
+namespace pdal
+{
+
+namespace
+{
+
+
+std::string getTestfilePath()
+{
+    return Support::datapath("optech/sample.csd");
+}
+
+
+class OptechReaderTest : public ::testing::Test
+{
+public:
+    OptechReaderTest()
+        : ::testing::Test()
+        , m_reader()
+    {
+        Options options;
+        options.add("filename", getTestfilePath());
+        m_reader.setOptions(options);
+    }
+
+    OptechReader m_reader;
+};
+}
+
+
+TEST(OptechReader, Constructor)
+{
+    OptechReader reader1;
+
+    StageFactory f;
+    Stage* reader2 = f.createStage("readers.optech");
+    EXPECT_TRUE(reader2);
+}
+
+
+TEST_F(OptechReaderTest, Header)
+{
+    PointTable table;
+    m_reader.prepare(table);
+    CsdHeader header = m_reader.getHeader();
+
+    EXPECT_STREQ("CSD", header.signature);
+    EXPECT_STREQ("Optech Incorporated", header.vendorId);
+    EXPECT_STREQ("DASHMap", header.softwareVersion);
+    EXPECT_FLOAT_EQ(5.2010002f, header.formatVersion);
+    EXPECT_EQ(2048, header.headerSize);
+    EXPECT_EQ(1660u, header.gpsWeek);
+    EXPECT_DOUBLE_EQ(575644.74484563898, header.minTime);
+    EXPECT_DOUBLE_EQ(575644.75883187703, header.maxTime);
+    EXPECT_EQ(1000u, header.numRecords);
+    EXPECT_EQ(1u, header.numStrips);
+    EXPECT_EQ(0u, header.stripPointers[0]);
+    EXPECT_DOUBLE_EQ(0.028000000000000001, header.misalignmentAngles[0]);
+    EXPECT_DOUBLE_EQ(0.014, header.misalignmentAngles[1]);
+    EXPECT_DOUBLE_EQ(0.002, header.misalignmentAngles[2]);
+    EXPECT_DOUBLE_EQ(0.002250602070446688, header.imuOffsets[0]);
+    EXPECT_DOUBLE_EQ(-0.0021128955924643355, header.imuOffsets[1]);
+    EXPECT_DOUBLE_EQ(0.0054852207731677788, header.imuOffsets[2]);
+    EXPECT_DOUBLE_EQ(13, header.temperature);
+    EXPECT_DOUBLE_EQ(1026.75, header.pressure);
+}
+
+
+TEST_F(OptechReaderTest, ReadingPoints)
+{
+    PointTable table;
+    m_reader.prepare(table);
+    PointViewSet viewSet = m_reader.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 1000u);
+
+    EXPECT_DOUBLE_EQ(-82.554028877408555,
+                     view->getFieldAs<double>(Dimension::Id::X, 0));
+    EXPECT_DOUBLE_EQ(36.534611447321907,
+                     view->getFieldAs<double>(Dimension::Id::Y, 0));
+    EXPECT_DOUBLE_EQ(344.80889224602356,
+                     view->getFieldAs<double>(Dimension::Id::Z, 0));
+    EXPECT_DOUBLE_EQ(5.756447448456390e5,
+                     view->getFieldAs<double>(Dimension::Id::GpsTime, 0));
+    EXPECT_EQ(1, view->getFieldAs<uint8_t>(Dimension::Id::ReturnNumber, 0));
+    EXPECT_EQ(1, view->getFieldAs<uint8_t>(Dimension::Id::NumberOfReturns, 0));
+    EXPECT_FLOAT_EQ(8.27356689453125e2,
+        view->getFieldAs<float>(Dimension::Id::EchoRange, 0));
+    EXPECT_EQ(384, view->getFieldAs<uint16_t>(Dimension::Id::Intensity, 0));
+	EXPECT_DOUBLE_EQ(-14.555161476135254,
+        view->getFieldAs<float>(Dimension::Id::ScanAngleRank, 0));
+}
+
+
+TEST_F(OptechReaderTest, Spatialreference)
+{
+    SpatialReference expected("EPSG:4326");
+    EXPECT_EQ(expected, m_reader.getSpatialReference());
+}
+}
diff --git a/test/unit/io/PlyReaderTest.cpp b/test/unit/io/PlyReaderTest.cpp
new file mode 100644
index 0000000..25d6f87
--- /dev/null
+++ b/test/unit/io/PlyReaderTest.cpp
@@ -0,0 +1,143 @@
+/******************************************************************************
+* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <io/PlyReader.hpp>
+#include "Support.hpp"
+
+namespace pdal
+{
+
+
+void checkPoint(const PointViewPtr& view, point_count_t idx,
+        double x, double y, double z)
+{
+    EXPECT_DOUBLE_EQ(x, view->getFieldAs<double>(Dimension::Id::X, idx));
+    EXPECT_DOUBLE_EQ(y, view->getFieldAs<double>(Dimension::Id::Y, idx));
+    EXPECT_DOUBLE_EQ(z, view->getFieldAs<double>(Dimension::Id::Z, idx));
+}
+
+
+TEST(PlyReader, Constructor)
+{
+    PlyReader reader1;
+
+    StageFactory f;
+    Stage* reader2(f.createStage("readers.ply"));
+    EXPECT_TRUE(reader2);
+}
+
+
+TEST(PlyReader, ReadText)
+{
+    PlyReader reader;
+    Options options;
+    options.add("filename", Support::datapath("ply/simple_text.ply"));
+    reader.setOptions(options);
+
+    PointTable table;
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 3u);
+
+    checkPoint(view, 0, -1, 0, 0);
+    checkPoint(view, 1, 0, 1, 0);
+    checkPoint(view, 2, 1, 0, 0);
+}
+
+
+TEST(PlyReader, ReadTextExtraDims)
+{
+    PlyReader reader;
+    Options options;
+    options.add("filename", Support::datapath("ply/text_extradim.ply"));
+    reader.setOptions(options);
+
+    PointTable table;
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 1u);
+
+    PointLayout *layout = view->layout();
+    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::X, 0), -2.64944f);
+    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::Y, 0), -13.0955f);
+    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::Z, 0), 0.00640115f);
+    EXPECT_EQ(view->getFieldAs<float>(layout->findDim("nx"), 0), -0.0237552f);
+    EXPECT_EQ(view->getFieldAs<float>(layout->findDim("ny"), 0), -0.00902114f);
+    EXPECT_EQ(view->getFieldAs<double>(layout->findDim("nz"), 0), .999665f);
+    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Red, 0), 63);
+    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Green, 0), 200);
+    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Blue, 0), 64);
+    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Alpha, 0), 255);
+    EXPECT_EQ(view->getFieldAs<double>(layout->findDim("omg"), 0), 1234);
+}
+
+
+TEST(PlyReader, ReadBinary)
+{
+    PlyReader reader;
+    Options options;
+    options.add("filename", Support::datapath("ply/simple_binary.ply"));
+    reader.setOptions(options);
+
+    PointTable table;
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 3u);
+
+    checkPoint(view, 0, -1, 0, 0);
+    checkPoint(view, 1, 0, 1, 0);
+    checkPoint(view, 2, 1, 0, 0);
+}
+
+
+TEST(PlyReader, NoVertex)
+{
+    PlyReader reader;
+    Options options;
+    options.add("filename", Support::datapath("ply/no_vertex.ply"));
+    reader.setOptions(options);
+
+    PointTable table;
+    EXPECT_THROW(reader.prepare(table), pdal_error);
+}
+
+}
diff --git a/test/unit/io/PlyWriterTest.cpp b/test/unit/io/PlyWriterTest.cpp
new file mode 100644
index 0000000..8ed588f
--- /dev/null
+++ b/test/unit/io/PlyWriterTest.cpp
@@ -0,0 +1,77 @@
+/******************************************************************************
+* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/StageFactory.hpp>
+#include <io/FauxReader.hpp>
+#include <io/PlyWriter.hpp>
+#include "Support.hpp"
+
+
+namespace pdal
+{
+
+
+TEST(PlyWriter, Constructor)
+{
+    PlyWriter writer1;
+
+    StageFactory f;
+    Stage* writer2(f.createStage("writers.ply"));
+    EXPECT_TRUE(writer2);
+}
+
+
+TEST(PlyWriter, Write)
+{
+    Options readerOptions;
+    readerOptions.add("count", 750);
+    readerOptions.add("mode", "random");
+    FauxReader reader;
+    reader.setOptions(readerOptions);
+
+    Options writerOptions;
+    writerOptions.add("filename", Support::temppath("out.ply"));
+    PlyWriter writer;
+    writer.setOptions(writerOptions);
+    writer.setInput(reader);
+
+    PointTable table;
+    writer.prepare(table);
+    writer.execute(table);
+}
+
+
+}
diff --git a/test/unit/io/PtsReaderTest.cpp b/test/unit/io/PtsReaderTest.cpp
new file mode 100644
index 0000000..48653c7
--- /dev/null
+++ b/test/unit/io/PtsReaderTest.cpp
@@ -0,0 +1,79 @@
+/******************************************************************************
+* Copyright (c) 2016, Howard Butler <howard at hobu.co>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+#include <pdal/StageFactory.hpp>
+#include <io/PtsReader.hpp>
+#include "Support.hpp"
+
+namespace pdal
+{
+
+TEST(PtsReader, Constructor)
+{
+    PtsReader reader1;
+
+    StageFactory f;
+    Stage* reader2(f.createStage("readers.pts"));
+    EXPECT_TRUE(reader2);
+}
+
+
+
+TEST(PtsReader, ReadPtsExtraDims)
+{
+    PtsReader reader;
+    Options options;
+    options.add("filename", Support::datapath("pts/test.pts"));
+    reader.setOptions(options);
+
+    PointTable table;
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 19u);
+
+    PointLayout *layout = view->layout();
+    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::X, 0), 3.9809721f);
+    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::Y, 0), -2.006119f);
+    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::Z, 0), -0.010086f);
+    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Red, 0), 97);
+    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Green, 0), 59);
+    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Blue, 0), 38);
+}
+
+
+
+}
diff --git a/test/unit/io/QFITReaderTest.cpp b/test/unit/io/QFITReaderTest.cpp
new file mode 100644
index 0000000..fcc71f1
--- /dev/null
+++ b/test/unit/io/QFITReaderTest.cpp
@@ -0,0 +1,109 @@
+/******************************************************************************
+* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/Options.hpp>
+#include <pdal/PointView.hpp>
+#include <io/QfitReader.hpp>
+#include "Support.hpp"
+
+#include <iostream>
+
+
+using namespace pdal;
+
+void Check_Point(const PointView& data,
+                 PointId index,
+                 double xref, double yref, double zref,
+                 int32_t tref)
+{
+    double x = data.getFieldAs<double>(Dimension::Id::X, index);
+    double y = data.getFieldAs<double>(Dimension::Id::Y, index);
+    double z = data.getFieldAs<double>(Dimension::Id::Z, index);
+    int32_t t = data.getFieldAs<int32_t>(Dimension::Id::OffsetTime, index);
+
+    EXPECT_FLOAT_EQ(x, xref);
+    EXPECT_FLOAT_EQ(y, yref);
+    EXPECT_FLOAT_EQ(z, zref);
+    EXPECT_EQ(t, tref);
+}
+
+TEST(QFITReaderTest, test_10_word)
+{
+    Options options;
+
+    options.add("filename", Support::datapath("qfit/10-word.qi"));
+    options.add("flip_coordinates", false);
+    options.add("scale_z", 0.001f);
+    options.add("count", 3);
+
+    std::shared_ptr<QfitReader> reader(new QfitReader);
+    reader->setOptions(options);
+    EXPECT_EQ(reader->getName(), "readers.qfit");
+
+    PointTable table;
+    reader->prepare(table);
+    PointViewSet viewSet = reader->execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 3u);
+
+    Check_Point(*view, 0, 221.826822, 59.205160, 32.0900, 0);
+    Check_Point(*view, 1, 221.826740, 59.205161, 32.0190, 0);
+    Check_Point(*view, 2, 221.826658, 59.205164, 32.0000, 0);
+}
+
+TEST(QFITReaderTest, test_14_word)
+{
+    Options options;
+
+    options.add("filename", Support::datapath("qfit/14-word.qi"));
+    options.add("flip_coordinates", false);
+    options.add("scale_z", 0.001f);
+    options.add("count", 3);
+
+    PointTable table;
+    std::shared_ptr<QfitReader> reader(new QfitReader);
+    reader->setOptions(options);
+    reader->prepare(table);
+    PointViewSet viewSet = reader->execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 3u);
+
+    Check_Point(*view, 0, 244.306337, 35.623317, 1056.830000000, 903);
+    Check_Point(*view, 1, 244.306260, 35.623280, 1056.409000000, 903);
+    Check_Point(*view, 2, 244.306204, 35.623257, 1056.483000000, 903);
+}
diff --git a/test/unit/io/SbetReaderTest.cpp b/test/unit/io/SbetReaderTest.cpp
new file mode 100644
index 0000000..70c337f
--- /dev/null
+++ b/test/unit/io/SbetReaderTest.cpp
@@ -0,0 +1,140 @@
+/******************************************************************************
+* Copyright (c) 2014, Peter J. Gadomski (pete.gadomski at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/Options.hpp>
+#include <pdal/PipelineManager.hpp>
+#include <pdal/PointView.hpp>
+#include <pdal/util/FileUtils.hpp>
+#include <io/SbetReader.hpp>
+
+#include "Support.hpp"
+
+using namespace pdal;
+
+void checkPoint(const PointView& data, PointId index, double time,
+    double latitude, double longitude, double altitude, double xvelocity,
+    double yvelocity, double zvelocity, double roll, double pitch,
+    double heading, double wander, double xaccel, double yaccel,
+    double zaccel, double xangrate, double yangrate, double zangrate)
+{
+    auto checkDimension = [&data,index](Dimension::Id dim,
+        double expected)
+    {
+        double actual = data.getFieldAs<double>(dim, index);
+        EXPECT_FLOAT_EQ(expected, actual);
+    };
+
+    checkDimension(Dimension::Id::GpsTime, time);
+    checkDimension(Dimension::Id::Y, latitude);
+    checkDimension(Dimension::Id::X, longitude);
+    checkDimension(Dimension::Id::Z, altitude);
+    checkDimension(Dimension::Id::XVelocity, xvelocity);
+    checkDimension(Dimension::Id::YVelocity, yvelocity);
+    checkDimension(Dimension::Id::ZVelocity, zvelocity);
+    checkDimension(Dimension::Id::Roll, roll);
+    checkDimension(Dimension::Id::Pitch, pitch);
+    checkDimension(Dimension::Id::Azimuth, heading);
+    checkDimension(Dimension::Id::WanderAngle, wander);
+    checkDimension(Dimension::Id::XBodyAccel, xaccel);
+    checkDimension(Dimension::Id::YBodyAccel, yaccel);
+    checkDimension(Dimension::Id::ZBodyAccel, zaccel);
+    checkDimension(Dimension::Id::XBodyAngRate, xangrate);
+    checkDimension(Dimension::Id::YBodyAngRate, yangrate);
+    checkDimension(Dimension::Id::ZBodyAngRate, zangrate);
+}
+
+TEST(SbetReaderTest, testRead)
+{
+    Options options;
+    options.add("filename", Support::datapath("sbet/2-points.sbet"));
+
+    SbetReader reader;
+    reader.setOptions(options);
+
+    PointTable table;
+
+    reader.prepare(table);
+    PointViewSet viewSet = reader.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+
+    EXPECT_EQ(view->size(), 2u);
+
+    checkPoint(*view.get(), 0,
+               1.516310028360710e+05, 5.680211852972264e-01,
+               -2.041654392303940e+00, 1.077152953296560e+02,
+               -2.332420866600025e+00, -3.335067504871401e-01,
+               -3.093961631767838e-02, -2.813407149321339e-02,
+               -2.429905393889139e-02, 3.046773230278662e+00,
+               -2.198414736922658e-02, 7.859639737752390e-01,
+               7.849084719295495e-01, -2.978807916450262e-01,
+               6.226807982589819e-05, 9.312162756440178e-03,
+               7.217812320996525e-02);
+    checkPoint(*view.get(), 1,
+               1.516310078318641e+05, 5.680211834722869e-01,
+               -2.041654392034053e+00, 1.077151424357507e+02,
+               -2.336228229691271e+00, -3.324663118952635e-01,
+               -3.022948961008987e-02, -2.813856631423094e-02,
+               -2.425215669392169e-02, 3.047131105236811e+00,
+               -2.198416007932108e-02, 8.397590491636475e-01,
+               3.252165276637165e-01, -1.558883225990844e-01,
+               8.379685112283802e-04, 7.372886784718076e-03,
+               7.179027672314571e-02);
+}
+
+TEST(SbetReaderTest, testBadFile)
+{
+    Options options;
+    options.add("filename", Support::datapath("sbet/badfile.sbet"));
+
+    SbetReader reader;
+    reader.setOptions(options);
+    PointTable table;
+    reader.prepare(table);
+    EXPECT_THROW(reader.execute(table), pdal_error);
+}
+
+
+TEST(SbetReaderTest, testPipelineJSON)
+{
+    PipelineManager manager;
+
+    manager.readPipeline(Support::configuredpath("sbet/pipeline.json"));
+
+    point_count_t numPoints = manager.execute();
+    EXPECT_EQ(numPoints, 2u);
+    FileUtils::deleteFile(Support::datapath("sbet/outfile.txt"));
+}
diff --git a/test/unit/io/SbetWriterTest.cpp b/test/unit/io/SbetWriterTest.cpp
new file mode 100644
index 0000000..21b0d7c
--- /dev/null
+++ b/test/unit/io/SbetWriterTest.cpp
@@ -0,0 +1,96 @@
+/******************************************************************************
+* Copyright (c) 2014, Peter J. Gadomski (pete.gadomski at gmail.com)
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+#include <pdal/util/FileUtils.hpp>
+
+#include <io/SbetReader.hpp>
+#include <io/SbetWriter.hpp>
+
+#include "Support.hpp"
+
+using namespace pdal;
+
+Options makeReaderOptions()
+{
+    Options options;
+    options.add("filename", Support::datapath("sbet/2-points.sbet"));
+
+    return options;
+}
+
+
+Options makeWriterOptions()
+{
+    Options options;
+    options.add("filename", Support::temppath("SbetWriterTest.sbet"));
+    return options;
+}
+
+TEST(SbetWriterTest, testConstructor)
+{
+    SbetReader reader;
+    reader.setOptions(makeReaderOptions());
+    SbetWriter writer;
+    writer.setOptions(makeWriterOptions());
+    writer.setInput(reader);
+
+    EXPECT_EQ(writer.getName(), "writers.sbet");
+}
+
+TEST(SbetWriterTest, testWrite)
+{
+    FileUtils::deleteFile(Support::temppath("SbetWriterTest.sbet"));
+
+    // Scope forces the writer's buffer to get written to the file.  Otherwise
+    // the output file will show a file size of zero and no contents.
+    {
+        SbetReader reader;
+        reader.setOptions(makeReaderOptions());
+        SbetWriter writer;
+        writer.setOptions(makeWriterOptions());
+        writer.setInput(reader);
+
+        PointTable table;
+        writer.prepare(table);
+        writer.execute(table);
+    }
+
+    //ABELL - Write of a read file is no longer identical.
+    /**
+    EXPECT_TRUE(Support::compare_files(
+        Support::temppath("SbetWriterTest.sbet"),
+        Support::datapath("sbet/2-points.sbet")));
+    **/
+}
diff --git a/test/unit/io/TerrasolidReaderTest.cpp b/test/unit/io/TerrasolidReaderTest.cpp
new file mode 100644
index 0000000..647f848
--- /dev/null
+++ b/test/unit/io/TerrasolidReaderTest.cpp
@@ -0,0 +1,126 @@
+/******************************************************************************
+* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
+*
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following
+* conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in
+*       the documentation and/or other materials provided
+*       with the distribution.
+*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
+*       names of its contributors may be used to endorse or promote
+*       products derived from this software without specific prior
+*       written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+* OF SUCH DAMAGE.
+****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include <pdal/StageFactory.hpp>
+#include <io/TerrasolidReader.hpp>
+#include "Support.hpp"
+
+namespace pdal
+{
+
+
+namespace
+{
+
+
+std::string getTestfilePath()
+{
+    return Support::datapath("terrasolid/20020715-time-color.bin");
+}
+
+
+class TerrasolidReaderTest : public ::testing::Test
+{
+public:
+    TerrasolidReaderTest()
+        : ::testing::Test()
+        , m_reader()
+    {
+        Options options;
+        options.add("filename", getTestfilePath());
+        m_reader.setOptions(options);
+    }
+
+    TerrasolidReader m_reader;
+};
+}
+
+
+TEST(TerrasolidReader, Constructor)
+{
+    TerrasolidReader reader1;
+
+    StageFactory f;
+    Stage* reader2(f.createStage("readers.terrasolid"));
+}
+
+
+TEST_F(TerrasolidReaderTest, Header)
+{
+    PointTable table;
+    m_reader.prepare(table);
+    TerraSolidHeader header = m_reader.getHeader();
+
+    EXPECT_EQ(56, header.HdrSize);
+    EXPECT_EQ(20020715, header.HdrVersion);
+    EXPECT_EQ(970401, header.RecogVal);
+    EXPECT_STREQ("CXYZ\xe8\x3", header.RecogStr);
+    EXPECT_EQ(1000, header.PntCnt);
+    EXPECT_EQ(100, header.Units);
+    EXPECT_DOUBLE_EQ(0, header.OrgX);
+    EXPECT_DOUBLE_EQ(0, header.OrgY);
+    EXPECT_DOUBLE_EQ(0, header.OrgZ);
+    EXPECT_EQ(1, header.Time);
+    EXPECT_EQ(1, header.Color);
+}
+
+
+TEST_F(TerrasolidReaderTest, ReadingPoints)
+{
+    PointTable table;
+    m_reader.prepare(table);
+    PointViewSet viewSet = m_reader.execute(table);
+    EXPECT_EQ(viewSet.size(), 1u);
+    PointViewPtr view = *viewSet.begin();
+    EXPECT_EQ(view->size(), 1000u);
+
+    EXPECT_DOUBLE_EQ(363127.94, view->getFieldAs<double>(Dimension::Id::X, 0));
+    EXPECT_DOUBLE_EQ(3437612.33, view->getFieldAs<double>(Dimension::Id::Y, 0));
+    EXPECT_DOUBLE_EQ(55.26, view->getFieldAs<double>(Dimension::Id::Z, 0));
+    EXPECT_DOUBLE_EQ(0, view->getFieldAs<double>(Dimension::Id::OffsetTime, 0));
+    EXPECT_EQ(1840, view->getFieldAs<uint16_t>(Dimension::Id::Intensity, 0));
+    EXPECT_EQ(27207, view->getFieldAs<uint16_t>(Dimension::Id::PointSourceId, 0));
+    EXPECT_EQ(239, view->getFieldAs<uint8_t>(Dimension::Id::Red, 0));
+    EXPECT_EQ(252, view->getFieldAs<uint8_t>(Dimension::Id::Green, 0));
+    EXPECT_EQ(95, view->getFieldAs<uint8_t>(Dimension::Id::Blue, 0));
+    EXPECT_EQ(0, view->getFieldAs<uint8_t>(Dimension::Id::Alpha, 0));
+    EXPECT_EQ(1, view->getFieldAs<uint8_t>(Dimension::Id::ReturnNumber, 0));
+    EXPECT_EQ(1, view->getFieldAs<uint8_t>(Dimension::Id::NumberOfReturns, 0));
+    EXPECT_EQ(2, view->getFieldAs<uint8_t>(Dimension::Id::Classification, 0));
+    EXPECT_EQ(0, view->getFieldAs<uint8_t>(Dimension::Id::Flag, 0));
+    EXPECT_EQ(0, view->getFieldAs<uint8_t>(Dimension::Id::Mark, 0));
+}
+}
diff --git a/test/unit/io/TextReaderTest.cpp b/test/unit/io/TextReaderTest.cpp
new file mode 100644
index 0000000..6401954
--- /dev/null
+++ b/test/unit/io/TextReaderTest.cpp
@@ -0,0 +1,110 @@
+/******************************************************************************
+ * Copyright (c) 2016, Hobu Inc. (info at hobu.co)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of Hobu, Inc. nor the
+ *       names of its contributors may be used to endorse or promote
+ *       products derived from this software without specific prior
+ *       written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include "Support.hpp"
+
+#include <io/LasReader.hpp>
+#include <io/TextReader.hpp>
+
+using namespace pdal;
+
+void compareTextLas(const std::string& textFilename,
+    const std::string& lasFilename)
+{
+    TextReader t;
+    Options to;
+    to.add("filename", textFilename);
+    t.setOptions(to);
+
+    LasReader l;
+    Options lo;
+    lo.add("filename", lasFilename);
+    l.setOptions(lo);
+    
+    PointTable tt;
+    t.prepare(tt);
+    PointViewSet ts = t.execute(tt);
+    EXPECT_EQ(ts.size(), 1U);
+    PointViewPtr tv = *ts.begin();
+
+    PointTable lt;
+    l.prepare(lt);
+    PointViewSet ls = l.execute(lt);
+    EXPECT_EQ(ls.size(), 1U);
+    PointViewPtr lv = *ls.begin();
+
+    EXPECT_EQ(tv->size(), lv->size());
+
+    // Validate some point data.
+    for (PointId i = 0; i < lv->size(); ++i)
+    {
+       EXPECT_DOUBLE_EQ(tv->getFieldAs<double>(Dimension::Id::X, i),
+           lv->getFieldAs<double>(Dimension::Id::X, i));
+       EXPECT_DOUBLE_EQ(tv->getFieldAs<double>(Dimension::Id::Y, i),
+           lv->getFieldAs<double>(Dimension::Id::Y, i));
+       EXPECT_DOUBLE_EQ(tv->getFieldAs<double>(Dimension::Id::Z, i),
+           lv->getFieldAs<double>(Dimension::Id::Z, i));
+    }
+}
+
+TEST(TextReaderTest, t1)
+{
+    compareTextLas(Support::datapath("text/utm17_1.txt"),
+        Support::datapath("las/utm17.las"));
+}
+
+TEST(TextReaderTest, t2)
+{
+    compareTextLas(Support::datapath("text/utm17_2.txt"),
+        Support::datapath("las/utm17.las"));
+}
+
+TEST(TextReaderTest, t3)
+{
+    compareTextLas(Support::datapath("text/utm17_3.txt"),
+        Support::datapath("las/utm17.las"));
+}
+
+TEST(TextReaderTest, badheader)
+{
+    TextReader t;
+    Options to;
+    to.add("filename", Support::datapath("text/badheader.txt"));
+    t.setOptions(to);
+
+    PointTable tt;
+    EXPECT_THROW(t.prepare(tt), pdal_error);
+}
diff --git a/test/unit/io/TextWriterTest.cpp b/test/unit/io/TextWriterTest.cpp
new file mode 100644
index 0000000..3d99416
--- /dev/null
+++ b/test/unit/io/TextWriterTest.cpp
@@ -0,0 +1,105 @@
+/******************************************************************************
+ * Copyright (c) 2016, Hobu Inc. (info at hobu.co)
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following
+ * conditions are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of Hobu, Inc. nor the
+ *       names of its contributors may be used to endorse or promote
+ *       products derived from this software without specific prior
+ *       written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ ****************************************************************************/
+
+#include <pdal/pdal_test_main.hpp>
+
+#include "Support.hpp"
+
+#include <io/TextReader.hpp>
+#include <io/TextWriter.hpp>
+
+using namespace pdal;
+
+TEST(TextWriterTest, t1)
+{
+    std::string outfile(Support::temppath("utm17.txt"));
+    std::string infile(Support::datapath("text/utm17_1.txt"));
+
+    FileUtils::deleteFile(outfile);
+
+    TextReader r;
+    Options ro;
+
+    ro.add("filename", infile);
+    r.setOptions(ro);
+
+    TextWriter w;
+    Options wo;
+
+    wo.add("filename", outfile);
+    wo.add("order", "X,Y,Z");
+    wo.add("quote_header", false);
+    wo.add("precision", 2);
+    w.setOptions(wo);
+    w.setInput(r);
+
+    PointTable t;
+
+    w.prepare(t);
+    w.execute(t);
+
+    EXPECT_EQ(Support::compare_text_files(infile, outfile), true);
+}
+
+TEST(TextWriterTest, t2)
+{
+    std::string outfile(Support::temppath("utm17.txt"));
+    std::string infile(Support::datapath("text/utm17_2.txt"));
+
+    FileUtils::deleteFile(outfile);
+
+    TextReader r;
+    Options ro;
+
+    ro.add("filename", infile);
+    r.setOptions(ro);
+
+    TextWriter w;
+    Options wo;
+
+    wo.add("filename", outfile);
+    wo.add("order", "X,Y,Z");
+    wo.add("quote_header", false);
+    wo.add("precision", 2);
+    wo.add("delimiter", "  ");
+    w.setOptions(wo);
+    w.setInput(r);
+
+    PointTable t;
+
+    w.prepare(t);
+    w.execute(t);
+
+    EXPECT_EQ(Support::compare_text_files(infile, outfile), true);
+}
diff --git a/test/unit/io/bpf/BPFTest.cpp b/test/unit/io/bpf/BPFTest.cpp
deleted file mode 100644
index 57f9420..0000000
--- a/test/unit/io/bpf/BPFTest.cpp
+++ /dev/null
@@ -1,711 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <array>
-
-#include <pdal/Filter.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/util/Utils.hpp>
-#include <pdal/util/FileUtils.hpp>
-
-#include <BpfReader.hpp>
-#include <BpfWriter.hpp>
-#include <BufferReader.hpp>
-
-#include "Support.hpp"
-
-using namespace pdal;
-
-namespace
-{
-
-template<typename LeftIter, typename RightIter>
-::testing::AssertionResult CheckEqualCollections(
-    LeftIter left_begin, LeftIter left_end, RightIter right_begin)
-{
-    bool equal(true);
-    std::string message;
-    size_t index(0);
-    while (left_begin != left_end)
-    {
-        if (*left_begin++ != *right_begin++)
-        {
-            equal = false;
-            message += "\n\tMismatch at index " + std::to_string(index);
-        }
-        ++index;
-    }
-    if (message.size())
-        message += "\n\t";
-    return equal ? ::testing::AssertionSuccess() :
-        ::testing::AssertionFailure() << message;
-}
-
-
-
-void test_file_type_view(const std::string& filename)
-{
-    PointTable table;
-
-    struct PtData
-    {
-        float x;
-        float y;
-        float z;
-    };
-
-    Options ops;
-
-    ops.add("filename", filename);
-    ops.add("count", 506);
-    std::shared_ptr<BpfReader> reader(new BpfReader);
-    reader->setOptions(ops);
-
-    reader->prepare(table);
-    PointViewSet viewSet = reader->execute(table);
-
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 506u);
-
-    PtData pts2[3] = { {494057.312f, 4877433.5f, 130.630005f},
-                       {494133.812f, 4877440.0f, 130.440002f},
-                       {494021.094f, 4877440.0f, 130.460007f} };
-
-    for (int i = 0; i < 3; ++i)
-    {
-        float x = view->getFieldAs<float>(Dimension::Id::X, i);
-        float y = view->getFieldAs<float>(Dimension::Id::Y, i);
-        float z = view->getFieldAs<float>(Dimension::Id::Z, i);
-
-        EXPECT_FLOAT_EQ(x, pts2[i].x);
-        EXPECT_FLOAT_EQ(y, pts2[i].y);
-        EXPECT_FLOAT_EQ(z, pts2[i].z);
-    }
-
-    PtData pts[3] = { {494915.25f, 4878096.5f, 128.220001f},
-                      {494917.062f, 4878124.5f, 128.539993f},
-                      {494920.781f, 4877914.5f, 127.43f} };
-
-    for (int i = 503; i < 3; ++i)
-    {
-        float x = view->getFieldAs<float>(Dimension::Id::X, i);
-        float y = view->getFieldAs<float>(Dimension::Id::Y, i);
-        float z = view->getFieldAs<float>(Dimension::Id::Z, i);
-
-        EXPECT_FLOAT_EQ(x, pts[i].x);
-        EXPECT_FLOAT_EQ(y, pts[i].y);
-        EXPECT_FLOAT_EQ(z, pts[i].z);
-    }
-}
-
-void test_file_type_stream(const std::string& filename)
-{
-    class Checker : public Filter
-    {
-    public:
-        Checker() : m_cnt(0)
-        {}
-
-        struct PtData
-        {
-            float x;
-            float y;
-            float z;
-        };
-
-        std::string getName() const
-        { return "checker"; }
-
-        bool processOne(PointRef& p)
-        {
-            PtData pts0[3] = { {494057.312f, 4877433.5f, 130.630005f},
-                {494133.812f, 4877440.0f, 130.440002f},
-                {494021.094f, 4877440.0f, 130.460007f} };
-
-            PtData pts503[3] = { {494915.25f, 4878096.5f, 128.220001f},
-                {494917.062f, 4878124.5f, 128.539993f},
-                {494920.781f, 4877914.5f, 127.43f} };
-
-            PtData d;
-
-            if (m_cnt < 3)
-               d = pts0[0 + m_cnt];
-            else if (m_cnt >= 503 && m_cnt < 506)
-               d = pts503[m_cnt - 503];
-            else
-            {
-                m_cnt++;
-                return true;
-            }
-
-            float x = p.getFieldAs<float>(Dimension::Id::X);
-            float y = p.getFieldAs<float>(Dimension::Id::Y);
-            float z = p.getFieldAs<float>(Dimension::Id::Z);
-
-            EXPECT_FLOAT_EQ(x, d.x);
-            EXPECT_FLOAT_EQ(y, d.y);
-            EXPECT_FLOAT_EQ(z, d.z);
-            EXPECT_TRUE(m_cnt < 506) << "Count exceeded amount requested "
-                "in 'count' option.";
-
-            m_cnt++;
-            return true;
-        }
-
-    private:
-        size_t m_cnt;
-    };
-
-    FixedPointTable table(50);
-
-    Options ops;
-
-    ops.add("filename", filename);
-    ops.add("count", 506);
-    BpfReader reader;
-    reader.setOptions(ops);
-
-    Checker c;
-    c.setInput(reader);
-
-    c.prepare(table);
-    c.execute(table);
-}
-
-
-void test_file_type(const std::string& filename)
-{
-    test_file_type_view(filename);
-    test_file_type_stream(filename);
-}
-
-
-void test_roundtrip(Options& writerOps)
-{
-    std::string infile(
-        Support::datapath("bpf/autzen-utm-chipped-25-v3-interleaved.bpf"));
-    std::string outfile(Support::temppath("tmp.bpf"));
-
-    PointTable table;
-
-    Options readerOps;
-
-    readerOps.add("filename", infile);
-    BpfReader reader;
-    reader.setOptions(readerOps);
-
-    writerOps.add("filename", outfile);
-    BpfWriter writer;
-    writer.setOptions(writerOps);
-    writer.setInput(reader);
-
-    FileUtils::deleteFile(outfile);
-    writer.prepare(table);
-    writer.execute(table);
-
-    test_file_type(outfile);
-}
-
-
-} //namespace
-
-TEST(BPFTest, test_point_major)
-{
-    test_file_type(
-        Support::datapath("bpf/autzen-utm-chipped-25-v3-interleaved.bpf"));
-}
-
-TEST(BPFTest, test_dim_major)
-{
-    test_file_type(
-        Support::datapath("bpf/autzen-utm-chipped-25-v3.bpf"));
-}
-
-TEST(BPFTest, test_byte_major)
-{
-    test_file_type(
-        Support::datapath("bpf/autzen-utm-chipped-25-v3-segregated.bpf"));
-}
-
-TEST(BPFTest, test_point_major_zlib)
-{
-    test_file_type(
-        Support::datapath("bpf/"
-            "autzen-utm-chipped-25-v3-deflate-interleaved.bpf"));
-}
-
-TEST(BPFTest, test_dim_major_zlib)
-{
-    test_file_type(
-        Support::datapath("bpf/autzen-utm-chipped-25-v3-deflate.bpf"));
-}
-
-TEST(BPFTest, test_byte_major_zlib)
-{
-    test_file_type(
-        Support::datapath("bpf/"
-            "autzen-utm-chipped-25-v3-deflate-segregated.bpf"));
-}
-
-TEST(BPFTest, roundtrip_byte)
-{
-    Options ops;
-
-    ops.add("format", "BYTE");
-    test_roundtrip(ops);
-}
-
-TEST(BPFTest, roundtrip_dimension)
-{
-    Options ops;
-
-    ops.add("format", "DIMENSION");
-    test_roundtrip(ops);
-}
-
-TEST(BPFTest, roundtrip_point)
-{
-    Options ops;
-
-    ops.add("format", "POINT");
-    test_roundtrip(ops);
-}
-
-TEST(BPFTest, roundtrip_byte_compression)
-{
-    Options ops;
-
-    ops.add("format", "BYTE");
-    ops.add("compression", true);
-    test_roundtrip(ops);
-}
-
-TEST(BPFTest, roundtrip_dimension_compression)
-{
-    Options ops;
-
-    ops.add("format", "DIMENSION");
-    ops.add("compression", true);
-    test_roundtrip(ops);
-}
-
-TEST(BPFTest, roundtrip_point_compression)
-{
-    Options ops;
-
-    ops.add("format", "POINT");
-    ops.add("compression", true);
-    test_roundtrip(ops);
-}
-
-TEST(BPFTest, roundtrip_scaling)
-{
-    Options ops;
-
-    ops.add("format", "POINT");
-    ops.add("offset_x", 494000.0);
-    ops.add("offset_y", 487000.0);
-    ops.add("offset_z", 130.0);
-    ops.add("scale_x", .001);
-    ops.add("scale_y", .01);
-    ops.add("scale_z", 10.0);
-    test_roundtrip(ops);
-}
-
-TEST(BPFTest, extra_bytes)
-{
-    std::string infile(
-        Support::datapath("bpf/autzen-utm-chipped-25-v3-interleaved.bpf"));
-    std::string outfile(Support::temppath("tmp.bpf"));
-
-    PointTable table;
-
-    Options readerOps;
-    readerOps.add("filename", infile);
-    BpfReader reader;
-    reader.setOptions(readerOps);
-
-    const unsigned char buf[] = "This is a test.";
-
-    Options writerOps;
-    writerOps.add("filename", outfile);
-    writerOps.add("header_data", Utils::base64_encode(buf, sizeof(buf)));
-    BpfWriter writer;
-    writer.setOptions(writerOps);
-    writer.setInput(reader);
-
-    FileUtils::deleteFile(outfile);
-    writer.prepare(table);
-    writer.execute(table);
-
-    test_file_type(outfile);
-
-    Options readerOps2;
-    readerOps2.add("filename", outfile);
-
-    PointTable table2;
-    BpfReader reader2;
-    reader2.setOptions(readerOps2);
-    reader2.prepare(table2);
-    reader2.execute(table2);
-    MetadataNode n = reader2.getMetadata();
-    std::vector<uint8_t> outbuf =
-        Utils::base64_decode(n.findChild("header_data").value());
-    EXPECT_EQ(memcmp(outbuf.data(), buf, sizeof(buf)), 0);
-}
-
-TEST(BPFTest, bundled)
-{
-    std::string infile(
-        Support::datapath("bpf/autzen-utm-chipped-25-v3-interleaved.bpf"));
-    std::string outfile(Support::temppath("tmp.bpf"));
-
-    PointTable table;
-
-    Options readerOps;
-    readerOps.add("filename", infile);
-    BpfReader reader;
-    reader.setOptions(readerOps);
-
-    Options writerOps;
-    writerOps.add("filename", outfile);
-    writerOps.add("bundledfile", Support::datapath("bpf/bundle1"));
-    writerOps.add("bundledfile", Support::datapath("bpf/bundle2"));
-
-    BpfWriter writer;
-    writer.setOptions(writerOps);
-    writer.setInput(reader);
-
-    FileUtils::deleteFile(outfile);
-    writer.prepare(table);
-    writer.execute(table);
-
-    test_file_type(outfile);
-
-    Options readerOps2;
-    readerOps2.add("filename", outfile);
-
-    PointTable table2;
-    BpfReader reader2;
-    reader2.setOptions(readerOps2);
-    reader2.prepare(table2);
-    reader2.execute(table2);
-    MetadataNode n = reader2.getMetadata();
-    std::vector<uint8_t> outbuf;
-    auto findbundle = [](MetadataNode& m)
-        { return m.name() == "bundled_file"; };
-    MetadataNodeList nodes = n.findChildren(findbundle);
-    EXPECT_EQ(nodes.size(), 2u);
-    auto findbundle1 = [](const MetadataNode& m)
-        { return m.name() == "bundle1"; };
-    outbuf = Utils::base64_decode(n.find(findbundle1).value());
-    EXPECT_EQ(memcmp(outbuf.data(), "This is a test",
-        outbuf.size() - 1), 0);
-    auto findbundle2 = [](const MetadataNode& m)
-        { return m.name() == "bundle2"; };
-    outbuf = Utils::base64_decode(n.find(findbundle2).value());
-    EXPECT_EQ(memcmp(outbuf.data(), "This is another test",
-        outbuf.size() - 1), 0);
-}
-
-TEST(BPFTest, inspect)
-{
-    Options ops;
-    ops.add("filename", Support::datapath("bpf/autzen-dd.bpf"));
-
-    BpfReader reader;
-    reader.setOptions(ops);
-
-    QuickInfo qi = reader.preview();
-
-    std::string testWkt = "PROJCS[\"WGS 84 / SCAR IMW ST05-08\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Lambert_Conformal_Conic_2SP\"],PARAMETER[\"standard_parallel_1\",-76.66666666666667],PARAMETER[\"standard_parallel_2\",-79.33333 [...]
-    EXPECT_EQ(qi.m_srs.getWKT(), testWkt);
-
-    EXPECT_EQ(qi.m_pointCount, 1065u);
-
-    BOX3D bounds(
-        -13676090.610841721296, 4894836.9556098170578, 123.93000030517578125,
-        -13674705.011110275984, 4896224.6888861842453, 178.7299957275390625);
-    EXPECT_EQ(qi.m_bounds, bounds);
-
-    const char *dims[] =
-    {
-        "Blue",
-        "Classification",
-        "GPSTime",
-        "Green",
-        "Intensity",
-        "Number of Returns",
-        "Red",
-        "Return Information",
-        "Return Number",
-        "X",
-        "Y",
-        "Z"
-    };
-
-    std::sort(qi.m_dimNames.begin(), qi.m_dimNames.end());
-    EXPECT_TRUE(CheckEqualCollections(qi.m_dimNames.begin(),
-        qi.m_dimNames.end(), std::begin(dims)));
-}
-
-TEST(BPFTest, mueller)
-{
-    BpfMuellerMatrix xform;
-
-    double x = 12.345;
-    double y = 45.345;
-    double z = 999.341;
-
-    double xp = x;
-    double yp = y;
-    double zp = z;
-    xform.apply(xp, yp, zp);
-    EXPECT_DOUBLE_EQ(x, xp);
-    EXPECT_DOUBLE_EQ(y, yp);
-    EXPECT_DOUBLE_EQ(z, zp);
-
-    // Test translation.
-    xp = 1.0;
-    yp = 1.0;
-    zp = 1.0;
-
-    BpfMuellerMatrix translate;
-
-    translate.m_vals[3] = 2.0;
-    translate.m_vals[7] = 2.0;
-    translate.m_vals[11] = 1.0;
-    translate.apply(xp, yp, zp);
-    EXPECT_DOUBLE_EQ(xp, 3.0);
-    EXPECT_DOUBLE_EQ(yp, 3.0);
-    EXPECT_DOUBLE_EQ(zp, 2.0);
-
-    BpfMuellerMatrix scale;
-    xp = 1.0;
-    yp = 1.0;
-    zp = 1.0;
-    scale.m_vals[0] = 2.0;
-    scale.m_vals[5] = 7.0;
-    scale.m_vals[10] = -3.0;
-    scale.apply(xp, yp, zp);
-    EXPECT_DOUBLE_EQ(xp, 2.0);
-    EXPECT_DOUBLE_EQ(yp, 7.0);
-    EXPECT_DOUBLE_EQ(zp, -3.0);
-}
-
-
-TEST(BPFTest, flex)
-{
-    std::array<std::string, 3> outname =
-        {{ "test_1.bpf", "test_2.bpf", "test_3.bpf" }};
-
-    Options readerOps;
-    readerOps.add("filename",
-        Support::datapath("bpf/autzen-utm-chipped-25-v3.bpf"));
-
-    PointTable table;
-
-    BpfReader reader;
-    reader.setOptions(readerOps);
-
-    reader.prepare(table);
-    PointViewSet views = reader.execute(table);
-    PointViewPtr v = *(views.begin());
-
-    PointViewPtr v1(new PointView(table));
-    PointViewPtr v2(new PointView(table));
-    PointViewPtr v3(new PointView(table));
-
-    std::vector<PointViewPtr> vs;
-    vs.push_back(v1);
-    vs.push_back(v2);
-    vs.push_back(v3);
-
-    for (PointId i = 0; i < v->size(); ++i)
-        vs[i % 3]->appendPoint(*v, i);
-
-    for (size_t i = 0; i < outname.size(); ++i)
-        FileUtils::deleteFile(Support::temppath(outname[i]));
-
-    BufferReader reader2;
-    reader2.addView(v1);
-    reader2.addView(v2);
-    reader2.addView(v3);
-
-    Options writerOps;
-    writerOps.add("filename", Support::temppath("test_#.bpf"));
-
-    BpfWriter writer;
-    writer.setOptions(writerOps);
-    writer.setInput(reader2);
-
-    writer.prepare(table);
-    writer.execute(table);
-
-    for (size_t i = 0; i < outname.size(); ++i)
-    {
-        std::string filename = Support::temppath(outname[i]);
-        EXPECT_TRUE(FileUtils::fileExists(filename));
-
-        Options ops;
-        ops.add("filename", filename);
-
-        BpfReader r;
-        r.setOptions(ops);
-        EXPECT_EQ(r.preview().m_pointCount, 355u);
-    }
-}
-
-TEST(BPFTest, flex2)
-{
-    Options readerOps;
-    readerOps.add("filename",
-        Support::datapath("bpf/autzen-utm-chipped-25-v3.bpf"));
-
-    PointTable table;
-
-    BpfReader reader;
-    reader.setOptions(readerOps);
-
-    reader.prepare(table);
-    PointViewSet views = reader.execute(table);
-    PointViewPtr v = *(views.begin());
-
-    PointViewPtr v1(new PointView(table));
-    PointViewPtr v2(new PointView(table));
-    PointViewPtr v3(new PointView(table));
-
-    std::vector<PointViewPtr> vs;
-    vs.push_back(v1);
-    vs.push_back(v2);
-    vs.push_back(v3);
-
-    for (PointId i = 0; i < v->size(); ++i)
-        vs[i % 3]->appendPoint(*v, i);
-
-    std::string outfile(Support::temppath("test_flex.bpf"));
-    FileUtils::deleteFile(outfile);
-
-    BufferReader reader2;
-    reader2.addView(v1);
-    reader2.addView(v2);
-    reader2.addView(v3);
-
-    Options writerOps;
-    writerOps.add("filename", outfile);
-
-    BpfWriter writer;
-    writer.setOptions(writerOps);
-    writer.setInput(reader2);
-
-    writer.prepare(table);
-    writer.execute(table);
-
-    EXPECT_TRUE(FileUtils::fileExists(outfile));
-
-    Options ops;
-    ops.add("filename", outfile);
-
-    BpfReader r;
-    r.setOptions(ops);
-    EXPECT_EQ(r.preview().m_pointCount, 1065u);
-}
-
-TEST(BPFTest, outputdims)
-{
-    Options ops;
-    ops.add("filename", Support::datapath("bpf/autzen-dd.bpf"));
-
-    BpfReader reader;
-    reader.setOptions(ops);
-
-    std::string testfile(Support::temppath("test.bpf"));
-
-    FileUtils::deleteFile(testfile);
-    Options writerOps;
-    writerOps.add("filename", testfile);
-    writerOps.add("output_dims", "X, Y, Z, Red, Green");
-
-    BpfWriter writer;
-    writer.setInput(reader);
-    writer.setOptions(writerOps);
-    
-    PointTable t;
-    writer.prepare(t);
-    writer.execute(t);
-
-    Options o2;
-    o2.add("filename", testfile);
-
-    BpfReader r;
-    r.setOptions(o2);
-
-    PointTable t2;
-    r.prepare(t2);
-
-    StringList dimNames;
-    Dimension::IdList dimList = t2.layout()->dims(); 
-    EXPECT_EQ(dimList.size(), 5u);
-    for (auto di : dimList)
-        dimNames.push_back(t2.layout()->dimName(di));
-
-    const char *dims[] =
-    {
-        "Green",
-        "Red",
-        "X",
-        "Y",
-        "Z"
-    };
-   
-    std::sort(dimNames.begin(), dimNames.end());
-    EXPECT_TRUE(CheckEqualCollections(dimNames.begin(),
-        dimNames.end(), std::begin(dims)));
-
-    Options o3;
-    o3.add("filename", testfile);
-    o3.add("output_dims", "Y, Z, Red, Green");
-
-    BpfWriter w3;
-    w3.setOptions(o3);
-    
-    // Missing X dimension.
-    PointTable t3;
-    EXPECT_THROW(w3.prepare(t3), pdal_error);
-    
-}
-
diff --git a/test/unit/io/buffer/BufferTest.cpp b/test/unit/io/buffer/BufferTest.cpp
deleted file mode 100644
index b62be8a..0000000
--- a/test/unit/io/buffer/BufferTest.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Hobu Inc. (hobu at hobu.inc)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <pdal/PointView.hpp>
-#include <pdal/StageFactory.hpp>
-#include <BufferReader.hpp>
-#include <StatsFilter.hpp>
-
-using namespace pdal;
-
-namespace
-{
-
-TEST(ViewTest, test_basic)
-{
-    PointTable table;
-    PointViewPtr view(new PointView(table));
-
-    table.layout()->registerDim(Dimension::Id::X);
-    table.layout()->registerDim(Dimension::Id::Y);
-    table.layout()->registerDim(Dimension::Id::Z);
-
-    for (int i = 0; i < 20; ++i)
-    {
-        view->setField(Dimension::Id::X, i, i);
-        view->setField(Dimension::Id::Y, i, 2 * i);
-        view->setField(Dimension::Id::Z, i, -i);
-    }
-
-    Options ops;
-    BufferReader r;
-    r.setOptions(ops);
-    r.addView(view);
-
-    StatsFilter s;
-    s.setOptions(ops);
-    s.setInput(r);
-
-    s.prepare(table);
-    PointViewSet viewSet = s.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 20u);
-
-    stats::Summary xSummary = s.getStats(Dimension::Id::X);
-    EXPECT_FLOAT_EQ(xSummary.minimum(), 0);
-    EXPECT_FLOAT_EQ(xSummary.maximum(), 19);
-    EXPECT_EQ(xSummary.count(), 20u);
-    EXPECT_FLOAT_EQ(xSummary.average(), 9.5);
-
-    stats::Summary ySummary = s.getStats(Dimension::Id::Y);
-    EXPECT_FLOAT_EQ(ySummary.minimum(), 0);
-    EXPECT_FLOAT_EQ(ySummary.maximum(), 38);
-    EXPECT_EQ(ySummary.count(), 20u);
-    EXPECT_FLOAT_EQ(ySummary.average(), 19);
-
-    stats::Summary zSummary = s.getStats(Dimension::Id::Z);
-    EXPECT_FLOAT_EQ(zSummary.minimum(), -19);
-    EXPECT_FLOAT_EQ(zSummary.maximum(), 0);
-    EXPECT_EQ(zSummary.count(), 20u);
-    EXPECT_FLOAT_EQ(zSummary.average(), -9.5);
-}
-
-}
diff --git a/test/unit/io/faux/FauxReaderTest.cpp b/test/unit/io/faux/FauxReaderTest.cpp
deleted file mode 100644
index 365e8a3..0000000
--- a/test/unit/io/faux/FauxReaderTest.cpp
+++ /dev/null
@@ -1,239 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <FauxReader.hpp>
-
-using namespace pdal;
-
-TEST(FauxReaderTest, test_constant_mode_sequential_iter)
-{
-    Options ops;
-
-    BOX3D bounds(1.0, 2.0, 3.0, 101.0, 102.0, 103.0);
-    ops.add("bounds", bounds);
-    ops.add("count", 750);
-    ops.add("mode", "constant");
-    std::shared_ptr<FauxReader> reader(new FauxReader);
-    reader->setOptions(ops);
-
-    PointTable table;
-    reader->prepare(table);
-    PointViewSet viewSet = reader->execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 750u);
-    for (point_count_t i = 0; i < view->size(); i++)
-    {
-        double x = view->getFieldAs<double>(Dimension::Id::X, i);
-        double y = view->getFieldAs<double>(Dimension::Id::Y, i);
-        double z = view->getFieldAs<double>(Dimension::Id::Z, i);
-        uint64_t t = view->getFieldAs<uint64_t>(Dimension::Id::OffsetTime, i);
-
-        EXPECT_FLOAT_EQ(x, 1.0);
-        EXPECT_FLOAT_EQ(y, 2.0);
-        EXPECT_FLOAT_EQ(z, 3.0);
-        EXPECT_EQ(t, i);
-    }
-}
-
-
-TEST(FauxReaderTest, test_random_mode)
-{
-    BOX3D bounds(1.0, 2.0, 3.0, 101.0, 102.0, 103.0);
-    Options ops;
-    ops.add("bounds", bounds);
-    ops.add("count", 750);
-    ops.add("mode", "constant");
-    std::shared_ptr<FauxReader> reader(new FauxReader);
-    reader->setOptions(ops);
-
-    PointTable table;
-    reader->prepare(table);
-    PointViewSet viewSet = reader->execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 750u);
-
-    for (point_count_t i = 0; i < view->size(); ++i)
-    {
-        double x = view->getFieldAs<double>(Dimension::Id::X, i);
-        double y = view->getFieldAs<double>(Dimension::Id::Y, i);
-        double z = view->getFieldAs<double>(Dimension::Id::Z, i);
-        uint64_t t = view->getFieldAs<uint64_t>(Dimension::Id::OffsetTime, i);
-
-        EXPECT_GE(x, 1.0);
-        EXPECT_LE(x, 101.0);
-
-        EXPECT_GE(y, 2.0);
-        EXPECT_LE(y, 102.0);
-
-        EXPECT_GE(z, 3.0);
-        EXPECT_LE(z, 103.0);
-
-        EXPECT_EQ(t, i);
-    }
-}
-
-
-TEST(FauxReaderTest, test_ramp_mode_1)
-{
-    BOX3D bounds(0, 0, 0, 4, 4, 4);
-    Options ops;
-    ops.add("bounds", bounds);
-    ops.add("count", 2);
-    ops.add("mode", "ramp");
-
-    std::shared_ptr<FauxReader> reader(new FauxReader);
-    reader->setOptions(ops);
-
-    PointTable table;
-    reader->prepare(table);
-    PointViewSet viewSet = reader->execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 2u);
-
-    double x0 = view->getFieldAs<double>(Dimension::Id::X, 0);
-    double y0 = view->getFieldAs<double>(Dimension::Id::Y, 0);
-    double z0 = view->getFieldAs<double>(Dimension::Id::Z, 0);
-    uint64_t t0 = view->getFieldAs<uint64_t>(Dimension::Id::OffsetTime, 0);
-
-    double x1 = view->getFieldAs<double>(Dimension::Id::X, 1);
-    double y1 = view->getFieldAs<double>(Dimension::Id::Y, 1);
-    double z1 = view->getFieldAs<double>(Dimension::Id::Z, 1);
-    uint64_t t1 = view->getFieldAs<uint64_t>(Dimension::Id::OffsetTime, 1);
-
-    EXPECT_FLOAT_EQ(x0, 0.0);
-    EXPECT_FLOAT_EQ(y0, 0.0);
-    EXPECT_FLOAT_EQ(z0, 0.0);
-    EXPECT_EQ(t0, 0u);
-
-    EXPECT_FLOAT_EQ(x1, 4.0);
-    EXPECT_FLOAT_EQ(y1, 4.0);
-    EXPECT_FLOAT_EQ(z1, 4.0);
-    EXPECT_EQ(t1, 1u);
-}
-
-
-TEST(FauxReaderTest, test_ramp_mode_2)
-{
-    BOX3D bounds(1.0, 2.0, 3.0, 101.0, 152.0, 203.0);
-    Options ops;
-    ops.add("bounds", bounds);
-    ops.add("count", 750);
-    ops.add("mode", "ramp");
-    std::shared_ptr<FauxReader> reader(new FauxReader);
-    reader->setOptions(ops);
-
-    PointTable table;
-    reader->prepare(table);
-    PointViewSet viewSet = reader->execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 750u);
-
-    double delX = (101.0 - 1.0) / (750.0 - 1.0);
-    double delY = (152.0 - 2.0) / (750.0 - 1.0);
-    double delZ = (203.0 - 3.0) / (750.0 - 1.0);
-
-    for (point_count_t i = 0; i < view->size(); ++i)
-    {
-        double x = view->getFieldAs<double>(Dimension::Id::X, i);
-        double y = view->getFieldAs<double>(Dimension::Id::Y, i);
-        double z = view->getFieldAs<double>(Dimension::Id::Z, i);
-        uint64_t t = view->getFieldAs<uint64_t>(Dimension::Id::OffsetTime, i);
-
-        EXPECT_FLOAT_EQ(x, 1.0 + delX * i);
-        EXPECT_FLOAT_EQ(y, 2.0 + delY * i);
-        EXPECT_FLOAT_EQ(z, 3.0 + delZ * i);
-        EXPECT_EQ(t, i);
-    }
-}
-
-
-TEST(FauxReaderTest, test_return_number)
-{
-    Options ops;
-
-    BOX3D bounds(1.0, 2.0, 3.0, 101.0, 102.0, 103.0);
-    ops.add("bounds", bounds);
-    ops.add("count", 100);
-    ops.add("mode", "constant");
-    ops.add("number_of_returns", 9);
-    std::shared_ptr<FauxReader> reader(new FauxReader);
-    reader->setOptions(ops);
-
-    PointTable table;
-    reader->prepare(table);
-    PointViewSet viewSet = reader->execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 100u);
-
-    for (point_count_t i = 0; i < view->size(); i++)
-    {
-        uint8_t returnNumber = view->getFieldAs<uint8_t>(
-            Dimension::Id::ReturnNumber, i);
-        uint8_t numberOfReturns =
-            view->getFieldAs<uint8_t>(Dimension::Id::NumberOfReturns, i);
-
-        EXPECT_EQ(returnNumber, (i % 9) + 1);
-        EXPECT_EQ(numberOfReturns, 9);
-    }
-}
-
-
-TEST(FauxReaderTest, one_point)
-{
-    Options ops;
-
-    ops.add("bounds", BOX3D(1, 2, 3, 1, 2, 3));
-    ops.add("count", 1);
-    ops.add("mode", "ramp");
-    FauxReader reader;
-    reader.setOptions(ops);
-
-    PointTable table;
-    reader.prepare(table);
-    PointViewSet viewSet = reader.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 1u);
-
-    EXPECT_EQ(1, view->getFieldAs<int>(Dimension::Id::X, 0));
-    EXPECT_EQ(2, view->getFieldAs<int>(Dimension::Id::Y, 0));
-    EXPECT_EQ(3, view->getFieldAs<int>(Dimension::Id::Z, 0));
-}
diff --git a/test/unit/io/gdal/GDALReaderTest.cpp b/test/unit/io/gdal/GDALReaderTest.cpp
deleted file mode 100644
index fca0582..0000000
--- a/test/unit/io/gdal/GDALReaderTest.cpp
+++ /dev/null
@@ -1,187 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Hobu Inc. (info at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-#include <fstream>
-
-#include "GDALReader.hpp"
-#include "Support.hpp"
-
-#include <iostream>
-
-using namespace pdal;
-
-TEST(GDALReaderTest, simple)
-{
-    Options ro;
-    ro.add("filename", Support::datapath("png/autzen-height.png"));
-
-    GDALReader gr;
-    gr.setOptions(ro);
-
-    PointTable t;
-    gr.prepare(t);
-    PointViewSet s = gr.execute(t);
-    PointViewPtr v = *s.begin();
-    PointLayoutPtr l = t.layout();
-    Dimension::Id id1 = l->findDim("band-1");
-    Dimension::Id id2 = l->findDim("band-2");
-    Dimension::Id id3 = l->findDim("band-3");
-    EXPECT_EQ(v->size(), (size_t)(735 * 973));
-
-    auto verify = [v, id1, id2, id3]
-        (PointId idx, double xx, double xy, double xr, double xg, double xb)
-    {
-        double r, g, b, x, y;
-        x = v->getFieldAs<double>(Dimension::Id::X, idx);
-        y = v->getFieldAs<double>(Dimension::Id::Y, idx);
-        r = v->getFieldAs<double>(id1, idx);
-        g = v->getFieldAs<double>(id2, idx);
-        b = v->getFieldAs<double>(id3, idx);
-        EXPECT_DOUBLE_EQ(x, xx);
-        EXPECT_DOUBLE_EQ(y, xy);
-        EXPECT_DOUBLE_EQ(r, xr);
-        EXPECT_DOUBLE_EQ(g, xg);
-        EXPECT_DOUBLE_EQ(b, xb);
-    };
-
-    verify(0, .5, .5, 0, 0, 0);
-    verify(120000, 195.5, 163.5, 255, 213, 0);
-    verify(290000, 410.5, 394.5, 0, 255, 206);
-    verify(715154, 734.5, 972.5, 0, 0, 0);
-}
-
-struct Point
-{
-    double m_x;
-    double m_y;
-    double m_z;
-};
-
-std::ostream& operator << (std::ostream& o, const Point& p)
-{
-    o << p.m_x << "/" << p.m_y << "/" << p.m_z;
-    return o;
-}
-
-bool operator < (const Point& p1, const Point& p2)
-{
-    return (p1.m_x < p2.m_x ? true :
-            p1.m_x > p2.m_x ? false :
-            p1.m_y < p2.m_y ? true :
-            p1.m_y > p2.m_y ? false :
-            p1.m_z < p2.m_z ? true : false);
-}
-
-class GDALReaderTypeTest : public ::testing::Test
-{
-protected:
-    GDALReaderTypeTest()
-    {
-        std::string xyzFilename = Support::datapath("gdal/data.xyz");
-
-        std::ifstream in(xyzFilename);
-
-        while (true)
-        {
-            Point p;
-            in >> p.m_x >> p.m_y >> p.m_z;
-            if (in.eof())
-                break;
-            m_xyzPoints.push_back(p);
-        }
-    }
-
-    void compare(const std::string& path)
-    {
-        Options ro;
-        ro.add("filename", path);
-
-        GDALReader gr;
-        gr.setOptions(ro);
-
-        PointTable t;
-        gr.prepare(t);
-        Dimension::Id b1 = t.layout()->findDim("band-1");
-        PointViewSet s = gr.execute(t);
-        PointViewPtr v = *s.begin();
-
-        EXPECT_EQ(v->size(), m_xyzPoints.size());
-        for (PointId idx = 0; idx < v->size(); ++idx)
-        {
-            Point p;
-            p.m_x = v->getFieldAs<double>(Dimension::Id::X, idx);
-            p.m_y = v->getFieldAs<double>(Dimension::Id::Y, idx);
-            p.m_z = v->getFieldAs<double>(b1, idx);
-            m_gdalPoints.push_back(p);
-        }
-        std::sort(m_xyzPoints.begin(), m_xyzPoints.end());
-        std::sort(m_gdalPoints.begin(), m_gdalPoints.end());
-        for (size_t i = 0; i < m_gdalPoints.size(); ++i)
-        {
-            EXPECT_DOUBLE_EQ(m_xyzPoints[i].m_x, m_gdalPoints[i].m_x);
-            EXPECT_DOUBLE_EQ(m_xyzPoints[i].m_y, m_gdalPoints[i].m_y);
-            EXPECT_DOUBLE_EQ(m_xyzPoints[i].m_z, m_gdalPoints[i].m_z);
-        }
-    }
-
-private:
-    std::vector<Point> m_xyzPoints;    
-    std::vector<Point> m_gdalPoints;
-};
-
-TEST_F(GDALReaderTypeTest, byte)
-{
-    compare(Support::datapath("gdal/byte.tif"));
-}
-
-TEST_F(GDALReaderTypeTest, int16)
-{
-    compare(Support::datapath("gdal/int16.tif"));
-}
-
-TEST_F(GDALReaderTypeTest, int32)
-{
-    compare(Support::datapath("gdal/int32.tif"));
-}
-
-TEST_F(GDALReaderTypeTest, float32)
-{
-    compare(Support::datapath("gdal/float32.tif"));
-}
-
-TEST_F(GDALReaderTypeTest, float64)
-{
-    compare(Support::datapath("gdal/float64.tif"));
-}
diff --git a/test/unit/io/ilvis2/Ilvis2MetadataReaderTest.cpp b/test/unit/io/ilvis2/Ilvis2MetadataReaderTest.cpp
deleted file mode 100644
index 7ac7b91..0000000
--- a/test/unit/io/ilvis2/Ilvis2MetadataReaderTest.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <Ilvis2MetadataReader.hpp>
-#include "Support.hpp"
-
-#include <iostream>
-#include <fstream>
-
-using namespace pdal;
-
-
-TEST(Ilvis2MetadataReaderTest, testReadMetadata)
-{
-    Ilvis2MetadataReader reader;
-    MetadataNode *m, n;
-    MetadataNodeList l,l1,l2,l3;
-    std::ofstream outfile;
-
-    m = new MetadataNode();
-    reader.readMetadataFile(Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT.xml"), m);
-    
-    n = m->children("GranuleUR")[0];
-    EXPECT_EQ("SC:ILVIS2.001:51203496", n.value());
-
-    n = m->children("DbID")[0];
-    EXPECT_EQ(51203496L, n.value<long>());
-
-    l = m->children("DataFile");
-    EXPECT_EQ(std::size_t{2}, l.size());
-    EXPECT_EQ("SHA1", l[1].children("ChecksumType")[0].value());
-
-    l = m->children("Campaign");
-    EXPECT_EQ(std::size_t{2}, l.size());
-
-    l = m->children("PSA");
-    EXPECT_EQ(std::size_t{3}, l.size());
-    EXPECT_EQ("SIPSMetGenVersion", l[0].children("PSAName")[0].value());
-    EXPECT_EQ("N426NA", l[2].children("PSAValue")[0].value());
-
-    l = m->children("BrowseProductGranuleId");
-    EXPECT_EQ(std::size_t{2}, l.size());
-
-    l = m->children("PHProductGranuleId");
-    EXPECT_EQ(std::size_t{1}, l.size());
-    EXPECT_EQ("PH_ID", l[0].value());
-
-    l = m->children("Platform");
-    EXPECT_EQ(std::size_t{1}, l.size());
-    l1 = l[0].children("Instrument");
-    EXPECT_EQ(std::size_t{1}, l1.size());
-    EXPECT_EQ(std::size_t{2}, l1[0].children("OperationMode").size());
-    EXPECT_EQ("Safe", l1[0].children("OperationMode")[1].value());
-    l2 = l1[0].children("Sensor");
-    EXPECT_EQ(std::size_t{1}, l2.size());
-    l3 = l2[0].children("SensorCharacteristic");
-    EXPECT_EQ(std::size_t{2}, l3.size());
-    EXPECT_EQ("CharName1", l3[0].children("CharacteristicName")[0].value());
-    EXPECT_EQ("MyValue", l3[1].children("CharacteristicValue")[0].value());
-
-    l = m->children("ConvexHull");
-    EXPECT_EQ(std::size_t{1}, l.size());
-    EXPECT_EQ(std::size_t{0}, l[0].value().find("POLYGON"));
-}
diff --git a/test/unit/io/ilvis2/Ilvis2ReaderTest.cpp b/test/unit/io/ilvis2/Ilvis2ReaderTest.cpp
deleted file mode 100644
index 5b3c038..0000000
--- a/test/unit/io/ilvis2/Ilvis2ReaderTest.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <pdal/Options.hpp>
-#include <pdal/PointView.hpp>
-
-#include <Ilvis2Reader.hpp>
-
-#include "Support.hpp"
-
-using namespace pdal;
-
-void checkPoint(const PointView& data, PointId index, double time,
-    double latitude, double longitude, double altitude)
-{
-    auto checkDimension = [&data,index](Dimension::Id dim,
-        double expected)
-    {
-        double actual = data.getFieldAs<double>(dim, index);
-        EXPECT_FLOAT_EQ(expected, actual);
-    };
-
-    checkDimension(Dimension::Id::Y, latitude);
-    checkDimension(Dimension::Id::X, longitude);
-    checkDimension(Dimension::Id::Z, altitude);
-    checkDimension(Dimension::Id::GpsTime, time);
-}
-
-TEST(Ilvis2ReaderTest, testReadDefault)
-{
-    Option filename("filename",
-        Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT"));
-    Options options(filename);
-    std::shared_ptr<Ilvis2Reader> reader(new Ilvis2Reader);
-    reader->setOptions(options);
-
-    PointTable table;
-
-    reader->prepare(table);
-    PointViewSet viewSet = reader->execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-
-    EXPECT_EQ(view->size(), 4u);
-
-    checkPoint(*view.get(), 0, 42504.48313,
-             78.307672,-58.785213,1956.777
-            );
-
-    checkPoint(*view.get(), 1, 42504.48512,
-             78.307592, 101.215097, 1956.588
-            );
-
-    checkPoint(*view.get(), 2, 42504.48712,
-             78.307512, -58.78459, 1956.667
-            );
-
-    checkPoint(*view.get(), 3, 42504.48712,
-             78.307512, -58.78459, 2956.667
-            );
-}
-
-
-TEST(Ilvis2ReaderTest, testReadHigh)
-{
-    Option filename("filename",
-        Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT"));
-    Options options(filename);
-    options.add("mapping","high");
-    std::shared_ptr<Ilvis2Reader> reader(new Ilvis2Reader);
-    reader->setOptions(options);
-
-    PointTable table;
-
-    reader->prepare(table);
-    PointViewSet viewSet = reader->execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-
-    EXPECT_EQ(view->size(), 3u);
-
-    checkPoint(*view.get(), 0, 42504.48313,
-             78.307672,-58.785213,1956.777
-            );
-
-    checkPoint(*view.get(), 1, 42504.48512,
-             78.307592, 101.215097, 1956.588
-            );
-
-    checkPoint(*view.get(), 2, 42504.48712,
-             78.307512, -58.78459, 2956.667
-            );
-}
diff --git a/test/unit/io/ilvis2/Ilvis2ReaderWithMDReaderTest.cpp b/test/unit/io/ilvis2/Ilvis2ReaderWithMDReaderTest.cpp
deleted file mode 100644
index 8f926ed..0000000
--- a/test/unit/io/ilvis2/Ilvis2ReaderWithMDReaderTest.cpp
+++ /dev/null
@@ -1,127 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Howard Butler (howard at hobu.co)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <pdal/Options.hpp>
-#include <pdal/PointView.hpp>
-
-#include <Ilvis2Reader.hpp>
-
-#include "Support.hpp"
-
-using namespace pdal;
-
-TEST(Ilvis2ReaderWithMDReaderTest, testInvalidMetadataFile)
-{
-    Option filename("filename",
-        Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT"));
-    Options options(filename);
-    options.add("metadata", "invalidfile");
-    std::shared_ptr<Ilvis2Reader> reader(new Ilvis2Reader);
-    reader->setOptions(options);
-
-    PointTable table;
-    try
-    {
-        reader->prepare(table);
-        reader->execute(table);
-        FAIL() << "Expected an exception for an invalid file";
-    }
-    catch (pdal_error const & err)
-    {
-        EXPECT_EQ("Invalid metadata file: 'invalidfile'", std::string(err.what()));
-    }
-}
-
-
-TEST(Ilvis2ReaderWithMDReaderTest, testValidMetadataFile)
-{
-    Option filename("filename",
-        Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT"));
-    Options options(filename);
-    options.add("metadata", Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT.xml"));
-    std::shared_ptr<Ilvis2Reader> reader(new Ilvis2Reader);
-    reader->setOptions(options);
-
-    PointTable table;
-    reader->prepare(table);
-    reader->execute(table);
-
-    MetadataNode m, n;
-    MetadataNodeList l;
-    m = reader->getMetadata();
-
-    n = m.children("GranuleUR")[0];
-    EXPECT_EQ("SC:ILVIS2.001:51203496", n.value());
-
-    l = m.children("DataFile");
-    EXPECT_EQ(std::size_t{2}, l.size());
-    EXPECT_EQ("SHA1", l[1].children("ChecksumType")[0].value());
-
-    l = m.children("Platform")[0].children("Instrument")[0].children("Sensor")[0].children("SensorCharacteristic");
-    EXPECT_EQ(std::size_t{2}, l.size());
-    EXPECT_EQ("CharName1", l[0].children("CharacteristicName")[0].value());
-    EXPECT_EQ("MyValue", l[1].children("CharacteristicValue")[0].value());
-}
-
-
-TEST(Ilvis2ReaderWithMDReaderTest, testNoMetadataFile)
-{
-    Option filename("filename",
-        Support::datapath("ilvis2/ILVIS2_TEST_FILE.TXT"));
-    Options options(filename);
-    std::shared_ptr<Ilvis2Reader> reader(new Ilvis2Reader);
-    reader->setOptions(options);
-
-    PointTable table;
-    reader->prepare(table);
-    reader->execute(table);
-
-    MetadataNode m;
-    MetadataNodeList l;
-    m = reader->getMetadata();
-
-    l = m.children("GranuleUR");
-    EXPECT_EQ(std::size_t{0}, l.size());
-
-    l = m.children("DataFile");
-    EXPECT_EQ(std::size_t{0}, l.size());
-
-    l = m.children("Platform");
-    EXPECT_EQ(std::size_t{0}, l.size());
-
-    l = m.children("ConvexHull");
-    EXPECT_EQ(std::size_t{0}, l.size());
-}
diff --git a/test/unit/io/las/LasReaderTest.cpp b/test/unit/io/las/LasReaderTest.cpp
deleted file mode 100644
index 73277b6..0000000
--- a/test/unit/io/las/LasReaderTest.cpp
+++ /dev/null
@@ -1,495 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
- *       names of its contributors may be used to endorse or promote
- *       products derived from this software without specific prior
- *       written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <pdal/Filter.hpp>
-#include <pdal/PointView.hpp>
-#include <pdal/StageFactory.hpp>
-#include <LasReader.hpp>
-#include "Support.hpp"
-
-using namespace pdal;
-
-namespace {
-template<typename LeftIter, typename RightIter>
-::testing::AssertionResult CheckEqualCollections(LeftIter left_begin,
-    LeftIter left_end, RightIter right_begin)
-{
-    bool equal(true);
-
-    std::string message;
-    size_t index(0);
-    while (left_begin != left_end)
-    {
-        if (*left_begin++ != *right_begin++)
-        {
-            equal = false;
-            message += "\n\tMismatch at index " + std::to_string(index);
-        }
-        ++index;
-    }
-    if (message.size())
-        message += "\n\t";
-    return equal ? ::testing::AssertionSuccess() :
-        ::testing::AssertionFailure() << message;
-}
-
-} // unnamed namespace
-
-TEST(LasReaderTest, create)
-{
-    StageFactory f;
-
-    auto s = f.createStage("readers.las");
-    EXPECT_TRUE(s);
-}
-
-
-TEST(LasReaderTest, header)
-{
-    PointTable table;
-    Options ops;
-    ops.add("filename", Support::datapath("las/simple.las"));
-
-    LasReader reader;
-    reader.setOptions(ops);
-
-    reader.prepare(table);
-    // This tests the copy ctor, too.
-    LasHeader h = reader.header();
-
-    EXPECT_EQ(h.fileSignature(), "LASF");
-    EXPECT_EQ(h.fileSourceId(), 0);
-    EXPECT_TRUE(h.projectId().isNull());
-    EXPECT_EQ(h.versionMajor(), 1);
-    EXPECT_EQ(h.versionMinor(), 2);
-    EXPECT_EQ(h.creationDOY(), 0);
-    EXPECT_EQ(h.creationYear(), 0);
-    EXPECT_EQ(h.vlrOffset(), 227);
-    EXPECT_EQ(h.pointFormat(), 3);
-    EXPECT_EQ(h.pointCount(), 1065u);
-    EXPECT_DOUBLE_EQ(h.scaleX(), .01);
-    EXPECT_DOUBLE_EQ(h.scaleY(), .01);
-    EXPECT_DOUBLE_EQ(h.scaleZ(), .01);
-    EXPECT_DOUBLE_EQ(h.offsetX(), 0);
-    EXPECT_DOUBLE_EQ(h.offsetY(), 0);
-    EXPECT_DOUBLE_EQ(h.offsetZ(), 0);
-    EXPECT_DOUBLE_EQ(h.maxX(), 638982.55);
-    EXPECT_DOUBLE_EQ(h.maxY(), 853535.43);
-    EXPECT_DOUBLE_EQ(h.maxZ(), 586.38);
-    EXPECT_DOUBLE_EQ(h.minX(), 635619.85);
-    EXPECT_DOUBLE_EQ(h.minY(), 848899.70);
-    EXPECT_DOUBLE_EQ(h.minZ(), 406.59);
-    EXPECT_EQ(h.compressed(), false);
-    EXPECT_EQ(h.compressionInfo(), "");
-    EXPECT_EQ(h.pointCountByReturn(0), 925u);
-    EXPECT_EQ(h.pointCountByReturn(1), 114u);
-    EXPECT_EQ(h.pointCountByReturn(2), 21u);
-    EXPECT_EQ(h.pointCountByReturn(3), 5u);
-    EXPECT_EQ(h.pointCountByReturn(4), 0u);
-}
-
-
-TEST(LasReaderTest, test_sequential)
-{
-    PointTable table;
-
-    Options ops1;
-    ops1.add("filename", Support::datapath("las/1.2-with-color.las"));
-    ops1.add("count", 103);
-    LasReader reader;
-    reader.setOptions(ops1);
-
-    reader.prepare(table);
-    PointViewSet viewSet = reader.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    Support::check_p0_p1_p2(*view);
-    PointViewPtr view2 = view->makeNew();
-    view2->appendPoint(*view, 100);
-    view2->appendPoint(*view, 101);
-    view2->appendPoint(*view, 102);
-    Support::check_p100_p101_p102(*view2);
-}
-
-
-static void test_a_format(const std::string& file, uint8_t majorVersion,
-    uint8_t minorVersion, int pointFormat,
-    double xref, double yref, double zref, double tref,
-    uint16_t rref,  uint16_t gref,  uint16_t bref)
-{
-    PointTable table;
-
-    Options ops1;
-    ops1.add("filename", Support::datapath(file));
-    ops1.add("count", 1);
-    LasReader reader;
-    reader.setOptions(ops1);
-    reader.prepare(table);
-
-    EXPECT_EQ(reader.header().pointFormat(), pointFormat);
-    EXPECT_EQ(reader.header().versionMajor(), majorVersion);
-    EXPECT_EQ(reader.header().versionMinor(), minorVersion);
-
-    PointViewSet viewSet = reader.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 1u);
-
-    Support::check_pN(*view, 0, xref, yref, zref, tref, rref, gref, bref);
-}
-
-TEST(LasReaderTest, test_different_formats)
-{
-    test_a_format("las/permutations/1.0_0.las", 1, 0, 0, 470692.440000, 4602888.900000, 16.000000, 0, 0, 0, 0);
-    test_a_format("las/permutations/1.0_1.las", 1, 0, 1, 470692.440000, 4602888.900000, 16.000000, 1205902800.000000, 0, 0, 0);
-
-    test_a_format("las/permutations/1.1_0.las", 1, 1, 0, 470692.440000, 4602888.900000, 16.000000, 0, 0, 0, 0);
-    test_a_format("las/permutations/1.1_1.las", 1, 1, 1, 470692.440000, 4602888.900000, 16.000000, 1205902800.000000, 0, 0, 0);
-
-    test_a_format("las/permutations/1.2_0.las", 1, 2, 0, 470692.440000, 4602888.900000, 16.000000, 0, 0, 0, 0);
-    test_a_format("las/permutations/1.2_1.las", 1, 2, 1, 470692.440000, 4602888.900000, 16.000000, 1205902800.000000, 0, 0, 0);
-    test_a_format("las/permutations/1.2_2.las", 1, 2, 2, 470692.440000, 4602888.900000, 16.000000, 0, 255, 12, 234);
-    test_a_format("las/permutations/1.2_3.las", 1, 2, 3, 470692.440000, 4602888.900000, 16.000000, 1205902800.000000, 255, 12, 234);
-}
-
-
-TEST(LasReaderTest, inspect)
-{
-    Options ops;
-    ops.add("filename", Support::datapath("las/epsg_4326.las"));
-
-    LasReader reader;
-    reader.setOptions(ops);
-
-    QuickInfo qi = reader.preview();
-
-    std::string testWkt = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433],AUTHORITY[\"EPSG\",\"4326\"]]";
-
-#ifdef PDAL_HAVE_LIBGEOTIFF
-    EXPECT_EQ(qi.m_srs.getWKT(), testWkt);
-#endif // PDAL_HAVE_LIBGEOTIFF
-
-    EXPECT_EQ(qi.m_pointCount, 5380u);
-
-    BOX3D bounds(-94.683465399999989, 31.0367341, 39.081000199999998,
-        -94.660631099999989, 31.047329099999999, 78.119000200000002);
-    EXPECT_EQ(qi.m_bounds, bounds);
-
-    const char *dims[] =
-    {
-        "Classification",
-        "EdgeOfFlightLine",
-        "Intensity",
-        "NumberOfReturns",
-        "PointSourceId",
-        "ReturnNumber",
-        "ScanAngleRank",
-        "ScanDirectionFlag",
-        "UserData",
-        "X",
-        "Y",
-        "Z"
-    };
-
-    std::sort(qi.m_dimNames.begin(), qi.m_dimNames.end());
-    EXPECT_TRUE(CheckEqualCollections(qi.m_dimNames.begin(),
-        qi.m_dimNames.end(), std::begin(dims)));
-}
-
-TEST(LasReaderTest, test_vlr)
-{
-    PointTable table;
-
-    Options ops1;
-    ops1.add("filename", Support::datapath("las/lots_of_vlr.las"));
-    LasReader reader;
-    reader.setOptions(ops1);
-    reader.prepare(table);
-    reader.execute(table);
-
-    MetadataNode root = reader.getMetadata();
-    for (size_t i = 0; i < 390; ++i)
-    {
-        std::string name("vlr_");
-        name += std::to_string(i);
-        MetadataNode m = root.findChild(name);
-        EXPECT_TRUE(!m.value().empty()) << "No node " << i;
-    }
-}
-
-
-TEST(LasReaderTest, testInvalidFileSignature)
-{
-    PointTable table;
-
-    Options ops1;
-    ops1.add("filename", Support::datapath("las/1.2-with-color.las.wkt"));
-    LasReader reader;
-    reader.setOptions(ops1);
-
-    EXPECT_TRUE(reader.header().valid());
-}
-
-TEST(LasReaderTest, extraBytes)
-{
-    PointTable table;
-    PointLayoutPtr layout(table.layout());
-
-    Options readOps;
-    readOps.add("filename", Support::datapath("las/extrabytes.las"));
-    LasReader reader;
-    reader.setOptions(readOps);
-
-    reader.prepare(table);
-
-    DimTypeList dimTypes = layout->dimTypes();
-    EXPECT_EQ(dimTypes.size(), (size_t)25);
-
-    Dimension::Id color0 = layout->findProprietaryDim("Colors0");
-    EXPECT_EQ(layout->dimType(color0), Dimension::Type::Unsigned16);
-    Dimension::Id color1 = layout->findProprietaryDim("Colors1");
-    EXPECT_EQ(layout->dimType(color1), Dimension::Type::Unsigned16);
-    Dimension::Id color2 = layout->findProprietaryDim("Colors2");
-    EXPECT_EQ(layout->dimType(color2), Dimension::Type::Unsigned16);
-
-    Dimension::Id flag0 = layout->findProprietaryDim("Flags0");
-    EXPECT_EQ(layout->dimType(flag0), Dimension::Type::Signed8);
-    Dimension::Id flag1 = layout->findProprietaryDim("Flags1");
-    EXPECT_EQ(layout->dimType(flag1), Dimension::Type::Signed8);
-
-    Dimension::Id intense2 = layout->findProprietaryDim("Intensity");
-    EXPECT_EQ(layout->dimType(intense2), Dimension::Type::Unsigned32);
-
-    Dimension::Id time2 = layout->findProprietaryDim("Time");
-    EXPECT_EQ(layout->dimType(time2), Dimension::Type::Unsigned64);
-
-    PointViewSet viewSet = reader.execute(table);
-    EXPECT_EQ(viewSet.size(), (size_t)1);
-    PointViewPtr view = *viewSet.begin();
-
-    Dimension::Id red = layout->findDim("Red");
-    Dimension::Id green = layout->findDim("Green");
-    Dimension::Id blue = layout->findDim("Blue");
-
-    Dimension::Id returnNum = layout->findDim("ReturnNumber");
-    Dimension::Id numReturns = layout->findDim("NumberOfReturns");
-
-    Dimension::Id intensity = layout->findDim("Intensity");
-    Dimension::Id time = layout->findDim("GpsTime");
-
-    for (PointId idx = 0; idx < view->size(); ++idx)
-    {
-        EXPECT_EQ(view->getFieldAs<uint16_t>(red, idx),
-            view->getFieldAs<uint16_t>(color0, idx));
-        EXPECT_EQ(view->getFieldAs<uint16_t>(green, idx),
-            view->getFieldAs<uint16_t>(color1, idx));
-        EXPECT_EQ(view->getFieldAs<uint16_t>(blue, idx),
-            view->getFieldAs<uint16_t>(color2, idx));
-
-        EXPECT_EQ(view->getFieldAs<uint16_t>(flag0, idx),
-            view->getFieldAs<uint16_t>(returnNum, idx));
-        EXPECT_EQ(view->getFieldAs<uint16_t>(flag1, idx),
-            view->getFieldAs<uint16_t>(numReturns, idx));
-
-        EXPECT_EQ(view->getFieldAs<uint16_t>(intensity, idx),
-            view->getFieldAs<uint16_t>(intense2, idx));
-
-        // Time was written truncated rather than rounded.
-        EXPECT_NEAR(view->getFieldAs<double>(time, idx),
-            view->getFieldAs<double>(time2, idx), 1.0);
-    }
-}
-
-TEST(LasReaderTest, callback)
-{
-    PointTable table;
-    point_count_t count = 0;
-
-    Options ops;
-    ops.add("filename", Support::datapath("las/simple.las"));
-
-    Reader::PointReadFunc cb = [&count](PointView& view, PointId id)
-    {
-        count++;
-    };
-    LasReader reader;
-    reader.setOptions(ops);
-    reader.setReadCb(cb);
-
-    reader.prepare(table);
-    reader.execute(table);
-    EXPECT_EQ(count, (point_count_t)1065);
-}
-
-#ifdef PDAL_HAVE_LAZPERF
-// LAZ files are normally written in chunks of 50,000, so a file of size
-// 110,000 ensures we read some whole chunks and a partial.
-TEST(LasReaderTest, lazperf)
-{
-    Options ops1;
-    ops1.add("filename", Support::datapath("laz/autzen_trim.laz"));
-    ops1.add("compression", "lazperf");
-
-    LasReader lazReader;
-    lazReader.setOptions(ops1);
-
-    PointTable t1;
-    lazReader.prepare(t1);
-    PointViewSet pbSet = lazReader.execute(t1);
-    EXPECT_EQ(pbSet.size(), 1UL);
-    PointViewPtr view1 = *pbSet.begin();
-    EXPECT_EQ(view1->size(), (point_count_t)110000);
-
-    Options ops2;
-    ops2.add("filename", Support::datapath("las/autzen_trim.las"));
-
-    LasReader lasReader;
-    lasReader.setOptions(ops2);
-
-    PointTable t2;
-    lasReader.prepare(t2);
-    pbSet = lasReader.execute(t2);
-    EXPECT_EQ(pbSet.size(), 1UL);
-    PointViewPtr view2 = *pbSet.begin();
-    EXPECT_EQ(view2->size(), (point_count_t)110000);
-
-    DimTypeList dims = view1->dimTypes();
-    size_t pointSize = view1->pointSize();
-    EXPECT_EQ(view1->pointSize(), view2->pointSize());
-    // Validate some point data.
-    std::unique_ptr<char> buf1(new char[pointSize]);
-    std::unique_ptr<char> buf2(new char[pointSize]);
-    for (PointId i = 0; i < 110000; i += 100)
-    {
-       view1->getPackedPoint(dims, i, buf1.get());
-       view2->getPackedPoint(dims, i, buf2.get());
-       EXPECT_EQ(memcmp(buf1.get(), buf2.get(), pointSize), 0);
-    }
-}
-#endif
-
-void streamTest(const std::string src, const std::string compression)
-{
-    Options ops1;
-    ops1.add("filename", src);
-    ops1.add("compression", compression);
-
-    LasReader lasReader;
-    lasReader.setOptions(ops1);
-
-    PointTable t;
-    lasReader.prepare(t);
-    PointViewSet s = lasReader.execute(t);
-    PointViewPtr p = *s.begin();
-
-    class Checker : public Filter
-    {
-    public:
-        std::string getName() const
-            { return "checker"; }
-        Checker(PointViewPtr v) : m_cnt(0), m_view(v),
-            m_bulkBuf(v->pointSize()), m_buf(v->pointSize()),
-            m_dims(v->dimTypes())
-            {}
-    private:
-        point_count_t m_cnt;
-        PointViewPtr m_view;
-        std::vector<char> m_bulkBuf;
-        std::vector<char> m_buf;
-        DimTypeList m_dims;
-
-        bool processOne(PointRef& point)
-        {
-            PointRef bulkPoint = m_view->point(m_cnt);
-
-            bulkPoint.getPackedData(m_dims, m_bulkBuf.data());
-            point.getPackedData(m_dims, m_buf.data());
-            EXPECT_EQ(memcmp(m_buf.data(), m_bulkBuf.data(),
-                m_view->pointSize()), 0);
-            m_cnt++;
-            return true;
-        }
-
-        void done(PointTableRef)
-        {
-            EXPECT_EQ(m_cnt, 110000u);
-        }
-    };
-
-    Options ops2;
-    ops2.add("filename", Support::datapath("las/autzen_trim.las"));
-
-    LasReader lazReader;
-    lazReader.setOptions(ops2);
-
-    Checker c(p);
-    c.setInput(lazReader);
-
-    FixedPointTable fixed(100);
-    c.prepare(fixed);
-    c.execute(fixed);
-}
-
-TEST(LasReaderTest, stream)
-{
-    // Compression option is ignored for non-compressed file.
-    streamTest(Support::datapath("las/autzen_trim.las"), "laszip");
-#ifdef PDAL_HAVE_LASZIP
-    streamTest(Support::datapath("laz/autzen_trim.laz"), "laszip");
-#endif
-#ifdef PDAL_HAVE_LAZPERF
-    streamTest(Support::datapath("laz/autzen_trim.laz"), "lazperf");
-#endif
-}
-
-
-// The header of 1.2-with-color-clipped says that it has 1065 points,
-// but it really only has 1064.
-TEST(LasReaderTest, LasHeaderIncorrentPointcount)
-{
-    PointTable table;
-
-    Options readOps;
-    readOps.add("filename", Support::datapath("las/1.2-with-color-clipped.las"));
-    LasReader reader;
-    reader.setOptions(readOps);
-
-    reader.prepare(table);
-    PointViewSet viewSet = reader.execute(table);
-    PointViewPtr view = *viewSet.begin();
-
-    EXPECT_EQ(1064u, view->size());
-}
diff --git a/test/unit/io/las/LasWriterTest.cpp b/test/unit/io/las/LasWriterTest.cpp
deleted file mode 100644
index 1a797b5..0000000
--- a/test/unit/io/las/LasWriterTest.cpp
+++ /dev/null
@@ -1,761 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <stdlib.h>
-
-#include <pdal/util/FileUtils.hpp>
-#include <BufferReader.hpp>
-#include <LasHeader.hpp>
-#include <LasReader.hpp>
-#include <LasWriter.hpp>
-#include <BpfReader.hpp>
-
-#include <pdal/PointView.hpp>
-#include <pdal/StageFactory.hpp>
-#include <pdal/StageWrapper.hpp>
-
-#include "Support.hpp"
-
-namespace pdal
-{
-
-//ABELL - Should probably be moved to its own file.
-class LasTester
-{
-public:
-    LasHeader *header(LasWriter& w)
-        { return &w.m_lasHeader; }
-    SpatialReference srs(LasWriter& w)
-        { return w.m_srs; }
-};
-
-} // namespace pdal
-
-using namespace pdal;
-
-#ifdef PDAL_HAVE_LIBGEOTIFF
-TEST(LasWriterTest, srs)
-{
-    Options readerOps;
-    readerOps.add("filename", Support::datapath("las/utm15.las"));
-
-    LasReader reader;
-    reader.setOptions(readerOps);
-
-    Options writerOps;
-    writerOps.add("filename", Support::temppath("out.las"));
-    LasWriter writer;
-    writer.setInput(reader);
-    writer.setOptions(writerOps);
-
-    PointTable table;
-    writer.prepare(table);
-    writer.execute(table);
-
-    LasTester tester;
-    SpatialReference srs = tester.srs(writer);
-    EXPECT_EQ(srs, SpatialReference("EPSG:26915"));
-}
-#endif
-
-
-#ifdef PDAL_HAVE_LIBGEOTIFF
-TEST(LasWriterTest, srs2)
-{
-    Options readerOps;
-    readerOps.add("filename", Support::datapath("las/utm15.las"));
-
-    LasReader reader;
-    reader.setOptions(readerOps);
-
-    Options writerOps;
-    writerOps.add("filename", Support::temppath("out.las"));
-    writerOps.add("a_srs", "EPSG:32615");
-    LasWriter writer;
-    writer.setInput(reader);
-    writer.setOptions(writerOps);
-
-    PointTable table;
-    writer.prepare(table);
-    writer.execute(table);
-
-    LasTester tester;
-    SpatialReference srs = tester.srs(writer);
-    EXPECT_EQ(srs, SpatialReference("EPSG:32615"));
-}
-#endif
-
-
-TEST(LasWriterTest, auto_offset)
-{
-    using namespace Dimension;
-
-    const std::string FILENAME(Support::temppath("offset_test.las"));
-    PointTable table;
-
-    table.layout()->registerDim(Id::X);
-
-    BufferReader bufferReader;
-
-    PointViewPtr view(new PointView(table));
-    view->setField(Id::X, 0, 125000.00);
-    view->setField(Id::X, 1, 74529.00);
-    view->setField(Id::X, 2, 523523.02);
-    bufferReader.addView(view);
-
-    Options writerOps;
-    writerOps.add("filename", FILENAME);
-    writerOps.add("offset_x", "auto");
-    writerOps.add("scale_x", "auto");
-
-    LasWriter writer;
-    writer.setOptions(writerOps);
-    writer.setInput(bufferReader);
-
-    writer.prepare(table);
-    writer.execute(table);
-
-    Options readerOps;
-    readerOps.add("filename", FILENAME);
-
-    PointTable readTable;
-
-    LasReader reader;
-    reader.setOptions(readerOps);
-
-    reader.prepare(readTable);
-    EXPECT_DOUBLE_EQ(74529.00, reader.header().offsetX());
-    PointViewSet viewSet = reader.execute(readTable);
-    EXPECT_EQ(viewSet.size(), 1u);
-    view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 3u);
-    EXPECT_NEAR(125000.00, view->getFieldAs<double>(Id::X, 0), .0001);
-    EXPECT_NEAR(74529.00, view->getFieldAs<double>(Id::X, 1), .0001);
-    EXPECT_NEAR(523523.02, view->getFieldAs<double>(Id::X, 2), .0001);
-    FileUtils::deleteFile(FILENAME);
-}
-
-TEST(LasWriterTest, extra_dims)
-{
-    Options readerOps;
-
-    readerOps.add("filename", Support::datapath("las/1.2-with-color.las"));
-    LasReader reader;
-    reader.setOptions(readerOps);
-
-    Options writerOps;
-    writerOps.add("extra_dims", "Red=int32, Blue = int16, Green = int32_t");
-    writerOps.add("filename", Support::temppath("simple.las"));
-    LasWriter writer;
-    writer.setInput(reader);
-    writer.setOptions(writerOps);
-
-    PointTable table;
-    writer.prepare(table);
-    PointViewSet viewSet = writer.execute(table);
-
-    LasTester tester;
-    LasHeader *header = tester.header(writer);
-    EXPECT_EQ(header->pointLen(), header->basePointLen() + 10);
-    PointViewPtr pb = *viewSet.begin();
-
-    uint16_t colors[][3] = {
-        { 68, 77, 88 },
-        { 92, 100, 110 },
-        { 79, 87, 87 },
-        { 100, 102, 116 },
-        { 162, 114, 145 },
-        { 163, 137, 155 },
-        { 154, 131, 144 },
-        { 104, 111, 126 },
-        { 164, 136, 156 },
-        { 72, 87, 82 },
-        { 117, 117, 136 }
-    };
-
-    Options reader2Ops;
-    reader2Ops.add("filename", Support::temppath("simple.las"));
-    reader2Ops.add("extra_dims", "R1 =int32, B1= int16 ,G1=int32_t");
-
-    LasReader reader2;
-    reader2.setOptions(reader2Ops);
-
-    PointTable readTable;
-    reader2.prepare(readTable);
-    viewSet = reader2.execute(readTable);
-    pb = *viewSet.begin();
-    Dimension::Id r1 = readTable.layout()->findDim("R1");
-    EXPECT_TRUE(r1 != Dimension::Id::Unknown);
-    Dimension::Id b1 = readTable.layout()->findDim("B1");
-    EXPECT_TRUE(b1 != Dimension::Id::Unknown);
-    Dimension::Id g1 = readTable.layout()->findDim("G1");
-    EXPECT_TRUE(g1 != Dimension::Id::Unknown);
-    EXPECT_EQ(pb->size(), (size_t)1065);
-    size_t j = 0;
-    for (PointId i = 0; i < pb->size(); i += 100)
-    {
-        EXPECT_EQ(pb->getFieldAs<int16_t>(r1, i), colors[j][0]);
-        EXPECT_EQ(pb->getFieldAs<int16_t>(g1, i), colors[j][1]);
-        EXPECT_EQ(pb->getFieldAs<int16_t>(b1, i), colors[j][2]);
-        j++;
-    }
-}
-
-TEST(LasWriterTest, all_extra_dims)
-{
-    Options readerOps;
-
-    readerOps.add("filename", Support::datapath("bpf/simple-extra.bpf"));
-    BpfReader reader;
-    reader.setOptions(readerOps);
-
-    FileUtils::deleteFile(Support::temppath("simple.las"));
-
-    Options writerOps;
-    writerOps.add("extra_dims", "all");
-    writerOps.add("filename", Support::temppath("simple.las"));
-    writerOps.add("minor_version", 4);
-    LasWriter writer;
-    writer.setInput(reader);
-    writer.setOptions(writerOps);
-
-    PointTable table;
-    writer.prepare(table);
-    writer.execute(table);
-
-    Options ops;
-    ops.add("filename", Support::temppath("simple.las"));
-
-    LasReader r;
-    r.setOptions(ops);
-
-    PointTable t2;
-    r.prepare(t2);
-    Dimension::Id foo = t2.layout()->findDim("Foo");
-    Dimension::Id bar = t2.layout()->findDim("Bar");
-    Dimension::Id baz = t2.layout()->findDim("Baz");
-
-    PointViewSet s = r.execute(t2);
-    EXPECT_EQ(s.size(), 1u);
-    PointViewPtr v = *s.begin();
-
-    // We test for floats instead of doubles because when X, Y and Z
-    // get written, they are written scaled, which loses precision.  The
-    // foo, bar and baz values are written as full-precision doubles.
-    for (PointId i = 0; i < v->size(); ++i)
-    {
-        using namespace Dimension;
-
-        ASSERT_FLOAT_EQ(v->getFieldAs<float>(Id::X, i),
-            v->getFieldAs<float>(foo, i));
-        ASSERT_FLOAT_EQ(v->getFieldAs<float>(Id::Y, i),
-            v->getFieldAs<float>(bar, i));
-        ASSERT_FLOAT_EQ(v->getFieldAs<float>(Id::Z, i),
-            v->getFieldAs<float>(baz, i));
-    }
-}
-
-// Merge a couple of 1.4 LAS files with point formats 1 and 6.  Write the
-// output with version 3.  Verify that you get point format 3 (default),
-// LAS 1.3 output and other header data forwarded.
-TEST(LasWriterTest, forward)
-{
-    Options readerOps1;
-    
-    readerOps1.add("filename", Support::datapath("las/4_1.las"));
-    
-    Options readerOps2;
-
-    readerOps2.add("filename", Support::datapath("las/4_6.las"));
-
-    LasReader r1;
-    r1.addOptions(readerOps1);
-
-    LasReader r2;
-    r2.addOptions(readerOps2);
-
-    StageFactory sf;
-    Stage *m = sf.createStage("filters.merge");
-    m->setInput(r1);
-    m->setInput(r2);
-
-    std::string testfile = Support::temppath("tmp.las");
-    FileUtils::deleteFile(testfile);
-
-    Options writerOps;
-    writerOps.add("forward", "header");
-    writerOps.add("minor_version", 3);
-    writerOps.add("filename", testfile);
-
-    LasWriter w;
-    w.setInput(*m);
-    w.addOptions(writerOps);
-
-    PointTable t;
-
-    w.prepare(t);
-    w.execute(t);
-
-    Options readerOps;
-    readerOps.add("filename", testfile);
-
-    LasReader r;
-
-    r.setOptions(readerOps);
-
-    PointTable t2;
-
-    r.prepare(t2);
-    r.execute(t2);
-
-    MetadataNode n1 = r.getMetadata();
-    EXPECT_EQ(n1.findChild("major_version").value<uint8_t>(), 1);
-    EXPECT_EQ(n1.findChild("minor_version").value<uint8_t>(), 3);
-    EXPECT_EQ(n1.findChild("dataformat_id").value<uint8_t>(), 3);
-    EXPECT_EQ(n1.findChild("filesource_id").value<uint8_t>(), 0);
-    // Global encoding doesn't match because 4_1.las has a bad value, so we
-    // get the default.
-    EXPECT_EQ(n1.findChild("global_encoding").value<uint8_t>(), 0);
-    EXPECT_EQ(n1.findChild("project_id").value<Uuid>(), Uuid());
-    EXPECT_EQ(n1.findChild("system_id").value(), "");
-    EXPECT_EQ(n1.findChild("software_id").value(), "TerraScan");
-    EXPECT_EQ(n1.findChild("creation_doy").value<uint16_t>(), 142);
-    EXPECT_EQ(n1.findChild("creation_year").value<uint16_t>(), 2014);
-}
-
-TEST(LasWriterTest, forwardvlr)
-{
-    Options readerOps1;
-    
-    readerOps1.add("filename", Support::datapath("las/lots_of_vlr.las"));
-    LasReader r1;
-    r1.addOptions(readerOps1);
-    
-    std::string testfile = Support::temppath("tmp.las");
-    FileUtils::deleteFile(testfile);
-
-    Options writerOps;
-    writerOps.add("forward", "vlr");
-    writerOps.add("filename", testfile);
-
-    LasWriter w;
-    w.setInput(r1);
-    w.addOptions(writerOps);
-
-    PointTable t;
-
-    w.prepare(t);
-    w.execute(t);
-
-    Options readerOps;
-    readerOps.add("filename", testfile);
-
-    LasReader r;
-
-    r.setOptions(readerOps);
-
-    PointTable t2;
-
-    r.prepare(t2);
-    r.execute(t2);
-    
-    MetadataNode forward = t2.privateMetadata("lasforward");
-
-    auto pred = [](MetadataNode temp)
-        { return Utils::startsWith(temp.name(), "vlr_"); };
-    MetadataNodeList nodes = forward.findChildren(pred);
-    EXPECT_EQ(nodes.size(), 388UL);
-}
-
-// Test that data from three input views gets written to separate output files.
-TEST(LasWriterTest, flex)
-{
-    std::array<std::string, 3> outname =
-        {{ "test_1.las", "test_2.las", "test_3.las" }};
-
-    Options readerOps;
-    readerOps.add("filename", Support::datapath("las/simple.las"));
-
-    PointTable table;
-
-    LasReader reader;
-    reader.setOptions(readerOps);
-
-    reader.prepare(table);
-    PointViewSet views = reader.execute(table);
-    PointViewPtr v = *(views.begin());
-
-    PointViewPtr v1(new PointView(table));
-    PointViewPtr v2(new PointView(table));
-    PointViewPtr v3(new PointView(table));
-
-    std::vector<PointViewPtr> vs;
-    vs.push_back(v1);
-    vs.push_back(v2);
-    vs.push_back(v3);
-
-    for (PointId i = 0; i < v->size(); ++i)
-        vs[i % 3]->appendPoint(*v, i);
-
-    for (size_t i = 0; i < outname.size(); ++i)
-        FileUtils::deleteFile(Support::temppath(outname[i]));
-
-    BufferReader reader2;
-    reader2.addView(v1);
-    reader2.addView(v2);
-    reader2.addView(v3);
-
-    Options writerOps;
-    writerOps.add("filename", Support::temppath("test_#.las"));
-
-    LasWriter writer;
-    writer.setOptions(writerOps);
-    writer.setInput(reader2);
-
-    writer.prepare(table);
-    writer.execute(table);
-
-    for (size_t i = 0; i < outname.size(); ++i)
-    {
-        std::string filename = Support::temppath(outname[i]);
-        EXPECT_TRUE(FileUtils::fileExists(filename));
-
-        Options ops;
-        ops.add("filename", filename);
-
-        LasReader r;
-        r.setOptions(ops);
-        EXPECT_EQ(r.preview().m_pointCount, 355u);
-    }
-}
-
-// Test that data from three input views gets written to a single output file.
-TEST(LasWriterTest, flex2)
-{
-    Options readerOps;
-    readerOps.add("filename", Support::datapath("las/simple.las"));
-
-    PointTable table;
-
-    LasReader reader;
-    reader.setOptions(readerOps);
-
-    reader.prepare(table);
-    PointViewSet views = reader.execute(table);
-    PointViewPtr v = *(views.begin());
-
-    PointViewPtr v1(new PointView(table));
-    PointViewPtr v2(new PointView(table));
-    PointViewPtr v3(new PointView(table));
-
-    std::vector<PointViewPtr> vs;
-    vs.push_back(v1);
-    vs.push_back(v2);
-    vs.push_back(v3);
-
-    for (PointId i = 0; i < v->size(); ++i)
-        vs[i % 3]->appendPoint(*v, i);
-
-    std::string outfile(Support::temppath("test_flex.las"));
-    FileUtils::deleteFile(outfile);
-
-    BufferReader reader2;
-    reader2.addView(v1);
-    reader2.addView(v2);
-    reader2.addView(v3);
-
-    Options writerOps;
-    writerOps.add("filename", outfile);
-
-    LasWriter writer;
-    writer.setOptions(writerOps);
-    writer.setInput(reader2);
-
-    writer.prepare(table);
-    writer.execute(table);
-
-    EXPECT_TRUE(FileUtils::fileExists(outfile));
-
-    Options ops;
-    ops.add("filename", outfile);
-
-    LasReader r;
-    r.setOptions(ops);
-    EXPECT_EQ(r.preview().m_pointCount, 1065u);
-}
-
-#if defined(PDAL_HAVE_LAZPERF) && defined(PDAL_HAVE_LASZIP)
-// LAZ files are normally written in chunks of 50,000, so a file of size
-// 110,000 ensures we read some whole chunks and a partial.
-TEST(LasWriterTest, lazperf)
-{
-    Options readerOps;
-    readerOps.add("filename", Support::datapath("las/autzen_trim.las"));
-
-    LasReader lazReader;
-    lazReader.setOptions(readerOps);
-
-    std::string testfile(Support::temppath("temp.laz"));
-
-    FileUtils::deleteFile(testfile);
-
-    Options writerOps;
-    writerOps.add("filename", testfile);
-    writerOps.add("compression", "lazperf");
-
-    LasWriter lazWriter;
-    lazWriter.setOptions(writerOps);
-    lazWriter.setInput(lazReader);
-
-    PointTable t;
-    lazWriter.prepare(t);
-    lazWriter.execute(t);
-
-    // Now test the points were properly written.  Use laszip.
-    Options ops1;
-    ops1.add("filename", testfile);
-
-    LasReader r1;
-    r1.setOptions(ops1);
-
-    PointTable t1;
-    r1.prepare(t1);
-    PointViewSet set1 = r1.execute(t1); 
-    PointViewPtr view1 = *set1.begin();
-
-    Options ops2;
-    ops2.add("filename", Support::datapath("las/autzen_trim.las"));
-
-    LasReader r2;
-    r2.setOptions(ops2);
-
-    PointTable t2;
-    r2.prepare(t2);
-    PointViewSet set2 = r2.execute(t2); 
-    PointViewPtr view2 = *set2.begin();
-
-    EXPECT_EQ(view1->size(), view2->size());
-    EXPECT_EQ(view1->size(), (point_count_t)110000);
-
-    DimTypeList dims = view1->dimTypes();
-    size_t pointSize = view1->pointSize();
-    EXPECT_EQ(view1->pointSize(), view2->pointSize());
-
-   // Validate some point data.
-    std::unique_ptr<char> buf1(new char[pointSize]);
-    std::unique_ptr<char> buf2(new char[pointSize]);
-    for (PointId i = 0; i < view1->pointSize(); i += 100)
-    {
-       view1->getPackedPoint(dims, i, buf1.get());
-       view2->getPackedPoint(dims, i, buf2.get());
-       EXPECT_EQ(memcmp(buf1.get(), buf2.get(), pointSize), 0);
-    }
-}
-#endif
-
-void compareFiles(const std::string& name1, const std::string& name2,
-    size_t increment = 100)
-{
-    Options o1;
-    o1.add("filename", name1);
-
-    Options o2;
-    o2.add("filename", name2);
-
-    LasReader r1;
-    r1.setOptions(o1);
-
-    LasReader r2;
-    r2.setOptions(o2);
-
-    PointTable t1;
-    r1.prepare(t1);
-    PointViewSet s1 = r1.execute(t1);
-    EXPECT_EQ(s1.size(), 1u);
-    PointViewPtr v1 = *s1.begin();
-    DimTypeList d1 = v1->dimTypes();
-    size_t size1 = v1->pointSize();
-    std::vector<char> buf1(size1);
-
-    PointTable t2;
-    r2.prepare(t2);
-    PointViewSet s2 = r2.execute(t2);
-    EXPECT_EQ(s2.size(), 1u);
-    PointViewPtr v2 = *s2.begin();
-    DimTypeList d2 = v2->dimTypes();
-    size_t size2 = v2->pointSize();
-    std::vector<char> buf2(size2);
-
-    EXPECT_EQ(v1->size(), v2->size());
-    EXPECT_EQ(d1.size(), d2.size());
-    EXPECT_EQ(size1, size2);
-
-    for (PointId i = 0; i < std::min(size1, size2); i += increment)
-    {
-       v1->getPackedPoint(d1, i, buf1.data());
-       v2->getPackedPoint(d2, i, buf2.data());
-       EXPECT_EQ(memcmp(buf1.data(), buf2.data(), std::min(size1, size2)), 0);
-    }
-}
-
-TEST(LasWriterTest, stream)
-{
-    std::string infile(Support::datapath("las/autzen_trim.las"));
-    std::string outfile(Support::temppath("trimtest.las"));
-
-    FileUtils::deleteFile(outfile);
-
-    Options ops1;
-    ops1.add("filename", infile);
-
-    LasReader r;
-    r.setOptions(ops1);
-
-    Options ops2;
-    ops2.add("filename", outfile);
-    ops2.add("forward", "all");
-    LasWriter w;
-    w.setOptions(ops2);
-    w.setInput(r);
-
-    FixedPointTable t(100);
-    w.prepare(t);
-    w.execute(t);
-
-    compareFiles(infile, outfile);
-}
-
-TEST(LasWriterTest, fix1063_1064_1065)
-{
-    std::string outfile = Support::temppath("out.las");
-    std::string infile = Support::datapath("las/test1_4.las");
-
-    FileUtils::deleteFile(outfile);
-
-    std::string cmd = "pdal translate --writers.las.forward=all "
-        "--writers.las.a_srs=\"EPSG:4326\" " + infile + " " + outfile;
-    std::string output;
-    std::cerr << "*** Shell command = " <<
-        Support::binpath(cmd) << "!\n";
-    Utils::run_shell_command(Support::binpath(cmd), output);
-
-    Options o;
-    o.add("filename", outfile);
-
-    LasReader r;
-    r.setOptions(o);
-
-    PointTable t;
-    r.prepare(t);
-    PointViewSet s = r.execute(t);
-    EXPECT_EQ(s.size(), 1u);
-    PointViewPtr v = *s.begin();
-    EXPECT_EQ(v->size(), 1000u);
-
-    // https://github.com/PDAL/PDAL/issues/1063
-    for (PointId idx = 0; idx < v->size(); ++idx)
-        EXPECT_EQ(8, v->getFieldAs<int>(Dimension::Id::ClassFlags, idx));
-
-    // https://github.com/PDAL/PDAL/issues/1064
-    MetadataNode m = r.getMetadata();
-    m = m.findChild("global_encoding");
-    EXPECT_EQ(17, m.value<int>());
-
-    // https://github.com/PDAL/PDAL/issues/1065
-    SpatialReference ref = v->spatialReference();
-    std::string wkt = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]";
-    EXPECT_EQ(ref.getWKT(), wkt);
-}
-
-/**
-namespace
-{
-
-bool diffdump(const std::string& f1, const std::string& f2)
-{
-    auto dump = [](const std::string& temp, const std::string& in)
-    {
-        std::stringstream ss;
-        ss << "lasdump -o " << temp << " " << in;
-        system(ss.str().c_str());
-    };
-
-    std::string t1 = Support::temppath("lasdump1.tmp");
-    std::string t2 = Support::temppath("lasdump2.tmp");
-
-    dump(t1, f1);
-    dump(t2, f2);
-
-    std::string diffFile = Support::temppath("dumpdiff.tmp");
-    std::stringstream ss;
-    ss << "diff " << t1 << " " << t2 << " > " << diffFile;
-    system(ss.str().c_str());
-
-    return true;
-}
-
-} // Unnamed namespace
-
-TEST(LasWriterTest, simple)
-{
-    PointTable table;
-
-    std::string infile(Support::datapath("las/1.2-with-color.las"));
-    std::string outfile(Support::temppath("simple.las"));
-
-    // remove file from earlier run, if needed
-    FileUtils::deleteFile(outfile);
-
-    Options readerOpts;
-    readerOpts.add("filename", infile);
-
-    Options writerOpts;
-    writerOpts.add("creation_year", 2014);
-    writerOpts.add("filename", outfile);
-
-    LasReader reader;
-    reader.setOptions(readerOpts);
-
-    LasWriter writer;
-    writer.setOptions(writerOpts);
-    writer.setInput(reader);
-    writer.prepare(table);
-    writer.execute(table);
-
-    diffdump(infile, outfile);
-}
-**/
-
diff --git a/test/unit/io/oci/oracle_array.cpp b/test/unit/io/oci/oracle_array.cpp
deleted file mode 100644
index ca4a370..0000000
--- a/test/unit/io/oci/oracle_array.cpp
+++ /dev/null
@@ -1,324 +0,0 @@
-
-
-#include <stdio.h>
-#include <stdlib.h> 
- #include <string.h>
- #include <oci.h>
-#include <iostream>
-#include <sstream>
-
-bool CheckError(sword nStatus, OCIError* hError)
-{
-    text    szMsg[512];
-    sb4     nCode = 0;
-    
-    std::stringstream err;
-    switch (nStatus)
-    {
-        case OCI_SUCCESS:
-            return false;
-            break;
-        case OCI_NEED_DATA:
-            err << "OCI_NEED_DATA" << std::endl;;
-            break;
-        case OCI_NO_DATA:
-            err << "OCI_NO_DATA" << std::endl;;
-            break;
-        case OCI_INVALID_HANDLE:
-            err << "OCI_INVALID_HANDLE" << std::endl;;
-            break;
-        case OCI_STILL_EXECUTING:
-            err << "OCI_STILL_EXECUTING" << std::endl;;
-            break;
-        case OCI_CONTINUE:
-            err << "OCI_CONTINUE" << std::endl;;
-            break;
-        case OCI_ERROR:
-        case OCI_SUCCESS_WITH_INFO:
-
-            if (hError == NULL)
-            {
-                err << "OCI_ERROR with no error handler" << std::endl;;
-            }
-
-            OCIErrorGet((dvoid *) hError, (ub4) 1,
-                        (text *) NULL, &nCode, szMsg,
-                        (ub4) sizeof(szMsg), OCI_HTYPE_ERROR);
-
-            if (nCode == 1405)  // Null field
-            {
-                return false;
-            }
-
-            err << "Error: " << szMsg << std::endl;
-            break;
-
-        default:
-
-            if (hError == NULL)
-            {
-                err << "default OCI_ERROR with no error handler" << std::endl;;
-
-            }
-
-            OCIErrorGet((dvoid *) hError, (ub4) 1,
-                        (text *) NULL, &nCode, szMsg,
-                        (ub4) sizeof(szMsg), OCI_HTYPE_ERROR);
-            // err << "Error: " << std::string(szMsg, static_cast<int>(sizeof(szMsg))) << std::endl;
-            break;
-
-    }
-    
-    std::cout << "Error: " << err.str() << std::endl;
-    exit(1);
-
-    return true;
-}
-
-
- int main()
- {
-       // OCI handles
-OCIEnv *envhp;
-OCIError *errhp;
-OCIServer *srvhp;
-OCISvcCtx *svchp;
-OCISession *authp;
-OCIStmt *stmtp;
-OCIDefine *defnpp;
-
-// Connection information
-text* user = (text*)"grid";
-text* pwd = (text*)"grid";
-text* sid = (text*)"localhost/orcl";
-
-#define FETCH_COUNT 40
-int prefetch_rows(FETCH_COUNT);
-int fetched;
-
-// char *query = "SELECT owner, table_name FROM all_tables";
-// char *query = "SELECT obj_id, blk_id, points FROM autzen_blocks";
-char *query = "SELECT points FROM autzen_blocks where obj_id=1";
-
-// Fetched data
-// char owner[FETCH_COUNT][31];
-// char table_name[FETCH_COUNT][31];
-oraub8 block_id[FETCH_COUNT];
-oraub8 object_id[FETCH_COUNT];
-OCILobLocator * lob_array[FETCH_COUNT];
-
-// Fetched data indicators, lengths and codes
-// sb2 owner_ind[100], table_name_ind[100];
-// ub2 owner_len[100], table_name_len[100];
-// ub2 owner_code[100], table_name_code[100];
-
-sb2 block_id_ind[FETCH_COUNT], object_id_ind[FETCH_COUNT];
-ub2 block_id_len[FETCH_COUNT], object_id_len[FETCH_COUNT];
-ub2 block_id_code[FETCH_COUNT], object_id_code[FETCH_COUNT];
-
-int rc(0);
-
-
-   // lob_array[i] = OCIDescriptorAlloc(..., OCI_DTYPE_LOB, ...);
-
-// Allocate environment
-rc = OCIEnvCreate(&envhp, OCI_DEFAULT, NULL, NULL, NULL, NULL, 0, NULL);
-
-// Allocate error handle
-rc = OCIHandleAlloc(envhp, (void**)&errhp, OCI_HTYPE_ERROR, 0, NULL);
-
-// Allocate server and service context handles
-rc = OCIHandleAlloc(envhp, (void**)&srvhp, OCI_HTYPE_SERVER, 0, NULL);
-rc = OCIHandleAlloc(envhp, (void**)&svchp, OCI_HTYPE_SVCCTX, 0, NULL);
-
-// Attach to the server
-rc = OCIServerAttach(srvhp, errhp, sid, strlen((char*)sid), 0);
-
-// Set server in the service context 
-rc = OCIAttrSet(svchp, OCI_HTYPE_SVCCTX, (dvoid*)srvhp, 0, OCI_ATTR_SERVER, errhp);
-
-// Allocate session handle
-rc = OCIHandleAlloc(envhp, (void**)&authp, OCI_HTYPE_SESSION, 0, NULL);
-
-// Set user name and password
-rc = OCIAttrSet(authp, OCI_HTYPE_SESSION, (void*)user, strlen((char*)user), 
-                                      OCI_ATTR_USERNAME, errhp);
-rc = OCIAttrSet(authp, OCI_HTYPE_SESSION, (void*)pwd, strlen((char *)pwd), 
-                                      OCI_ATTR_PASSWORD, errhp);
-
-// Connect
-rc = OCISessionBegin(svchp, errhp, authp, OCI_CRED_RDBMS, OCI_DEFAULT);
-
-// Set session in the service context
-rc = OCIAttrSet(svchp, OCI_HTYPE_SVCCTX, authp, 0, OCI_ATTR_SESSION, errhp);
-
-// Allocate statement handle
-rc = OCIHandleAlloc(envhp, (void**)&stmtp, OCI_HTYPE_STMT, 0, NULL);
-
-// Set prefetch count
-rc = OCIAttrSet(stmtp, OCI_HTYPE_STMT, (void*)&prefetch_rows, sizeof(int), 
-                                      OCI_ATTR_PREFETCH_ROWS, errhp);
-
-// Prepare the query
-rc = OCIStmtPrepare(stmtp, errhp, (text*)query, strlen(query), OCI_NTV_SYNTAX, OCI_DEFAULT);
-
-// Define the select list items 
-// rc = OCIDefineByPos(stmtp, &defnpp, errhp, 1, (void*)owner, 31, SQLT_STR, (void*)owner_ind,
-//                     owner_len, owner_code, OCI_DEFAULT);
-// rc = OCIDefineByPos(stmtp, &defnpp, errhp, 2, (void*)table_name, 31, SQLT_STR, (void*)table_name_ind,
-//                     table_name_len, table_name_code, OCI_DEFAULT);
-
-for (int i = 0; i < prefetch_rows; i++)
-{
-
-    rc = OCIDescriptorAlloc(
-        envhp,
-          (dvoid **) &lob_array[i],
-        OCI_DTYPE_LOB, (size_t) 0, (void**) 0);
-    CheckError(rc, errhp);    
-
-    OCILobEnableBuffering(
-                           svchp,
-                           errhp,
-                           lob_array[i]);
-}
-
-// rc = OCIDefineByPos(stmtp, &defnpp, errhp, 1, (void*)block_id, (sb4) sizeof(long int), SQLT_INT, (void*)block_id_ind,
-//                     block_id_len, block_id_code, OCI_DEFAULT);
-// CheckError(rc, errhp);
-// rc = OCIDefineByPos(stmtp, &defnpp, errhp, 2, (void*)object_id, (sb4) sizeof(long int), SQLT_INT, (void*)object_id_ind,
-//                     object_id_len, object_id_code, OCI_DEFAULT);
-// CheckError(rc, errhp);
-rc = OCIDefineByPos(stmtp, &defnpp, errhp, 1, (dvoid*)lob_array, (sb4) 0, SQLT_BLOB, 0,
-					0, 0, OCI_DEFAULT);
-CheckError(rc, errhp);
-
-// Execute the statement and perform the initial fetch of 100 rows into the defined array
-rc = OCIStmtExecute(svchp, stmtp, errhp, prefetch_rows, 0, NULL, NULL, OCI_DEFAULT);
-std::cout << "executing query" << std::endl;
-CheckError(rc, errhp);
-int cnt(0);
-
-oraub8 offset[FETCH_COUNT];
-char  *bufp[FETCH_COUNT];
-oraub8 bufl[FETCH_COUNT] = {0};
-oraub8 buf_amtp[FETCH_COUNT] = {0};
-ub4 array_iter = prefetch_rows;
-
-
-while(rc >= 0)
-{
-	OCIAttrGet(stmtp, OCI_HTYPE_STMT, (void*)&fetched, NULL, OCI_ATTR_ROWS_FETCHED, errhp);
-    
-    std::cout << "Fetched : " << fetched << std::endl;
-	// OCI_NO_DATA is returned by OCIStmtExecute and OCIStmtFetch2 when the number of fetched rows 
-	// is less than the number of rows allocated in the array
-    cnt += fetched;
-	if(fetched == 0)
-		break;
-
-    // // Output fetched data 
-    // for(int i = 0; i < fetched; i++)
-    //         // printf("%s.%s\n", owner[i], table_name[i]);
-    //         printf("block_id: %ld. object_id: %ld\n", block_id[i], object_id[i]);
-
-    for (int i=0; i<prefetch_rows; i++)
-    {
-      buf_amtp[i] = 0;
-    }    
-    
-    int candidate = 2;
-    for (int i=0; i<fetched; i++)
-    {
-        oraub8 nLength      = (oraub8) 0;
-        rc = OCILobGetLength2(
-                            svchp,
-                           errhp,
-                           lob_array[i],
-                           &nLength);
-           std::cout << "i: " << i << " bufl[i]: " << bufl[i] << " nLength: " << nLength << std::endl;
-           if (bufl[i] != 0 || bufl[i] < nLength)
-           {
-               if (bufl[i])
-                   free(bufp[i]);
-               bufp[i] = (char *)malloc(nLength);
-               bufl[i] = nLength;
-               
-           }
-      offset[i] = 1;
-      buf_amtp[i] = nLength;
-
-    } 
-    
-
-    std::cout << "before bufl["<<candidate<<"]: " << bufl[candidate] << " buf_amtp["<<candidate<<"]: " << buf_amtp[candidate] << " offset["<<candidate<<"]: "<< offset[candidate] << std::endl;             
-
-    rc = OCILobArrayRead(svchp, errhp,
-                    &array_iter, /* array size */
-                    lob_array,   /* array of locators */
-                    buf_amtp,        /* array of byte amounts */
-                    NULL,   /* array of char amounts */
-                    offset,      /* array of offsets */
-           (void **)bufp,        /* array of read buffers */
-                    bufl,        /* array of buffer lengths */
-                    OCI_ONE_PIECE,  /* piece information */
-                    NULL,           /* callback context */
-                    NULL,           /* callback function */
-                    0,              /* character set ID - default */
-                    SQLCS_IMPLICIT);/* character set form */
-    // std::cout << "called array read!" << std::endl;
-    // CheckError(rc, errhp);      
-    // std::cout << "did array read!" << std::endl;       
-    // std::cout << afterblob length: " << nLength << std::endl;
-    std::cout << "after bufl["<<candidate<<"]: " << bufl[candidate] << " buf_amtp["<<candidate<<"]: " << buf_amtp[candidate] << " offset["<<candidate<<"]: "<< offset[candidate] << std::endl;             
-    
-
-    while (rc == OCI_NEED_DATA)
-    {
-        rc = OCILobArrayRead(svchp, errhp,
-                        &array_iter, /* array size */
-                        lob_array,   /* array of locators */
-                        buf_amtp,        /* array of byte amounts */
-                        NULL,   /* array of char amounts */
-                        offset,      /* array of offsets */
-               (void **)bufp,        /* array of read buffers */
-                        bufl,        /* array of buffer lengths */
-                        OCI_NEXT_PIECE,  /* piece information */
-                        NULL,           /* callback context */
-                        NULL,           /* callback function */
-                        0,              /* character set ID - default */
-                        SQLCS_IMPLICIT);/* character set form */
-        
-        // for (int i = 0; i < prefetch_rows; ++i)
-        // {
-        //     offset[i] = offset[i] + bufl[i];
-        // }    
-        // std::cout << "read array part!" << std::endl;
-        // CheckError(rc, errhp);
-        std::cout << "part bufl[34]" << bufl[34] << " buf_amtp[34]: " << buf_amtp[34] << "offset[34]: " << offset[34] << std::endl;             
-        
-        // std::cout << "no error for array part!" << std::endl;       
-    }
-	if(rc == OCI_NO_DATA)
-		break;
-
-    // for (int i=0; i<prefetch_rows; i++)
-    // {
-    //   offset[i] = 1;
-    // }
-
-	// Fetch another set of rows
-	rc = OCIStmtFetch2(stmtp, errhp, prefetch_rows, OCI_DEFAULT, 0, OCI_DEFAULT);
-}
-
-printf("selected %d blocks\n", cnt);
-
-rc = OCIHandleFree(stmtp, OCI_HTYPE_STMT);
-
-// Disconnect
-rc = OCISessionEnd(svchp, errhp, authp, OCI_DEFAULT);
-rc = OCIServerDetach(srvhp, errhp, OCI_DEFAULT);
-
-rc = OCIHandleFree(envhp, OCI_HTYPE_ENV);
-return 0;
- }
diff --git a/test/unit/io/optech/OptechReaderTest.cpp b/test/unit/io/optech/OptechReaderTest.cpp
deleted file mode 100644
index 1857a38..0000000
--- a/test/unit/io/optech/OptechReaderTest.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include "OptechReader.hpp"
-
-#include <pdal/StageFactory.hpp>
-#include "Support.hpp"
-
-
-namespace pdal
-{
-
-
-namespace
-{
-
-
-std::string getTestfilePath()
-{
-    return Support::datapath("optech/sample.csd");
-}
-
-
-class OptechReaderTest : public ::testing::Test
-{
-public:
-    OptechReaderTest()
-        : ::testing::Test()
-        , m_reader()
-    {
-        Options options;
-        options.add("filename", getTestfilePath());
-        m_reader.setOptions(options);
-    }
-
-    OptechReader m_reader;
-};
-}
-
-
-TEST(OptechReader, Constructor)
-{
-    OptechReader reader1;
-
-    StageFactory f;
-    Stage* reader2 = f.createStage("readers.optech");
-    EXPECT_TRUE(reader2);
-}
-
-
-TEST_F(OptechReaderTest, Header)
-{
-    PointTable table;
-    m_reader.prepare(table);
-    CsdHeader header = m_reader.getHeader();
-
-    EXPECT_STREQ("CSD", header.signature);
-    EXPECT_STREQ("Optech Incorporated", header.vendorId);
-    EXPECT_STREQ("DASHMap", header.softwareVersion);
-    EXPECT_FLOAT_EQ(5.2010002f, header.formatVersion);
-    EXPECT_EQ(2048, header.headerSize);
-    EXPECT_EQ(1660u, header.gpsWeek);
-    EXPECT_DOUBLE_EQ(575644.74484563898, header.minTime);
-    EXPECT_DOUBLE_EQ(575644.75883187703, header.maxTime);
-    EXPECT_EQ(1000u, header.numRecords);
-    EXPECT_EQ(1u, header.numStrips);
-    EXPECT_EQ(0u, header.stripPointers[0]);
-    EXPECT_DOUBLE_EQ(0.028000000000000001, header.misalignmentAngles[0]);
-    EXPECT_DOUBLE_EQ(0.014, header.misalignmentAngles[1]);
-    EXPECT_DOUBLE_EQ(0.002, header.misalignmentAngles[2]);
-    EXPECT_DOUBLE_EQ(0.002250602070446688, header.imuOffsets[0]);
-    EXPECT_DOUBLE_EQ(-0.0021128955924643355, header.imuOffsets[1]);
-    EXPECT_DOUBLE_EQ(0.0054852207731677788, header.imuOffsets[2]);
-    EXPECT_DOUBLE_EQ(13, header.temperature);
-    EXPECT_DOUBLE_EQ(1026.75, header.pressure);
-}
-
-
-TEST_F(OptechReaderTest, ReadingPoints)
-{
-    PointTable table;
-    m_reader.prepare(table);
-    PointViewSet viewSet = m_reader.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 1000u);
-
-    EXPECT_DOUBLE_EQ(-82.554028877408555,
-                     view->getFieldAs<double>(Dimension::Id::X, 0));
-    EXPECT_DOUBLE_EQ(36.534611447321907,
-                     view->getFieldAs<double>(Dimension::Id::Y, 0));
-    EXPECT_DOUBLE_EQ(344.80889224602356,
-                     view->getFieldAs<double>(Dimension::Id::Z, 0));
-    EXPECT_DOUBLE_EQ(5.756447448456390e5,
-                     view->getFieldAs<double>(Dimension::Id::GpsTime, 0));
-    EXPECT_EQ(1, view->getFieldAs<uint8_t>(Dimension::Id::ReturnNumber, 0));
-    EXPECT_EQ(1, view->getFieldAs<uint8_t>(Dimension::Id::NumberOfReturns, 0));
-    EXPECT_FLOAT_EQ(8.27356689453125e2,
-        view->getFieldAs<float>(Dimension::Id::EchoRange, 0));
-    EXPECT_EQ(384, view->getFieldAs<uint16_t>(Dimension::Id::Intensity, 0));
-    EXPECT_FLOAT_EQ(-14.55516,
-        view->getFieldAs<float>(Dimension::Id::ScanAngleRank, 0));
-}
-
-
-TEST_F(OptechReaderTest, Spatialreference)
-{
-    SpatialReference expected;
-    expected.setFromUserInput("EPSG:4326");
-    SpatialReference actual = m_reader.getSpatialReference();
-    EXPECT_EQ(expected, actual);
-}
-}
diff --git a/test/unit/io/ply/PlyReaderTest.cpp b/test/unit/io/ply/PlyReaderTest.cpp
deleted file mode 100644
index 0b00c09..0000000
--- a/test/unit/io/ply/PlyReaderTest.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <PlyReader.hpp>
-#include "Support.hpp"
-
-
-namespace pdal
-{
-
-
-void checkPoint(const PointViewPtr& view, point_count_t idx,
-        double x, double y, double z)
-{
-    EXPECT_DOUBLE_EQ(x, view->getFieldAs<double>(Dimension::Id::X, idx));
-    EXPECT_DOUBLE_EQ(y, view->getFieldAs<double>(Dimension::Id::Y, idx));
-    EXPECT_DOUBLE_EQ(z, view->getFieldAs<double>(Dimension::Id::Z, idx));
-}
-
-
-TEST(PlyReader, Constructor)
-{
-    PlyReader reader1;
-
-    StageFactory f;
-    Stage* reader2(f.createStage("readers.ply"));
-    EXPECT_TRUE(reader2);
-}
-
-
-TEST(PlyReader, ReadText)
-{
-    PlyReader reader;
-    Options options;
-    options.add("filename", Support::datapath("ply/simple_text.ply"));
-    reader.setOptions(options);
-
-    PointTable table;
-    reader.prepare(table);
-    PointViewSet viewSet = reader.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 3u);
-
-    checkPoint(view, 0, -1, 0, 0);
-    checkPoint(view, 1, 0, 1, 0);
-    checkPoint(view, 2, 1, 0, 0);
-}
-
-
-TEST(PlyReader, ReadTextExtraDims)
-{
-    PlyReader reader;
-    Options options;
-    options.add("filename", Support::datapath("ply/text_extradim.ply"));
-    reader.setOptions(options);
-
-    PointTable table;
-    reader.prepare(table);
-    PointViewSet viewSet = reader.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 1u);
-
-    PointLayout *layout = view->layout();
-    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::X, 0), -2.64944f);
-    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::Y, 0), -13.0955f);
-    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::Z, 0), 0.00640115f);
-    EXPECT_EQ(view->getFieldAs<float>(layout->findDim("nx"), 0), -0.0237552f);
-    EXPECT_EQ(view->getFieldAs<float>(layout->findDim("ny"), 0), -0.00902114f);
-    EXPECT_EQ(view->getFieldAs<double>(layout->findDim("nz"), 0), .999665f);
-    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Red, 0), 63);
-    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Green, 0), 200);
-    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Blue, 0), 64);
-    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Alpha, 0), 255);
-    EXPECT_EQ(view->getFieldAs<double>(layout->findDim("omg"), 0), 1234);
-}
-
-
-TEST(PlyReader, ReadBinary)
-{
-    PlyReader reader;
-    Options options;
-    options.add("filename", Support::datapath("ply/simple_binary.ply"));
-    reader.setOptions(options);
-
-    PointTable table;
-    reader.prepare(table);
-    PointViewSet viewSet = reader.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 3u);
-
-    checkPoint(view, 0, -1, 0, 0);
-    checkPoint(view, 1, 0, 1, 0);
-    checkPoint(view, 2, 1, 0, 0);
-}
-
-
-TEST(PlyReader, NoVertex)
-{
-    PlyReader reader;
-    Options options;
-    options.add("filename", Support::datapath("ply/no_vertex.ply"));
-    reader.setOptions(options);
-
-    PointTable table;
-    EXPECT_THROW(reader.prepare(table), pdal_error);
-}
-
-}
diff --git a/test/unit/io/ply/PlyWriterTest.cpp b/test/unit/io/ply/PlyWriterTest.cpp
deleted file mode 100644
index aa47563..0000000
--- a/test/unit/io/ply/PlyWriterTest.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <FauxReader.hpp>
-#include <PlyWriter.hpp>
-#include <pdal/StageFactory.hpp>
-#include "Support.hpp"
-
-
-namespace pdal
-{
-
-
-TEST(PlyWriter, Constructor)
-{
-    PlyWriter writer1;
-
-    StageFactory f;
-    Stage* writer2(f.createStage("writers.ply"));
-    EXPECT_TRUE(writer2);
-}
-
-
-TEST(PlyWriter, Write)
-{
-    Options readerOptions;
-    readerOptions.add("count", 750);
-    readerOptions.add("mode", "random");
-    FauxReader reader;
-    reader.setOptions(readerOptions);
-
-    Options writerOptions;
-    writerOptions.add("filename", Support::temppath("out.ply"));
-    PlyWriter writer;
-    writer.setOptions(writerOptions);
-    writer.setInput(reader);
-
-    PointTable table;
-    writer.prepare(table);
-    writer.execute(table);
-}
-
-
-}
diff --git a/test/unit/io/pts/PtsReaderTest.cpp b/test/unit/io/pts/PtsReaderTest.cpp
deleted file mode 100644
index deb2d4f..0000000
--- a/test/unit/io/pts/PtsReaderTest.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2016, Howard Butler <howard at hobu.co>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-#include <pdal/StageFactory.hpp>
-
-#include <PtsReader.hpp>
-#include "Support.hpp"
-
-
-namespace pdal
-{
-
-TEST(PtsReader, Constructor)
-{
-    PtsReader reader1;
-
-    StageFactory f;
-    Stage* reader2(f.createStage("readers.pts"));
-    EXPECT_TRUE(reader2);
-}
-
-
-
-TEST(PtsReader, ReadPtsExtraDims)
-{
-    PtsReader reader;
-    Options options;
-    options.add("filename", Support::datapath("pts/test.pts"));
-    reader.setOptions(options);
-
-    PointTable table;
-    reader.prepare(table);
-    PointViewSet viewSet = reader.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 19u);
-
-    PointLayout *layout = view->layout();
-    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::X, 0), 3.9809721f);
-    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::Y, 0), -2.006119f);
-    EXPECT_FLOAT_EQ(view->getFieldAs<float>(Dimension::Id::Z, 0), -0.010086f);
-    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Red, 0), 97);
-    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Green, 0), 59);
-    EXPECT_EQ(view->getFieldAs<int>(Dimension::Id::Blue, 0), 38);
-}
-
-
-
-}
diff --git a/test/unit/io/qfit/QFITReaderTest.cpp b/test/unit/io/qfit/QFITReaderTest.cpp
deleted file mode 100644
index ad26251..0000000
--- a/test/unit/io/qfit/QFITReaderTest.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2011, Michael P. Gerlek (mpg at flaxen.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <pdal/Options.hpp>
-#include <pdal/PointView.hpp>
-#include <QfitReader.hpp>
-#include "Support.hpp"
-
-#include <iostream>
-
-
-using namespace pdal;
-
-void Check_Point(const PointView& data,
-                 PointId index,
-                 double xref, double yref, double zref,
-                 int32_t tref)
-{
-    double x = data.getFieldAs<double>(Dimension::Id::X, index);
-    double y = data.getFieldAs<double>(Dimension::Id::Y, index);
-    double z = data.getFieldAs<double>(Dimension::Id::Z, index);
-    int32_t t = data.getFieldAs<int32_t>(Dimension::Id::OffsetTime, index);
-
-    EXPECT_FLOAT_EQ(x, xref);
-    EXPECT_FLOAT_EQ(y, yref);
-    EXPECT_FLOAT_EQ(z, zref);
-    EXPECT_EQ(t, tref);
-}
-
-TEST(QFITReaderTest, test_10_word)
-{
-    Options options;
-
-    options.add("filename", Support::datapath("qfit/10-word.qi"));
-    options.add("flip_coordinates", false);
-    options.add("scale_z", 0.001f);
-    options.add("count", 3);
-
-    std::shared_ptr<QfitReader> reader(new QfitReader);
-    reader->setOptions(options);
-    EXPECT_EQ(reader->getName(), "readers.qfit");
-
-    PointTable table;
-    reader->prepare(table);
-    PointViewSet viewSet = reader->execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 3u);
-
-    Check_Point(*view, 0, 221.826822, 59.205160, 32.0900, 0);
-    Check_Point(*view, 1, 221.826740, 59.205161, 32.0190, 0);
-    Check_Point(*view, 2, 221.826658, 59.205164, 32.0000, 0);
-}
-
-TEST(QFITReaderTest, test_14_word)
-{
-    Options options;
-
-    options.add("filename", Support::datapath("qfit/14-word.qi"));
-    options.add("flip_coordinates", false);
-    options.add("scale_z", 0.001f);
-    options.add("count", 3);
-
-    PointTable table;
-    std::shared_ptr<QfitReader> reader(new QfitReader);
-    reader->setOptions(options);
-    reader->prepare(table);
-    PointViewSet viewSet = reader->execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 3u);
-
-    Check_Point(*view, 0, 244.306337, 35.623317, 1056.830000000, 903);
-    Check_Point(*view, 1, 244.306260, 35.623280, 1056.409000000, 903);
-    Check_Point(*view, 2, 244.306204, 35.623257, 1056.483000000, 903);
-}
diff --git a/test/unit/io/sbet/SbetReaderTest.cpp b/test/unit/io/sbet/SbetReaderTest.cpp
deleted file mode 100644
index 181abd9..0000000
--- a/test/unit/io/sbet/SbetReaderTest.cpp
+++ /dev/null
@@ -1,150 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Peter J. Gadomski (pete.gadomski at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <pdal/Options.hpp>
-#include <pdal/PipelineManager.hpp>
-#include <pdal/PointView.hpp>
-
-#include <SbetReader.hpp>
-
-#include "Support.hpp"
-
-using namespace pdal;
-
-void checkPoint(const PointView& data, PointId index, double time,
-    double latitude, double longitude, double altitude, double xvelocity,
-    double yvelocity, double zvelocity, double roll, double pitch,
-    double heading, double wander, double xaccel, double yaccel,
-    double zaccel, double xangrate, double yangrate, double zangrate)
-{
-    auto checkDimension = [&data,index](Dimension::Id dim,
-        double expected)
-    {
-        double actual = data.getFieldAs<double>(dim, index);
-        EXPECT_FLOAT_EQ(expected, actual);
-    };
-
-    checkDimension(Dimension::Id::GpsTime, time);
-    checkDimension(Dimension::Id::Y, latitude);
-    checkDimension(Dimension::Id::X, longitude);
-    checkDimension(Dimension::Id::Z, altitude);
-    checkDimension(Dimension::Id::XVelocity, xvelocity);
-    checkDimension(Dimension::Id::YVelocity, yvelocity);
-    checkDimension(Dimension::Id::ZVelocity, zvelocity);
-    checkDimension(Dimension::Id::Roll, roll);
-    checkDimension(Dimension::Id::Pitch, pitch);
-    checkDimension(Dimension::Id::Azimuth, heading);
-    checkDimension(Dimension::Id::WanderAngle, wander);
-    checkDimension(Dimension::Id::XBodyAccel, xaccel);
-    checkDimension(Dimension::Id::YBodyAccel, yaccel);
-    checkDimension(Dimension::Id::ZBodyAccel, zaccel);
-    checkDimension(Dimension::Id::XBodyAngRate, xangrate);
-    checkDimension(Dimension::Id::YBodyAngRate, yangrate);
-    checkDimension(Dimension::Id::ZBodyAngRate, zangrate);
-}
-
-TEST(SbetReaderTest, testRead)
-{
-    Options options;
-    options.add("filename", Support::datapath("sbet/2-points.sbet"));
-
-    SbetReader reader;
-    reader.setOptions(options);
-
-    PointTable table;
-
-    reader.prepare(table);
-    PointViewSet viewSet = reader.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-
-    EXPECT_EQ(view->size(), 2u);
-
-    checkPoint(*view.get(), 0,
-               1.516310028360710e+05, 5.680211852972264e-01,
-               -2.041654392303940e+00, 1.077152953296560e+02,
-               -2.332420866600025e+00, -3.335067504871401e-01,
-               -3.093961631767838e-02, -2.813407149321339e-02,
-               -2.429905393889139e-02, 3.046773230278662e+00,
-               -2.198414736922658e-02, 7.859639737752390e-01,
-               7.849084719295495e-01, -2.978807916450262e-01,
-               6.226807982589819e-05, 9.312162756440178e-03,
-               7.217812320996525e-02);
-    checkPoint(*view.get(), 1,
-               1.516310078318641e+05, 5.680211834722869e-01,
-               -2.041654392034053e+00, 1.077151424357507e+02,
-               -2.336228229691271e+00, -3.324663118952635e-01,
-               -3.022948961008987e-02, -2.813856631423094e-02,
-               -2.425215669392169e-02, 3.047131105236811e+00,
-               -2.198416007932108e-02, 8.397590491636475e-01,
-               3.252165276637165e-01, -1.558883225990844e-01,
-               8.379685112283802e-04, 7.372886784718076e-03,
-               7.179027672314571e-02);
-}
-
-TEST(SbetReaderTest, testBadFile)
-{
-    Options options;
-    options.add("filename", Support::datapath("sbet/badfile.sbet"));
-
-    SbetReader reader;
-    reader.setOptions(options);
-    PointTable table;
-    reader.prepare(table);
-    EXPECT_THROW(reader.execute(table), pdal_error);
-}
-
-TEST(SbetReaderTest, testPipelineXML)
-{
-    PipelineManager manager;
-
-    manager.readPipeline(Support::configuredpath("sbet/pipeline.xml"));
-
-    point_count_t numPoints = manager.execute();
-    EXPECT_EQ(numPoints, 2u);
-    FileUtils::deleteFile(Support::datapath("sbet/outfile.txt"));
-}
-
-TEST(SbetReaderTest, testPipelineJSON)
-{
-    PipelineManager manager;
-
-    manager.readPipeline(Support::configuredpath("sbet/pipeline.json"));
-
-    point_count_t numPoints = manager.execute();
-    EXPECT_EQ(numPoints, 2u);
-    FileUtils::deleteFile(Support::datapath("sbet/outfile.txt"));
-}
diff --git a/test/unit/io/sbet/SbetWriterTest.cpp b/test/unit/io/sbet/SbetWriterTest.cpp
deleted file mode 100644
index 7f5276c..0000000
--- a/test/unit/io/sbet/SbetWriterTest.cpp
+++ /dev/null
@@ -1,95 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2014, Peter J. Gadomski (pete.gadomski at gmail.com)
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include <SbetReader.hpp>
-#include <SbetWriter.hpp>
-
-#include "Support.hpp"
-
-using namespace pdal;
-
-Options makeReaderOptions()
-{
-    Options options;
-    options.add("filename", Support::datapath("sbet/2-points.sbet"));
-
-    return options;
-}
-
-
-Options makeWriterOptions()
-{
-    Options options;
-    options.add("filename", Support::temppath("SbetWriterTest.sbet"));
-    return options;
-}
-
-TEST(SbetWriterTest, testConstructor)
-{
-    SbetReader reader;
-    reader.setOptions(makeReaderOptions());
-    SbetWriter writer;
-    writer.setOptions(makeWriterOptions());
-    writer.setInput(reader);
-
-    EXPECT_EQ(writer.getName(), "writers.sbet");
-}
-
-TEST(SbetWriterTest, testWrite)
-{
-    FileUtils::deleteFile(Support::temppath("SbetWriterTest.sbet"));
-
-    // Scope forces the writer's buffer to get written to the file.  Otherwise
-    // the output file will show a file size of zero and no contents.
-    {
-        SbetReader reader;
-        reader.setOptions(makeReaderOptions());
-        SbetWriter writer;
-        writer.setOptions(makeWriterOptions());
-        writer.setInput(reader);
-
-        PointTable table;
-        writer.prepare(table);
-        writer.execute(table);
-    }
-
-    //ABELL - Write of a read file is no longer identical.
-    /**
-    EXPECT_TRUE(Support::compare_files(
-        Support::temppath("SbetWriterTest.sbet"),
-        Support::datapath("sbet/2-points.sbet")));
-    **/
-}
diff --git a/test/unit/io/terrasolid/TerrasolidReaderTest.cpp b/test/unit/io/terrasolid/TerrasolidReaderTest.cpp
deleted file mode 100644
index f2d54e4..0000000
--- a/test/unit/io/terrasolid/TerrasolidReaderTest.cpp
+++ /dev/null
@@ -1,128 +0,0 @@
-/******************************************************************************
-* Copyright (c) 2015, Peter J. Gadomski <pete.gadomski at gmail.com>
-*
-* All rights reserved.
-*
-* Redistribution and use in source and binary forms, with or without
-* modification, are permitted provided that the following
-* conditions are met:
-*
-*     * Redistributions of source code must retain the above copyright
-*       notice, this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright
-*       notice, this list of conditions and the following disclaimer in
-*       the documentation and/or other materials provided
-*       with the distribution.
-*     * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
-*       names of its contributors may be used to endorse or promote
-*       products derived from this software without specific prior
-*       written permission.
-*
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
-* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
-* OF SUCH DAMAGE.
-****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include "TerrasolidReader.hpp"
-
-#include <pdal/StageFactory.hpp>
-#include "Support.hpp"
-
-
-namespace pdal
-{
-
-
-namespace
-{
-
-
-std::string getTestfilePath()
-{
-    return Support::datapath("terrasolid/20020715-time-color.bin");
-}
-
-
-class TerrasolidReaderTest : public ::testing::Test
-{
-public:
-    TerrasolidReaderTest()
-        : ::testing::Test()
-        , m_reader()
-    {
-        Options options;
-        options.add("filename", getTestfilePath());
-        m_reader.setOptions(options);
-    }
-
-    TerrasolidReader m_reader;
-};
-}
-
-
-TEST(TerrasolidReader, Constructor)
-{
-    TerrasolidReader reader1;
-
-    StageFactory f;
-    Stage* reader2(f.createStage("readers.terrasolid"));
-}
-
-
-TEST_F(TerrasolidReaderTest, Header)
-{
-    PointTable table;
-    m_reader.prepare(table);
-    TerraSolidHeader header = m_reader.getHeader();
-
-    EXPECT_EQ(56, header.HdrSize);
-    EXPECT_EQ(20020715, header.HdrVersion);
-    EXPECT_EQ(970401, header.RecogVal);
-    EXPECT_STREQ("CXYZ\xe8\x3", header.RecogStr);
-    EXPECT_EQ(1000, header.PntCnt);
-    EXPECT_EQ(100, header.Units);
-    EXPECT_DOUBLE_EQ(0, header.OrgX);
-    EXPECT_DOUBLE_EQ(0, header.OrgY);
-    EXPECT_DOUBLE_EQ(0, header.OrgZ);
-    EXPECT_EQ(1, header.Time);
-    EXPECT_EQ(1, header.Color);
-}
-
-
-TEST_F(TerrasolidReaderTest, ReadingPoints)
-{
-    PointTable table;
-    m_reader.prepare(table);
-    PointViewSet viewSet = m_reader.execute(table);
-    EXPECT_EQ(viewSet.size(), 1u);
-    PointViewPtr view = *viewSet.begin();
-    EXPECT_EQ(view->size(), 1000u);
-
-    EXPECT_DOUBLE_EQ(363127.94, view->getFieldAs<double>(Dimension::Id::X, 0));
-    EXPECT_DOUBLE_EQ(3437612.33, view->getFieldAs<double>(Dimension::Id::Y, 0));
-    EXPECT_DOUBLE_EQ(55.26, view->getFieldAs<double>(Dimension::Id::Z, 0));
-    EXPECT_DOUBLE_EQ(0, view->getFieldAs<double>(Dimension::Id::OffsetTime, 0));
-    EXPECT_EQ(1840, view->getFieldAs<uint16_t>(Dimension::Id::Intensity, 0));
-    EXPECT_EQ(27207, view->getFieldAs<uint16_t>(Dimension::Id::PointSourceId, 0));
-    EXPECT_EQ(239, view->getFieldAs<uint8_t>(Dimension::Id::Red, 0));
-    EXPECT_EQ(252, view->getFieldAs<uint8_t>(Dimension::Id::Green, 0));
-    EXPECT_EQ(95, view->getFieldAs<uint8_t>(Dimension::Id::Blue, 0));
-    EXPECT_EQ(0, view->getFieldAs<uint8_t>(Dimension::Id::Alpha, 0));
-    EXPECT_EQ(1, view->getFieldAs<uint8_t>(Dimension::Id::ReturnNumber, 0));
-    EXPECT_EQ(1, view->getFieldAs<uint8_t>(Dimension::Id::NumberOfReturns, 0));
-    EXPECT_EQ(2, view->getFieldAs<uint8_t>(Dimension::Id::Classification, 0));
-    EXPECT_EQ(0, view->getFieldAs<uint8_t>(Dimension::Id::Flag, 0));
-    EXPECT_EQ(0, view->getFieldAs<uint8_t>(Dimension::Id::Mark, 0));
-}
-}
diff --git a/test/unit/io/text/TextReaderTest.cpp b/test/unit/io/text/TextReaderTest.cpp
deleted file mode 100644
index 72266af..0000000
--- a/test/unit/io/text/TextReaderTest.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/******************************************************************************
- * Copyright (c) 2016, Hobu Inc. (info at hobu.co)
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following
- * conditions are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided
- *       with the distribution.
- *     * Neither the name of Hobu, Inc. nor the
- *       names of its contributors may be used to endorse or promote
- *       products derived from this software without specific prior
- *       written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- ****************************************************************************/
-
-#include <pdal/pdal_test_main.hpp>
-
-#include "Support.hpp"
-
-#include <LasReader.hpp>
-#include <TextReader.hpp>
-
-using namespace pdal;
-
-void compareTextLas(const std::string& textFilename,
-    const std::string& lasFilename)
-{
-    TextReader t;
-    Options to;
-    to.add("filename", textFilename);
-    t.setOptions(to);
-
-    LasReader l;
-    Options lo;
-    lo.add("filename", lasFilename);
-    l.setOptions(lo);
-    
-    PointTable tt;
-    t.prepare(tt);
-    PointViewSet ts = t.execute(tt);
-    EXPECT_EQ(ts.size(), 1U);
-    PointViewPtr tv = *ts.begin();
-
-    PointTable lt;
-    l.prepare(lt);
-    PointViewSet ls = l.execute(lt);
-    EXPECT_EQ(ls.size(), 1U);
-    PointViewPtr lv = *ls.begin();
-
-    EXPECT_EQ(tv->size(), lv->size());
-
-    // Validate some point data.
-    for (PointId i = 0; i < lv->size(); ++i)
-    {
-       EXPECT_DOUBLE_EQ(tv->getFieldAs<double>(Dimension::Id::X, i),
-           lv->getFieldAs<double>(Dimension::Id::X, i));
-       EXPECT_DOUBLE_EQ(tv->getFieldAs<double>(Dimension::Id::Y, i),
-           lv->getFieldAs<double>(Dimension::Id::Y, i));
-       EXPECT_DOUBLE_EQ(tv->getFieldAs<double>(Dimension::Id::Z, i),
-           lv->getFieldAs<double>(Dimension::Id::Z, i));
-    }
-}
-
-TEST(TextReaderTest, t1)
-{
-    compareTextLas(Support::datapath("text/utm17_1.txt"),
-        Support::datapath("las/utm17.las"));
-}
-
-TEST(TextReaderTest, t2)
-{
-    compareTextLas(Support::datapath("text/utm17_2.txt"),
-        Support::datapath("las/utm17.las"));
-}
-
-TEST(TextReaderTest, t3)
-{
-    compareTextLas(Support::datapath("text/utm17_3.txt"),
-        Support::datapath("las/utm17.las"));
-}
diff --git a/tools/lasdump/CMakeLists.txt b/tools/lasdump/CMakeLists.txt
index 7904a53..daffff3 100644
--- a/tools/lasdump/CMakeLists.txt
+++ b/tools/lasdump/CMakeLists.txt
@@ -12,8 +12,6 @@ if(NOT ROOT_DIR)
 endif()
 include(${ROOT_DIR}/cmake/common.cmake NO_POLICY_SCOPE)
 
-include_directories(${PDAL_UTIL_DIR} ${PDAL_INCLUDE_DIR})
-
 #
 # Add necessary modules.
 #
@@ -24,25 +22,18 @@ find_package(LASzip QUIET)
 # Right now we don't bother if we don't have LASzip.
 #
 if (LASZIP_FOUND)
-    set (SOURCES
+    add_executable(lasdump
         Dumper.cpp
         Header.cpp
     )
-
-    set (HEADERS
-        Dumper.hpp
-        Header.hpp
-        Lasdump.hpp
-        Vlr.hpp
-    )
-
-    add_executable(lasdump ${SOURCES} ${HEADERS})
-    target_link_libraries(lasdump
+    target_link_libraries(lasdump PRIVATE
         ${PDAL_UTIL_LIB_NAME}
         ${LASZIP_LIBRARIES}
     )
+    target_include_directories(lasdump PRIVATE
+        ${PDAL_INCLUDE_DIR})
     if (WIN32)
-        target_link_libraries(lasdump wsock32 ws2_32)
+        target_link_libraries(lasdump PRIVATE wsock32 ws2_32)
     endif()
 endif()
 
diff --git a/tools/nitfwrap/CMakeLists.txt b/tools/nitfwrap/CMakeLists.txt
index 4bbb8c0..701c72d 100644
--- a/tools/nitfwrap/CMakeLists.txt
+++ b/tools/nitfwrap/CMakeLists.txt
@@ -3,40 +3,28 @@ include(${PDAL_CMAKE_DIR}/nitro.cmake)
 
 set(PDAL_NITF_DIR ${ROOT_DIR}/plugins/nitf/io)
 
-set (SOURCES
+add_executable(nitfwrap
     NitfWrap.cpp
     ${PDAL_NITF_DIR}/MetadataReader.cpp
     ${PDAL_NITF_DIR}/NitfFileReader.cpp
     ${PDAL_NITF_DIR}/NitfFileWriter.cpp
     ${PDAL_NITF_DIR}/tre_plugins.cpp
-)
-
-set (HEADERS
-    NitfWrap.hpp
-    ${PDAL_NITF_DIR}/MetadataReader.hpp
-    ${PDAL_NITF_DIR}/NitfFileReader.hpp
-    ${PDAL_NITF_DIR}/NitfFileWriter.hpp
-    ${PDAL_NITF_DIR}/tre_plugins.hpp
-)
-
-add_executable(nitfwrap ${SOURCES} ${HEADERS})
+ )
 add_dependencies(nitfwrap generate_dimension_hpp)
-target_link_libraries(nitfwrap
+target_link_libraries(nitfwrap PRIVATE
     ${PDAL_BASE_LIB_NAME}
     ${PDAL_UTIL_LIB_NAME}
     ${NITRO_LIBRARIES}
 )
-target_include_directories(nitfwrap
-    PRIVATE
-    "${CMAKE_CURRENT_BINARY_DIR}/include"
-    "${PDAL_INCLUDE_DIR}"
-    "${PDAL_NITF_DIR}"
-)
 
+target_include_directories(nitfwrap PRIVATE
+    ${PROJECT_BINARY_DIR}/include
+    ${PDAL_INCLUDE_DIR}
+    ${ROOT_DIR}
+)
 
 if (WITH_TESTS)
-    PDAL_ADD_TEST(
-        nitfwrap_test
+    PDAL_ADD_TEST(nitfwrap_test
         FILES NitfWrapTest.cpp
     )
 endif()
diff --git a/tools/nitfwrap/NitfWrap.cpp b/tools/nitfwrap/NitfWrap.cpp
index 36911a7..ec98034 100644
--- a/tools/nitfwrap/NitfWrap.cpp
+++ b/tools/nitfwrap/NitfWrap.cpp
@@ -35,13 +35,13 @@
 #include <string>
 #include <vector>
 
-#include <NitfFileReader.hpp>
 #include <pdal/Dimension.hpp>
 #include <pdal/GDALUtils.hpp>
-#include <bpf/BpfHeader.hpp>
-#include <las/LasHeader.hpp>
 #include <pdal/util/FileUtils.hpp>
 #include <pdal/util/IStream.hpp>
+#include <io/BpfHeader.hpp>
+#include <io/LasHeader.hpp>
+#include <plugins/nitf/io/NitfFileReader.hpp>
 
 #include "NitfWrap.hpp"
 
diff --git a/tools/nitfwrap/NitfWrap.hpp b/tools/nitfwrap/NitfWrap.hpp
index 975a12f..cc3f0d0 100644
--- a/tools/nitfwrap/NitfWrap.hpp
+++ b/tools/nitfwrap/NitfWrap.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <NitfFileWriter.hpp>
+#include <plugins/nitf/io/NitfFileWriter.hpp>
 
 namespace pdal
 {
diff --git a/vendor/arbiter/CMakeLists.txt b/vendor/arbiter/CMakeLists.txt
index a3c6f47..576de24 100644
--- a/vendor/arbiter/CMakeLists.txt
+++ b/vendor/arbiter/CMakeLists.txt
@@ -1,23 +1,26 @@
 #
 # Make sure we don't attempt to add a library more than once
 #
-get_property(EXISTS GLOBAL PROPERTY _PDALARBITER_INCLUDED)
-if (EXISTS)
+
+if (NOT PDAL_ARBITER_ENABLED)
     return()
 endif()
 
-file(GLOB PDAL_ARBITER_SOURCES
-    "arbiter.cpp"
-    "arbiter.hpp"
-)
-
-if(UNIX)
-  add_definitions("-fPIC")
+get_property(EXISTS GLOBAL PROPERTY _PDALARBITER_INCLUDED)
+if (EXISTS)
+    return()
 endif()
 
-
-PDAL_ADD_LIBRARY(${PDAL_ARBITER_LIB_NAME} STATIC "${PDAL_ARBITER_SOURCES}")
+PDAL_ADD_FREE_LIBRARY(${PDAL_ARBITER_LIB_NAME} STATIC arbiter.cpp)
+target_include_directories(${PDAL_ARBITER_LIB_NAME} PRIVATE
+    ${PDAL_VENDOR_DIR}/jsoncpp/dist)
 target_link_libraries(${PDAL_ARBITER_LIB_NAME} )
+#
+# Arbiter is built static but is included in a shared lib.
+#
+if (UNIX)
+target_compile_options(${PDAL_ARBITER_LIB_NAME} PRIVATE "-fPIC")
+endif()
 
 set_target_properties(${PDAL_ARBITER_LIB_NAME} PROPERTIES
     VERSION "${PDAL_BUILD_VERSION}"
diff --git a/vendor/arbiter/arbiter.cpp b/vendor/arbiter/arbiter.cpp
index c52b12c..1a7f638 100644
--- a/vendor/arbiter/arbiter.cpp
+++ b/vendor/arbiter/arbiter.cpp
@@ -77,22 +77,12 @@ namespace
     const std::size_t httpRetryCount(8);
 }
 
-Arbiter::Arbiter()
-    : m_drivers()
-    , m_pool(concurrentHttpReqs, httpRetryCount, Json::Value())
-{
-    init(Json::Value());
-}
+Arbiter::Arbiter() : Arbiter(Json::Value()) { }
 
 Arbiter::Arbiter(const Json::Value& json)
     : m_drivers()
     , m_pool(concurrentHttpReqs, httpRetryCount, json)
 {
-    init(json);
-}
-
-void Arbiter::init(const Json::Value& json)
-{
     using namespace drivers;
 
     auto fs(Fs::create(json["file"]));
@@ -104,6 +94,9 @@ void Arbiter::init(const Json::Value& json)
     auto http(Http::create(m_pool, json["http"]));
     if (http) m_drivers[http->type()] = std::move(http);
 
+    auto https(Https::create(m_pool, json["http"]));
+    if (https) m_drivers[https->type()] = std::move(https);
+
     auto s3(S3::create(m_pool, json["s3"]));
     if (s3) m_drivers[s3->type()] = std::move(s3);
 
@@ -272,7 +265,7 @@ void Arbiter::copy(
 
 void Arbiter::copyFile(
         const std::string file,
-        const std::string dst,
+        std::string dst,
         const bool verbose) const
 {
     if (dst.empty()) throw ArbiterError("Cannot copy to empty destination");
@@ -283,24 +276,22 @@ void Arbiter::copyFile(
     {
         // If the destination is a directory, maintain the basename of the
         // source file.
-        const std::string basename(util::getBasename(file));
-        if (verbose)
-        {
-            std::cout <<
-                file << " -> " <<
-                dstEndpoint.type() + "://" + dstEndpoint.fullPath(basename) <<
-                std::endl;
-        }
+        dst += util::getBasename(file);
+    }
+
+    if (verbose) std::cout << file << " -> " << dst << std::endl;
 
-        if (dstEndpoint.isLocal()) fs::mkdirp(dst);
+    if (dstEndpoint.isLocal()) fs::mkdirp(util::getNonBasename(dst));
 
-        dstEndpoint.put(util::getBasename(file), getBinary(file));
+    if (getEndpoint(file).type() == dstEndpoint.type())
+    {
+        // If this copy is within the same driver domain, defer to the
+        // hopefully specialized copy method.
+        getDriver(file).copy(stripType(file), stripType(dst));
     }
     else
     {
-        if (verbose) std::cout << file << " -> " << dst << std::endl;
-
-        if (dstEndpoint.isLocal()) fs::mkdirp(util::getNonBasename(dst));
+        // Otherwise do a GET/PUT for the copy.
         put(dst, getBinary(file));
     }
 }
@@ -507,6 +498,11 @@ void Driver::put(std::string path, const std::string& data) const
     put(path, std::vector<char>(data.begin(), data.end()));
 }
 
+void Driver::copy(std::string src, std::string dst) const
+{
+    put(dst, getBinary(src));
+}
+
 std::vector<std::string> Driver::resolve(
         std::string path,
         const bool verbose) const
@@ -790,6 +786,8 @@ const drivers::Http& Endpoint::getHttpDriver() const
 #include <algorithm>
 #include <cstdlib>
 #include <fstream>
+#include <ios>
+#include <istream>
 
 #ifdef ARBITER_CUSTOM_NAMESPACE
 namespace ARBITER_CUSTOM_NAMESPACE
@@ -895,78 +893,30 @@ void Fs::put(std::string path, const std::vector<char>& data) const
     }
 }
 
-std::vector<std::string> Fs::glob(std::string path, bool verbose) const
+void Fs::copy(std::string src, std::string dst) const
 {
-    std::vector<std::string> results;
+    src = fs::expandTilde(src);
+    dst = fs::expandTilde(dst);
 
-    path = fs::expandTilde(path);
-
-    const bool recursive(([&path]()
+    std::ifstream instream(src, std::ifstream::in | std::ifstream::binary);
+    if (!instream.good())
     {
-        if (path.size() > 2 && path[path.size() - 2] == '*')
-        {
-            path.pop_back();
-            return true;
-        }
-        else return false;
-    })());
-
-#ifndef ARBITER_WINDOWS
-    glob_t buffer;
-    struct stat info;
-
-    ::glob(path.c_str(), GLOB_NOSORT | GLOB_MARK, 0, &buffer);
-
-    for (std::size_t i(0); i < buffer.gl_pathc; ++i)
-    {
-        const std::string val(buffer.gl_pathv[i]);
-
-        if (stat(val.c_str(), &info) == 0)
-        {
-            if (S_ISREG(info.st_mode))
-            {
-                if (verbose && results.size() % 10000 == 0)
-                {
-                    std::cout << "." << std::flush;
-                }
-
-                results.push_back(val);
-            }
-            else if (recursive && S_ISDIR(info.st_mode))
-            {
-                const auto nested(glob(val + "**", verbose));
-                results.insert(results.end(), nested.begin(), nested.end());
-            }
-        }
-        else
-        {
-            throw ArbiterError("Error globbing - POSIX stat failed");
-        }
+        throw ArbiterError("Could not open " + src + " for reading");
     }
+    instream >> std::noskipws;
 
-    globfree(&buffer);
-#else
-    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
-    const std::wstring wide(converter.from_bytes(path));
-
-    LPWIN32_FIND_DATAW data{};
-    HANDLE hFind(FindFirstFileW(wide.c_str(), data));
-
-    if (hFind != INVALID_HANDLE_VALUE)
+    std::ofstream outstream(dst, binaryTruncMode);
+    if (!outstream.good())
     {
-        do
-        {
-            if ((data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
-            {
-                results.push_back(converter.to_bytes(data->cFileName));
-            }
-            // TODO Recurse if necessary.
-        }
-        while (FindNextFileW(hFind, data));
+        throw ArbiterError("Could not open " + dst + " for writing");
     }
-#endif
 
-    return results;
+    outstream << instream.rdbuf();
+}
+
+std::vector<std::string> Fs::glob(std::string path, bool verbose) const
+{
+    return fs::glob(path);
 }
 
 } // namespace drivers
@@ -1023,6 +973,120 @@ bool remove(std::string filename)
 #endif
 }
 
+namespace
+{
+    struct Globs
+    {
+        std::vector<std::string> files;
+        std::vector<std::string> dirs;
+    };
+
+    Globs globOne(std::string path)
+    {
+        Globs results;
+
+#ifndef ARBITER_WINDOWS
+        glob_t buffer;
+        struct stat info;
+
+        ::glob(path.c_str(), GLOB_NOSORT | GLOB_MARK, 0, &buffer);
+
+        for (std::size_t i(0); i < buffer.gl_pathc; ++i)
+        {
+            const std::string val(buffer.gl_pathv[i]);
+
+            if (stat(val.c_str(), &info) == 0)
+            {
+                if (S_ISREG(info.st_mode)) results.files.push_back(val);
+                else if (S_ISDIR(info.st_mode)) results.dirs.push_back(val);
+            }
+            else
+            {
+                throw ArbiterError("Error globbing - POSIX stat failed");
+            }
+        }
+
+        globfree(&buffer);
+#else
+        std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
+        const std::wstring wide(converter.from_bytes(path));
+
+        LPWIN32_FIND_DATAW data{};
+        HANDLE hFind(FindFirstFileW(wide.c_str(), data));
+
+        if (hFind != INVALID_HANDLE_VALUE)
+        {
+            do
+            {
+                if ((data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
+                {
+                    results.files.push_back(
+                            converter.to_bytes(data->cFileName));
+                }
+                else
+                {
+                    results.dirs.push_back(converter.to_bytes(data->cFileName));
+                }
+            }
+            while (FindNextFileW(hFind, data));
+        }
+#endif
+
+        return results;
+    }
+
+    std::vector<std::string> walk(std::string dir)
+    {
+        std::vector<std::string> paths;
+        paths.push_back(dir);
+
+        for (const auto& d : globOne(dir + '*').dirs)
+        {
+            const auto next(walk(d));
+            paths.insert(paths.end(), next.begin(), next.end());
+        }
+
+        return paths;
+    }
+}
+
+std::vector<std::string> glob(std::string path)
+{
+    std::vector<std::string> results;
+
+    path = fs::expandTilde(path);
+
+    if (path.find('*') == std::string::npos)
+    {
+        results.push_back(path);
+        return results;
+    }
+
+    std::vector<std::string> dirs;
+
+    const std::size_t recPos(path.find("**"));
+    if (recPos != std::string::npos)
+    {
+        // Convert this recursive glob into multiple non-recursive ones.
+        const auto pre(path.substr(0, recPos));     // Cut off before the '*'.
+        const auto post(path.substr(recPos + 1));   // Includes the second '*'.
+
+        for (const auto d : walk(pre)) dirs.push_back(d + post);
+    }
+    else
+    {
+        dirs.push_back(path);
+    }
+
+    for (const auto& p : dirs)
+    {
+        Globs globs(globOne(p));
+        results.insert(results.end(), globs.files.begin(), globs.files.end());
+    }
+
+    return results;
+}
+
 std::string expandTilde(std::string in)
 {
     std::string out(in);
@@ -1221,9 +1285,10 @@ void Http::put(
 Response Http::internalGet(
         const std::string path,
         const Headers headers,
-        const Query query) const
+        const Query query,
+        const std::size_t reserve) const
 {
-    return m_pool.acquire().get(path, headers, query);
+    return m_pool.acquire().get(path, headers, query, reserve);
 }
 
 Response Http::internalPut(
@@ -1316,8 +1381,8 @@ namespace
     std::string getBaseUrl(const std::string& region)
     {
         // https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
-        if (region == "us-east-1") return ".s3.amazonaws.com/";
-        else return ".s3-" + region + ".amazonaws.com/";
+        if (region == "us-east-1") return "s3.amazonaws.com/";
+        else return "s3-" + region + ".amazonaws.com/";
     }
 
     drivers::Fs fsDriver;
@@ -1334,7 +1399,7 @@ namespace
                 in.begin(),
                 in.end(),
                 std::string(),
-                [](const std::string& out, const char c)
+                [](const std::string& out, const char c) -> std::string
                 {
                     return out + static_cast<char>(::tolower(c));
                 });
@@ -1348,7 +1413,7 @@ namespace
                 in.begin(),
                 in.end(),
                 std::string(),
-                [](const std::string& out, const char c)
+                [](const std::string& out, const char c) -> std::string
                 {
                     if (
                         std::isspace(c) &&
@@ -1424,12 +1489,14 @@ S3::S3(
         Pool& pool,
         const S3::Auth& auth,
         const std::string region,
-        const bool sse)
+        const bool sse,
+        const bool precheck)
     : Http(pool)
     , m_auth(auth)
     , m_region(region)
     , m_baseUrl(getBaseUrl(region))
     , m_baseHeaders()
+    , m_precheck(precheck)
 {
     if (sse)
     {
@@ -1446,6 +1513,7 @@ std::unique_ptr<S3> S3::create(Pool& pool, const Json::Value& json)
 
     const std::string profile(extractProfile(json));
     const bool sse(json["sse"].asBool());
+    const bool precheck(json["precheck"].asBool());
 
     if (!json.isNull() && json.isMember("access") & json.isMember("hidden"))
     {
@@ -1544,7 +1612,7 @@ std::unique_ptr<S3> S3::create(Pool& pool, const Json::Value& json)
             "Region not found in ~/.aws/config - using us-east-1" << std::endl;
     }
 
-    s3.reset(new S3(pool, *auth, region, sse));
+    s3.reset(new S3(pool, *auth, region, sse, precheck));
 
     return s3;
 }
@@ -1603,6 +1671,10 @@ bool S3::get(
         const Headers headers,
         const Query query) const
 {
+    std::unique_ptr<std::size_t> size(
+            m_precheck && !headers.count("Range") ?
+                tryGetSize(rawPath) : nullptr);
+
     const Resource resource(m_baseUrl, rawPath);
     const ApiV4 apiV4(
             "GET",
@@ -1617,7 +1689,8 @@ bool S3::get(
             Http::internalGet(
                 resource.url(),
                 apiV4.headers(),
-                apiV4.query()));
+                apiV4.query(),
+                size ? *size : 0));
 
     if (res.ok())
     {
@@ -1667,6 +1740,14 @@ void S3::put(
     }
 }
 
+void S3::copy(const std::string src, const std::string dst) const
+{
+    Headers headers;
+    const Resource resource(m_baseUrl, src);
+    headers["x-amz-copy-source"] = resource.bucket() + '/' + resource.object();
+    put(dst, std::vector<char>(), headers, Query());
+}
+
 std::vector<std::string> S3::glob(std::string path, bool verbose) const
 {
     std::vector<std::string> results;
@@ -1677,8 +1758,8 @@ std::vector<std::string> S3::glob(std::string path, bool verbose) const
 
     // https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html
     const Resource resource(m_baseUrl, path);
-    const std::string& bucket(resource.bucket);
-    const std::string& object(resource.object);
+    const std::string& bucket(resource.bucket());
+    const std::string& object(resource.object());
 
     Query query;
 
@@ -1691,9 +1772,9 @@ std::vector<std::string> S3::glob(std::string path, bool verbose) const
     {
         if (verbose) std::cout << "." << std::flush;
 
-        if (!get(resource.bucket + "/", data, Headers(), query))
+        if (!get(resource.bucket() + "/", data, Headers(), query))
         {
-            throw ArbiterError("Couldn't S3 GET " + resource.bucket);
+            throw ArbiterError("Couldn't S3 GET " + resource.bucket());
         }
 
         data.push_back('\0');
@@ -1843,7 +1924,7 @@ std::string S3::ApiV4::buildCanonicalRequest(
         const Query& query,
         const std::vector<char>& data) const
 {
-    const std::string canonicalUri(sanitize("/" + resource.object));
+    const std::string canonicalUri(sanitize("/" + resource.object()));
 
     auto canonicalizeQuery([](const std::string& s, const Query::value_type& q)
     {
@@ -1909,29 +1990,61 @@ std::string S3::ApiV4::getAuthHeader(
 }
 
 S3::Resource::Resource(std::string baseUrl, std::string fullPath)
-    : baseUrl(baseUrl)
-    , bucket()
-    , object()
+    : m_baseUrl(baseUrl)
+    , m_bucket()
+    , m_object()
+    , m_virtualHosted(true)
 {
     fullPath = sanitize(fullPath);
     const std::size_t split(fullPath.find("/"));
 
-    bucket = fullPath.substr(0, split);
+    m_bucket = fullPath.substr(0, split);
 
     if (split != std::string::npos)
     {
-        object = fullPath.substr(split + 1);
+        m_object = fullPath.substr(split + 1);
     }
+
+    m_virtualHosted = m_bucket.find_first_of('.') == std::string::npos;
 }
 
 std::string S3::Resource::url() const
 {
-    return "https://" + bucket + baseUrl + object;
+    // We can't use virtual-host style paths if the bucket contains dots.
+    if (m_virtualHosted)
+    {
+        return "https://" + m_bucket + "." + m_baseUrl + m_object;
+    }
+    else
+    {
+        return "https://" + m_baseUrl + m_bucket + "/" + m_object;
+    }
+}
+
+std::string S3::Resource::object() const
+{
+    // We can't use virtual-host style paths if the bucket contains dots.
+    if (m_virtualHosted)
+    {
+        return m_object;
+    }
+    else
+    {
+        return m_bucket + "/" + m_object;
+    }
 }
 
 std::string S3::Resource::host() const
 {
-    return bucket + baseUrl.substr(0, baseUrl.size() - 1); // Pop slash.
+    if (m_virtualHosted)
+    {
+        // Pop slash.
+        return m_bucket + "." + m_baseUrl.substr(0, m_baseUrl.size() - 1);
+    }
+    else
+    {
+        return m_baseUrl.substr(0, m_baseUrl.size() - 1);
+    }
 }
 
 S3::FormattedTime::FormattedTime()
@@ -2220,18 +2333,38 @@ bool Dropbox::get(
     {
         if (!userHeaders.count("Range"))
         {
-            if (!res.headers().count("size")) return false;
+            if (!res.headers().count("dropbox-api-result"))
+            {
+                std::cout << "No dropbox-api-result header found" << std::endl;
+                return false;
+            }
 
-            const std::size_t size(std::stoul(res.headers().at("size")));
-            data = res.data();
+            Json::Value apiJson;
+            Json::Reader reader;
+            if (reader.parse(res.headers().at("dropbox-api-result"), apiJson))
+            {
+                if (!apiJson.isMember("size"))
+                {
+                    std::cout << "No size found in API result" << std::endl;
+                    return false;
+                }
 
-            if (size == data.size()) return true;
+                const std::size_t size(apiJson["size"].asUInt64());
+                data = res.data();
+
+                if (size == data.size()) return true;
+                else
+                {
+                    std::cout <<
+                        "Data size check failed - got " <<
+                        size << " of " << res.data().size() << " bytes." <<
+                        std::endl;
+                }
+            }
             else
             {
-                std::cout <<
-                    "Data size check failed - got " <<
-                    size << " of " << res.data().size() << " bytes." <<
-                    std::endl;
+                std::cout << "Could not parse API result: " <<
+                    reader.getFormattedErrorMessages() << std::endl;
             }
         }
         else
@@ -2408,6 +2541,7 @@ std::vector<std::string> Dropbox::glob(std::string rawPath, bool verbose) const
 #include <arbiter/util/http.hpp>
 #endif
 
+#include <iostream>
 #include <numeric>
 
 #include <curl/curl.h>
@@ -2614,15 +2748,21 @@ void Curl::init(
     }
 }
 
-Response Curl::get(std::string path, Headers headers, Query query)
+Response Curl::get(
+        std::string path,
+        Headers headers,
+        Query query,
+        const std::size_t reserve)
 {
-    int httpCode(0);
+    long httpCode(0);
     std::vector<char> data;
 
+    if (reserve) data.reserve(reserve);
+
     init(path, headers, query);
     if (m_verbose) curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L);
 
-    // Register callback function and date pointer to consume the result.
+    // Register callback function and data pointer to consume the result.
     curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, getCb);
     curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &data);
 
@@ -2644,13 +2784,13 @@ Response Curl::get(std::string path, Headers headers, Query query)
 
 Response Curl::head(std::string path, Headers headers, Query query)
 {
-    int httpCode(0);
+    long httpCode(0);
     std::vector<char> data;
 
     init(path, headers, query);
     if (m_verbose) curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L);
 
-    // Register callback function and date pointer to consume the result.
+    // Register callback function and data pointer to consume the result.
     curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, getCb);
     curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &data);
 
@@ -2682,7 +2822,7 @@ Response Curl::put(
     init(path, headers, query);
     if (m_verbose) curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L);
 
-    int httpCode(0);
+    long httpCode(0);
 
     std::unique_ptr<PutData> putData(new PutData(data));
 
@@ -2724,7 +2864,7 @@ Response Curl::post(
     init(path, headers, query);
     if (m_verbose) curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L);
 
-    int httpCode(0);
+    long httpCode(0);
 
     std::unique_ptr<PutData> putData(new PutData(data));
     std::vector<char> writeData;
@@ -2785,11 +2925,12 @@ Resource::~Resource()
 Response Resource::get(
         const std::string path,
         const Headers headers,
-        const Query query)
+        const Query query,
+        const std::size_t reserve)
 {
-    return exec([this, path, headers, query]()->Response
+    return exec([this, path, headers, query, reserve]()->Response
     {
-        return m_curl.get(path, headers, query);
+        return m_curl.get(path, headers, query, reserve);
     });
 }
 
@@ -3565,6 +3706,9 @@ std::string getNonBasename(const std::string fullPath)
         result = sub;
     }
 
+    const std::string type(Arbiter::getType(fullPath));
+    if (type != "file") result = type + "://" + result;
+
     return result;
 }
 
diff --git a/vendor/arbiter/arbiter.hpp b/vendor/arbiter/arbiter.hpp
index a096110..ff55e53 100644
--- a/vendor/arbiter/arbiter.hpp
+++ b/vendor/arbiter/arbiter.hpp
@@ -1,7 +1,7 @@
 /// Arbiter amalgamated header (https://github.com/connormanning/arbiter).
 /// It is intended to be used with #include "arbiter.hpp"
 
-// Git SHA: 4bbe7fc3c79a8b3843f50de195630f110a4a0e24
+// Git SHA: 333088e3eac1056ba6e718984a967cc241c4d385
 
 // //////////////////////////////////////////////////////////////////////
 // Beginning of content of file: LICENSE
@@ -149,13 +149,20 @@ class Curl
 public:
     ~Curl();
 
-    http::Response get(std::string path, Headers headers, Query query);
+    http::Response get(
+            std::string path,
+            Headers headers,
+            Query query,
+            std::size_t reserve);
+
     http::Response head(std::string path, Headers headers, Query query);
+
     http::Response put(
             std::string path,
             const std::vector<char>& data,
             Headers headers,
             Query query);
+
     http::Response post(
             std::string path,
             const std::vector<char>& data,
@@ -187,7 +194,8 @@ public:
     http::Response get(
             std::string path,
             Headers headers = Headers(),
-            Query query = Query());
+            Query query = Query(),
+            std::size_t reserve = 0);
 
     http::Response head(
             std::string path,
@@ -345,6 +353,11 @@ public:
     /** Write string data. */
     void put(std::string path, const std::string& data) const;
 
+    /** Copy a file, where @p src and @p dst must both be of this driver
+     * type.  Type-prefixes must be stripped from the input parameters.
+     */
+    virtual void copy(std::string src, std::string dst) const;
+
     /** @brief Resolve a possibly globbed path.
      *
      * See Arbiter::resolve for details.
@@ -450,6 +463,9 @@ namespace fs
     /** @brief Get temporary path from environment. */
     std::string getTempPath();
 
+    /** @brief Resolve a possible wildcard path. */
+    std::vector<std::string> glob(std::string path);
+
     /** @brief A scoped local filehandle for a possibly remote path.
      *
      * This is an RAII style pseudo-filehandle.  It manages the scope of a
@@ -522,6 +538,8 @@ public:
 
     virtual bool isRemote() const override { return false; }
 
+    virtual void copy(std::string src, std::string dst) const override;
+
 protected:
     virtual bool get(std::string path, std::vector<char>& data) const override;
 };
@@ -666,7 +684,8 @@ protected:
     http::Response internalGet(
             std::string path,
             http::Headers headers = http::Headers(),
-            http::Query query = http::Query()) const;
+            http::Query query = http::Query(),
+            std::size_t reserve = 0) const;
 
     http::Response internalPut(
             std::string path,
@@ -696,6 +715,24 @@ private:
     http::Pool& m_pool;
 };
 
+/** @brief HTTPS driver.  Identical to the HTTP driver except for its type
+ * string.
+ */
+class Https : public Http
+{
+public:
+    Https(http::Pool& pool) : Http(pool) { }
+
+    static std::unique_ptr<Https> create(
+            http::Pool& pool,
+            const Json::Value& json)
+    {
+        return std::unique_ptr<Https>(new Https(pool));
+    }
+
+    virtual std::string type() const override { return "https"; }
+};
+
 } // namespace drivers
 } // namespace arbiter
 
@@ -3752,7 +3789,8 @@ public:
             http::Pool& pool,
             const Auth& auth,
             std::string region = "us-east-1",
-            bool sse = false);
+            bool sse = false,
+            bool precheck = false);
 
     /** Try to construct an S3 Driver.  Searches @p json primarily for the keys
      * `access` and `hidden` to construct an S3::Auth.  If not found, common
@@ -3780,6 +3818,8 @@ public:
             http::Headers headers,
             http::Query query) const override;
 
+    virtual void copy(std::string src, std::string dst) const override;
+
     /** @brief AWS authentication information. */
     class Auth
     {
@@ -3823,10 +3863,15 @@ private:
 
         std::string url() const;
         std::string host() const;
+        std::string baseUrl() const { return m_baseUrl; }
+        std::string bucket() const { return m_bucket; }
+        std::string object() const;
 
-        std::string baseUrl;
-        std::string bucket;
-        std::string object;
+    private:
+        std::string m_baseUrl;
+        std::string m_bucket;
+        std::string m_object;
+        bool m_virtualHosted;
     };
 
     class FormattedTime
@@ -3901,6 +3946,7 @@ private:
     std::string m_region;
     std::string m_baseUrl;
     http::Headers m_baseHeaders;
+    bool m_precheck;
 };
 
 } // namespace drivers
@@ -4577,9 +4623,6 @@ public:
     http::Pool& httpPool() { return m_pool; }
 
 private:
-    // Registers all available default Driver instances.
-    void init(const Json::Value& json);
-
     const drivers::Http* tryGetHttpDriver(std::string path) const;
     const drivers::Http& getHttpDriver(std::string path) const;
 
diff --git a/vendor/eigen-3.2.8/Eigen/Array b/vendor/eigen/Eigen/Array
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Array
rename to vendor/eigen/Eigen/Array
diff --git a/vendor/eigen-3.2.8/Eigen/CMakeLists.txt b/vendor/eigen/Eigen/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/CMakeLists.txt
rename to vendor/eigen/Eigen/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/Cholesky b/vendor/eigen/Eigen/Cholesky
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Cholesky
rename to vendor/eigen/Eigen/Cholesky
diff --git a/vendor/eigen-3.2.8/Eigen/CholmodSupport b/vendor/eigen/Eigen/CholmodSupport
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/CholmodSupport
rename to vendor/eigen/Eigen/CholmodSupport
diff --git a/vendor/eigen-3.2.8/Eigen/Core b/vendor/eigen/Eigen/Core
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Core
rename to vendor/eigen/Eigen/Core
diff --git a/vendor/eigen-3.2.8/Eigen/Dense b/vendor/eigen/Eigen/Dense
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Dense
rename to vendor/eigen/Eigen/Dense
diff --git a/vendor/eigen-3.2.8/Eigen/Eigen b/vendor/eigen/Eigen/Eigen
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Eigen
rename to vendor/eigen/Eigen/Eigen
diff --git a/vendor/eigen-3.2.8/Eigen/Eigen2Support b/vendor/eigen/Eigen/Eigen2Support
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Eigen2Support
rename to vendor/eigen/Eigen/Eigen2Support
diff --git a/vendor/eigen-3.2.8/Eigen/Eigenvalues b/vendor/eigen/Eigen/Eigenvalues
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Eigenvalues
rename to vendor/eigen/Eigen/Eigenvalues
diff --git a/vendor/eigen-3.2.8/Eigen/Geometry b/vendor/eigen/Eigen/Geometry
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Geometry
rename to vendor/eigen/Eigen/Geometry
diff --git a/vendor/eigen-3.2.8/Eigen/Householder b/vendor/eigen/Eigen/Householder
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Householder
rename to vendor/eigen/Eigen/Householder
diff --git a/vendor/eigen-3.2.8/Eigen/IterativeLinearSolvers b/vendor/eigen/Eigen/IterativeLinearSolvers
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/IterativeLinearSolvers
rename to vendor/eigen/Eigen/IterativeLinearSolvers
diff --git a/vendor/eigen-3.2.8/Eigen/Jacobi b/vendor/eigen/Eigen/Jacobi
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Jacobi
rename to vendor/eigen/Eigen/Jacobi
diff --git a/vendor/eigen-3.2.8/Eigen/LU b/vendor/eigen/Eigen/LU
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/LU
rename to vendor/eigen/Eigen/LU
diff --git a/vendor/eigen-3.2.8/Eigen/LeastSquares b/vendor/eigen/Eigen/LeastSquares
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/LeastSquares
rename to vendor/eigen/Eigen/LeastSquares
diff --git a/vendor/eigen-3.2.8/Eigen/MetisSupport b/vendor/eigen/Eigen/MetisSupport
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/MetisSupport
rename to vendor/eigen/Eigen/MetisSupport
diff --git a/vendor/eigen-3.2.8/Eigen/OrderingMethods b/vendor/eigen/Eigen/OrderingMethods
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/OrderingMethods
rename to vendor/eigen/Eigen/OrderingMethods
diff --git a/vendor/eigen-3.2.8/Eigen/PaStiXSupport b/vendor/eigen/Eigen/PaStiXSupport
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/PaStiXSupport
rename to vendor/eigen/Eigen/PaStiXSupport
diff --git a/vendor/eigen-3.2.8/Eigen/PardisoSupport b/vendor/eigen/Eigen/PardisoSupport
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/PardisoSupport
rename to vendor/eigen/Eigen/PardisoSupport
diff --git a/vendor/eigen-3.2.8/Eigen/QR b/vendor/eigen/Eigen/QR
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/QR
rename to vendor/eigen/Eigen/QR
diff --git a/vendor/eigen-3.2.8/Eigen/QtAlignedMalloc b/vendor/eigen/Eigen/QtAlignedMalloc
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/QtAlignedMalloc
rename to vendor/eigen/Eigen/QtAlignedMalloc
diff --git a/vendor/eigen-3.2.8/Eigen/SPQRSupport b/vendor/eigen/Eigen/SPQRSupport
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/SPQRSupport
rename to vendor/eigen/Eigen/SPQRSupport
diff --git a/vendor/eigen-3.2.8/Eigen/SVD b/vendor/eigen/Eigen/SVD
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/SVD
rename to vendor/eigen/Eigen/SVD
diff --git a/vendor/eigen-3.2.8/Eigen/Sparse b/vendor/eigen/Eigen/Sparse
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/Sparse
rename to vendor/eigen/Eigen/Sparse
diff --git a/vendor/eigen-3.2.8/Eigen/SparseCholesky b/vendor/eigen/Eigen/SparseCholesky
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/SparseCholesky
rename to vendor/eigen/Eigen/SparseCholesky
diff --git a/vendor/eigen-3.2.8/Eigen/SparseCore b/vendor/eigen/Eigen/SparseCore
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/SparseCore
rename to vendor/eigen/Eigen/SparseCore
diff --git a/vendor/eigen-3.2.8/Eigen/SparseLU b/vendor/eigen/Eigen/SparseLU
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/SparseLU
rename to vendor/eigen/Eigen/SparseLU
diff --git a/vendor/eigen-3.2.8/Eigen/SparseQR b/vendor/eigen/Eigen/SparseQR
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/SparseQR
rename to vendor/eigen/Eigen/SparseQR
diff --git a/vendor/eigen-3.2.8/Eigen/StdDeque b/vendor/eigen/Eigen/StdDeque
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/StdDeque
rename to vendor/eigen/Eigen/StdDeque
diff --git a/vendor/eigen-3.2.8/Eigen/StdList b/vendor/eigen/Eigen/StdList
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/StdList
rename to vendor/eigen/Eigen/StdList
diff --git a/vendor/eigen-3.2.8/Eigen/StdVector b/vendor/eigen/Eigen/StdVector
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/StdVector
rename to vendor/eigen/Eigen/StdVector
diff --git a/vendor/eigen-3.2.8/Eigen/SuperLUSupport b/vendor/eigen/Eigen/SuperLUSupport
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/SuperLUSupport
rename to vendor/eigen/Eigen/SuperLUSupport
diff --git a/vendor/eigen-3.2.8/Eigen/UmfPackSupport b/vendor/eigen/Eigen/UmfPackSupport
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/UmfPackSupport
rename to vendor/eigen/Eigen/UmfPackSupport
diff --git a/vendor/eigen-3.2.8/Eigen/src/CMakeLists.txt b/vendor/eigen/Eigen/src/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/CMakeLists.txt
rename to vendor/eigen/Eigen/src/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Cholesky/CMakeLists.txt b/vendor/eigen/Eigen/src/Cholesky/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Cholesky/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Cholesky/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Cholesky/LDLT.h b/vendor/eigen/Eigen/src/Cholesky/LDLT.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Cholesky/LDLT.h
rename to vendor/eigen/Eigen/src/Cholesky/LDLT.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Cholesky/LLT.h b/vendor/eigen/Eigen/src/Cholesky/LLT.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Cholesky/LLT.h
rename to vendor/eigen/Eigen/src/Cholesky/LLT.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Cholesky/LLT_MKL.h b/vendor/eigen/Eigen/src/Cholesky/LLT_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Cholesky/LLT_MKL.h
rename to vendor/eigen/Eigen/src/Cholesky/LLT_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/CholmodSupport/CMakeLists.txt b/vendor/eigen/Eigen/src/CholmodSupport/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/CholmodSupport/CMakeLists.txt
rename to vendor/eigen/Eigen/src/CholmodSupport/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/CholmodSupport/CholmodSupport.h b/vendor/eigen/Eigen/src/CholmodSupport/CholmodSupport.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/CholmodSupport/CholmodSupport.h
rename to vendor/eigen/Eigen/src/CholmodSupport/CholmodSupport.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Array.h b/vendor/eigen/Eigen/src/Core/Array.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Array.h
rename to vendor/eigen/Eigen/src/Core/Array.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/ArrayBase.h b/vendor/eigen/Eigen/src/Core/ArrayBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/ArrayBase.h
rename to vendor/eigen/Eigen/src/Core/ArrayBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/ArrayWrapper.h b/vendor/eigen/Eigen/src/Core/ArrayWrapper.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/ArrayWrapper.h
rename to vendor/eigen/Eigen/src/Core/ArrayWrapper.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Assign.h b/vendor/eigen/Eigen/src/Core/Assign.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Assign.h
rename to vendor/eigen/Eigen/src/Core/Assign.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Assign_MKL.h b/vendor/eigen/Eigen/src/Core/Assign_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Assign_MKL.h
rename to vendor/eigen/Eigen/src/Core/Assign_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/BandMatrix.h b/vendor/eigen/Eigen/src/Core/BandMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/BandMatrix.h
rename to vendor/eigen/Eigen/src/Core/BandMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Block.h b/vendor/eigen/Eigen/src/Core/Block.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Block.h
rename to vendor/eigen/Eigen/src/Core/Block.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/BooleanRedux.h b/vendor/eigen/Eigen/src/Core/BooleanRedux.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/BooleanRedux.h
rename to vendor/eigen/Eigen/src/Core/BooleanRedux.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/CMakeLists.txt b/vendor/eigen/Eigen/src/Core/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Core/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/CommaInitializer.h b/vendor/eigen/Eigen/src/Core/CommaInitializer.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/CommaInitializer.h
rename to vendor/eigen/Eigen/src/Core/CommaInitializer.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/CoreIterators.h b/vendor/eigen/Eigen/src/Core/CoreIterators.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/CoreIterators.h
rename to vendor/eigen/Eigen/src/Core/CoreIterators.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/CwiseBinaryOp.h b/vendor/eigen/Eigen/src/Core/CwiseBinaryOp.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/CwiseBinaryOp.h
rename to vendor/eigen/Eigen/src/Core/CwiseBinaryOp.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/CwiseNullaryOp.h b/vendor/eigen/Eigen/src/Core/CwiseNullaryOp.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/CwiseNullaryOp.h
rename to vendor/eigen/Eigen/src/Core/CwiseNullaryOp.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/CwiseUnaryOp.h b/vendor/eigen/Eigen/src/Core/CwiseUnaryOp.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/CwiseUnaryOp.h
rename to vendor/eigen/Eigen/src/Core/CwiseUnaryOp.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/CwiseUnaryView.h b/vendor/eigen/Eigen/src/Core/CwiseUnaryView.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/CwiseUnaryView.h
rename to vendor/eigen/Eigen/src/Core/CwiseUnaryView.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/DenseBase.h b/vendor/eigen/Eigen/src/Core/DenseBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/DenseBase.h
rename to vendor/eigen/Eigen/src/Core/DenseBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/DenseCoeffsBase.h b/vendor/eigen/Eigen/src/Core/DenseCoeffsBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/DenseCoeffsBase.h
rename to vendor/eigen/Eigen/src/Core/DenseCoeffsBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/DenseStorage.h b/vendor/eigen/Eigen/src/Core/DenseStorage.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/DenseStorage.h
rename to vendor/eigen/Eigen/src/Core/DenseStorage.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Diagonal.h b/vendor/eigen/Eigen/src/Core/Diagonal.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Diagonal.h
rename to vendor/eigen/Eigen/src/Core/Diagonal.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/DiagonalMatrix.h b/vendor/eigen/Eigen/src/Core/DiagonalMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/DiagonalMatrix.h
rename to vendor/eigen/Eigen/src/Core/DiagonalMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/DiagonalProduct.h b/vendor/eigen/Eigen/src/Core/DiagonalProduct.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/DiagonalProduct.h
rename to vendor/eigen/Eigen/src/Core/DiagonalProduct.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Dot.h b/vendor/eigen/Eigen/src/Core/Dot.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Dot.h
rename to vendor/eigen/Eigen/src/Core/Dot.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/EigenBase.h b/vendor/eigen/Eigen/src/Core/EigenBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/EigenBase.h
rename to vendor/eigen/Eigen/src/Core/EigenBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Flagged.h b/vendor/eigen/Eigen/src/Core/Flagged.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Flagged.h
rename to vendor/eigen/Eigen/src/Core/Flagged.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/ForceAlignedAccess.h b/vendor/eigen/Eigen/src/Core/ForceAlignedAccess.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/ForceAlignedAccess.h
rename to vendor/eigen/Eigen/src/Core/ForceAlignedAccess.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Functors.h b/vendor/eigen/Eigen/src/Core/Functors.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Functors.h
rename to vendor/eigen/Eigen/src/Core/Functors.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Fuzzy.h b/vendor/eigen/Eigen/src/Core/Fuzzy.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Fuzzy.h
rename to vendor/eigen/Eigen/src/Core/Fuzzy.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/GeneralProduct.h b/vendor/eigen/Eigen/src/Core/GeneralProduct.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/GeneralProduct.h
rename to vendor/eigen/Eigen/src/Core/GeneralProduct.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/GenericPacketMath.h b/vendor/eigen/Eigen/src/Core/GenericPacketMath.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/GenericPacketMath.h
rename to vendor/eigen/Eigen/src/Core/GenericPacketMath.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/GlobalFunctions.h b/vendor/eigen/Eigen/src/Core/GlobalFunctions.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/GlobalFunctions.h
rename to vendor/eigen/Eigen/src/Core/GlobalFunctions.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/IO.h b/vendor/eigen/Eigen/src/Core/IO.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/IO.h
rename to vendor/eigen/Eigen/src/Core/IO.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Map.h b/vendor/eigen/Eigen/src/Core/Map.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Map.h
rename to vendor/eigen/Eigen/src/Core/Map.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/MapBase.h b/vendor/eigen/Eigen/src/Core/MapBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/MapBase.h
rename to vendor/eigen/Eigen/src/Core/MapBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/MathFunctions.h b/vendor/eigen/Eigen/src/Core/MathFunctions.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/MathFunctions.h
rename to vendor/eigen/Eigen/src/Core/MathFunctions.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Matrix.h b/vendor/eigen/Eigen/src/Core/Matrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Matrix.h
rename to vendor/eigen/Eigen/src/Core/Matrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/MatrixBase.h b/vendor/eigen/Eigen/src/Core/MatrixBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/MatrixBase.h
rename to vendor/eigen/Eigen/src/Core/MatrixBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/NestByValue.h b/vendor/eigen/Eigen/src/Core/NestByValue.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/NestByValue.h
rename to vendor/eigen/Eigen/src/Core/NestByValue.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/NoAlias.h b/vendor/eigen/Eigen/src/Core/NoAlias.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/NoAlias.h
rename to vendor/eigen/Eigen/src/Core/NoAlias.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/NumTraits.h b/vendor/eigen/Eigen/src/Core/NumTraits.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/NumTraits.h
rename to vendor/eigen/Eigen/src/Core/NumTraits.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/PermutationMatrix.h b/vendor/eigen/Eigen/src/Core/PermutationMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/PermutationMatrix.h
rename to vendor/eigen/Eigen/src/Core/PermutationMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/PlainObjectBase.h b/vendor/eigen/Eigen/src/Core/PlainObjectBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/PlainObjectBase.h
rename to vendor/eigen/Eigen/src/Core/PlainObjectBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/ProductBase.h b/vendor/eigen/Eigen/src/Core/ProductBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/ProductBase.h
rename to vendor/eigen/Eigen/src/Core/ProductBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Random.h b/vendor/eigen/Eigen/src/Core/Random.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Random.h
rename to vendor/eigen/Eigen/src/Core/Random.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Redux.h b/vendor/eigen/Eigen/src/Core/Redux.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Redux.h
rename to vendor/eigen/Eigen/src/Core/Redux.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Ref.h b/vendor/eigen/Eigen/src/Core/Ref.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Ref.h
rename to vendor/eigen/Eigen/src/Core/Ref.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Replicate.h b/vendor/eigen/Eigen/src/Core/Replicate.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Replicate.h
rename to vendor/eigen/Eigen/src/Core/Replicate.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/ReturnByValue.h b/vendor/eigen/Eigen/src/Core/ReturnByValue.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/ReturnByValue.h
rename to vendor/eigen/Eigen/src/Core/ReturnByValue.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Reverse.h b/vendor/eigen/Eigen/src/Core/Reverse.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Reverse.h
rename to vendor/eigen/Eigen/src/Core/Reverse.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Select.h b/vendor/eigen/Eigen/src/Core/Select.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Select.h
rename to vendor/eigen/Eigen/src/Core/Select.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/SelfAdjointView.h b/vendor/eigen/Eigen/src/Core/SelfAdjointView.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/SelfAdjointView.h
rename to vendor/eigen/Eigen/src/Core/SelfAdjointView.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/SelfCwiseBinaryOp.h b/vendor/eigen/Eigen/src/Core/SelfCwiseBinaryOp.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/SelfCwiseBinaryOp.h
rename to vendor/eigen/Eigen/src/Core/SelfCwiseBinaryOp.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/SolveTriangular.h b/vendor/eigen/Eigen/src/Core/SolveTriangular.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/SolveTriangular.h
rename to vendor/eigen/Eigen/src/Core/SolveTriangular.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/StableNorm.h b/vendor/eigen/Eigen/src/Core/StableNorm.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/StableNorm.h
rename to vendor/eigen/Eigen/src/Core/StableNorm.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Stride.h b/vendor/eigen/Eigen/src/Core/Stride.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Stride.h
rename to vendor/eigen/Eigen/src/Core/Stride.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Swap.h b/vendor/eigen/Eigen/src/Core/Swap.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Swap.h
rename to vendor/eigen/Eigen/src/Core/Swap.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Transpose.h b/vendor/eigen/Eigen/src/Core/Transpose.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Transpose.h
rename to vendor/eigen/Eigen/src/Core/Transpose.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Transpositions.h b/vendor/eigen/Eigen/src/Core/Transpositions.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Transpositions.h
rename to vendor/eigen/Eigen/src/Core/Transpositions.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/TriangularMatrix.h b/vendor/eigen/Eigen/src/Core/TriangularMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/TriangularMatrix.h
rename to vendor/eigen/Eigen/src/Core/TriangularMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/VectorBlock.h b/vendor/eigen/Eigen/src/Core/VectorBlock.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/VectorBlock.h
rename to vendor/eigen/Eigen/src/Core/VectorBlock.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/VectorwiseOp.h b/vendor/eigen/Eigen/src/Core/VectorwiseOp.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/VectorwiseOp.h
rename to vendor/eigen/Eigen/src/Core/VectorwiseOp.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/Visitor.h b/vendor/eigen/Eigen/src/Core/Visitor.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/Visitor.h
rename to vendor/eigen/Eigen/src/Core/Visitor.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/AltiVec/CMakeLists.txt b/vendor/eigen/Eigen/src/Core/arch/AltiVec/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/AltiVec/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Core/arch/AltiVec/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/AltiVec/Complex.h b/vendor/eigen/Eigen/src/Core/arch/AltiVec/Complex.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/AltiVec/Complex.h
rename to vendor/eigen/Eigen/src/Core/arch/AltiVec/Complex.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/AltiVec/PacketMath.h b/vendor/eigen/Eigen/src/Core/arch/AltiVec/PacketMath.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/AltiVec/PacketMath.h
rename to vendor/eigen/Eigen/src/Core/arch/AltiVec/PacketMath.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/CMakeLists.txt b/vendor/eigen/Eigen/src/Core/arch/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Core/arch/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/Default/CMakeLists.txt b/vendor/eigen/Eigen/src/Core/arch/Default/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/Default/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Core/arch/Default/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/Default/Settings.h b/vendor/eigen/Eigen/src/Core/arch/Default/Settings.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/Default/Settings.h
rename to vendor/eigen/Eigen/src/Core/arch/Default/Settings.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/NEON/CMakeLists.txt b/vendor/eigen/Eigen/src/Core/arch/NEON/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/NEON/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Core/arch/NEON/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/NEON/Complex.h b/vendor/eigen/Eigen/src/Core/arch/NEON/Complex.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/NEON/Complex.h
rename to vendor/eigen/Eigen/src/Core/arch/NEON/Complex.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/NEON/PacketMath.h b/vendor/eigen/Eigen/src/Core/arch/NEON/PacketMath.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/NEON/PacketMath.h
rename to vendor/eigen/Eigen/src/Core/arch/NEON/PacketMath.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/SSE/CMakeLists.txt b/vendor/eigen/Eigen/src/Core/arch/SSE/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/SSE/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Core/arch/SSE/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/SSE/Complex.h b/vendor/eigen/Eigen/src/Core/arch/SSE/Complex.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/SSE/Complex.h
rename to vendor/eigen/Eigen/src/Core/arch/SSE/Complex.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/SSE/MathFunctions.h b/vendor/eigen/Eigen/src/Core/arch/SSE/MathFunctions.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/SSE/MathFunctions.h
rename to vendor/eigen/Eigen/src/Core/arch/SSE/MathFunctions.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/arch/SSE/PacketMath.h b/vendor/eigen/Eigen/src/Core/arch/SSE/PacketMath.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/arch/SSE/PacketMath.h
rename to vendor/eigen/Eigen/src/Core/arch/SSE/PacketMath.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/CMakeLists.txt b/vendor/eigen/Eigen/src/Core/products/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Core/products/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/CoeffBasedProduct.h b/vendor/eigen/Eigen/src/Core/products/CoeffBasedProduct.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/CoeffBasedProduct.h
rename to vendor/eigen/Eigen/src/Core/products/CoeffBasedProduct.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralBlockPanelKernel.h b/vendor/eigen/Eigen/src/Core/products/GeneralBlockPanelKernel.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralBlockPanelKernel.h
rename to vendor/eigen/Eigen/src/Core/products/GeneralBlockPanelKernel.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixMatrix.h b/vendor/eigen/Eigen/src/Core/products/GeneralMatrixMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixMatrix.h
rename to vendor/eigen/Eigen/src/Core/products/GeneralMatrixMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixMatrixTriangular.h b/vendor/eigen/Eigen/src/Core/products/GeneralMatrixMatrixTriangular.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixMatrixTriangular.h
rename to vendor/eigen/Eigen/src/Core/products/GeneralMatrixMatrixTriangular.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixMatrixTriangular_MKL.h b/vendor/eigen/Eigen/src/Core/products/GeneralMatrixMatrixTriangular_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixMatrixTriangular_MKL.h
rename to vendor/eigen/Eigen/src/Core/products/GeneralMatrixMatrixTriangular_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixMatrix_MKL.h b/vendor/eigen/Eigen/src/Core/products/GeneralMatrixMatrix_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixMatrix_MKL.h
rename to vendor/eigen/Eigen/src/Core/products/GeneralMatrixMatrix_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixVector.h b/vendor/eigen/Eigen/src/Core/products/GeneralMatrixVector.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixVector.h
rename to vendor/eigen/Eigen/src/Core/products/GeneralMatrixVector.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixVector_MKL.h b/vendor/eigen/Eigen/src/Core/products/GeneralMatrixVector_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/GeneralMatrixVector_MKL.h
rename to vendor/eigen/Eigen/src/Core/products/GeneralMatrixVector_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/Parallelizer.h b/vendor/eigen/Eigen/src/Core/products/Parallelizer.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/Parallelizer.h
rename to vendor/eigen/Eigen/src/Core/products/Parallelizer.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointMatrixMatrix.h b/vendor/eigen/Eigen/src/Core/products/SelfadjointMatrixMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointMatrixMatrix.h
rename to vendor/eigen/Eigen/src/Core/products/SelfadjointMatrixMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointMatrixMatrix_MKL.h b/vendor/eigen/Eigen/src/Core/products/SelfadjointMatrixMatrix_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointMatrixMatrix_MKL.h
rename to vendor/eigen/Eigen/src/Core/products/SelfadjointMatrixMatrix_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointMatrixVector.h b/vendor/eigen/Eigen/src/Core/products/SelfadjointMatrixVector.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointMatrixVector.h
rename to vendor/eigen/Eigen/src/Core/products/SelfadjointMatrixVector.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointMatrixVector_MKL.h b/vendor/eigen/Eigen/src/Core/products/SelfadjointMatrixVector_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointMatrixVector_MKL.h
rename to vendor/eigen/Eigen/src/Core/products/SelfadjointMatrixVector_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointProduct.h b/vendor/eigen/Eigen/src/Core/products/SelfadjointProduct.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointProduct.h
rename to vendor/eigen/Eigen/src/Core/products/SelfadjointProduct.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointRank2Update.h b/vendor/eigen/Eigen/src/Core/products/SelfadjointRank2Update.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/SelfadjointRank2Update.h
rename to vendor/eigen/Eigen/src/Core/products/SelfadjointRank2Update.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularMatrixMatrix.h b/vendor/eigen/Eigen/src/Core/products/TriangularMatrixMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularMatrixMatrix.h
rename to vendor/eigen/Eigen/src/Core/products/TriangularMatrixMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularMatrixMatrix_MKL.h b/vendor/eigen/Eigen/src/Core/products/TriangularMatrixMatrix_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularMatrixMatrix_MKL.h
rename to vendor/eigen/Eigen/src/Core/products/TriangularMatrixMatrix_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularMatrixVector.h b/vendor/eigen/Eigen/src/Core/products/TriangularMatrixVector.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularMatrixVector.h
rename to vendor/eigen/Eigen/src/Core/products/TriangularMatrixVector.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularMatrixVector_MKL.h b/vendor/eigen/Eigen/src/Core/products/TriangularMatrixVector_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularMatrixVector_MKL.h
rename to vendor/eigen/Eigen/src/Core/products/TriangularMatrixVector_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularSolverMatrix.h b/vendor/eigen/Eigen/src/Core/products/TriangularSolverMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularSolverMatrix.h
rename to vendor/eigen/Eigen/src/Core/products/TriangularSolverMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularSolverMatrix_MKL.h b/vendor/eigen/Eigen/src/Core/products/TriangularSolverMatrix_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularSolverMatrix_MKL.h
rename to vendor/eigen/Eigen/src/Core/products/TriangularSolverMatrix_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularSolverVector.h b/vendor/eigen/Eigen/src/Core/products/TriangularSolverVector.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/products/TriangularSolverVector.h
rename to vendor/eigen/Eigen/src/Core/products/TriangularSolverVector.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/BlasUtil.h b/vendor/eigen/Eigen/src/Core/util/BlasUtil.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/BlasUtil.h
rename to vendor/eigen/Eigen/src/Core/util/BlasUtil.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/CMakeLists.txt b/vendor/eigen/Eigen/src/Core/util/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Core/util/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/Constants.h b/vendor/eigen/Eigen/src/Core/util/Constants.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/Constants.h
rename to vendor/eigen/Eigen/src/Core/util/Constants.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/DisableStupidWarnings.h b/vendor/eigen/Eigen/src/Core/util/DisableStupidWarnings.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/DisableStupidWarnings.h
rename to vendor/eigen/Eigen/src/Core/util/DisableStupidWarnings.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/ForwardDeclarations.h b/vendor/eigen/Eigen/src/Core/util/ForwardDeclarations.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/ForwardDeclarations.h
rename to vendor/eigen/Eigen/src/Core/util/ForwardDeclarations.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/MKL_support.h b/vendor/eigen/Eigen/src/Core/util/MKL_support.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/MKL_support.h
rename to vendor/eigen/Eigen/src/Core/util/MKL_support.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/Macros.h b/vendor/eigen/Eigen/src/Core/util/Macros.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/Macros.h
rename to vendor/eigen/Eigen/src/Core/util/Macros.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/Memory.h b/vendor/eigen/Eigen/src/Core/util/Memory.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/Memory.h
rename to vendor/eigen/Eigen/src/Core/util/Memory.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/Meta.h b/vendor/eigen/Eigen/src/Core/util/Meta.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/Meta.h
rename to vendor/eigen/Eigen/src/Core/util/Meta.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/NonMPL2.h b/vendor/eigen/Eigen/src/Core/util/NonMPL2.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/NonMPL2.h
rename to vendor/eigen/Eigen/src/Core/util/NonMPL2.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/ReenableStupidWarnings.h b/vendor/eigen/Eigen/src/Core/util/ReenableStupidWarnings.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/ReenableStupidWarnings.h
rename to vendor/eigen/Eigen/src/Core/util/ReenableStupidWarnings.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/StaticAssert.h b/vendor/eigen/Eigen/src/Core/util/StaticAssert.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/StaticAssert.h
rename to vendor/eigen/Eigen/src/Core/util/StaticAssert.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Core/util/XprHelper.h b/vendor/eigen/Eigen/src/Core/util/XprHelper.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Core/util/XprHelper.h
rename to vendor/eigen/Eigen/src/Core/util/XprHelper.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Block.h b/vendor/eigen/Eigen/src/Eigen2Support/Block.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Block.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Block.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/CMakeLists.txt b/vendor/eigen/Eigen/src/Eigen2Support/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Eigen2Support/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Cwise.h b/vendor/eigen/Eigen/src/Eigen2Support/Cwise.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Cwise.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Cwise.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/CwiseOperators.h b/vendor/eigen/Eigen/src/Eigen2Support/CwiseOperators.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/CwiseOperators.h
rename to vendor/eigen/Eigen/src/Eigen2Support/CwiseOperators.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/AlignedBox.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/AlignedBox.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/AlignedBox.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/AlignedBox.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/All.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/All.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/All.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/All.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/AngleAxis.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/AngleAxis.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/AngleAxis.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/AngleAxis.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/CMakeLists.txt b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Hyperplane.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/Hyperplane.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Hyperplane.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/Hyperplane.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/ParametrizedLine.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/ParametrizedLine.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/ParametrizedLine.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/ParametrizedLine.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Quaternion.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/Quaternion.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Quaternion.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/Quaternion.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Rotation2D.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/Rotation2D.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Rotation2D.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/Rotation2D.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/RotationBase.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/RotationBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/RotationBase.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/RotationBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Scaling.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/Scaling.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Scaling.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/Scaling.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Transform.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/Transform.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Transform.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/Transform.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Translation.h b/vendor/eigen/Eigen/src/Eigen2Support/Geometry/Translation.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Geometry/Translation.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Geometry/Translation.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/LU.h b/vendor/eigen/Eigen/src/Eigen2Support/LU.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/LU.h
rename to vendor/eigen/Eigen/src/Eigen2Support/LU.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Lazy.h b/vendor/eigen/Eigen/src/Eigen2Support/Lazy.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Lazy.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Lazy.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/LeastSquares.h b/vendor/eigen/Eigen/src/Eigen2Support/LeastSquares.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/LeastSquares.h
rename to vendor/eigen/Eigen/src/Eigen2Support/LeastSquares.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Macros.h b/vendor/eigen/Eigen/src/Eigen2Support/Macros.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Macros.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Macros.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/MathFunctions.h b/vendor/eigen/Eigen/src/Eigen2Support/MathFunctions.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/MathFunctions.h
rename to vendor/eigen/Eigen/src/Eigen2Support/MathFunctions.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Memory.h b/vendor/eigen/Eigen/src/Eigen2Support/Memory.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Memory.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Memory.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Meta.h b/vendor/eigen/Eigen/src/Eigen2Support/Meta.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Meta.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Meta.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Minor.h b/vendor/eigen/Eigen/src/Eigen2Support/Minor.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/Minor.h
rename to vendor/eigen/Eigen/src/Eigen2Support/Minor.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/QR.h b/vendor/eigen/Eigen/src/Eigen2Support/QR.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/QR.h
rename to vendor/eigen/Eigen/src/Eigen2Support/QR.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/SVD.h b/vendor/eigen/Eigen/src/Eigen2Support/SVD.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/SVD.h
rename to vendor/eigen/Eigen/src/Eigen2Support/SVD.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/TriangularSolver.h b/vendor/eigen/Eigen/src/Eigen2Support/TriangularSolver.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/TriangularSolver.h
rename to vendor/eigen/Eigen/src/Eigen2Support/TriangularSolver.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigen2Support/VectorBlock.h b/vendor/eigen/Eigen/src/Eigen2Support/VectorBlock.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigen2Support/VectorBlock.h
rename to vendor/eigen/Eigen/src/Eigen2Support/VectorBlock.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/CMakeLists.txt b/vendor/eigen/Eigen/src/Eigenvalues/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Eigenvalues/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/ComplexEigenSolver.h b/vendor/eigen/Eigen/src/Eigenvalues/ComplexEigenSolver.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/ComplexEigenSolver.h
rename to vendor/eigen/Eigen/src/Eigenvalues/ComplexEigenSolver.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/ComplexSchur.h b/vendor/eigen/Eigen/src/Eigenvalues/ComplexSchur.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/ComplexSchur.h
rename to vendor/eigen/Eigen/src/Eigenvalues/ComplexSchur.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/ComplexSchur_MKL.h b/vendor/eigen/Eigen/src/Eigenvalues/ComplexSchur_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/ComplexSchur_MKL.h
rename to vendor/eigen/Eigen/src/Eigenvalues/ComplexSchur_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/EigenSolver.h b/vendor/eigen/Eigen/src/Eigenvalues/EigenSolver.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/EigenSolver.h
rename to vendor/eigen/Eigen/src/Eigenvalues/EigenSolver.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/GeneralizedEigenSolver.h b/vendor/eigen/Eigen/src/Eigenvalues/GeneralizedEigenSolver.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/GeneralizedEigenSolver.h
rename to vendor/eigen/Eigen/src/Eigenvalues/GeneralizedEigenSolver.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/GeneralizedSelfAdjointEigenSolver.h b/vendor/eigen/Eigen/src/Eigenvalues/GeneralizedSelfAdjointEigenSolver.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/GeneralizedSelfAdjointEigenSolver.h
rename to vendor/eigen/Eigen/src/Eigenvalues/GeneralizedSelfAdjointEigenSolver.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/HessenbergDecomposition.h b/vendor/eigen/Eigen/src/Eigenvalues/HessenbergDecomposition.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/HessenbergDecomposition.h
rename to vendor/eigen/Eigen/src/Eigenvalues/HessenbergDecomposition.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/MatrixBaseEigenvalues.h b/vendor/eigen/Eigen/src/Eigenvalues/MatrixBaseEigenvalues.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/MatrixBaseEigenvalues.h
rename to vendor/eigen/Eigen/src/Eigenvalues/MatrixBaseEigenvalues.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/RealQZ.h b/vendor/eigen/Eigen/src/Eigenvalues/RealQZ.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/RealQZ.h
rename to vendor/eigen/Eigen/src/Eigenvalues/RealQZ.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/RealSchur.h b/vendor/eigen/Eigen/src/Eigenvalues/RealSchur.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/RealSchur.h
rename to vendor/eigen/Eigen/src/Eigenvalues/RealSchur.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/RealSchur_MKL.h b/vendor/eigen/Eigen/src/Eigenvalues/RealSchur_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/RealSchur_MKL.h
rename to vendor/eigen/Eigen/src/Eigenvalues/RealSchur_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/SelfAdjointEigenSolver.h b/vendor/eigen/Eigen/src/Eigenvalues/SelfAdjointEigenSolver.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/SelfAdjointEigenSolver.h
rename to vendor/eigen/Eigen/src/Eigenvalues/SelfAdjointEigenSolver.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/SelfAdjointEigenSolver_MKL.h b/vendor/eigen/Eigen/src/Eigenvalues/SelfAdjointEigenSolver_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/SelfAdjointEigenSolver_MKL.h
rename to vendor/eigen/Eigen/src/Eigenvalues/SelfAdjointEigenSolver_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Eigenvalues/Tridiagonalization.h b/vendor/eigen/Eigen/src/Eigenvalues/Tridiagonalization.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Eigenvalues/Tridiagonalization.h
rename to vendor/eigen/Eigen/src/Eigenvalues/Tridiagonalization.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/AlignedBox.h b/vendor/eigen/Eigen/src/Geometry/AlignedBox.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/AlignedBox.h
rename to vendor/eigen/Eigen/src/Geometry/AlignedBox.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/AngleAxis.h b/vendor/eigen/Eigen/src/Geometry/AngleAxis.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/AngleAxis.h
rename to vendor/eigen/Eigen/src/Geometry/AngleAxis.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/CMakeLists.txt b/vendor/eigen/Eigen/src/Geometry/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Geometry/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/EulerAngles.h b/vendor/eigen/Eigen/src/Geometry/EulerAngles.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/EulerAngles.h
rename to vendor/eigen/Eigen/src/Geometry/EulerAngles.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/Homogeneous.h b/vendor/eigen/Eigen/src/Geometry/Homogeneous.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/Homogeneous.h
rename to vendor/eigen/Eigen/src/Geometry/Homogeneous.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/Hyperplane.h b/vendor/eigen/Eigen/src/Geometry/Hyperplane.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/Hyperplane.h
rename to vendor/eigen/Eigen/src/Geometry/Hyperplane.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/OrthoMethods.h b/vendor/eigen/Eigen/src/Geometry/OrthoMethods.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/OrthoMethods.h
rename to vendor/eigen/Eigen/src/Geometry/OrthoMethods.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/ParametrizedLine.h b/vendor/eigen/Eigen/src/Geometry/ParametrizedLine.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/ParametrizedLine.h
rename to vendor/eigen/Eigen/src/Geometry/ParametrizedLine.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/Quaternion.h b/vendor/eigen/Eigen/src/Geometry/Quaternion.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/Quaternion.h
rename to vendor/eigen/Eigen/src/Geometry/Quaternion.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/Rotation2D.h b/vendor/eigen/Eigen/src/Geometry/Rotation2D.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/Rotation2D.h
rename to vendor/eigen/Eigen/src/Geometry/Rotation2D.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/RotationBase.h b/vendor/eigen/Eigen/src/Geometry/RotationBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/RotationBase.h
rename to vendor/eigen/Eigen/src/Geometry/RotationBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/Scaling.h b/vendor/eigen/Eigen/src/Geometry/Scaling.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/Scaling.h
rename to vendor/eigen/Eigen/src/Geometry/Scaling.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/Transform.h b/vendor/eigen/Eigen/src/Geometry/Transform.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/Transform.h
rename to vendor/eigen/Eigen/src/Geometry/Transform.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/Translation.h b/vendor/eigen/Eigen/src/Geometry/Translation.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/Translation.h
rename to vendor/eigen/Eigen/src/Geometry/Translation.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/Umeyama.h b/vendor/eigen/Eigen/src/Geometry/Umeyama.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/Umeyama.h
rename to vendor/eigen/Eigen/src/Geometry/Umeyama.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/arch/CMakeLists.txt b/vendor/eigen/Eigen/src/Geometry/arch/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/arch/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Geometry/arch/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Geometry/arch/Geometry_SSE.h b/vendor/eigen/Eigen/src/Geometry/arch/Geometry_SSE.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Geometry/arch/Geometry_SSE.h
rename to vendor/eigen/Eigen/src/Geometry/arch/Geometry_SSE.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Householder/BlockHouseholder.h b/vendor/eigen/Eigen/src/Householder/BlockHouseholder.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Householder/BlockHouseholder.h
rename to vendor/eigen/Eigen/src/Householder/BlockHouseholder.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Householder/CMakeLists.txt b/vendor/eigen/Eigen/src/Householder/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Householder/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Householder/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Householder/Householder.h b/vendor/eigen/Eigen/src/Householder/Householder.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Householder/Householder.h
rename to vendor/eigen/Eigen/src/Householder/Householder.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Householder/HouseholderSequence.h b/vendor/eigen/Eigen/src/Householder/HouseholderSequence.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Householder/HouseholderSequence.h
rename to vendor/eigen/Eigen/src/Householder/HouseholderSequence.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/BasicPreconditioners.h b/vendor/eigen/Eigen/src/IterativeLinearSolvers/BasicPreconditioners.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/BasicPreconditioners.h
rename to vendor/eigen/Eigen/src/IterativeLinearSolvers/BasicPreconditioners.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/BiCGSTAB.h b/vendor/eigen/Eigen/src/IterativeLinearSolvers/BiCGSTAB.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/BiCGSTAB.h
rename to vendor/eigen/Eigen/src/IterativeLinearSolvers/BiCGSTAB.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/CMakeLists.txt b/vendor/eigen/Eigen/src/IterativeLinearSolvers/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/CMakeLists.txt
rename to vendor/eigen/Eigen/src/IterativeLinearSolvers/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/ConjugateGradient.h b/vendor/eigen/Eigen/src/IterativeLinearSolvers/ConjugateGradient.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/ConjugateGradient.h
rename to vendor/eigen/Eigen/src/IterativeLinearSolvers/ConjugateGradient.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/IncompleteLUT.h b/vendor/eigen/Eigen/src/IterativeLinearSolvers/IncompleteLUT.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/IncompleteLUT.h
rename to vendor/eigen/Eigen/src/IterativeLinearSolvers/IncompleteLUT.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/IterativeSolverBase.h b/vendor/eigen/Eigen/src/IterativeLinearSolvers/IterativeSolverBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/IterativeLinearSolvers/IterativeSolverBase.h
rename to vendor/eigen/Eigen/src/IterativeLinearSolvers/IterativeSolverBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/Jacobi/CMakeLists.txt b/vendor/eigen/Eigen/src/Jacobi/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Jacobi/CMakeLists.txt
rename to vendor/eigen/Eigen/src/Jacobi/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/Jacobi/Jacobi.h b/vendor/eigen/Eigen/src/Jacobi/Jacobi.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/Jacobi/Jacobi.h
rename to vendor/eigen/Eigen/src/Jacobi/Jacobi.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/LU/CMakeLists.txt b/vendor/eigen/Eigen/src/LU/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/LU/CMakeLists.txt
rename to vendor/eigen/Eigen/src/LU/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/LU/Determinant.h b/vendor/eigen/Eigen/src/LU/Determinant.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/LU/Determinant.h
rename to vendor/eigen/Eigen/src/LU/Determinant.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/LU/FullPivLU.h b/vendor/eigen/Eigen/src/LU/FullPivLU.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/LU/FullPivLU.h
rename to vendor/eigen/Eigen/src/LU/FullPivLU.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/LU/Inverse.h b/vendor/eigen/Eigen/src/LU/Inverse.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/LU/Inverse.h
rename to vendor/eigen/Eigen/src/LU/Inverse.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/LU/PartialPivLU.h b/vendor/eigen/Eigen/src/LU/PartialPivLU.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/LU/PartialPivLU.h
rename to vendor/eigen/Eigen/src/LU/PartialPivLU.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/LU/PartialPivLU_MKL.h b/vendor/eigen/Eigen/src/LU/PartialPivLU_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/LU/PartialPivLU_MKL.h
rename to vendor/eigen/Eigen/src/LU/PartialPivLU_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/LU/arch/CMakeLists.txt b/vendor/eigen/Eigen/src/LU/arch/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/LU/arch/CMakeLists.txt
rename to vendor/eigen/Eigen/src/LU/arch/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/LU/arch/Inverse_SSE.h b/vendor/eigen/Eigen/src/LU/arch/Inverse_SSE.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/LU/arch/Inverse_SSE.h
rename to vendor/eigen/Eigen/src/LU/arch/Inverse_SSE.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/MetisSupport/CMakeLists.txt b/vendor/eigen/Eigen/src/MetisSupport/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/MetisSupport/CMakeLists.txt
rename to vendor/eigen/Eigen/src/MetisSupport/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/MetisSupport/MetisSupport.h b/vendor/eigen/Eigen/src/MetisSupport/MetisSupport.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/MetisSupport/MetisSupport.h
rename to vendor/eigen/Eigen/src/MetisSupport/MetisSupport.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/OrderingMethods/Amd.h b/vendor/eigen/Eigen/src/OrderingMethods/Amd.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/OrderingMethods/Amd.h
rename to vendor/eigen/Eigen/src/OrderingMethods/Amd.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/OrderingMethods/CMakeLists.txt b/vendor/eigen/Eigen/src/OrderingMethods/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/OrderingMethods/CMakeLists.txt
rename to vendor/eigen/Eigen/src/OrderingMethods/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/OrderingMethods/Eigen_Colamd.h b/vendor/eigen/Eigen/src/OrderingMethods/Eigen_Colamd.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/OrderingMethods/Eigen_Colamd.h
rename to vendor/eigen/Eigen/src/OrderingMethods/Eigen_Colamd.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/OrderingMethods/Ordering.h b/vendor/eigen/Eigen/src/OrderingMethods/Ordering.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/OrderingMethods/Ordering.h
rename to vendor/eigen/Eigen/src/OrderingMethods/Ordering.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/PaStiXSupport/CMakeLists.txt b/vendor/eigen/Eigen/src/PaStiXSupport/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/PaStiXSupport/CMakeLists.txt
rename to vendor/eigen/Eigen/src/PaStiXSupport/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/PaStiXSupport/PaStiXSupport.h b/vendor/eigen/Eigen/src/PaStiXSupport/PaStiXSupport.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/PaStiXSupport/PaStiXSupport.h
rename to vendor/eigen/Eigen/src/PaStiXSupport/PaStiXSupport.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/PardisoSupport/CMakeLists.txt b/vendor/eigen/Eigen/src/PardisoSupport/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/PardisoSupport/CMakeLists.txt
rename to vendor/eigen/Eigen/src/PardisoSupport/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/PardisoSupport/PardisoSupport.h b/vendor/eigen/Eigen/src/PardisoSupport/PardisoSupport.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/PardisoSupport/PardisoSupport.h
rename to vendor/eigen/Eigen/src/PardisoSupport/PardisoSupport.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/QR/CMakeLists.txt b/vendor/eigen/Eigen/src/QR/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/QR/CMakeLists.txt
rename to vendor/eigen/Eigen/src/QR/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/QR/ColPivHouseholderQR.h b/vendor/eigen/Eigen/src/QR/ColPivHouseholderQR.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/QR/ColPivHouseholderQR.h
rename to vendor/eigen/Eigen/src/QR/ColPivHouseholderQR.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/QR/ColPivHouseholderQR_MKL.h b/vendor/eigen/Eigen/src/QR/ColPivHouseholderQR_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/QR/ColPivHouseholderQR_MKL.h
rename to vendor/eigen/Eigen/src/QR/ColPivHouseholderQR_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/QR/FullPivHouseholderQR.h b/vendor/eigen/Eigen/src/QR/FullPivHouseholderQR.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/QR/FullPivHouseholderQR.h
rename to vendor/eigen/Eigen/src/QR/FullPivHouseholderQR.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/QR/HouseholderQR.h b/vendor/eigen/Eigen/src/QR/HouseholderQR.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/QR/HouseholderQR.h
rename to vendor/eigen/Eigen/src/QR/HouseholderQR.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/QR/HouseholderQR_MKL.h b/vendor/eigen/Eigen/src/QR/HouseholderQR_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/QR/HouseholderQR_MKL.h
rename to vendor/eigen/Eigen/src/QR/HouseholderQR_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SPQRSupport/CMakeLists.txt b/vendor/eigen/Eigen/src/SPQRSupport/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SPQRSupport/CMakeLists.txt
rename to vendor/eigen/Eigen/src/SPQRSupport/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/SPQRSupport/SuiteSparseQRSupport.h b/vendor/eigen/Eigen/src/SPQRSupport/SuiteSparseQRSupport.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SPQRSupport/SuiteSparseQRSupport.h
rename to vendor/eigen/Eigen/src/SPQRSupport/SuiteSparseQRSupport.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SVD/CMakeLists.txt b/vendor/eigen/Eigen/src/SVD/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SVD/CMakeLists.txt
rename to vendor/eigen/Eigen/src/SVD/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/SVD/JacobiSVD.h b/vendor/eigen/Eigen/src/SVD/JacobiSVD.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SVD/JacobiSVD.h
rename to vendor/eigen/Eigen/src/SVD/JacobiSVD.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SVD/JacobiSVD_MKL.h b/vendor/eigen/Eigen/src/SVD/JacobiSVD_MKL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SVD/JacobiSVD_MKL.h
rename to vendor/eigen/Eigen/src/SVD/JacobiSVD_MKL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SVD/UpperBidiagonalization.h b/vendor/eigen/Eigen/src/SVD/UpperBidiagonalization.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SVD/UpperBidiagonalization.h
rename to vendor/eigen/Eigen/src/SVD/UpperBidiagonalization.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCholesky/CMakeLists.txt b/vendor/eigen/Eigen/src/SparseCholesky/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCholesky/CMakeLists.txt
rename to vendor/eigen/Eigen/src/SparseCholesky/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCholesky/SimplicialCholesky.h b/vendor/eigen/Eigen/src/SparseCholesky/SimplicialCholesky.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCholesky/SimplicialCholesky.h
rename to vendor/eigen/Eigen/src/SparseCholesky/SimplicialCholesky.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCholesky/SimplicialCholesky_impl.h b/vendor/eigen/Eigen/src/SparseCholesky/SimplicialCholesky_impl.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCholesky/SimplicialCholesky_impl.h
rename to vendor/eigen/Eigen/src/SparseCholesky/SimplicialCholesky_impl.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/AmbiVector.h b/vendor/eigen/Eigen/src/SparseCore/AmbiVector.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/AmbiVector.h
rename to vendor/eigen/Eigen/src/SparseCore/AmbiVector.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/CMakeLists.txt b/vendor/eigen/Eigen/src/SparseCore/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/CMakeLists.txt
rename to vendor/eigen/Eigen/src/SparseCore/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/CompressedStorage.h b/vendor/eigen/Eigen/src/SparseCore/CompressedStorage.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/CompressedStorage.h
rename to vendor/eigen/Eigen/src/SparseCore/CompressedStorage.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/ConservativeSparseSparseProduct.h b/vendor/eigen/Eigen/src/SparseCore/ConservativeSparseSparseProduct.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/ConservativeSparseSparseProduct.h
rename to vendor/eigen/Eigen/src/SparseCore/ConservativeSparseSparseProduct.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/MappedSparseMatrix.h b/vendor/eigen/Eigen/src/SparseCore/MappedSparseMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/MappedSparseMatrix.h
rename to vendor/eigen/Eigen/src/SparseCore/MappedSparseMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseBlock.h b/vendor/eigen/Eigen/src/SparseCore/SparseBlock.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseBlock.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseBlock.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseColEtree.h b/vendor/eigen/Eigen/src/SparseCore/SparseColEtree.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseColEtree.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseColEtree.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseCwiseBinaryOp.h b/vendor/eigen/Eigen/src/SparseCore/SparseCwiseBinaryOp.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseCwiseBinaryOp.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseCwiseBinaryOp.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseCwiseUnaryOp.h b/vendor/eigen/Eigen/src/SparseCore/SparseCwiseUnaryOp.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseCwiseUnaryOp.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseCwiseUnaryOp.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseDenseProduct.h b/vendor/eigen/Eigen/src/SparseCore/SparseDenseProduct.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseDenseProduct.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseDenseProduct.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseDiagonalProduct.h b/vendor/eigen/Eigen/src/SparseCore/SparseDiagonalProduct.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseDiagonalProduct.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseDiagonalProduct.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseDot.h b/vendor/eigen/Eigen/src/SparseCore/SparseDot.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseDot.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseDot.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseFuzzy.h b/vendor/eigen/Eigen/src/SparseCore/SparseFuzzy.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseFuzzy.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseFuzzy.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseMatrix.h b/vendor/eigen/Eigen/src/SparseCore/SparseMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseMatrix.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseMatrixBase.h b/vendor/eigen/Eigen/src/SparseCore/SparseMatrixBase.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseMatrixBase.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseMatrixBase.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparsePermutation.h b/vendor/eigen/Eigen/src/SparseCore/SparsePermutation.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparsePermutation.h
rename to vendor/eigen/Eigen/src/SparseCore/SparsePermutation.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseProduct.h b/vendor/eigen/Eigen/src/SparseCore/SparseProduct.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseProduct.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseProduct.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseRedux.h b/vendor/eigen/Eigen/src/SparseCore/SparseRedux.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseRedux.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseRedux.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseSelfAdjointView.h b/vendor/eigen/Eigen/src/SparseCore/SparseSelfAdjointView.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseSelfAdjointView.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseSelfAdjointView.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseSparseProductWithPruning.h b/vendor/eigen/Eigen/src/SparseCore/SparseSparseProductWithPruning.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseSparseProductWithPruning.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseSparseProductWithPruning.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseTranspose.h b/vendor/eigen/Eigen/src/SparseCore/SparseTranspose.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseTranspose.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseTranspose.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseTriangularView.h b/vendor/eigen/Eigen/src/SparseCore/SparseTriangularView.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseTriangularView.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseTriangularView.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseUtil.h b/vendor/eigen/Eigen/src/SparseCore/SparseUtil.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseUtil.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseUtil.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseVector.h b/vendor/eigen/Eigen/src/SparseCore/SparseVector.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseVector.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseVector.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseView.h b/vendor/eigen/Eigen/src/SparseCore/SparseView.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/SparseView.h
rename to vendor/eigen/Eigen/src/SparseCore/SparseView.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseCore/TriangularSolver.h b/vendor/eigen/Eigen/src/SparseCore/TriangularSolver.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseCore/TriangularSolver.h
rename to vendor/eigen/Eigen/src/SparseCore/TriangularSolver.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/CMakeLists.txt b/vendor/eigen/Eigen/src/SparseLU/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/CMakeLists.txt
rename to vendor/eigen/Eigen/src/SparseLU/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLUImpl.h b/vendor/eigen/Eigen/src/SparseLU/SparseLUImpl.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLUImpl.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLUImpl.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_Memory.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_Memory.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_Memory.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_Memory.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_Structs.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_Structs.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_Structs.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_Structs.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_SupernodalMatrix.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_SupernodalMatrix.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_SupernodalMatrix.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_SupernodalMatrix.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_Utils.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_Utils.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_Utils.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_Utils.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_column_bmod.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_column_bmod.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_column_bmod.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_column_bmod.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_column_dfs.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_column_dfs.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_column_dfs.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_column_dfs.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_copy_to_ucol.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_copy_to_ucol.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_copy_to_ucol.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_copy_to_ucol.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_gemm_kernel.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_gemm_kernel.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_gemm_kernel.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_gemm_kernel.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_heap_relax_snode.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_heap_relax_snode.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_heap_relax_snode.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_heap_relax_snode.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_kernel_bmod.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_kernel_bmod.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_kernel_bmod.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_kernel_bmod.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_panel_bmod.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_panel_bmod.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_panel_bmod.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_panel_bmod.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_panel_dfs.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_panel_dfs.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_panel_dfs.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_panel_dfs.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_pivotL.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_pivotL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_pivotL.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_pivotL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_pruneL.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_pruneL.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_pruneL.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_pruneL.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_relax_snode.h b/vendor/eigen/Eigen/src/SparseLU/SparseLU_relax_snode.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseLU/SparseLU_relax_snode.h
rename to vendor/eigen/Eigen/src/SparseLU/SparseLU_relax_snode.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseQR/CMakeLists.txt b/vendor/eigen/Eigen/src/SparseQR/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseQR/CMakeLists.txt
rename to vendor/eigen/Eigen/src/SparseQR/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/SparseQR/SparseQR.h b/vendor/eigen/Eigen/src/SparseQR/SparseQR.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SparseQR/SparseQR.h
rename to vendor/eigen/Eigen/src/SparseQR/SparseQR.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/StlSupport/CMakeLists.txt b/vendor/eigen/Eigen/src/StlSupport/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/StlSupport/CMakeLists.txt
rename to vendor/eigen/Eigen/src/StlSupport/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/StlSupport/StdDeque.h b/vendor/eigen/Eigen/src/StlSupport/StdDeque.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/StlSupport/StdDeque.h
rename to vendor/eigen/Eigen/src/StlSupport/StdDeque.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/StlSupport/StdList.h b/vendor/eigen/Eigen/src/StlSupport/StdList.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/StlSupport/StdList.h
rename to vendor/eigen/Eigen/src/StlSupport/StdList.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/StlSupport/StdVector.h b/vendor/eigen/Eigen/src/StlSupport/StdVector.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/StlSupport/StdVector.h
rename to vendor/eigen/Eigen/src/StlSupport/StdVector.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/StlSupport/details.h b/vendor/eigen/Eigen/src/StlSupport/details.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/StlSupport/details.h
rename to vendor/eigen/Eigen/src/StlSupport/details.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/SuperLUSupport/CMakeLists.txt b/vendor/eigen/Eigen/src/SuperLUSupport/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SuperLUSupport/CMakeLists.txt
rename to vendor/eigen/Eigen/src/SuperLUSupport/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/SuperLUSupport/SuperLUSupport.h b/vendor/eigen/Eigen/src/SuperLUSupport/SuperLUSupport.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/SuperLUSupport/SuperLUSupport.h
rename to vendor/eigen/Eigen/src/SuperLUSupport/SuperLUSupport.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/UmfPackSupport/CMakeLists.txt b/vendor/eigen/Eigen/src/UmfPackSupport/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/UmfPackSupport/CMakeLists.txt
rename to vendor/eigen/Eigen/src/UmfPackSupport/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/UmfPackSupport/UmfPackSupport.h b/vendor/eigen/Eigen/src/UmfPackSupport/UmfPackSupport.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/UmfPackSupport/UmfPackSupport.h
rename to vendor/eigen/Eigen/src/UmfPackSupport/UmfPackSupport.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/misc/CMakeLists.txt b/vendor/eigen/Eigen/src/misc/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/misc/CMakeLists.txt
rename to vendor/eigen/Eigen/src/misc/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/misc/Image.h b/vendor/eigen/Eigen/src/misc/Image.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/misc/Image.h
rename to vendor/eigen/Eigen/src/misc/Image.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/misc/Kernel.h b/vendor/eigen/Eigen/src/misc/Kernel.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/misc/Kernel.h
rename to vendor/eigen/Eigen/src/misc/Kernel.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/misc/Solve.h b/vendor/eigen/Eigen/src/misc/Solve.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/misc/Solve.h
rename to vendor/eigen/Eigen/src/misc/Solve.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/misc/SparseSolve.h b/vendor/eigen/Eigen/src/misc/SparseSolve.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/misc/SparseSolve.h
rename to vendor/eigen/Eigen/src/misc/SparseSolve.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/misc/blas.h b/vendor/eigen/Eigen/src/misc/blas.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/misc/blas.h
rename to vendor/eigen/Eigen/src/misc/blas.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/plugins/ArrayCwiseBinaryOps.h b/vendor/eigen/Eigen/src/plugins/ArrayCwiseBinaryOps.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/plugins/ArrayCwiseBinaryOps.h
rename to vendor/eigen/Eigen/src/plugins/ArrayCwiseBinaryOps.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/plugins/ArrayCwiseUnaryOps.h b/vendor/eigen/Eigen/src/plugins/ArrayCwiseUnaryOps.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/plugins/ArrayCwiseUnaryOps.h
rename to vendor/eigen/Eigen/src/plugins/ArrayCwiseUnaryOps.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/plugins/BlockMethods.h b/vendor/eigen/Eigen/src/plugins/BlockMethods.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/plugins/BlockMethods.h
rename to vendor/eigen/Eigen/src/plugins/BlockMethods.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/plugins/CMakeLists.txt b/vendor/eigen/Eigen/src/plugins/CMakeLists.txt
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/plugins/CMakeLists.txt
rename to vendor/eigen/Eigen/src/plugins/CMakeLists.txt
diff --git a/vendor/eigen-3.2.8/Eigen/src/plugins/CommonCwiseBinaryOps.h b/vendor/eigen/Eigen/src/plugins/CommonCwiseBinaryOps.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/plugins/CommonCwiseBinaryOps.h
rename to vendor/eigen/Eigen/src/plugins/CommonCwiseBinaryOps.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/plugins/CommonCwiseUnaryOps.h b/vendor/eigen/Eigen/src/plugins/CommonCwiseUnaryOps.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/plugins/CommonCwiseUnaryOps.h
rename to vendor/eigen/Eigen/src/plugins/CommonCwiseUnaryOps.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/plugins/MatrixCwiseBinaryOps.h b/vendor/eigen/Eigen/src/plugins/MatrixCwiseBinaryOps.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/plugins/MatrixCwiseBinaryOps.h
rename to vendor/eigen/Eigen/src/plugins/MatrixCwiseBinaryOps.h
diff --git a/vendor/eigen-3.2.8/Eigen/src/plugins/MatrixCwiseUnaryOps.h b/vendor/eigen/Eigen/src/plugins/MatrixCwiseUnaryOps.h
similarity index 100%
rename from vendor/eigen-3.2.8/Eigen/src/plugins/MatrixCwiseUnaryOps.h
rename to vendor/eigen/Eigen/src/plugins/MatrixCwiseUnaryOps.h
diff --git a/vendor/gtest-1.7.0/CHANGES b/vendor/gtest/CHANGES
similarity index 100%
rename from vendor/gtest-1.7.0/CHANGES
rename to vendor/gtest/CHANGES
diff --git a/vendor/gtest-1.7.0/CMakeLists.txt b/vendor/gtest/CMakeLists.txt
similarity index 100%
rename from vendor/gtest-1.7.0/CMakeLists.txt
rename to vendor/gtest/CMakeLists.txt
diff --git a/vendor/gtest-1.7.0/CONTRIBUTORS b/vendor/gtest/CONTRIBUTORS
similarity index 100%
rename from vendor/gtest-1.7.0/CONTRIBUTORS
rename to vendor/gtest/CONTRIBUTORS
diff --git a/vendor/gtest-1.7.0/LICENSE b/vendor/gtest/LICENSE
similarity index 100%
rename from vendor/gtest-1.7.0/LICENSE
rename to vendor/gtest/LICENSE
diff --git a/vendor/gtest-1.7.0/README b/vendor/gtest/README
similarity index 100%
rename from vendor/gtest-1.7.0/README
rename to vendor/gtest/README
diff --git a/vendor/gtest-1.7.0/aclocal.m4 b/vendor/gtest/aclocal.m4
similarity index 100%
rename from vendor/gtest-1.7.0/aclocal.m4
rename to vendor/gtest/aclocal.m4
diff --git a/vendor/gtest-1.7.0/build-aux/config.guess b/vendor/gtest/build-aux/config.guess
similarity index 100%
rename from vendor/gtest-1.7.0/build-aux/config.guess
rename to vendor/gtest/build-aux/config.guess
diff --git a/vendor/gtest-1.7.0/build-aux/config.h.in b/vendor/gtest/build-aux/config.h.in
similarity index 100%
rename from vendor/gtest-1.7.0/build-aux/config.h.in
rename to vendor/gtest/build-aux/config.h.in
diff --git a/vendor/gtest-1.7.0/build-aux/config.sub b/vendor/gtest/build-aux/config.sub
similarity index 100%
rename from vendor/gtest-1.7.0/build-aux/config.sub
rename to vendor/gtest/build-aux/config.sub
diff --git a/vendor/gtest-1.7.0/build-aux/depcomp b/vendor/gtest/build-aux/depcomp
similarity index 100%
rename from vendor/gtest-1.7.0/build-aux/depcomp
rename to vendor/gtest/build-aux/depcomp
diff --git a/vendor/gtest-1.7.0/build-aux/install-sh b/vendor/gtest/build-aux/install-sh
similarity index 100%
rename from vendor/gtest-1.7.0/build-aux/install-sh
rename to vendor/gtest/build-aux/install-sh
diff --git a/vendor/gtest-1.7.0/build-aux/ltmain.sh b/vendor/gtest/build-aux/ltmain.sh
similarity index 100%
rename from vendor/gtest-1.7.0/build-aux/ltmain.sh
rename to vendor/gtest/build-aux/ltmain.sh
diff --git a/vendor/gtest-1.7.0/build-aux/missing b/vendor/gtest/build-aux/missing
similarity index 100%
rename from vendor/gtest-1.7.0/build-aux/missing
rename to vendor/gtest/build-aux/missing
diff --git a/vendor/gtest-1.7.0/cmake/internal_utils.cmake b/vendor/gtest/cmake/internal_utils.cmake
similarity index 100%
rename from vendor/gtest-1.7.0/cmake/internal_utils.cmake
rename to vendor/gtest/cmake/internal_utils.cmake
diff --git a/vendor/gtest-1.7.0/codegear/gtest.cbproj b/vendor/gtest/codegear/gtest.cbproj
similarity index 100%
rename from vendor/gtest-1.7.0/codegear/gtest.cbproj
rename to vendor/gtest/codegear/gtest.cbproj
diff --git a/vendor/gtest-1.7.0/codegear/gtest.groupproj b/vendor/gtest/codegear/gtest.groupproj
similarity index 100%
rename from vendor/gtest-1.7.0/codegear/gtest.groupproj
rename to vendor/gtest/codegear/gtest.groupproj
diff --git a/vendor/gtest-1.7.0/codegear/gtest_all.cc b/vendor/gtest/codegear/gtest_all.cc
similarity index 100%
rename from vendor/gtest-1.7.0/codegear/gtest_all.cc
rename to vendor/gtest/codegear/gtest_all.cc
diff --git a/vendor/gtest-1.7.0/codegear/gtest_link.cc b/vendor/gtest/codegear/gtest_link.cc
similarity index 100%
rename from vendor/gtest-1.7.0/codegear/gtest_link.cc
rename to vendor/gtest/codegear/gtest_link.cc
diff --git a/vendor/gtest-1.7.0/codegear/gtest_main.cbproj b/vendor/gtest/codegear/gtest_main.cbproj
similarity index 100%
rename from vendor/gtest-1.7.0/codegear/gtest_main.cbproj
rename to vendor/gtest/codegear/gtest_main.cbproj
diff --git a/vendor/gtest-1.7.0/codegear/gtest_unittest.cbproj b/vendor/gtest/codegear/gtest_unittest.cbproj
similarity index 100%
rename from vendor/gtest-1.7.0/codegear/gtest_unittest.cbproj
rename to vendor/gtest/codegear/gtest_unittest.cbproj
diff --git a/vendor/gtest-1.7.0/configure b/vendor/gtest/configure
similarity index 100%
rename from vendor/gtest-1.7.0/configure
rename to vendor/gtest/configure
diff --git a/vendor/gtest-1.7.0/configure.ac b/vendor/gtest/configure.ac
similarity index 100%
rename from vendor/gtest-1.7.0/configure.ac
rename to vendor/gtest/configure.ac
diff --git a/vendor/gtest-1.7.0/fused-src/gtest/gtest-all.cc b/vendor/gtest/fused-src/gtest/gtest-all.cc
similarity index 100%
rename from vendor/gtest-1.7.0/fused-src/gtest/gtest-all.cc
rename to vendor/gtest/fused-src/gtest/gtest-all.cc
diff --git a/vendor/gtest-1.7.0/fused-src/gtest/gtest.h b/vendor/gtest/fused-src/gtest/gtest.h
similarity index 100%
rename from vendor/gtest-1.7.0/fused-src/gtest/gtest.h
rename to vendor/gtest/fused-src/gtest/gtest.h
diff --git a/vendor/gtest-1.7.0/fused-src/gtest/gtest_main.cc b/vendor/gtest/fused-src/gtest/gtest_main.cc
similarity index 100%
rename from vendor/gtest-1.7.0/fused-src/gtest/gtest_main.cc
rename to vendor/gtest/fused-src/gtest/gtest_main.cc
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest-death-test.h b/vendor/gtest/include/gtest/gtest-death-test.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest-death-test.h
rename to vendor/gtest/include/gtest/gtest-death-test.h
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest-message.h b/vendor/gtest/include/gtest/gtest-message.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest-message.h
rename to vendor/gtest/include/gtest/gtest-message.h
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest-param-test.h b/vendor/gtest/include/gtest/gtest-param-test.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest-param-test.h
rename to vendor/gtest/include/gtest/gtest-param-test.h
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest-param-test.h.pump b/vendor/gtest/include/gtest/gtest-param-test.h.pump
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest-param-test.h.pump
rename to vendor/gtest/include/gtest/gtest-param-test.h.pump
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest-printers.h b/vendor/gtest/include/gtest/gtest-printers.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest-printers.h
rename to vendor/gtest/include/gtest/gtest-printers.h
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest-spi.h b/vendor/gtest/include/gtest/gtest-spi.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest-spi.h
rename to vendor/gtest/include/gtest/gtest-spi.h
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest-test-part.h b/vendor/gtest/include/gtest/gtest-test-part.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest-test-part.h
rename to vendor/gtest/include/gtest/gtest-test-part.h
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest-typed-test.h b/vendor/gtest/include/gtest/gtest-typed-test.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest-typed-test.h
rename to vendor/gtest/include/gtest/gtest-typed-test.h
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest.h b/vendor/gtest/include/gtest/gtest.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest.h
rename to vendor/gtest/include/gtest/gtest.h
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest_pred_impl.h b/vendor/gtest/include/gtest/gtest_pred_impl.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest_pred_impl.h
rename to vendor/gtest/include/gtest/gtest_pred_impl.h
diff --git a/vendor/gtest-1.7.0/include/gtest/gtest_prod.h b/vendor/gtest/include/gtest/gtest_prod.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/gtest_prod.h
rename to vendor/gtest/include/gtest/gtest_prod.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-death-test-internal.h b/vendor/gtest/include/gtest/internal/gtest-death-test-internal.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-death-test-internal.h
rename to vendor/gtest/include/gtest/internal/gtest-death-test-internal.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-filepath.h b/vendor/gtest/include/gtest/internal/gtest-filepath.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-filepath.h
rename to vendor/gtest/include/gtest/internal/gtest-filepath.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-internal.h b/vendor/gtest/include/gtest/internal/gtest-internal.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-internal.h
rename to vendor/gtest/include/gtest/internal/gtest-internal.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-linked_ptr.h b/vendor/gtest/include/gtest/internal/gtest-linked_ptr.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-linked_ptr.h
rename to vendor/gtest/include/gtest/internal/gtest-linked_ptr.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-param-util-generated.h b/vendor/gtest/include/gtest/internal/gtest-param-util-generated.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-param-util-generated.h
rename to vendor/gtest/include/gtest/internal/gtest-param-util-generated.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-param-util-generated.h.pump b/vendor/gtest/include/gtest/internal/gtest-param-util-generated.h.pump
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-param-util-generated.h.pump
rename to vendor/gtest/include/gtest/internal/gtest-param-util-generated.h.pump
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-param-util.h b/vendor/gtest/include/gtest/internal/gtest-param-util.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-param-util.h
rename to vendor/gtest/include/gtest/internal/gtest-param-util.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-port.h b/vendor/gtest/include/gtest/internal/gtest-port.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-port.h
rename to vendor/gtest/include/gtest/internal/gtest-port.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-string.h b/vendor/gtest/include/gtest/internal/gtest-string.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-string.h
rename to vendor/gtest/include/gtest/internal/gtest-string.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-tuple.h b/vendor/gtest/include/gtest/internal/gtest-tuple.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-tuple.h
rename to vendor/gtest/include/gtest/internal/gtest-tuple.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-tuple.h.pump b/vendor/gtest/include/gtest/internal/gtest-tuple.h.pump
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-tuple.h.pump
rename to vendor/gtest/include/gtest/internal/gtest-tuple.h.pump
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-type-util.h b/vendor/gtest/include/gtest/internal/gtest-type-util.h
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-type-util.h
rename to vendor/gtest/include/gtest/internal/gtest-type-util.h
diff --git a/vendor/gtest-1.7.0/include/gtest/internal/gtest-type-util.h.pump b/vendor/gtest/include/gtest/internal/gtest-type-util.h.pump
similarity index 100%
rename from vendor/gtest-1.7.0/include/gtest/internal/gtest-type-util.h.pump
rename to vendor/gtest/include/gtest/internal/gtest-type-util.h.pump
diff --git a/vendor/gtest-1.7.0/m4/acx_pthread.m4 b/vendor/gtest/m4/acx_pthread.m4
similarity index 100%
rename from vendor/gtest-1.7.0/m4/acx_pthread.m4
rename to vendor/gtest/m4/acx_pthread.m4
diff --git a/vendor/gtest-1.7.0/m4/gtest.m4 b/vendor/gtest/m4/gtest.m4
similarity index 100%
rename from vendor/gtest-1.7.0/m4/gtest.m4
rename to vendor/gtest/m4/gtest.m4
diff --git a/vendor/gtest-1.7.0/m4/libtool.m4 b/vendor/gtest/m4/libtool.m4
similarity index 100%
rename from vendor/gtest-1.7.0/m4/libtool.m4
rename to vendor/gtest/m4/libtool.m4
diff --git a/vendor/gtest-1.7.0/m4/ltoptions.m4 b/vendor/gtest/m4/ltoptions.m4
similarity index 100%
rename from vendor/gtest-1.7.0/m4/ltoptions.m4
rename to vendor/gtest/m4/ltoptions.m4
diff --git a/vendor/gtest-1.7.0/m4/ltsugar.m4 b/vendor/gtest/m4/ltsugar.m4
similarity index 100%
rename from vendor/gtest-1.7.0/m4/ltsugar.m4
rename to vendor/gtest/m4/ltsugar.m4
diff --git a/vendor/gtest-1.7.0/m4/ltversion.m4 b/vendor/gtest/m4/ltversion.m4
similarity index 100%
rename from vendor/gtest-1.7.0/m4/ltversion.m4
rename to vendor/gtest/m4/ltversion.m4
diff --git a/vendor/gtest-1.7.0/m4/lt~obsolete.m4 b/vendor/gtest/m4/lt~obsolete.m4
similarity index 100%
rename from vendor/gtest-1.7.0/m4/lt~obsolete.m4
rename to vendor/gtest/m4/lt~obsolete.m4
diff --git a/vendor/gtest-1.7.0/msvc/gtest-md.vcproj b/vendor/gtest/msvc/gtest-md.vcproj
similarity index 100%
rename from vendor/gtest-1.7.0/msvc/gtest-md.vcproj
rename to vendor/gtest/msvc/gtest-md.vcproj
diff --git a/vendor/gtest-1.7.0/msvc/gtest.vcproj b/vendor/gtest/msvc/gtest.vcproj
similarity index 100%
rename from vendor/gtest-1.7.0/msvc/gtest.vcproj
rename to vendor/gtest/msvc/gtest.vcproj
diff --git a/vendor/gtest-1.7.0/msvc/gtest_main-md.vcproj b/vendor/gtest/msvc/gtest_main-md.vcproj
similarity index 100%
rename from vendor/gtest-1.7.0/msvc/gtest_main-md.vcproj
rename to vendor/gtest/msvc/gtest_main-md.vcproj
diff --git a/vendor/gtest-1.7.0/msvc/gtest_main.vcproj b/vendor/gtest/msvc/gtest_main.vcproj
similarity index 100%
rename from vendor/gtest-1.7.0/msvc/gtest_main.vcproj
rename to vendor/gtest/msvc/gtest_main.vcproj
diff --git a/vendor/gtest-1.7.0/msvc/gtest_prod_test-md.vcproj b/vendor/gtest/msvc/gtest_prod_test-md.vcproj
similarity index 100%
rename from vendor/gtest-1.7.0/msvc/gtest_prod_test-md.vcproj
rename to vendor/gtest/msvc/gtest_prod_test-md.vcproj
diff --git a/vendor/gtest-1.7.0/msvc/gtest_prod_test.vcproj b/vendor/gtest/msvc/gtest_prod_test.vcproj
similarity index 100%
rename from vendor/gtest-1.7.0/msvc/gtest_prod_test.vcproj
rename to vendor/gtest/msvc/gtest_prod_test.vcproj
diff --git a/vendor/gtest-1.7.0/msvc/gtest_unittest-md.vcproj b/vendor/gtest/msvc/gtest_unittest-md.vcproj
similarity index 100%
rename from vendor/gtest-1.7.0/msvc/gtest_unittest-md.vcproj
rename to vendor/gtest/msvc/gtest_unittest-md.vcproj
diff --git a/vendor/gtest-1.7.0/msvc/gtest_unittest.vcproj b/vendor/gtest/msvc/gtest_unittest.vcproj
similarity index 100%
rename from vendor/gtest-1.7.0/msvc/gtest_unittest.vcproj
rename to vendor/gtest/msvc/gtest_unittest.vcproj
diff --git a/vendor/gtest-1.7.0/samples/prime_tables.h b/vendor/gtest/samples/prime_tables.h
similarity index 100%
rename from vendor/gtest-1.7.0/samples/prime_tables.h
rename to vendor/gtest/samples/prime_tables.h
diff --git a/vendor/gtest-1.7.0/samples/sample1.cc b/vendor/gtest/samples/sample1.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample1.cc
rename to vendor/gtest/samples/sample1.cc
diff --git a/vendor/gtest-1.7.0/samples/sample1.h b/vendor/gtest/samples/sample1.h
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample1.h
rename to vendor/gtest/samples/sample1.h
diff --git a/vendor/gtest-1.7.0/samples/sample10_unittest.cc b/vendor/gtest/samples/sample10_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample10_unittest.cc
rename to vendor/gtest/samples/sample10_unittest.cc
diff --git a/vendor/gtest-1.7.0/samples/sample1_unittest.cc b/vendor/gtest/samples/sample1_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample1_unittest.cc
rename to vendor/gtest/samples/sample1_unittest.cc
diff --git a/vendor/gtest-1.7.0/samples/sample2.cc b/vendor/gtest/samples/sample2.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample2.cc
rename to vendor/gtest/samples/sample2.cc
diff --git a/vendor/gtest-1.7.0/samples/sample2.h b/vendor/gtest/samples/sample2.h
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample2.h
rename to vendor/gtest/samples/sample2.h
diff --git a/vendor/gtest-1.7.0/samples/sample2_unittest.cc b/vendor/gtest/samples/sample2_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample2_unittest.cc
rename to vendor/gtest/samples/sample2_unittest.cc
diff --git a/vendor/gtest-1.7.0/samples/sample3-inl.h b/vendor/gtest/samples/sample3-inl.h
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample3-inl.h
rename to vendor/gtest/samples/sample3-inl.h
diff --git a/vendor/gtest-1.7.0/samples/sample3_unittest.cc b/vendor/gtest/samples/sample3_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample3_unittest.cc
rename to vendor/gtest/samples/sample3_unittest.cc
diff --git a/vendor/gtest-1.7.0/samples/sample4.cc b/vendor/gtest/samples/sample4.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample4.cc
rename to vendor/gtest/samples/sample4.cc
diff --git a/vendor/gtest-1.7.0/samples/sample4.h b/vendor/gtest/samples/sample4.h
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample4.h
rename to vendor/gtest/samples/sample4.h
diff --git a/vendor/gtest-1.7.0/samples/sample4_unittest.cc b/vendor/gtest/samples/sample4_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample4_unittest.cc
rename to vendor/gtest/samples/sample4_unittest.cc
diff --git a/vendor/gtest-1.7.0/samples/sample5_unittest.cc b/vendor/gtest/samples/sample5_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample5_unittest.cc
rename to vendor/gtest/samples/sample5_unittest.cc
diff --git a/vendor/gtest-1.7.0/samples/sample6_unittest.cc b/vendor/gtest/samples/sample6_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample6_unittest.cc
rename to vendor/gtest/samples/sample6_unittest.cc
diff --git a/vendor/gtest-1.7.0/samples/sample7_unittest.cc b/vendor/gtest/samples/sample7_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample7_unittest.cc
rename to vendor/gtest/samples/sample7_unittest.cc
diff --git a/vendor/gtest-1.7.0/samples/sample8_unittest.cc b/vendor/gtest/samples/sample8_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample8_unittest.cc
rename to vendor/gtest/samples/sample8_unittest.cc
diff --git a/vendor/gtest-1.7.0/samples/sample9_unittest.cc b/vendor/gtest/samples/sample9_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/samples/sample9_unittest.cc
rename to vendor/gtest/samples/sample9_unittest.cc
diff --git a/vendor/gtest-1.7.0/scripts/fuse_gtest_files.py b/vendor/gtest/scripts/fuse_gtest_files.py
similarity index 100%
rename from vendor/gtest-1.7.0/scripts/fuse_gtest_files.py
rename to vendor/gtest/scripts/fuse_gtest_files.py
diff --git a/vendor/gtest-1.7.0/scripts/gen_gtest_pred_impl.py b/vendor/gtest/scripts/gen_gtest_pred_impl.py
similarity index 100%
rename from vendor/gtest-1.7.0/scripts/gen_gtest_pred_impl.py
rename to vendor/gtest/scripts/gen_gtest_pred_impl.py
diff --git a/vendor/gtest-1.7.0/scripts/gtest-config.in b/vendor/gtest/scripts/gtest-config.in
similarity index 100%
rename from vendor/gtest-1.7.0/scripts/gtest-config.in
rename to vendor/gtest/scripts/gtest-config.in
diff --git a/vendor/gtest-1.7.0/scripts/pump.py b/vendor/gtest/scripts/pump.py
similarity index 100%
rename from vendor/gtest-1.7.0/scripts/pump.py
rename to vendor/gtest/scripts/pump.py
diff --git a/vendor/gtest-1.7.0/src/gtest-all.cc b/vendor/gtest/src/gtest-all.cc
similarity index 100%
rename from vendor/gtest-1.7.0/src/gtest-all.cc
rename to vendor/gtest/src/gtest-all.cc
diff --git a/vendor/gtest-1.7.0/src/gtest-death-test.cc b/vendor/gtest/src/gtest-death-test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/src/gtest-death-test.cc
rename to vendor/gtest/src/gtest-death-test.cc
diff --git a/vendor/gtest-1.7.0/src/gtest-filepath.cc b/vendor/gtest/src/gtest-filepath.cc
similarity index 100%
rename from vendor/gtest-1.7.0/src/gtest-filepath.cc
rename to vendor/gtest/src/gtest-filepath.cc
diff --git a/vendor/gtest-1.7.0/src/gtest-internal-inl.h b/vendor/gtest/src/gtest-internal-inl.h
similarity index 100%
rename from vendor/gtest-1.7.0/src/gtest-internal-inl.h
rename to vendor/gtest/src/gtest-internal-inl.h
diff --git a/vendor/gtest-1.7.0/src/gtest-port.cc b/vendor/gtest/src/gtest-port.cc
similarity index 100%
rename from vendor/gtest-1.7.0/src/gtest-port.cc
rename to vendor/gtest/src/gtest-port.cc
diff --git a/vendor/gtest-1.7.0/src/gtest-printers.cc b/vendor/gtest/src/gtest-printers.cc
similarity index 100%
rename from vendor/gtest-1.7.0/src/gtest-printers.cc
rename to vendor/gtest/src/gtest-printers.cc
diff --git a/vendor/gtest-1.7.0/src/gtest-test-part.cc b/vendor/gtest/src/gtest-test-part.cc
similarity index 100%
rename from vendor/gtest-1.7.0/src/gtest-test-part.cc
rename to vendor/gtest/src/gtest-test-part.cc
diff --git a/vendor/gtest-1.7.0/src/gtest-typed-test.cc b/vendor/gtest/src/gtest-typed-test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/src/gtest-typed-test.cc
rename to vendor/gtest/src/gtest-typed-test.cc
diff --git a/vendor/gtest-1.7.0/src/gtest.cc b/vendor/gtest/src/gtest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/src/gtest.cc
rename to vendor/gtest/src/gtest.cc
diff --git a/vendor/gtest-1.7.0/src/gtest_main.cc b/vendor/gtest/src/gtest_main.cc
similarity index 100%
rename from vendor/gtest-1.7.0/src/gtest_main.cc
rename to vendor/gtest/src/gtest_main.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-death-test_ex_test.cc b/vendor/gtest/test/gtest-death-test_ex_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-death-test_ex_test.cc
rename to vendor/gtest/test/gtest-death-test_ex_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-death-test_test.cc b/vendor/gtest/test/gtest-death-test_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-death-test_test.cc
rename to vendor/gtest/test/gtest-death-test_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-filepath_test.cc b/vendor/gtest/test/gtest-filepath_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-filepath_test.cc
rename to vendor/gtest/test/gtest-filepath_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-linked_ptr_test.cc b/vendor/gtest/test/gtest-linked_ptr_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-linked_ptr_test.cc
rename to vendor/gtest/test/gtest-linked_ptr_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-listener_test.cc b/vendor/gtest/test/gtest-listener_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-listener_test.cc
rename to vendor/gtest/test/gtest-listener_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-message_test.cc b/vendor/gtest/test/gtest-message_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-message_test.cc
rename to vendor/gtest/test/gtest-message_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-options_test.cc b/vendor/gtest/test/gtest-options_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-options_test.cc
rename to vendor/gtest/test/gtest-options_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-param-test2_test.cc b/vendor/gtest/test/gtest-param-test2_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-param-test2_test.cc
rename to vendor/gtest/test/gtest-param-test2_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-param-test_test.cc b/vendor/gtest/test/gtest-param-test_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-param-test_test.cc
rename to vendor/gtest/test/gtest-param-test_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-param-test_test.h b/vendor/gtest/test/gtest-param-test_test.h
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-param-test_test.h
rename to vendor/gtest/test/gtest-param-test_test.h
diff --git a/vendor/gtest-1.7.0/test/gtest-port_test.cc b/vendor/gtest/test/gtest-port_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-port_test.cc
rename to vendor/gtest/test/gtest-port_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-printers_test.cc b/vendor/gtest/test/gtest-printers_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-printers_test.cc
rename to vendor/gtest/test/gtest-printers_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-test-part_test.cc b/vendor/gtest/test/gtest-test-part_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-test-part_test.cc
rename to vendor/gtest/test/gtest-test-part_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-tuple_test.cc b/vendor/gtest/test/gtest-tuple_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-tuple_test.cc
rename to vendor/gtest/test/gtest-tuple_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-typed-test2_test.cc b/vendor/gtest/test/gtest-typed-test2_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-typed-test2_test.cc
rename to vendor/gtest/test/gtest-typed-test2_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-typed-test_test.cc b/vendor/gtest/test/gtest-typed-test_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-typed-test_test.cc
rename to vendor/gtest/test/gtest-typed-test_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest-typed-test_test.h b/vendor/gtest/test/gtest-typed-test_test.h
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-typed-test_test.h
rename to vendor/gtest/test/gtest-typed-test_test.h
diff --git a/vendor/gtest-1.7.0/test/gtest-unittest-api_test.cc b/vendor/gtest/test/gtest-unittest-api_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest-unittest-api_test.cc
rename to vendor/gtest/test/gtest-unittest-api_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_all_test.cc b/vendor/gtest/test/gtest_all_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_all_test.cc
rename to vendor/gtest/test/gtest_all_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_break_on_failure_unittest.py b/vendor/gtest/test/gtest_break_on_failure_unittest.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_break_on_failure_unittest.py
rename to vendor/gtest/test/gtest_break_on_failure_unittest.py
diff --git a/vendor/gtest-1.7.0/test/gtest_break_on_failure_unittest_.cc b/vendor/gtest/test/gtest_break_on_failure_unittest_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_break_on_failure_unittest_.cc
rename to vendor/gtest/test/gtest_break_on_failure_unittest_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_catch_exceptions_test.py b/vendor/gtest/test/gtest_catch_exceptions_test.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_catch_exceptions_test.py
rename to vendor/gtest/test/gtest_catch_exceptions_test.py
diff --git a/vendor/gtest-1.7.0/test/gtest_catch_exceptions_test_.cc b/vendor/gtest/test/gtest_catch_exceptions_test_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_catch_exceptions_test_.cc
rename to vendor/gtest/test/gtest_catch_exceptions_test_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_color_test.py b/vendor/gtest/test/gtest_color_test.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_color_test.py
rename to vendor/gtest/test/gtest_color_test.py
diff --git a/vendor/gtest-1.7.0/test/gtest_color_test_.cc b/vendor/gtest/test/gtest_color_test_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_color_test_.cc
rename to vendor/gtest/test/gtest_color_test_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_env_var_test.py b/vendor/gtest/test/gtest_env_var_test.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_env_var_test.py
rename to vendor/gtest/test/gtest_env_var_test.py
diff --git a/vendor/gtest-1.7.0/test/gtest_env_var_test_.cc b/vendor/gtest/test/gtest_env_var_test_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_env_var_test_.cc
rename to vendor/gtest/test/gtest_env_var_test_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_environment_test.cc b/vendor/gtest/test/gtest_environment_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_environment_test.cc
rename to vendor/gtest/test/gtest_environment_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_filter_unittest.py b/vendor/gtest/test/gtest_filter_unittest.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_filter_unittest.py
rename to vendor/gtest/test/gtest_filter_unittest.py
diff --git a/vendor/gtest-1.7.0/test/gtest_filter_unittest_.cc b/vendor/gtest/test/gtest_filter_unittest_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_filter_unittest_.cc
rename to vendor/gtest/test/gtest_filter_unittest_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_help_test.py b/vendor/gtest/test/gtest_help_test.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_help_test.py
rename to vendor/gtest/test/gtest_help_test.py
diff --git a/vendor/gtest-1.7.0/test/gtest_help_test_.cc b/vendor/gtest/test/gtest_help_test_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_help_test_.cc
rename to vendor/gtest/test/gtest_help_test_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_list_tests_unittest.py b/vendor/gtest/test/gtest_list_tests_unittest.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_list_tests_unittest.py
rename to vendor/gtest/test/gtest_list_tests_unittest.py
diff --git a/vendor/gtest-1.7.0/test/gtest_list_tests_unittest_.cc b/vendor/gtest/test/gtest_list_tests_unittest_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_list_tests_unittest_.cc
rename to vendor/gtest/test/gtest_list_tests_unittest_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_main_unittest.cc b/vendor/gtest/test/gtest_main_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_main_unittest.cc
rename to vendor/gtest/test/gtest_main_unittest.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_no_test_unittest.cc b/vendor/gtest/test/gtest_no_test_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_no_test_unittest.cc
rename to vendor/gtest/test/gtest_no_test_unittest.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_output_test.py b/vendor/gtest/test/gtest_output_test.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_output_test.py
rename to vendor/gtest/test/gtest_output_test.py
diff --git a/vendor/gtest-1.7.0/test/gtest_output_test_.cc b/vendor/gtest/test/gtest_output_test_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_output_test_.cc
rename to vendor/gtest/test/gtest_output_test_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_output_test_golden_lin.txt b/vendor/gtest/test/gtest_output_test_golden_lin.txt
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_output_test_golden_lin.txt
rename to vendor/gtest/test/gtest_output_test_golden_lin.txt
diff --git a/vendor/gtest-1.7.0/test/gtest_pred_impl_unittest.cc b/vendor/gtest/test/gtest_pred_impl_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_pred_impl_unittest.cc
rename to vendor/gtest/test/gtest_pred_impl_unittest.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_premature_exit_test.cc b/vendor/gtest/test/gtest_premature_exit_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_premature_exit_test.cc
rename to vendor/gtest/test/gtest_premature_exit_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_prod_test.cc b/vendor/gtest/test/gtest_prod_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_prod_test.cc
rename to vendor/gtest/test/gtest_prod_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_repeat_test.cc b/vendor/gtest/test/gtest_repeat_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_repeat_test.cc
rename to vendor/gtest/test/gtest_repeat_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_shuffle_test.py b/vendor/gtest/test/gtest_shuffle_test.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_shuffle_test.py
rename to vendor/gtest/test/gtest_shuffle_test.py
diff --git a/vendor/gtest-1.7.0/test/gtest_shuffle_test_.cc b/vendor/gtest/test/gtest_shuffle_test_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_shuffle_test_.cc
rename to vendor/gtest/test/gtest_shuffle_test_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_sole_header_test.cc b/vendor/gtest/test/gtest_sole_header_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_sole_header_test.cc
rename to vendor/gtest/test/gtest_sole_header_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_stress_test.cc b/vendor/gtest/test/gtest_stress_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_stress_test.cc
rename to vendor/gtest/test/gtest_stress_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_test_utils.py b/vendor/gtest/test/gtest_test_utils.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_test_utils.py
rename to vendor/gtest/test/gtest_test_utils.py
diff --git a/vendor/gtest-1.7.0/test/gtest_throw_on_failure_ex_test.cc b/vendor/gtest/test/gtest_throw_on_failure_ex_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_throw_on_failure_ex_test.cc
rename to vendor/gtest/test/gtest_throw_on_failure_ex_test.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_throw_on_failure_test.py b/vendor/gtest/test/gtest_throw_on_failure_test.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_throw_on_failure_test.py
rename to vendor/gtest/test/gtest_throw_on_failure_test.py
diff --git a/vendor/gtest-1.7.0/test/gtest_throw_on_failure_test_.cc b/vendor/gtest/test/gtest_throw_on_failure_test_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_throw_on_failure_test_.cc
rename to vendor/gtest/test/gtest_throw_on_failure_test_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_uninitialized_test.py b/vendor/gtest/test/gtest_uninitialized_test.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_uninitialized_test.py
rename to vendor/gtest/test/gtest_uninitialized_test.py
diff --git a/vendor/gtest-1.7.0/test/gtest_uninitialized_test_.cc b/vendor/gtest/test/gtest_uninitialized_test_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_uninitialized_test_.cc
rename to vendor/gtest/test/gtest_uninitialized_test_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_unittest.cc b/vendor/gtest/test/gtest_unittest.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_unittest.cc
rename to vendor/gtest/test/gtest_unittest.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_xml_outfile1_test_.cc b/vendor/gtest/test/gtest_xml_outfile1_test_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_xml_outfile1_test_.cc
rename to vendor/gtest/test/gtest_xml_outfile1_test_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_xml_outfile2_test_.cc b/vendor/gtest/test/gtest_xml_outfile2_test_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_xml_outfile2_test_.cc
rename to vendor/gtest/test/gtest_xml_outfile2_test_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_xml_outfiles_test.py b/vendor/gtest/test/gtest_xml_outfiles_test.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_xml_outfiles_test.py
rename to vendor/gtest/test/gtest_xml_outfiles_test.py
diff --git a/vendor/gtest-1.7.0/test/gtest_xml_output_unittest.py b/vendor/gtest/test/gtest_xml_output_unittest.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_xml_output_unittest.py
rename to vendor/gtest/test/gtest_xml_output_unittest.py
diff --git a/vendor/gtest-1.7.0/test/gtest_xml_output_unittest_.cc b/vendor/gtest/test/gtest_xml_output_unittest_.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_xml_output_unittest_.cc
rename to vendor/gtest/test/gtest_xml_output_unittest_.cc
diff --git a/vendor/gtest-1.7.0/test/gtest_xml_test_utils.py b/vendor/gtest/test/gtest_xml_test_utils.py
similarity index 100%
rename from vendor/gtest-1.7.0/test/gtest_xml_test_utils.py
rename to vendor/gtest/test/gtest_xml_test_utils.py
diff --git a/vendor/gtest-1.7.0/test/production.cc b/vendor/gtest/test/production.cc
similarity index 100%
rename from vendor/gtest-1.7.0/test/production.cc
rename to vendor/gtest/test/production.cc
diff --git a/vendor/gtest-1.7.0/test/production.h b/vendor/gtest/test/production.h
similarity index 100%
rename from vendor/gtest-1.7.0/test/production.h
rename to vendor/gtest/test/production.h
diff --git a/vendor/gtest-1.7.0/xcode/Config/DebugProject.xcconfig b/vendor/gtest/xcode/Config/DebugProject.xcconfig
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Config/DebugProject.xcconfig
rename to vendor/gtest/xcode/Config/DebugProject.xcconfig
diff --git a/vendor/gtest-1.7.0/xcode/Config/FrameworkTarget.xcconfig b/vendor/gtest/xcode/Config/FrameworkTarget.xcconfig
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Config/FrameworkTarget.xcconfig
rename to vendor/gtest/xcode/Config/FrameworkTarget.xcconfig
diff --git a/vendor/gtest-1.7.0/xcode/Config/General.xcconfig b/vendor/gtest/xcode/Config/General.xcconfig
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Config/General.xcconfig
rename to vendor/gtest/xcode/Config/General.xcconfig
diff --git a/vendor/gtest-1.7.0/xcode/Config/ReleaseProject.xcconfig b/vendor/gtest/xcode/Config/ReleaseProject.xcconfig
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Config/ReleaseProject.xcconfig
rename to vendor/gtest/xcode/Config/ReleaseProject.xcconfig
diff --git a/vendor/gtest-1.7.0/xcode/Config/StaticLibraryTarget.xcconfig b/vendor/gtest/xcode/Config/StaticLibraryTarget.xcconfig
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Config/StaticLibraryTarget.xcconfig
rename to vendor/gtest/xcode/Config/StaticLibraryTarget.xcconfig
diff --git a/vendor/gtest-1.7.0/xcode/Config/TestTarget.xcconfig b/vendor/gtest/xcode/Config/TestTarget.xcconfig
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Config/TestTarget.xcconfig
rename to vendor/gtest/xcode/Config/TestTarget.xcconfig
diff --git a/vendor/gtest-1.7.0/xcode/Resources/Info.plist b/vendor/gtest/xcode/Resources/Info.plist
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Resources/Info.plist
rename to vendor/gtest/xcode/Resources/Info.plist
diff --git a/vendor/gtest-1.7.0/xcode/Samples/FrameworkSample/Info.plist b/vendor/gtest/xcode/Samples/FrameworkSample/Info.plist
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Samples/FrameworkSample/Info.plist
rename to vendor/gtest/xcode/Samples/FrameworkSample/Info.plist
diff --git a/vendor/gtest-1.7.0/xcode/Samples/FrameworkSample/runtests.sh b/vendor/gtest/xcode/Samples/FrameworkSample/runtests.sh
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Samples/FrameworkSample/runtests.sh
rename to vendor/gtest/xcode/Samples/FrameworkSample/runtests.sh
diff --git a/vendor/gtest-1.7.0/xcode/Samples/FrameworkSample/widget.cc b/vendor/gtest/xcode/Samples/FrameworkSample/widget.cc
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Samples/FrameworkSample/widget.cc
rename to vendor/gtest/xcode/Samples/FrameworkSample/widget.cc
diff --git a/vendor/gtest-1.7.0/xcode/Samples/FrameworkSample/widget.h b/vendor/gtest/xcode/Samples/FrameworkSample/widget.h
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Samples/FrameworkSample/widget.h
rename to vendor/gtest/xcode/Samples/FrameworkSample/widget.h
diff --git a/vendor/gtest-1.7.0/xcode/Samples/FrameworkSample/widget_test.cc b/vendor/gtest/xcode/Samples/FrameworkSample/widget_test.cc
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Samples/FrameworkSample/widget_test.cc
rename to vendor/gtest/xcode/Samples/FrameworkSample/widget_test.cc
diff --git a/vendor/gtest-1.7.0/xcode/Scripts/runtests.sh b/vendor/gtest/xcode/Scripts/runtests.sh
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Scripts/runtests.sh
rename to vendor/gtest/xcode/Scripts/runtests.sh
diff --git a/vendor/gtest-1.7.0/xcode/Scripts/versiongenerate.py b/vendor/gtest/xcode/Scripts/versiongenerate.py
similarity index 100%
rename from vendor/gtest-1.7.0/xcode/Scripts/versiongenerate.py
rename to vendor/gtest/xcode/Scripts/versiongenerate.py
diff --git a/vendor/jsoncpp-1.6.2/dist/CMakeLists.txt b/vendor/jsoncpp-1.6.2/dist/CMakeLists.txt
deleted file mode 100644
index 50c00a8..0000000
--- a/vendor/jsoncpp-1.6.2/dist/CMakeLists.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Make sure we don't attempt to add a library more than once
-#
-get_property(EXISTS GLOBAL PROPERTY _PDALJSONCPP_INCLUDED)
-if (EXISTS)
-    return()
-endif()
-
-file(GLOB PDAL_JSONCPP_SOURCES
-    "jsoncpp.cpp"
-)
-
-if(UNIX)
-  add_definitions("-fPIC")
-endif()
-
-
-PDAL_ADD_LIBRARY(${PDAL_JSONCPP_LIB_NAME} STATIC "${PDAL_JSONCPP_SOURCES}")
-
-set_target_properties(${PDAL_JSONCPP_LIB_NAME} PROPERTIES
-    VERSION "${PDAL_BUILD_VERSION}"
-    SOVERSION "${PDAL_API_VERSION}"
-    CLEAN_DIRECT_OUTPUT 1)
-
-set_property(GLOBAL PROPERTY _PDALJSONCPP_INCLUDED TRUE)
-
diff --git a/vendor/jsoncpp/dist/CMakeLists.txt b/vendor/jsoncpp/dist/CMakeLists.txt
new file mode 100644
index 0000000..cafc229
--- /dev/null
+++ b/vendor/jsoncpp/dist/CMakeLists.txt
@@ -0,0 +1,34 @@
+#
+# Make sure we don't attempt to add a library more than once
+#
+get_property(EXISTS GLOBAL PROPERTY _PDALJSONCPP_INCLUDED)
+if (EXISTS)
+    return()
+endif()
+
+file(GLOB PDAL_JSONCPP_SOURCES
+    "jsoncpp.cpp"
+)
+
+PDAL_ADD_FREE_LIBRARY(${PDAL_JSONCPP_LIB_NAME} STATIC "${PDAL_JSONCPP_SOURCES}")
+
+if (UNIX)
+    target_compile_options(${PDAL_JSONCPP_LIB_NAME} PRIVATE -fPIC)
+endif()
+
+#
+# This forces dll_export on the symbols so that they're available from any
+# DLL that the JSON library gets linked with.
+#
+if (WIN32)
+    target_compile_options(${PDAL_JSONCPP_LIB_NAME} PRIVATE -DJSON_DLL_BUILD)
+endif()
+
+
+set_target_properties(${PDAL_JSONCPP_LIB_NAME} PROPERTIES
+    VERSION "${PDAL_BUILD_VERSION}"
+    SOVERSION "${PDAL_API_VERSION}"
+    CLEAN_DIRECT_OUTPUT 1)
+
+set_property(GLOBAL PROPERTY _PDALJSONCPP_INCLUDED TRUE)
+
diff --git a/vendor/jsoncpp-1.6.2/dist/json/forwards.h b/vendor/jsoncpp/dist/json/forwards.h
similarity index 100%
rename from vendor/jsoncpp-1.6.2/dist/json/forwards.h
rename to vendor/jsoncpp/dist/json/forwards.h
diff --git a/vendor/jsoncpp-1.6.2/dist/json/json.h b/vendor/jsoncpp/dist/json/json.h
similarity index 100%
rename from vendor/jsoncpp-1.6.2/dist/json/json.h
rename to vendor/jsoncpp/dist/json/json.h
diff --git a/vendor/jsoncpp-1.6.2/dist/jsoncpp.cpp b/vendor/jsoncpp/dist/jsoncpp.cpp
similarity index 100%
rename from vendor/jsoncpp-1.6.2/dist/jsoncpp.cpp
rename to vendor/jsoncpp/dist/jsoncpp.cpp
diff --git a/vendor/nanoflann-1.1.8/nanoflann.hpp b/vendor/nanoflann/nanoflann.hpp
similarity index 100%
rename from vendor/nanoflann-1.1.8/nanoflann.hpp
rename to vendor/nanoflann/nanoflann.hpp
diff --git a/vendor/pdalboost/CMakeLists.txt b/vendor/pdalboost/CMakeLists.txt
index a586fc4..7786581 100644
--- a/vendor/pdalboost/CMakeLists.txt
+++ b/vendor/pdalboost/CMakeLists.txt
@@ -25,12 +25,13 @@ endif()
 
 add_definitions(-DBOOST_SYSTEM_NO_DEPRECATED)
 
-PDAL_ADD_LIBRARY(${PDAL_BOOST_LIB_NAME} STATIC "${PDAL_BOOST_SOURCES}")
- 
+PDAL_ADD_FREE_LIBRARY(${PDAL_BOOST_LIB_NAME} STATIC "${PDAL_BOOST_SOURCES}")
 set_target_properties(${PDAL_BOOST_LIB_NAME} PROPERTIES
     VERSION "${PDAL_BUILD_VERSION}"
     SOVERSION "${PDAL_API_VERSION}"
     CLEAN_DIRECT_OUTPUT 1)
+target_include_directories(${PDAL_BOOST_LIB_NAME} PRIVATE
+    ${PDAL_VENDOR_DIR}/pdalboost)
 
 set_property(GLOBAL PROPERTY _PDALBOOST_INCLUDED TRUE)
 
diff --git a/vendor/pdalboost/boost/format/detail/compat_workarounds.hpp b/vendor/pdalboost/boost/format/detail/compat_workarounds.hpp
index 89e3df1..e39e347 100644
--- a/vendor/pdalboost/boost/format/detail/compat_workarounds.hpp
+++ b/vendor/pdalboost/boost/format/detail/compat_workarounds.hpp
@@ -17,7 +17,7 @@
 //  and compiler-specific switches)
 
 // Non-conformant Std-libs fail to supply conformant traits (std::char_traits,
-//  std::allocator) and/or  the std::string doesnt support them.
+//  std::allocator) and/or  the std::string doesn't support them.
 // We don't want to have hundreds of #ifdef workarounds, so we define 
 // replacement traits.
 // But both char_traits and allocator traits are visible in the interface, 
diff --git a/vendor/pdalboost/boost/format/detail/config_macros.hpp b/vendor/pdalboost/boost/format/detail/config_macros.hpp
index 55f0762..55218b3 100644
--- a/vendor/pdalboost/boost/format/detail/config_macros.hpp
+++ b/vendor/pdalboost/boost/format/detail/config_macros.hpp
@@ -79,7 +79,7 @@ namespace pdalboost {
 #endif
 
 
-// ***  hide std::locale if it doesnt exist. 
+// ***  hide std::locale if it doesn't exist. 
 // this typedef is either std::locale or int, avoids placing ifdefs everywhere
 namespace pdalboost { namespace io { namespace detail {
 #if ! defined(BOOST_NO_STD_LOCALE)
diff --git a/vendor/pdalboost/boost/format/detail/workarounds_gcc-2_95.hpp b/vendor/pdalboost/boost/format/detail/workarounds_gcc-2_95.hpp
index 095df19..42d46c3 100644
--- a/vendor/pdalboost/boost/format/detail/workarounds_gcc-2_95.hpp
+++ b/vendor/pdalboost/boost/format/detail/workarounds_gcc-2_95.hpp
@@ -29,7 +29,7 @@
 #ifndef BOOST_FORMAT_WORKAROUNDS_GCC295_H
 #define BOOST_FORMAT_WORKAROUNDS_GCC295_H
 
-// SGI STL doesnt have <ostream> and others, so we need iostream.
+// SGI STL doesn't have <ostream> and others, so we need iostream.
 #include <iostream> 
 #define BOOST_FORMAT_OSTREAM_DEFINED
 
diff --git a/vendor/pdalboost/boost/mpl/for_each.hpp b/vendor/pdalboost/boost/mpl/for_each.hpp
index b3c9c6e..847f738 100644
--- a/vendor/pdalboost/boost/mpl/for_each.hpp
+++ b/vendor/pdalboost/boost/mpl/for_each.hpp
@@ -113,7 +113,7 @@ BOOST_MPL_CFG_GPU_ENABLED
 inline
 void for_each(F f, Sequence* = 0)
 {
-  // jfalcou: fully qualifying this call so it doesnt clash with pdalboostphoenix::for_each
+  // jfalcou: fully qualifying this call so it doesn't clash with pdalboostphoenix::for_each
   // ons ome compilers -- done on 02/28/2011
   pdalboost::mpl::for_each<Sequence, identity<> >(f);
 }
diff --git a/vendor/rply-1.1.4/LICENSE b/vendor/rply/LICENSE
similarity index 100%
rename from vendor/rply-1.1.4/LICENSE
rename to vendor/rply/LICENSE
diff --git a/vendor/rply-1.1.4/etc/convert.c b/vendor/rply/etc/convert.c
similarity index 100%
rename from vendor/rply-1.1.4/etc/convert.c
rename to vendor/rply/etc/convert.c
diff --git a/vendor/rply-1.1.4/etc/dump.c b/vendor/rply/etc/dump.c
similarity index 100%
rename from vendor/rply-1.1.4/etc/dump.c
rename to vendor/rply/etc/dump.c
diff --git a/vendor/rply-1.1.4/etc/input.ply b/vendor/rply/etc/input.ply
similarity index 100%
rename from vendor/rply-1.1.4/etc/input.ply
rename to vendor/rply/etc/input.ply
diff --git a/vendor/rply-1.1.4/etc/sconvert.c b/vendor/rply/etc/sconvert.c
similarity index 100%
rename from vendor/rply-1.1.4/etc/sconvert.c
rename to vendor/rply/etc/sconvert.c
diff --git a/vendor/rply-1.1.4/manual/manual.html b/vendor/rply/manual/manual.html
similarity index 100%
rename from vendor/rply-1.1.4/manual/manual.html
rename to vendor/rply/manual/manual.html
diff --git a/vendor/rply-1.1.4/manual/reference.css b/vendor/rply/manual/reference.css
similarity index 100%
rename from vendor/rply-1.1.4/manual/reference.css
rename to vendor/rply/manual/reference.css
diff --git a/vendor/rply-1.1.4/manual/rply.png b/vendor/rply/manual/rply.png
similarity index 100%
rename from vendor/rply-1.1.4/manual/rply.png
rename to vendor/rply/manual/rply.png
diff --git a/vendor/rply-1.1.4/rply.c b/vendor/rply/rply.c
similarity index 100%
rename from vendor/rply-1.1.4/rply.c
rename to vendor/rply/rply.c
diff --git a/vendor/rply-1.1.4/rply.h b/vendor/rply/rply.h
similarity index 100%
rename from vendor/rply-1.1.4/rply.h
rename to vendor/rply/rply.h
diff --git a/vendor/rply-1.1.4/rplyfile.h b/vendor/rply/rplyfile.h
similarity index 100%
rename from vendor/rply-1.1.4/rplyfile.h
rename to vendor/rply/rplyfile.h

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/pdal.git



More information about the Pkg-grass-devel mailing list