...
 
Commits (47)
......@@ -3,4 +3,7 @@
.travis.yml
demo.R
_pkgdown.yml
docs
\ No newline at end of file
docs
DISCLAIMER.md
code.json
appveyor.yml
\ No newline at end of file
Package: ncdfgeom
Type: Package
Title: NetCDF Geometry and Timeseries
Version: 0.5.0
Date: 2017-07-17
Title: 'NetCDF' Geometry and Timeseries
Version: 1.0.0
Date: 2019-06-05
Authors@R: c(person("David", "Blodgett", role = c("aut", "cre"),
email = "dblodgett@usgs.gov"),
person("Luke", "Winslow", role = "ctb"))
Description: Tools to create discrete sampling geometry and spatial geometry NetCDF files.
URL: https://github.com/USGS-R/ncdfgeom
Description: Tools to create timeseries and geometry 'NetCDF' files.
URL: https://code.usgs.gov/water/ncdfgeom
BugReports: https://github.com/USGS-R/ncdfgeom/issues
Imports:
ncdf4,
sf,
sp,
rgdal,
methods,
dplyr,
rgeos,
ncmeta
Imports: RNetCDF, ncmeta, sf, dplyr, methods
Depends:
R (>= 3.0)
Suggests:
testthat,
geoknife,
maptools,
knitr,
rmarkdown,
pkgdown,
tidyr
Remotes: hypertidy/ncmeta
Suggests: testthat, knitr, rmarkdown, pkgdown, tidyverse, sp, geoknife, ncdf4, jsonlite
License: CC0
LazyData: TRUE
Encoding: UTF-8
......
......@@ -5,30 +5,36 @@ export(read_geometry)
export(read_timeseries_dsg)
export(write_attribute_data)
export(write_geometry)
export(write_point_dsg)
export(write_timeseries_dsg)
importFrom(RNetCDF,att.put.nc)
importFrom(RNetCDF,close.nc)
importFrom(RNetCDF,create.nc)
importFrom(RNetCDF,dim.def.nc)
importFrom(RNetCDF,dim.inq.nc)
importFrom(RNetCDF,open.nc)
importFrom(RNetCDF,utcal.nc)
importFrom(RNetCDF,var.def.nc)
importFrom(RNetCDF,var.get.nc)
importFrom(RNetCDF,var.inq.nc)
importFrom(RNetCDF,var.put.nc)
importFrom(dplyr,filter)
importFrom(dplyr,group_by)
importFrom(dplyr,select)
importFrom(methods,is)
importFrom(ncdf4,nc_close)
importFrom(ncdf4,nc_create)
importFrom(ncdf4,nc_open)
importFrom(ncdf4,ncatt_get)
importFrom(ncdf4,ncatt_put)
importFrom(ncdf4,ncdim_def)
importFrom(ncdf4,ncvar_add)
importFrom(ncdf4,ncvar_def)
importFrom(ncdf4,ncvar_get)
importFrom(ncdf4,ncvar_put)
importFrom(ncmeta,nc_atts)
importFrom(ncmeta,nc_axes)
importFrom(ncmeta,nc_dim)
importFrom(ncmeta,nc_meta)
importFrom(sf,st_as_sf)
importFrom(sp,CRS)
importFrom(sp,Line)
importFrom(sp,Lines)
importFrom(sp,Polygon)
importFrom(sp,Polygons)
importFrom(sp,SpatialLines)
importFrom(sp,SpatialLinesDataFrame)
importFrom(sp,SpatialPoints)
importFrom(sp,SpatialPointsDataFrame)
importFrom(sp,SpatialPolygons)
importFrom(sp,SpatialPolygonsDataFrame)
importFrom(sp,polygons)
importFrom(sf,st_coordinates)
importFrom(sf,st_crs)
importFrom(sf,st_geometry)
importFrom(sf,st_geometry_type)
importFrom(sf,st_linestring)
importFrom(sf,st_multilinestring)
importFrom(sf,st_multipolygon)
importFrom(sf,st_polygon)
importFrom(sf,st_set_geometry)
importFrom(sf,st_sf)
importFrom(sf,st_sfc)
importFrom(stats,setNames)
.onAttach <- function(libname, pkgname) {
packageStartupMessage("This information is preliminary or provisional
and is subject to revision. It is being provided
to meet the need for timely best science. The
information has not received final approval by the
U.S. Geological Survey (USGS) and is provided on the
condition that neither the USGS nor the U.S. Government
shall be held liable for any damages resulting from the
authorized or unauthorized use of the information.
****Support Package****
packageStartupMessage("****Support Package****
This package is a USGS-R Support package.
see: https://owi.usgs.gov/R/packages.html#support")
}
......@@ -54,9 +45,32 @@ pkg.env$lon_coord_var_standard_name <- "longitude"
pkg.env$alt_coord_var_standard_name <- "height"
pkg.env$timeseries_id_cf_role <- "timeseries_id"
check_geomData <- function(geomData) {
if (any(c("sf", "sfc") %in% class(geomData))) {
geomData <- sf::as_Spatial(geomData)
pkg.env$nc_types <- list(double = "NC_DOUBLE", float = "NC_FLOAT", numeric="NC_DOUBLE", short = "NC_SHORT", integer = "NC_INT", char="NC_CHAR", character="NC_CHAR")
#' @importFrom sf st_as_sf
check_geom_data <- function(geom_data) {
if (!any(c("sf", "sfc") %in% class(geom_data))) {
geom_data <- sf::st_as_sf(geom_data)
}
return(geomData)
return(geom_data)
}
add_var <- function(nc, name, dim, type, units = NA, missing = NA, long_name = NA, char_dim_len = NULL, data = NULL) {
if(type == "NC_CHAR") {
suppressWarnings(if(is.null(char_dim_len) & is.null(data)) stop("can't determine character dim length"))
if(is.null(char_dim_len)) suppressWarnings(char_dim_len <- max(sapply(data, function(x) max(nchar(x),
na.rm = TRUE)),
na.rm = TRUE))
char_dim <- paste0(name,"_char")
dim.def.nc(nc, char_dim, char_dim_len, unlim = FALSE)
dim <- c(char_dim, dim)
}
var.def.nc(nc, name, type, dim)
if(!any(is.na(units)))
att.put.nc(nc, name, "units", "NC_CHAR", units)
if(!is.na(missing))
att.put.nc(nc, name, "missing_value", type, missing)
if(!is.na(long_name))
att.put.nc(nc, name, "long_name", "NC_CHAR", long_name)
}
#'@title Check NetCDF-DSG File
#'
#'
#'@param nc A open ncdf4 object.
#'@param nc A NetCDF path or URL to be opened.
#'
#'@description
#'Introspects a netcdf file and tries to interpret it as a NetCDF-DSG file. Returns a named
#'Introspects a netCDF file and tries to interpret it as a NetCDF-DSG file. Returns a named
#'\code{list} containing \code{instance_id} \code{instance_dim} \code{node_count}
#'\code{part_node_count} \code{part_type} If these values aren't found or aren't applicable,
#'they are returned \code{NULL}.
......@@ -12,7 +12,8 @@
#'@references
#'https://github.com/twhiteaker/netCDF-CF-simple-geometry
#'
#'@importFrom ncdf4 ncatt_get
#'@importFrom ncmeta nc_atts nc_axes nc_dim
#'@importFrom stats setNames
#'
#'@noRd
#'
......@@ -23,11 +24,14 @@ check_netcdf <- function(nc) {
geom_container <- list(geom_type = NULL, node_count = NULL, part_node_count = NULL,
part_type = NULL, x = NULL, y = NULL)
atts <- nc_atts(nc)
# Check important global atts
if(!grepl('CF',ncatt_get(nc,0,'Conventions')$value)) {
check <- get_att(atts, "NC_GLOBAL", "Conventions")$value
if(length(check) > 0 && !grepl('CF', check)) {
warning('File does not advertise CF conventions, unexpected behavior may result.')}
geom_container_var<-findVarByAtt(nc, pkg.env$geom_type_attr_name, strict = FALSE)
geom_container_var<-find_var_by_att(atts, pkg.env$geom_type_attr_name)
if(length(geom_container_var) > 1) {
stop("only one geometry container per file supported")
......@@ -36,19 +40,19 @@ check_netcdf <- function(nc) {
} else {
geom_container_var <- geom_container_var[[1]]
geom_container$geom_type <- ncatt_get(nc, geom_container_var, pkg.env$geom_type_attr_name)$value
geom_container$geom_type <- as.character(get_att(atts, geom_container_var, pkg.env$geom_type_attr_name)$value)
geom_container$node_count <- ncatt_get(nc, geom_container_var, pkg.env$node_count_attr_name)$value
geom_container$node_count <- as.character(get_att(atts, geom_container_var, pkg.env$node_count_attr_name)$value)
geom_container$part_node_count <- ncatt_get(nc, geom_container_var, pkg.env$part_node_count_attr_name)$value
geom_container$part_node_count <- as.character(get_att(atts, geom_container_var, pkg.env$part_node_count_attr_name)$value)
geom_container$part_type <- ncatt_get(nc, geom_container_var, pkg.env$part_type_attr_name)$value
geom_container$part_type <- as.character(get_att(atts, geom_container_var, pkg.env$part_type_attr_name)$value)
node_coordinates <- strsplit(ncatt_get(nc, geom_container_var, pkg.env$node_coordinates)$value, " ")[[1]]
node_coordinates <- strsplit(get_att(atts, geom_container_var, pkg.env$node_coordinates)$value[[1]], " ")[[1]]
for(v in node_coordinates) {
att <- ncatt_get(nc, v, "axis")
if(att$hasatt) {
att <- get_att(atts, v, "axis")
if(nrow(att) != 0) {
if(att$value == pkg.env$x_axis) {
geom_container$x <- v
} else if(att$value == pkg.env$y_axis) {
......@@ -59,52 +63,64 @@ check_netcdf <- function(nc) {
}
}
variable_list <- findVarByAtt(nc, pkg.env$geometry_container_att_name, geom_container_var)
variable_list <- find_var_by_att(atts, pkg.env$geometry_container_att_name, geom_container_var)
}
# Look for variable with the timeseries_id in it.
instance_id<-list()
instance_id<-append(instance_id, findVarByAtt(nc, 'cf_role', 'timeseries_id'))
instance_id<-append(instance_id, find_var_by_att(atts, 'cf_role', 'timeseries_id'))
instance_id<-unlist(unique(instance_id))
if(length(instance_id)>1) { stop('multiple timeseries id variables were found.') }
if(geom_container$node_count == 0) {
instance_dim <- nc$var[geom_container$x][[1]]$dim[[1]]$name
if(length(geom_container$node_count) == 0) {
instance_dim <- nc_dim(nc, nc_axes(nc, geom_container$x)$dimension)$name
} else {
instance_dim <- nc$var[geom_container$node_count][[1]]$dim[[1]]$name }
instance_dim <- nc_dim(nc, nc_axes(nc, geom_container$node_count)$dimension)$name
}
crs_referents <- c(findVarByAtt(nc, "grid_mapping", strict="false"))
crs_referents <- c(find_var_by_att(atts, "grid_mapping"))
crs <- list()
if(length(crs_referents) > 0) {
for(crs_referent in crs_referents) {
crs <- c(crs, ncatt_get(nc, crs_referent, "grid_mapping")$value)
crs <- c(crs, get_att(atts, crs_referent, "grid_mapping")$value)
}
if(length(unique(crs)) > 1) {
warning("Only one crs is supported, more than one was found, may be handling projections wrong.")
crs <- crs[1]
}
crs <- ncatt_get(nc, crs[[1]])
crs <- get_att(atts, crs[[1]])
crs <- stats::setNames(crs$value, crs$name)
}
return(list(instance_id = instance_id,
out <- list(instance_id = instance_id,
instance_dim = instance_dim,
geom_container = geom_container,
variable_list = variable_list,
crs = crs))
crs = crs)
class(out) <- geom_container$geom_type
return(out)
}
findVarByAtt <- function(nc, attribute, value = ".*", strict = TRUE) {
foundVar<-list()
for(variable in c(names(nc$var), names(nc$dim))) {
temp<-try(ncatt_get(nc,variable,attribute))
if(strict) value<-paste0("^",value,"$")
if(!is.null(temp$hasatt) && temp$hasatt && grepl(value,temp$value)) {
foundVar<-append(foundVar,variable)
}
get_att <- function(atts, var, att = NULL) {
name <- value <- variable <- NULL
if(is.null(att)) {
filter(atts, variable == var)
} else {
filter(atts, variable == var, name == att)
}
return(foundVar)
}
find_var_by_att <- function(atts, attribute, search_value = ".*", strict = TRUE) {
name <- value <- NULL
if(strict) search_value <-paste0("^", search_value, "$")
filter(atts, name == attribute, grepl(search_value, value))$variable
}
#'@title Read attribute dataframe from NetCDF-DSG file
#'
#'@param nc An open ncdf4 object.
#'@param nc A NetCDF path or urlto be opened.
#'@param instance_dim The NetCDF instance/station dimension.
#'
#'@description
......@@ -8,30 +8,51 @@
#'This function is intended as a convenience to be used within workflows where
#'the netCDF file is already open and well understood.
#'
#'@export
#'
#' @export
#' @importFrom ncmeta nc_meta
#' @importFrom RNetCDF open.nc close.nc var.get.nc
#' @importFrom dplyr filter
#' @examples
#' hucPolygons <- sf::read_sf(system.file('extdata','example_huc_eta.json', package = 'ncdfgeom'))
#' hucPolygons_nc <- ncdfgeom::write_geometry(tempfile(), hucPolygons)
#'
#' nc <- ncdf4::nc_open(hucPolygons_nc)
#' read_attribute_data(nc, "instance")
#' read_attribute_data(hucPolygons_nc, "instance")
#'
read_attribute_data <- function(nc, instance_dim) {
name <- variable <- NULL
nc_meta <- nc_meta(nc)
if(!any(grepl(instance_dim, names(nc$dim)))) {
if(is.character(nc)) {
nc <- open.nc(nc)
on.exit(close.nc(nc), add = TRUE)
}
nc_dim <- filter(nc_meta$dimension, name == instance_dim)
if(nrow(nc_dim) == 0) {
stop("The instance dimension was not found in the provided NetCDF object.")
}
dataFrame <- as.data.frame(list(id = 1:nc$dim[instance_dim][[1]]$len))
dataFrame <- as.data.frame(list(id = 1:nc_dim$length))
for(var in nc$var) {
if(var$ndims==1 && grepl(var$dim[[1]]$name, instance_dim)) {
dataFrame[var$name] <- c(ncvar_get(nc, var$name))
} else if(grepl(var$prec, paste0("^char$")) &&
(grepl(var$dim[[1]]$name, instance_dim) ||
grepl(var$dim[[2]]$name, instance_dim)))
dataFrame[var$name] <- c(ncvar_get(nc, var$name))
for(i in 1:nrow(nc_meta$variable)) {
var <- nc_meta$variable[i, ]
axis <- filter(nc_meta$axis, variable == var$name)
if(var$ndims == 1 && axis$dimension == nc_dim$id) {
if(var$type == "NC_INT") {
dataFrame[var$name] <- as.integer(c(var.get.nc(nc, var$name)))
} else {
dataFrame[var$name] <- c(var.get.nc(nc, var$name))
}
} else if(var$type == "NC_CHAR" &&
(nc_dim$id %in% axis$dimension))
dataFrame[var$name] <- tryCatch({
c(var.get.nc(nc, var$name))
}, error = function(e) {
t(var.get.nc(nc, var$name))
})
}
dataFrame[] <- lapply(dataFrame, make.true.NA)
......
......@@ -3,17 +3,20 @@
#'@param nc_file character file path to the nc file to be read.
#'
#'@description
#'Attemps to convert a NetCDF-CF DSG Simple Geometry file into a sf data.frame.
#'Attempts to convert a NetCDF-CF DSG Simple Geometry file into a sf data.frame.
#'
#'@references
#'https://github.com/twhiteaker/netCDF-CF-simple-geometry
#'http://cfconventions.org/index.html
#'
#'@importFrom ncdf4 nc_open nc_close ncvar_get ncatt_get
#'@importFrom sp Polygon Polygons SpatialPolygons SpatialPolygonsDataFrame CRS Line Lines SpatialLines SpatialLinesDataFrame SpatialPointsDataFrame
#'@importFrom sf st_as_sf
#'@importFrom RNetCDF open.nc var.get.nc close.nc
#'@importFrom sf st_sf st_sfc st_linestring st_polygon st_multipolygon st_multilinestring st_crs
#'
#'@return sf \code{data.frame} containing spatial geometry of type found in the NetCDF-CF DSG file.
#'
#'@references
#' \enumerate{
#' \item \url{http://cfconventions.org/cf-conventions/cf-conventions.html#_features_and_feature_types}
#' }
#'@export
#'
#'@examples
......@@ -21,121 +24,170 @@
#'file.copy(system.file('extdata','example_huc_eta.nc', package = 'ncdfgeom'),
#' huc_eta_nc, overwrite = TRUE)
#'
#'hucTimeseries <- ncdf4::nc_open(huc_eta_nc)
#'vars <- ncmeta::nc_vars(huc_eta_nc)
#'
#'hucPolygons <- sf::read_sf(system.file('extdata','example_huc_eta.json', package = 'ncdfgeom'))
#'plot(sf::st_geometry(hucPolygons))
#'names(hucPolygons)
#'
#'hucPolygons_nc <- ncdfgeom::write_geometry(nc_file=huc_eta_nc,
#' geomData = hucPolygons,
#' geom_data = hucPolygons,
#' instance_dim_name = "station",
#' variables = hucTimeseries$var)
#' variables = vars$name)
#'huc_poly <- read_geometry(huc_eta_nc)
#'plot(sf::st_geometry(huc_poly))
#'names(huc_poly)
#'
read_geometry = function(nc_file) {
nc <- open.nc(nc_file)
on.exit(close.nc(nc), add = TRUE)
nc_props <- check_netcdf(nc)
return(read_geom_data(nc_props, nc))
}
nc <- nc_open(nc_file)
checkVals <- check_netcdf(nc)
instance_id<-checkVals$instance_id
instance_dim<-checkVals$instance_dim
geom_container <- checkVals$geom_container
variable_list <- checkVals$variable_list
crs <- checkVals$crs
#' @noRd
read_geom_data <- function(nc_props, nc) UseMethod("read_geom_data")
#' @noRd
#' @name read_geom_data
read_geom_data.point <- function(nc_props, nc) {
coord <- get_coords(nc, nc_props)
prj <- get_prj_nc(nc_props)
point_data <- data.frame(x = coord$x, y = coord$y)
data_frame <- read_attribute_data(nc, nc_props$instance_dim)
if(nrow(data_frame) != nrow(point_data)) {
stop("Reading multipoint is not supported yet.")
# This is where handling for multipoint would go.
}
return(st_as_sf(cbind(point_data, data_frame), crs = st_crs(prj), coords = c("x", "y")))
}
line<-FALSE
poly<-FALSE
point<-FALSE
if(grepl("polygon", geom_container$geom_type)) { poly<-TRUE
} else if(grepl("line", geom_container$geom_type)) { line<-TRUE
} else point <- TRUE
#' @noRd
#' @name read_geom_data
read_geom_data.line <- function(nc_props, nc) {
return(parse_geom(nc_props, nc))
}
xCoords <- c(ncvar_get(nc, geom_container$x))
yCoords <- c(ncvar_get(nc, geom_container$y))
#' @noRd
#' @name read_geom_data
read_geom_data.polygon <- function(nc_props, nc) {
return(parse_geom(nc_props, nc))
}
get_coords <- function(nc, nc_props) {
list(x = c(var.get.nc(nc, nc_props$geom_container$x)),
y = c(var.get.nc(nc, nc_props$geom_container$y)))
}
if(length(crs) == 0) {
get_prj_nc <- function(nc_props) {
if(length(nc_props$crs) == 0) {
warning("no crs found, using WGS84")
prj <- "+proj=longlat +datum=WGS84"
} else {
prj <- ncmeta::nc_gm_to_prj(crs)
prj <- ncmeta::nc_gm_to_prj(nc_props$crs)
}
}
if(point) {
point_data <- matrix(c(xCoords,
yCoords), ncol=2)
dataFrame <- read_attribute_data(nc, instance_dim)
if(nrow(dataFrame) != nrow(point_data)) {
stop("Reading multipoint is not supported yet.")
# This is where handling for multipoint would go.
}
SPGeom <- SpatialPointsDataFrame(point_data, proj4string = CRS(prj),
data = dataFrame, match.ID = FALSE)
populate_nc_props <- function(nc_props, nc) {
nc_props$geom_container$node_count <- c(var.get.nc(nc, nc_props$geom_container$node_count))
if(length(nc_props$geom_container$part_node_count) > 0) {
nc_props$geom_container$part_node_count <- var.get.nc(nc, nc_props$geom_container$part_node_count)
} else {
node_count <- c(ncvar_get(nc, geom_container$node_count))
if(is.character(geom_container$part_node_count)) {
part_node_count <- ncvar_get(nc, geom_container$part_node_count)
} else {
part_node_count <- node_count
}
if(is.character(geom_container$part_type)) {
part_type <- ncvar_get(nc, geom_container$part_type)
} else {
part_type <- rep(pkg.env$multi_val, length(part_node_count))
}
node_start <- 1
geom_node_stop <- 0
pInd <- 1
Srl <- list()
for(geom in 1:length(node_count)) {
geom_node_stop <- geom_node_stop + node_count[geom]
srl <- list()
while(node_start < geom_node_stop) {
part_node_stop <- node_start + part_node_count[pInd] - 1
if(part_type[pInd] == pkg.env$hole_val) { hole <- TRUE
} else { hole <- FALSE }
coords <- matrix(c(xCoords[node_start:part_node_stop],yCoords[node_start:part_node_stop]),ncol=2)
if(poly) { tsrl<-Polygon(coords, hole=hole)
} else if(line) { tsrl<-Line(coords) }
dimnames(tsrl@coords) <- list(NULL, c(pkg.env$x_nodes, pkg.env$y_nodes))
srl <- append(srl, tsrl)
nc_props$geom_container$part_node_count <- nc_props$geom_container$node_count
}
if(length(nc_props$geom_container$part_type) > 0) {
nc_props$geom_container$part_type <- var.get.nc(nc, nc_props$geom_container$part_type)
} else {
nc_props$geom_container$part_type <- rep(pkg.env$multi_val, length(nc_props$geom_container$part_node_count))
}
return(nc_props)
}
node_start <- node_start + part_node_count[pInd]; pInd <- pInd + 1
parse_geom <- function(nc_props, nc) {
coord <- get_coords(nc, nc_props)
prj <- get_prj_nc(nc_props)
nc_props <- populate_nc_props(nc_props, nc)
poly <- class(nc_props) == "polygon"
node_start <- 1
geom_node_stop <- 0
p_ind <- 1
f_list <- list()
multi_geometry <- FALSE
for(geom in 1:length(nc_props$geom_container$node_count)) {
multi_g <- FALSE
geom_node_stop <- geom_node_stop + nc_props$geom_container$node_count[geom]
p_list <- list()
p <- 0
while(node_start < geom_node_stop) {
p <- p + 1
part_node_stop <- node_start + nc_props$geom_container$part_node_count[p_ind] - 1
coords <- list(matrix(c(coord$x[node_start:part_node_stop],
coord$y[node_start:part_node_stop]),
ncol = 2))
dimnames(coords[[1]]) <- list(NULL, c(pkg.env$x_nodes, pkg.env$y_nodes))
# Assume not multi to start
if(nc_props$geom_container$part_type[p_ind] == pkg.env$hole_val) {
p <- p - 1
p_list[[p]] <- append(p_list[[p]], coords)
} else {
if(p == 2) {
multi_g <- TRUE
p_list <- append(p_list, list(coords))
} else if(p > 2) {
p_list <- append(p_list, list(coords))
} else {
p_list <- append(p_list, list(coords))
}
}
node_start <- node_start + nc_props$geom_container$part_node_count[p_ind]
p_ind <- p_ind + 1
}
if(multi_g) {
multi_geometry <- TRUE
if(poly) {
Srl <- append(Srl, Polygons(srl, as.character(geom)))
} else if(line) {
Srl <- append(Srl, Lines(srl, as.character(geom)))
f_list <- append(f_list, list(st_multipolygon(p_list)))
} else {
if(p_ind > 1) {
p_list <- unlist(p_list, recursive = FALSE)
f_list <- append(f_list, list(st_multilinestring(p_list)))
} else {
f_list <- append(f_list, list(st_linestring(p_list)))
}
}
}
dataFrame <- read_attribute_data(nc, instance_dim)
for(varName in names(dataFrame)) {
if(!varName %in% variable_list) {
dataFrame[varName] <- NULL
} else {
p_list <- unlist(p_list, recursive = FALSE)
if(poly) {
f_list <- append(f_list, list(st_polygon(p_list)))
} else {
f_list <- append(f_list, list(st_linestring(p_list[[1]])))
}
}
if(poly) {
SPGeom <- SpatialPolygonsDataFrame(SpatialPolygons(Srl, proj4string = CRS(prj)),
dataFrame, match.ID = FALSE)
} else if(line) {
SPGeom <- SpatialLinesDataFrame(SpatialLines(Srl, proj4string = CRS(prj)),
dataFrame, match.ID = FALSE)
}
df <- read_attribute_data(nc, nc_props$instance_dim)
for(var_name in names(df)) {
if(!var_name %in% nc_props$variable_list) {
df[var_name] <- NULL
}
}
nc_close(nc)
return(sf::st_as_sf(SPGeom))
}
return(st_sf(geom = st_sfc(f_list, crs = st_crs(prj)), check_ring_dir = FALSE, df,
stringsAsFactors = FALSE, agr = "constant", sfc_last = TRUE))
}
\ No newline at end of file
This diff is collapsed.
......@@ -2,10 +2,10 @@
#'
#' @param nc_file \code{character} file path to the nc file to be created.
#' If adding to a file, it must already have the named instance dimension.
#'@param attData \code{data.frame} with instances as rows and attributes as rows.
#'@param att_data \code{data.frame} with instances as columns and attributes as rows.
#'@param instance_dim_name \code{character} name for the instance dimension. Defaults to "instance"
#'@param units \code{character} vector with units for each column of attData. Defaults to "unknown" for all.
#'@param ... additional arguments to be passed on to \code{nc_create}.
#'@param units \code{character} vector with units for each column of att_data. Defaults to "unknown" for all.
#'@param overwrite boolean overwrite existing file? Will append if FALSE.
#'
#'@description
#'Creates a NetCDF file with an instance dimension, and any attributes from a data frame.
......@@ -14,7 +14,7 @@
#'This function does not implement any CF convention attributes or standard names.
#'Any columns of class date will be converted to character.
#'
#'@importFrom ncdf4 nc_open ncvar_add nc_create nc_close ncvar_def ncvar_put ncdim_def
#'@importFrom RNetCDF create.nc open.nc close.nc dim.def.nc dim.inq.nc var.def.nc var.put.nc att.put.nc
#'@importFrom methods is
#'
#'@export
......@@ -26,66 +26,81 @@
#' example_file <-write_attribute_data(tempfile(), sample_data,
#' units = rep("unknown", ncol(sample_data)))
#'
#' ncdump <- system(paste("ncdump -h", example_file), intern = TRUE)
#' cat(ncdump ,sep = "\n")
#' try({
#' ncdump <- system(paste("ncdump -h", example_file), intern = TRUE)
#' cat(ncdump ,sep = "\n")
#' }, silent = TRUE)
#'
write_attribute_data <- function(nc_file, attData, instance_dim_name = "instance", units = rep("unknown", ncol(attData)), ...) {
write_attribute_data <- function(nc_file, att_data, instance_dim_name = "instance",
units = rep("unknown", ncol(att_data)), overwrite = FALSE) {
if(file.exists(nc_file) & overwrite == FALSE) {
nc <- open.nc(nc_file, write = TRUE)
} else if(overwrite == TRUE) {
unlink(nc_file)
nc <- create.nc(nc_file, large = TRUE)
} else {
nc <- create.nc(nc_file, large = TRUE)
}
n <- nrow(att_data)
n <- nrow(attData)
instance_dim <- ncdim_def(instance_dim_name, '', 1:n, create_dimvar=FALSE)
instance_dim <- tryCatch({
dim.inq.nc(nc, instance_dim_name)$id
},
error = function(e) {
dim <- dim.def.nc(nc, instance_dim_name, n, unlim = FALSE)
dim.inq.nc(nc, instance_dim_name)$id
})
vars<-list()
types <- list(numeric="double", integer = "integer", character="char")
types <- list(numeric="NC_DOUBLE", integer = "NC_INT", character="NC_CHAR")
# Convert any dates to character. This could be improved later.
i <- sapply(attData, is, class2 = "Date")
attData[i] <- lapply(attData[i], as.character)
i <- sapply(att_data, is, class2 = "Date")
att_data[i] <- lapply(att_data[i], as.character)
charDimLen<-0
for(colName in names(attData)) {
if(grepl(class(attData[colName][[1]]), "character")) {
charDimLen<-max(sapply(attData[colName][[1]], nchar, keepNA=FALSE), charDimLen)
for(colName in names(att_data)) {
if(grepl(class(att_data[colName][[1]]), "character")) {
charDimLen<-max(sapply(att_data[colName][[1]], nchar, keepNA=FALSE), charDimLen)
}
}
if(charDimLen>0) {
char_dim <- ncdim_def('char', '', 1:charDimLen, create_dimvar=FALSE)
char_dim <- dim.def.nc(nc, "char", charDimLen, unlim = FALSE)
char_dim <- dim.inq.nc(nc, "char")$id
}
col <- 1
for(colName in names(attData)) {
if(grepl(class(attData[colName][[1]]), "character")) {
vars <- c(vars, list(ncvar_def(name=colName, units = units[col], dim = list(char_dim, instance_dim),
prec = types[[class(attData[colName][[1]])]])))
} else if(grepl(class(attData[colName][[1]]), "integer")) {
vars <- c(vars, list(ncvar_def(name=colName, units = units[col], dim = instance_dim,
prec = types[[class(attData[colName][[1]])]], missval = -9999)))
for(colName in names(att_data)) {
if(grepl(class(att_data[colName][[1]]), "character")) {
dim <- c(char_dim, instance_dim)
missval <- ""
char <- TRUE
att_data[colName][[1]][is.na(att_data[colName][[1]])] <- ""
} else if(grepl(class(att_data[colName][[1]]), "integer")) {
dim <- instance_dim
missval <- -9999
} else {
vars <- c(vars, list(ncvar_def(name=colName, units = units[col], dim = instance_dim,
prec = types[[class(attData[colName][[1]])]], missval = NA)))
dim <- instance_dim
missval <- -9999.999
}
var.def.nc(nc, colName, types[[class(att_data[colName][[1]])]], dim)
att.put.nc(nc, colName, "units", "NC_CHAR", units[col])
att.put.nc(nc, colName, "missing_value", types[[class(att_data[colName][[1]])]], missval)
col <- col + 1
}
close.nc(nc)
if(file.exists(nc_file)) {
nc <- nc_open(nc_file, write = TRUE)
for(var in vars) {
nc <- ncvar_add(nc, var)
}
} else {
nc <- nc_create(filename = nc_file, vars = vars, ...)
}
nc_close(nc)
nc <- nc_open(nc_file, write = TRUE)
nc <- open.nc(nc_file, write = TRUE)
if(!is.null(attData)) {
for(colName in names(attData)) {
ncvar_put(nc = nc, varid = colName, vals = attData[colName][[1]])
if(!is.null(att_data)) {
for(colName in names(att_data)) {
var.put.nc(nc, colName, att_data[colName][[1]])
}
}
nc_close(nc)
close.nc(nc)
return(nc_file)
}
This diff is collapsed.
......@@ -19,7 +19,7 @@
#' @description
#' Creates a point feature type discrete sampling features NetCDF file.
#' Returns the created filename.
#' Can pass in netcdf creation options like force_v4 to pass on to nc_create().
#' Can pass in netCDF creation options like force_v4 to pass on to nc_create().
#'
#'@references
#' \enumerate{
......@@ -27,9 +27,10 @@
#' \item \url{http://www.unidata.ucar.edu/software/thredds/current/netcdf-java/reference/FeatureDatasets/CFpointImplement.html}
#' }
#'
#'@importFrom ncdf4 nc_create nc_close ncvar_def ncvar_put ncatt_put ncdim_def
#'@importFrom RNetCDF open.nc close.nc var.def.nc var.put.nc att.put.nc
#'
#'@export
#' @noRd
#' @noMd
write_point_dsg = function(nc_file, lats, lons, alts, times, data, data_units=rep('', ncol(data)), feature_names = NULL, ...){
n = length(lats)
......@@ -52,49 +53,62 @@ write_point_dsg = function(nc_file, lats, lons, alts, times, data, data_units=re
times = as.POSIXct(times)
}
nc_file <- write_attribute_data(nc_file=nc_file, attData = data, instance_dim_name = "obs", units = data_units, ...)
instance_dim_name <- "obs"
nc <- nc_open(nc_file, write = TRUE)
nc_file <- write_attribute_data(nc_file=nc_file, att_data = data,
instance_dim_name = instance_dim_name,
units = data_units, ...)
nc <- open.nc(nc_file, write = TRUE)
#Setup our spatial and info
lat_var = ncvar_def('lat', 'degrees_north', nc$dim$obs, -999, prec='double', longname = 'latitude of the observation')
nc <- ncvar_add(nc, lat_var)
lon_var = ncvar_def('lon', 'degrees_east', nc$dim$obs, -999, prec='double', longname = 'longitude of the observation')
nc <- ncvar_add(nc, lon_var)
alt_var = ncvar_def('alt', 'm', nc$dim$obs, -999, prec='double', longname='vertical distance above the surface')
nc <- ncvar_add(nc, alt_var)
time_var = ncvar_def('time', 'days since 1970-01-01 00:00:00', nc$dim$obs, -999, prec='integer', longname = 'time stamp')
nc <- ncvar_add(nc, time_var)
var.def.nc(nc, "lat", "NC_DOUBLE", instance_dim_name)
att.put.nc(nc, "lat", "missing_value", "NC_DOUBLE", -999)
att.put.nc(nc, "lat", "long_name", "NC_CHAR", "latitude of the observation")
att.put.nc(nc, "lat", "units", "NC_CHAR", 'degrees_north')
var.def.nc(nc, "lon", "NC_DOUBLE", instance_dim_name)
att.put.nc(nc, "lon", "missing_value", "NC_DOUBLE", -999)
att.put.nc(nc, "lon", "long_name", "NC_CHAR", "longitude of the observation")
att.put.nc(nc, "lon", "units", "NC_CHAR", 'degrees_east')
var.def.nc(nc, "alt", "NC_DOUBLE", instance_dim_name)
att.put.nc(nc, "alt", "missing_value", "NC_DOUBLE", -999)
att.put.nc(nc, "alt", "long_name", "NC_CHAR", "vertical distance above the surface")
att.put.nc(nc, "alt", "units", "NC_CHAR", 'm')
nc_close(nc)
var.def.nc(nc, "time", "NC_DOUBLE", instance_dim_name)
att.put.nc(nc, "time", "long_name", "NC_CHAR", "time stamp")
att.put.nc(nc, "time", "units", "NC_CHAR", "days since 1970-01-01 00:00:00")
nc <- nc_open(nc_file, write = TRUE)
close.nc(nc)
nc <- open.nc(nc_file, write = TRUE)
#add standard_names
ncatt_put(nc, 'lat', 'standard_name', 'latitude')
ncatt_put(nc, 'lon', 'standard_name', 'longitude')
ncatt_put(nc, 'alt', 'standard_name', 'height')
ncatt_put(nc, 'time', 'standard_name', 'time')
att.put.nc(nc, 'lat', 'standard_name', "NC_CHAR", 'latitude')
att.put.nc(nc, 'lon', 'standard_name', "NC_CHAR", 'longitude')
att.put.nc(nc, 'alt', 'standard_name', "NC_CHAR", 'height')
att.put.nc(nc, 'time', 'standard_name', "NC_CHAR", 'time')
#use the same names for "standard names" and add coordinates as well
for(data_name in names(data)){
ncatt_put(nc, data_name, 'coordinates', 'lat lon alt time')
att.put.nc(nc, data_name, 'coordinates', "NC_CHAR", 'lat lon alt time')
}
#some final stuff
ncatt_put(nc, 0,'featureType','point')
ncatt_put(nc, 0,'Conventions','CF-1.7')
att.put.nc(nc, "NC_GLOBAL", 'featureType', "NC_CHAR", 'point')
att.put.nc(nc, "NC_GLOBAL", 'Conventions', "NC_CHAR", 'CF-1.7')
#Put data in NC file
if(!is.null(feature_names)) {
ncatt_put(nc, 'feature_name', 'long_name', 'Feature Name')
att.put.nc(nc, 'feature_name', 'long_name', "NC_CHAR", 'Feature Name')
}
ncvar_put(nc, 'lat', lats)
ncvar_put(nc, 'lon', lons)
ncvar_put(nc, 'alt', alts)
ncvar_put(nc, 'time', as.numeric(times)/86400) #convert to days since 1970-01-01
var.put.nc(nc, 'lat', lats)
var.put.nc(nc, 'lon', lons)
var.put.nc(nc, 'alt', alts)
var.put.nc(nc, 'time', as.numeric(times)/86400) #convert to days since 1970-01-01
nc_close(nc)
close.nc(nc)
return(nc_file)
}
This diff is collapsed.
......@@ -2,11 +2,14 @@ NetCDF-CF Geometry and Timeseries Tools for R
===
[![Build Status](https://travis-ci.org/USGS-R/ncdfgeom.svg)](https://travis-ci.org/USGS-R/ncdfgeom) [![Coverage Status](https://coveralls.io/repos/github/USGS-R/ncdfgeom/badge.svg?branch=master)](https://coveralls.io/github/USGS-R/ncdfgeom?branch=master)
`ncdfgeom` is an **in development** package that reads and writes geometry data (points lines and polygons), attributes of geometries, and time series associated with the geometries in a standards-compliant way. It implements the NetCDF-CF Spatial Geometries specification and the timeSeries feature type of the [Discrete Sampling Geometry](http://cfconventions.org/cf-conventions/cf-conventions.html#discrete-sampling-geometries) NetCDF-CF specification.
`ncdfgeom` reads and writes geometry data (points lines and polygons), attributes of geometries, and time series associated with the geometries in a standards-compliant way.
**Visit the [`pkgdown` site](http://usgs-r.github.io/ncdfgeom/dev/articles/ncdfgeom.html) for a complete overview of the package.**
It implements the NetCDF-CF Spatial Geometries specification and the timeSeries feature type of the [Discrete Sampling Geometry](http://cfconventions.org/cf-conventions/cf-conventions.html#discrete-sampling-geometries) NetCDF-CF specification.
Given that this package is in active development, please test it out and consider [submitting issues and/or contributions!](https://github.com/USGS-R/ncdfgeom/issues)
**Visit the [`pkgdown` site](http://usgs-r.github.io/ncdfgeom/articles/ncdfgeom.html) for a complete overview of the package.**
Given that this package is fairly new and in active development, please test it out
and consider [submitting issues and/or contributions!](https://github.com/USGS-R/ncdfgeom/issues)
## Installation
......@@ -16,6 +19,12 @@ install.packages("devtools")
devtools::install_github("USGS-R/ncdfgeom")
```
`ncdfgeom` will be released on CRAN in spring/summer 2019. When available on CRAN, installation will be available via:
```
install.packages("ncdfgeom")
```
## Contributing
First, thanks for considering a contribution! I hope to make this package a community created resource for us all to gain from and won’t be able to do that without your help!
......
development:
mode: devel
init:
ps: |
$ErrorActionPreference = "Stop"
Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1"
Import-Module '..\appveyor-tool.ps1'
install:
ps: Bootstrap
environment:
NOT_CRAN: "true"
build_script:
- travis-tool.sh install_deps
test_script:
- travis-tool.sh run_tests
on_failure:
- travis-tool.sh dump_logs
artifacts:
- path: '*.Rcheck\**\*.log'
name: Logs
- path: '*.Rcheck\**\*.out'
name: Logs
- path: '*.Rcheck\**\*.fail'
name: Logs
- path: '*.Rcheck\**\*.Rout'
name: Logs
- path: '\*_*.tar.gz'
name: Bits
- path: '\*_*.zip'
name: Bits
......@@ -3,8 +3,8 @@
"name": "ncdfgeom",
"organization": "U.S. Geological Survey",
"description": "NetCDF Geometry and Timeseries",
"version": "0.5.0",
"status": "Beta",
"version": "0.6.0",
"status": "Production",
"permissions": {
"usageType": "openSource",
......@@ -41,7 +41,7 @@
},
"date": {
"metadataLastUpdated": "2019-03-07"
"metadataLastUpdated": "2019-03-26"
}
}
]
<!-- Generated by pkgdown: do not edit by hand -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Disclaimer • ncdfgeom</title>
<!-- jquery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w=" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=" crossorigin="anonymous"></script>
<!-- Font Awesome icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
<!-- clipboard.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js" integrity="sha256-FiZwavyI2V6+EXO1U+xzLG3IKldpiTFf3153ea9zikQ=" crossorigin="anonymous"></script>
<!-- sticky kit -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sticky-kit/1.1.3/sticky-kit.min.js" integrity="sha256-c4Rlo1ZozqTPE2RLuvbusY3+SU1pQaJC0TjuhygMipw=" crossorigin="anonymous"></script>
<!-- pkgdown -->
<link href="pkgdown.css" rel="stylesheet">
<script src="pkgdown.js"></script>
<meta property="og:title" content="Disclaimer" />
<!-- mathjax -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js" integrity="sha256-nvJJv9wWKEm88qvoQl9ekL2J+k/RWIsaSScxxlsrv8k=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-84DKXVJXs0/F8OTMzX4UR909+jtl4G7SPypPavF+GfA=" crossorigin="anonymous"></script>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container template-title-body">
<header>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="navbar-brand">
<a class="navbar-link" href="index.html">ncdfgeom</a>
<span class="version label label-default" data-toggle="tooltip" data-placement="bottom" title="Released version">1.0.0</span>
</span>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>
<a href="index.html">
<span class="fa fa-home fa-lg"></span>
</a>
</li>
<li>
<a href="articles/ncdfgeom.html">Get started</a>
</li>
<li>
<a href="reference/index.html">Reference</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
Articles
<span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li>
<a href="articles/geometry.html">Reading and Writing NetCDF-CF Geometry</a>
</li>
<li>
<a href="articles/timeseries.html">Reading and Writing Timeseries</a>
</li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
</ul>
</div><!--/.nav-collapse -->
</div><!--/.container -->
</div><!--/.navbar -->
</header>
<div class="row">
<div class="contents col-md-9">
<div class="page-header">
<h1>Disclaimer</h1>
</div>
<div id="disclaimer" class="section level1">
<p>This information is preliminary or provisional and is subject to revision. It is being provided to meet the need for timely best science. The information has not received final approval by the U.S. Geological Survey (USGS) and is provided on the condition that neither the USGS nor the U.S. Government shall be held liable for any damages resulting from the authorized or unauthorized use of the information.</p>
<p>This software is in the public domain because it contains materials that originally came from the U.S. Geological Survey (USGS), an agency of the United States Department of Interior. For more information, see the official USGS copyright policy at <a href="https://www.usgs.gov/visual-id/credit_usgs.html#copyright" class="uri">https://www.usgs.gov/visual-id/credit_usgs.html#copyright</a></p>
<p>Although this software program has been used by the USGS, no warranty, expressed or implied, is made by the USGS or the U.S. Government as to the accuracy and functioning of the program and related program material nor shall the fact of distribution constitute any such warranty, and no responsibility is assumed by the USGS in connection therewith. This software is provided “AS IS.”</p>
</div>
</div>
</div>
<footer>
<div class="copyright">
<p>Developed by David Blodgett.</p>
</div>
<div class="pkgdown">
<p>Site built with <a href="https://pkgdown.r-lib.org/">pkgdown</a> 1.3.0.</p>
</div>
</footer>
</div>
</body>
</html>
CC0 1.0 Universal
<!-- Generated by pkgdown: do not edit by hand -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>License • ncdfgeom</title>
<!-- jquery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w=" crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=" crossorigin="anonymous"></script>
<!-- Font Awesome icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
<!-- clipboard.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js" integrity="sha256-FiZwavyI2V6+EXO1U+xzLG3IKldpiTFf3153ea9zikQ=" crossorigin="anonymous"></script>
<!-- sticky kit -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sticky-kit/1.1.3/sticky-kit.min.js" integrity="sha256-c4Rlo1ZozqTPE2RLuvbusY3+SU1pQaJC0TjuhygMipw=" crossorigin="anonymous"></script>
<!-- pkgdown -->
<link href="pkgdown.css" rel="stylesheet">
<script src="pkgdown.js"></script>
<meta property="og:title" content="License" />
<!-- mathjax -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js" integrity="sha256-nvJJv9wWKEm88qvoQl9ekL2J+k/RWIsaSScxxlsrv8k=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-84DKXVJXs0/F8OTMzX4UR909+jtl4G7SPypPavF+GfA=" crossorigin="anonymous"></script>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container template-title-body">
<header>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="navbar-brand">
<a class="navbar-link" href="index.html">ncdfgeom</a>
<span class="version label label-default" data-toggle="tooltip" data-placement="bottom" title="Released version">1.0.0</span>
</span>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>
<a href="index.html">
<span class="fa fa-home fa-lg"></span>
</a>
</li>
<li>
<a href="articles/ncdfgeom.html">Get started</a>
</li>
<li>
<a href="reference/index.html">Reference</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
Articles
<span class="caret"></span>
</a>