mirror of
https://github.com/JHUAPL/pubgeo.git
synced 2026-01-08 22:18:11 -05:00
Pubgeo first release. Enjoy.
This commit is contained in:
28
CMakeLists.txt
Normal file
28
CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.0.2)
|
||||
PROJECT(pubgeo)
|
||||
|
||||
SET(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
IF(WIN32)
|
||||
MESSAGE(ERROR "Windows builds are currently not working due to erroneous linking errors.")
|
||||
# Default install directory
|
||||
MESSAGE(WARNING "Advance INSTALL OSGeo4W64. Include: (Command Line Tools) pdal and its dependencies. Potentially requires change to PDALConfig.cmake.")
|
||||
SET(CMAKE_PREFIX_PATH "C:\\OSGeo4W64\\")
|
||||
|
||||
# This part is for auto-defines in windows libraries that cause macro errors in our code
|
||||
add_definitions(-DWIN32_LEAN_AND_MEAN -DNOMINMAX)
|
||||
ENDIF()
|
||||
|
||||
FIND_PACKAGE(GDAL 1.9.0 REQUIRED)# Using Open Source Geo for Windows installer
|
||||
FIND_PACKAGE(PDAL 1.4.0 REQUIRED)
|
||||
|
||||
# This makes linking/including a little cleaner looking later
|
||||
SET(DAL_LIBS ${GDAL_LIBRARY} ${PDAL_LIBRARIES})
|
||||
SET(DAL_INCLUDE_DIRS ${GDAL_INCLUDE_DIR} ${PDAL_INCLUDE_DIRS})
|
||||
MESSAGE(STATUS "${DAL_INCLUDE_DIRS}")
|
||||
|
||||
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
ADD_SUBDIRECTORY(src/common)
|
||||
ADD_SUBDIRECTORY(src/align3d)
|
||||
ADD_SUBDIRECTORY(src/shr3d)
|
||||
18
DISTRIBUTION
Normal file
18
DISTRIBUTION
Normal file
@@ -0,0 +1,18 @@
|
||||
Distribution statement:
|
||||
Approved for Public Release; Distribution is Unlimited
|
||||
|
||||
|
||||
Please reference the following when reporting any results using this software:
|
||||
|
||||
M. Bosch, A. Leichtman, D. Chilcott, H. Goldberg, M. Brown, “Metric Evaluation Pipeline for 3D Modeling of Urban Scenes,” ISPRS Archives, 2017.
|
||||
|
||||
S. Almes, S. Hagstrom, D. Chilcott, H. Goldberg, M. Brown, “Open Source Geospatial Tools to Enable Large Scale 3D Scene Modeling,” FOSS4G, 2017.
|
||||
|
||||
For more information, please see: http://www.jhuapl.edu/pubgeo.html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
32
Dockerfile
Normal file
32
Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM ubuntu:17.04
|
||||
MAINTAINER JHUAPL <pubgeo@jhuapl.edu>
|
||||
|
||||
RUN apt update && apt upgrade -y && apt install -y --fix-missing --no-install-recommends\
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
cmake \
|
||||
curl \
|
||||
gdal-bin \
|
||||
git \
|
||||
libgdal-dev \
|
||||
libpdal-dev \
|
||||
pdal \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN git clone https://github.com/pubgeo/pubgeo
|
||||
|
||||
# Make a directory to work out of, and change to it
|
||||
WORKDIR /build
|
||||
RUN cmake ../pubgeo && make -j 10
|
||||
|
||||
# cleanup
|
||||
RUN rm -rf /pubgeo
|
||||
RUN apt purge -y \
|
||||
build-essential \
|
||||
libgdal-dev \
|
||||
libpdal-dev \
|
||||
cmake \
|
||||
git
|
||||
CMD echo "Please run a valid executable:" && \
|
||||
echo "docker run -v <path to 3D data> shr3d <3D file> DH=2 DZ=1 AGL=2 AREA=50" && \
|
||||
echo "docker run -v <path to point cloud> align3d <reference point cloud> <target pc> gsd=1.0 maxt=10.0"
|
||||
22
LICENSE
22
LICENSE
@@ -1,21 +1,7 @@
|
||||
MIT License
|
||||
Copyright 2016 The Johns Hopkins University Applied Physics Laboratory
|
||||
|
||||
Copyright (c) 2017 pubgeo
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
28
src/align3d/CMakeLists.txt
Normal file
28
src/align3d/CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
SET(ALIGN3D_HEADER_FILES
|
||||
align3d.h)
|
||||
|
||||
SET(ALIGN3D_SOURCE_FILES
|
||||
align3d.cpp)
|
||||
|
||||
ADD_LIBRARY(ALIGN3D_LIB STATIC ${ALIGN3D_HEADER_FILES} ${ALIGN3D_SOURCE_FILES})
|
||||
TARGET_INCLUDE_DIRECTORIES(ALIGN3D_LIB
|
||||
PUBLIC
|
||||
PUBGEO_LIB)
|
||||
|
||||
TARGET_LINK_LIBRARIES(ALIGN3D_LIB
|
||||
PUBLIC
|
||||
PUBGEO_LIB)
|
||||
|
||||
### Add some executables to play with.
|
||||
### Main exe will showcase all features
|
||||
ADD_EXECUTABLE(align3d main.cpp)
|
||||
TARGET_LINK_LIBRARIES(align3d
|
||||
PUBLIC
|
||||
ALIGN3D_LIB)
|
||||
|
||||
if(TESTING)
|
||||
ADD_EXECUTABLE(align3dTest test-main.cpp)
|
||||
TARGET_LINK_LIBRARIES(align3dTest
|
||||
PUBLIC
|
||||
ALIGN3D_LIB)
|
||||
endif()
|
||||
261
src/align3d/align3d.cpp
Normal file
261
src/align3d/align3d.cpp
Normal file
@@ -0,0 +1,261 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include "align3d.h"
|
||||
|
||||
namespace align3d {
|
||||
bool computeRMS(float dx, float dy, long numSamples, long maxSamples, std::vector<double> &xlist,
|
||||
std::vector<double> &ylist, OrthoImage<unsigned short> &referenceDSM,
|
||||
OrthoImage<unsigned short> &targetDSM, float &medianDZ, float &rms, long &ndx,
|
||||
float &completeness) {
|
||||
// Loop on number of samples and compute RMS.
|
||||
// Just in case there aren't many valid points, don't let this loop forever.
|
||||
long count = 0;
|
||||
ndx = 0;
|
||||
std::vector<float> differences;
|
||||
while ((count < numSamples) && (ndx < maxSamples)) {
|
||||
// Get the next point for matching.
|
||||
double x = xlist[ndx];
|
||||
double y = ylist[ndx];
|
||||
ndx++;
|
||||
|
||||
// Map the point into the target.
|
||||
// Skip if this isn't a valid point.
|
||||
long col = long((x - targetDSM.easting + dx) / targetDSM.gsd + 0.5);
|
||||
long row = targetDSM.height - 1 - long((y - targetDSM.northing + dy) / targetDSM.gsd + 0.5);
|
||||
if (col <= 0) continue;
|
||||
if (row <= 0) continue;
|
||||
if (col >= targetDSM.width - 1) continue;
|
||||
if (row >= targetDSM.height - 1) continue;
|
||||
if (targetDSM.data[row][col] == 0) continue;
|
||||
float targetZ = float(targetDSM.data[row][col]) * targetDSM.scale + targetDSM.offset;
|
||||
|
||||
// Map the point into the reference.
|
||||
// Skip if this isn't a valid point.
|
||||
col = long((x - referenceDSM.easting) / referenceDSM.gsd + 0.5);
|
||||
row = referenceDSM.height - 1 - long((y - referenceDSM.northing) / referenceDSM.gsd + 0.5);
|
||||
if (col <= 0) continue;
|
||||
if (row <= 0) continue;
|
||||
if (col >= referenceDSM.width - 1) continue;
|
||||
if (row >= referenceDSM.height - 1) continue;
|
||||
if (referenceDSM.data[row][col] == 0) continue;
|
||||
float referenceZ = float(referenceDSM.data[row][col]) * referenceDSM.scale + referenceDSM.offset;
|
||||
|
||||
// Keep going until we have enough points.
|
||||
float difference = referenceZ - targetZ;
|
||||
differences.push_back(difference);
|
||||
count++;
|
||||
}
|
||||
|
||||
// Skip if not enough sampled points.
|
||||
if (count < numSamples) return false;
|
||||
|
||||
// Compute median Z offset and a robust estimate of the RMS difference.
|
||||
rms = 0.0;
|
||||
sort(differences.begin(), differences.end());
|
||||
medianDZ = differences[count / 2];
|
||||
for (long k = 0; k < count; k++) {
|
||||
differences[k] = fabs(differences[k] - medianDZ);
|
||||
}
|
||||
sort(differences.begin(), differences.end());
|
||||
rms = differences[(long) (count * 0.67)];
|
||||
|
||||
// Compute the completeness.
|
||||
long good = 0;
|
||||
for (long k = 0; k < count; ++k) {
|
||||
if (differences[k] < 1.0) ++good;
|
||||
}
|
||||
completeness = good / (float) numSamples;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Estimate 3D rigid body transform parameters to align target points with reference.
|
||||
void EstimateRigidBody(OrthoImage<unsigned short> &referenceDSM, OrthoImage<unsigned short> &targetDSM, float maxt,
|
||||
AlignBounds &bounds, AlignResult &result) {
|
||||
float step = MIN(referenceDSM.gsd, targetDSM.gsd);
|
||||
long numSamples = 10000;
|
||||
long maxSamples = numSamples * 10;
|
||||
|
||||
// Allocate RMS array.
|
||||
long bins = long(maxt / step * 2) + 1;
|
||||
float **rmsArray = new float *[bins];
|
||||
for (long i = 0; i < bins; i++) rmsArray[i] = new float[bins];
|
||||
|
||||
// Get random samples.
|
||||
srand(0);
|
||||
long seed = 0;
|
||||
long count = 0;
|
||||
std::vector<double> xlist;
|
||||
std::vector<double> ylist;
|
||||
for (long i = 0; i < maxSamples; i++) {
|
||||
// Get a random point in the overlap area.
|
||||
double x = bounds.xmin + ((rand() / (double) RAND_MAX) * bounds.width);
|
||||
double y = bounds.ymin + ((rand() / (double) RAND_MAX) * bounds.height);
|
||||
xlist.push_back(x);
|
||||
ylist.push_back(y);
|
||||
}
|
||||
|
||||
// Start with brute force, but sample points to reduce timeline.
|
||||
// Loop on X and Y translation within search distance.
|
||||
float threshold = MAX_FLOAT;
|
||||
float bestDX = 0.0;
|
||||
float bestDY = 0.0;
|
||||
float bestDZ = 0.0;
|
||||
long besti = 0;
|
||||
long bestj = 0;
|
||||
float bestRMS = MAX_FLOAT;
|
||||
float medianDZ = 0.0;
|
||||
bestRMS = threshold;
|
||||
float bestCompleteness = 0.0;
|
||||
long numSampled = 0;
|
||||
for (long i = 0; i < bins; i++) {
|
||||
float dx = -maxt + i * step;
|
||||
for (long j = 0; j < bins; j++) {
|
||||
float dy = -maxt + j * step;
|
||||
rmsArray[i][j] = 0.0;
|
||||
float rms = 0.0;
|
||||
float completeness = 0.0;
|
||||
bool ok = computeRMS(dx, dy, numSamples, maxSamples, xlist, ylist, referenceDSM, targetDSM, medianDZ,
|
||||
rms, numSampled, completeness);
|
||||
if (!ok) continue;
|
||||
rmsArray[i][j] = rms;
|
||||
if (rms < bestRMS) {
|
||||
bestCompleteness = completeness;
|
||||
|
||||
bestRMS = rms;
|
||||
bestDX = dx;
|
||||
bestDY = dy;
|
||||
bestDZ = medianDZ;
|
||||
besti = i;
|
||||
bestj = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply quadratic interpolation to localize the peak.
|
||||
if ((besti > 0) && (besti < bins - 1) && (bestj > 0) && (bestj < bins - 1)) {
|
||||
float dx = (rmsArray[besti + 1][bestj] - rmsArray[besti - 1][bestj]) / 2.f;
|
||||
float dy = (rmsArray[besti][bestj + 1] - rmsArray[besti][bestj - 1]) / 2.f;
|
||||
float dxx = (rmsArray[besti + 1][bestj] + rmsArray[besti - 1][bestj] - 2 * rmsArray[besti][bestj]);
|
||||
float dyy = (rmsArray[besti][bestj + 1] + rmsArray[besti][bestj - 1] - 2 * rmsArray[besti][bestj]);
|
||||
float dxy =
|
||||
(rmsArray[besti + 1][bestj + 1] - rmsArray[besti + 1][bestj - 1] - rmsArray[besti - 1][bestj + 1] +
|
||||
rmsArray[besti - 1][bestj - 1]) / 4.f;
|
||||
float det = dxx * dyy - dxy * dxy;
|
||||
if (det != 0.0) {
|
||||
float ix = besti - (dyy * dx - dxy * dy) / det;
|
||||
float iy = bestj - (dxx * dy - dxy * dx) / det;
|
||||
bestDX = -maxt + ix * step;
|
||||
bestDY = -maxt + iy * step;
|
||||
}
|
||||
}
|
||||
|
||||
// Deallocate RMS array.
|
||||
for (long i = 0; i < bins; i++) delete[]rmsArray[i];
|
||||
delete[]rmsArray;
|
||||
|
||||
// Update the result and return.
|
||||
result.rms = bestRMS;
|
||||
result.tx = -bestDX;
|
||||
result.ty = -bestDY;
|
||||
result.tz = bestDZ;
|
||||
|
||||
printf("Percent less than 1m Z difference = %6.2f%%\n", bestCompleteness * 100.0);
|
||||
printf("X offset = %f m\n", result.tx);
|
||||
printf("Y offset = %f m\n", result.ty);
|
||||
printf("Z offset = %f m\n", result.tz);
|
||||
printf("Z RMS = %f m\n", result.rms);
|
||||
}
|
||||
|
||||
// Align target file to match reference file.
|
||||
bool AlignTarget2Reference(char *referenceFileName, char *targetFileName, AlignParameters params) {
|
||||
// Read the reference LAS file as a DSM.
|
||||
// Fill small voids.
|
||||
// Remove points along edges which are difficult to match.
|
||||
printf("Reading reference point cloud: %s\n", referenceFileName);
|
||||
OrthoImage<unsigned short> referenceDSM;
|
||||
bool ok = referenceDSM.readFromPointCloud(referenceFileName, params.gsd, MAX_VALUE);
|
||||
if (!ok) {
|
||||
printf("Failed to read %s\n", referenceFileName);
|
||||
return false;
|
||||
}
|
||||
referenceDSM.fillVoidsPyramid(true, 2);
|
||||
printf("Filtering reference point cloud.\n");
|
||||
referenceDSM.edgeFilter((long) (params.maxdz / referenceDSM.scale));
|
||||
|
||||
// Read the target LAS file as a DSM.
|
||||
// Fill small voids.
|
||||
// Remove points along edges which are difficult to match.
|
||||
printf("Reading target point cloud: %s\n", targetFileName);
|
||||
OrthoImage<unsigned short> targetDSM;
|
||||
ok = targetDSM.readFromPointCloud(targetFileName, params.gsd, MAX_VALUE);
|
||||
if (!ok) {
|
||||
printf("Failed to read %s\n", targetFileName);
|
||||
return false;
|
||||
}
|
||||
targetDSM.fillVoidsPyramid(true, 2);
|
||||
printf("Filtering target point cloud.\n");
|
||||
targetDSM.edgeFilter((long) (params.maxdz / targetDSM.scale));
|
||||
|
||||
// Get overlapping bounds.
|
||||
AlignBounds bounds;
|
||||
bounds.xmin = MAX(referenceDSM.easting, targetDSM.easting);
|
||||
bounds.ymin = MAX(referenceDSM.northing, targetDSM.northing);
|
||||
bounds.xmax = MIN(referenceDSM.easting + (referenceDSM.width * referenceDSM.gsd),
|
||||
targetDSM.easting + (targetDSM.width * targetDSM.gsd));
|
||||
bounds.ymax = MIN(referenceDSM.northing + (referenceDSM.height * referenceDSM.gsd),
|
||||
targetDSM.northing + (targetDSM.height * targetDSM.gsd));
|
||||
bounds.width = bounds.xmax - bounds.xmin;
|
||||
bounds.height = bounds.ymax - bounds.ymin;
|
||||
double overlap_km = bounds.width / 1000.0 * bounds.height / 1000.0;
|
||||
printf("Overlap = %d m x %d m = %f km\n", (long) bounds.width, (long) bounds.height, overlap_km);
|
||||
if (overlap_km == 0.0) return false;
|
||||
|
||||
// Estimate rigid body transform to align target points to reference.
|
||||
AlignResult result;
|
||||
printf("Estimating rigid body transformation.\n");
|
||||
EstimateRigidBody(referenceDSM, targetDSM, params.maxt, bounds, result);
|
||||
|
||||
// Write offsets text file.
|
||||
printf("Writing offsets text file.\n");
|
||||
int len = strlen(targetFileName);
|
||||
char outFileName[1024];
|
||||
sprintf(outFileName, "%s", targetFileName);
|
||||
sprintf(&outFileName[len - 4], "_offsets.txt");
|
||||
FILE *fptr = fopen(outFileName, "w");
|
||||
if (fptr) {
|
||||
fprintf(fptr, "X Offset Y Offset Z Offset Z RMS\n");
|
||||
fprintf(fptr, "%08.3f %08.3f %08.3f %08.3f\n", result.tx, result.ty, result.tz, result.rms);
|
||||
fclose(fptr);
|
||||
} else {
|
||||
printf("Failed to write %s\n", outFileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write aligned TIF file.
|
||||
printf("Writing aligned TIF file.\n");
|
||||
sprintf(&outFileName[len - 4], "_aligned.tif");
|
||||
targetDSM.offset += result.tz;
|
||||
targetDSM.easting += result.tx;
|
||||
targetDSM.northing += result.ty;
|
||||
ok = targetDSM.write(outFileName, true);
|
||||
if (!ok) {
|
||||
printf("Failed to write %s\n", outFileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write aligned BPF file.
|
||||
// For now, this requires an extra read of the point cloud file.
|
||||
printf("Writing aligned LAS file.\n");
|
||||
sprintf(&outFileName[len - 4], "_aligned.las");
|
||||
ok = PointCloud::TransformPointCloud(targetFileName, outFileName, result.tx, result.ty, result.tz);
|
||||
if (!ok) {
|
||||
printf("Failed to write %s\n", outFileName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/align3d/align3d.h
Normal file
51
src/align3d/align3d.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
#ifndef PUBGEO_ALIGN3D_H
|
||||
#define PUBGEO_ALIGN3D_H
|
||||
|
||||
#include <vector>
|
||||
#include "orthoimage.h"
|
||||
#include "Image.h"
|
||||
|
||||
namespace align3d {
|
||||
using namespace pubgeo;
|
||||
|
||||
typedef struct {
|
||||
float tx;
|
||||
float ty;
|
||||
float tz;
|
||||
float rms;
|
||||
}
|
||||
AlignResult;
|
||||
|
||||
typedef struct {
|
||||
float gsd;
|
||||
float maxdz;
|
||||
float maxt;
|
||||
}
|
||||
AlignParameters;
|
||||
|
||||
typedef struct {
|
||||
double xmin;
|
||||
double xmax;
|
||||
double ymin;
|
||||
double ymax;
|
||||
double width;
|
||||
double height;
|
||||
}
|
||||
AlignBounds;
|
||||
|
||||
bool computeRMS(float dx, float dy, long numSamples, long maxSamples, std::vector<double> &xlist,
|
||||
std::vector<double> &ylist, OrthoImage<unsigned short> &referenceDSM,
|
||||
OrthoImage<unsigned short> &targetDSM, float &medianDZ, float &rms, long &ndx, float &completeness);
|
||||
|
||||
// Estimate 3D rigid body transform parameters to align target points with reference.
|
||||
void EstimateRigidBody(OrthoImage<unsigned short> &referenceDSM, OrthoImage<unsigned short> &targetDSM, float maxt,
|
||||
AlignBounds &bounds, AlignResult &result);
|
||||
|
||||
// Align target file to match reference file.
|
||||
bool AlignTarget2Reference(char *referenceFileName, char *targetFileName, AlignParameters params);
|
||||
}
|
||||
|
||||
#endif // PUBGEO_ALIGN3D_H
|
||||
72
src/align3d/main.cpp
Normal file
72
src/align3d/main.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
#include "align3d.h"
|
||||
|
||||
void printArguments();
|
||||
|
||||
// Main program for simple 3d alignment.
|
||||
int main(int argc, char **argv) {
|
||||
// If no parameters, then print command line arguments.
|
||||
if (argc < 4) {
|
||||
printf("\nNumber of arguments = %d\n", argc);
|
||||
for (int i = 1; i < argc; i++) {
|
||||
printf(" ARG[%d] = %s\n", i, argv[i]);
|
||||
}
|
||||
printArguments();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse command line parameters.
|
||||
char referenceFileName[1024];
|
||||
char targetFileName[1024];
|
||||
sprintf(referenceFileName, argv[1]);
|
||||
sprintf(targetFileName, argv[2]);
|
||||
align3d::AlignParameters params;
|
||||
params.gsd = 1.0;
|
||||
params.maxt = 10.0;
|
||||
params.maxdz = 0.0;
|
||||
for (int i = 2; i < argc; i++) {
|
||||
if (strstr(argv[i], "maxdz=")) { params.maxdz = (float) atof(&(argv[i][6])); }
|
||||
if (strstr(argv[i], "gsd=")) { params.gsd = (float) atof(&(argv[i][4])); }
|
||||
if (strstr(argv[i], "maxt=")) { params.maxt = (float) atof(&(argv[i][5])); }
|
||||
}
|
||||
|
||||
// Default MAXDZ = GSD x 2 to ensure reliable performance on steep slopes.
|
||||
if (params.maxdz == 0.0) params.maxdz = params.gsd * 2.0;
|
||||
|
||||
printf("Selected Parameters:\n");
|
||||
printf(" ref = %s\n", referenceFileName);
|
||||
printf(" tgt = %s\n", targetFileName);
|
||||
printf(" gsd = %f\n", params.gsd);
|
||||
printf(" maxdz = %f\n", params.maxdz);
|
||||
printf(" maxt = %f\n", params.maxt);
|
||||
|
||||
// Initialize the timer.
|
||||
time_t t0;
|
||||
time(&t0);
|
||||
|
||||
try {
|
||||
// Align the target point cloud to the reference.
|
||||
AlignTarget2Reference(referenceFileName, targetFileName, params);
|
||||
} catch (char const *err) {
|
||||
std::cerr << "Reporting error: " << err << std::endl;
|
||||
}
|
||||
// Report total elapsed time.
|
||||
time_t t1;
|
||||
time(&t1);
|
||||
printf("Total time elapsed = %f seconds\n", double(t1 - t0));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Print command line arguments.
|
||||
void printArguments() {
|
||||
printf("Command Line: align-3d <reference> <target> <parameters>\n");
|
||||
printf("Parameters:\n");
|
||||
printf(" maxdz= Max local Z difference (meters) for matching\n");
|
||||
printf(" gsd= Ground Sample Distance (GSD) for gridding (meters)\n");
|
||||
printf(" maxt= Maximum XYZ translation in search (meters); default = 10.0\n");
|
||||
printf("Examples:\n");
|
||||
printf(" align-3d ref.las tgt.las maxt=10.0 gsd=0.5 maxdz=0.5 \n\n");
|
||||
}
|
||||
14
src/align3d/test-main.cpp
Normal file
14
src/align3d/test-main.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
#include <PointCloud.h>
|
||||
|
||||
using namespace pubgeo;
|
||||
|
||||
int main() {
|
||||
if (!PointCloud::TransformPointCloud("someFile.las", "someOut.las", 3.0, 4.0, 5.0)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
18
src/common/CMakeLists.txt
Normal file
18
src/common/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
SET(PUBGEO_HEADER_FILES
|
||||
util.h
|
||||
Image.h
|
||||
orthoimage.h
|
||||
PointCloud.h)
|
||||
|
||||
SET(PUBGEO_SOURCE_FILES
|
||||
PointCloud.cpp
|
||||
)
|
||||
|
||||
ADD_LIBRARY(PUBGEO_LIB STATIC ${PUBGEO_HEADER_FILES} ${PUBGEO_SOURCE_FILES})
|
||||
TARGET_INCLUDE_DIRECTORIES(PUBGEO_LIB
|
||||
PUBLIC
|
||||
${DAL_INCLUDE_DIRS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
TARGET_LINK_LIBRARIES(PUBGEO_LIB
|
||||
PUBLIC
|
||||
${DAL_LIBS})
|
||||
46
src/common/Image.h
Normal file
46
src/common/Image.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
// Created by almess1 on 4/21/17.
|
||||
//
|
||||
|
||||
#ifndef PUBGEO_IMAGE_H
|
||||
#define PUBGEO_IMAGE_H
|
||||
|
||||
#include <cstring>
|
||||
namespace pubgeo {
|
||||
template<class TYPE>
|
||||
class Image {
|
||||
|
||||
public:
|
||||
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int bands;
|
||||
float scale;
|
||||
float offset;
|
||||
TYPE **data; // Order is HEIGHT (outside), WIDTH, BAND (inside).
|
||||
|
||||
// Deallocate memory.
|
||||
void Deallocate() {
|
||||
if (!data) return;
|
||||
for (unsigned int y = 0; y < height; y++) delete[]data[y];
|
||||
delete[]data;
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
// Allocate memory.
|
||||
void Allocate(unsigned int numColumns, unsigned int numRows, unsigned int numBands = 1) {
|
||||
if (data) Deallocate();
|
||||
width = numColumns;
|
||||
height = numRows;
|
||||
bands = numBands;
|
||||
data = new TYPE *[height];
|
||||
for (unsigned int y = 0; y < height; y++) {
|
||||
data[y] = new TYPE[width * bands];
|
||||
std::memset(data[y], 0, width * bands * sizeof(TYPE));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif //PUBGEO_IMAGE_H
|
||||
91
src/common/PointCloud.cpp
Normal file
91
src/common/PointCloud.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
#include "PointCloud.h"
|
||||
|
||||
// Pipeline needs to read in point cloud file of any type, and read it in. ideally in meters
|
||||
static std::string PDAL_PIPELINE_OPEN_ENGINE = R"({ "pipeline": [ ")";
|
||||
static std::string PDAL_PIPELINE_OPEN_CABOOSE = R"("] } )";
|
||||
|
||||
std::string buildPipelineStr(const char *fileName) {
|
||||
return PDAL_PIPELINE_OPEN_ENGINE + fileName + PDAL_PIPELINE_OPEN_CABOOSE;
|
||||
}
|
||||
|
||||
namespace pubgeo {
|
||||
PointCloud::PointCloud() : executor(nullptr), pv(nullptr), zone(0), xOff(0), yOff(0), zOff(0), numPoints(0) {
|
||||
bounds = MinMaxXYZ{0, 0, 0, 0, 0, 0};
|
||||
}
|
||||
|
||||
PointCloud::~PointCloud() {
|
||||
CleanupPdalPointers();
|
||||
}
|
||||
|
||||
bool PointCloud::Read(const char *fileName) {
|
||||
try {
|
||||
CleanupPdalPointers();
|
||||
executor = new pdal::PipelineExecutor(buildPipelineStr(fileName));
|
||||
executor->execute();
|
||||
const pdal::PointViewSet &pvs = executor->getManagerConst().views();
|
||||
pv = pvs.begin()->get();
|
||||
if (pvs.size() > 1) {
|
||||
std::cerr << "[PUBGEO::PointCloud::READ] File contains additional unread sets." << std::endl;
|
||||
}
|
||||
|
||||
numPoints = pv->size();
|
||||
if (numPoints < 1) {
|
||||
std::cerr << "[PUBGEO::PointCloud::READ] No points found in file." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
pdal::BOX3D box;
|
||||
pv->calculateBounds(box);
|
||||
pdal::SpatialReference sr = pv->spatialReference();
|
||||
zone = sr.computeUTMZone(box);
|
||||
|
||||
// used later to return points
|
||||
xOff = (int) floor(box.minx);
|
||||
yOff = (int) floor(box.miny);
|
||||
zOff = (int) floor(box.minz);
|
||||
|
||||
bounds = {box.minx, box.maxx, box.miny, box.maxy, box.minz, box.maxz};
|
||||
return true;
|
||||
}
|
||||
catch (pdal::pdal_error &pe) {
|
||||
std::cerr << pe.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PointCloud::TransformPointCloud(const char *inputFileName, const char *outputFileName,
|
||||
float translateX = 0, float translateY = 0, float translateZ = 0) {
|
||||
std::ostringstream pipeline;
|
||||
pipeline << "{\n\t\"pipeline\":[\n\t\t\"" << inputFileName
|
||||
<< "\",\n\t\t{\n\t\t\t\"type\":\"filters.transformation\",\n"
|
||||
<< "\t\t\t\"matrix\":\""
|
||||
<< "1 0 0 " << translateX << " "
|
||||
<< "0 1 0 " << translateY << " "
|
||||
<< "0 0 1 " << translateZ << " "
|
||||
<< "0 0 0 1\"\n\t\t},\n\t\t{"
|
||||
<< "\n\t\t\t\"filename\":\"" << outputFileName << "\"\n\t\t}\n\t]\n}";
|
||||
try {
|
||||
const std::string pipe = pipeline.str();
|
||||
pdal::PipelineExecutor executor(pipe);
|
||||
executor.execute();
|
||||
} catch (pdal::pdal_error &pe) {
|
||||
std::cerr << pe.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PointCloud::CleanupPdalPointers() {
|
||||
if (pv != nullptr) {
|
||||
pv = nullptr;
|
||||
}
|
||||
if (executor != nullptr) {
|
||||
delete executor;
|
||||
executor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
69
src/common/PointCloud.h
Normal file
69
src/common/PointCloud.h
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
//
|
||||
// Created by almess1 on 4/20/17.
|
||||
// This file was created in order to adapt the SHR3D and ALIGN3D to open source IO.
|
||||
//
|
||||
|
||||
#ifndef PUBGEO_NOT_POINT_SETS_H
|
||||
#define PUBGEO_NOT_POINT_SETS_H
|
||||
|
||||
#include <pdal/PointView.hpp>
|
||||
#include <pdal/PipelineExecutor.hpp>
|
||||
|
||||
namespace pubgeo {
|
||||
struct MinMaxXYZ {
|
||||
double xMin;
|
||||
double xMax;
|
||||
double yMin;
|
||||
double yMax;
|
||||
double zMin;
|
||||
double zMax;
|
||||
};
|
||||
|
||||
|
||||
class PointCloud {
|
||||
public:
|
||||
PointCloud();
|
||||
|
||||
~PointCloud();
|
||||
|
||||
static bool TransformPointCloud(const char *inputFileName, const char *outputFileName,
|
||||
float translateX, float translateY, float translateZ);
|
||||
|
||||
bool Read(const char *fileName);
|
||||
|
||||
MinMaxXYZ bounds;
|
||||
int zone;
|
||||
unsigned long numPoints;
|
||||
int xOff;
|
||||
int yOff;
|
||||
int zOff;
|
||||
|
||||
inline float x(int i) {
|
||||
if (pv != nullptr)
|
||||
return (float) (pv->getFieldAs<double>(pdal::Dimension::Id::X, i) - xOff);
|
||||
throw "Point set has not be initialized.";
|
||||
}
|
||||
|
||||
inline float y(int i) {
|
||||
if (pv != nullptr)
|
||||
return (float) (pv->getFieldAs<double>(pdal::Dimension::Id::Y, i) - yOff);
|
||||
throw "Point set has not be initialized.";
|
||||
}
|
||||
|
||||
inline float z(int i) {
|
||||
if (pv != nullptr)
|
||||
return (float) (pv->getFieldAs<double>(pdal::Dimension::Id::Z, i) - zOff);
|
||||
throw "Point set has not be initialized.";
|
||||
}
|
||||
|
||||
private:
|
||||
pdal::PipelineExecutor *executor;
|
||||
pdal::PointView *pv;
|
||||
|
||||
void CleanupPdalPointers();
|
||||
};
|
||||
}
|
||||
#endif //PUBGEO_NOT_POINT_SETS_H
|
||||
549
src/common/orthoimage.h
Normal file
549
src/common/orthoimage.h
Normal file
@@ -0,0 +1,549 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
// orthoimage.h
|
||||
//
|
||||
|
||||
|
||||
#ifndef PUBGEO_ORTHO_IMAGE_H
|
||||
#define PUBGEO_ORTHO_IMAGE_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include <typeinfo>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef WIN32
|
||||
#include "gdal_priv.h"
|
||||
#include "ogr_spatialref.h"
|
||||
#else
|
||||
|
||||
#include <gdal/gdal_priv.h>
|
||||
#include "gdal/ogr_spatialref.h"
|
||||
|
||||
#endif
|
||||
|
||||
#include "util.h"
|
||||
#include "PointCloud.h"
|
||||
#include "Image.h"
|
||||
|
||||
namespace pubgeo {
|
||||
typedef enum {
|
||||
MIN_VALUE, MAX_VALUE
|
||||
} MIN_MAX_TYPE;
|
||||
|
||||
//
|
||||
// Ortho image template class
|
||||
//
|
||||
template<class TYPE>
|
||||
class OrthoImage : public Image<TYPE> {
|
||||
|
||||
public:
|
||||
|
||||
double easting;
|
||||
double northing;
|
||||
int zone;
|
||||
float gsd;
|
||||
|
||||
// Constructor
|
||||
OrthoImage() {
|
||||
memset(this, 0, sizeof(OrthoImage));
|
||||
this->scale = 1.0f;
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~OrthoImage() {
|
||||
this->Deallocate();
|
||||
}
|
||||
|
||||
// Read any GDAL-supported image.
|
||||
bool read(char *fileName) {
|
||||
// Open the image.
|
||||
GDALAllRegister();
|
||||
CPLSetConfigOption("GDAL_DATA", ".\\gdaldata");
|
||||
GDALDataset *poDataset = (GDALDataset *) GDALOpen(fileName, GA_ReadOnly);
|
||||
if (poDataset == NULL) {
|
||||
printf("Error opening %s.\n", fileName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get geospatial metadata.
|
||||
double adfGeoTransform[6];
|
||||
printf("Driver: %s/%s\n", poDataset->GetDriver()->GetDescription(),
|
||||
poDataset->GetDriver()->GetMetadataItem(GDAL_DMD_LONGNAME));
|
||||
this->width = (unsigned int) poDataset->GetRasterXSize();
|
||||
this->height = (unsigned int) poDataset->GetRasterYSize();
|
||||
this->bands = (unsigned int) poDataset->GetRasterCount();
|
||||
printf("Width = %d\nHeight = %d\nBands = %d\n", this->width, this->height, this->bands);
|
||||
if (poDataset->GetProjectionRef() != NULL) printf("Projection is %s.\n", poDataset->GetProjectionRef());
|
||||
if (poDataset->GetGeoTransform(adfGeoTransform) == CE_None) {
|
||||
printf("GeoTransform = (%.6f,%.6f,%.6f,%.6f,%.6f,%.6f)\n",
|
||||
adfGeoTransform[0], adfGeoTransform[1], adfGeoTransform[2], adfGeoTransform[3],
|
||||
adfGeoTransform[4],
|
||||
adfGeoTransform[5]);
|
||||
}
|
||||
double xscale = adfGeoTransform[1];
|
||||
double yscale = -adfGeoTransform[5];
|
||||
this->gsd = (float) ((xscale + yscale) / 2.0);
|
||||
this->easting = adfGeoTransform[0] - adfGeoTransform[2] * xscale;
|
||||
this->northing = adfGeoTransform[3] - (this->height) * yscale;
|
||||
OGRSpatialReference myOGRS(poDataset->GetProjectionRef());
|
||||
int north = 1;
|
||||
this->zone = myOGRS.GetUTMZone(&north);
|
||||
if (north == 0) {
|
||||
|
||||
this->zone *= -1;
|
||||
}
|
||||
printf("UTM Easting = %lf\n", this->easting);
|
||||
printf("UTM Northing = %lf\n", this->northing);
|
||||
printf("UTM Zone = %d\n", this->zone);
|
||||
printf("GSD = %f\n", this->gsd);
|
||||
|
||||
// Get band information.
|
||||
GDALRasterBand *poBand = poDataset->GetRasterBand(1);
|
||||
if (poBand == NULL) {
|
||||
printf("Error opening first band.");
|
||||
return false;
|
||||
}
|
||||
GDALDataType BandDataType = poBand->GetRasterDataType();
|
||||
int bytesPerPixel = GDALGetDataTypeSize(BandDataType) / 8;
|
||||
unsigned char *poBandBlock = (unsigned char *) CPLMalloc(bytesPerPixel * this->width);
|
||||
int ok = 0;
|
||||
double noData = poBand->GetNoDataValue(&ok);
|
||||
if (!ok) {
|
||||
// Set noData only for floating point images.
|
||||
if (strcmp(typeid(TYPE).name(), "float") == 0)
|
||||
noData = -10000.0;
|
||||
else
|
||||
noData = 0;
|
||||
}
|
||||
|
||||
// Get scale and offset values.
|
||||
if (strcmp(typeid(TYPE).name(), "float") == 0) {
|
||||
// Do not scale if floating point values.
|
||||
this->scale = 1.0;
|
||||
this->offset = 0.0;
|
||||
} else {
|
||||
float minVal = MAX_FLOAT;
|
||||
float maxVal = MAX_FLOAT;
|
||||
for (unsigned int i = 0; i < this->bands; i++) {
|
||||
poBand = poDataset->GetRasterBand(i + 1);
|
||||
if (poBand == NULL) {
|
||||
printf("Error opening band %d", i + 1);
|
||||
return false;
|
||||
}
|
||||
int bGotMin, bGotMax;
|
||||
double adfMinMax[2];
|
||||
adfMinMax[0] = poBand->GetMinimum(&bGotMin);
|
||||
adfMinMax[1] = poBand->GetMaximum(&bGotMax);
|
||||
if (!(bGotMin && bGotMax)) GDALComputeRasterMinMax((GDALRasterBandH) poBand, false, adfMinMax);
|
||||
if (minVal > adfMinMax[0]) minVal = (float) adfMinMax[0];
|
||||
if (maxVal < adfMinMax[1]) maxVal = (float) adfMinMax[1];
|
||||
}
|
||||
minVal -= 1; // Reserve zero for noData value
|
||||
maxVal += 1;
|
||||
float maxImageVal = (float) (pow(2.0, int(sizeof(TYPE) * 8)) - 1);
|
||||
this->offset = minVal;
|
||||
this->scale = (maxVal - minVal) / maxImageVal;
|
||||
}
|
||||
printf("Offset = %f\n", this->offset);
|
||||
printf("Scale = %f\n", this->scale);
|
||||
|
||||
// Allocate memory for the image.
|
||||
this->Allocate(this->width, this->height, this->bands);
|
||||
|
||||
// Read the image, one band at a time.
|
||||
for (unsigned int irow = 0; irow < this->height; irow++) {
|
||||
for (unsigned int ib = 0; ib < this->bands; ib++) {
|
||||
// Read the next row.
|
||||
poBand = poDataset->GetRasterBand(ib + 1);
|
||||
poBand->RasterIO(GF_Read, 0, irow, this->width, 1, poBandBlock, this->width, 1, BandDataType, 0, 0);
|
||||
switch (BandDataType) {
|
||||
case GDT_Byte: {
|
||||
for (unsigned int icols = 0; icols < this->width; icols++) {
|
||||
long k = icols * this->bands;
|
||||
if (poBandBlock[icols] == (unsigned char) noData)
|
||||
this->data[irow][k + ib] = 0;
|
||||
else
|
||||
this->data[irow][k + ib] = (TYPE) ((poBandBlock[icols] - this->offset) /
|
||||
this->scale);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GDT_UInt16: {
|
||||
unsigned int *poBandBlockShort = (unsigned int *) poBandBlock;
|
||||
for (unsigned int icols = 0; icols < this->width; icols++) {
|
||||
long k = icols * this->bands;
|
||||
if (poBandBlockShort[icols] == (unsigned int) noData)
|
||||
this->data[irow][k + ib] = 0;
|
||||
else
|
||||
this->data[irow][k + ib] = (TYPE) ((poBandBlockShort[icols] - this->offset) /
|
||||
this->scale);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GDT_Float32: {
|
||||
float *poBandBlockFloat = (float *) poBandBlock;
|
||||
for (unsigned int icols = 0; icols < this->width; icols++) {
|
||||
long k = icols * this->bands;
|
||||
if (poBandBlockFloat[icols] == (float) noData)
|
||||
this->data[irow][k + ib] = 0;
|
||||
else
|
||||
this->data[irow][k + ib] = (TYPE) ((poBandBlockFloat[icols] - this->offset) /
|
||||
this->scale);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// TODO: shouldn't get here, relay error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deallocate memory.
|
||||
CPLFree(poBandBlock);
|
||||
delete poDataset;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write GEOTIFF image using GDAL.
|
||||
bool write(char *fileName, bool convertToFloat = false, bool egm96 = false) {
|
||||
GDALAllRegister();
|
||||
const char *pszFormat = "GTiff";
|
||||
GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName(pszFormat);
|
||||
if (poDriver == NULL) return false;
|
||||
char **papszMetadata = poDriver->GetMetadata();
|
||||
if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATE, false)) return false;
|
||||
|
||||
// Get the GDAL data type to match the image data type.
|
||||
GDALDataType theBandDataType = (GDALDataType) sizeof(TYPE);
|
||||
if (strcmp(typeid(TYPE).name(), "float") == 0) theBandDataType = GDT_Float32;
|
||||
|
||||
// If converting this image to FLOAT, then update the output format type.
|
||||
if (convertToFloat) theBandDataType = GDT_Float32;
|
||||
|
||||
// Write geospatial metadata.
|
||||
char **papszOptions = nullptr;
|
||||
GDALDataset *poDstDS = poDriver->Create(fileName, this->width, this->height, this->bands, theBandDataType,
|
||||
papszOptions);
|
||||
double adfGeoTransform[6] = {this->easting, this->gsd, 0, this->northing + this->height * this->gsd, 0,
|
||||
-1 * this->gsd};
|
||||
poDstDS->SetGeoTransform(adfGeoTransform);
|
||||
OGRSpatialReference oSRS;
|
||||
char *pszSRS_WKT = NULL;
|
||||
oSRS.SetProjCS("UTM (WGS84)");
|
||||
oSRS.SetUTM(abs(this->zone), (this->zone > 0));
|
||||
int theZone = 255;
|
||||
int theZoneIsNorthern = 255;
|
||||
theZone = oSRS.GetUTMZone(&theZoneIsNorthern);
|
||||
oSRS.SetWellKnownGeogCS("WGS84");
|
||||
if (!egm96)
|
||||
oSRS.SetVertCS("WGS 84", "World Geodetic System 1984");
|
||||
else
|
||||
oSRS.SetVertCS("EGM96 Geoid", "EGM96 geoid");
|
||||
oSRS.SetExtension("VERT_DATUM", "PROJ4_GRIDS", "g2009conus.gtx");
|
||||
oSRS.exportToWkt(&pszSRS_WKT);
|
||||
poDstDS->SetProjection(pszSRS_WKT);
|
||||
CPLFree(pszSRS_WKT);
|
||||
|
||||
// Write the image.
|
||||
if (convertToFloat) {
|
||||
// Write the image as FLOAT and convert values.
|
||||
float *raster = new float[this->width * this->bands];
|
||||
float noData = -10000.0;
|
||||
for (unsigned int i = 1; i <= this->bands; i++) {
|
||||
GDALRasterBand *poBand = poDstDS->GetRasterBand(i);
|
||||
poBand->SetNoDataValue(noData);
|
||||
for (unsigned int j = 0; j < this->height; j++) {
|
||||
for (int k = 0; k < this->width * this->bands; k++) {
|
||||
if (this->data[j][k] == 0)
|
||||
raster[k] = noData;
|
||||
else
|
||||
raster[k] = (float(this->data[j][k]) * this->scale) + this->offset;
|
||||
}
|
||||
poBand->RasterIO(GF_Write, 0, j, this->width, 1, raster, this->width, 1, theBandDataType,
|
||||
sizeof(float) * this->bands,
|
||||
this->width * this->bands * sizeof(float));
|
||||
}
|
||||
}
|
||||
delete[]raster;
|
||||
} else {
|
||||
// Write the image without conversion.
|
||||
for (unsigned int i = 1; i <= this->bands; i++) {
|
||||
GDALRasterBand *poBand = poDstDS->GetRasterBand(i);
|
||||
for (unsigned int j = 0; j < this->height; j++) {
|
||||
poBand->RasterIO(GF_Write, 0, j, this->width, 1, this->data[j], this->width, 1, theBandDataType,
|
||||
sizeof(TYPE) * this->bands,
|
||||
this->width * this->bands * sizeof(TYPE));
|
||||
}
|
||||
}
|
||||
}
|
||||
GDALClose((GDALDatasetH) poDstDS);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read image from point cloud file.
|
||||
bool readFromPointCloud(char *fileName, float gsdMeters, MIN_MAX_TYPE mode = MIN_VALUE) {
|
||||
// Read a PSET file (e.g., BPF or LAS).
|
||||
PointCloud pset;
|
||||
bool ok = pset.Read(fileName);
|
||||
if (!ok) return false;
|
||||
|
||||
// Calculate scale and offset for conversion to TYPE.
|
||||
float minVal = pset.bounds.zMin - 1; // Reserve zero for noData value
|
||||
float maxVal = pset.bounds.zMax + 1;
|
||||
float maxImageVal = (float) (pow(2.0, int(sizeof(TYPE) * 8)) - 1);
|
||||
this->offset = minVal;
|
||||
this->scale = (maxVal - minVal) / maxImageVal;
|
||||
|
||||
// Calculate image width and height.
|
||||
this->width = (unsigned int) ((pset.bounds.xMax - pset.bounds.xMin) / gsdMeters + 1);
|
||||
this->height = (unsigned int) ((pset.bounds.yMax - pset.bounds.yMin) / gsdMeters + 1);
|
||||
|
||||
// Allocate an ortho image.
|
||||
this->Allocate(this->width, this->height);
|
||||
this->easting = pset.bounds.xMin;
|
||||
this->northing = pset.bounds.yMin;
|
||||
this->zone = pset.zone;
|
||||
this->gsd = gsdMeters;
|
||||
|
||||
// Copy points into the ortho image.
|
||||
if (mode == MIN_VALUE) {
|
||||
for (int i = 0; i < pset.numPoints; i++) {
|
||||
int x = int((pset.x(i) + pset.xOff - easting) / gsd + 0.5);
|
||||
if ((x < 0) || (x > this->width - 1)) continue;
|
||||
int y = this->height - 1 - int((pset.y(i) + pset.yOff - northing) / gsd + 0.5);
|
||||
if ((y < 0) || (y > this->height - 1)) continue;
|
||||
TYPE z = TYPE((pset.z(i) + pset.zOff - this->offset) / this->scale);
|
||||
if ((this->data[y][x] == 0) || (z < this->data[y][x])) this->data[y][x] = z;
|
||||
}
|
||||
} else if (mode == MAX_VALUE) {
|
||||
for (int i = 0; i < pset.numPoints; i++) {
|
||||
int x = int((pset.x(i) + pset.xOff - easting) / gsd + 0.5);
|
||||
if ((x < 0) || (x > this->width - 1)) continue;
|
||||
int y = this->height - 1 - int((pset.y(i) + pset.yOff - northing) / gsd + 0.5);
|
||||
if ((y < 0) || (y > this->height - 1)) continue;
|
||||
TYPE z = TYPE((pset.z(i) + pset.zOff - this->offset) / this->scale);
|
||||
if ((this->data[y][x] == 0) || (z > this->data[y][x])) this->data[y][x] = z;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Count voids in an image.
|
||||
// Note that voids are always labeled zero in this data structure.
|
||||
long countVoids() {
|
||||
long count = 0;
|
||||
for (unsigned int j = 0; j < this->height; j++) {
|
||||
for (unsigned int i = 0; i < this->width; i++) {
|
||||
if (this->data[j][i] == 0) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Fill any voids in the image using a simple multigrid scheme.
|
||||
// Note that voids are always labeled zero.
|
||||
// MaxLevel by default is the maximum value of int
|
||||
void fillVoidsPyramid(bool noSmoothing, unsigned int maxLevel = MAX_INT) {
|
||||
// Check for voids.
|
||||
long count = countVoids();
|
||||
if (count == 0) return;
|
||||
|
||||
// Create image pyramid.
|
||||
std::vector<OrthoImage<TYPE> *> pyramid;
|
||||
pyramid.push_back(this);
|
||||
int level = 0;
|
||||
while ((count > 0) && (level < maxLevel)) {
|
||||
// Create next level.
|
||||
OrthoImage<TYPE> *newImagePtr = new OrthoImage<TYPE>;
|
||||
|
||||
int nextWidth = pyramid[level]->width / 2;
|
||||
int nextHeight = pyramid[level]->height / 2;
|
||||
newImagePtr->Allocate(nextWidth, nextHeight, 1);
|
||||
|
||||
// Fill in non-void values from level below building up the pyramid with a simple running average.
|
||||
for (int j = 0; j < nextHeight; j++) {
|
||||
for (int i = 0; i < nextWidth; i++) {
|
||||
int j2 = MIN(MAX(0, j * 2 + 1), pyramid[level]->height - 1);
|
||||
int i2 = MIN(MAX(0, i * 2 + 1), pyramid[level]->width - 1);
|
||||
|
||||
// Average neighboring pixels from below.
|
||||
float z = 0;
|
||||
int ct = 0;
|
||||
std::vector<TYPE> neighbors;
|
||||
for (int jj = MAX(0, j2 - 1); jj <= MIN(j2 + 1, pyramid[level]->height - 1); jj++) {
|
||||
for (int ii = MAX(0, i2 - 1); ii <= MIN(i2 + 1, pyramid[level]->width - 1); ii++) {
|
||||
if (pyramid[level]->data[jj][ii] != 0) {
|
||||
z += pyramid[level]->data[jj][ii];
|
||||
ct++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ct != 0) {
|
||||
z = z / ct;
|
||||
newImagePtr->data[j][i] = (TYPE) z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pyramid.push_back(newImagePtr);
|
||||
level++;
|
||||
count = pyramid[level]->countVoids();
|
||||
}
|
||||
|
||||
// Void fill down the pyramid.
|
||||
for (int k = level - 1; k >= 0; k--) {
|
||||
for (int j = 0; j < pyramid[k]->height; j++) {
|
||||
for (int i = 0; i < pyramid[k]->width; i++) {
|
||||
// Fill this pixel if it is currently void.
|
||||
if (pyramid[k]->data[j][i] == 0) {
|
||||
int j2 = MIN(MAX(0, j / 2), pyramid[k + 1]->height - 1);
|
||||
int i2 = MIN(MAX(0, i / 2), pyramid[k + 1]->width - 1);
|
||||
|
||||
if (noSmoothing) {
|
||||
// Just use the closest pixel from above.
|
||||
pyramid[k]->data[j][i] = pyramid[k + 1]->data[j2][i2];
|
||||
} else {
|
||||
// Average neighboring pixels from above.
|
||||
float z = 0;
|
||||
int ct = 0;
|
||||
for (int jj = MAX(0, j2 - 1);
|
||||
jj <= MIN(j2 + 1, pyramid[k + 1]->height - 1); jj++) {
|
||||
for (int ii = MAX(0, i2 - 1);
|
||||
ii <= MIN(i2 + 1, pyramid[k + 1]->width - 1); ii++) {
|
||||
z += pyramid[k + 1]->data[jj][ii];
|
||||
ct++;
|
||||
}
|
||||
}
|
||||
z = z / ct;
|
||||
pyramid[k]->data[j][i] = (TYPE) z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deallocate memory for all but the input DSM.
|
||||
for (int i = 1; i <= level; i++) {
|
||||
pyramid[i]->Deallocate();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply a median filter to an image.
|
||||
void medianFilter(int rad, unsigned int dzShort) {
|
||||
for (int j = 0; j < this->height; j++) {
|
||||
for (int i = 0; i < this->width; i++) {
|
||||
// Skip if void.
|
||||
if (this->data[j][i] == 0) continue;
|
||||
|
||||
// Define bounds;
|
||||
int i1 = MAX(0, i - rad);
|
||||
int i2 = MIN(i + rad, this->width - 1);
|
||||
int j1 = MAX(0, j - rad);
|
||||
int j2 = MIN(j + rad, this->height - 1);
|
||||
|
||||
// Add valid values to the list.
|
||||
std::vector<unsigned short> values;
|
||||
for (int jj = j1; jj <= j2; jj++) {
|
||||
for (int ii = i1; ii <= i2; ii++) {
|
||||
if (this->data[jj][ii] != 0) {
|
||||
values.push_back(this->data[jj][ii]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the median value.
|
||||
unsigned long num = values.size();
|
||||
if (num > 0) {
|
||||
std::sort(values.begin(), values.end());
|
||||
unsigned short medianValue = values[num / 2];
|
||||
if (fabs(float(medianValue) - float(this->data[j][i])) > dzShort)
|
||||
this->data[j][i] = medianValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply a minimum filter to an image.
|
||||
void minFilter(int rad) {
|
||||
OrthoImage<TYPE> tempImage;
|
||||
tempImage.Allocate(this->width, this->height);
|
||||
for (int j = 0; j < this->height; j++) {
|
||||
for (int i = 0; i < this->width; i++) {
|
||||
tempImage.data[j][i] = this->data[j][i];
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < this->height; j++) {
|
||||
for (int i = 0; i < this->width; i++) {
|
||||
// Skip if void.
|
||||
if (this->data[j][i] == 0) continue;
|
||||
|
||||
// Define bounds;
|
||||
int i1 = MAX(0, i - rad);
|
||||
int i2 = MIN(i + rad, this->width - 1);
|
||||
int j1 = MAX(0, j - rad);
|
||||
int j2 = MIN(j + rad, this->height - 1);
|
||||
|
||||
// Check all neighbors.
|
||||
for (int jj = j1; jj <= j2; jj++) {
|
||||
for (int ii = i1; ii <= i2; ii++) {
|
||||
if (this->data[jj][ii] != 0) {
|
||||
if (this->data[jj][ii] < tempImage.data[j][i])
|
||||
tempImage.data[j][i] = this->data[jj][ii];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < this->height; j++) {
|
||||
for (int i = 0; i < this->width; i++) {
|
||||
this->data[j][i] = tempImage.data[j][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void edgeFilter(int dzShort) {
|
||||
OrthoImage<TYPE> tempImage;
|
||||
tempImage.Allocate(this->width, this->height);
|
||||
for (int j = 0; j < this->height; j++) {
|
||||
for (int i = 0; i < this->width; i++) {
|
||||
tempImage.data[j][i] = this->data[j][i];
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < this->height; j++) {
|
||||
for (int i = 0; i < this->width; i++) {
|
||||
// Skip if void.
|
||||
if (this->data[j][i] == 0) continue;
|
||||
|
||||
// Define bounds;
|
||||
int i1 = MAX(0, i - 1);
|
||||
int i2 = MIN(i + 1, this->width - 1);
|
||||
int j1 = MAX(0, j - 1);
|
||||
int j2 = MIN(j + 1, this->height - 1);
|
||||
|
||||
// If there's an edge with any neighbor, then remove.
|
||||
for (int jj = j1; jj <= j2; jj++) {
|
||||
for (int ii = i1; ii <= i2; ii++) {
|
||||
if (fabs(float(this->data[jj][ii] - this->data[j][i])) > dzShort) {
|
||||
tempImage.data[j][i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < this->height; j++) {
|
||||
for (int i = 0; i < this->width; i++) {
|
||||
this->data[j][i] = tempImage.data[j][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //PUBGEO_ORTHO_IMAGE_H
|
||||
27
src/common/util.h
Normal file
27
src/common/util.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
//
|
||||
// Created by almess1 on 4/20/17.
|
||||
//
|
||||
|
||||
#ifndef PUBGEO_UTIL_H
|
||||
#define PUBGEO_UTIL_H
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace pubgeo {
|
||||
template<typename T>
|
||||
inline T &Max(const T &a, const T &b) {
|
||||
return a < b ? b : a;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T &Min(const T &a, const T &b) {
|
||||
return a > b ? b : a;
|
||||
}
|
||||
|
||||
const int MAX_INT = std::numeric_limits<int>::max();
|
||||
const float MAX_FLOAT = std::numeric_limits<float>::max();
|
||||
}
|
||||
#endif //PUBGEO_UTIL_H
|
||||
25
src/shr3d/CMakeLists.txt
Normal file
25
src/shr3d/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
SET(SHR3D_HEADER_FILES
|
||||
shr3d.h)
|
||||
|
||||
SET(SHR3D_SOURCE_FILES
|
||||
shr3d.cpp)
|
||||
|
||||
ADD_LIBRARY(SHR3D_LIB STATIC ${SHR3D_HEADER_FILES} ${SHR3D_SOURCE_FILES})
|
||||
TARGET_LINK_LIBRARIES(SHR3D_LIB
|
||||
PUBLIC
|
||||
PUBGEO_LIB)
|
||||
|
||||
### Add some executables to play with.
|
||||
### Main exe will showcase all features
|
||||
ADD_EXECUTABLE(shr3d main.cpp)
|
||||
TARGET_LINK_LIBRARIES(shr3d
|
||||
PUBLIC
|
||||
SHR3D_LIB)
|
||||
|
||||
IF(TESTING)
|
||||
## This executable will be used to test IO
|
||||
ADD_EXECUTABLE(shr3dTest test-main.cpp)
|
||||
TARGET_LINK_LIBRARIES(shr3dTest
|
||||
PUBLIC
|
||||
SHR3D_LIB)
|
||||
ENDIF()
|
||||
265
src/shr3d/main.cpp
Normal file
265
src/shr3d/main.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include "orthoimage.h"
|
||||
#include "shr3d.h"
|
||||
|
||||
// Print command line arguments.
|
||||
void printArguments() {
|
||||
printf("Command line arguments: <Input File (LAS|TIF)> <Options>\n");
|
||||
printf("Options:\n");
|
||||
printf(" DH= horizontal uncertainty (meters)\n");
|
||||
printf(" DZ= vertical uncertainty (meters)\n");
|
||||
printf(" AGL= minimum building height above ground level (meters)\n");
|
||||
printf(" AREA= minimum building area (meters)\n");
|
||||
printf(" EGM96 set this flag to write vertical datum = EGM96\n");
|
||||
printf(" FOPEN set this flag for ground detection under dense foliage\n");
|
||||
printf("Examples:\n");
|
||||
printf(" For EO DSM: shr3d dsm.tif DH=5.0 DZ=1.0 AGL=2 AREA=50.0 EGM96\n");
|
||||
printf(" For lidar DSM: shr3d dsm.tif DH=1.0 DZ=1.0 AGL=2.0 AREA=50.0\n");
|
||||
printf(" For lidar LAS: shr3d pts.las DH=1.0 DZ=1.0 AGL=2.0 AREA=50.0\n");
|
||||
}
|
||||
|
||||
|
||||
// Main program for bare earth classification.
|
||||
int main(int argc, char **argv) {
|
||||
// If no parameters, then print command line arguments.
|
||||
if (argc < 4) {
|
||||
printf("Number of arguments = %d\n", argc);
|
||||
for (int i = 0; i < argc; i++) {
|
||||
printf("ARG[%d] = %s\n", i, argv[i]);
|
||||
}
|
||||
printArguments();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get command line arguments and confirm they are valid.
|
||||
double gsd_meters = 0.0;
|
||||
double dh_meters = 0.0;
|
||||
double dz_meters = 0.0;
|
||||
double agl_meters = 0.0;
|
||||
double min_area_meters = 50.0;
|
||||
bool egm96 = false;
|
||||
bool convert = false;
|
||||
char inputFileName[1024];
|
||||
sprintf(inputFileName, argv[1]);
|
||||
for (int i = 2; i < argc; i++) {
|
||||
if (strstr(argv[i], "DH=")) { dh_meters = atof(&(argv[i][3])); }
|
||||
if (strstr(argv[i], "DZ=")) { dz_meters = atof(&(argv[i][3])); }
|
||||
if (strstr(argv[i], "AGL=")) { agl_meters = atof(&(argv[i][4])); }
|
||||
if (strstr(argv[i], "AREA=")) { min_area_meters = atof(&(argv[i][5])); }
|
||||
if (strstr(argv[i], "EGM96")) { egm96 = true; }
|
||||
if (strstr(argv[i], "CONVERT")) { convert = true; }
|
||||
}
|
||||
if ((dh_meters == 0.0) || (dz_meters == 0.0) || (agl_meters == 0.0)) {
|
||||
printf("DH_METERS = %f\n", dh_meters);
|
||||
printf("DZ_METERS = %f\n", dz_meters);
|
||||
printf("AGL_METERS = %f\n", agl_meters);
|
||||
printf("Error: All three values must be nonzero.\n");
|
||||
printArguments();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Initialize the timer.
|
||||
time_t t0;
|
||||
time(&t0);
|
||||
|
||||
// If specified, then convert to GDAL TIFF.
|
||||
char readFileName[1024];
|
||||
strcpy(readFileName, inputFileName);
|
||||
if (convert) {
|
||||
char cmd[4096];
|
||||
sprintf(readFileName, "temp.tif\0");
|
||||
sprintf(cmd, ".\\gdal\\gdal_translate %s temp.tif\n", inputFileName);
|
||||
system(cmd);
|
||||
}
|
||||
|
||||
// Read DSM as SHORT.
|
||||
// Input can be GeoTIFF or LAS.
|
||||
shr3d::OrthoImage<unsigned short> dsmImage;
|
||||
shr3d::OrthoImage<unsigned short> minImage;
|
||||
printf("Reading DSM as SHORT.\n");
|
||||
int len = (int) strlen(inputFileName);
|
||||
char *ext = &inputFileName[len - 3];
|
||||
printf("File Type = .%s\n", ext);
|
||||
if (strcmp(ext, "tif") == 0) {
|
||||
bool ok = dsmImage.read(readFileName);
|
||||
if (!ok) return -1;
|
||||
} else if ((strcmp(ext, "las") == 0) || (strcmp(ext, "bpf") == 0)) {
|
||||
// First get the max Z values for the DSM.
|
||||
bool ok = dsmImage.readFromPointCloud(inputFileName, (float) dh_meters, shr3d::MAX_VALUE);
|
||||
if (!ok) return -1;
|
||||
|
||||
// Median filter, replacing only points differing by more than the AGL threshold.
|
||||
dsmImage.medianFilter(1, (unsigned int) (agl_meters / dsmImage.scale));
|
||||
|
||||
// Fill small voids in the DSM.
|
||||
dsmImage.fillVoidsPyramid(true, 2);
|
||||
|
||||
// Write the DSM image as FLOAT.
|
||||
char dsmOutFileName[1024];
|
||||
sprintf(dsmOutFileName, "%s_DSM.tif\0", inputFileName);
|
||||
dsmImage.write(dsmOutFileName, true);
|
||||
|
||||
// Now get the minimum Z values for the DTM.
|
||||
ok = minImage.readFromPointCloud(inputFileName, (float) dh_meters, shr3d::MIN_VALUE);
|
||||
if (!ok) return -1;
|
||||
|
||||
// Median filter, replacing only points differing by more than the AGL threshold.
|
||||
minImage.medianFilter(1, (unsigned int) (agl_meters / minImage.scale));
|
||||
|
||||
// Fill small voids in the DSM.
|
||||
minImage.fillVoidsPyramid(true, 2);
|
||||
|
||||
// Write the MIN image as FLOAT.
|
||||
char minOutFileName[1024];
|
||||
sprintf(minOutFileName, "%s_MIN.tif\0", inputFileName);
|
||||
minImage.write(minOutFileName, true);
|
||||
|
||||
// Find many of the trees by comparing MIN and MAX. Set their values to void.
|
||||
for (int j = 0; j < dsmImage.height; j++) {
|
||||
for (int i = 0; i < dsmImage.width; i++) {
|
||||
float minValue = (float) minImage.data[j][i];
|
||||
|
||||
// This check is to avoid penalizing spurious returns under very tall buildings.
|
||||
// CAUTION: This is a hack to address an observed lidar sensor issue and may not generalize well.
|
||||
if (((float) dsmImage.data[j][i] - minValue) < (40.0 / minImage.scale)) {
|
||||
// If this is in the trees, then set to void.
|
||||
bool found = false;
|
||||
double threshold = dz_meters / dsmImage.scale;
|
||||
int i1 = MAX(0, i - 1);
|
||||
int i2 = MIN(i + 1, dsmImage.width - 1);
|
||||
int j1 = MAX(0, j - 1);
|
||||
int j2 = MIN(j + 1, dsmImage.height - 1);
|
||||
for (int jj = j1; jj <= j2; jj++) {
|
||||
for (int ii = i1; ii <= i2; ii++) {
|
||||
float diff = (float) dsmImage.data[jj][ii] - minValue;
|
||||
if (diff < threshold) found = true;
|
||||
}
|
||||
}
|
||||
if (!found) dsmImage.data[j][i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the DSM2 image as FLOAT.
|
||||
char dsm2OutFileName[1024];
|
||||
sprintf(dsm2OutFileName, "%s_DSM2.tif\0", inputFileName);
|
||||
dsmImage.write(dsm2OutFileName, true);
|
||||
} else {
|
||||
printf("Error: Unrecognized file type.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Convert horizontal and vertical uncertainty values to bin units.
|
||||
int dh_bins = MAX(1, (int) floor(dh_meters / dsmImage.gsd));
|
||||
printf("DZ_METERS = %f\n", dz_meters);
|
||||
printf("DH_METERS = %f\n", dh_meters);
|
||||
printf("DH_BINS = %d\n", dh_bins);
|
||||
unsigned int dz_short = (unsigned int) (dz_meters / dsmImage.scale);
|
||||
printf("DZ_SHORT = %d\n", dz_short);
|
||||
printf("AGL_METERS = %f\n", agl_meters);
|
||||
unsigned int agl_short = (unsigned int) (agl_meters / dsmImage.scale);
|
||||
printf("AGL_SHORT = %d\n", agl_short);
|
||||
printf("AREA_METERS = %f\n", min_area_meters);
|
||||
|
||||
// Generate label image.
|
||||
shr3d::OrthoImage<unsigned long> labelImage;
|
||||
labelImage.Allocate(dsmImage.width, dsmImage.height);
|
||||
labelImage.easting = dsmImage.easting;
|
||||
labelImage.northing = dsmImage.northing;
|
||||
labelImage.zone = dsmImage.zone;
|
||||
labelImage.gsd = dsmImage.gsd;
|
||||
|
||||
// Allocate a DTM image as SHORT and copy in the DSM values.
|
||||
shr3d::OrthoImage<unsigned short> dtmImage;
|
||||
dtmImage.Allocate(dsmImage.width, dsmImage.height);
|
||||
dtmImage.easting = dsmImage.easting;
|
||||
dtmImage.northing = dsmImage.northing;
|
||||
dtmImage.zone = dsmImage.zone;
|
||||
dtmImage.gsd = dsmImage.gsd;
|
||||
dtmImage.scale = dsmImage.scale;
|
||||
dtmImage.offset = dsmImage.offset;
|
||||
dtmImage.bands = dsmImage.bands;
|
||||
for (int j = 0; j < dsmImage.height; j++) {
|
||||
for (int i = 0; i < dsmImage.width; i++) {
|
||||
dtmImage.data[j][i] = minImage.data[j][i];
|
||||
}
|
||||
}
|
||||
|
||||
// Classify ground points.
|
||||
shr3d::Shr3dder::classifyGround(labelImage, dsmImage, dtmImage, dh_bins, dz_short);
|
||||
|
||||
// For DSM voids, also set DTM value to void.
|
||||
printf("Setting DTM values to VOID where DSM is VOID...\n");
|
||||
for (int j = 0; j < dsmImage.height; j++) {
|
||||
for (int i = 0; i < dsmImage.width; i++) {
|
||||
if (dsmImage.data[j][i] == 0) dtmImage.data[j][i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Median filter, replacing only points differing by more than the DZ threshold.
|
||||
dtmImage.medianFilter(1, (unsigned int) (dz_meters / dsmImage.scale));
|
||||
|
||||
// Refine the object label image and export building outlines.
|
||||
shr3d::Shr3dder::classifyNonGround(dsmImage, dtmImage, labelImage, dz_short, agl_short, (float) min_area_meters);
|
||||
|
||||
// Fill small voids in the DTM after all processing is complete.
|
||||
dtmImage.fillVoidsPyramid(true, 2);
|
||||
|
||||
// Write the DTM image as FLOAT.
|
||||
char dtmOutFileName[1024];
|
||||
sprintf(dtmOutFileName, "%s_DTM.tif\0", inputFileName);
|
||||
dtmImage.write(dtmOutFileName, true, egm96);
|
||||
|
||||
// Produce a classification raster image with LAS standard point classes.
|
||||
shr3d::OrthoImage<unsigned char> classImage;
|
||||
classImage.Allocate(labelImage.width, labelImage.height);
|
||||
classImage.easting = dsmImage.easting;
|
||||
classImage.northing = dsmImage.northing;
|
||||
classImage.zone = dsmImage.zone;
|
||||
classImage.gsd = dsmImage.gsd;
|
||||
for (int j = 0; j < classImage.height; j++) {
|
||||
for (int i = 0; i < classImage.width; i++) {
|
||||
// Set default as unlabeled.
|
||||
classImage.data[j][i] = LAS_UNCLASSIFIED;
|
||||
|
||||
// Label trees.
|
||||
if ((dsmImage.data[j][i] == 0) ||
|
||||
(fabs((float) dsmImage.data[j][i] - (float) dtmImage.data[j][i]) > agl_short))
|
||||
classImage.data[j][i] = LAS_TREE;
|
||||
|
||||
// Label buildings.
|
||||
if (labelImage.data[j][i] == 1) classImage.data[j][i] = LAS_BUILDING;
|
||||
|
||||
// Label ground.
|
||||
if (fabs((float) dsmImage.data[j][i] - (float) dtmImage.data[j][i]) < dz_short)
|
||||
classImage.data[j][i] = LAS_GROUND;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill missing labels inside building regions.
|
||||
shr3d::Shr3dder::fillInsideBuildings(classImage);
|
||||
|
||||
// Write the classification image.
|
||||
char classOutFileName[1024];
|
||||
sprintf(classOutFileName, "%s_class.tif\0", inputFileName);
|
||||
classImage.write(classOutFileName, false, egm96);
|
||||
for (int j = 0; j < classImage.height; j++) {
|
||||
for (int i = 0; i < classImage.width; i++) {
|
||||
if (classImage.data[j][i] != LAS_BUILDING) classImage.data[j][i] = 0;
|
||||
}
|
||||
}
|
||||
sprintf(classOutFileName, "%s_buildings.tif\0", inputFileName);
|
||||
classImage.write(classOutFileName, false, egm96);
|
||||
|
||||
// Report total elapsed time.
|
||||
time_t t1;
|
||||
time(&t1);
|
||||
printf("Total time elapsed = %f seconds\n", double(t1 - t0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
727
src/shr3d/shr3d.cpp
Normal file
727
src/shr3d/shr3d.cpp
Normal file
@@ -0,0 +1,727 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
// shr3d.cpp
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
#include <climits>
|
||||
#include <vector>
|
||||
#include "shr3d.h"
|
||||
|
||||
using namespace shr3d;
|
||||
|
||||
// Extend object boundaries to capture points missed around the edges.
|
||||
void extendObjectBoundaries(OrthoImage<unsigned short> &dsmImage, OrthoImage<unsigned long> &labelImage,
|
||||
int edgeResolution, unsigned int minDistanceShortValue) {
|
||||
// Loop enough to capture the edge resolution.
|
||||
for (unsigned int k = 0; k < edgeResolution; k++) {
|
||||
// First, label any close neighbor LABEL_TEMP.
|
||||
for (unsigned int j = 1; j < labelImage.height - 1; j++) {
|
||||
for (unsigned int i = 1; i < labelImage.width - 1; i++) {
|
||||
// For any labeled point, check all neighbors.
|
||||
if (labelImage.data[j][i] == 1) {
|
||||
for (unsigned int jj = j - 1; jj <= j + 1; jj++) {
|
||||
for (unsigned int ii = i - 1; ii <= i + 1; ii++) {
|
||||
if (labelImage.data[jj][ii] == 1) continue;
|
||||
if (((float) dsmImage.data[j][i] - (float) dsmImage.data[jj][ii]) <
|
||||
minDistanceShortValue / 2.0) {
|
||||
labelImage.data[jj][ii] = LABEL_TEMP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then label any high points labeled LABEL_TEMP as an object of interest.
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
if (labelImage.data[j][i] == LABEL_TEMP) {
|
||||
// Check to make sure this point is also higher than one of its neighbors.
|
||||
unsigned int j1 = MAX(0, j - 1);
|
||||
unsigned int j2 = MIN(j + 1, labelImage.height - 1);
|
||||
unsigned int i1 = MAX(0, i - 1);
|
||||
unsigned int i2 = MIN(i + 1, labelImage.width - 1);
|
||||
for (unsigned int jj = j1; jj <= j2; jj++) {
|
||||
for (unsigned int ii = i1; ii <= i2; ii++) {
|
||||
if (((float) dsmImage.data[j][i] - (float) dsmImage.data[jj][ii]) >
|
||||
minDistanceShortValue / 2.0) {
|
||||
labelImage.data[j][i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset any temporary values.
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
if (labelImage.data[j][i] == LABEL_TEMP) {
|
||||
labelImage.data[j][i] = LABEL_GROUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Label boundaries of objects above ground level.
|
||||
void labelObjectBoundaries(OrthoImage<unsigned short> &dsmImage, OrthoImage<unsigned long> &labelImage,
|
||||
int edgeResolution, unsigned int minDistanceShortValue) {
|
||||
// Initialize the labels to LABEL_GROUND.
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
labelImage.data[j][i] = LABEL_GROUND;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the label image with object boundaries.
|
||||
int radius = 2;
|
||||
float threshold = (float) minDistanceShortValue;
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
// Look for Z steps greater than a threshold.
|
||||
float value = (float) dsmImage.data[j][i];
|
||||
|
||||
// Interestingly, this works about as well as checking every step.
|
||||
for (int dj = -edgeResolution; dj <= edgeResolution; dj += edgeResolution) {
|
||||
for (int di = -edgeResolution; di <= edgeResolution; di += edgeResolution) {
|
||||
int j2 = MIN(MAX(0, j + dj), dsmImage.height - 1);
|
||||
int i2 = MIN(MAX(0, i + di), dsmImage.width - 1);
|
||||
if (dsmImage.data[j2][i2] != 0) {
|
||||
// Remove local slope to avoid tagging rough terrain.
|
||||
int j3 = MIN(MAX(0, j + dj * 2), dsmImage.height - 1);
|
||||
int i3 = MIN(MAX(0, i + di * 2), dsmImage.width - 1);
|
||||
float myGradient = (float) dsmImage.data[j][i] - (float) dsmImage.data[j2][i2];
|
||||
float neighborGradient = (float) dsmImage.data[j2][i2] - (float) dsmImage.data[j3][i3];
|
||||
float distance = (myGradient - neighborGradient);
|
||||
if (distance > threshold) labelImage.data[j][i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill inside the object countour labels if points are above the nearby ground level.
|
||||
void fillObjectBounds(OrthoImage<unsigned long> &labelImage, OrthoImage<unsigned short> &dsmImage, ObjectType &obj,
|
||||
int edgeResolution, unsigned int dzShort) {
|
||||
unsigned int label = obj.label;
|
||||
|
||||
// Loop on rows, filling in labels.
|
||||
for (unsigned int j = MAX(0, obj.ymin - 1); j <= MIN(obj.ymax + 1, labelImage.height - 1); j++) {
|
||||
// Get start index.
|
||||
int startIndex = -1;
|
||||
for (unsigned int i = MAX(0, obj.xmin - 1); i <= MIN(obj.xmax + 1, labelImage.width - 1); i++) {
|
||||
if (labelImage.data[j][i] == label) {
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no labels in this row, then continue.
|
||||
if (startIndex == -1) continue;
|
||||
|
||||
// Get stop index.
|
||||
int stopIndex = -1;
|
||||
for (unsigned int i = MIN(obj.xmax + 1, labelImage.width - 1); i >= MAX(0, obj.xmin - 1); i--) {
|
||||
if (labelImage.data[j][i] == label) {
|
||||
stopIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If entire column is labeled, then continue.
|
||||
if ((startIndex == 0) && (stopIndex == labelImage.width-1)) continue;
|
||||
|
||||
// Get max ground level height for this row.
|
||||
// If the DSM image value is void, then the ground level value is zero, so that's ok.
|
||||
unsigned short groundLevel;
|
||||
if (startIndex == 0)
|
||||
groundLevel = dsmImage.data[j][stopIndex + 1];
|
||||
else if (stopIndex == labelImage.width - 1)
|
||||
groundLevel = dsmImage.data[j][startIndex - 1];
|
||||
else
|
||||
groundLevel = MAX(dsmImage.data[j][startIndex - 1], dsmImage.data[j][stopIndex + 1]);
|
||||
|
||||
// Fill in the label for any point in between that has height above ground level.
|
||||
for (unsigned int i = startIndex; i <= stopIndex; i++) {
|
||||
if (dsmImage.data[j][i] > groundLevel) {
|
||||
if (labelImage.data[j][i] != label) labelImage.data[j][i] = LABEL_IN_ONE;
|
||||
} else {
|
||||
if (labelImage.data[j][i] == label) labelImage.data[j][i] = LABEL_GROUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop on columns, filling in labels.
|
||||
for (unsigned int i = MAX(0, obj.xmin - 1); i <= MIN(obj.xmax + 1, labelImage.width - 1); i++) {
|
||||
// Get start index.
|
||||
int startIndex = -1;
|
||||
for (unsigned int j = MAX(0, obj.ymin - 1); j <= MIN(obj.ymax + 1, labelImage.height - 1); j++) {
|
||||
if (labelImage.data[j][i] == label) {
|
||||
startIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no labels in this row, then continue.
|
||||
if (startIndex == -1) continue;
|
||||
|
||||
// Get stop index.
|
||||
int stopIndex = -1;
|
||||
for (unsigned int j = MIN(obj.ymax + 1, labelImage.height - 1); j >= MAX(0, obj.ymin - 1); j--) {
|
||||
if (labelImage.data[j][i] == label) {
|
||||
stopIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If entire row is labeled, then continue.
|
||||
if ((startIndex == 0) && (stopIndex == labelImage.height-1)) continue;
|
||||
|
||||
// Get max ground level height for this row.
|
||||
unsigned short groundLevel;
|
||||
if (startIndex == 0)
|
||||
groundLevel = dsmImage.data[stopIndex + 1][i];
|
||||
else if (stopIndex == labelImage.height - 1)
|
||||
groundLevel = dsmImage.data[startIndex - 1][i];
|
||||
else
|
||||
groundLevel = MAX(dsmImage.data[startIndex - 1][i], dsmImage.data[stopIndex + 1][i]);
|
||||
|
||||
// Fill in the label for any point in between that has height above ground level.
|
||||
// This time make sure both the horizontal and vertical check pass and set to LABEL_ACCEPTED.
|
||||
for (unsigned int j = startIndex; j <= stopIndex; j++) {
|
||||
if (dsmImage.data[j][i] > groundLevel) {
|
||||
if ((labelImage.data[j][i] == label) || (labelImage.data[j][i] == LABEL_IN_ONE)) {
|
||||
labelImage.data[j][i] = LABEL_ACCEPTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Erode the labels with a kernel size based on edge resolution.
|
||||
int rad = edgeResolution;
|
||||
for (unsigned int j = MAX(0, obj.ymin - 1); j <= MIN(obj.ymax + 1, labelImage.height - 1); j++) {
|
||||
for (unsigned int i = MAX(0, obj.xmin - 1); i <= MIN(obj.xmax + 1, labelImage.width - 1); i++) {
|
||||
if (labelImage.data[j][i] == LABEL_ACCEPTED) {
|
||||
int i1 = MAX(i - rad, 0);
|
||||
int i2 = MIN(i + rad, labelImage.width - 1);
|
||||
int j1 = MAX(j - rad, 0);
|
||||
int j2 = MIN(j + rad, labelImage.height - 1);
|
||||
for (unsigned int jj = j1; jj <= j2; jj++) {
|
||||
for (unsigned int ii = i1; ii <= i2; ii++) {
|
||||
if (labelImage.data[jj][ii] != LABEL_ACCEPTED) labelImage.data[jj][ii] = LABEL_TEMP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update object bounds to include erosion.
|
||||
obj.xmin = MAX(0, obj.xmin - edgeResolution - 1);
|
||||
obj.ymin = MAX(0, obj.ymin - edgeResolution - 1);
|
||||
obj.xmax = MIN(obj.xmax + edgeResolution + 1, labelImage.width - 1);
|
||||
obj.ymax = MIN(obj.ymax + edgeResolution + 1, labelImage.height - 1);
|
||||
|
||||
for (unsigned int j = obj.ymin; j <= obj.ymax; j++) {
|
||||
for (unsigned int i = obj.xmin; i <= obj.xmax; i++) {
|
||||
if (labelImage.data[j][i] == LABEL_TEMP) {
|
||||
labelImage.data[j][i] = LABEL_ACCEPTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finish up the labels.
|
||||
for (unsigned int j = MAX(0, obj.ymin - 1); j <= MIN(obj.ymax + 1, labelImage.height - 1); j++) {
|
||||
for (unsigned int i = MAX(0, obj.xmin - 1); i <= MIN(obj.xmax + 1, labelImage.width - 1); i++) {
|
||||
if (labelImage.data[j][i] == label) labelImage.data[j][i] = LABEL_GROUND;
|
||||
if (labelImage.data[j][i] == LABEL_ACCEPTED) labelImage.data[j][i] = LABEL_OBJECT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add neighboring pixels to an object.
|
||||
bool addNeighbors(std::vector<PixelType> &neighbors, OrthoImage<unsigned long> &labelImage,
|
||||
OrthoImage<unsigned short> &dsmImage, ObjectType &obj, unsigned int dzShort) {
|
||||
// Get neighbors for all pixels in the list.
|
||||
std::vector<PixelType> newNeighbors;
|
||||
for (long k = 0; k < neighbors.size(); k++) {
|
||||
long i = neighbors[k].i;
|
||||
long j = neighbors[k].j;
|
||||
float value = (float) dsmImage.data[j][i];
|
||||
for (long jj = MAX(0, j - 1); jj <= MIN(j + 1, labelImage.height - 1); jj++) {
|
||||
for (long ii = MAX(0, i - 1); ii <= MIN(i + 1, labelImage.width - 1); ii++) {
|
||||
// Skip if pixel is already labeled or if it is LABEL_GROUND.
|
||||
// Note that non-ground labels are initialized with 1.
|
||||
if (labelImage.data[jj][ii] > 1) continue;
|
||||
|
||||
// Skip if height is too different.
|
||||
if (fabs((float) dsmImage.data[jj][ii] - (float) dsmImage.data[j][i]) > (float) dzShort) continue;
|
||||
|
||||
// Update the label.
|
||||
labelImage.data[jj][ii] = labelImage.data[j][i];
|
||||
|
||||
// Add to the new list.
|
||||
PixelType pixel = {ii, jj};
|
||||
newNeighbors.push_back(pixel);
|
||||
|
||||
// Update object bounds.
|
||||
obj.xmin = MIN(obj.xmin, ii);
|
||||
obj.xmax = MAX(obj.xmax, ii);
|
||||
obj.ymin = MIN(obj.ymin, jj);
|
||||
obj.ymax = MAX(obj.ymax, jj);
|
||||
obj.count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the neighbors list with the new one. No need to merge them.
|
||||
if (newNeighbors.size() > 0) {
|
||||
neighbors = newNeighbors;
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
// Group connected labeled pixels into objects.
|
||||
void groupObjects(OrthoImage<unsigned long> &labelImage, OrthoImage<unsigned short> &dsmImage,
|
||||
std::vector<ObjectType> &objects, long maxCount, unsigned int dzShort) {
|
||||
// Sweep from top left to bottom right, assigning object labels.
|
||||
long maxGroupSize = 0;
|
||||
unsigned int label = 1;
|
||||
int bigCount = 0;
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
// Skip unlabeled pixels.
|
||||
if (labelImage.data[j][i] == LABEL_GROUND) continue;
|
||||
|
||||
// Skip already labeled pixels.
|
||||
if (labelImage.data[j][i] > 1) continue;
|
||||
|
||||
// Create a new label.
|
||||
label++;
|
||||
|
||||
// Create a new object structure.
|
||||
ObjectType obj;
|
||||
|
||||
// Initialize the object and neighbors structures.
|
||||
obj.label = label;
|
||||
obj.xmin = i;
|
||||
obj.ymin = j;
|
||||
obj.xmax = i;
|
||||
obj.ymax = j;
|
||||
obj.count = 1;
|
||||
std::vector<PixelType> neighbors;
|
||||
PixelType pixel = {i, j};
|
||||
neighbors.push_back(pixel);
|
||||
labelImage.data[j][i] = label;
|
||||
|
||||
// Get points in new group.
|
||||
bool keepSearching = true;
|
||||
while (keepSearching) {
|
||||
keepSearching = addNeighbors(neighbors, labelImage, dsmImage, obj, dzShort);
|
||||
|
||||
// This is very quick but not especially smart.
|
||||
// Try to find a reasonably quick way to split the regions more sensibly.
|
||||
// That said, this also seems to work well enough.
|
||||
if (obj.count > maxCount) {
|
||||
bigCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add this object to the list.
|
||||
objects.push_back(obj);
|
||||
maxGroupSize = MAX(maxGroupSize, obj.count);
|
||||
}
|
||||
}
|
||||
printf("Max group size = %ld\n", maxGroupSize);
|
||||
printf("Number of cropped groups = %d\n", bigCount);
|
||||
}
|
||||
|
||||
// Finish label image for display as image overlay in QT Reader.
|
||||
// Set all labeled values to 1. Leave all unlabeled values LABEL_GROUND.
|
||||
void finishLabelImage(OrthoImage<unsigned long> &labelImage) {
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
if ((labelImage.data[j][i] != LABEL_GROUND)) {
|
||||
labelImage.data[j][i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Classify ground points, fill the voids, and generate a bare earth terrain model.
|
||||
void Shr3dder::classifyGround(OrthoImage<unsigned long> &labelImage, OrthoImage<unsigned short> &dsmImage,
|
||||
OrthoImage<unsigned short> &dtmImage, int dhBins, unsigned int dzShort) {
|
||||
// Fill voids.
|
||||
printf("Filling voids...\n");
|
||||
dtmImage.fillVoidsPyramid(true);
|
||||
|
||||
// Allocate a binary label image to indicate voids to be filled.
|
||||
// The long integer label image has unique labels for objects detected in each iteration.
|
||||
OrthoImage<unsigned char> voidImage;
|
||||
voidImage.Allocate(labelImage.width, labelImage.height);
|
||||
|
||||
// Iteratively label and remove objects from the DEM.
|
||||
// Each new iteration removes debris not identified by the previous iteration.
|
||||
int numIterations = 5;
|
||||
long maxCount = 10000 / (dsmImage.gsd * dsmImage.gsd); // max count is in meters
|
||||
for (unsigned int k = 0; k < numIterations; k++) {
|
||||
printf("Iteration #%d\n", k + 1);
|
||||
|
||||
// Label the object boundaries.
|
||||
printf("Labeling object boundaries...\n");
|
||||
labelObjectBoundaries(dtmImage, labelImage, dhBins, dzShort);
|
||||
|
||||
// Extend labels for object boundaries..
|
||||
printf("Extending object boundaries...\n");
|
||||
extendObjectBoundaries(dtmImage, labelImage, dhBins, dzShort);
|
||||
|
||||
// Group the objects.
|
||||
printf("Grouping objects...\n");
|
||||
std::vector<ObjectType> objects;
|
||||
groupObjects(labelImage, dtmImage, objects, maxCount, dzShort);
|
||||
printf("Number of objects = %ld\n", objects.size());
|
||||
|
||||
// Generate object groups and void fill them in the DEM image.
|
||||
printf("Labeling and removing objects...\n");
|
||||
for (long i = 0; i < objects.size(); i++) {
|
||||
fillObjectBounds(labelImage, dtmImage, objects[i], dhBins, dzShort);
|
||||
}
|
||||
|
||||
// Update the label image values for easy viewing.
|
||||
printf("Finishing label image for display...\n");
|
||||
finishLabelImage(labelImage);
|
||||
|
||||
// Update the void image.
|
||||
printf("Updating void image...\n");
|
||||
for (unsigned int j = 0; j < voidImage.height; j++) {
|
||||
for (unsigned int i = 0; i < voidImage.width; i++) {
|
||||
if (labelImage.data[j][i] == 1) voidImage.data[j][i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill voids.
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
if (voidImage.data[j][i] == 1) dtmImage.data[j][i] = 0;
|
||||
}
|
||||
}
|
||||
bool noSmoothing = true;
|
||||
if (k == numIterations - 1) noSmoothing = false;
|
||||
printf("Filling voids with noSmoothing = %d\n", noSmoothing);
|
||||
dtmImage.fillVoidsPyramid(noSmoothing);
|
||||
}
|
||||
|
||||
// If any DTM points are above the DSM, then restore the DSM values.
|
||||
for (unsigned int j = 0; j < dtmImage.height; j++) {
|
||||
for (unsigned int i = 0; i < dtmImage.width; i++) {
|
||||
if (dtmImage.data[j][i] >= dsmImage.data[j][i]) {
|
||||
dtmImage.data[j][i] = dsmImage.data[j][i];
|
||||
labelImage.data[j][i] = LABEL_GROUND;
|
||||
voidImage.data[j][i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any leftover single point spikes.
|
||||
printf("Removing spikes...\n");
|
||||
for (unsigned int j = 0; j < dtmImage.height; j++) {
|
||||
for (unsigned int i = 0; i < dtmImage.width; i++) {
|
||||
float minDiff = FLT_MAX;
|
||||
for (int jj = -1; jj <= 1; jj++) {
|
||||
int j2 = MAX(0, MIN(dsmImage.height - 1, j + jj));
|
||||
for (int ii = -1; ii <= 1; ii++) {
|
||||
if ((ii == 0) && (jj == 0)) continue;
|
||||
int i2 = MAX(0, MIN(dsmImage.width - 1, i + ii));
|
||||
float diff = MAX(0, (float) dtmImage.data[j][i] - (float) dtmImage.data[j2][i2]);
|
||||
minDiff = MIN(minDiff, diff);
|
||||
}
|
||||
}
|
||||
if (minDiff > dzShort / 2.0) {
|
||||
labelImage.data[j][i] = 1;
|
||||
voidImage.data[j][i] = 1;
|
||||
dtmImage.data[j][i] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill voids.
|
||||
printf("Filling voids...\n");
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
if (voidImage.data[j][i] == 1) dtmImage.data[j][i] = 0;
|
||||
}
|
||||
}
|
||||
dtmImage.fillVoidsPyramid(false);
|
||||
|
||||
// Mark all voids.
|
||||
printf("Marking voids in label image after all iterations are complete...\n");
|
||||
for (unsigned int j = 0; j < voidImage.height; j++) {
|
||||
for (unsigned int i = 0; i < voidImage.width; i++) {
|
||||
if (voidImage.data[j][i] == 1) labelImage.data[j][i] = 1;
|
||||
else labelImage.data[j][i] = LABEL_GROUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Classify non-ground points.
|
||||
void Shr3dder::classifyNonGround(OrthoImage<unsigned short> &dsmImage, OrthoImage<unsigned short> &dtmImage,
|
||||
OrthoImage<unsigned long> &labelImage, unsigned int dzShort, unsigned int aglShort,
|
||||
float minAreaMeters) {
|
||||
// Compute minimum number of points based on threshold given for area.
|
||||
// Note that ISPRS challenges indicate that performance is dramatically better for structures larger than 50m area.
|
||||
int minPointCount = int(minAreaMeters / (dsmImage.gsd * dsmImage.gsd));
|
||||
printf("Min points for removing small objects = %d\n", minPointCount);
|
||||
|
||||
// Apply AGL threshold to individual points to reduce clutter.
|
||||
// Note that ground level clutter tends to be less than 2m AGL.
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
if (labelImage.data[j][i] != LABEL_GROUND) {
|
||||
if (dsmImage.data[j][i] == 0) labelImage.data[j][i] = LABEL_GROUND;
|
||||
else if (((float) dsmImage.data[j][i] - (float) dtmImage.data[j][i]) < (float) aglShort)
|
||||
labelImage.data[j][i] = LABEL_GROUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group the labeled objects based on height similarity.
|
||||
{
|
||||
std::vector<ObjectType> objects;
|
||||
groupObjects(labelImage, dsmImage, objects, LONG_MAX, dzShort / 2);
|
||||
|
||||
// Accept or reject each object independently based on boundary values and flatness.
|
||||
int numRejected = 0;
|
||||
for (unsigned int k = 0; k < objects.size(); k++) {
|
||||
bool reject = false;
|
||||
|
||||
// Reject if boundary gradients with neighbors labeled ground are too small.
|
||||
float meanGradient = 0.0;
|
||||
int count = 0;
|
||||
for (int j = objects[k].ymin; j <= objects[k].ymax; j++) {
|
||||
for (unsigned int i = objects[k].xmin; i <= objects[k].xmax; i++) {
|
||||
if (labelImage.data[j][i] == objects[k].label) {
|
||||
for (int jj = -1; jj <= 1; jj++) {
|
||||
unsigned int j2 = MAX(0, MIN(dsmImage.height - 1, j + jj));
|
||||
for (int ii = -1; ii <= 1; ii++) {
|
||||
unsigned int i2 = MAX(0, MIN(dsmImage.width - 1, i + ii));
|
||||
//if (labelImage.data[j2][i2] != objects[k].label)
|
||||
if (labelImage.data[j2][i2] == LABEL_GROUND) {
|
||||
unsigned int j3 = MAX(0, MIN(dsmImage.height - 1, j + jj * 2));
|
||||
unsigned int i3 = MAX(0, MIN(dsmImage.width - 1, i + ii * 2));
|
||||
|
||||
// These assume I'm higher than my neighbors.
|
||||
float myGradient = MAX(0, ((float) dsmImage.data[j][i] -
|
||||
(float) dsmImage.data[j2][i2]));
|
||||
float neighborGradient = MAX(0, ((float) dsmImage.data[j2][i2] -
|
||||
(float) dsmImage.data[j3][i3]));
|
||||
meanGradient += MAX(0, (myGradient - neighborGradient));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
meanGradient /= count;
|
||||
if ((meanGradient < dzShort / 2.0) && (meanGradient != 0.0)) reject = true;
|
||||
|
||||
// If rejected, then label each point.
|
||||
if (reject) {
|
||||
numRejected++;
|
||||
for (unsigned int j = objects[k].ymin; j <= objects[k].ymax; j++) {
|
||||
for (unsigned int i = objects[k].xmin; i <= objects[k].xmax; i++) {
|
||||
if (labelImage.data[j][i] == objects[k].label) {
|
||||
labelImage.data[j][i] = LABEL_GROUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Erode and then dilate labels to remove narrow objects.
|
||||
{
|
||||
OrthoImage<unsigned long> tempImage;
|
||||
tempImage.Allocate(labelImage.width, labelImage.height);
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
tempImage.data[j][i] = labelImage.data[j][i];
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < labelImage.height; j++) {
|
||||
for (int i = 0; i < labelImage.width; i++) {
|
||||
if (labelImage.data[j][i] != LABEL_GROUND) {
|
||||
// Define neighbor bounds;
|
||||
int i1 = MAX(0, i - 1);
|
||||
int i2 = MIN(i + 1, labelImage.width - 1);
|
||||
int j1 = MAX(0, j - 1);
|
||||
int j2 = MIN(j + 1, labelImage.height - 1);
|
||||
|
||||
// Unlabel any point with an unlabeled neighbor.
|
||||
for (int jj = j1; jj <= j2; jj++) {
|
||||
for (int ii = i1; ii <= i2; ii++) {
|
||||
if (labelImage.data[jj][ii] == LABEL_GROUND) {
|
||||
tempImage.data[j][i] = LABEL_GROUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < labelImage.height; j++) {
|
||||
for (int i = 0; i < labelImage.width; i++) {
|
||||
if (labelImage.data[j][i] != LABEL_GROUND) {
|
||||
// Define neighbor bounds;
|
||||
int i1 = MAX(0, i - 1);
|
||||
int i2 = MIN(i + 1, labelImage.width - 1);
|
||||
int j1 = MAX(0, j - 1);
|
||||
int j2 = MIN(j + 1, labelImage.height - 1);
|
||||
|
||||
// Unlabel any point with no labeled neighbors after erosion.
|
||||
bool found = false;
|
||||
for (int jj = j1; jj <= j2; jj++) {
|
||||
for (int ii = i1; ii <= i2; ii++) {
|
||||
if (tempImage.data[jj][ii] != LABEL_GROUND) found = true;
|
||||
}
|
||||
}
|
||||
if (!found) labelImage.data[j][i] = LABEL_GROUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset all non-ground labels to one.
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
if (labelImage.data[j][i] != LABEL_GROUND) labelImage.data[j][i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Group labeled points to remove small objects.
|
||||
// Do not split groups based on height or point count.
|
||||
{
|
||||
printf("Grouping to remove small objects...\n");
|
||||
std::vector<ObjectType> objects;
|
||||
groupObjects(labelImage, dsmImage, objects, LONG_MAX, INT_MAX);
|
||||
int numRejected = 0;
|
||||
for (unsigned int k = 0; k < objects.size(); k++) {
|
||||
bool reject = false;
|
||||
|
||||
// Reject if too small.
|
||||
if (objects[k].count < minPointCount) reject = true;
|
||||
|
||||
// If rejected, then label each point.
|
||||
if (reject) {
|
||||
numRejected++;
|
||||
for (unsigned int j = objects[k].ymin; j <= objects[k].ymax; j++) {
|
||||
for (unsigned int i = objects[k].xmin; i <= objects[k].xmax; i++) {
|
||||
if (labelImage.data[j][i] == objects[k].label) {
|
||||
labelImage.data[j][i] = LABEL_GROUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("Number of small objects rejected = %d\n", numRejected);
|
||||
}
|
||||
|
||||
// Reset all non-ground labels to one.
|
||||
for (unsigned int j = 0; j < labelImage.height; j++) {
|
||||
for (unsigned int i = 0; i < labelImage.width; i++) {
|
||||
if (labelImage.data[j][i] != LABEL_GROUND) labelImage.data[j][i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add neighboring pixels to a void group.
|
||||
bool
|
||||
addClassNeighbors(std::vector<PixelType> &neighbors, OrthoImage<unsigned char> &classImage,
|
||||
OrthoImage<unsigned char> &labeled, unsigned int label) {
|
||||
// Get neighbors for all pixels in the list.
|
||||
std::vector<PixelType> newNeighbors;
|
||||
for (long k = 0; k < neighbors.size(); k++) {
|
||||
long i = neighbors[k].i;
|
||||
long j = neighbors[k].j;
|
||||
for (long jj = MAX(0, j - 1); jj <= MIN(j + 1, classImage.height - 1); jj++) {
|
||||
for (long ii = MAX(0, i - 1); ii <= MIN(i + 1, classImage.width - 1); ii++) {
|
||||
// If already labeled, then skip.
|
||||
if (labeled.data[jj][ii] == 1) continue;
|
||||
|
||||
// If not the same label, then skip.
|
||||
if (classImage.data[jj][ii] != label) continue;
|
||||
|
||||
// Update the label.
|
||||
labeled.data[jj][ii] = 1;
|
||||
|
||||
// Add to the new list.
|
||||
PixelType pixel = {ii, jj};
|
||||
newNeighbors.push_back(pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the neighbors list.
|
||||
if (newNeighbors.size() > 0) {
|
||||
for (int k = 0; k < newNeighbors.size(); k++) {
|
||||
neighbors.push_back(newNeighbors[k]);
|
||||
}
|
||||
// neighbors = newNeighbors;
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
// Fill in any pixels labeled tree that fall entirely within a labeled building group.
|
||||
void Shr3dder::fillInsideBuildings(OrthoImage<unsigned char> &classImage) {
|
||||
int numFilled = 0;
|
||||
OrthoImage<unsigned char> labeled;
|
||||
labeled.Allocate(classImage.width, classImage.height);
|
||||
for (unsigned int j = 0; j < classImage.height; j++) {
|
||||
for (unsigned int i = 0; i < classImage.width; i++) {
|
||||
bool consider = ((labeled.data[j][i] == 0) && (classImage.data[j][i] == LAS_TREE));
|
||||
if (!consider) continue;
|
||||
|
||||
// Get all pixels in this contiguous group.
|
||||
std::vector<PixelType> neighbors;
|
||||
PixelType pixel = {i, j};
|
||||
neighbors.push_back(pixel);
|
||||
bool keepSearching = true;
|
||||
unsigned int label = (unsigned int) classImage.data[j][i];
|
||||
labeled.data[j][i] = 1;
|
||||
while (keepSearching) {
|
||||
keepSearching = addClassNeighbors(neighbors, classImage, labeled, label);
|
||||
}
|
||||
|
||||
// Check all pixels for neighboring labels.
|
||||
bool inside = true;
|
||||
for (int k = 0; k < neighbors.size(); k++) {
|
||||
long i1 = neighbors[k].i;
|
||||
long j1 = neighbors[k].j;
|
||||
for (long jj = MAX(0, j1 - 1); jj <= MIN(j1 + 1, classImage.height - 1); jj++) {
|
||||
for (long ii = MAX(0, i1 - 1); ii <= MIN(i1 + 1, classImage.width - 1); ii++) {
|
||||
if ((labeled.data[jj][ii] == 0) && (classImage.data[jj][ii] != LAS_BUILDING)) {
|
||||
inside = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the region is completely inside a building region, then fill it.
|
||||
if (inside) {
|
||||
for (int k = 0; k < neighbors.size(); k++) {
|
||||
numFilled++;
|
||||
classImage.data[neighbors[k].j][neighbors[k].i] = LAS_BUILDING;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
printf("Removed %d tree pixels inside building label groups.\n", numFilled);
|
||||
}
|
||||
|
||||
76
src/shr3d/shr3d.h
Normal file
76
src/shr3d/shr3d.h
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
// shr3d.h
|
||||
//
|
||||
|
||||
#ifndef PUBGEO_SHR3D_H
|
||||
#define PUBGEO_SHR3D_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include "orthoimage.h"
|
||||
|
||||
#define LABEL_OBJECT 1
|
||||
#define LABEL_GROUND UINT_MAX
|
||||
#define LABEL_IN_ONE UINT_MAX-1
|
||||
#define LABEL_ACCEPTED UINT_MAX-2
|
||||
#define LABEL_TEMP UINT_MAX-3
|
||||
#define LABEL_BOUNDARY UINT_MAX-5
|
||||
|
||||
/*
|
||||
* Las Classifications list
|
||||
0 Created, never classified
|
||||
1 Unclassified1
|
||||
2 Ground
|
||||
3 Low Vegetation
|
||||
4 Medium Vegetation
|
||||
5 High Vegetation
|
||||
6 Building
|
||||
7 Low Point (noise)
|
||||
8 Model Key-point (mass point)
|
||||
9 Water
|
||||
10 Reserved for ASPRS Definition
|
||||
11 Reserved for ASPRS Definition
|
||||
12 Overlap Points2
|
||||
13-31 Reserved for ASPRS Definition
|
||||
*/
|
||||
#define LAS_UNCLASSIFIED (1*40)
|
||||
#define LAS_GROUND (2*40)
|
||||
#define LAS_TREE (5*40)
|
||||
#define LAS_BUILDING (6*40)
|
||||
// For now, multiply these by 40 so we can view the images easily.
|
||||
|
||||
namespace shr3d {
|
||||
using namespace pubgeo;
|
||||
|
||||
typedef struct {
|
||||
unsigned int i;
|
||||
unsigned int j;
|
||||
} PixelType;
|
||||
|
||||
typedef struct {
|
||||
unsigned int label;
|
||||
long int xmin;
|
||||
long int xmax;
|
||||
long int ymin;
|
||||
long int ymax;
|
||||
long int count;
|
||||
} ObjectType;
|
||||
|
||||
class Shr3dder {
|
||||
public:
|
||||
// Function declarations.
|
||||
static void classifyGround(OrthoImage<unsigned long> &labelImage, OrthoImage<unsigned short> &dsmImage,
|
||||
OrthoImage<unsigned short> &dtmImage, int dhBins, unsigned int dzShort);
|
||||
|
||||
static void classifyNonGround(OrthoImage<unsigned short> &dsmImage, OrthoImage<unsigned short> &dtmImage,
|
||||
OrthoImage<unsigned long> &labelImage, unsigned int dzShort,
|
||||
unsigned int aglShort,
|
||||
float minAreaMeters);
|
||||
|
||||
static void fillInsideBuildings(OrthoImage<unsigned char> &classImage);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // PUBGEO_SHR3D_H
|
||||
36
src/shr3d/test-main.cpp
Normal file
36
src/shr3d/test-main.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2017 The Johns Hopkins University Applied Physics Laboratory.
|
||||
// Licensed under the MIT License. See LICENSE.txt in the project root for full license information.
|
||||
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include "PointCloud.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char *fileName = "test.las";
|
||||
switch (argc) {
|
||||
case 2:
|
||||
fileName = argv[1];
|
||||
case 1: {
|
||||
pubgeo::PointCloud pointCloud;
|
||||
std::cerr << "Processing file: " << fileName << std::endl;
|
||||
if (pointCloud.Read(fileName)) {
|
||||
// Grab a truly random c++ 11 number
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_int_distribution<> dis(0, pointCloud.numPoints);
|
||||
long int index = dis(gen);
|
||||
std::cerr << pointCloud.numPoints << " points in file." << std::endl
|
||||
<< "Here is a sample point from index " << index << ":"
|
||||
<< pointCloud.x(index) << ", " << pointCloud.y(index) << ", "
|
||||
<< pointCloud.z(index) << std::endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cerr << "Invalid number of arguments" << std::endl;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
system("pause");
|
||||
#endif
|
||||
}
|
||||
36
windowsInstallation.txt
Normal file
36
windowsInstallation.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
These instructions are not complete and do not currently result in a working build. It is close, and subsets of the project have worked with PDAL. The final project could not be entirely integrated.
|
||||
|
||||
Download and run OSGeo4W64 Installer.
|
||||
Select advanced installation. Everything else until packages is default.
|
||||
From Command Line Utilities select:
|
||||
Gdal, gdal-dev, pdal
|
||||
Install packages to meet dependencies. (page after package selection)
|
||||
|
||||
Download and install visual studio 2017 community (REQUIRES SYSTEM-RESTART)
|
||||
Install packages:
|
||||
.Net desktop development (possibly unnecessary)
|
||||
Desktop development with c++
|
||||
** include c++/cli support, std lib mods, mfc and atl, win8.1 sdk **
|
||||
https://stackoverflow.com/questions/42701019/problems-generating-solution-for-vs-2017-with-cmake
|
||||
This combination of packages generates a compatible visual studio environment. Some packages may be unnecessary (mfc +atl, or .net desktop env)
|
||||
|
||||
Load project (folder with CmakeLists.txt) AS 64 BIT!! VS will probably default you to X86
|
||||
Visual studio should start processing the cmake file
|
||||
cmake will probably fail:
|
||||
Missing c/cxx compiler
|
||||
see above package installation, you probably missed a package that was necessary in 2017 vs installation
|
||||
pdal config fails "file or directory x referenced by variable _foo does no exist ! (typo from source)
|
||||
edit C:\OSGeo4W64\lib\pdal\cmake\PDALConfig.cmake
|
||||
replace lines 31+ with: (Changing the OSGEO4W64_INSTALLATION_LOCATION respective to your installation directory) This is due PDAL being compiled on a different machine with a different folder structure
|
||||
set(OSGEO4W64_INSTALLATION_LOCATION "C:/OSGeo4W64/")
|
||||
set(PDAL_INCLUDE_DIRS "${OSGEO4W64_INSTALLATION_LOCATION}include")
|
||||
set(PDAL_LIBRARY_DIRS "${OSGEO4W64_INSTALLATION_LOCATION}lib")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/PDALTargets.cmake")
|
||||
set(PDAL_LIBRARIES ${PDAL_LIBRARY_DIRS}/pdalcpp.lib ${PDAL_LIBRARY_DIRS}/pdal_util.lib)
|
||||
check_required_components(PDAL)
|
||||
|
||||
These instructions will generate a linker error missing what appear to be network conversions for ints/shorts/etc referenced in PDALs BEExtractor.
|
||||
Possible solutions (Scott's best guesses)
|
||||
Perhaps un-defining 'WIN32_LEAN_AND_MEAN'? It was used to remove macros that were breaking the build. IE MAX(..) and MIN(..)
|
||||
PDAL build for windows on OSGeo package improperly built/configured (Partly already true
|
||||
Build PDAL on local machine and
|
||||
Reference in New Issue
Block a user