summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRafael Laboissière <rafael@debian.org>2020-02-15 14:49:54 -0300
committerRafael Laboissière <rafael@debian.org>2020-02-15 14:49:54 -0300
commit420a8c19a845bd03f076419617c167cd4f298258 (patch)
treefb4741a69b5377d898a69196eb487b1bcd2ea105
parentd3acb582c331f816caef85031a8f544e7fdaf0b2 (diff)
parent73a0fc85dc1845d5a438ed5c7cae817e1acffe7e (diff)
Merge tag 'upstream/1.4.0'
Upstream version 1.4.0
-rw-r--r--DESCRIPTION10
-rw-r--r--INDEX81
-rw-r--r--NEWS90
-rw-r--r--inst/aer2ecef.m142
-rw-r--r--inst/aer2enu.m122
-rw-r--r--inst/aer2geodetic.m128
-rw-r--r--inst/aer2ned.m112
-rw-r--r--inst/antipode.m77
-rw-r--r--inst/axes2ecc.m93
-rw-r--r--inst/azimuth.m4
-rw-r--r--inst/closePolygonParts.m152
-rw-r--r--inst/deg2km.m5
-rw-r--r--inst/deg2nm.m62
-rw-r--r--inst/deg2sm.m62
-rw-r--r--inst/degrees2dm.m2
-rw-r--r--inst/degrees2dms.m2
-rw-r--r--inst/degtorad.m2
-rw-r--r--inst/distance.m103
-rw-r--r--inst/dm2degrees.m2
-rw-r--r--inst/dms2degrees.m2
-rw-r--r--inst/dxfdraw.m178
-rw-r--r--inst/dxfparse.m477
-rw-r--r--inst/dxfread.m72
-rw-r--r--inst/earthRadius.m50
-rw-r--r--inst/ecc2flat.m66
-rw-r--r--inst/ecc2n.m79
-rw-r--r--inst/ecef2geodetic.m174
-rw-r--r--inst/enu2aer.m120
-rw-r--r--inst/enu2uvw.m100
-rw-r--r--inst/extractfield.m2
-rw-r--r--inst/flat2ecc.m66
-rw-r--r--inst/fromDegrees.m2
-rw-r--r--inst/fromRadians.m2
-rw-r--r--inst/geocentricLatitude.m95
-rw-r--r--inst/geodetic2ecef.m152
-rw-r--r--inst/geodeticLatitudeFromGeocentric.m92
-rw-r--r--inst/geodeticLatitudeFromParametric.m92
-rw-r--r--inst/geoshow.m2
-rw-r--r--inst/gmlread.m263
-rw-r--r--inst/gpxread.m264
-rw-r--r--inst/isShapeMultipart.m72
-rw-r--r--inst/km2deg.m7
-rw-r--r--inst/km2nm.m2
-rw-r--r--inst/km2rad.m5
-rw-r--r--inst/km2sm.m2
-rw-r--r--inst/kmlread.m223
-rw-r--r--inst/kmzread.m62
-rw-r--r--inst/majaxis.m80
-rw-r--r--inst/makesymbolspec.m8
-rw-r--r--inst/mapshow.m2
-rw-r--r--inst/meridianarc.m97
-rw-r--r--inst/minaxis.m80
-rw-r--r--inst/n2ecc.m74
-rw-r--r--inst/nm2deg.m2
-rw-r--r--inst/nm2km.m2
-rw-r--r--inst/nm2rad.m2
-rw-r--r--inst/nm2sm.m2
-rw-r--r--inst/parametricLatitude.m93
-rw-r--r--inst/polycut.m54
-rw-r--r--inst/private/__dbl2int64__.m47
-rw-r--r--inst/private/clipplg.m207
-rw-r--r--inst/private/clippln.m178
-rw-r--r--inst/private/spheres_radius.m2
-rw-r--r--inst/rad2km.m5
-rw-r--r--inst/rad2nm.m62
-rw-r--r--inst/rad2sm.m62
-rw-r--r--inst/radtodeg.m2
-rw-r--r--inst/rasterclip.m129
-rw-r--r--inst/rasterdraw.m3
-rw-r--r--inst/rasterinfo.m21
-rw-r--r--inst/rasterread.m4
-rw-r--r--inst/rcurve.m193
-rw-r--r--inst/reckon.m2
-rw-r--r--inst/referenceEllipsoid.m349
-rw-r--r--inst/removeExtraNanSeparators.m2
-rw-r--r--inst/roundn.m2
-rw-r--r--inst/shapedraw.m60
-rw-r--r--inst/shapeinfo.m8
-rw-r--r--inst/shaperead.m498
-rw-r--r--inst/shapewrite.m572
-rw-r--r--inst/sm2deg.m2
-rw-r--r--inst/sm2km.m2
-rw-r--r--inst/sm2nm.m2
-rw-r--r--inst/sm2rad.m2
-rw-r--r--inst/toDegrees.m2
-rw-r--r--inst/toRadians.m2
-rw-r--r--inst/unitsratio.m2
-rw-r--r--inst/utmzone.m206
-rw-r--r--inst/validateLengthUnit.m2
-rw-r--r--inst/wgs84Ellipsoid.m79
-rw-r--r--inst/wrapTo180.m2
-rw-r--r--inst/wrapTo2Pi.m2
-rw-r--r--inst/wrapTo360.m4
-rw-r--r--inst/wrapToPi.m2
-rw-r--r--src/aclocal.m44
-rwxr-xr-xsrc/configure32
-rw-r--r--src/configure.ac2
-rw-r--r--src/deg2rad.m.in2
-rw-r--r--src/gdalread.cc4
-rw-r--r--src/misc.cpp2
-rw-r--r--src/misc.h2
-rw-r--r--src/rad2deg.m.in2
102 files changed, 6350 insertions, 889 deletions
diff --git a/DESCRIPTION b/DESCRIPTION
index da8a548..b11e7fa 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,13 +1,13 @@
Name: mapping
-Version: 1.2.1
-Date: 2016-02-06
+Version: 1.4.0
+Date: 2020-01-20
Author: various authors
Maintainer: Philip Nienhuis <prnienhuis@users.sf.net>
Title: Mapping Functions
-Description: Simple mapping and GIS .shp and raster file functions.
+Description: Simple mapping and GIS .shp .dxf and raster file functions.
Categories: Mathematics
-Depends: octave (>= 3.8.0)
-Suggested: io (>= 2.2.7), geometry (>= 1.4.0), octclip (>= 1.0.3), GDAL
+Depends: octave (>= 3.8.0), io (>= 2.2.7), geometry (>= 4.0.0)
+Suggested: GDAL
Autoload: no
License: GPLv3+
Url: http://octave.sf.net
diff --git a/INDEX b/INDEX
index 579eab3..73e08fa 100644
--- a/INDEX
+++ b/INDEX
@@ -1,28 +1,76 @@
mapping >> Mapping Functions
-Paths in curves
- azimuth
- distance
- reckon
-Lengths and angles
+Coordinate systems
+ aer2ecef
+ aer2enu
+ aer2geodetic
+ aer2ned
+ ecef2geodetic
+ enu2aer
+ enu2uvw
+ geocentricLatitude
+ geodetic2ecef
+ geodeticLatitudeFromGeocentric
+ geodeticLatitudeFromParametric
+ parametricLatitude
+ referenceEllipsoid
+ wgs84Ellipsoid
+DXF files
+ dxfdraw
+ dxfparse
+ dxfread
+GIS raster files
+ gdalread
+ rasterclip
+ rasterdraw
+ rasterinfo
+ rasterread
+GIS vector files
+ gmlread
+ isShapeMultipart
+ kmlread
+ kmzread
+ mapshow
+ geoshow
+ shapedraw
+ shapeinfo
+ shaperead
+ shapewrite
+GPS files
+ gpxread
+Lengths, angles and dimensions
+ axes2ecc
deg2rad
+ deg2nm
+ deg2sm
degtorad
degrees2dm
degrees2dms
dm2degrees
dms2degrees
+ earthRadius
+ ecc2flat
+ ecc2n
+ flat2ecc
fromDegrees
fromRadians
km2deg
km2rad
km2nm
km2sm
+ majaxis
+ meridianarc
+ minaxis
+ n2ecc
nm2km
nm2sm
nm2rad
nm2deg
rad2deg
- radtodeg
rad2km
+ rad2nm
+ rad2sm
+ radtodeg
+ rcurve
sm2km
sm2nm
sm2rad
@@ -34,21 +82,16 @@ Lengths and angles
wrapTo360
wrapToPi
wrapTo2Pi
+Paths in curves
+ azimuth
+ distance
+ reckon
Utilities
+ closePolygonParts
extractfield
+ makesymbolspec
+ polycut
removeExtraNanSeparators
roundn
+ utmzone
validateLengthUnit
-Shape files
- mapshow
- geoshow
- makesymbolspec
- shapedraw
- shapeinfo
- shaperead
- shapewrite
-Raster files
- gdalread
- rasterdraw
- rasterinfo
- rasterread
diff --git a/NEWS b/NEWS
index fbdcdbc..16f68d4 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,93 @@
+Summary of important user-visible changes for mapping 1.4.0:
+-------------------------------------------------------------------
+
+ ** The mapping package now depends on the geometry and io packages. The
+ (suggested) dependency on the octclip package has been removed.
+
+ ** The following functions are new in mapping 1.4.0:
+
+ aer2ecef
+ aer2enu
+ aer2geodetic
+ aer2ned
+ axes2ecc
+ antipode
+ closePolygonParts
+ deg2nm
+ deg2sm
+ dxfdraw
+ dxfparse
+ dxfread
+ earthRadius
+ ecc2flat
+ ecc2n
+ ecef2geodetic
+ enu2aer
+ enu2uvw
+ flat2ecc
+ geodetic3ecef
+ geocentricLatitude
+ geodeticLatitudeFromGeocentric
+ geodeticLatitudeFromParametric
+ gmlread
+ gpxread
+ isShapeMultipart
+ kmlread
+ kmzread
+ majaxis
+ meridianarc
+ minaxis
+ n2ecc
+ parametricLatitude
+ polycut
+ rad2nm
+ rad2sm
+ rasterclip
+ rcurve
+ referenceEllipsoid
+ utmzone
+ wgs84Ellipsoid
+
+ ** Bug fixes:
+ shapedraw.m: * Fix color arg. bug when drawing (poly)line
+ geometries.
+ * Restore input check order.
+ * Do not connect multipoint shapes.
+ shaperead.m: * Fix reading 'line' geometries.
+ * Ignore shapes with (almost) infinite
+ coordinates.
+ * Fix .shx file usage.
+ * Move file existence check to start of function.
+ * Fix reading MultiPoint shapefiles.
+ * Provision for absent M-values in M and Z
+ shapetypes.
+ * More robust input validation.
+ * Properly process BoundingBox limits.
+ * Fix incompatible dimensions bug when reading Multipoint files.
+ * Fix reading selected attributes.
+ shapewrite.m: * Various fixes (credits to a.o., Martin Kunz, M.Parkan).
+ * Properly write missing M-values.
+ * Fix OOM error when writing large Point type files.
+ * Fix and overhaul writing requested attributes.
+ Fixes for bug #53422:
+ * Unconditionally write .dbf file.
+ * Fix record lengths for all Point types.
+ * Fix XY coordinate write order for all Multipoint types.
+ * Update .shx header as well.
+ rasterinfo.m: Show nr. of bands and bounding box.
+
+ ** Code improvements
+ shaperead.m: * Invoke the Clipper library for clipping polylines
+ and polygons, leading to much improved performance
+ when invoking the BoundingBox option together with
+ the Clip option. To use this feature the Octave-
+ Forge geometry package >= 4.0.0 must be installed
+ and loaded.
+
+ ** New features
+ shapewrite.m: * Allow writing M & Z shape types (Matlab-incompatible
+ types, yet supported by Octave).
+
Summary of important user-visible changes for mapping 1.2.1:
-------------------------------------------------------------------
diff --git a/inst/aer2ecef.m b/inst/aer2ecef.m
new file mode 100644
index 0000000..97e9706
--- /dev/null
+++ b/inst/aer2ecef.m
@@ -0,0 +1,142 @@
+## Copyright (c) 2014-2020 Michael Hirsch, Ph.D.
+## Copyright (c) 2013-2020, Felipe Geremia Nievinski
+## Copyright (C) 2019-2020 Philip Nienhuis
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are met:
+## 1. Redistributions of source code must retain the above copyright notice,
+## this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright notice,
+## this list of conditions and the following disclaimer in the documentation
+## and/or other materials provided with the distribution.
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+## THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+## OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+## SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{x}, @var{y}, @var{z} =} aer2ecef (@var{az},@var{el}, @var{slantRange}, @var{lat0}, @var{lon0}, @var{alt0})
+## @deftypefnx {Function File} {@var{x}, @var{y}, @var{z} =} aer2ecef (@var{az},@var{el}, @var{slantRange}, @var{lat0}, @var{lon0}, @var{alt0}, @var{spheroid})
+## @deftypefnx {Function File} {@var{x}, @var{y}, @var{z} =} aer2ecef (@var{az},@var{el}, @var{slantRange}, @var{lat0}, @var{lon0}, @var{alt0}, @var{spheroid}, @var{angleUnit})
+## Convert azimuth, elevation, range to target from observer to ECEF coordinates.
+##
+## Inputs:
+##
+## @var{az}, @var{el}, @var{slantrange}: look angles and distance to point
+## under consideration (degrees, degrees, meters). Vectors values are accepted
+## if they have equal dimensions.
+##
+## @var{lat0}, @var{lon0}, @var{alt0}: ellipsoid geodetic coordinates of
+## observer/reference (degrees, degrees, meters). This must be just one
+## position.
+##
+## @var{spheroid}: referenceEllipsoid parameter struct; default is wgs84. A
+## string value describing the spheroid is also accepted.
+##
+## @var{angleUnit}: string for angular units ('degrees' or 'radians',
+## case-insensitive, just the first charachter will do). Default is 'degrees'.
+##
+## Outputs:
+##
+## @var{x}, @var{y}, @var{z}: Earth Centered Earth Fixed (ECEF) coordinates.
+##
+## Example
+## @example
+## [x, y, z] = aer2ecef (33, 70, 1e3, 42, -82, 200)
+## x = 6.6057e+05
+## y = -4.7002e+06
+## z = 4.2450e+06
+## @end example
+##
+## With radians
+## @example
+## [x, y, z] = aer2ecef (pi/6, pi/3, 1e3, pi/4, -pi/2, 200, "wgs84", "radians")
+## x = 250.00
+## y = -4.5180e+06
+## z = 4.4884e+06
+## @end example
+##
+## Note: aer2ecef is a mere wrapper for functions geodetic2ecef, aer2enu and
+## enu2uvw.
+##
+## @seealso {geodetic2ecef, aer2enu, enu2uvw, referenceEllipsoid}
+## @end deftypefn
+
+## Function adapted from patch by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?8377
+
+function [x,y,z] = aer2ecef (az, el, slantrange, lat0, lon0, alt0, ...
+ spheroid="", angleUnit="degrees")
+
+ if (nargin < 6)
+ print_usage();
+ endif
+
+ if (! isnumeric (az) || ! isreal (az) || ...
+ ! isnumeric (el) || ! isreal (el) || ...
+ ! isnumeric (slantrange) || ! isreal (slantrange) ||...
+ ! isnumeric (lat0) || ! isreal (lat0) || ...
+ ! isnumeric (lon0) || ! isreal (lon0) || ...
+ ! isnumeric (alt0) || ! isreal (alt0))
+ error ("aer2ecef.m: numeric values expected for first 6 inputs.");
+ endif
+
+ if (! all (size (az) == size (el)) || ...
+ ! all (size (el) == size (slantrange))) ...
+ error ("aer2ecef.m: non-matching dimensions of inputs.");
+ endif
+ if (! isscalar (lat0) || ! isscalar (lon0) || ! isscalar (alt0))
+ error ("aer2ecef: reference coordinates must be scalars.");
+ endif
+
+ if (isempty (spheroid))
+ E = wgs84Ellipsoid;
+ elseif (isstruct (spheroid))
+ E = spheroid;
+ elseif (ischar (spheroid))
+ E = referenceEllipsoid (spheroid);
+ endif
+
+ %% Origin of the local system in geocentric coordinates.
+ [x0, y0, z0] = geodetic2ecef (spheroid, lat0, lon0, alt0, angleUnit);
+ %% Convert Local Spherical AER to ENU
+ [e, n, u] = aer2enu (az, el, slantrange, angleUnit);
+ %% Rotating ENU to ECEF
+ [dx, dy, dz] = enu2uvw (e, n, u, lat0, lon0, angleUnit);
+ %% Origin + offset from origin equals position in ECEF
+ x = x0 + dx;
+ y = y0 + dy;
+ z = z0 + dz;
+
+endfunction
+
+%!test
+% [x2, y2, z2] = aer2ecef (33, 70, 1e3, 42, -82, 200);
+% assert ([x2, y2, z2], [660.930e3, -4701.424e3, 4246.579e3], 10e-6)
+% [x3, y3, z3] = aer2ecef ( 0.575958653158129, 1.22173047639603, 1e3, 0.733038285837618, -1.43116998663535, 200, "", "rad");
+% assert ([x3, y3, z3], [660.93019e3, -4701.42422e3, 4246.5796e3],10e-3)
+
+%!error <numeric> aer2ecef("s", 25, 1e3, 0, 0, 0)
+%!error <numeric> aer2ecef(3i, 25, 1e3, 0, 0, 0)
+%!error <numeric> aer2ecef(33, "s", 1e3, 0, 0, 0)
+%!error <numeric> aer2ecef(33, 3i, 1e3, 0, 0, 0)
+%!error <numeric> aer2ecef(33, 25, "s", 0, 0, 0)
+%!error <numeric> aer2ecef(33, 25, 3i, 0, 0, 0)
+%!error <numeric> aer2ecef(33, 25, 1e3, "s", 0, 0)
+%!error <numeric> aer2ecef(33, 25, 1e3, 3i, 0, 0)
+%!error <numeric> aer2ecef(33, 25, 1e3, 0, "s", 0)
+%!error <numeric> aer2ecef(33, 25, 1e3, 0, 3i, 0)
+%!error <numeric> aer2ecef(33, 25, 1e3, 0, 0, "s")
+%!error <numeric> aer2ecef(33, 25, 1e3, 0, 0, 3i)
+%!error <non-matching> aer2ecef ([1 1], [2 2]', [3 3], 4, 5, 6)
+%!error <non-matching> aer2ecef ([1 1], [2 2], [33], 4, 5, 6)
+%!error <reference> aer2ecef ([1 1], [2 2], [3 3], [4 4], 5, 6)
+%!error <reference> aer2ecef ([1 1], [2 2], [3 3], 4, [5 5], 6)
+%!error <reference> aer2ecef ([1 1], [2 2], [3 3], 4, 5, [6 6])
diff --git a/inst/aer2enu.m b/inst/aer2enu.m
new file mode 100644
index 0000000..3c60a86
--- /dev/null
+++ b/inst/aer2enu.m
@@ -0,0 +1,122 @@
+## Copyright (C) 2014-2020 Michael Hirsch
+## Copyright (C) 2013-2020 Felipe Geremia Nievinski
+## Copyright (C) 2019-2020 Philip Nienhuis
+##
+## Redistribution and use in source and binary forms, with or without modification, are permitted
+## provided that the following conditions are met:
+## 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
+## in the documentation and/or other materials provided with the distribution.
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+## INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+## IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+## (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+## HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{e}, @var{n}, @var{u} =} aer2enu (@var{az}, @var{el}, @var{slantrange})
+## @deftypefnx {Function File} {@var{e}, @var{n}, @var{u} =} aer2enu (@var{az}, @var{el}, @var{slantrange}, @var{angleUnit})
+## Converts spherical azimuth, elevation and range coordinates into Cartesian
+## ENU coordinates.
+##
+## Inputs:
+## @itemize
+## @item
+## @var{az}, @var{el}, @var{slantrange}: look angles and distance to point
+## (degrees, degrees, meters).
+##
+## @item
+## @var{az}: azimuth angle clockwise from local north.
+##
+## @item
+## @var{el}: elevation angle above local horizon.
+##
+## @item
+## @var{slantrange}: distance from origin in local spherical system.
+##
+## @item
+## (Optional) angleUnit: string for angular units (radians or degrees).
+## Default is 'd': degrees
+## @end itemize
+##
+## Outputs:
+##
+## @itemize
+## @var{e}, @var{n}, @var{u}: East, North, Up Cartesian coordinates
+## (meters).
+## @end itemize
+##
+## Example:
+## @example
+## [e, n, u] = aer2enu (33, 70, 1e3)
+## e = 186.28
+## n = 286.84
+## u = 939.69
+## @end example
+##
+## In radians
+## @example
+## [e, n, u] = aer2enu (pi/4, pi/3,1e3, "radians")
+## e = 353.55
+## n = 353.55
+## u = 866.03
+## @end example
+##
+## @end deftypefn
+
+## Function adapted by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?8377
+
+function [e, n, u] = aer2enu (az, el, slantrange, angleUnit = "degrees")
+
+ if (nargin < 3 || nargin > 4)
+ print_usage();
+ endif
+
+ if (! isnumeric (az) || ! isreal (az) || ...
+ ! isnumeric (el) || ! isreal (el) || ...
+ ! isnumeric (slantrange) || ! isreal (slantrange))
+ error ("aer2enu.m : numeric input values expected");
+ endif
+
+ if (! all (size (az) == size (el)) || ! all (size (el) == size (slantrange)))
+ error ("aer2enu.m: non-matching dimensions of inputs.");
+ endif
+
+ if (! ischar (angleUnit))
+ error ("aer2enu.m: character value expected for 'angleUnit'");
+ elseif (strncmpi (angleUnit, "degrees", length (angleUnit)))
+ az = deg2rad (az);
+ el = deg2rad (el);
+ elseif (! strncmpi (angleUnit, "radians", length (angleUnit)))
+ error ("aer2enu.m: illegal input for 'angleUnit'");
+ endif
+
+ ## Calculation of AER2ENU
+ u = slantrange .* sin (el);
+ r = slantrange .* cos (el);
+ e = r .* sin (az);
+ n = r .* cos (az);
+
+endfunction
+
+
+%!test
+%! [e, n, u] = aer2enu (33, 70, 1e3);
+%! assert ([e, n, u], [186.277521, 286.84222, 939.69262], 10e-6)
+%! [e, n, u] = aer2enu (0.57595865, 1.221730476, 1e3, "rad");
+%! assert ([e, n, u], [186.277521, 286.84222, 939.69262], 10e-6)
+
+%!error <numeric> aer2enu("s", 25, 1e3)
+%!error <numeric> aer2enu(3i, 25, 1e3)
+%!error <numeric> aer2enu(33, "s", 1e3)
+%!error <numeric> aer2enu(33, 3i, 1e3)
+%!error <numeric> aer2enu(33, 25, "s")
+%!error <numeric> aer2enu(33, 25, 3i)
+%!error <non-matching> aer2enu ([1 1], [2 2]', [4 5])
+%!error <non-matching> aer2enu ([1 1], [2 2], [4 5 6])
+%!error <character> aer2enu (1, 2, 3, 4);
+%!error <Invalid call> aer2enu (1, 2)
+%!error <illegal> aer2enu (33, 70, 1e3, "f");
+%!error <illegal> aer2enu (33, 70, 1e3, "degreef");
diff --git a/inst/aer2geodetic.m b/inst/aer2geodetic.m
new file mode 100644
index 0000000..45f9b90
--- /dev/null
+++ b/inst/aer2geodetic.m
@@ -0,0 +1,128 @@
+## Copyright (c) 2014-2020 Michael Hirsch, Ph.D.
+## Copyright (c) 2013-2020, Felipe Geremia Nievinski
+## Copyright (C) 2019-2020 Philip Nienhuis
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are met:
+## 1. Redistributions of source code must retain the above copyright notice,
+## this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright notice,
+## this list of conditions and the following disclaimer in the documentation
+## and/or other materials provided with the distribution.
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+## THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+## OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+## SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{lat1}, @var{lon1}, @var{alt1} =} aer2geodetic (@var{az},@var{el}, @var{slantRange}, @var{lat0}, @var{lon0}, @var{alt0})
+## @deftypefnx {Function File} {@var{lat1}, @var{lon1}, @var{alt1} =} aer2geodetic (@var{az},@var{el}, @var{slantRange}, @var{lat0}, @var{lon0}, @var{alt0}, @var{spheroid})
+## @deftypefnx {Function File} {@var{lat1}, @var{lon1}, @var{alt1} =} aer2geodetic (@var{az},@var{el}, @var{slantRange}, @var{lat0}, @var{lon0}, @var{alt0}, @var{spheroid}, @var{angleUnit})
+## Convert azimuth, elevation and range of target from observer to geodetic
+## coordinates.
+##
+## Inputs:
+##
+## @var{az}, @var{el}, @var{slantrange}: look angles and distance to point
+## under consideration (degrees, degrees, meters). Vectors values are accepted
+## if they have equal dimensions.
+##
+## @var{lat0}, @var{lon0}, @var{alt0}: ellipsoid geodetic coordinates of
+## observer/reference (degrees, degrees, meters). This must be just one
+## position.
+##
+## @var{spheroid}: referenceEllipsoid parameter struct or name (string value)
+## of referenceEllipsoid; default is 'wgs84'.
+##
+## @var{angleUnit}: string for angular units ('degrees' or 'radians',
+## case-insensitive, just first character will suffice). Default is 'degrees'.
+##
+## Outputs:
+##
+## @var{lat1}, @var{lon1}, @var{alt1}: geodetic coordinates of points (degrees,
+## degrees, meters).
+##
+## Example
+## @example
+## [x, y, z] = aer2geodetic (33, 70, 1e3, 42, -82, 200)
+## x = 42.000
+## y = -82.000
+## z = 1139.7
+## @end example
+##
+## With radians
+## @example
+## [x, y, z] = aer2geodetic (pi/6, pi/3, 1e3, pi/4, -pi/2, 200, "wgs84", "radians")
+## x = 0.78547
+## y = -1.5707
+## z = 1066.0
+## @end example
+##
+## Note: aer2geodetic is a mere wrapper for functions aer2ecef followed by
+## ecef2geodetic.
+##
+## @seealso {aer2ecef, ecef2geodetic, referenceEllipsoid}
+## @end deftypefn
+
+## Function adapted by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?8377
+
+function [lat1, lon1, alt1] = aer2geodetic (az, el, slantrange, lat0, lon0, alt0, spheroid = "", angleUnit = "degrees")
+
+ if nargin < 6
+ print_usage();
+ endif
+
+ if (! isnumeric (az) || ! isreal (az) || ...
+ ! isnumeric (el) || ! isreal (el) || ...
+ ! isnumeric (slantrange) || ! isreal (slantrange) || ...
+ ! isnumeric (lat0) || ! isreal (lat0) || ...
+ ! isnumeric (lon0) || ! isreal (lon0) || ...
+ ! isnumeric (alt0) || ! isreal (alt0))
+ error ("aer2geodetic.m : numeric values expected for first six inputs.");
+ endif
+
+ if (isempty (spheroid))
+ E = wgs84Ellipsoid;
+ elseif (isstruct (spheroid))
+ E = spheroid;
+ elseif (ischar (spheroid))
+ E = referenceEllipsoid (spheroid);
+ else
+ error ("aer2geodetic: illegal value for 'spheroid'.");
+ endif
+
+ [x, y, z] = aer2ecef (az, el, slantrange, lat0, lon0, alt0, spheroid, angleUnit);
+ [lat1, lon1, alt1] = ecef2geodetic (spheroid, x, y, z, angleUnit);
+
+endfunction
+
+%!test
+%! [lat2, lon2, alt2] = aer2geodetic (33, 70, 1e3, 42, -82, 200);
+%! assert ([lat2, lon2, alt2], [42.002581, -81.997752, 1.1397018e3], 10e-6);
+
+%!test
+%! [lat2, lon2, alt2] = aer2geodetic ( 0.575958653158129, 1.22173047639603, ...
+%! 1e3, 0.733038285837618, -1.43116998663535, 200, "", "rad");
+%! assert ([lat2, lon2, alt2], [0.7330833, -1.4311307, 1.13970179e3], 10e-6)
+
+%!error <numeric> aer2geodetic ("s", 25, 1e3, 0, 0, 0)
+%!error <numeric> aer2geodetic (3i, 25, 1e3, 0, 0, 0)
+%!error <numeric> aer2geodetic (33, "s", 1e3, 0, 0, 0)
+%!error <numeric> aer2geodetic (33, 3i, 1e3, 0, 0, 0)
+%!error <numeric> aer2geodetic (33, 25, "s", 0, 0, 0)
+%!error <numeric> aer2geodetic (33, 25, 3i, 0, 0, 0)
+%!error <numeric> aer2geodetic (33, 25, 1e3, "s", 0, 0)
+%!error <numeric> aer2geodetic (33, 25, 1e3, 3i, 0, 0)
+%!error <numeric> aer2geodetic (33, 25, 1e3, 0, "s", 0)
+%!error <numeric> aer2geodetic (33, 25, 1e3, 0, 3i, 0)
+%!error <numeric> aer2geodetic (33, 25, 1e3, 0, 0, "s")
+%!error <numeric> aer2geodetic (33, 25, 1e3, 0, 0, 3i)
+%!error <illegal> aer2geodetic (33, 25, 1e3, 0, 0, 3, 5)
diff --git a/inst/aer2ned.m b/inst/aer2ned.m
new file mode 100644
index 0000000..8f65ceb
--- /dev/null
+++ b/inst/aer2ned.m
@@ -0,0 +1,112 @@
+## Copyright (c) 2014-2020 Michael Hirsch, Ph.D.
+## Copyright (c) 2013-2020, Felipe Geremia Nievinski
+## Copyright (C) 2019-2020 Philip Nienhuis
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are met:
+## 1. Redistributions of source code must retain the above copyright notice,
+## this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright notice,
+## this list of conditions and the following disclaimer in the documentation
+## and/or other materials provided with the distribution.
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+## THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+## OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+## SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{n}, @var{e}, @var{d} =} aer2ned (@var{az}, @var{el}, @var{slantrange})
+## @deftypefnx {Function File} {@var{n}, @var{e}, @var{d} =} aer2ned (@var{az}, @var{el}, @var{slantrange}, @var{angleUnit})
+## Convert azimuth, elevation and range to NED coordinates.
+##
+## Inputs:
+##
+## @var{az}, @var{el}, @var{slantrange}: look angles and distance to point
+## under consideration (degrees, degrees, meters).
+##
+## @var{angleUnit}: string for angular units ('degrees' or 'radians',
+## case-insensitive, lust the first character will do). Default is 'degrees'.
+##
+## Outputs:
+##
+## @var{n}, @var{e}, @var{d}: North, East, Down coordinates of points.
+## (meters)
+##
+## Examples:
+## @example
+## [n, e, d] = aer2ned (33, 70, 1e3)
+## n = 286.84
+## e = 186.28
+## d = -939.69
+## @end example
+##
+## With radians
+## @example
+## [n, e, d] = aer2ned (pi/4, pi/3,1e3, "radians")
+## n = 353.55
+## e = 353.55
+## d = -866.03
+## @end example
+##
+## @end deftypefn
+
+## Function adapted by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?8377
+
+function [n, e, d] = aer2ned (az, el, slantrange, angleUnit = "degrees")
+
+ if (nargin < 3)
+ print_usage();
+ endif
+
+ if (! isnumeric (az) || ! isreal (az) || ...
+ ! isnumeric (el) || ! isreal (el) || ...
+ ! isnumeric (slantrange) || ! isreal (slantrange))
+ error ("aer2ned.m : numeric values expected for first three inputs.");
+ endif
+
+ if (! all (size (az) == size (el)) || ! all (size (el) == size (slantrange)))
+ error ("aer2ned.m: non-matching dimensions of inputs.");
+ endif
+
+ if (! ischar (angleUnit))
+ error ("aer2ned.m: character value expected for 'angleUnit'");
+ elseif (strncmpi (angleUnit, "degrees", length (angleUnit)))
+ az = deg2rad (az);
+ el = deg2rad (el);
+ elseif (! strncmpi (angleUnit, "radians", length (angleUnit)))
+ error ("aer2ned.m: illegal input for 'angleUnit'");
+ endif
+
+ ## Calculation of AER2NED
+ d = -slantrange .* sin (el);
+ r = slantrange .* cos (el);
+ e = r .* sin (az);
+ n = r .* cos (az);
+
+endfunction
+
+%!test
+%! [n, e, d] = aer2ned (33, 70, 1e3);
+%! assert ([n, e, d], [286.84222, 186.277521, -939.69262], 10e-6)
+%! [e, n, u] = aer2ned (0.57595865, 1.221730476, 1e3, "rad");
+%! assert ([e, n, u], [286.84222, 186.277521, -939.69262], 10e-6)
+
+%!error <numeric> aer2ned("s", 25, 1e3)
+%!error <numeric> aer2ned(3i, 25, 1e3)
+%!error <numeric> aer2ned(33, "s", 1e3)
+%!error <numeric> aer2ned(33, 3i, 1e3)
+%!error <numeric> aer2ned(33, 25, "s")
+%!error <numeric> aer2ned(33, 25, 3i)
+%!error <non-matching> aer2ned ([1 1], [2 2]', [4 5])
+%!error <non-matching> aer2ned ([1 1], [2 2], [4 5 6])
+%!error <character> aer2ned (1, 2, 3, 4);
+%!error <illegal> aer2ned (33, 70, 1e3, "f");
+%!error <illegal> aer2ned (33, 70, 1e3, "degreef");
diff --git a/inst/antipode.m b/inst/antipode.m
new file mode 100644
index 0000000..1476693
--- /dev/null
+++ b/inst/antipode.m
@@ -0,0 +1,77 @@
+## Copyright (C) 2017-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{lat_o, lon_o} =} antipode (@var{lat_i}, @var{lon_i})
+## @deftypefnx {} {@var{lat_o, lon_o} =} antipode (@var{lat_i}, @var{lon_i}, @var{unit})
+## Compute antipode (opposite point on globe).
+##
+## @var{lat_i} and @var{lon_i} (numeric values) are input latitude and
+## longitude, optionally vectors.
+##
+## Optional character argument @var{unit} (case-insensitive) can be one
+## of 'Degrees' or 'Radians' and can be used to specify the input
+## units. Just 'd' or 'r' will do.
+##
+## @end deftypefn
+
+## Author: Philip Nienhuis <pr.nienhuis@users.sf.net>
+## Created: 2017-03-02
+
+function [lato, lono] = antipode (lati, loni, unit="")
+
+ if (nargin < 2 || nargout < 2)
+ print_usage ();
+ elseif (! isnumeric (lati) || ! isnumeric (loni))
+ error ("antipode: numeric arguments expected for latitude and longitude\n");
+ elseif (! ischar (unit))
+ error ("antipode: character argument expected for lat/lon unit\n");
+ elseif (nargin >= 3 && ! ismember (lower (unit(1)), {"d", "r"}))
+ error ("antipode: units must be one of 'Degrees' of 'Radians'");
+ endif
+
+ if (strncmpi (unit, "r", 1))
+ convfac = 180.0 / pi;
+ lati *= convfac;
+ loni *= convfac;
+ endif
+ lato = -abs (180.0 - wrapTo360 (lati - 90.0)) + 90.0;
+ lono = wrapTo180 (loni + 180);
+ if (strncmpi (unit, "r", 1))
+ lato /= convfac;
+ lono /= convfac;
+ endif
+
+endfunction
+
+%!test
+%! [lato, lono] = antipode (90, 0);
+%! assert ([lato, lono], [-90, 180], eps);
+
+%!test
+%! [lato, lono] = antipode (43, 15);
+%! assert ([lato, lono], [-43, -165], eps);
+
+%!test
+%! [lato, lono] = antipode ([-365; -360; -315; -270; -225; -185; -180; -135; -90; -45; 0; 45; 90; 135; 180; 225; 270; 315; 360], ...
+%! [-361; -359; -315; -270; -225; -185; -180; -135; -90; -45; 0; 45; 90; 135; 180; 225; 270; 315; 360]);
+%! assert ([lato, lono], [[5; 0; -45; -90; -45; -5; 0; 45; 90; 45; 0; -45; -90; -45; 0; 45; 90; 45; 0], ...
+%! [179; -179; -135; -90; -45; -5; 0; 45; 90; 135; 180; -135; -90; -45; 0; 45; 90; 135; 180]], eps);
+
+%!error <numeric argument> [a, b] = antipode ("a", 1);
+%!error <Invalid call> a = antipode (0, 0);
+%!error <Invalid call> [a, b] = antipode (0);
+%!error <character argument expected> [a, b] = antipode (0, 0, 0);
+%!error <units must be one> [a, b] = antipode (0, 0, "a");
diff --git a/inst/axes2ecc.m b/inst/axes2ecc.m
new file mode 100644
index 0000000..aacda22
--- /dev/null
+++ b/inst/axes2ecc.m
@@ -0,0 +1,93 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} {@var{ecc} =} axes2ecc (@var{semimajor}, @var{semiminor})
+## @deftypefnx {Function File} {} {@var{ecc} =} axes2ecc (@var{axes})
+## Calculates the eccentricity from semimajor and semiminor axes.
+##
+## @var{semimajor} and @var{semiminor} are scalars or vectors of
+## semimajor and semiminor axes, resp. Alternatively, they can also be
+## supplied as coordinate (row) vectors or a N2 matrix with each row
+## consisting of a (semimajor, semiminor) pair.
+##
+## Output arg @var{ecc} is a scalar or column vector of eccentricities.
+##
+## Examples:
+##
+## Scalar input:
+## @example
+## format long
+## axes2ecc (6378137, 6356752.314245)
+## => 0.0818191908429654
+## @end example
+##
+## Row vector (semimajor, semiminor):
+## @example
+## axes2ecc ([6378137, 6356752.314245])
+## => 0.0818191908429654
+## @end example
+##
+## Multivectors:
+## @example
+## axes2ecc ([ 71492, 66854; ...
+## 6378137, 6356752.314245])
+## ans =
+## 0.3543163789650412
+## 0.0818191908429654
+## @end example
+##
+## @seealso(ecc2flat,flat2ecc)
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9492
+
+function ecc = axes2ecc (semimajor, semiminor=[])
+
+ if (nargin < 1)
+ print_usage ();
+
+ elseif (ischar (semimajor) || ischar (semiminor))
+ error ("axes2ecc: input args must be numeric");
+
+ elseif (nargin == 1)
+ s = size (semimajor);
+ if (s(2) != 2)
+ error ("axes2ecc: Nx2 matrix expected for arg. #1");
+ endif
+ ecc = sqrt ((semimajor(:, 1) .^ 2 .- semimajor(:, 2) .^ 2) ./ ...
+ (semimajor(:, 1) .^ 2));
+ else
+ ecc = sqrt ((semimajor .^ 2 .- semiminor .^ 2) ./ (semimajor .^ 2));
+ endif
+
+endfunction
+
+
+%!test
+%! semimajor = 6378137;
+%! semiminor = 6356752.314245;
+%! Earth = [ semimajor, semiminor ];
+%! Jupiter = [ 71492 , 66854 ];
+%! Planets = [ Jupiter ; Earth ];
+%! assert (axes2ecc (semimajor, semiminor), 0.0818191908429654, 10e-12);
+%! assert (axes2ecc (Earth), 0.0818191908429654, 10e-12);
+%! assert (axes2ecc (Planets), [ 0.354316379; 0.081819190843 ], 10e-10);
+%! assert (axes2ecc (Planets(:, 1), Planets(:, 2)), [ 0.354316379; 0.081819190843 ], 10e-10);
+
+%!error <must be numeric> axes2ecc ("a", 1);
+%!error <Nx2 matrix expected> axes2ecc ([1; 2]);
diff --git a/inst/azimuth.m b/inst/azimuth.m
index cbb24bd..698e0fd 100644
--- a/inst/azimuth.m
+++ b/inst/azimuth.m
@@ -1,5 +1,5 @@
-## Copyright (C) 2004 Andrew Collier <abcollier@users.sourceforge.net>
-## Copyright (C) 2006 Alexander Barth <abarth93@users.sourceforge.net>
+## Copyright (C) 2004-2020 Andrew Collier <abcollier@users.sourceforge.net>
+## Copyright (C) 2006-2020 Alexander Barth <abarth93@users.sourceforge.net>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
diff --git a/inst/closePolygonParts.m b/inst/closePolygonParts.m
new file mode 100644
index 0000000..9a60863
--- /dev/null
+++ b/inst/closePolygonParts.m
@@ -0,0 +1,152 @@
+## Copyright (C) 2017-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} [@var{Xo}, @var{Yo}, ... ] = closePolygonParts (@var{Xi}, @var{Yi}, ...)
+## @deftypefnx {Function File} [@var{Xo}, @var{Yo}, ... ] = closePolygonParts (@var{Xi}, @var{Yi}, ..., @var{angunit})
+## Ensure that (each part of a multipart) polygon is a closed ring.
+##
+## For each (part of a) polygon, closePolygonParts checks if the vertices
+## of that part form a closed ring, i.e., if first and last vertices coincide.
+## If the polygon or polygon parts (the latter separated separated by NaNs) do
+## not form a closed ring, the first vertex coordinates of each open part are
+## appended after its last vertex. Input polygons need not be multipart.
+##
+## @var{Xi} and @var{Yi} (plus optionally, @var{Zi} and/or @var{Mi}) are input
+## vectors of X and Y or Longitude and Latitude (plus optionally Z or Height,
+## and/or M) vectors of vertex coordinates (and measure values) of an input
+## polygon. If a vector of measures is given as argument, it should always be
+## the last vector.
+##
+## Optional last argument @var{angunit} can be one of 'Degrees' or 'Radians'
+## (case-insensitive, only the first 3 characters need to match). If this
+## argument is given and if the first two input vectors are longitude/latitude
+## vectors rather than X/Y vectors, it indicates that the longitudes of those
+## first and last vectors need only coincide modulo 360 degrees or 2*pi radians.
+##
+## The number of output vectors @var{Xo}, @var{Yo}, ... matches the number of
+## input vectors.
+##
+## @seealso{isShapeMultiPart}
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2017-11-08
+
+function [varargout] = closePolygonParts (varargin)
+
+ nargs = nargin;
+ ang = 0;
+ ## Input checks
+ if (ischar (varargin{end}))
+ ang = find (ismember ({"rad", "deg"}, lower (varargin{end}(1:3))));
+ if (isempty (ang))
+ error ("closePolygonParts: unknown angle unit: '%s'; must be Degrees or Radians", ...
+ varargin{end});
+ endif
+ --nargs;
+ endif
+ if (! all (cellfun (@isnumeric, varargin(1:nargs))))
+ error ("closePolygonParts: numeric input vectors expected");
+ elseif (nargs != nargout)
+ error ("closePolygonParts: nr. of input vectors doesn't match nr. of output vectors");
+ endif
+ ## Check orientation, matching NaN positions and lengths of input vectors
+ mp = cell (nargs, 1);
+ for ii=1:nargs
+ mp(ii) = find (isnan (varargin{ii}));
+ endfor
+ for ii=2:nargs
+ if (numel (varargin{ii-1}) != numel (varargin{ii}))
+ error ("closePolygonParts: incompatible input vectors #%d and #%d", ii-1, ii);
+ endif
+ if (isrow (varargin{ii-1}) != isrow (varargin{ii}))
+ error ("closePolygonParts: all input vectors should have same dimension");
+ endif
+ ## Check NaN positions. M vectors may have more NaNs indicating missing values
+ if (! isempty (mp{ii-1}) && ! isempty (mp{ii}) && ...
+ numel (mp{ii-1}) == numel (mp{ii}))
+ ## The next loop uses ismember() to cope with M vectors with missing values
+ if (! all (ismember (mp{ii-1}, mp{ii})))
+ error ("closePolygonParts: NaN positions of arg# %d and arg #d don't match", ii-1, ii);
+ endif
+ endif
+ endfor
+
+ ## Input validation done, check for open rings.
+ ## Assess extent of each polygon part.
+ idn = [ 0 (find (isnan (varargin{1}))) (numel (varargin{1})+1) ];
+ ## Process polygons backwards to avoid stale (multipart) idn indices
+ for jj=numel (idn)-1 : -1 : 1
+ isclosed = true;
+ if (ang == 1)
+ isclosed = isclosed && ...
+ abs (wrapTo2Pi (varargin{1}(idn(jj)+1)) - wrapTo2Pi (varargin{1}(idn(jj+1)-1))) < eps;
+ elseif (ang == 2)
+ isclosed = isclosed && ...
+ abs (wrapTo360 (varargin{1}(idn(jj)+1)) - wrapTo360 (varargin{1}(idn(jj+1)-1))) < eps;
+ else
+ isclosed = isclosed && ...
+ abs (varargin{1}(idn(jj)+1) - varargin{1}(idn(jj+1)-1)) < eps;
+ endif
+ for ii=2:nargs
+ isclosed = isclosed && ...
+ abs (varargin{ii}(idn(jj)+1) - varargin{ii}(idn(jj+1)-1)) < eps;
+ endfor
+ if (! isclosed)
+ for ii=1:nargs
+ varargin{ii}(idn(jj+1):end+1) = varargin{ii}(idn(jj+1)-1:end);
+ varargin{ii}(idn(jj+1)) = varargin{ii}(idn(jj)+1);
+ endfor
+ endif
+ endfor
+
+ for ii=1:nargs
+ varargout{ii} = varargin{ii};
+ endfor
+
+endfunction
+
+
+%!test
+%! xi = [ 1 5 6 2 NaN 11 15 16 12 ];
+%! yi = [ 1 2 5 6 NaN 1 2 5 6 ];
+%! zi = [ 1 3 5 3 NaN 11 13 15 13 ];
+%! mi = [ 8 9 NaN -1 NaN NaN -3 -2 NaN];
+%! [a, b, c, d] = closePolygonParts (xi, yi, zi, mi);
+%! assert (a, [1 5 6 2 1 NaN 11 15 16 12 11], 1e-10);
+%! assert (b, [1 2 5 6 1 NaN 1 2 5 6 1], 1e-10);
+%! assert (c, [1 3 5 3 1 NaN 11 13 15 13 11], 1e-10);
+%! [d, e, f] = closePolygonParts (a, b, c);
+%! assert (a, d, 1e-10);
+%! assert (b, e, 1e-10);
+%! assert (c, f, 1e-10);
+
+%!test
+%! xxi = [ 400 405 406 402 NaN 311 315 316 312 671 ];
+%! yyi = [ 1 2 5 6 NaN 1 2 5 6 1 ];
+%! zzi = [ 1 3 5 3 NaN 11 13 15 13 11 ];
+%! [a, b, c] = closePolygonParts (xxi, yyi, zzi, "deg");
+%! assert (a, [400 405 406 402 400 NaN 311 315 316 312 671], 1e-10);
+%! assert (b, [ 1 2 5 6 1 NaN 1 2 5 6 1], 1e-10);
+%! assert (c, [ 1 3 5 3 1 NaN 11 13 15 13 11], 1e-10);
+
+%!error <unknown angle unit> a = closePolygonParts ([0 1], "ged");
+%!error <numeric input vectors expected> [a, b] = closePolygonParts ([0 1], {"c", "d"})
+%!error <nr. of input vectors> a = closePolygonParts ([0 1], [2 3])
+%!error <nr. of input vectors> a = closePolygonParts ("radians")
+%!error <incompatible input vectors> [a, b] = closePolygonParts ([0 NaN 1], [2 NaN 3 4])
+%!error <NaN positions of> [a, b] = closePolygonParts ([0 1 NaN 1], [2 NaN 3 4])
diff --git a/inst/deg2km.m b/inst/deg2km.m
index 4b687b9..d50d6f5 100644
--- a/inst/deg2km.m
+++ b/inst/deg2km.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2013 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2013-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
@@ -27,7 +27,8 @@
## "moon", "mars", "jupiter", "saturn", "uranus", "neptune", or "pluto", in
## which case radius will be set to that object mean radius.
##
-## @seealso{km2deg}
+## @seealso{deg2km, deg2sm, km2rad, km2deg,
+## nm2deg, nm2rad, rad2km, rad2nm, rad2sm, sm2deg, sm2rad}
## @end deftypefn
## Author: Alexander Barth <barth.alexander@gmail.com>
diff --git a/inst/deg2nm.m b/inst/deg2nm.m
new file mode 100644
index 0000000..728d266
--- /dev/null
+++ b/inst/deg2nm.m
@@ -0,0 +1,62 @@
+## Copyright (C) 2013-2020 Alexander Barth
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{nm} =} deg2nm (@var{deg})
+## @deftypefnx {Function File} {@var{nm} =} deg2nm (@var{deg}, @var{radius})
+## @deftypefnx {Function File} {@var{nm} =} deg2nm (@var{deg}, @var{sphere})
+## Converts angle in degrees to distance in nautical miles by multiplying angle
+## with radius.
+##
+## Calculates the distances @var{nm} in a sphere with @var{radius} (also in
+## nautical miles) for the angles @var{deg}. If unspecified, radius defaults to
+## 3440 nm, the mean radius of Earth.
+##
+## Alternatively, @var{sphere} can be one of "sun", "mercury", "venus", "earth",
+## "moon", "mars", "jupiter", "saturn", "uranus", "neptune", or "pluto", in
+## which case radius will be set to that object's mean radius.
+##
+## @seealso{deg2km, deg2sm, km2rad, km2deg,
+## nm2deg, nm2rad, rad2km, rad2nm, rad2sm, sm2deg, sm2rad}
+## @end deftypefn
+
+## Built with insight from
+## Author: Alexander Barth <barth.alexander@gmail.com>
+## Adapted from deg2km.m by Anonymous contributor, see patch #9709
+
+function nm = deg2nm (deg, radius="earth")
+
+ ## Check arguments
+ if (nargin < 1 || nargin > 2)
+ print_usage();
+ elseif (ischar (radius))
+ ## Get radius of sphere with its default units (km)
+ radius = km2nm (spheres_radius (radius));
+ ## Check input
+ elseif (! isnumeric (radius) || ! isreal (radius))
+ error ("deg2nm: RADIUS must be a numeric scalar");
+ endif
+ nm = (deg2rad (deg) * radius);
+
+endfunction
+
+
+%!test
+%!assert (nm2deg (deg2nm (10)), 10, 10*eps);
+%!assert (nm2deg (deg2nm (10, 80), 80), 10, 10*eps);
+%!assert (nm2deg (deg2nm (10, "pluto"), "pluto"), 10, 10*eps);
+
+%!error <RADIUS> deg2nm (5, 5i)
diff --git a/inst/deg2sm.m b/inst/deg2sm.m
new file mode 100644
index 0000000..ea60b2a
--- /dev/null
+++ b/inst/deg2sm.m
@@ -0,0 +1,62 @@
+## Copyright (C) 2013-2020 Alexander Barth
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{sm} =} deg2sm (@var{deg})
+## @deftypefnx {Function File} {@var{sm} =} deg2sm (@var{deg}, @var{radius})
+## @deftypefnx {Function File} {@var{sm} =} deg2sm (@var{deg}, @var{sphere})
+## Converts angle n degrees to distance in statute miles by multiplying angle
+## with radius.
+##
+## Calculates the distances @var{sm} in a sphere with @var{radius} (also in
+## statute miles) for the angles @var{deg}. If unspecified, radius defaults to
+## 3958 sm, the mean radius of Earth.
+##
+## Alternatively, @var{sphere} can be one of "sun", "mercury", "venus", "earth",
+## "moon", "mars", "jupiter", "saturn", "uranus", "neptune", or "pluto", in
+## which case radius will be set to that object's mean radius.
+##
+## @seealso{deg2km, deg2nm, km2rad, km2deg,
+## nm2deg, nm2rad, rad2km, rad2nm, rad2sm, sm2deg, sm2rad}
+## @end deftypefn
+
+## Built with insight from
+## Author: Alexander Barth <barth.alexander@gmail.com>
+## Adapted from deg2km.m by Anonymous contributor, see patch #9709
+
+function sm = deg2sm (deg, radius = "earth")
+
+ ## Check arguments
+ if (nargin < 1 || nargin > 2)
+ print_usage();
+ elseif (ischar (radius))
+ ## Get radius of sphere with its default units (km)
+ radius = km2sm (spheres_radius (radius));
+ ## Check input
+ elseif (! isnumeric (radius) || ! isreal (radius))
+ error ("deg2sm: RADIUS must be a numeric scalar");
+ endif
+ sm = (deg2rad (deg) * radius);
+
+endfunction
+
+
+%!test
+%!assert (sm2deg (deg2sm (10)), 10, 10*eps);
+%!assert (sm2deg (deg2sm (10, 80), 80), 10, 10*eps);
+%!assert (sm2deg (deg2sm (10, "pluto"), "pluto"), 10, 10*eps);
+
+%!error <RADIUS> deg2sm (5, 5i)
diff --git a/inst/degrees2dm.m b/inst/degrees2dm.m
index 36bb64a..1bfb713 100644
--- a/inst/degrees2dm.m
+++ b/inst/degrees2dm.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/degrees2dms.m b/inst/degrees2dms.m
index 444c494..3e9d9f3 100644
--- a/inst/degrees2dms.m
+++ b/inst/degrees2dms.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/degtorad.m b/inst/degtorad.m
index 25ebccf..7ac81f8 100644
--- a/inst/degtorad.m
+++ b/inst/degtorad.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/distance.m b/inst/distance.m
index 9d83dfa..d1d258e 100644
--- a/inst/distance.m
+++ b/inst/distance.m
@@ -1,5 +1,6 @@
-## Copyright (C) 2004 Andrew Collier <abcollier@users.sourceforge.net>
-## Copyright (C) 2011 Alexander Barth <abarth93@users.sourceforge.net>
+## Copyright (C) 2004-2020 Andrew Collier <abcollier@users.sourceforge.net>
+## Copyright (C) 2011-2020 Alexander Barth <abarth93@users.sourceforge.net>
+## Copyright (C) 2019-2020 Philip Nienhuis <prnienhuis@users.sf.net>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
@@ -15,16 +16,19 @@
## this program; if not, see <http://www.gnu.org/licenses/>.
## -*- texinfo -*-
-## @deftypefn {Function File} {} [@var{dist},@var{az}] = distance(@var{pt1}, @var{pt2})
-## @deftypefnx {Function File} {} [@var{dist},@var{az}] = distance(@var{pt1}, @var{pt2},@var{units})
-## @deftypefnx {Function File} {} [@var{dist},@var{az}] = distance(@var{lat1},@var{lon1},@var{lat2},@var{lon2})
-## @deftypefnx {Function File} {} [@var{dist},@var{az}] = distance(@var{lat1},@var{lon1},@var{lat2},@var{lon2},@var{units})
+## @deftypefn {Function File} {} [@var{dist}, @var{az}] = distance(@var{pt1}, @var{pt2})
+## @deftypefnx {Function File} {} [@var{dist}, @var{az}] = distance(@var{pt1}, @var{pt2},@var{units})
+## @deftypefnx {Function File} {} [@var{dist}, @var{az}] = distance(@var{lat1}, @var{lon1}, @var{lat2}, @var{lon2})
+## @deftypefnx {Function File} {} [@var{dist}, @var{az}] = distance(@var{lat1}, @var{lon1}, @var{lat2}, @var{lon2}, @var{units})
##
-## Calculates the great circle distance @var{dist} between @var{pt1} and @var{pt2} and optionally the azimuth @var{az}.
-## @var{pt1} and @var{pt2} are two-column matrices of the form [latitude longitude].
-## The coordinates can also be given by the parameters @var{lat1}, @var{lon1}, @var{lat2} and @var{lon2}.
-## Units can be either 'degrees' (the default) or 'radians'.
+## Calculates the great circle distance @var{dist} between @var{pt1} and
+## @var{pt2} and optionally the azimuth @var{az}.
##
+## @var{pt1} and @var{pt2} are two-column matrices of the form
+## [latitude longitude].
+## The coordinates can also be given by the parameters @var{lat1}, @var{lon1},
+## @var{lat2} and @var{lon2}.
+## Units can be either 'degrees' (the default) or 'radians'.
##
## @example
## >> distance([37,-76], [37,-9])
@@ -43,69 +47,68 @@
## Uses "cosine formula".
-function [dist,az] = distance(varargin)
- ## default units are degrees
+function [dist, az] = distance (varargin)
- units = 'degrees';
+ ## default units are degrees
+ units = "degrees";
- [reg,prop] = parseparams(varargin);
+ [reg, prop] = parseparams (varargin);
- if length(reg) == 2
+ if (length (reg) == 2)
pt1 = reg{1};
pt2 = reg{2};
- a = pt1(:,1);
- b = pt2(:,1);
- C = pt2(:,2) - pt1(:,2);
- elseif length(reg) == 4
+ a = pt1(:, 1);
+ b = pt2(:, 1);
+ C = pt2(:, 2) - pt1(:, 2);
+ elseif (length (reg) == 4)
a = reg{1};
b = reg{3};
C = reg{4} - reg{2};
else
- error('Wrong number of type of arguments');
- end
+ error ("Wrong number or type of arguments");
+ endif
- if length(prop) == 1
+ if (length (prop) == 1)
units = prop{1};
- if (~strcmp(units,'degrees') && ~strcmp(units,'radians'))
- error('Only degrees and radians are allowed as units');
- end
- elseif length(prop) > 1
- error('Wrong number of type of arguments');
- end
+ if (! strcmp (units, "degrees") && ! strcmp (units, "radians"))
+ error ("Only degrees and radians are allowed as units");
+ endif
+ elseif (length(prop) > 1)
+ error ("Wrong number of type of arguments");
+ endif
- if (strcmp(units,'degrees'))
- a = deg2rad(a);
- b = deg2rad(b);
- C = deg2rad(C);
- end
+ if (strcmp (units, "degrees"))
+ a = deg2rad (a);
+ b = deg2rad (b);
+ C = deg2rad (C);
+ endif
- dist = acos(sin(b) .* sin(a) + cos(b) .* cos(a) .* cos(C));
+ dist = acos (sin (b) .* sin (a) + cos (b) .* cos (a) .* cos (C));
- if (strcmp(units,'degrees'))
- dist = rad2deg(dist);
- end
+ if (strcmp (units, "degrees"))
+ dist = rad2deg (dist);
+ endif
- if nargout == 2
- az = atan2(sin(C) , cos(a) .* tan(b) - sin(a) .* cos(C) );
+ if (nargout == 2)
+ az = atan2 (sin(C), cos (a) .* tan (b) - sin (a) .* cos (C));
## bring the angle in the interval [0 2*pi[
-
- az = mod(az,2*pi);
+ az = mod(az, 2 * pi);
## convert to degrees if desired
-
- if (strcmp(units,'degrees'))
- az = rad2deg(az);
- end
- end
+ if (strcmp (units, "degrees"))
+ az = rad2deg (az);
+ endif
+ endif
endfunction
-%!assert(distance([37,-76], [37,-9]), 52.30942093, 1e-7)
+
+%!assert (distance ([37, -76], [37, -9]), 52.30942093, 1e-7)
%!test
-%! [d,az] = distance(0,0, 0,pi,'radians');
-%! assert(d,pi,1e-7)
-%! assert(az,pi/2,1e-7)
+%! [d, az] = distance (0, 0, 0, pi, "radians");
+%! assert (d, pi, 1e-7)
+%! assert (az, pi / 2, 1e-7)
diff --git a/inst/dm2degrees.m b/inst/dm2degrees.m
index 861f0cc..7d560d6 100644
--- a/inst/dm2degrees.m
+++ b/inst/dm2degrees.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/dms2degrees.m b/inst/dms2degrees.m
index 51b5993..69bc5cd 100644
--- a/inst/dms2degrees.m
+++ b/inst/dms2degrees.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/dxfdraw.m b/inst/dxfdraw.m
new file mode 100644
index 0000000..d70da84
--- /dev/null
+++ b/inst/dxfdraw.m
@@ -0,0 +1,178 @@
+## Copyright (C) 2016-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} [@var{h}] = dxfdraw (@var{dxf})
+## @deftypefnx {} [@var{h}] = dxfdraw (@var{dxf}, @var{clr})
+## @deftypefnx {} [@var{h}] = dxfdraw (@dots{}, @var{name}, @var{value}, @dots{})
+## Draw a map of a DXF file based on a DXF cell array or DXF drawing struct.
+##
+## Input argument @var{dxf} is the name of a DXF cell array (see
+## dxfread.m), or the name of a DXF file, or a DXF drawing struct made
+## by dxfparse.
+##
+## @var{clr} is the color used to draw the DXF contents. All lines and
+## arcs are drawn in the same color; similar for all filled surfaces.
+## For points, lines and polylines this can be a 3x1 RGB vector or a color
+## code. For polygons it can be a 2x1 vector of color codes or a 2x3 double
+## array of RGB entries. The default is [0.7 0.7 0.7; 0.8 0.9 0.99].
+##
+## In addition several graphics properties can be specified, e.g., linestyle
+## and linewidth. No checks are performed whether these are valid for the
+## entities present in the DXF cell array or file.
+##
+## Currently the following entities are supported: POINT, LINE, POLYLINE,
+## LWPOLYLINE, CIRCLE, ARC and 3DFACE. For drawing CIRCLE and ARC entities
+## functions from the geometry packge are required.
+##
+## Optional output argument @var{h} is the graphics handle of the resulting
+## map.
+##
+## @seealso{dxfread, dxfparse}
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2016-01-25
+
+function [h] = dxfdraw (dxf, clr=[0.7 0.7 0.7; 0.8 0.9 0.99], varargin)
+
+ if (isstruct (dxf))
+ ## Cursory validation
+ fldnm = fieldnames (dxf);
+ if (! all (ismember({"i3d", "ic", "ia", "j3", "jr", "il", "ip"}, fldnm)))
+ error ("dxfdraw: invalid struct input for arg #1");
+ endif
+
+ else
+ if (isempty (dxf))
+ return
+
+ elseif (iscell (dxf))
+ ## Cursory Q-checks: minumum 3 columns, col 1 & 3 numeric, col #2 character
+ if (size (dxf, 2) < 3 || ! all (all (cellfun (@isnumeric, dxf(:, [1, 3])))) ...
+ || ! all (cellfun (@ischar, dxf(:, 2))))
+ error ("dxfdraw: input arg #1 does not look like a valid dxf cell array");
+ endif
+
+ elseif (ischar (dxf))
+ ## Maybe a DXF file?
+ [~, ~, ext] = fileparts (dxf);
+ if (isempty (ext) || ! strcmpi (ext, ".dxf"))
+ ## Just add a .dxf suffix
+ dxf = [dxf ".dxf"];
+ endif
+ fid = fopen (dxf);
+ if (fid < 0)
+ error ("File '%s' not found", dxf);
+ else
+ fclose (fid);
+ endif
+ ## Read DXF file into DXF cell array
+ dxf = dxfread (dxf);
+
+ else
+ error ("dxfdraw: DXF cell array, DXF file name, or DXF drawing struct \
+expected for arg #1 ");
+ endif
+
+ ## parse DXF cell array into a DXF drawing struct
+ dxf = dxfparse (dxf, 0);
+ endif
+
+ ## We should have a valid struct now. Extract data
+ i3d = dxf.i3d;
+ is = dxf.is;
+ ir = dxf.ir;
+ ic = dxf.ic;
+ ia = dxf.ia;
+ j3 = dxf.j3;
+ jr = dxf.jr;
+ il = dxf.il;
+ ip = dxf.ip;
+ jf = dxf.jf;
+ jw = dxf.jw;
+ jl = dxf.jl;
+ XYZ = dxf.XYZ;
+ XYZp = dxf.XYZp;
+ XY = dxf.XY;
+ CIRCLES = dxf.CIRCLES;
+ ARCS = dxf.ARCS;
+ VRT3 = dxf.VRT3;
+ FAC3 = dxf.FAC3;
+ LWP = dxf.LWP;
+ LWV = dxf.LWV;
+ VRTS = dxf.VRTS;
+ FACP = dxf.FACP;
+ ## dxf not needed from here, clear it as it may hold lots of RAM needed for plot
+ clear dxf;
+
+ h = figure ();
+ hold on;
+ axis equal;
+
+ if (i3d)
+ if (is > 0)
+ plot3 (XYZp(:, 1), XYZp(:, 2), XYZp(:, 3), "color", clr(1, :), varargin{:});
+ endif
+ if (ir > 0)
+ plot3 (XYZ(:, 1), XYZ(:, 2), XYZ(:, 3), "color", clr(1, :), varargin{:});
+ endif
+ if (ic > 0)
+ drawCircle3d (CIRCLES, "color", clr(1, :), varargin{:});
+ endif
+ if (ia > 0)
+ drawCircleArc3d (ARCS, "color", clr(1, :), varargin{:});
+ endif
+ if (j3 > 0)
+ patch ("vertices", VRT3, "faces", FAC3, "edgecolor", clr(1, :), ...
+ "facecolor", clr(2, :), varargin{:});
+ endif
+
+ else
+ if (is > 0)
+ plot (XYZp(:, 1), XYZp(:, 2), "color", clr(1, :), varargin{:});
+ endif
+ if (ir > 0)
+ plot (XYZ(:, 1), XYZ(:, 2), "color", clr(1, :), varargin{:});
+ endif
+ if (ic > 0)
+ drawCircle (CIRCLES, "color", clr(1, :), varargin{:});
+ endif
+ if (ia > 0)
+ drawCircleArc (ARCS, "color", clr(1, :), varargin{:});
+ endif
+ if (jr > 0)
+ plot (XY(:, 1), XY(:, 2), "color", clr(1, :), varargin{:});
+ endif
+ if (il > 0)
+ LWV(jw+1:end, :) = [];
+ LWV(LWV == 0) = NaN;
+ patch ("vertices", LWP, "faces", LWV, "edgecolor", clr(1, :), ...
+ "facecolor", clr(2, :), varargin{:});
+ endif
+
+ endif
+
+ if (ip > 0)
+ FACP(jf+1:end, :) = [];
+ FACP(FACP == 0) = NaN;
+ if (! i3d)
+ VRTS(:, 3) = [];
+ endif
+ patch ("vertices", VRTS, "faces", FACP, "edgecolor", clr(1, :), ...
+ "facecolor", clr(2, :), varargin{:});
+ endif
+
+endfunction
diff --git a/inst/dxfparse.m b/inst/dxfparse.m
new file mode 100644
index 0000000..15e3f39
--- /dev/null
+++ b/inst/dxfparse.m
@@ -0,0 +1,477 @@
+## Copyright (C) 2017-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{dxfo} =} dxfparse (@var{dxfi})
+## @deftypefnx {} {@var{dxfo} =} dxfparse (@var{dxfi}, @var{outstyle})
+## Parse a DXF struct (read by dxfread) into a DXF drawing struct or
+## into a mapstruct.
+##
+## Input arg @var{dxfi} can be a DXF cell array produced by dxfread, or a
+## DXF file name (dxfparse will invoke dfread to read it).
+##
+## Optional numeric input argument @var{outstyle} can be used to select
+## a desired output format:
+##
+## @itemize
+## @item 0 (default)
+## Return an output struct optimized for fast drawing with dxfdraw.m
+##
+## @item 1
+## Return an output struct containing 2D (Matlab-compatible) mapstructs
+## like those returned by shaperead.m. The output struct contains a "dxfpl"
+## Polyline mapstruct for LINEs and open POLYLINEs and LWPOLYLINE entities;
+## a "dxfpt" Point mapstruct for POINT entities; and a "dxfpg" Polygon
+## mapstucts for closed POLYLINE and LWPOLYLINE entities.
+##
+## @item 2
+## If the DXF file is 3D, return a 3D mapstruct as returned by shaperead.m
+## with Z coordinates.
+## @end itemize
+##
+## @seealso{dxfread, dxfdraw}
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2017-01-07
+
+function [dxfo] = dxfparse (dxfi, outstyle=0)
+
+ if (isempty (dxfi))
+ return
+
+ elseif (iscell (dxfi))
+ ## Cursory Q-checks: minumum 3 columns, col 1 & 3 numeric, col #2 character
+ if (size (dxfi, 2) < 3 || ! all (all (cellfun (@isnumeric, dxfi(:, [1, 3])))) ...
+ || ! all (cellfun (@ischar, dxfi(:, 2))))
+ error ("dxfdraw: input arg #1 does not look like a valid dxf cell array");
+ endif
+
+ elseif (ischar (dxfi))
+ ## Maybe a DXF file?
+ [~, ~, ext] = fileparts (dxfi);
+ if (isempty (ext) || ! strcmpi (ext, ".dxf"))
+ ## Just add a .dxf suffix
+ dxfi = [dxfi ".dxf"];
+ endif
+ fid = fopen (dxfi);
+ if (fid < 0)
+ error ("File '%s' not found", dxfi);
+ else
+ fclose (fid);
+ endif
+ ## Read DXF file
+ dxfi = dxfread (dxfi);
+
+ else
+ error ("dxfdraw: DXF cell array or DXF file name expected for arg #1 ");
+ endif
+
+ ## Interpret DXF struct
+ ## 1. Cast numeric parts of dxf into double to speed up processing
+ dxfn = cell2mat (dxfi(:, [1, 3]));
+
+ ## 2. Find extent of different ENTITIES in file
+ idx = find (dxfn(:, 1) == 0);
+ idx(idx < strmatch ("ENTITIES", dxfi(:, 2))) = [];
+ idx(find (ismember (idx, strmatch ("VERTEX", dxfi(:, 2))))) = [];
+ idx(find (ismember (idx, strmatch ("SEQEND", dxfi(:, 2))))) = [];
+
+ ## 3. Is DXF 3D or not
+ iz = find (cell2mat (dxfi(:, 1)) == 30);
+ i3d = ! isempty (iz) && any (abs (cell2mat (dxfi(iz, 3))) > eps);
+
+ ## 4. Preallocate XYZp array for POINTS
+ ns = numel (strmatch ("POINT", dxfi(:, 2)));
+ is = 0; ## Pointer array row counter
+ if (outstyle == 0)
+ XYZp = NaN (ns*2-1, 3);
+ plyrs = cell (ns, 1);
+ else
+ ## Preallocate 2D mapstruct. If Z is needed it'll be added automatically
+ ## the first time it is referenced in one action
+ dxfpt = repmat (struct ("Geometry", "Point", ...
+ "X", NaN, ...
+ "Y", NaN), ns, 1);
+ endif
+
+ ## 5. Preallocate XYZ array to hold LINE & POLYLINE coordinates.
+ ## 2 rows + NaN row for all LINE entities
+ nr = numel (strmatch ("LINE", dxfi(:, 2))) * 3;
+ ## 1 NaN row for each POLYLINE
+ np = numel (strmatch ("POLYLINE", dxfi(:, 2)));
+ nr += np;
+ jl = jp = 0;
+ if (outstyle == 0)
+ ## Allocate layer info
+ llyrs = cell (np+nr/3, 1);
+ ## 1 row for each VERTEX
+ nr += numel (strmatch ("VERTEX", dxfi(:, 2)));
+ ## Avoid trailing NaN row
+ XYZ = NaN (nr-1, 3);
+ ir = 0; ## (poly)lines / llyrs row index
+ ## Vertices for polygons
+ VRTS = NaN (nr-1, 3);
+ ## Allocate provisionally 100 vertices per facet
+ FACP = NaN (np, 100);
+ ip = jf = 0; ## polygon vertices/faces row indices
+ else
+ ## We can't foretell which POLYLINES are closed or open ==> assign struct
+ ## arrays large enough for polygons and polylines alike
+ dxfpl = repmat (struct ("Geometry", "Polyline", ...
+ "BoundingBox", NaN (2, 2), ...
+ "X", NaN, ...
+ "Y", NaN), nr, 1);
+ dxfpg = repmat (struct ("Geometry", "Polygon", ...
+ "BoundingBox", NaN (2, 2), ...
+ "X", NaN, ...
+ "Y", NaN), nr, 1);
+ endif
+
+ ## 6. LWPOLYLINE - no real preallocation yet, will be extended incrementally
+ nw = numel (strmatch ("LWPOLYLINE", dxfi(:, 2)));
+ wlyrs = cell (nw, 1);
+ if (outstyle == 0)
+ LWP = NaN (5000, 2);
+ LWV = NaN (250, 100);
+ il = jw = jr = iw = 0; ## polyline vertices/faces/lyrs row indices
+ XY = NaN (5000, 2);
+ else
+ ## Extend Polyline/-gon arrays. If we have LWPOLYLINE the DXF file is 2D
+ dxfpl = repmat (struct ("Geometry", "Polyline", ...
+ "BoundingBox", NaN (2, 2), ...
+ "X", NaN, ...
+ "Y", NaN), nr+nw, 1);
+ dxfpg = repmat (struct ("Geometry", "Polygon", ...
+ "BoundingBox", NaN (2, 2), ...
+ "X", NaN, ...
+ "Y", NaN), nr+nw, 1);
+ endif
+
+ ## 7. Preallocate array for CIRCLEs
+ nc = numel (strmatch ("CIRCLE", dxfi(:, 2)));
+ if (i3d)
+ CIRCLES = zeros (nc, 4);
+ else
+ CIRCLES = zeros (nc, 3);
+ endif
+ ic = 0; ## Circle row counter
+ clyrs = cell (nc, 1);
+
+ ## 8. Preallocate array for ARCs
+ na = numel (strmatch ("ARC", dxfi(:, 2), "exact"));
+ if (i3d)
+ ARCS = zeros (na, 6);
+ else
+ ARCS = zeros (na, 5);
+ endif
+ ia = 0; ## Arc row counter
+ alyrs = cell (na, 1);
+
+ ## 9. Preallocate 3DFACE array
+ n3 = numel (strmatch ("3DFACE", dxfi(:, 2)));
+ VRT3 = NaN (4*n3, 3);
+ FAC3 = NaN (n3, 5);
+ iv = j3 = id = 0; ## 3Dface / dlyrs row counter
+ dlyrs = cell (n3, 1);
+
+ ## 10. Start interpreting. For each ENTITY:
+ for ii=1:numel (idx) - 1
+ switch dxfi{idx(ii), 2}
+
+ case "POINT"
+ x1 = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 10) + idx(ii)-1, 2);
+ y1 = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 20) + idx(ii)-1, 2);
+ z1 = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 30) + idx(ii)-1, 2);
+ lb = dxfi(find (dxfn(idx(ii):idx(ii+1), 1) == 8) + idx(ii)-1, 2){1};
+ if (outsyle == 0)
+ ## X, Y [,Z] coordinates
+ XYZp(++is, 1) = x1;
+ XYZ(is, 2) = y1;
+ if (i3d)
+ XYZp(is, 3) = z1;
+ endif
+ plyrs{is} = lb;
+ ## Leave NaN row before next point
+ ++is;
+ else
+ dxfpt(++is).Geometry = "Point";
+ dxfpt(is).X = x1;
+ dxfpt(is).Y = y1;
+ if (outstyle == 2 && i3d)
+ dxfpt(is).Z = z1;
+ endif
+ dxfpt(is).LAYER = lb;
+ dxfpt(is)._SOURCE_ = "DXF_POINT";
+ endif
+
+ case "LINE"
+ ## X, Y [,Z] coordinates of end points
+ x1 = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 10) + idx(ii)-1, 2);
+ y1 = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 20) + idx(ii)-1, 2);
+ x2 = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 11) + idx(ii)-1, 2);
+ y2 = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 21) + idx(ii)-1, 2);
+ lb = dxfi(find (dxfn(idx(ii):idx(ii+1), 1) == 8) + idx(ii)-1, 2){1};
+ if (i3d)
+ z1 = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 30) + idx(ii)-1, 2);
+ z2 = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 31) + idx(ii)-1, 2);
+ endif
+ if (outstyle == 0)
+ XYZ(++ir, 1) = x1;
+ XYZ(ir, 2) = y1;
+ XYZ(++ir, 1) = x2;
+ XYZ(ir, 2) = y2;
+ if (i3d)
+ XYZ(ir-1, 3) = z1;
+ XYZ(ir, 3) = z2;
+ endif
+ llyrs{++jl} = lb;
+ ++ir;
+ else
+ dxfpl(++jl).Geometry = "Polyline";
+ dxfpl(jl).X = [x1 x2];
+ dxfpl(jl).Y = [y1 y2];
+ dxfpl(jl).BoundingBox = [min(x1, x2) min(y1, y2); max(x1, x2) max(y1, y2)];
+ if (outstyle == 2 && i3d)
+ dxfpl(jl).Z = [z1 z2];
+ endif
+ dxfpl(jl).LAYER = lb;
+ dxfpl(jl)._SOURCE_ = "DXF_LINE";
+ endif
+
+ case "POLYLINE"
+ ## Find nr. of vertices
+ jv = strmatch ("VERTEX", dxfi(idx(ii):idx(ii+1), 2)) + idx(ii) - 1;
+ nv = numel (jv);
+ ## Get polyline flag and check if it's a closed one (i.e., a polygon)
+ pflags = uint8 (dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 70) + idx(ii) - 1, 2));
+ ## Polygon / closed polyline. Get vertices
+ xx = dxfn(find (dxfn(jv(1):idx(ii+1), 1) == 10) + jv(1) - 1, 2);
+ yy = dxfn(find (dxfn(jv(1):idx(ii+1), 1) == 20) + jv(1) - 1, 2);
+ lb = dxfi(find (dxfn(idx(ii):idx(ii+1), 1) == 8) + idx(ii)-1, 2){1};
+ if (i3d)
+ zz = dxfn(find (dxfn(jv(1):idx(ii+1), 1) == 30) + jv(1) - 1, 2);
+ endif
+ if (outstyle == 0)
+ if (bitget (pflags, 1))
+ ## Polygon / closed polyline. Get vertices
+ VRTS(++ip:ip+nv-1, 1) = xx;
+ VRTS(ip:ip+nv-1, 2) = yy;
+ if (i3d)
+ VRTS(ip:ip+nv-1, 3) = zz;
+ endif
+ ip += nv - 1;
+ ++jf;
+ ## Update faces
+ FACP(jf, 1:nv+1) = [ (ip-nv + 1 : ip) (ip - nv + 1) ];
+ else
+ ## Open polyline
+ XYZ(++ir:ir+nv-1, 1) = xx;
+ XYZ(ir:ir+nv-1, 2) = yy;
+ if (i3d)
+ XYZ(ir:ir+nv-1, 3) = zz;
+ endif
+ ir += nv;
+ endif
+ llyrs(++jl) = lb;
+ else
+ if (bitget (pflags, 1))
+ ## Polygon / closed polyline
+ dxfpg(++jp).Geometry = "Polygon";
+ dxfpg(jp).X = xx';
+ dxfpg(jp).Y = yy';
+ dxfpg(jp).BoundingBox = [min(xx) min(yy); max(xx) max(yy)];
+ if (outstyle == 2 && i3d)
+ dxfpg(jp).Z = zz';
+ endif
+ dxfpg(jp).LAYER = lb;
+ dxfpg(jp)._SOURCE_ = "DXF_POLYLN";
+ else
+ dxfpl(++jl).Geometry = "Line";
+ dxfpl(jl).X = xx';
+ dxfpl(jl).Y = yy';
+ dxfpl(jl).BoundingBox = [min(xx) min(yy); max(xx) max(yy)];
+ if (outstyle == 2 && i3d)
+ dxfpl(jl).Z = zz';
+ endif
+ dxfpl(jl).LAYER = lb;
+ dxfpl(jl)._SOURCE_ = "DXF_POLYLN";
+ endif
+ endif
+
+ case "LWPOLYLINE"
+ if (i3d)
+ error ("Inconsistent DXF input - LWPOLYLINE = 2D but there are 3D entities");
+ endif
+ ## Nr. of vertices
+ nw = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 90) + idx(ii)-1, 2);
+ xx = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 10) + idx(ii)-1, 2);
+ yy = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 20) + idx(ii)-1, 2);
+ lb = dxfi(find (dxfn(idx(ii):idx(ii+1), 1) == 8) + idx(ii)-1, 2){1};
+ ## Get polyline flag and check if it's a closed one (i.e., a polygon)
+ pflags = uint8 (dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 70) + idx(ii) - 1, 2));
+ if (outstyle == 0)
+ if (bitget (pflags, 1))
+ ## Closed polyline
+ if (il + nw > size (LWP, 1))
+ ## Extend LWP array (vertices)
+ LWP = [LWP ; NaN(5000, 2)];
+ endif
+ if (jw + 1 > size (LWV, 1))
+ ## Extend LWV array (faces)
+ LWV = [LWV ; NaN(250, size(LWV, 2))];
+ endif
+ LWP(il+1:il+nw, 1) = xx;
+ LWP(il+1:il+nw, 2) = yy;
+ ++jw;
+ LWV(jw, 1:nw+1) = [ (il+1 : il+nw) (il + 1) ];
+ il += nw - 1;
+ else
+ ## Open polyline
+ if (jr + nw + 1 > size (XY, 1))
+ ## Extend XY array
+ XY = [XY ; XY(5000, 2)];
+ endif
+ XY(jr+1:jr+nw, 1) = xx;
+ XY(jr+1:jr+nw, 2) = yy;
+ jr += nw;
+ endif
+ wlyrs(++iw) = lb;
+ else
+ if (bitget (pflags, 1))
+ ## Polygon / closed polyline
+ dxfpg(++jp).Geometry = "Polygon";
+ dxfpg(jp).X = xx';
+ dxfpg(jp).Y = yy';
+ dxfpg(jp).BoundingBox = [min(xx) min(yy); max(xx) max(yy)];
+ dxfpg(jp).LAYER = lb;
+ dxfpg(jp)._SOURCE_ = "DXF_LWPOLY";
+ else
+ dxfpl(++jl).Geometry = "Line";
+ dxfpl(jl).X = xx';
+ dxfpl(jl).Y = yy';
+ dxfpl(jl).BoundingBox = [min(xx) min(yy); max(xx) max(yy)];
+ dxfpl(jl).LAYER = lb;
+ dxfpl(jl)._SOURCE_ = "DXF_LWPOLY";
+ endif
+ endif
+
+ case "CIRCLE"
+ if (outstyle == 0)
+ ## Get center point
+ CIRCLES(++ic, 1) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 10) + idx(ii)-1, 2);
+ CIRCLES(ic, 2) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 20) + idx(ii)-1, 2);
+ if (i3d)
+ CIRCLES(ic, 3) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 30) + idx(ii)-1, 2);
+ CIRCLES(ic, 4) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 40) + idx(ii)-1, 2);
+ else
+ CIRCLES(ic, 3) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 40) + idx(ii)-1, 2);
+ endif
+ clyrs(ic) = dxfi(find (dxfn(idx(ii):idx(ii+1), 1) == 8) + idx(ii)-1, 2){1};
+ endif
+
+ case "ARC"
+ if (outstyle == 0)
+ ## Get center point
+ ARCS(++ia, 1) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 10) + idx(ii)-1, 2);
+ ARCS(ia, 2) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 20) + idx(ii)-1, 2);
+ if (i3d)
+ ARCS(ia, 3) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 30) + idx(ii)-1, 2);
+ ARCS(ia, 4) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 40) + idx(ii)-1, 2);
+ ARCS(ia, 5) = wrapTo180 (dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 50) + idx(ii)-1, 2));
+ ARCS(ia, 6) = wrapTo180 (dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 51) + idx(ii)-1, 2)) - ARCS(ia, 5);
+ if (ARCS(ia, 6) < 0)
+ ARCS(ia, 6) = wrapTo360 (ARCS(ia, 6));
+ endif
+ else
+ ARCS(ia, 3) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 40) + idx(ii)-1, 2);
+ ARCS(ia, 4) = wrapTo180 (dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 50) + idx(ii)-1, 2));
+ ARCS(ia, 5) = wrapTo180 (dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 51) + idx(ii)-1, 2)) - ARCS(ia, 4);
+ if (ARCS(ia, 5) < 0)
+ ARCS(ia, 5) = wrapTo360 (ARCS(ia, 5));
+ endif
+ endif
+ alyrs{ia} = dxfi(find (dxfn(idx(ii):idx(ii+1), 1) == 8) + idx(ii)-1, 2){1};
+ endif
+
+ case "3DFACE"
+ if (outstyle == 0)
+ VRT3(++iv, 1) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 10) + idx(ii)-1, 2);
+ VRT3(iv, 2) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 20) + idx(ii)-1, 2);
+ VRT3(iv, 3) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 30) + idx(ii)-1, 2);
+ VRT3(++iv, 1) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 11) + idx(ii)-1, 2);
+ VRT3(iv, 2) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 21) + idx(ii)-1, 2);
+ VRT3(iv, 3) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 31) + idx(ii)-1, 2);
+ VRT3(++iv, 1) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 12) + idx(ii)-1, 2);
+ VRT3(iv, 2) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 22) + idx(ii)-1, 2);
+ VRT3(iv, 3) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 32) + idx(ii)-1, 2);
+ VRT3(++iv, 1) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 13) + idx(ii)-1, 2);
+ VRT3(iv, 2) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 23) + idx(ii)-1, 2);
+ VRT3(iv, 3) = dxfn(find (dxfn(idx(ii):idx(ii+1), 1) == 33) + idx(ii)-1, 2);
+ ++j3;
+ FAC3(j3, :) = [ ((j3-1)*4+1 : (j3-1)*4+4) (j3-1)*4+1 ];
+ ++id;
+ dlyrs{id, 1} = dxfi(find (dxfn(idx(ii):idx(ii+1), 1) == 8) + idx(ii)-1, 2){1};
+ endif
+
+ otherwise
+ ## Ignored entities
+
+ endswitch
+ endfor
+
+ ## Put all info in a struct to speed up redrawing
+ if (outstyle == 0)
+ dxfo.i3d = i3d;
+ dxfo.is = is;
+ dxfo.ir = ir;
+ dxfo.ic = ic;
+ dxfo.ia = ia;
+ dxfo.j3 = j3;
+ dxfo.jr = jr;
+ dxfo.il = il;
+ dxfo.ip = ip;
+ dxfo.jf = jf;
+ dxfo.jw = jw;
+ dxfo.jl = jl;
+ dxfo.XYZ = XYZ;
+ dxfo.XYZp = XYZp;
+ if (il > 0)
+ dxfo.LWP = LWP(1:il+1, :);
+ dxfo.LWV = LWV(1:jw, :);
+ LWV(LWV == 0) = NaN;
+ else
+ dxfo.LWP = dxfo.LWV = [];
+ endif
+ dxfo.XY = XY(1:jr, :);
+ dxfo.CIRCLES = CIRCLES;
+ dxfo.ARCS = ARCS;
+ dxfo.VRT3 = VRT3;
+ dxfo.FAC3 = FAC3;
+ dxfo.VRTS = VRTS;
+ dxfo.FACP = FACP;
+ else
+ if (is > 0)
+ dxfo.dxfpt = dxfpt;
+ endif
+ if (jl > 0)
+ dxfo.dxfpl = dxfpl(1:jl);
+ endif
+ if (jp > 0)
+ dxfo.dxfpg = dxfpg(1:jp);
+ endif
+ endif
+
+endfunction
diff --git a/inst/dxfread.m b/inst/dxfread.m
new file mode 100644
index 0000000..6dd0eb3
--- /dev/null
+++ b/inst/dxfread.m
@@ -0,0 +1,72 @@
+## Copyright (C) 2016-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{dxf} =} dxfread (@var{fname})
+## Read a DXF file (text file) into a Nx3 cell array.
+##
+## @var{fname} is the file name or full path name of a text format (not
+## binary) DXF file, with or without "dxf" extension.
+##
+## Output variable @var{dxf} is a cell array with DXF codes in column 1,
+## the corresponding DXF text info (or empty string) in column 2, and
+## corresponding numeric values (or NaN) in column 3. Use dxfparse for
+## converting the output into a DXF drawing struct or separate mapstructs
+## for each ENTITY type in the DXF file.
+##
+## @seealso{dxfparse, dxfdraw}
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2016-01-25
+
+function [dxf] = dxfread (fname)
+
+ if (! ischar (fname))
+ print_usage ();
+ endif
+
+ [pth, fn, ext] = fileparts (fname);
+ if (isempty (ext))
+ ext = ".dxf";
+ fname = [fname ext];
+ elseif (! strcmpi (ext, ".dxf"))
+ error ("dxfread: file is no .DXF file");
+ endif
+
+ fid = fopen (fname);
+ if (fid < 0)
+ error ("file %s not found", fname);
+ else
+ txt = fread (fid, Inf, "char=>char")';
+ fclose (fid);
+ endif
+
+ ## Assess EOL character
+ eol = regexp (txt(1: min (2000, length (txt))), "\r\n", "match", "once");
+ if (isempty (eol))
+ eol = "\n";
+ endif
+
+ ## DXF files comprise pairs of lines: 1st line = numeric code, 2nd = contents
+ dxf = reshape (cell2mat ( ...
+ regexp (txt, sprintf ('(\\d+)%s(.+?)%s', eol, eol), "tokens")), 2, [])';
+ ## Convert col 1 into numeric
+ dxf(:, 1) = num2cell (str2double (dxf(:, 1)));
+ ## Convert numeric entries in col2 into numeric col3
+ dxf(:, 3) = num2cell (str2double (dxf(:, 2)));
+ dxf(! cellfun (@isnan, dxf(:, 3)), 2) = {""};
+
+endfunction
diff --git a/inst/earthRadius.m b/inst/earthRadius.m
new file mode 100644
index 0000000..74aa0f0
--- /dev/null
+++ b/inst/earthRadius.m
@@ -0,0 +1,50 @@
+## Copyright (C) 2017-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} earthRadius (@var{unit})
+## Converts the Earth's radius into other units.
+##
+## Input argument @var{units} can be one of the units of validateLengthUnit.
+## The default is meters.
+##
+## @example
+## earthRadius ('km')
+## => ans = 6371
+## @end example
+##
+## @seealso {validateLengthUnit,unitsratio}
+## @end deftypefn
+
+function R = earthRadius (unit)
+
+ radius = spheres_radius ("earth") * 1000; ## This is the default in meters
+ if (nargin == 0)
+ R = radius;
+ elseif (nargin > 1)
+ print_usage ();
+ elseif ( ! ischar( unit ) )
+ error ("earthRadius.m: string value expected");
+ else
+ ratio = unitsratio (unit , "Meters");
+ R = radius * ratio;
+ endif
+
+endfunction
+
+%!test
+%! radius = earthRadius / 1000;;
+%! assert (earthRadius ("km"), radius);
diff --git a/inst/ecc2flat.m b/inst/ecc2flat.m
new file mode 100644
index 0000000..6d05f7b
--- /dev/null
+++ b/inst/ecc2flat.m
@@ -0,0 +1,66 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{flat} =} flat2ecc (@var{ecc})
+## Return flattening given an eccentricity
+##
+## Exmples:
+##
+## Scalar input:
+## @example
+## e_earth = .081819221456;
+## f_earth = ecc2flat (e_earth)
+## => f_earth = 0.0033528
+## @end example
+##
+## Vector input:
+## @example
+## ec = 0 : .01 : .05;
+## f = ecc2flat (ec)
+## => f =
+## 0.0000000 0.0000500 0.0002000 0.0004501 0.0008003 0.0012508
+## @end example
+##
+## @seealso{flat2ecc}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9492
+
+function ff = ecc2flat (ec)
+
+ if nargin < 1
+ print_usage ();
+ endif
+
+ if (! isnumeric (ec))
+ error ("ecc2flat.m: numeric input expected");
+ elseif (any (ec < 0) || any (ec >= 1))
+ error ("ecc2flat.m: eccentricity must lie in the real interval [0..1)");
+ else
+ ff = 1 - sqrt (1 - ec.^2);
+ endif
+
+endfunction
+
+%!test
+%! ec = 0.081819221456;
+%! ev = 0 : 0.01 : 0.05;
+%! assert (ecc2flat (ec), 0.00335281317793612, 10^-12);
+%! assert (ecc2flat (ev), [0, 5e-5, 2e-4, 4.501e-4, 8.0032e-4, 0.00125078], 10^-6);
+
+%!error <numeric input expected> ecc2flat ("a")
+%!error <eccentricity> ecc2flat(1)
diff --git a/inst/ecc2n.m b/inst/ecc2n.m
new file mode 100644
index 0000000..cabea91
--- /dev/null
+++ b/inst/ecc2n.m
@@ -0,0 +1,79 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{n} =} ecc2n (@var{ecc})
+## This returns the third flattening given an eccentricity.
+##
+## Examples:
+##
+## Scalar input:
+## @example
+## e_earth = 0.081819221456;
+## n_earth = ecc2n (e_earth)
+## => n_earth = 0.0016792
+## @end example
+##
+## Vector input:
+## @example
+## e_vec = [ 0.081819221456 0.3543164 ]
+## n = ecc2n (e_vec)
+## => n =
+## 0.0016792 0.033525
+## @end example
+##
+## @seealso{n2ecc}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9566
+## For background see https://en.wikipedia.org/wiki/Flattening
+
+function n = ecc2n ( ecc )
+
+ if (nargin < 1)
+ print_usage ();
+ end
+
+ if (! isnumeric (ecc) || ! isreal (ecc))
+ error ("ecc2n.m: numeric input expected");
+ elseif (any (ecc < 0) || any (ecc > 1))
+ error ("ecc2n.m: eccentricity should lie in real interval [0..1]")
+ else
+ B = (4 ./ ecc.^2 - 2); ## Taken from e^2 = 4n / (1+n)^2
+ ## Use to get in the form n^2 - Bn + 1 = 0
+ ## From testing the other definition of third flattening (a-b)/(a+b)
+ ## Use the - version for the quadratic equation
+ n = (B - sqrt (B .^2 - 4)) ./ 2;
+ end
+
+endfunction
+
+%!test
+%!
+%! ecc_earth = .081819221456;
+%! ecc_jupiter = 0.3543164;
+%! e_vec = [ ecc_earth ecc_jupiter ];
+%! assert ( ecc2n ( ecc_earth ) , 0.001679222 , 10e-10 );
+%! assert ( ecc2n ( e_vec ), [0.0016792 0.03352464],10e-8);
+
+%!error <numeric input expected> ecc2n ("ecc")
+%!error <numeric input expected> ecc2n (0.5 + 3i)
+%!error <n should lie> n2ecc (-1)
+%!error <n should lie> n2ecc (2)
+%!error <n should lie> n2ecc (-Inf)
+%!error <n should lie> n2ecc (Inf)
+
diff --git a/inst/ecef2geodetic.m b/inst/ecef2geodetic.m
new file mode 100644
index 0000000..ec17879
--- /dev/null
+++ b/inst/ecef2geodetic.m
@@ -0,0 +1,174 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{lat}, @var{lon}, @var{alt} =} ecef2geodetic (@var{spheroid}, @var{X}, @var{Y}, @var{Z})
+## @deftypefnx {Function File} {@var{lat}, @var{lon}, @var{alt} =} ecef2geodetic (@var{X}, @var{Y}, @var{Z})
+## @deftypefnx {Function File} {@var{lat}, @var{lon}, @var{alt} =} ecef2geodetic (@dots{}, @var{angleUnit})
+## Converts from ecef coordinate frame to geodetic coordinate frame.
+##
+## Optional argument @var{spheroid} can be an ellipsoid vector or spheroid
+## (see help for referenceEllipsoid.m). If omitted or if an empty string is
+## supplied the WGS84 ellipsoid is taken.
+##
+## @var{X}, @var{Y} and @var{Z} are Earth-Centered Earth Fixed coordinates in
+## meters. They can be scalars, vectors or matrices but they must all have
+## the same size and dimensions.
+##
+## The default @var{lat} and @var{lon} output is in degrees; specify "radians"
+## for optional last argument @var{angleUnit} to return coordinates in
+## radians. @var{alt} is always in meters. The size and dimension(s) of
+## @var{lat}, @var{lon} and @var{alt} are the same as @var{X}, @var{Y}
+## and @var{Z}.
+##
+## @example
+## Aalborg GPS Centre
+## X = 3426949.39675307
+## Y = 601195.852419885
+## Z = 5327723.99358255
+## lat = 57.02929569;
+## lon = 9.950248114;
+## h = 56.95; # meters
+##
+## >> [lat, lon, alt] = geodetic2ecef ("", X, Y, Z)
+## lat = 57.029
+## lon = 9.9502
+## alt = 56.95
+## @end example
+## @seealso{geodetic2ecef, referenceEllipsoid}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9658
+
+function [lat, lon, alt] = ecef2geodetic (varargin)
+
+ ## ip = XYZ offset into varargin
+ ip = 0;
+ if (nargin < 3 || nargin > 5)
+ print_usage ();
+ elseif (nargin == 3)
+ ## Only XYZ input expected
+ spheroid = "wgs84";
+ angleUnit = "degrees";
+ elseif (nargin == 4)
+ if (isnumeric (varargin{4}))
+ ## Assume arg #1 = spheroid
+ angleUnit = "degrees";
+ spheroid = varargin{1};
+ ip = 1;
+ else
+ ## Assume arg #4 = angleunit
+ spheroid = "wgs84";
+ angleUnit = varargin{4};
+ endif
+ elseif (nargin == 5)
+ ip = 1;
+ spheroid = varargin{1};
+ angleUnit = varargin{5};
+ endif
+ X = varargin{ip + 1};
+ Y = varargin{ip + 2};
+ Z = varargin{ip + 3};
+
+ if (! isnumeric (X) || ! isreal (X) || ...
+ ! isnumeric (Y) || ! isreal (Y) || ...
+ ! isnumeric (Z) || ! isreal (Z))
+ error ("ecef2geodetic.m : numeric input expected");
+ endif
+
+ if isempty(spheroid)
+ E = wgs84Ellipsoid;
+ else
+ E = referenceEllipsoid (spheroid);
+ endif
+
+ if (! ischar (angleUnit) || ! ismember (lower (angleUnit(1)), {"d", "r"}))
+ error ("ecef2geodetic.m: angleUnit should be one of 'degrees' or 'radians'")
+ endif
+
+ if isempty(spheroid)
+ E = wgs84Ellipsoid;
+ else
+ E = referenceEllipsoid (spheroid);
+ endif
+
+ ## Insight from: http://wiki.gis.com/wiki/index.php/Geodetic_system
+ lon = atan2 (Y, X);
+
+ ecc = E.Eccentricity;
+ e_sq = ecc ^ 2;
+ ep_2 = e_sq / (1 - e_sq) ; ## This is (e')^2
+ r = hypot (X, Y);
+ E2 = E.SemimajorAxis ^ 2 - E.SemiminorAxis ^ 2;
+ F = 54 * E.SemiminorAxis .^ 2 * Z .^ 2;
+ G = r .^ 2 + (1 - e_sq) * Z .^ 2 - e_sq * E2;
+ C = (ecc .^ 4 .* F .* r .^ 2) ./ (G .^ 3);
+ S = (1 + C + sqrt (C .^ 2 + 2 .* C)) .^ (1 / 3);
+ P = F ./ (3 .* (S + 1 ./ S + 1) .^ 2 .* G .^ 2);
+ Q = sqrt (1 + 2 * ecc .^ 4 * P);
+ r0 = -(P * e_sq .* r) ./ (1 + Q) + ...
+ sqrt (.5 * E.SemimajorAxis .^ 2 * (1 + 1 ./ Q) - ...
+ (P .* (1 - e_sq) .* Z .^ 2) ./ (Q .^ 2 + Q) - .5 * P .* r .^ 2);
+ U = sqrt ((r - e_sq .* r0) .^ 2 + Z .^ 2);
+ V = sqrt (( r - e_sq .* r0) .^ 2 + (1 - e_sq) .* Z .^ 2);
+ Frac = E.SemiminorAxis .^ 2 ./ (E.SemimajorAxis * V);
+ Z0 = Frac .* Z;
+ alt = U .* (1 - Frac);
+ lat = atan2 ((Z + ep_2 .* Z0), r) ;
+
+ if ( strncmpi (lower (angleUnit), "d", 1) == 1 )
+ lon = rad2deg (lon);
+ lat = rad2deg (lat);
+ endif
+
+endfunction
+
+
+%!shared X, Y, Z
+%! X = 3426949.397;
+%! Y = 601195.852;
+%! Z = 5327723.994; ## meters
+
+%!test
+%! [latd, lond, h] = ecef2geodetic ("wgs84", X, Y, Z);
+%! assert ([latd, lond, h], [57.02929569, 9.950248114, 56.95], 10e-3);
+
+%!test
+%! [latd, lond, h] = ecef2geodetic (X, Y, Z);
+%! assert ([latd, lond, h], [57.02929569, 9.950248114, 56.95], 10e-3);
+
+%!test
+%! latr = deg2rad (57.02929569);
+%! lonr = deg2rad (9.950248114);
+%! [lat, lon, h2] = ecef2geodetic ("wgs84", X, Y, Z, "radians");
+%! assert ([lat, lon, h2], [latr, lonr, 56.95], 10e-3);
+
+%!test
+%! latr = deg2rad (57.02929569);
+%! lonr = deg2rad (9.950248114);
+%! [lat, lon, h2] = ecef2geodetic (X, Y, Z, "radians");
+%! assert ([lat, lon, h2], [latr, lonr, 56.95], 10e-3);
+
+%!error <angleUnit> ecef2geodetic ("", 4500000, 450000, 50000000, "km")
+%!error <angleUnit> ecef2geodetic (4500000, 450000, 50000000, "km")
+%!error <numeric input expected> ecef2geodetic ("", "A", 450000, 50000000)
+%!error <numeric input expected> ecef2geodetic ("", 45i, 450000, 50000000)
+%!error <numeric input expected> ecef2geodetic ("", 4500000, "B", 50000000)
+%!error <numeric input expected> ecef2geodetic ("", 4500000, 45i, 50000000)
+%!error <numeric input expected> ecef2geodetic ("", 4500000, 450000, "C")
+%!error <numeric input expected> ecef2geodetic (4500000, 450000, "C")
+%!error <numeric input expected> ecef2geodetic ("", 4500000, 450000, 50i)
+%!error <numeric input expected> ecef2geodetic (4500000, 450000, 50i)
diff --git a/inst/enu2aer.m b/inst/enu2aer.m
new file mode 100644
index 0000000..f47db5f
--- /dev/null
+++ b/inst/enu2aer.m
@@ -0,0 +1,120 @@
+## Copyright (c) 2014-2020 Michael Hirsch, Ph.D.
+## Copyright (c) 2013-2020, Felipe Geremia Nievinski
+## Copyright (C) 2019-2020 Philip Nienhuis
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are met:
+## 1. Redistributions of source code must retain the above copyright notice,
+## this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright notice,
+## this list of conditions and the following disclaimer in the documentation
+## and/or other materials provided with the distribution.
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+## THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+## OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+## SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{az}, @var{el}, @var{slantrange} =} enu2aer (@var{east}, @var{n}, @var{up})
+## @deftypefnx {Function File} {@var{az}, @var{el}, @var{slantrange} =} enu2aer (@dots{...}, @var{angleUnit})
+## Converts ENU coordinates to azimuth, elevation, slantrange
+##
+## Inputs:
+## @itemize
+## @item
+## @var{east}, @var{n}, @var{up}: East, North, Up coordinates of test points (meters)
+##
+## @item
+## (Optional) angleUnit: string for angular units ('radians' or 'degrees',
+## case-insensitive, only first character is significant).
+## Default is 'd': degrees
+## @end itemize
+##
+## Outputs:
+## @itemize
+## @item
+## @var{az}, @var{el}, @var{slantrange}: look angles and distance to point
+## under test (degrees, degrees, meters)
+## @end itemize
+##
+## Example:
+## @example
+## [az, el, slantrange] = enu2aer (186.28, 286.84, 939.69)
+## az = 33.001
+## el = 70.000
+## slantrange = 1000.00
+## @end example
+##
+## With radians
+## @example
+## [az, el, slantrange] = enu2aer (353.55, 353.55, 866.03,"r")
+## az = 0.78540
+## el = 1.0472
+## slantrange = 1000.0
+## @end example
+##
+## @end deftypefn
+
+## Function adapted by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?8377
+
+function [az, el, slantrange] = enu2aer (east, n, up, angleUnit = "degrees")
+
+ if (nargin < 3 || nargin > 4)
+ print_usage();
+ endif
+
+ if (! isnumeric (east) || ! isreal (east) || ...
+ ! isnumeric (n) || ! isreal (n) || ...
+ ! isnumeric (up) || ! isreal (up))
+ error ("enu2aer.m : numeric values expected");
+ endif
+
+ if (! all (size (east) == size (n)) || ! all (size (n) == size (up)))
+ error ("enu2aer.m: non-matching dimensions of inputs.");
+ endif
+
+ if (! ischar (angleUnit))
+ error ("enu2ear.m: character value expected for 'angleUnit'");
+ elseif (! strncmpi (angleUnit, "degrees", length (angleUnit)) &&
+ ! strncmpi (angleUnit, "radians", length (angleUnit)))
+ error ("enu2aer.m: illegal input for 'angleUnit'");
+ endif
+
+ r = hypot (east, n);
+ slantrange = hypot (r, up);
+ ## Radians
+ el = atan2 (up, r);
+ az = mod (atan2 (east, n), 2 * atan2 (0, -1));
+
+ if (nargin == 3)
+ az = rad2deg (az);
+ el = rad2deg (el);
+ endif
+
+endfunction
+
+
+%!test
+%! [az, el, slantrange] = enu2aer (186.277521, 286.84222, 939.69262);
+%! assert ([az, el, slantrange], [33, 70, 1e3], 10e-6)
+%! [az, el, slantrange] = enu2aer (186.277521, 286.84222, 939.69262, "rad");
+%! assert ([az, el, slantrange], [0.57595865, 1.221730476, 1e3], 10e-6)
+
+%!error <numeric> enu2aer ("s", 25, 1e3)
+%!error <numeric> enu2aer (3i, 25, 1e3)
+%!error <numeric> enu2aer (33, "s", 1e3)
+%!error <numeric> enu2aer (33, 3i, 1e3)
+%!error <numeric> enu2aer (33, 25, "s")
+%!error <numeric> enu2aer (33, 25, 3i)
+%!error <non-matching> enu2aer ([1 1], [2 2]', [4 5])
+%!error <non-matching> enu2aer ([1 1], [2 2], [4 5 6])
+%!error <illegal> enu2aer (33, 70, 1e3, "f");
+%!error <illegal> enu2aer (33, 70, 1e3, "radianf");
diff --git a/inst/enu2uvw.m b/inst/enu2uvw.m
new file mode 100644
index 0000000..20179a2
--- /dev/null
+++ b/inst/enu2uvw.m
@@ -0,0 +1,100 @@
+## Copyright (C) 2014-2020 Michael Hirsch, Ph.D.
+## Copyright (C) 2013-2020 Felipe Geremia Nievinski
+## Copyright (C) 2019-2020 Philip Nienhuis
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions are met:
+## 1. Redistributions of source code must retain the above copyright notice,
+## this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright notice,
+## this list of conditions and the following disclaimer in the documentation
+## and/or other materials provided with the distribution.
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+## THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+## OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+## SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{u}, @var{v}, @var{w} =} enu2uvw (@var{east}, @var{north}, @var{up}, @var{lat0}, @var{lon0})
+## @deftypefnx {Function File} {@var{u}, @var{v}, @var{w} =} enu2uvw (@var{east}, @var{north}, @var{up}, @var{lat0}, @var{lon0}, @var{angleUnit})
+## Convert ENU coordinates to UVW coordinates.
+##
+## Inputs:
+##
+## @var{east}, @var{north}, @var{up}: East, North, Up: coordinates of point(s)
+## (meters)
+##
+## @var{lat0}, @var{lon0}: geodetic coordinates of observer/reference point
+## (degrees)
+##
+## @var{angleUnit}: string for angular units ('degrees' or 'radians',
+## case-insensitive, first character will suffice). Default = 'degrees'.
+##
+## Outputs:
+##
+## @var{u}, @var{v}, @var{w}: coordinates of point(s) (meters).
+##
+## @end deftypefn
+
+## Function adapted by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?8377
+
+function [u, v, w] = enu2uvw (east, n, up, lat0, lon0, angleUnit = "degrees")
+
+ if (nargin < 5)
+ print_usage ();
+ endif
+
+ if (! isnumeric (east) || ! isreal (east) || ...
+ ! isnumeric (n) || ! isreal (n) || ...
+ ! isnumeric (up) || ! isreal (up) || ...
+ ! isnumeric (lat0) || ! isreal (lat0) || ...
+ ! isnumeric (lon0) || ! isreal (lon0))
+ error ("enu2uvw.m : numeric values expected for first 5 arguments");
+ endif
+
+ if (! ischar (angleUnit))
+ error ("enu2uvw.m: character value expected for 'angleUnit'");
+ elseif (strncmpi (angleUnit, "degrees", length (angleUnit)))
+ lat0 = deg2rad (lat0);
+ lon0 = deg2rad (lon0);
+ elseif (! strncmpi (angleUnit, "radians", length (angleUnit)))
+ error ("enu2uvw.m: illegal input for 'angleUnit'");
+ endif
+
+ t = cos (lat0) * up - sin (lat0) * n;
+ w = sin (lat0) * up + cos (lat0) * n;
+
+ u = cos (lon0) * t - sin (lon0) * east;
+ v = sin (lon0) * t + cos (lon0) * east;
+
+endfunction
+
+%!test
+%! [u, v, w] = enu2uvw (186.277521, 286.84222, 939.69262, 42, -82, "d");
+%! assert ([u, v, w], [254.940936348589, -475.5397947444, 841.942404132992], 10e-6)
+
+%!test
+%! [u, v, w] = enu2uvw (186.277521, 286.84222, 939.69262, ...
+%! 0.733038285837618, -1.43116998663535, "r");
+%! assert ([u, v, w], [254.940936348589, -475.5397947444, 841.942404132992], 10e-6)
+
+%!error <numeric> enu2uvw("s", 25, 1e3, 0, 0)
+%!error <numeric> enu2uvw(3i, 25, 1e3, 0, 0)
+%!error <numeric> enu2uvw(33, "s", 1e3, 0, 0)
+%!error <numeric> enu2uvw(33, 3i, 1e3, 0, 0)
+%!error <numeric> enu2uvw(33, 25, "s", 0, 0)
+%!error <numeric> enu2uvw(33, 25, 3i, 0, 0)
+%!error <numeric> enu2uvw(33, 25, 1e3, "s", 0)
+%!error <numeric> enu2uvw(33, 25, 1e3, 3i, 0)
+%!error <numeric> enu2uvw(33, 25, 1e3, 0, "s")
+%!error <numeric> enu2uvw(33, 25, 1e3, 0, 3i)
+%!error <illegal> enu2uvw (33, 70, 1e3, 0, 0, "f");
+%!error <illegal> enu2uvw (33, 70, 1e3, 0, 0, "degreef");
diff --git a/inst/extractfield.m b/inst/extractfield.m
index d7afd77..b18aa29 100644
--- a/inst/extractfield.m
+++ b/inst/extractfield.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/flat2ecc.m b/inst/flat2ecc.m
new file mode 100644
index 0000000..c76818f
--- /dev/null
+++ b/inst/flat2ecc.m
@@ -0,0 +1,66 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{ecc} =} flat2ecc (@var{flat})
+## Return the eccentricity given a flattening
+##
+## Examples:
+##
+## Scalar input:
+## @example
+## f_earth = 0.0033528;
+## flat2ecc (f_earth)
+## => 0.081819
+## @end example
+##
+## Vector input:
+## @example
+## flat = 0 : .01 : .05;
+## flat2ecc (flat)
+## ans =
+## 0.00000 0.14107 0.19900 0.24310 0.28000 0.31225
+## @end example
+##
+## @seealso{ecc2flat}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9492
+
+function ec = flat2ecc (fl)
+
+ if nargin < 1
+ print_usage ();
+ endif
+
+ if ( ischar ( fl ) )
+ error ("flat2ecc.m: numeric input expected");
+ elseif (any (fl < 0) || any (fl >= 1))
+ error ("flat2ecc.m: flattening must lie in the real interval [0..1)")
+ else
+ ec = sqrt (2 * fl - fl .^ 2);
+ endif
+
+ endfunction
+
+%!test
+%! flat = 0.00335281317793612;
+%! f_vec = 0:.01:.05;
+%! assert (flat2ecc (flat), 0.0818192214560008, 10^-12 )
+%! assert (flat2ecc (f_vec), [0 , .141067, .198997, .2431049, .28, .31225], 10^-6);
+
+%!error <numeric input expected> flat2ecc ("a")
+%!error <flattening> flat2ecc(1)
diff --git a/inst/fromDegrees.m b/inst/fromDegrees.m
index da04df0..01575b6 100644
--- a/inst/fromDegrees.m
+++ b/inst/fromDegrees.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/fromRadians.m b/inst/fromRadians.m
index 508776d..7725068 100644
--- a/inst/fromRadians.m
+++ b/inst/fromRadians.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/geocentricLatitude.m b/inst/geocentricLatitude.m
new file mode 100644
index 0000000..6f79b14
--- /dev/null
+++ b/inst/geocentricLatitude.m
@@ -0,0 +1,95 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{psi} =} geocentricLatitude (@var{phi}, @var{flattening})
+## @deftypefnx {Function File} {@var{psi} =} geocentricLatitude (@var{phi}, @var{flattening}, @var{angleUnit})
+## Return geocentric latitude (psi) given geodetic latitude (phi) and flattening.
+##
+## The default input and output is in degrees; use optional third parameter
+## @var{angleUnit} for radians. @var{phi} can be a scalar, vector, matrix or
+## any ND array. @var{flattening} must be a scalar value in the interval
+## [0..1).
+##
+## Examples
+## Scalar input:
+## @example
+## psi = geocentricLatitude (45, 0.0033528)
+## => psi =
+## 44.8076
+## @end example
+##
+## Also can use radians:
+## @example
+## psi = geocentricLatitude (pi/4, 0.0033528, "radians")
+## => psi =
+## 0.78204
+## @end example
+##
+## Vector Input:
+## @example
+## phi = 35:5:45;
+## psi = geocentricLatitude (phi, 0.0033528)
+## => psi =
+## 34.819 39.811 44.808
+## @end example
+##
+## @seealso{parametricLatitude, geodeticLatitudeFromGeocentric, geodeticLatitudeFromParametric}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9640
+
+function psi = geocentricLatitude (phi, flattening, angleUnit="degrees")
+
+ if (nargin < 2 || isempty (angleUnit))
+ print_usage ();
+ endif
+
+ if (! isnumeric (phi) || ! isreal (phi) || ...
+ ! isnumeric (flattening) || ! isreal (flattening))
+ error ("geocentricLatitude: numeric input expected for args #1 and #2");
+ elseif (! isscalar (flattening))
+ error ("geocentricLatitude: scalar value expected for flattening");
+ elseif (flattening < 0 || flattening >= 1)
+ error ("geocentricLatitude: flattening must lie in the real interval [0..1)" )
+ elseif (! ischar (angleUnit) || ! ismember (lower (angleUnit(1)), {"d", "r"}))
+ error ("geocentricLatitude: angleUnit should be one of 'degrees' or 'radians'");
+ endif
+
+ ## Make sure phi and (later on) flattening are element-wise conformant
+ if (strncmpi (angleUnit, "r", 1) == 1)
+ ## Insight From: Fundamentals of Astrodynamics and Applications,
+ ## (David Vallado 3rd edition pg 148)
+ psi = atan ((1 - flattening)^2 * tan (phi));
+ else
+ psi = atand ((1 - flattening)^2 * tand (phi));
+ end
+
+endfunction
+
+
+%!test
+%! earth_flattening = 0.0033528;
+%! assert (geocentricLatitude (45, earth_flattening), 44.8075766, 10e-6);
+%! assert (geocentricLatitude (pi/4, earth_flattening, 'radians'), 0.78204, 10e-6);
+
+%!error <numeric input expected> geocentricLatitude (0.5, "flat")
+%!error <numeric input expected> geocentricLatitude (0.5, 5i)
+%!error <numeric input expected> geocentricLatitude ("phi", 0.0033528)
+%!error <numeric input expected> geocentricLatitude (5i, 0.0033528 )
+%!error <scalar value expected> geocentricLatitude ([45 50], [0.7 0.8])
+%!error <flattening must lie> geocentricLatitude (45, 1)
+%!error <angleUnit> geocentricLatitude (45, 0.0033528, "km")
diff --git a/inst/geodetic2ecef.m b/inst/geodetic2ecef.m
new file mode 100644
index 0000000..62df15c
--- /dev/null
+++ b/inst/geodetic2ecef.m
@@ -0,0 +1,152 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{X}, @var{Y}, @var{Z} =} geodetic2ecef (@var{lat}, @var{lon}, @var{alt})
+## @deftypefnx {Function File} {@var{X}, @var{Y}, @var{Z} =} geodetic2ecef (@var{spheroid}, @var{lat}, @var{lon}, @var{alt})
+## @deftypefnx {Function File} {@var{X}, @var{Y}, @var{Z} =} geodetic2ecef (@dots{}, @var{angleUnit})
+## @deftypefnx {Function File} {@var{X}, @var{Y}, @var{Z} =} geodetic2ecef (@var{lat}, @var{lon}, @var{alt}, @var{spheroid})
+## Converts from geodetic coordinate frame to ECEF coordinate frame.
+##
+## @var{lat}, @var{lon} and @var{alt} (latitude, longitude and height,
+## respectively) can each be scalars, vectors or matrices but must all have
+## the exact same size and dimension(s).
+##
+## @var{spheroid} ia user-specified sheroid (see referenceEllipsoid); it can
+## be omitted or given as an ampty string, in which cases WGS84 will be the
+## default spheroid.
+##
+## @var{angleUnit} can be "degrees" (= default) or "radians". In the last
+## calling fom, with @var{spheroid} as 4th input argument, @var{angleUnit}
+## is in degrees and cannot be changed.
+##
+## The output arguments @var{X}, @var{Y}, @var{Z} (Earth-Centered Earth
+## Fixed coordinates) are in meters and have the same sizes and dimensions
+## as input arguments @var{lat}, @var{lon} and @var{alt}.
+##
+## @example
+## Aalborg GPS Centre
+## lat=57.02929569;
+## lon=9.950248114;
+## h= 56.95; # meters
+## >> [X, Y, Z] = geodetic2ecef ("", lat, lon, h)
+## X = 3426949.39675307
+## Y = 601195.852419885
+## Z = 5327723.99358255
+## @end example
+## @seealso{ecef2geodetic}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9658
+
+function [X, Y, Z] = geodetic2ecef (varargin)
+
+ ip = 0;
+ spheroid = "";
+ angleUnit = "degrees";
+ if (nargin < 3 || nargin > 5)
+ print_usage ();
+ elseif (nargin == 3)
+ ## Assume just Lat, Lon and Alt given
+ elseif (nargin == 4)
+ if (isnumeric (varargin{1}))
+ ## Find out if arg #4 = angleunit or spheroid
+ if (isnumeric (varargin{4}))
+ ## Spheroid
+ spheroid = varargin{4};
+ elseif (ischar (varargin{4}))
+ if (ismember (varargin{4}(1), {"r", "d"}))
+ angleUnit = varargin{4};
+ else
+ spheroid = varargin{4};
+ endif
+ else
+ error ("geodetic3ecef.m: spheroid or angleutin expected for arg. #4");
+ endif
+ else
+ ip = 1;
+ spheroid = varargin{1};
+ endif
+ elseif (nargin == 5)
+ ip = 1;
+ spheroid = varargin{1};
+ angleUnit = varargin{5};
+ endif
+ lat = varargin{ip + 1};
+ lon = varargin{ip + 2};
+ alt = varargin{ip + 3};
+
+ if (! isnumeric (lat) || ! isreal (lat) || ...
+ ! isnumeric (lon) || ! isreal (lon) || ...
+ ! isnumeric (alt) || ! isreal (alt))
+ error ("geodetic2ecef.m : numeric input expected");
+ endif
+
+ if (! ischar (angleUnit) || ! ismember (lower (angleUnit(1)), {"d", "r"}))
+ error ("geodetic2ecef.m: angleUnit should be one of 'degrees' or 'radians'")
+ endif
+
+ if (isempty (spheroid))
+ E = wgs84Ellipsoid;
+ else
+ E = referenceEllipsoid (spheroid);
+ endif
+
+ if (strncmpi (lower (angleUnit), "r", 1) == 1)
+ c_p = cos (lat);
+ s_p = sin (lat);
+
+ c_l = cos (lon);
+ s_l = sin (lon);
+ else
+ c_p = cosd (lat);
+ s_p = sind (lat);
+
+ c_l = cosd (lon);
+ s_l = sind (lon);
+ endif
+
+
+ #Insight From: Algorithms for Global Positioning pg 42
+ N = E.SemimajorAxis ./ sqrt (1 - E.Eccentricity ^ 2 * s_p .^ 2);
+ X = (N + alt) .* (c_p .* c_l) ;
+ Y = (N + alt) .* (c_p .* s_l) ;
+ Z = (N .* (1 - E.Flattening) ^ 2 + alt) .* s_p;
+
+endfunction
+
+
+%!test
+%!shared h
+%! latd = 57.02929569;
+%! lond = 9.950248114;
+%! h = 56.95; ## meters
+%! [x, y, z]=geodetic2ecef("wgs84", latd, lond, h);
+%! assert ([x, y, z], [3426949.397, 601195.852, 5327723.994], 10e-3);
+
+%!test
+%! lat = deg2rad (57.02929569);
+%! lon = deg2rad (9.950248114);
+%! [x2, y2, z2] = geodetic2ecef ("wgs84", lat, lon, h, "radians");
+%! assert ([x2, y2, z2], [3426949.397, 601195.852, 5327723.994], 10e-3);
+
+%!error <angleUnit> geodetic2ecef ("", 45, 45, 50, "km")
+%!error <numeric input expected> geodetic2ecef ("", "A", 45, 50)
+%!error <numeric input expected> geodetic2ecef ("", 45i, 45, 50)
+%!error <numeric input expected> geodetic2ecef ("", 45, "B", 50)
+%!error <numeric input expected> geodetic2ecef ("", 45, 45i, 50)
+%!error <numeric input expected> geodetic2ecef ("", 45, 45, "C")
+%!error <numeric input expected> geodetic2ecef ("", 45, 45, 50i) \ No newline at end of file
diff --git a/inst/geodeticLatitudeFromGeocentric.m b/inst/geodeticLatitudeFromGeocentric.m
new file mode 100644
index 0000000..ca030f0
--- /dev/null
+++ b/inst/geodeticLatitudeFromGeocentric.m
@@ -0,0 +1,92 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{phi} =} geodeticLatitudeFromGeocentric (@var{psi}, @var{flattening})
+## @deftypefnx {Function File} {@var{phi} =} geodeticLatitudeFromGeocentric (@var{psi}, @var{flattening}, @var{angleUnit})
+## Return geodetic latitude (phi) given geocentric latitude (psi) and flattening.
+##
+## The default input and output is in degrees; use optional third parameter
+## @var{angleUnit} for radians. @var{psi} can be a scalar, vector, matrix or
+## any ND array. @var{flattening} must be a scalar value in the interval
+## [0..1).
+##
+## Examples
+## Scalar input:
+## @example
+## phi = geodeticLatitudeFromGeocentric (45, 0.0033528)
+## => phi =
+## 45.192
+## @end example
+##
+## Also can use radians:
+## @example
+## phi = geodeticLatitudeFromGeocentric (pi/4, 0.0033528, "radians")
+## => phi =
+## 0.78876
+## @end example
+##
+## Vector Input:
+## @example
+## psi = 35:5:45;
+## phi = geodeticLatitudeFromGeocentric (psi, 0.0033528)
+## => phi =
+## 35.181 40.19 45.192
+## @end example
+##
+## @seealso{geocentricLatitude, geodeticLatitudeFromGeocentric, parametricLatitude}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9640
+
+function phi = geodeticLatitudeFromGeocentric (psi, flattening, angleUnit="degrees")
+
+ if (nargin < 2 || isempty (angleUnit))
+ print_usage ();
+ endif
+
+ if (! isnumeric (psi) || ! isreal (psi) || ...
+ ! isnumeric (flattening) || ! isreal (flattening))
+ error ("geodeticLatitudeFromGeocentric: numeric input expected");
+ elseif (! isscalar (flattening))
+ error ("geodeticLatitudeFromGeocentric: scalar value expected for flattening");
+ elseif (flattening < 0 || flattening >= 1)
+ error ( "geodeticLatitudeFromGeocentric.m: flattening must lie in the real interval [0..1)" )
+ elseif (! ischar (angleUnit) ||! ismember (lower (angleUnit(1)), {"d", "r"}))
+ error ("geodeticLatitudeFromGeocentric: angleUnit should be one of 'degrees' or 'radians'");
+ endif
+
+ if (strncmpi (angleUnit, "r", 1) == 1)
+ phi = atan2 (tan (psi), (1 - flattening) ^ 2);
+ else
+ phi = atan2d (tand (psi), (1 - flattening) ^ 2);
+ endif
+
+endfunction
+
+
+%!test
+%! earth_flattening = 0.0033528;
+%! assert (geodeticLatitudeFromGeocentric (45, earth_flattening), 45.1924226, 10e-6);
+%! assert (geodeticLatitudeFromGeocentric (pi/4, earth_flattening, 'radians'), 0.78876, 10e-6);
+
+%!error <numeric input expected> geodeticLatitudeFromGeocentric (0.5, "flat")
+%!error <numeric input expected> geodeticLatitudeFromGeocentric (0.5, 5i )
+%!error <numeric input expected> geodeticLatitudeFromGeocentric ("psi", 0.0033528)
+%!error <numeric input expected> geodeticLatitudeFromGeocentric (5i, 0.0033528 )
+%!error <scalar value expected> geodeticLatitudeFromGeocentric ([45 50], [0.7 0.8])
+%!error <flattening must lie> geodeticLatitudeFromGeocentric (45, 1)
+%!error <angleUnit> geodeticLatitudeFromGeocentric (45, 0.0033528 ,"km")
diff --git a/inst/geodeticLatitudeFromParametric.m b/inst/geodeticLatitudeFromParametric.m
new file mode 100644
index 0000000..ef7203f
--- /dev/null
+++ b/inst/geodeticLatitudeFromParametric.m
@@ -0,0 +1,92 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{phi} =} geodeticLatitudeFromParametric (@var{beta}, @var{flattening})
+## @deftypefnx {Function File} {@var{phi} =} geodeticLatitudeFromParametric (@var{beta}, @var{flattening}, @var{angleUnit})
+## Returns geodetic latitude (phi) given parametric latitude and flattening.
+##
+## Parametric latitude (@var{beta}) is also known as a reduced latitude.
+## The default input and output is in degrees; use optional third parameter
+## @var{angleUnit} for radians. @var{beta} can be a scalar, vector, matrix
+## or any ND array. @var{flattening} must be a scalar value in the interval
+## [0..1).
+##
+## Examples:
+## Scalar input:
+## @example
+## phi = geodeticLatitudeFromParametric (45, 0.0033528)
+## => phi =
+## 45.096
+## @end example
+##
+## Also can use radians:
+## @example
+## phi = geodeticLatitudeFromParametric (pi/4, 0.0033528, "radians")
+## => phi =
+## 0.78708
+## @end example
+##
+## Vector Input:
+## @example
+## beta = 35:5:45;
+## phi = geodeticLatitudeFromParametric (beta, 0.0033528)
+## => phi =
+## 35.09 40.095 45.096
+## @end example
+##
+## @seealso{geocentricLatitude, geodeticLatitudeFromGeocentric, parametricLatitude}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9640
+
+function phi = geodeticLatitudeFromParametric (beta, flattening, angleUnit="degrees")
+
+ if (nargin < 2)
+ print_usage ();
+ endif
+
+ if (! isnumeric (beta) || ! isreal (beta) || ...
+ ! isnumeric (flattening) || ! isreal (flattening))
+ error ("geodeticLatitudeFromParametric : numeric input expected");
+ elseif (! isscalar (flattening))
+ error ("geodeticLatitudeFromParametric: scalar value expected for flattening");
+ elseif (flattening < 0 || flattening >= 1)
+ error ("geodeticLatitudeFromParametric: flattening must lie in the real interval [0..1)" );
+ elseif (! ischar (angleUnit) ||! ismember (lower (angleUnit(1)), {"d", "r"}))
+ error ("geodeticLatitudeFromParametric: angleUnit should be one of 'degrees' or 'radians'");
+ endif
+
+ if (strncmpi (angleUnit, "r", 1) == 1)
+ phi = atan2 (tan (beta), (1 - flattening)) ;
+ else
+ phi = atan2d (tand (beta), (1 - flattening)) ;
+ endif
+
+endfunction
+
+%!test
+%! earth_flattening = 0.0033528 ;
+%! assert ( geodeticLatitudeFromParametric (45, earth_flattening), 45.0962122, 10e-6);
+%! assert ( geodeticLatitudeFromParametric (pi/4, earth_flattening, 'radians'), 0.78708, 10e-6);
+
+%!error <numeric input expected> geodeticLatitudeFromParametric (0.5, "flat")
+%!error <numeric input expected> geodeticLatitudeFromParametric (0.5, 5i)
+%!error <numeric input expected> geodeticLatitudeFromParametric ("beta", 0.0033528)
+%!error <numeric input expected> geodeticLatitudeFromParametric (5i, 0.0033528 )
+%!error <scalar value expected> geodeticLatitudeFromParametric ([45 50], [0.7 0.8])
+%!error <flattening must lie> geodeticLatitudeFromParametric (45, 1)
+%!error <angleUnit> geodeticLatitudeFromParametric (45, 0.0033528, "km")
diff --git a/inst/geoshow.m b/inst/geoshow.m
index 8ca676f..b851a99 100644
--- a/inst/geoshow.m
+++ b/inst/geoshow.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Philip Nienhuis
+## Copyright (C) 2014-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/gmlread.m b/inst/gmlread.m
new file mode 100644
index 0000000..6f3308c
--- /dev/null
+++ b/inst/gmlread.m
@@ -0,0 +1,263 @@
+## Copyright (C) 2017-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{gml} =} gmlread (@var{fname}, @var{dots})
+## Read a .gml (Geographic Markup Language) file.
+##
+## gmlread only reads coordinates, no attributes.
+##
+## Required input argument @var{fname} is the name of a .gml file. If
+## no other arguments are specified all features in the entire .gml file
+## will be read.
+##
+## The following optional property/value pairs (all case-insensitive)
+## can be specified to select only some feature types and/or features
+## limited to a certain area:
+##
+## @itemize
+## @item "FeatureType"
+## (Just one "f" will do) Only read a certain feature type; the value
+## can be one of "Points", "LineStrings" or "Polygons" (only the first
+## three characters matter). Multiple feature types can be selected by
+## specifying multiple FeatureType property/value pairs.
+## @end item
+##
+## @item BoundingBox
+## (just one "b" suffices) Only read features that lie entirely within
+## a coordinate rectangle specified as a 2x2 matrix containing [minX minY;
+## maxX maxY].
+## @end item
+## @end itemize
+##
+## In addition verbose output can be obtained by specifying the following
+## property/value pair:
+##
+## @itemize
+## @item Debug
+## (a "d" will do) followed by a numeric value of 1 (or true) specifies
+## verbose output; a numeric value of 0 (or false) suppresses verbose output.
+## @end item
+## @end itemize
+##
+## The output of gmlread comprises a struct containing up to three
+## mapstructs (MultiPoint and/or Polyline and/or Polygon mapstructs),
+## depending on optional featuretype selection.
+##
+## @seealso{shaperead}
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2017-03-06
+
+function [gml] = gmlread (fname, varargin)
+
+ ## FIXME Input validation
+ fid = fopen (fname, "r");
+ if (fid < 0)
+ error ("gmlread: file %s not found.\n", fname);
+ endif
+
+ ## Process [property, value] options
+ idx = pol = [];
+ dbug = feat = ipt = iln = ipg = 0;
+ for ii=1:2:numel (varargin)
+ if (ischar (varargin{ii}) && numel (varargin{ii}) > 0 && mod (numel (varargin), 2) == 0)
+ switch lower (varargin{ii})(1)
+ case "f"
+ ## Feature type. Keep track of whether this option was entered
+ feat = 1;
+ if (ischar (varargin{ii+1}))
+ switch lower (varargin{ii+1})(1:3)
+ case "poi"
+ ipt = 1;
+ case "lin"
+ iln = 1;
+ case "pol"
+ ipg = 1;
+ otherwise
+ error ("gmlread: unknown FeatureType '%s'", varargin{ii+1});
+ endswitch
+ else
+ error ("gmlread: illegal value for argument %d\n", ii+1);
+ endif
+ case "b"
+ ## Bounding box
+ bbox = varargin{ii+1};
+ if (! isnumeric (idx) && size (bbox, 1) != 2 && size (bbox, 2) != 2)
+ error ("gmlread: numeric 2x2 matrix expected for arg.# %d\n", ii+1);
+ else
+ pol = [bbox(1, 1), bbox(2, 1), bbox(2, 1), bbox(1, 1), bbox(1, 1); ...
+ bbox(1, 2), bbox(1, 2), bbox(2, 2), bbox(2, 2), bbox(1, 2)];
+ endif
+ case ("d")
+ ## Debug
+ dbug = varargin{ii+1} == 1;
+ otherwise
+ warning ("gmlread: unknown option '%s' - ignored\n", varargin{ii});
+ endswitch
+ else
+ error ("gmlread: wrong [property, value] options list");
+ endif
+ endfor
+ if (! feat)
+ ipt = iln = ipg = 1;
+ endif
+
+ if (dbug)
+ printf ("Reading %s ... ", fname);
+ endif
+ xml = fread (fid, 1000, "char=>char")';
+
+ ## Skip header lines/-nodes
+ hdls = cell2mat (regexp (xml, '(<\?.*?\?>)|(<!.*?>)', "tokenExtents"));
+ spos = hdls(end);
+ ## Start with outer content node
+ fseek (fid, spos+1, "bof");
+ xml = fread (fid, Inf, "char=>char")';
+ fclose (fid);
+
+ ## Parse xml int 5xN cell array
+ ptrn = '<gml:((Point|LineString|Polygon)) .*?<gml:(coordinates|posList|pos).*?srsDimension="(\d)".*?>([\d\. ]*?)</gml:(coordinates|posList|pos)>';
+ feats = reshape (cell2mat (regexp (xml, ptrn, "tokens")), 5, []);
+
+ if (ipt)
+ if (dbug)
+ printf ("\nSearching Points ... ");
+ endif
+ ipt = find (strcmp (feats(1, :), "Point"));
+ if (dbug)
+ printf ("%d found.\n", numel (ipt));
+ endif
+ else
+ ipt = [];
+ endif
+
+ if (iln)
+ if (dbug)
+ printf ("Searching LineStrings ... ");
+ endif
+ iln = find (strcmp (feats(1, :), "LineString"));
+ if (dbug)
+ printf ("%d found.\n", numel (iln));
+ endif
+ else
+ iln = [];
+ endif
+
+ if (ipg)
+ if (dbug)
+ printf ("Searching Polygons ... ");
+ endif
+ ipg = find (strcmp (feats(1, :), "Polygon"));
+ if (dbug)
+ printf ("%d found.\n", numel (ipg));
+ endif
+ else
+ ipg = [];
+ endif
+
+ if (dbug)
+ printf ("Converting ....\n", numel (ipg));
+ endif
+
+ ## Points
+ if (! isempty (ipt))
+ gmlpt = repmat (struct ("Geometry", "Multipoint", "X", NaN (1, 10), "Y",
+ NaN (1, 10), "BoundingBox", NaN (2, 2)), numel (ipt), 1);
+ jpt = 0;
+ for ii=1:numel (ipt)
+ if (dbug)
+ printf ("%d of %d Points ....\r", ii, numel (ipt));
+ endif
+ [xy, cnt] = sscanf (feats{4, ipt(ii)}, "%f");
+ dimsn = str2double (feats{3, ipt(ii)});
+ xy = reshape (xy, dimsn, []);
+ if (isempty (pol) || bbox <= 0 || all (inpolygon (xy(1, :), xy(2, :), pol(1, :), pol(2, :))))
+ ++jpt;
+ gmlpt(jpt).X = xy(1, :);
+ gmlpt(jpt).Y = xy(2, :);
+ gmlpt(jpt).BoundingBox = [min(xy(1, :)), min(xy(2, :)); max(xy(1, :)), max(xy(2, :))];
+ if (dimsn >= 3)
+ gmlpt(jpt).Z = xy(3, :);
+ gmlpt(jpt).BoundingBox = [gmlpt(jpt).BoundingBox, [min(xy(3, :)); max(xy(3, :))]];
+ endif
+ endif
+ endfor
+ if (dbug)
+ printf ("\n");
+ endif
+ gml.Points = gmlpt(1:jpt);
+ endif
+
+ ## LineStrings
+ if (! isempty (iln))
+ jpt = 0;
+ gmlpl = repmat (struct ("Geometry", "Polyline", "X", NaN (1, 15), "Y",
+ NaN (1, 15), "BoundingBox", NaN (2, 2)), numel (iln), 1);
+ for ii=1:numel (iln)
+ if (dbug)
+ printf ("%d of %d LineStrings ....\r", ii, numel (iln));
+ endif
+ [xy, cnt] = sscanf (feats{4, iln(ii)}, "%f");
+ dimsn = str2double (feats{3, iln(ii)});
+ xy = reshape (xy, dimsn, []);
+ if (isempty (pol) || all (inpolygon (xy(1, :), xy(2, :), pol(1, :), pol(2, :))))
+ ++jpt;
+ gmlpl(jpt).X = xy(1, :);
+ gmlpl(jpt).Y = xy(2, :);
+ gmlpl(jpt).BoundingBox = [min(xy(1, :)), min(xy(2, :)); max(xy(1, :)), max(xy(2, :))];
+ if (dimsn >= 3)
+ gmlpl(jpt).Z = xy(3, :);
+ gmlpl(jpt).BoundingBox = [gmlpl(jpt).BoundingBox, [min(xy(3, :)); max(xy(3, :))]];
+ endif
+ endif
+ endfor
+ if (dbug)
+ printf ("\n");
+ endif
+ gml.Polylines = gmlpl(1:jpt);
+ endif
+
+ ## Polygons
+ if (! isempty (ipg))
+ jpt = 0;
+ gmlpg = repmat (struct ("Geometry", "Polygon", "X", NaN (1, 20), "Y",
+ NaN (1, 20), "BoundingBox", NaN (2, 2)), numel (ipg), 1);
+ for ii=1:numel (ipg)
+ if (dbug)
+ printf ("%d of %d Polygons ....\r", ii, numel (ipg));
+ endif
+ [xy, cnt] = sscanf (feats{4, ipg(ii)}, "%f");
+ dimsn = str2double (feats{3, ipg(ii)});
+ xy = reshape (xy, dimsn, []);
+ if (isempty (pol) || all (inpolygon (xy(1, :), xy(2, :), pol(1, :), pol(2, :))))
+ ++jpt;
+ gmlpg(jpt).X = xy(1, :);
+ gmlpg(jpt).Y = xy(2, :);
+ gmlpg(jpt).BoundingBox = [min(xy(1, :)), min(xy(2, :)); max(xy(1, :)), max(xy(2, :))];
+ if (dimsn >= 3)
+ gmlpg(jpt).Z = xy(3, :);
+ gmlpg(jpt).BoundingBox = [gmlpg(jpt).BoundingBox, [min(xy(3, :)); max(xy(3, :))]];
+ endif
+ endif
+ endfor
+ if (dbug)
+ printf ("\n");
+ endif
+ gml.Polygons = gmlpg(1:jpt);
+ endif
+
+endfunction
diff --git a/inst/gpxread.m b/inst/gpxread.m
new file mode 100644
index 0000000..2eec30f
--- /dev/null
+++ b/inst/gpxread.m
@@ -0,0 +1,264 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{out} =} gpxread (@var{fname})
+## @deftypefnx {} {@var{out} =} gpxread (@var{fname}, @dots{})
+## Read data from a gpx file.
+##
+## Input argument @var{fname} (a character string) can be a valid file
+## name or a valid URL.
+##
+## If no other input arguments are given gpxread will read all data in
+## @var{fname} into one output struct @var{out}. The data to be read can
+## be selected and/or limited by specifying one or more of the following
+## optional property/value pairs (all case-insensitive):
+##
+## @itemize
+## @item "FeatureType'
+## This option (a mere "f" suffices) can be one of:
+## @table @asis
+## @item
+## "WayPoint or simply "w": Read waypoints.
+## @end item
+##
+## @item
+## "Track" or "t": read tracks.
+## @end item
+##
+## @item
+## "Route" or "t": read routes.
+## @end item
+##
+## @item
+## "Auto" or "a" (default value): read all data.
+## @end item
+## @end table
+##
+## Multiple FeatureType property/value pairs can be specified.
+## @end item
+##
+## @item "Index"
+## The ensuing Index value should be a numeric value, or numeric vector,
+## of indices of the features to be read.
+## @end item
+## @end itemize
+##
+## Output argument @var{out} is a struct array with field names Name,
+## Lat, Lon, Ele, Time, and -in case of routes- Names. "Name" refers
+## to the name of the waypoints, tracks or routes that have been read.
+## "Lat", "Lon" and "Ele" refer to the latitude, longitude and elevation
+## of the various features, in case of tracks field "Time" refers to
+## the time of the trackpoint. In case of tracks and routes these are
+## vectors, each element corresponding to one track point. For each
+## individual track multiple track segments are separated by NaNs. For
+## routes the field "Names" contains a cell array holding the names of
+## the individual route points.
+##
+## Examples:
+##
+## @example
+## A = gpxread ("trip2.gpx", "feature", "track", "index", 2);
+## (which returns data from the second track in file "trip1.gpx")
+## @end example
+##
+## @example
+## B = gpxread ("trip2.gpx", "f", "t", "f", "r");
+## (which returns track and route data from file "trip2.gpx")
+## @end example
+##
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2017-02-05
+
+function [outp] = gpxread (fname, varargin)
+
+ ## Input validation
+ if (nargin < 1)
+ print_usage ();
+ elseif (! ischar (fname))
+ error ("gpread: file name expected for arg 1\n");
+ elseif (! isempty (cell2mat (cell2mat ...
+ (regexp (fname, '(ftp://|http://|file://)', "tokens")))))
+ url = true;
+ else
+ [pth, fnm, ext] = fileparts (fname);
+ if (isempty (ext))
+ fname = [fname ".gpx"];
+ endif
+ url = false;
+ endif
+
+ if (nargin < 2)
+ gtr = grt = gpt = 1;
+ else
+ gtr = grt = gpt = 0;
+ endif
+
+ if (mod (nargin, 2) != 1)
+ error ("gpxread: insufficient nr. of input arguments\n");
+ endif
+
+ idx = [];
+ for ii=1:2:numel (varargin)
+ if (ischar (varargin{ii}) && numel (varargin{ii}) > 0)
+ switch lower (varargin{ii})(1)
+ case "f"
+ switch lower (varargin{ii+1})(1)
+ case "t"
+ gtr = 1;
+ case "r"
+ grt = 1;
+ case "w"
+ gpt = 1;
+ case "a"
+ gtr = grt = gpt = 1;
+ otherwise
+ error ("gpxread: unknown FeatureType '%s'", varargin{ii+1});
+ endswitch
+ case "i"
+ idx = varargin{ii+1};
+ if (! isnumeric (idx))
+ error ("gpxread: numeric value or vector expected for arg.# %d\n", ii+1);
+ else
+ idx = uint32 (idx);
+ endif
+ otherwise
+ warning ("gpxread: unknown option '%s' - ignored\n", varargin{ii});
+ endswitch
+ else
+ error ("gpxread: wrong argument type for arg. %d - 'FeatureType' or \
+'Index' expected", ii+1);
+ endif
+ endfor
+
+ if (url)
+ ## Untested
+ xml = urlread (fname);
+ else
+ fid = fopen (fname, "r");
+ if (fid < 0)
+ error ("gpxread: couldn't open file %s\n", fname);
+ endif
+ xml = fread (fid, Inf, "char=>char")';
+ fclose (fid);
+ endif
+
+ outp = repmat (struct ("Type", "-", ...
+ "Lat", [], ...
+ "Lon", [], ...
+ "Ele", [], ...
+ "Time", [], ...
+ "Name", "-"), 0, 1);
+
+ if (gpt)
+ ## (Try to) Read waypoints
+ ptrnp = 'wpt lat="(.*?)" lon="(.*?)">.*?ele>(.*?)</ele.*?name>(.*?)</name';
+ wpts = reshape (cell2mat ( regexp (xml, ptrnp, "tokens")'), [], 4);
+ if (! isempty (wpts))
+ wpts(:, 1:3) = num2cell (str2double (wpts(:, 1:3)));
+ if (isempty (idx))
+ idx = [1:size(wpts, 1)]';
+ else
+ idx(idx < 1) = [];
+ idx(idx > size (wpts, 1)) = [];
+ endif
+ [outp(1:numel (idx)).Name] = deal (wpts(idx, 4){:});
+ [outp(1:numel (idx)).Type] = deal ("WayPoint");
+ [outp(1:numel (idx)).Lat] = deal (wpts(idx, 1){:});
+ [outp(1:numel (idx)).Lon] = deal (wpts(idx, 2){:});
+ [outp(1:numel (idx)).Ele] = deal (wpts(idx, 3){:});
+ endif
+ endif
+
+ if (gtr)
+ ## Read tracks
+ ptrnt1A = '<trkpt lat="(.*?)" lon="(.*?)".*?ele>(.*?)</ele>.*?<time>(.*?)</time';
+ ptrnt1B = '<trkpt lat="(.*?)" lon="(.*?)".*?time>(.*?)</time.*?ele>(.*?)</ele>';
+ ptrnt2 = '<trkpt lat="(.*?)" lon="(.*?)".*?ele>(.*?)</ele';
+ [trk, ~, is] = getxmlnode (xml, "trk", 1, 1);
+ itr = 0;
+ if (is != 0)
+ do
+ if (isempty (idx) || ismember (++itr, idx))
+ [trkseg, ~, ist] = getxmlnode (trk, "trkseg", 1, 1);
+ if (ist != 0)
+ outp(end+1).Type = "Track";
+ outp(end).Name = getxmlnode (trk, "name", 1, 1);
+ ## Check if tracks have time node
+ it = index (trkseg, "</time>", "first");
+ ## Check ele/time node order
+ if (it)
+ if (index (trkseg, "</ele>", "first") < it)
+ ptrnt = ptrnt1A;
+ dcol = 4;
+ else
+ ptrnt = ptrnt1B;
+ dcol = 3;
+ endif
+ ncols = 4;
+ else
+ ptrnt = ptrnt2;
+ ncols = 3;
+ endif
+ out = NaN (0, ncols);
+ do
+ arr = reshape (cell2mat (regexp (trkseg, ptrnt, "tokens")), ncols, [])';
+ if (ncols == 4)
+ arr = [ (str2double (arr(:, [1:2 7-dcol]))) (datenum (arr(:, dcol), "yyyy-mm-ddTHH:MM:SSZ")) ];
+ else
+ arr = str2double (arr);
+ endif
+ out = [out; arr; NaN(1, ncols)];
+ [trkseg, ~, ist] = getxmlnode (trk, "trkseg", ist, 1);
+ until (ist == 0);
+ out(end, :) = [];
+ outp(end).Lat = out(:, 1);
+ outp(end).Lon = out(:, 2);
+ outp(end).Ele = out(:, 3);
+ if (ncols == 4)
+ outp(end).Time = out(:, 4);
+ endif
+ endif
+ endif
+ [trk, ~, is] = getxmlnode (xml, "trk", is, 1);
+ until (is == 0);
+ endif
+ endif
+
+ if (grt)
+ ## Read routes
+ ptrnr = 'rtept lat="(.*?)" lon="(.*?)".*?ele>(.*?)</ele.*?name>(.*?)</name';
+ [rte, ~, is] = getxmlnode (xml, "rte", 1, 1);
+ irt = 0;
+ if (is != 0)
+ do
+ if (isempty (idx) || ismember (++irt, idx))
+ outp(end+1).Type = "Route";
+ outp(end).Name = getxmlnode (rte, "name", 1, 1);
+ [rteseg, ~, isr] = getxmlnode (rte, "rtept", 1, 1);
+ rtp = reshape (cell2mat ( regexp (rte, ptrnr, "tokens")'), [], 4);
+ [outp(end).Lat] = str2double (rtp(:, 1));
+ [outp(end).Lon] = str2double (rtp(:, 2));
+ [outp(end).Ele] = str2double (rtp(:, 3));
+ [outp(end).Names] = rtp(:, 4);
+ endif
+ [rte, ~, is] = getxmlnode (xml, "rte", is, 1);
+ until (is == 0);
+ endif
+ endif
+
+endfunction
diff --git a/inst/isShapeMultipart.m b/inst/isShapeMultipart.m
new file mode 100644
index 0000000..3db52d3
--- /dev/null
+++ b/inst/isShapeMultipart.m
@@ -0,0 +1,72 @@
+## Copyright (C) 2016-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{mp} =} isShapeMultipart (@var{x}, @var{y})
+## Checks if a polygon or polyline consists of multiple parts separated by
+## NaN rows.
+##
+## @var{x} and @var{y} must be vectors with the same orientation (either
+## row vectors of column vectors).
+##
+## Output argument @var{mp} is zero (false) if the shape contains no NaN
+## rows, otherwise it equals the number of polygon/polyline shape parts.
+##
+## @seealso{}
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2016-05-22
+
+function mp = isShapeMultipart (x, y)
+
+ if (nargin < 2)
+ print_usage ();
+ endif
+ if (isrow (x) != isrow (y))
+ error ("isShapeMultipart: x and y must be both row vectors or both column vectors")
+ endif
+ if (numel (x) != numel (y))
+ error ("isShapeMultipart: incompatible input vectors");
+ endif
+
+ mp = 0;
+ mp_x = find (isnan (x));
+ mp_y = find (isnan (y));
+ if (! isempty (mp_x) && ! isempty (mp_y) && numel (mp_x) == numel (mp_y))
+ if (any (mp_x - mp_y))
+ error ("isShapeMultipart: NaN positions don't match");
+ else
+ mp = numel (mp_x) + 1;
+ endif
+ endif
+
+endfunction
+
+
+%!test
+%! assert (isShapeMultipart ([0 1 0], [1 0 0]), 0);
+
+%!test
+%! h = [0 0 1 NaN 2 2 NaN 3 3];
+%! k = [0 1 0 NaN 2 3 NaN 3 2];
+%! assert (isShapeMultipart (h, k), 3);
+
+%!error <x and y must be both> isShapeMultipart ([0 0 1 NaN 2 2 NaN 3 3], ...
+%! [0 1 0 NaN 2 3 NaN 3 2]')
+%!error <NaN positions don't match> isShapeMultipart ([0 1 NaN 2 3 NaN 4], ...
+%! [0 1 NaN 2 NaN 3 4])
+%!error <incompatible input> isShapeMultipart ([0 0 1 NaN 2 2 NaN 3 3], ...
+%! [0 1 0 NaN 2 3 NaN 3])
diff --git a/inst/km2deg.m b/inst/km2deg.m
index a80e949..80bca2b 100644
--- a/inst/km2deg.m
+++ b/inst/km2deg.m
@@ -1,5 +1,5 @@
-## Copyright (C) 2008 Alexander Barth <abarth93@users.sourceforge.net>
-## Copyright (C) 2013 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2008-2020 Alexander Barth <abarth93@users.sourceforge.net>
+## Copyright (C) 2013-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
@@ -28,7 +28,8 @@
## "moon", "mars", "jupiter", "saturn", "uranus", "neptune", or "pluto", in
## which case radius will be set to that object mean radius.
##
-## @seealso{deg2km}
+## @seealso{deg2km, deg2sm, km2rad, km2deg,
+## nm2deg, nm2rad, rad2km, rad2nm, rad2sm, sm2deg, sm2rad}
## @end deftypefn
## Author: Alexander Barth <barth.alexander@gmail.com>
diff --git a/inst/km2nm.m b/inst/km2nm.m
index 71e0dc9..82e66b9 100644
--- a/inst/km2nm.m
+++ b/inst/km2nm.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
+## Copyright (C) 2014-2020 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/km2rad.m b/inst/km2rad.m
index 4bb848b..3f81b35 100644
--- a/inst/km2rad.m
+++ b/inst/km2rad.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Pooja Rao <poojarao12@gmail.com>
+## Copyright (C) 2014-2020 Pooja Rao <poojarao12@gmail.com>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
@@ -27,7 +27,8 @@
## "moon", "mars", "jupiter", "saturn", "uranus", "neptune", or "pluto", in
## which case radius will be set to that object mean radius.
##
-## @seealso{km2deg}
+## @seealso{deg2km, deg2sm, km2rad, km2deg,
+## nm2deg, nm2rad, rad2km, rad2nm, rad2sm, sm2deg, sm2rad}
## @end deftypefn
## Author: Pooja Rao <poojarao12@gmail.com>
diff --git a/inst/km2sm.m b/inst/km2sm.m
index 3a04cc9..48bde15 100644
--- a/inst/km2sm.m
+++ b/inst/km2sm.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
+## Copyright (C) 2014-2020 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/kmlread.m b/inst/kmlread.m
new file mode 100644
index 0000000..713fbc7
--- /dev/null
+++ b/inst/kmlread.m
@@ -0,0 +1,223 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{kml} =} kmlread (@var{fname})
+## (EXPERIMENTAL) Read a Google kml file and return its contents in a struct.
+##
+## @var{name} is the name of a .kml file.
+## Currently kmlread can read Point, LineString, Polygon, Track nd Multitrack
+## entries.
+##
+## @var{kml} is a struct with fields Type, Lat, Lon, Ele, Time and Name.
+##
+## @seealso{kmzread}
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2018-05-03
+
+function outp = kmlread (fname)
+
+ fid = fopen (fname, "r");
+ if (fid > 0)
+ xml = fread (fid, Inf, "*char")';
+ fclose (fid);
+ else
+ error ("kmlread: couldn't read %s", fname);
+ endif
+
+ outp = repmat (struct ("Type", "-", ...
+ "Lat", [], ...
+ "Lon", [], ...
+ "Ele", [], ...
+ "Time", [], ...
+ "Name", "-"), 0, 1);
+
+ ## Points
+ is = 1;
+ ipt = 1;
+ while (is > 0)
+ [pnt, il, is] = getxmlnode (xml, "Point", is, 1);
+ if (is > 0)
+ outp(end+1).Type = "Point";
+ ++ipt;
+ pnam = cell2mat (regexp (xml(max (1, il - 1000):il+7), ...
+ '<Placemark>.*?<name>(.+?)</name>.*?<Point>', "tokens")){end};
+ if (! isempty (pnam))
+ outp(end).Name = pnam;
+ else
+ outp(end).Name = ["Point #" num2str(ipt)];
+ endif
+ coords = strrep (getxmlnode (pnt, "coordinates", 1, 1), ",", " ");
+ xyz = sscanf (coords, "%f", Inf);
+ outp(end).Lon = xyz(1);
+ outp(end).Lat = xyz(2);
+ outp(end).Ele = xyz(3);
+ endif
+ endwhile
+
+ ## Linestrings / Polylines
+ is = 1;
+ iln = 1;
+ while (is > 0)
+ [lin, il, is] = getxmlnode (xml, "LineString", is, 1);
+ if (is > 0)
+ outp(end+1).Type = "Line";
+ ++iln;
+ lnam = cell2mat (regexp (xml(max (1, il - 1000):il+12), ...
+ '<Placemark>.*?<name>(.+?)</name>.*?<LineString>', "tokens")){end};
+ if (! isempty (lnam))
+ outp(end).Name = lnam;
+ else
+ outp(end).Name = ["Line #" num2str(iln)];
+ endif
+ coords = getxmlnode (lin, "coordinates", 1, 1);
+ ## Check if we have Ele (Z). Coordinate tuples are separated by spaces
+ lines = strsplit (strtrim (coords), " ");
+ nd = numel (strfind (lines{1}, ",")) + 1;
+ xyz = reshape (sscanf (strrep (coords, ",", " "), "%f", Inf)', nd, [])';
+ outp(end).Lon = xyz(:, 1);
+ outp(end).Lat = xyz(:, 2);
+ outp(end).BoundingBox = [min(outp(end).Lon), max(outp(end).Lon); ...
+ min(outp(end).Lat), max(outp(end).Lat)];
+ if (nd > 2)
+ outp(end).Ele = xyz(:, 3);
+ outp(end).BoundingBox = [outp(end).BoundingBox;
+ min(outp(end).Ele), max(outp(end).Ele)];
+ else
+ outp(end).BoundingBox = [outp(end).BoundingBox;
+ Nan, NaN];
+ endif
+ endif
+ endwhile
+
+ ## Polygons
+ is = 1;
+ ip = 1;
+ while (is > 0)
+ [pol, il, is] = getxmlnode (xml, "Polygon", is, 1);
+ if (is > 0)
+ outp(end+1).Type = "Polygon";
+ ++ip;
+ pnam = cell2mat (regexp (xml(max (1, il - 1000):il+12), ...
+ '<Placemark>.*?<name>(.+?)</name>.*?<Polygon>', "tokens")){end};
+ if (! isempty (pnam))
+ outp(end).Name = pnam;
+ else
+ outp(end).Name = ["Polygon #" num2str (ip)];
+ endif
+ ir = 1;
+ ## First get outer ring
+ [ring, ~, ir] = getxmlnode (pol, "outerBoundaryIs", ir);
+ coords = strrep (getxmlnode (ring, "coordinates", 1, 1), ",", " ");
+ xyz = reshape (sscanf (coords, "%f", Inf)', 3, [])';
+ ## FIXME check on CCW
+ outp(end).Lon = xyz(:, 1);
+ outp(end).Lat = xyz(:, 2);
+ outp(end).Ele = xyz(:, 3);
+ ## Next, any inner rings
+ while (ir > 0)
+ [ring, ~, ir] = getxmlnode (pol, "innerBoundaryIs", ir);
+ if (ir > 0)
+ coords = strrep (getxmlnode (ring, "coordinates", 1, 1), ",", " ");
+ xyz = reshape (sscanf (coords, "%f", Inf)', 3, [])';
+ ## FIXME check on CW
+ outp(end).Lon = [outp(end).Lon; NaN; xyz(:, 1)];
+ outp(end).Lat = [outp(end).Lat; NaN; xyz(:, 2)];
+ outp(end).Ele = [outp(end).Ele; NaN; xyz(:, 3)];
+ endif
+ endwhile
+ outp(end).BoundingBox = [min(outp(end).Lon), max(outp(end).Lon); ...
+ min(outp(end).Lat), max(outp(end).Lat); ...
+ min(outp(end).Ele), max(outp(end).Ele)];
+ endif
+ endwhile
+
+ ## Tracks and MultiTracks
+ ptrnT = '<when>(.*?)</when>';
+ is = em = 1;
+ it = 0;
+ while (is > 0)
+ if (em <= is)
+ ## Try to get extent of multiTrack node
+ [mtrk, ~, em] = getxmlnode (xml, "gx:MultiTrack", is, 1);
+ if (em > 0)
+ ## Get specific attributes for this Multitrack
+ mtid = getxmlattv (mtrk, "id");
+ intpm = str2double (getxmlnode (mtrk, "gx:interpolate", 10, 1));
+ ## MultiTrack; start new struct item for next tracks
+ outp(end+1).Type = "Track";
+ ++it;
+ if (isempty (mtid))
+ outp(end).Name = ["Track #" num2str(it)];
+ else
+ outp(end).Name = mtid;
+ endif
+ endif
+ endif
+
+ [trk, ~, is] = getxmlnode (xml, "gx:Track", is, 1);
+ if (is > 0)
+ if (! em)
+ ## Separate track; start new struct item
+ outp(end+1).Type = "Track";
+ ++it;
+ tnam = getxmlattv (trk, "id");
+ if (isempty (tnam))
+ outp(end).Name = ["Track #" num2str(it)];
+ else
+ outp(end) = tnam;
+ endif
+ outp(end).BoundingBox = [min(outp(end).Lon), max(outp(end).Lon); ...
+ min(outp(end).Lat), max(outp(end).Lat); ...
+ min(outp(end).Ele), max(outp(end).Ele)];
+
+ endif
+ times = cell2mat (regexp (trk, ptrnT, "tokens"));
+ times = datenum (times, "yyyy-mm-ddTHH:MM:SSZ");
+ ptrnP = '<gx:coord>(.+?) (.+?) (.+?)</gx:coord>';
+ xyz = reshape (str2double (cell2mat (regexp (trk, ptrnP, "tokens"))), 3, [])';
+ if (isempty (outp(end).Lat))
+ outp(end).Lon = xyz(:, 1);
+ outp(end).Lat = xyz(:, 2);
+ outp(end).Ele = xyz(:, 3);
+ if (! isempty (times))
+ outp(end).Time = times;
+ endif
+ elseif (intpm)
+ outp(end).Lon = [outp(end).Lon; xyz(:, 1)];
+ outp(end).Lat = [outp(end).Lat; xyz(:, 2)];
+ outp(end).Ele = [outp(end).Ele; xyz(:, 3)];
+ if (! isempty (times))
+ outp(end).Time = [outp(end).Time; times];
+ endif
+ else
+ outp(end).Lon = [outp(end).Lon; NaN; xyz(:, 1)];
+ outp(end).Lat = [outp(end).Lat; NaN; xyz(:, 2)];
+ outp(end).Ele = [outp(end).Ele; NaN; xyz(:, 3)];
+ if (! isempty (times))
+ outp(end).Time = [outp(end).Time; NaN; times];
+ endif
+ endif
+ outp(end).BoundingBox = [min(outp(end).Lon), max(outp(end).Lon); ...
+ min(outp(end).Lat), max(outp(end).Lat); ...
+ min(outp(end).Ele), max(outp(end).Ele)];
+ endif
+
+ endwhile
+
+endfunction
diff --git a/inst/kmzread.m b/inst/kmzread.m
new file mode 100644
index 0000000..7dcc4ad
--- /dev/null
+++ b/inst/kmzread.m
@@ -0,0 +1,62 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{retval} =} kmzread (@var{fname}, @dots{})
+## Read a compressed Google kmz file and return its contents in a struct.
+##
+## See 'help kmlread' for more information.
+##
+## @seealso{kmlread}
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2018-05-13
+
+function out = kmzread (fzname, varargin)
+
+ ## Check filename etc.
+ [~, fn, ext] = fileparts (fzname);
+ if (isempty (ext))
+ ext = ".kmz";
+ fzname = [fzname ext];
+ elseif (! strcmp (lower (ext), ".kmz"))
+ error ("kmzread: filename extension should be '.kmz'");
+ endif
+
+ ## Unpack into temp directory
+ tmpd = tempdir;
+ fl = unzip (fzname, tempdir);
+ if (isempty (fl{1}))
+ ## Unzip failed. Check if a previous unzipped doc.kml file exists and wipe it
+ if (unlink ([tempdir filesep "doc.kml"]) == 0)
+ ## Yep existed, now try again
+ fl = unzip (fzname, tempdir);
+ else
+ error ("kmzread: couldn't unzip %s", fzname);
+ endif
+ endif
+ flname = [tempdir fl{1}];
+
+ unwind_protect
+ ## Read file
+ out = kmlread (flname, varargin{:});
+ unwind_protect_cleanup
+ ## Delete unpacked file
+ unlink (flname);
+ end_unwind_protect
+
+endfunction
diff --git a/inst/majaxis.m b/inst/majaxis.m
new file mode 100644
index 0000000..35b0423
--- /dev/null
+++ b/inst/majaxis.m
@@ -0,0 +1,80 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{semimajoraxis} =} majaxis (@var{semiminoraxis}, @var{ecc})
+## Return the semimajor axis given the semiminoraxis (b) and eccentricity (e).
+##
+## Examples
+##
+## Scalar input:
+## @example
+## earth_b = 6356752.314245; ## meter
+## earth_ecc = 0.081819221456;
+## a = majaxis (earth_b, earth_ecc)
+## => a =
+## 6.3781e+06
+## @end example
+##
+## Vector input:
+## @example
+## planets_b = [ 6356752.314245 ; 66854000 ]; ## meter
+## planets_ecc = [ 0.081819221456 ; 0.3543164 ];
+## planets_a = majaxis ( planets_b , planets_ecc )
+## => planets_a =
+## 6.3781e+06
+## 7.1492e+07
+## @end example
+##
+## @seealso{minaxis}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9566
+## For background see https://en.wikipedia.org/wiki/Flattening
+
+function a = majaxis (b, ecc)
+
+ if (nargin < 2)
+ print_usage ();
+ end
+
+ if (! isnumeric (b) || ! isreal (b) || ! isnumeric (ecc) || ! isreal (ecc))
+ error ( "majaxis.m : numeric input expected");
+ elseif (any (ecc < 0) || any (ecc > 1))
+ error ( "majaxis.m: eccentricity must lie in the real interval [0..1]" )
+ elseif ((length (b) != 1 && length (ecc) != 1) && ((size (b) != size (ecc))))
+ error ("vectors must be the same size")
+ else
+ a = b ./ sqrt (1 - ecc .^ 2);
+ end
+
+endfunction
+
+%!test
+%!
+%! earth_b = 6356752.314245; ## meter
+%! earth_ecc = 0.081819221456;
+%! assert ( majaxis (earth_b, earth_ecc), 6378137.01608, 10e-6);
+%! planets_b = [ 6356752.314245 ; 66854000 ]; ## meter
+%! planets_ecc = [ 0.081819221456 ; 0.3543164 ];
+%! assert( majaxis (planets_b, planets_ecc), [ 6378137.01608; 71492000.609327 ], 10e-6 );
+
+%!error <numeric input expected> majaxis (0.5, "ecc")
+%!error <numeric input expected> majaxis (0.5, 0.3 + 0.5i)
+%!error <numeric input expected> majaxis ("b", 0.5)
+%!error <numeric input expected> majaxis (0.3 + 0.5i , 0.5)
+%!error <eccentricity must lie> majaxis ([10; 10; 10], [0.5; 0; -0.5])
+%!error <vectors must be the same size> minaxis ( [ 6356752.314245 ; 66854000 ] , [ 0.081819221456 ; 0.3543164 ]') \ No newline at end of file
diff --git a/inst/makesymbolspec.m b/inst/makesymbolspec.m
index 6031cb1..f523a85 100644
--- a/inst/makesymbolspec.m
+++ b/inst/makesymbolspec.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2015 Philip Nienhuis
+## Copyright (C) 2015-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
@@ -181,7 +181,7 @@ function [symspec] = makesymbolspec (geom, varargin)
if (rem (size (varargin{ii}(3:end), 1), 2) != 0)
error ("makesymbolspec: a property or value missing in rule #%d\n", ii);
else
- error ("makesymbolspec: uncomprehensible input in rul #%d\n", ii);
+ error ("makesymbolspec: uncomprehensible input in rule #%d\n", ii);
endif
end_try_catch
endfor
@@ -191,8 +191,10 @@ endfunction
%!test
%% Illegal graphics properties & case of properties
-%! ssp = makesymbolspec ("Line", {"LENGTH", [100 150], "color", "b", ...
+%!warning<properties> ssp = makesymbolspec ("Line", {"LENGTH", [100 150], "color", "b", ...
%! "nonsense", "?", "lineWidth", 3, "markersize", "BS", "Visible", 1});
+%! ssp = makesymbolspec ("Line", {"LENGTH", [100 150], "color", "b", ...
+%! "lineWidth", 3, "Visible", 1});
%! assert (reshape (ssp{2}(3:end), 2, [])(1, :), {"Color", "LineWidth", ...
%! "Visible"});
%! assert (ssp{1}, "Line");
diff --git a/inst/mapshow.m b/inst/mapshow.m
index 94369a5..013a123 100644
--- a/inst/mapshow.m
+++ b/inst/mapshow.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014,2015 Philip Nienhuis
+## Copyright (C) 2014-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/meridianarc.m b/inst/meridianarc.m
new file mode 100644
index 0000000..13b8cf7
--- /dev/null
+++ b/inst/meridianarc.m
@@ -0,0 +1,97 @@
+## Copyright (C) 2019-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSEll. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{s} =} meridianarc (@var{phi}, @var{phi_2}, @var{spheroid}, @var{angleUnit})
+## Returns the meridian arch length given two latitudes @var{phi} and @var{phi_2}.
+##
+## If phi_2 is larger than phi the value will be negative.
+##
+## If no spheroid is given the default is wgs84.
+## The angleunit can be degrees or radians (the latter is default).
+##
+## Examples
+## Full options:
+## @example
+## s = meridianarc (0, 56, "int24", "degrees")
+## => s =
+## 6.2087e+06
+## @end example
+## Short version:
+## @example
+## s = meridianarc (0, pi/4)
+## => s =
+## 4.9849e+06
+## @end example
+## If want different units:
+## @example
+## s = meridianarc (0, 56, referenceEllipsoid ("int24", "km"), "degrees")
+## => s =
+## 6208.7
+## @end example
+## @seealso{referenceEllipsoid}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9720
+
+function s = meridianarc (phi, phi_2, spheroid="wgs84", angleUnit="radians")
+
+ persistent intv = "-pi/2, pi/2";
+ persistent degintv = "-90, 90";
+
+ if (nargin < 2)
+ print_usage ();
+ endif
+
+ if (strncmpi (lower (angleUnit), "d", 1) == 1)
+ phi = deg2rad (phi);
+ phi_2 = deg2rad (phi_2);
+ intv = degintv;
+ endif
+ if (abs (phi) > pi / 2 || abs (phi_2) > pi / 2)
+ error ("meridianarc: latitudes must lie in interval [%s]", intv);
+ endif
+
+ if isempty (spheroid)
+ spheroid = "wgs84";
+ end
+
+ ## From: Algorithms for global positioning. Kai Borre and Gilbert Strang pg 373
+ ## Note: Using integral instead of Taylor Expansion
+ if (isstruct (spheroid))
+ E = spheroid;
+ elseif (ischar (spheroid))
+ E = referenceEllipsoid (spheroid);
+ else
+ error ("meridianarc.m: spheroid must be a string or a stucture");
+ endif
+
+ e_sq = E.Eccentricity ^ 2;
+ F = @(x) ((1 - e_sq * sin(x) ^ 2) ^ (-3 / 2));
+ s = E.SemimajorAxis * (1 - e_sq) * quad ( F, phi, phi_2, 1.0e-12);
+
+endfunction
+
+%!test
+%! s = meridianarc (0, 56, "int24", "degrees");
+%! assert (s, 6208700.08662672, 10e-6)
+
+%!error <spheroid> meridianarc ( 0, pi/4, 7)
+%!error <spheroid> meridianarc ( 0, pi/4, 7i)
+%!error <latitudes> meridianarc (-2, 2)
+%!error <latitudes> meridianarc (-91, 91, "", "d")
+
diff --git a/inst/minaxis.m b/inst/minaxis.m
new file mode 100644
index 0000000..bdc61fa
--- /dev/null
+++ b/inst/minaxis.m
@@ -0,0 +1,80 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{semiminoraxis} =} minaxis (@var{semimajoraxis}, @var{ecc})
+## Return the semiminor axis given the semimmajor axis (a) and eccentricity (ecc).
+##
+## Examples
+##
+## Scalar input:
+## @example
+## earth_a = 6378137; %m
+## earth_ecc = 0.081819221456;
+## earth_b = minaxis (earth_a, earth_ecc)
+## => earth_b =
+## 6.3568e+06
+## @end example
+##
+## Vector input:
+## @example
+## planets_a = [ 6378137 ; 66854000 ];
+## planets_ecc = [ 0.081819221456 ; 0.3543164 ];
+## planets_b = minaxis (planets_a , planets_ecc)
+## => planets_b =
+## 6.3568e+06
+## 6.2517e+07
+## @end example
+##
+## @seealso{majaxis}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9566
+## For background see https://en.wikipedia.org/wiki/Flattening
+
+function b = minaxis (a, ecc)
+
+ if (nargin < 2)
+ print_usage ();
+ end
+
+ if (! isnumeric (a) || ! isreal (a) || ! isnumeric (ecc) || ! isreal (ecc))
+ error ("minaxis.m : numeric input expected");
+ elseif (any (ecc < 0) || any (ecc > 1))
+ error ("minaxis.m: eccentricity must lie in interval [0..1]")
+ elseif ((length (a) != 1 && length(ecc) != 1) && ((size (a) != size (ecc))))
+ error ("vectors must be the same size")
+ else
+ b = a .* sqrt ( 1 - ecc .^ 2 );
+ end
+
+endfunction
+
+%!test
+%!
+%! earth_a = 6378137;
+%! earth_ecc = 0.081819221456;
+%! assert ( minaxis (earth_a, earth_ecc), 6356752.2982157, 10e-8 )
+%! planets_a = [ 6378137 ; 66854000 ];
+%! planets_ecc = [ 0.081819221456 ; 0.3543164 ];
+%! assert ( minaxis (planets_a, planets_ecc), [ 6356752.29821572 ; 62516886.8951319 ], 10e-8 )
+
+%!error <numeric input expected> minaxis (0.5, "ecc")
+%!error <numeric input expected> minaxis (0.5, 0.3 + 0.5i)
+%!error <numeric input expected> minaxis ("a", 0.5)
+%!error <numeric input expected> minaxis (0.3 + 0.5i , 0.5)
+%!error <vectors must be the same size> minaxis ( [ 6378137 ; 66854000 ], [ 0.081819221456 ; 0.3543164 ]')
+%!error <eccentricity must lie> minaxis ([10; 10; 10], [0.5; 0; -0.5])
diff --git a/inst/n2ecc.m b/inst/n2ecc.m
new file mode 100644
index 0000000..1bddfe7
--- /dev/null
+++ b/inst/n2ecc.m
@@ -0,0 +1,74 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{ecc} =} n2ecc (@var{n})
+## This returns the eccentricity given the third flattening (n).
+##
+## Examples:
+##
+## Scalar input:
+## @example
+## n_earth = 0.0016792;
+## ecc_earth = n2ecc (n_earth)
+## => ecc_earth = 0.081819
+## @end example
+##
+## Vector input:
+## @example
+## n_vec = [ 0.0016792 0.033525 ];
+## ecc = n2ecc (n_vec)
+## => ecc =
+## 0.081819 0.35432
+## @end example
+##
+## @seealso{n2ecc}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9566
+## For background see https://en.wikipedia.org/wiki/Flattening
+
+function ecc = n2ecc (n)
+
+ if (nargin < 1)
+ print_usage ();
+ end
+ if (! isnumeric (n) || ! isreal (n) )
+ error ("n2ecc.m: numeric input expected");
+ elseif (any (n < 0) || any (n > 1))
+ error ("n2ecc.m: n should lie in the real interval [0..1]" )
+ else
+ ecc = sqrt (4 * n ./ (1 + n) .^2);
+ end
+
+endfunction
+
+%!test
+%!
+%! n_earth = 0.001679221647179929;
+%! n_jupiter = 0.03352464537391420;
+%! n_vec = [ n_earth n_jupiter ];
+%! assert (n2ecc (n_earth) , .081819221456 , 10e-12);
+%! assert (n2ecc (n_vec), [0.08181922 0.3543164], 10e-8)
+
+%!error <numeric input expected> n2ecc ("n")
+%!error <numeric input expected> n2ecc (0.5 + 3i)
+%!error <n should lie> n2ecc (-1)
+%!error <n should lie> n2ecc (2)
+%!error <n should lie> n2ecc (-Inf)
+%!error <n should lie> n2ecc (Inf)
+
diff --git a/inst/nm2deg.m b/inst/nm2deg.m
index b9818a8..332d912 100644
--- a/inst/nm2deg.m
+++ b/inst/nm2deg.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Pooja Rao <poojarao12@gmail.com>
+## Copyright (C) 2014-2020 Pooja Rao <poojarao12@gmail.com>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
diff --git a/inst/nm2km.m b/inst/nm2km.m
index b783396..596ccb2 100644
--- a/inst/nm2km.m
+++ b/inst/nm2km.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
+## Copyright (C) 2014-2020 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/nm2rad.m b/inst/nm2rad.m
index bda4fd7..f043706 100644
--- a/inst/nm2rad.m
+++ b/inst/nm2rad.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Pooja Rao <poojarao12@gmail.com>
+## Copyright (C) 2014-2020 Pooja Rao <poojarao12@gmail.com>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
diff --git a/inst/nm2sm.m b/inst/nm2sm.m
index f1f9514..91ac8de 100644
--- a/inst/nm2sm.m
+++ b/inst/nm2sm.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
+## Copyright (C) 2014-2020 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/parametricLatitude.m b/inst/parametricLatitude.m
new file mode 100644
index 0000000..0ac0c2e
--- /dev/null
+++ b/inst/parametricLatitude.m
@@ -0,0 +1,93 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{beta} =} parametricLatitude (@var{phi}, @var{flattening})
+## @deftypefnx {Function File} {@var{beta} =} parametricLatitude (@var{phi}, @var{flattening}, @var{angleUnit})
+## Returns parametric latitude given geodetic latitude (phi) and flattening.
+##
+## The parametric latitude @var{beta} is also known as a reduced latitude.
+## The default input and output is in degrees; use optional third parameter
+## @var{angleUnit} for radians. @var{phi} can be a scalar, vector, matrix
+## or any ND array. @var{flattening} must be a scalar value in the interval
+## [0..1).
+##
+## Examples:
+## Scalar input:
+## @example
+## beta = parametricLatitude (45, 0.0033528)
+## => beta =
+## 44.904
+## @end example
+##
+## Also can use radians:
+## @example
+## beta = parametricLatitude (pi/4, 0.0033528, "radians")
+## => beta =
+## 0.78372
+## @end example
+##
+## Vector Input:
+## @example
+## phi = 35:5:45;
+## beta = parametricLatitude (phi, 0.0033528)
+## => beta =
+## 34.91 39.905 44.904
+## @end example
+## @seealso{geocentricLatitude , geodeticLatitudeFromGeocentric , geodeticLatitudeFromParametric}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9640
+
+function beta = parametricLatitude (phi, flattening, angleUnit="degrees")
+
+ if (nargin < 2)
+ print_usage ();
+ endif
+
+ if (! isnumeric (phi) || ! isreal (phi) || ...
+ ! isnumeric (flattening) || ! isreal (flattening))
+ error ("parametricLatitude : numeric input expected");
+ elseif (! isscalar (flattening))
+ error ("parametricLatitude: scalar value expected for flattening");
+ elseif (flattening < 0 || flattening >= 1)
+ error ("parametricLatitude: flattening must lie in the real interval [0..1)" );
+ elseif (! ischar (angleUnit) ||! ismember (lower (angleUnit(1)), {"d", "r"}))
+ error ("parametricLatitude: angleUnit should be one of 'degrees' or 'radians'");
+ endif
+
+ if (strncmpi (angleUnit, "r", 1) == 1)
+ ## From: Algorithms for global positioning. Kai Borre and Gilbert Strang pg 371
+ beta = atan ((1 - flattening) * tan (phi));
+ else
+ beta = atand ((1 - flattening) * tand (phi));
+ end
+
+
+endfunction
+
+%!test
+%! earth_flattening = 0.0033528 ;
+%! assert (parametricLatitude (45, earth_flattening), 44.903787, 10e-6)
+%! assert (parametricLatitude (pi/4, earth_flattening, 'radians'), 0.78372, 10e-6)
+
+%!error <numeric input expected> parametricLatitude (0.5, "flat")
+%!error <numeric input expected> parametricLatitude (0.5, 5i)
+%!error <numeric input expected> parametricLatitude ("phi", 0.0033528)
+%!error <numeric input expected> parametricLatitude (5i, 0.0033528 )
+%!error <scalar value expected> parametricLatitude ([45 50], [0.7 0.8])
+%!error <flattening must lie> parametricLatitude (45, 1)
+%!error <angleUnit> parametricLatitude (45, 0.0033528, "km")
diff --git a/inst/polycut.m b/inst/polycut.m
new file mode 100644
index 0000000..4423a93
--- /dev/null
+++ b/inst/polycut.m
@@ -0,0 +1,54 @@
+## Copyright (C) 2017-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} [@var{Xo}, @var{Yo}] = polycut (@var{Xi}, @var{Yi})
+## @deftypefnx {Function File} [@var{Xo}, @var{Yo}, @var{Zo}] = polycut (@var{Xi}, @var{Yi}, @var{Zi})
+## @deftypefnx {Function File} [@var{XY_o}] = polycut (@var{XY_i})
+## Reorder nested multipart polygons in such a way that branch cuts aren't
+## drawn when using the patch command.
+##
+## Normally when drawing multipart nested polygons (with holes and other
+## polygons inside the holes; polygon parts separated by NaNs) holes will be
+## filled. Connecting the polygon parts by deleting the NaNs leads to edges
+## of some polygon parts to be drawn across neighboring polygon parts.
+## polycut reorders the polygon parts such that the last vertices of polygon
+## parts have minimum distance to the first vertices of the next parts,
+## avoiding the connecting lines ("branch cuts") to show up in the drawing.
+##
+## Input consists of separate X, Y, and -optionally- Z vectors, or an Nx2 or
+## Nx3 matrix of vertex coordinates (X, Y) or (X, Y, Z). If individual X and
+## Y vectors were input, the output consists of the same number of vectors.
+## If an Nx 2 or Nx3 array was input, the output will be an Nx2 or Nx3 matrix
+## as well.
+##
+## polycut is a mere wrapper around the function polygon2patch in the OF
+## geometry package.
+##
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2017-11-10
+
+function [X, Y, Z] = polycut (varargin)
+
+ if (isempty (which ("polygon2patch")))
+ error ("function polygon2patch not found. OF geometry package \
+installed and loaded?");
+ endif
+ [X, Y, Z] = polygon2patch (varargin{:});
+
+endfunction
diff --git a/inst/private/__dbl2int64__.m b/inst/private/__dbl2int64__.m
new file mode 100644
index 0000000..0fa8478
--- /dev/null
+++ b/inst/private/__dbl2int64__.m
@@ -0,0 +1,47 @@
+## Copyright (C) 2017-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+function [opol, xy_mean, xy_magn] = __dbl2int64__ (inpoly, clippoly=[], xy_mean=[], xy_magn=[])
+
+ if (isempty (clippoly))
+ clippoly = zeros (0, size (inpoly, 2));
+ endif
+ if (isempty (xy_mean))
+ ## Convert & scale to int64 (that's what Clipper works with)
+ ## Find (X, Y) translation
+ xy_mean = mean ([inpoly; clippoly] (isfinite ([inpoly; clippoly](:, 1)), :));
+ ## Find (X, Y) magnitude
+ xy_magn = max ([inpoly; clippoly] (isfinite ([inpoly; clippoly](:, 1)), :)) ...
+ - min ([inpoly; clippoly] (isfinite ([inpoly; clippoly](:, 1)), :));
+ ## Apply (X,Y) multiplication (floor (log10 (intmax ("int64"))) = 18)
+ xy_magn = 10^(17 - ceil (max (log10 (xy_magn))));
+ endif
+
+ ## Scale inpoly coordinates to optimally use int64
+ inpoly(:, 1:2) -= xy_mean;
+ inpoly *= xy_magn;
+
+ idin = [ 0 find(isnan (inpoly (:, 1)))' numel(inpoly (:, 1))+1 ];
+ ## Provisional preallocation. npolx is average nr. of vertices per polygon
+ npolx = fix (size (inpoly, 1) / numel (idin)-1);
+ npoly = size (inpoly, 2);
+ opol = repmat (struct ("x", zeros (npolx, npoly), "y", zeros (npolx, npoly)), ...
+ 1, numel(idin) - 1);
+ for ii=1:numel (idin) - 1
+ opol(ii).x = int64 (inpoly(idin(ii)+1:idin(ii+1)-1, 1));
+ opol(ii).y = int64 (inpoly(idin(ii)+1:idin(ii+1)-1, 2));
+ endfor
+
+endfunction
diff --git a/inst/private/clipplg.m b/inst/private/clipplg.m
deleted file mode 100644
index 7c523aa..0000000
--- a/inst/private/clipplg.m
+++ /dev/null
@@ -1,207 +0,0 @@
-## Copyright (C) 2014 Philip Nienhuis
-##
-## This program is free software; you can redistribute it and/or modify it
-## under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 3 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-## -*- texinfo -*-
-## @deftypefn {Function File} [@var{val}, @var{npts}, @var{pptr}] = clipplg (@var{val}, @var{npts}, @var{pptr}, @var{sbox}[, @var{styp]})
-## Undocumented internal function for clipping polygons & interpolating Z & M
-## values.
-##
-## @seealso{}
-## @end deftypefn
-
-## Author: Philip Nienhuis <prnienhuis@users.sf.net>
-## Created: 2014-11-18
-## Updates:
-## 2014-11-22 Create stanza for finding back vertices and interpolating Z & M
-## 2014-11-25 Fix interpolating M & Z values, properly treat new corner points
-## of clipped polygons
-## 2014-11-26 Update tnpt and tnpr arrays
-## 2015-07-10 Provision for segments crossed twice by clipping polygon
-
-function [val, tnpt, tnpr] = clipplg (val, tnpt, tnpr, sbox, styp=5)
-
- ## Indices to start of each subfeature, plus end+1
- tnprt = [(tnpr + 1) tnpt+1];
-
- ## Initialize total number of clipped vertices
- tnptclp = 0;
- tnprclp = cell (numel (tnpr, 1));
-
- for kk=1:numel (tnpr)
- ## Work from end back to start of subfeatures to avoid mixing up index arrays
- jj = numel (tnpr) - kk + 1;
- ## Select rows belonging to this partial feature. First save non-selected rows
- b_val = e_val = [];
- if (jj > 1)
- ## There's one or more subfeatures lower down
- b_val = val(1:tnprt(jj)-1, :);
- endif
- if (jj < numel (tnpr))
- ## There's one or more subfeatures higher up
- e_val = val(tnprt(jj+1):end, :);
- endif
- tval = val(tnprt(jj):tnprt(jj+1)-1, :);
- ## oc_polybool is in OF geometry package
- [X, Y, npol, b, c] = oc_polybool (tval(:, 1:2), sbox, 'AND');
-
- ## Initialize new number of points & new part pointers in clipped polygon(s)
- nptclp = 0;
- nprclp = 0;
- valn = [];
- if (npol)
- ## Make an XY matrix, remove NaNs on upper and lower row
- valc = [X Y](2:end-1, :);
- ## Augment NaNs for Z and M, and augment type + shape record index columns
- ncl = size (valc, 1);
- valc = [ valc NaN(ncl, 2) tval(1, 5)*ones(ncl, 1) tval(1, 6)*ones(ncl, 1) ];
- ## Pointers to subpolygons resulting from clipping
- ipt = find (isnan (valc(:, 1)))';
- ipt = [ 0 ipt (size (valc, 1) + 1) ];
- ## For each new polygon...
- for ipol=1:npol
- valn = valc(ipt(ipol)+1:ipt(ipol+1)-1, :);
- ## Update total number of points in clipped polygon(s)
- nptclp += size (valn, 1);
- tnptclp += size (valn, 1);
- ## Add a new 0-based pointer to next part
- nprclp = [ nprclp nptclp ];
- ## Compute all interdistances. distancePoints is in OF geometry package
- ## Avoid polygon end point ( = start point)
- dsts = distancePoints (valn(1:end-1, 1:2), tval(1:end-1, 1:2));
- ## Find matching points in sub and out polygon (row, col)
- [rw, cl] = ind2sub (size (dsts), find (abs (dsts) < eps));
- ## Transfer known Z and M-values
- valn(rw, 3:4) = tval(cl, 3:4);
- ## cl indices refer to original shape, rw indices to clipped shape
- if (numel (cl) >= 1)
- ## Separate polygon segments clipped, or vertex on bounding box side
- ## For each valn row coords not in tval, interpolate Z and M values
- im = setdiff ([1:size(valn, 1)-1], rw);
- ## mi equals cl filled with zeros for non-matches, to easen indexing
- mi = zeros (1, size (valn, 1) - 1);
- mi(rw) = cl;
- ## Find direction of polyline
- pdir = find (abs (diff (rw)) - 1 < eps);
- if (isempty (pdir))
- ## Single point within bounding box. Direction doesn't matter then
- drctn = 1;
- else
- drctn = sign (diff (rw([pdir pdir+1])))(1);
- endif
- for ii=1:numel (im)
- ## Get matching outer vertex. Below IF-ELSEIF order = critical to
- ## avoid index out-of-range errors
- if (im(ii) == 1)
- ## Clipped off outer vertex = previous in tval. diff(cl) = direction
- intpl = true;
- idx = mi(im(ii)+1) - drctn;
- ovtx = tval(idx, :);
- cvtx = tval(mi(im(ii)+1), :);
- elseif ((! ismember (im(ii)-1, rw)) && (! ismember (im(ii)+1, rw)))
- ## Probably a corner point. Just retain NaN values
- intpl = false;
- elseif (! ismember (im(ii)-1, rw))
- ## Clipped off outer vertex = previous in tval. diff(cl) = direction
- intpl = true;
- idx = mi(im(ii)+1) - drctn;
- ovtx = tval(idx, :);
- cvtx = tval(mi(im(ii)+1), :);
- elseif (! ismember (im(ii)+1, rw))
- ## Clipped off outer vertex = next in tval. diff(cl) = direction
- intpl = true;
- idx = mi(im(ii)-1) + drctn;
- ovtx = tval(idx, :);
- cvtx = tval(mi(im(ii)-1), :);
- endif
- ## Parent points found, now interpolate M and Z (if appropriate)
- if (intpl && styp > 5)
- ## Compute missing M and Z values. Invoke largest diff of X/Y coordinates
- difx = abs (cvtx(1) - ovtx(1));
- dify = abs (cvtx(2) - ovtx(2));
- if (difx > dify)
- ## X distance is greater
- fac = (valn(im(ii), 1) - cvtx(1)) / difx;
- else
- ## Y distance is greater
- fac = (valn(im(ii), 2) - cvtx(2)) / dify;
- endif
- fac = abs(fac);
- ## FIXME a debug stmt to detect wrong interpolation => wrong vertices
- if (fac > 1.0)
- printf ("Oops - fac > 1..\n");
- % keyboard
- endif
- if (isfinite (ovtx(3)))
- valn(im(ii), 3) = fac * (ovtx(3) - cvtx(3)) + cvtx(3); ## Z-value
- endif
- if (isfinite (ovtx(4)))
- valn(im(ii), 4) = fac * (ovtx(4) - cvtx(4)) + cvtx(4); ## M-value
- endif
- endif
- endfor
- ## Remove last nprclp entry and temporarily store it in a cell arr
- tnprclp(jj) = nprclp;
-
- elseif (numel (cl) == 0)
- ## One polygon segment clipped twice. Simply assign nearest Z & M values
- ## FIXME proper interpolation required
- ## Find points interpolated on segment(s); they're not in sbox
- [im, ix] = min (distancePoints (sbox(1:end-1, :), valn(1:end-1, 1:2)));
- im = find (im > 0);
- ix = ix(im);
- ## Find nearest polygon points (could be on another polygon segment !)
- [~, ix] = min (distancePoints (tval(1:end-1, 1:2), valn(im, 1:2)));
- ## Assign Z and M values
- valn(im, 3:4) = tval(ix, 3:4);
- ## Remove last nprclp entry and temporarily store it in a cell arr
- tnprclp(jj) = nprclp;
-
- endif
- ## Last row of polygon equals first
- valn(end, :) = valn(1, :);
- ## Augment new polygon after (yet untouched) previous polygons
- b_val = [ b_val; valn ];
-
- endfor ## clipped subpolygons
-
- else
- ## No intersection at all. Just drop tval
- % tnprclp = {};
- endif
-
- val = [b_val ; e_val];
- tnprt(jj+1:end) -= tnprt(jj+1) - tnprt(jj) - size (valn, 1);
- if (isempty (valn))
- ## This subfeature has no points in boundingbox +> drop from list
- tnprt(jj+1) = [];
- endif
-
- endfor
-
- ## Adapt & clean up npt
- tnpt = tnptclp;
- ## Adapt & clean up npr. Concatenate all pointers created by oc_polybool
- tnpr = [ tnprclp{1} ];
- for ii=2:numel (tnprclp)
- ## Skip empty entries
- if (! isempty (tnprclp{ii}))
- tnpr = [tnpr(1:end-1) (tnprclp{ii} + tnpr(end)) ];
- endif
- endfor
- if (! isempty (tnpr))
- tnpr(end) = [];
- endif
-
-endfunction
diff --git a/inst/private/clippln.m b/inst/private/clippln.m
index 7d8a575..cfcf32c 100644
--- a/inst/private/clippln.m
+++ b/inst/private/clippln.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Philip Nienhuis
+## Copyright (C) 2014-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
@@ -16,7 +16,7 @@
## -*- texinfo -*-
## @deftypefn {Function File} [@var{val}, @var{npts}, @var{pprt}] = clipplg (@var{val}, @var{npts}, @var{pprt}, @var{sbox}, @var{styp})
## Undocumented internal function for clipping (poly)lines within a bounding
-## box and interpolating M and Z-values.
+## box and copying M and Z-values from nearest old vertex.
##
## @seealso{}
## @end deftypefn
@@ -24,162 +24,28 @@
## Author: Philip Nienhuis <prnienhuis@users.sf.net>
## Created: 2014-12-01
-function [val, tnpt, tnpr] = clippln (val, tnpt, tnpr, sbox, styp=3)
+function [valn, tnpt, tnpr] = clippln (val, tnpt, tnpr, sbox, styp=3)
- ## Backup
- btnpr = tnpr;
- ## Prepare augmented npr array for easier indexing into subfeatures
- ttnpr = [tnpr tnpt];
- ctnpr = {};
- ## Initialize output array
- valt = [];
-
- ## First step is to check all polyline vertices whether they lie in sbox
- [aa, bb] = inpolygon (val(:, 1), val(:, 2), sbox(:, 1), sbox(:, 2));
- if (any (aa) || any (bb))
- ## So there are. Morph bounding box into something that OF geometry likes:
- ## Parametric representation of bounding box. "createLine" is in OF geometry
- ssbox = createLine (sbox(1:4, :), sbox (2:5, :));
-## Create sbx from sbox ([minx max miny maxy])
-sbx = [min(sbox(:, 1)), max(sbox(:, 1)), min(sbox(:, 2)), max(sbox(:, 2))];
- ## For those points ON the boundary no interpolation is needed.
- ## First treat those points requiring interpolation (1st inside,
- ## 2nd outside or vice versa)
- for ll=1:numel (tnpr)
- ## Start at end of polyline-part to avoid mixing up tnpr pointers
- ii = numel (tnpr) - ll + 1;
- nprt = [];
- ## Select polyline part
- valn = val(ttnpr(ii)+1:ttnpr(ii+1), :);
- a = aa(ttnpr(ii)+1:ttnpr(ii+1));
- if (any (a))
- b = bb(ttnpr(ii)+1:ttnpr(ii+1));
- da = diff (a);
- idx = find (da);
- ## Find out which bounding box segments have been crossed where
- ## 1. Parametric representation of line
- lines = createLine (valn(idx, 1:2), valn(idx+1, 1:2));
- ## 2. Compute intersections. clipLine & distancePoints are in OF geometry
- for jj=1:numel (idx)
- ## Start at end of polyline-part to avoid mixing up tnpr pointers
- kk = numel (idx) - jj + 1;
- ## Skip points on border
- if (! b(idx(kk)+1))
- intsc = reshape (clipLine (lines(kk, :), sbx), 2, 2)';
- dst = distancePoints (intsc, valn(idx(kk):idx(kk)+1, 1:2));
- ## If segment goes out, take nearest point to valn(idx(kk)+1)
- if (da(idx(kk)) < 1)
- [~, ix] = min (dst(:, 2));
- else
- [~, ix] = min (dst(:, 1));
- endif
- fac = dst(ix, 2) / sum (dst(2, :));
- intsc = intsc (ix, :);
- ## Insert new intersection point
- valn(idx(kk)+2:end+1, :) = valn(idx(kk)+1:end, :);
- valn(idx(kk)+1, 1:2) = intsc(1:2);
- if (styp > 3)
- valn(idx(kk)+1, 3:4) = valn(idx(kk)+2, 3:4) + fac * ...
- (valn(idx(kk), 3:4) - valn(idx(kk)+2, 3:4));
- else
- valn(idx(kk)+1, 3:4) = [NaN NaN];
- endif
- ## Also update a, b, da and idx. Also mark first point outside sbox
- aa(ttnpr(ii)+idx(kk):ttnpr(ii)+idx(kk)+1) = 1;
- a = [ a(1:idx(kk)); 1; a(idx(kk)+1:end) ];
- b = [ b(1:idx(kk)); 1; b(idx(kk)+1:end) ];
- endif
- if (da(idx(kk)) < 0)
- ## Segment leaves bbox. Replace first outer point with NaN row
- valn(idx(kk)+2, :) = NaN (1, 6);
- ## Be sure to include NaN row
- a(idx(kk)+2) = 1;
- endif
- endfor
- ## Update valt, ttnpr
- valn = valn(find(a), :);
- if (! isempty (valn))
- ## Remove last NaN row if present
- if (all (isnan (valn(end, 1:2))))
- valn(end,:) = [];
- endif
- isn = 1;
- while (! isempty (isn))
- isn = find (isnan (valn(:, 1)));
- if (! isempty (isn))
- ## Insert new subfeature pointer. npart pointers are 0-based
- nprt = [ nprt isn(1) ];
- valn(isn(1), :) = [];
- endif
- endwhile
- ## Build up new points from top down
- valt = [ valn; valt ];
- ## nprt only gets non-empty if a polyline part was split up
- ## FIXME moet net als bij polygons
- ttnpr = [ ttnpr(1:ii) (nprt+ttnpr(ii)-1) ttnpr(ii+1:end) ];
- endif
- endif
- endfor
- tnpt = size (valt, 1);
- tnpr = ttnpr(1:end-1);
+ ## Clip (intersection)
+ if (mod (styp, 10) == 3)
+ [valn, nparts] = clipPolyline_clipper (val(:, 1:2), sbox, 1);
+ else
+ [valn, nparts] = clipPolygon_clipper (val(:, 1:2), sbox, 1);
endif
- ## There's still the possibility of segments crossing through Bounding Box
- ## w/o having points inside. Search these segments one by one
- cc = find (! aa);
- if (numel (cc) > 1)
- ## Find discontinuities; and merge wit tnpr (multipart) discontinuities
- ci = find (diff(cc) > 1)';
- ## Make sure to only include multipart discontinuities within range of cc
- ctnpr = unique ([max(btnpr, cc(1)-1) cc(ci)' min(cc(end), size (val, 1))]);
- ## Create sbx from sbox ([minx max miny maxy])
- sbx = [min(sbox(:, 1)), max(sbox(:, 1)), min(sbox(:, 2)), max(sbox(:, 2))];
- for ic=1:numel (ctnpr) - 1
- valc = val(ctnpr(ic)+1:ctnpr(ic+1), :);
- ## Eliminate segments that lie to one outside of the bounding box.
- ## Step 1: assess which side of the bounding box the segments lie
- f1 = valc(:, 1) < min (sbox(:, 1));
- f2 = valc(:, 1) > max (sbox(:, 1));
- f3 = valc(:, 2) < min (sbox(:, 2));
- f4 = valc(:, 2) > max (sbox(:, 2));
- ff = [f1'; f2' ; f3' ; f4']';
- ## Step 2: find those segments that cross > 2 extended sbox boundary line
- dd = find (sum ([abs(diff(f1)'); abs(diff(f2)'); ...
- abs(diff(f3)'); abs(diff(f4)')]) > 1);
- ## If there are any vertices changing orientation w.r.t. bounding box:
- if (! isempty (dd))
- ## Create array of lines potentially crossing sbox
- lines = [];
- for ii=1:numel (dd)
- lines = [ lines; createLine(valc(dd(ii), 1:2), valc(dd(ii)+1, 1:2))];
- endfor
- ## For each of that segment, try to compute intersections with sbox
- for ii=1:numel (dd)
- ## clipLine is in OF geometry
- jntsc = reshape (clipLine (lines, sbx), 2, 2)';
- ## Check if there are any intersections at all
- if (! any (isnan (jntsc)))
- valn = [ jntsc NaN(2, 4) ];
- ## Interpolate M and Z values. valc(dd(ii)+1) also required ...
- dist = distancePoints (valc(dd(ii):dd(ii)+1, 1:2), valn(:, 1:2));
- ## ... for length of original segment:
- sl = sum (dist(:, 1));
- ## Find closest point of original segment
- valn(1, 3:4) = valc(dd(ii), 3:4) + dist(1, 1) / sl * ...
- (valc(dd(ii)+1, 3:4) - valc(dd(ii), 3:4));
- valn(2, 3:4) = valc(dd(ii), 3:4) + dist(1, 2) / sl * ...
- (valc(dd(ii)+1, 3:4) - valc(dd(ii), 3:4));
- ## Adapt tnpr
- tnpr = [ tnpr size(valt, 1) ];
- valt = [ valt; valn ];
- endif
- endfor
- tnpr = unique (tnpr);
- endif
- endfor
- endif
-
- valt(:, 5:6) = repmat (val(1, 5:6), size (valt, 1), 1);
- val = valt;
+ ## Set up pointers to subpolygons
+ idn = [0 find(isnan (valn(:, 1)))' size(valn, 1)+1];
+ tnpr = idn(1:end-1);
+ ## Setup Z/M/nr/type columns
+ for ii=1:nparts
+ dists = distancePoints (val(:, 1:2), valn(idn(ii)+1:idn(ii+1)-1, 1:2));
+ [mind, idm] = min (dists);
+ ## Simply copy over Z & M values & no. and type of nearest "old" vertex
+ valn(idn(ii)+1:idn(ii+1)-1, 3:6) = val(idm, 3:6);
+ endfor
+ ## Fix up pointers and vertices
+ valn(idn(2:end-1), :) = [];
+ tnpr = tnpr - [0 : numel(tnpr)-1];
+ tnpt = size (valn, 1);
endfunction
diff --git a/inst/private/spheres_radius.m b/inst/private/spheres_radius.m
index 2e22da0..10f1778 100644
--- a/inst/private/spheres_radius.m
+++ b/inst/private/spheres_radius.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2013 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2013-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
diff --git a/inst/rad2km.m b/inst/rad2km.m
index 731b8c4..3225f3d 100644
--- a/inst/rad2km.m
+++ b/inst/rad2km.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Pooja Rao <poojarao12@gmail.com>
+## Copyright (C) 2014-2020 Pooja Rao <poojarao12@gmail.com>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
@@ -27,7 +27,8 @@
## "moon", "mars", "jupiter", "saturn", "uranus", "neptune", or "pluto", in
## which case radius will be set to that object mean radius.
##
-## @seealso{km2rad}
+## @seealso{deg2km, deg2sm, km2rad, km2deg,
+## nm2deg, nm2rad, rad2km, rad2nm, rad2sm, sm2deg, sm2rad}
## @end deftypefn
## Author: Pooja Rao <poojarao12@gmail.com>
diff --git a/inst/rad2nm.m b/inst/rad2nm.m
new file mode 100644
index 0000000..06e4a88
--- /dev/null
+++ b/inst/rad2nm.m
@@ -0,0 +1,62 @@
+## Copyright (C) 2014-2020 Pooja Rao
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{nm} =} rad2nm (@var{rad})
+## @deftypefnx {Function File} {@var{nm} =} rad2nm (@var{rad}, @var{radius})
+## @deftypefnx {Function File} {@var{nm} =} rad2nm (@var{rad}, @var{sphere})
+## Converts angle in radians to distance in nautical miles by multiplying angle
+## with radius.
+##
+## Calculates the distances @var{nm} in a sphere with @var{radius} (also in
+## nautical miles) for the angles @var{rad}. If unspecified, radius defaults to
+## 3440 nm, the mean radius of Earth.
+##
+## Alternatively, @var{sphere} can be one of "sun", "mercury", "venus", "earth",
+## "moon", "mars", "jupiter", "saturn", "uranus", "neptune", or "pluto", in
+## which case radius will be set to that object's mean radius.
+##
+## @seealso{deg2km, deg2nm, deg2sm, km2rad, km2deg,
+## nm2deg, nm2rad, rad2km, rad2sm, sm2deg, sm2rad}
+## @end deftypefn
+
+## Built with insight from
+## Author: Pooja Rao <poojarao12@gmail.com>
+## Adapted from deg2km.m by Anonymous contributor, see patch #9709
+
+function nm = rad2nm (rad, radius = "earth")
+
+ ## Check arguments
+ if (nargin < 1 || nargin > 2)
+ print_usage();
+ elseif (ischar (radius))
+ ## Get radius of sphere with its default units (km)
+ radius = km2nm (spheres_radius (radius));
+ ## Check input
+ elseif (! isnumeric (radius) || ! isreal (radius))
+ error ("rad2nm: RADIUS must be a numeric scalar");
+ endif
+ nm = (rad * radius);
+
+endfunction
+
+
+%!test
+%!assert (nm2rad (rad2nm (10)), 10, 10*eps);
+%!assert (nm2rad (rad2nm (10, 80), 80), 10, 10*eps);
+%!assert (nm2rad (rad2nm (10, "pluto"), "pluto"), 10, 10*eps);
+
+%!error <RADIUS> rad2nm (5, 5i)
diff --git a/inst/rad2sm.m b/inst/rad2sm.m
new file mode 100644
index 0000000..917e758
--- /dev/null
+++ b/inst/rad2sm.m
@@ -0,0 +1,62 @@
+## Copyright (C) 2014-2020 Pooja Rao
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free Software
+## Foundation; either version 3 of the License, or (at your option) any later
+## version.
+##
+## This program is distributed in the hope that it will be useful, but WITHOUT
+## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+## details.
+##
+## You should have received a copy of the GNU General Public License along with
+## this program; if not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{sm} =} rad2sm (@var{rad})
+## @deftypefnx {Function File} {@var{sm} =} rad2sm (@var{rad}, @var{radius})
+## @deftypefnx {Function File} {@var{sm} =} rad2sm (@var{rad}, @var{sphere})
+## Converts angle in radians to distance in statute miles by multiplying angle
+## with radius.
+##
+## Calculates the distances @var{sm} in a sphere with @var{radius} (also in
+## statute miles) for the angles @var{rad}. If unspecified, radius defaults to
+## 3958 sm, the mean radius of Earth.
+##
+## Alternatively, @var{sphere} can be one of "sun", "mercury", "venus", "earth",
+## "moon", "mars", "jupiter", "saturn", "uranus", "neptune", or "pluto", in
+## which case radius will be set to that object's mean radius.
+##
+## @seealso{deg2km, deg2nm, deg2sm, km2rad, km2deg,
+## nm2deg, nm2rad, rad2km, rad2nm, sm2deg, sm2rad}
+## @end deftypefn
+
+## Built with insight from
+## Author: Pooja Rao <poojarao12@gmail.com>
+## Adapted from deg2km.m by Anonymous contributor, see patch #9709
+
+function sm = rad2sm (rad, radius = "earth")
+
+ ## Check arguments
+ if (nargin < 1 || nargin > 2)
+ print_usage();
+ elseif (ischar (radius))
+ ## Get radius of sphere with its default units (km)
+ radius = km2sm (spheres_radius (radius));
+ ## Check input
+ elseif (! isnumeric (radius) || ! isreal (radius))
+ error ("rad2sm: RADIUS must be a numeric scalar");
+ endif
+ sm = (rad * radius);
+
+endfunction
+
+
+%!test
+%!assert (sm2rad (rad2sm (10)), 10, 10*eps);
+%!assert (sm2rad (rad2sm (10, 80), 80), 10, 10*eps);
+%!assert (sm2rad (rad2sm (10, "pluto"), "pluto"), 10, 10*eps);
+
+%!error <RADIUS> rad2sm (5, 5i)
diff --git a/inst/radtodeg.m b/inst/radtodeg.m
index 1685f40..ad9b7e0 100644
--- a/inst/radtodeg.m
+++ b/inst/radtodeg.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/rasterclip.m b/inst/rasterclip.m
new file mode 100644
index 0000000..d2ea08f
--- /dev/null
+++ b/inst/rasterclip.m
@@ -0,0 +1,129 @@
+## Copyright (C) 2017-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} [@var{rbo}, @var{rio}] = rasterclip (@var{rbi}, @var{rii}, @var{clpbox})
+## Clip a georaster with a rectangle and return adapted bands and info structs.
+##
+## rasterclip is useful for extracting a part of a raster map to enable e.g.,
+## faster drawing
+## @var{rbi} and @var{rii} are output structs from rasterread.m @var{clpbox}
+## is a 2x2 matrix containing [Xmin, Ymin; Xmax, Ymax] of the rectangle.
+##
+## Output structs @var{rbo} and @var{rio} contain adapted bbox, data, Width,
+## Height and GeoTransformation fields. All other fields are copied verbatim,
+## except fields FileSize, FileName and FileModDate which are all left empty.
+##
+## @seealso{rasterread,rasterdraw}
+## @end deftypefn
+
+## Author: Philip Nienhuis <prnienhuis@users.sf.net>
+## Created: 2017-11-01
+
+function [rbo, rio] = rasterclip (rbi, rii, clpbox)
+
+ ## FIXME maybe stricter and more extensive input validation
+ if (nargin < 3 || ! isstruct (rbi) || ! isstruct (rii) || ! ismatrix (clpbox))
+ usage ();
+ endif
+
+ ## Check required fieldnames and remove required (processed) ones from lists
+ fldsr = fieldnames (rbi(1));
+ reqr = {"bbox", "data"};
+ if (! all (ismember (reqr, fldsr)))
+ error ("rasterclip: arg#1: missing fields, improper band struct");
+ endif
+ fldsr (ismember (fldsr, reqr)) = [];
+
+ fldsi = fieldnames (rii);
+ reqi = {"GeoTransformation", "Width", "Height", "nbands", "BitDepth"};
+ if (! all (ismember (reqi, fldsi)))
+ error ("rasterclip: arg#2: missing fields, improper band struct");
+ endif
+ fldsi(ismember (fldsi, reqi)) = [];
+
+% ## Check clip rectangle
+% if (! (issquare (clpbox) && numel (clpbox) != 4) || ...
+% clpbox(1, 1) >= clpbox(2, 1) || clpbox(2, 1) >= clpbox(2, 2))
+% error: ("rasterclip: 2x2 array [Xmin Ymin; Xmax Ymax] expected");
+% endif
+
+ rbox = rii.bbox;
+ clpbox(1, 1) = max (clpbox(1, 1), rbox(1, 1));
+ clpbox(2, 1) = min (clpbox(2, 1), rbox(2, 1));
+ clpbox(1, 2) = max (clpbox(1, 2), rbox(1, 2));
+ clpbox(2, 2) = min (clpbox(2, 2), rbox(2, 2));
+ if (any (! isfinite (clpbox)))
+ error ("One or more of clpbox coordinates outside raster");
+ endif
+
+ ## Check if clpbox endpoints lie within raster
+ rbox = [rbox(1, 1) rbox(1, 2); ...
+ rbox(2, 1) rbox(1, 2); ...
+ rbox(2, 1) rbox(2, 2); ...
+ rbox(1, 1) rbox(2, 2); ...
+ rbox(1, 1) rbox(1, 2)];
+ [inp, onp] = inpolygon (clpbox(:, 1), clpbox(:, 2), rbox(:, 1), rbox(:, 2));
+
+ ## Clip clpbox endpoints to pixel borders
+ xpx = [rbox(1, 1) : ((rbox(2, 1) - rbox(1, 1)) / rii.Width) : rbox(2, 1)];
+ ypx = [rbox(1, 2) : ((rbox(3, 2) - rbox(1, 2)) / rii.Height) : rbox(3, 2)];
+ irl = find (clpbox(1, 1) <= xpx)(1);
+ irr = find (clpbox(2, 1) >= xpx)(end);
+ irb = find (clpbox(1, 2) <= ypx)(1);
+ irt = find (clpbox(2, 2) >= ypx)(end);
+
+ ## Copy band struct fields over
+ for jj=1:rii.nbands
+ for ii=1:numel (fldsr)
+ rbo(jj).(fldsr{ii}) = rbi(jj).(fldsr{ii});
+ endfor
+ ## Adapt required fields
+ rbo(jj).bbox(1, 1) = xpx(irl);
+ rbo(jj).bbox(2, 1) = xpx(irr);
+ rbo(jj).bbox(1, 2) = ypx(irb);
+ rbo(jj).bbox(2, 2) = ypx(irt);
+ rbo(jj).data = rbi(jj).data(irb:irt-1, irl:irr-1);
+ endfor
+
+ ## Copy info struct fields over
+ for ii=1:numel (fldsi)
+ rio.(fldsi{ii}) = rii.(fldsi{ii});
+ endfor
+ rio.BitDepth = rii.BitDepth;
+ ## Adapt required fields
+ ## Geotransformation: right-left or left-right
+ rio.GeoTransformation = rii.GeoTransformation;
+ if (rio.GeoTransformation(2) > 0)
+ rio.GeoTransformation(1) = xpx(irl);
+ else
+ rio.GeoTransformation(1) = xpx(irr);
+ endif
+ ## Top-down or bottom-up
+ if (rio.GeoTransformation(6) < 0)
+ rio.GeoTransformation(4) = ypx(irt);
+ else
+ rio.GeoTransformation(4) = ypx(irb);
+ endif
+ rio.Width = irr - irl;
+ rio.Height = irt - irb;
+ rio.nbands = rii.nbands;
+ rio.bbox = rbo.bbox;
+ rio.Filename = "";
+ rio.Filesize = [];
+ rio.FileModDate = "";
+
+endfunction
diff --git a/inst/rasterdraw.m b/inst/rasterdraw.m
index 7723a08..88999ce 100644
--- a/inst/rasterdraw.m
+++ b/inst/rasterdraw.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2015 Philip Nienhuis
+## Copyright (C) 2015-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
@@ -199,5 +199,6 @@ will be drawn\n", ibands(1));
endif
set (gca, "YDir", "normal");
axis equal;
+ axis tight;
endfunction
diff --git a/inst/rasterinfo.m b/inst/rasterinfo.m
index 8aa1f24..5c3b8df 100644
--- a/inst/rasterinfo.m
+++ b/inst/rasterinfo.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2015 Philip Nienhuis
+## Copyright (C) 2015-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
@@ -54,12 +54,29 @@ function [rinfo] = rasterinfo (fname)
[rstatus, binfo] = gdalread (fname, 0);
if (! rstatus)
## Check if we have a geotiff file. If so, get extra info
- if (strcmpi (binfo.FileType, 'geotiff'))
+ if (strcmpi (binfo.FileType, "geotiff"))
rinfo = imfinfo (fname);
rinfo.FileType = binfo.FileType;
rinfo.datatype_name = binfo.datatype_name;
rinfo.GeoTransformation = binfo.GeoTransformation;
rinfo.Projection = binfo.Projection;
+ rinfo.nbands = binfo.nbands;
+ if (isfield (binfo, "bbox"))
+ rinfo.bbox = binfo.bbox;
+ else
+ gt = binfo.GeoTransformation;
+ rinfo.bbox = zeros (2);
+ rinfo.bbox(1, 1) = gt(1);
+ rinfo.bbox(2, 1) = gt(1) + (binfo.Width * gt(2)) + (binfo.Height * gt(3));
+ if (gt(2) < 0)
+ rinfo.bbox(:, 1) = flipud (rinfo.bbox(:, 1));
+ endif
+ rinfo.bbox(1, 2) = gt(4);
+ rinfo.bbox(2, 2) = gt(4) + (binfo.Height * gt(6)) + (binfo.Width * gt(5));
+ if (gt(6) < 0)
+ rinfo.bbox(:, 2) = flipud (rinfo.bbox(:, 2));
+ endif
+ endif
else
rinfo = binfo;
rinfo.Filename = canonicalize_file_name (fname);
diff --git a/inst/rasterread.m b/inst/rasterread.m
index 422a9af..ca24a26 100644
--- a/inst/rasterread.m
+++ b/inst/rasterread.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2015 Philip Nienhuis
+## Copyright (C) 2015-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
@@ -27,7 +27,7 @@
## (if present for the band) a GDAL colortable (see GDAL on-line
## reference).
##
-## Outpur argument @var{binfo} contains various info of the raster file:
+## Output argument @var{binfo} contains various info of the raster file:
## overall bounding box, geotransformation, projection, size, nr. of columns
## and rows, datatype, nr. of bands.
##
diff --git a/inst/rcurve.m b/inst/rcurve.m
new file mode 100644
index 0000000..acceb88
--- /dev/null
+++ b/inst/rcurve.m
@@ -0,0 +1,193 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSEll. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{r} =} rcurve (@var{spheroid}, @var{lat})
+## @deftypefnx {Function File} {@var{r} =} rcurve (@var{type}, @var{spheroid}, @var{lat})
+## @deftypefnx {Function File} {@var{r} =} rcurve (@dots{}, @var{angleUnit})
+## Return the length of a curve based on its type: meridian, parallel, or
+## transverse.
+##
+## Optional input argument @var{type} is one of "meridian", "parallel", or
+## "transverse; default (when left empty or skipped) is "parallel".
+## @var{spheroid} is the spheroid of choice (default: "wgs84"). @var{lat}
+## is the latitude at which the curve length should be computed and can be
+## a numeric scalar, vector or matrix. Output argument @var{r} will have the
+## same size and dimension(s) as @var{lat}.
+##
+## Optional input argument @var{angleUnit} can be either "radians" or "degrees"
+## (= default); just "r" or "d" will do. All character input is
+## case-insensitive.
+##
+## Examples:
+##
+## @example
+## r = rcurve ("parallel", "wgs84", 45)
+## => r =
+## 4.5176e+06
+## Note: this is in meters
+## @end example
+##
+## @example
+## r = rcurve ("", 45)
+## => r =
+## 4.5176e+06
+## @end example
+##
+## @example
+## r = rcurve ("", "", 45)
+## => r =
+## 4.5176e+06
+## @end example
+##
+## @example
+## r = rcurve ("", "", pi/4, "radians")
+## => r =
+## 4.5176e+06
+## @end example
+##
+## @example
+## r = rcurve ("meridian", "wgs84", 45)
+## => r =
+## 6.3674e+06
+## @end example
+##
+## @example
+## r = rcurve ("transverse", "wgs84", 45)
+## => r =
+## 6.3888e+06
+## @end example
+##
+## Also can use structures as inputs:
+## @example
+## r = rcurve("", referenceEllipsoid ("venus"), 45)
+## => r =
+## 4.2793e+06
+## @end example
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9658
+
+function r = rcurve (varargin)
+
+ if (nargin < 2 || nargin > 4)
+ print_usage ();
+ elseif (nargin == 2)
+ ## Neither type nor angleUnit specified
+ type = "parallel";
+ angleUnit = "degrees";
+ spheroid = varargin{1};
+ lat = varargin{2};
+ ip = 1;
+ elseif (nargin >= 3)
+ if (isnumeric (varargin{2}) && isreal (varargin{2}))
+ ## arg{1} = spheroid, type skipped
+ type = "parallel";
+ ip = 1;
+ elseif (isnumeric (varargin{3}) && isreal (varargin{3}))
+ ## arg{1} = type, no angleunit given
+ angleUnit = "degrees";
+ ip = 0;
+ else
+ error ("rcurve.m : real numeric input expected for Lat");
+ endif
+ type = varargin{ip + 1};
+ spheroid = varargin{ip + 2};
+ lat = varargin{ip + 3};
+ endif
+ if (nargin == 4)
+ if (ischar (varargin{4}))
+ angleUnit = varargin{4};
+ else
+ error ("rcurve.m : 'degrees' or 'radians' expected for angleUnits");
+ endif
+ endif
+
+ if isempty (type)
+ type = "parallel";
+ endif
+
+ if isempty (spheroid)
+ E = wgs84Ellipsoid;
+ elseif (isstruct (spheroid))
+ E = spheroid;
+ else
+ E = referenceEllipsoid (spheroid);
+ endif
+
+ if (! ischar (angleUnit) || ! ismember (lower (angleUnit(1)), {"d", "r"}))
+ error ("rcurve.m: angleUnit should be one of 'degrees' or 'radians'")
+ endif
+
+ if (strncmpi (lower (angleUnit), "r", 1) == 1)
+ c_l = cos (lat);
+ s_l = sin (lat);
+ else
+ c_l = cosd (lat);
+ s_l = sind (lat);
+ endif
+
+ ## Insight From: Algorithms for Global Positioning pg 370-372
+
+ e2 = E.Eccentricity ^ 2;
+ R = E.SemimajorAxis;
+ e_p = e2 / (1 - e2);
+
+ N = (R * sqrt ( 1 + e_p) ./ (sqrt (1 + e_p * c_l .^ 2)));
+ switch type
+ case {"meridian"}
+ w = sqrt (1 - e2 .* s_l .^ 2);
+ r = R * (1 - e2 ) ./ (w .^ 3);
+ case {"parallel"}
+ r = N .* c_l;
+ case {"transverse"}
+ r = N;
+ otherwise
+ error ("rcurve: type should be one of'meridian', 'parallel', or 'transverse'")
+ endswitch
+
+endfunction
+
+
+%!test
+%! assert (rcurve ("", 45), 4517590.87885, 10e-6)
+
+%% Row vector
+%!test
+%! assert (rcurve ("", [45; 20]), [4517590.87885; 5995836.38390], 10e-6)
+
+%% Column vector
+%!test
+%! assert (rcurve ("", [45, 20]), [4517590.87885, 5995836.38390], 10e-6)
+
+%% Matrix
+%!test
+%! assert (rcurve ("", [60 45; 35 20]), [3197104.58692, 4517590.87885; 5230426.84020, 5995836.38390], 10e-6)
+
+%!test
+%! assert (rcurve ("", "", 45), 4517590.87885, 10e-6)
+
+%!test
+%! assert (rcurve ("transverse", "", 45), 6388838.29012, 10e-6)
+
+%!test
+%! assert (rcurve ("meridian", "", 45), 6367381.81562, 10e-6)
+
+%!error <angleUnit> rcurve ("","", 45, "km")
+%!error <numeric input expected> rcurve ("", "", "A")
+%!error <numeric input expected> rcurve ("", "", 45i)
+%!error <type> rcurve ('All', "", 45)
diff --git a/inst/reckon.m b/inst/reckon.m
index bfd154d..090ec57 100644
--- a/inst/reckon.m
+++ b/inst/reckon.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2008 Alexander Barth <abarth93@users.sourceforge.net>
+## Copyright (C) 2008-2020 Alexander Barth <abarth93@users.sourceforge.net>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
diff --git a/inst/referenceEllipsoid.m b/inst/referenceEllipsoid.m
new file mode 100644
index 0000000..c7b819e
--- /dev/null
+++ b/inst/referenceEllipsoid.m
@@ -0,0 +1,349 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSEll. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} referenceEllipsoid (@var{name}, @var{unit})
+## Returns the parameters of an ellipsoid.
+##
+## @var{Name} can be the name (e.g., "wgs84") or (integer) EPSG code of a
+## reference ellipoid. Case is not important. If the number or code 0
+## (zero) is specified, referenceEllipsoid echoes a list of implemented
+## ellipsoids to the screen.
+##
+## @var{unit} can be the name of any unit accepted by function
+## validateLengthUnit.m. Also here case is not important.
+##
+## The output consists of a scalar struct with fields "Code" (EPSG code of the
+## ellipsoid), "Name" (name of the ellipsoid), "LengthUnit", "SemimajorAxis",
+## "SemiminorAxis", "InverseFlattening", "Eccentricity", "Flattening",
+## "ThirdFlattening", "MeanRadius", "SurfaceArea", and "Volume".
+##
+## Examples:
+##
+## @example
+## >> E = referenceEllipsoid ("wgs84")
+## E =
+##
+## scalar structure containing the fields:
+##
+## Code = 7030
+## Name = World Geodetic System 1984
+## LengthUnit = meter
+## SemimajorAxis = 6378137
+## SemiminorAxis = 6.3568e+06
+## InverseFlattening = 298.26
+## Eccentricity = 0.081819
+## Flattening = 0.0033528
+## ThirdFlattening = 0.0016792
+## MeanRadius = 6.3710e+06
+## SurfaceArea = 5.1007e+14
+## Volume = 1.0832e+21
+## @end example
+##
+## The code number can be used:
+##
+## @example
+## >> E = referenceEllipsoid (7019)
+## E =
+##
+## scalar structure containing the fields:
+##
+## Code = 7019
+## Name = Geodetic Reference System 1980
+## LengthUnit = meter
+## SemimajorAxis = 6.3781e+06
+## SemiminorAxis = 6.3568e+06
+## InverseFlattening = 298.26
+## Eccentricity = 0.081819
+## Flattening = 0.0033528
+## ThirdFlattening = 0.0016792
+## MeanRadius = 6.371e+06
+## SurfaceArea = 5.1007e+14
+## Volume = 1.0832e+21
+## @end example
+##
+## @example
+## >> E = referenceEllipsoid ("wgs84", "km")
+## E =
+##
+## scalar structure containing the fields:
+##
+## Code = 7030
+## Name = World Geodetic System 1984
+## LengthUnit = km
+## SemimajorAxis = 6378.1
+## SemiminorAxis = 6356.8
+## InverseFlattening = 298.26
+## Eccentricity = 0.081819
+## Flattening = 0.0033528
+## ThirdFlattening = 0.0016792
+## MeanRadius = 6371.0
+## SurfaceArea = 5.1007e+08
+## Volume = 1.0832e+12
+## @end example
+##
+## @seealso{validateLengthUnit, wgs84Ellipsoid}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9634
+
+function Ell = referenceEllipsoid (name="wgs84", unit="meter")
+
+ ## List of implemented codes. To be updated if codes are added.
+ persistent codes;
+ if (isempty (codes))
+ codes = {"7001", "Airy30", " "; ...
+ "7002", "Airy49", " "; ...
+ "7003", "Australia", " "; ...
+ "7004", "Bessel", " "; ...
+ "7008", "Clarke66", " "; ...
+ "7012", "Bessel 1880", " "; ...
+ "7015", "Everest 1830", " "; ...
+ "7019", "grs80", "1980"; ...
+ "7022", "int24", "1924"; ...
+ "7024", "Kras40", "Krasovsky"; ...
+ "7030", "Wgs84", "1984"; ...
+ "7043", "Wgs72", "1972"; ...
+ " ", "Sun", " "; ...
+ " ", "Mercury", " "; ...
+ " ", "Venus", " "; ...
+ " ", "Earth", " "; ...
+ " ", "Moon", " "; ...
+ " ", "Mars", " "; ...
+ " ", "Jupiter", " "; ...
+ " ", "Saturn", " "; ...
+ " ", "Uranus", " "; ...
+ " ", "Neptune", " "; ...
+ " ", "Pluto", " "};
+ endif
+
+ if (isnumeric (name) && isreal (name))
+ name = num2str (fix (name));
+ elseif (! ischar (name))
+ error ("referenceEllipsoid: value must be a string or real number");
+ elseif (strcmp (name, " "))
+ error ("referenceEllipsoid: name required");
+ endif
+
+ if (! ischar (unit))
+ error ("referenceEllipsoid: length name expected for input arg. #2");
+ endif
+
+ switch lower (name)
+ ## Semimajor axis and Inverse flattening from
+ ## USER's HANDBOOK ON DATUM TRANSFORMATIONS INVOLVING WGS 84
+ ## 3rd Edition, July 2003, (Last correction August 2008),
+ ## Special Publication No. 60
+ ## Codenames are from https://epsg.io/
+ ## Planet values from Report of the IAU Working Group on
+ ## CartographicCoordinates and Rotational Elements: 2015
+
+ case lower (codes(1, :))
+ Code = 7001;
+ Name = "Airy 1830";
+ SemimajorAxis = 6377563.396;
+ InverseFlattening = 299.3249646;
+
+ case lower (codes (2, :))
+ Code = 7002;
+ Name = "Airy Modified 1849";
+ SemimajorAxis = 6377340.189;
+ InverseFlattening = 299.3249646;
+
+ case lower (codes (3, :))
+ Code = 7003;
+ Name = "Australian National";
+ SemimajorAxis = 6378160;
+ InverseFlattening = 298.25;
+
+ case lower (codes (4, :))
+ Code = 7004;
+ Name = "Bessel 1841";
+ SemimajorAxis = 6377397.155;
+ InverseFlattening = 299.1528128;
+
+ case lower (codes (5, :))
+ Code = 7008;
+ Name = "Clarke 1866";
+ SemimajorAxis = 6378206.4;
+ InverseFlattening = 294.9786982;
+
+ case lower (codes (6, :))
+ Code = 7012;
+ Name = "Bessel 1880";
+ SemimajorAxis = 6378249.145;
+ InverseFlattening = 293.465;
+
+ case lower (codes (7, :))
+ Code = 7015;
+ Name = "Everest 1830";
+ SemimajorAxis = 6377276.34518;
+ InverseFlattening = 300.8017;
+
+ case lower (codes (8, :))
+ Code = 7019;
+ Name = "Geodetic Reference System 1980";
+ SemimajorAxis = 6378137;
+ InverseFlattening = 298.257222101;
+
+ case lower (codes (9, :))
+ Code = 7022;
+ Name = "International 1924";
+ SemimajorAxis = 6378388;
+ InverseFlattening = 297;
+
+ case lower (codes (10, :))
+ Code = 7024;
+ Name = "Krasovsky 1940";
+ SemimajorAxis = 6378245;
+ InverseFlattening = 298.3;
+
+ case lower (codes (11, :))
+ Code = 7030;
+ Name = "World Geodetic System 1984";
+ SemimajorAxis = 6378137;
+ InverseFlattening = 298.2572235630;
+
+ case lower (codes (12, :))
+ Code = 7043;
+ Name = "World Geodetic System 1972";
+ SemimajorAxis = 6378135;
+ InverseFlattening = 298.26;
+
+ case lower (codes (13, :))
+ Code = " ";
+ Name = "Sun";
+ SemimajorAxis = 695700000;
+ InverseFlattening = 111111;
+
+ case lower (codes (14, :))
+ Code = " ";
+ Name = "Mercury";
+ SemimajorAxis = 2440530;
+ InverseFlattening = 1075;
+
+ case lower (codes (15, :))
+ Code = " ";
+ Name = "Venus";
+ SemimajorAxis = 6051800;
+ InverseFlattening = Inf;
+
+ case lower (codes (16, :))
+ Code = " ";
+ Name = "Earth";
+ SemimajorAxis = 6378137;
+ InverseFlattening = 298.2572235630;
+
+ case lower (codes (17, :))
+ Code = " ";
+ Name = "Moon";
+ SemimajorAxis = 1738100;
+ InverseFlattening = 833.33;
+
+ case lower (codes (18, :))
+ Code = " ";
+ Name = "Mars";
+ SemimajorAxis = 3396190;
+ InverseFlattening = 169.894;
+
+ case lower (codes (19, :))
+ Code = " ";
+ Name = "Jupiter";
+ SemimajorAxis = 71492000;
+ InverseFlattening = 15.4144;
+
+ case lower (codes (20, :))
+ Code = " ";
+ Name = "Saturn";
+ SemimajorAxis = 60268000;
+ InverseFlattening = 10.208;
+
+ case lower (codes (21, :))
+ Code = " ";
+ Name = "Uranus";
+ SemimajorAxis = 25559000;
+ InverseFlattening = 43.616;
+
+ case lower (codes (22, :))
+ Code = " ";
+ Name = "Neptune";
+ SemimajorAxis = 24764000;
+ InverseFlattening = 58.5437;
+
+ case lower (codes (23, :))
+ Code = " ";
+ Name = "Pluto";
+ SemimajorAxis = 1188300;
+ InverseFlattening = INF;
+
+ case "0"
+ ## Show list of codes
+ printf ("\n referenceEllipsoid.m:\n list of implemented ellipsoids:\n");
+ printf (" Code Alias 1 Alias 2\n");
+ printf (" ==== ======= =======\n");
+ for ii=1:size (codes, 1)
+ printf ("%5s %15s %15s\n", codes (ii, :){:});
+ endfor
+ return
+
+ otherwise
+ error ("referenceEllipsoid: ellipsoid %s has not been implemented", name)
+
+ endswitch
+
+ ## Calculations
+ Ell = param_calc (Code, Name, SemimajorAxis, InverseFlattening, unit);
+
+endfunction
+
+
+function ell = param_calc (Code, Name, SemimajorAxis, InvF, unit)
+
+ ell.Code = Code;
+ ell.Name = Name;
+ ratio = unitsratio (unit, "Meters");
+ ell.LengthUnit = unit;
+ Major = SemimajorAxis * ratio;
+ ell.SemimajorAxis = Major;
+ Inverse = InvF;
+ ell.InverseFlattening = InvF;
+ Ecc = flat2ecc (1 / InvF);
+ ell.Eccentricity = Ecc;
+ Minor = minaxis (Major, Ecc);
+ ell.SemiminorAxis = Minor;
+ ell.Flattening = 1 / InvF;
+ ell.ThirdFlattening = (Major - Minor) / (Major + Minor);
+ ell.MeanRadius = (2 * Major + Minor) / 3;
+ ## From Knud Thomsen this results in a max error of 1.061 %:
+ P = 1.6075;
+ Surface = 4 * pi * ((Major^(2*P) + 2 * (Major * Minor)^P) / 3 )^(1/P);
+ ell.SurfaceArea = Surface;
+ ell.Volume = (4 * pi) / 3 * Major^2 * Minor;
+
+endfunction
+
+
+%!test
+%!
+%! E = referenceEllipsoid ("wgs84");
+%! assert ( E.SemiminorAxis, 6356752.314245, 10e-7 )
+%! assert ( E.Eccentricity, 0.081819221456, 10e-8)
+%! assert ( E.Flattening, 1 / 298.2572235630, 10e-8 )
+
+%!error <value must be a string> referenceEllipsoid ( 7i )
+%!error <not been implemented> referenceEllipsoid ( "yy" )
+%!error <name required> referenceEllipsoid ( " " )
diff --git a/inst/removeExtraNanSeparators.m b/inst/removeExtraNanSeparators.m
index 53ab84e..618af11 100644
--- a/inst/removeExtraNanSeparators.m
+++ b/inst/removeExtraNanSeparators.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/roundn.m b/inst/roundn.m
index 3fb697d..8a35fe6 100644
--- a/inst/roundn.m
+++ b/inst/roundn.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2015 Markus Bergholz <markuman@gmail.com>
+## Copyright (C) 2015-2020 Markus Bergholz <markuman@gmail.com>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
diff --git a/inst/shapedraw.m b/inst/shapedraw.m
index d0221c0..dfeb0fb 100644
--- a/inst/shapedraw.m
+++ b/inst/shapedraw.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014,2015 Philip Nienhuis
+## Copyright (C) 2014-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
@@ -68,48 +68,6 @@
## Author: Philip Nienhuis <prnienhuis@users.sf.net>
## Created: 2014-11-15
-## Updates:
-## 2014-11-15 First draft
-## 2014-11-17 Add support for geostructs
-## 2014-11-29 Solid fill (patches)
-## 2014-12-16 Solid fill (polygons) with holes
-## 2014-12-17 MultiPatch plots (3D)
-## 2014-12-18 3D Points/Polylines/-gons
-## 2014-12-30 Transpose npr values for MultiPatch prior to drawing patches
-## 2015-01-01 Fix function name in error message
-## 2015-01-06 Improve plotting of polygons with holes - optimize branch cuts
-## 2015-01-07 Largely rewritten;
-## - Combined line and patch sections into one switch
-## - More rigorous checks on input parameters
-## 2015-01-08 More rewrites, better input checks
-## '' Allow marker indicators for (multi)point
-## 2015-01-09 Add code to check if individual polygon parts are holes (not all)
-## '' Improve code for single color code/stle arguments
-## 2015-01-10 Renamed to shapedraw.m (shapeplot is already in OF-geometry)
-## '' Fixed argument checking for "color" property (swapping if checks)
-## 2015-01-20 Change "color" to "edgecolor" for multipatch
-## '' Add undocumented "center" argument to cope with large coordinates
-## (OpenGL chokes there as it only works with single precision)
-## 2015-01-21 Apply varargin to 3D-patches using "set" command
-## '' Simplify input args, eliminate "clr" arg
-## '' Support for extended mapstructs
-## 2015-01-24 Restructured a bit, debugged MultiPatch drawing
-## 2015-01-27 Texinfo header, check on Z-values
-## 2015-01-30 Simplify ML style struct test; allow Point types (no BoundingBox)
-## 2015-01-31 Fix wrong indexing in MultiPatch-triangle processing
-## 2015-02-03 Swap checks for first color arg and graphics properties
-## 2015-02-04 Check for Z and make it a column vector before calling plot3
-## '' Morefixes for color argument checks
-## 2015-02-11 Eliminate duplicate code, move to subfunc chk_props
-## 2015-02-17 Markerstyle => Marker in point draw switch stmt
-## '' Don't reshape back args in chk_props subfuc
-## '' Improve checks for default color
-## 2015-04-19 Make warning state changes local
-## 2015-07-10 Add try-catch around varargin reshape to catch wrong input
-## '' Simplify polygon plot code
-## '' Fix 3D-plotting for "extended" map/geostructs
-## 2015-12-27 Improve speed of drawing many polygons
-## 2015-12-30 Improve multipatch drawing speed
function [h] = shapedraw (shpf, varargin)
@@ -140,11 +98,6 @@ function [h] = shapedraw (shpf, varargin)
## Nothing to plot
return;
- if (isempty (varargin))
- ## Supply default color
- varargin = {[0.6, 0.6, 0.6]};
- endif
-
elseif (isstruct (shpf))
## Yep. Find out what type
fldn = fieldnames (shpf);
@@ -183,6 +136,11 @@ function [h] = shapedraw (shpf, varargin)
error ("struct name of file name expected");
endif
+ if (isempty (varargin))
+ ## Supply default color
+ varargin = {[0.6, 0.6, 0.6]};
+ endif
+
## 2. Morph XY[Z} data in a suitable form for fastest plotting
## Prepare XY plot. Get vertices & prepare some geometry data
@@ -389,7 +347,7 @@ function [h] = shapedraw (shpf, varargin)
endif
varargin = {"edgecolor", varargin{1}(1, :) "facecolor" varargin{1}(2, :)};
else
- varargin = {"color" clr(1, :) varargin{:}};
+ varargin = {"color", varargin{:}};
endif
endif
endif
@@ -421,7 +379,7 @@ function [h] = shapedraw (shpf, varargin)
## Check for some marker style or marker color code (latter always arg #1)
if (! ismember ("marker", lower (varargin(1, :))) && ...
! ismember (varargin(1), color_codes))
- varargin = { "marker", "." varargin{:} };
+ varargin = { "marker", ".", "linestyle", "none" varargin{:} };
endif
plot (X, Y, varargin{:});
@@ -430,7 +388,7 @@ function [h] = shapedraw (shpf, varargin)
## Check for some marker style or marker color code (latter always arg #1)
if (! ismember ("marker", lower (varargin(1, :))) && ...
! ismember (varargin(1), color_codes))
- varargin = {"marker", "." varargin{:} };
+ varargin = {"marker", ".", "linestyle", "none" varargin{:} };
endif
plot3 (X, Y, Z, varargin{:});
diff --git a/inst/shapeinfo.m b/inst/shapeinfo.m
index b11e040..694f730 100644
--- a/inst/shapeinfo.m
+++ b/inst/shapeinfo.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014,2015 Philip Nienhuis
+## Copyright (C) 2014-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
@@ -89,6 +89,9 @@ function [infs] = shapeinfo (fname)
## Start reading header
fidp = fopen (fname, 'r');
+ if (fidp < 0)
+ error ("shapeinfo: can't open file %s\n", fname);
+ endif
fseek (fidp, 0, "bof");
## Read & check file code
@@ -143,9 +146,12 @@ function [infs] = shapeinfo (fname)
endif
fclose (fidp);
+ finfo = dir (fname);
## Return info
infs.NumFeatures = recn;
+ infs.FileSize = finfo.bytes;
+ infs.LastModified = datestr (finfo.datenum, 0);
## ====================== 3. .dbf ======================
diff --git a/inst/shaperead.m b/inst/shaperead.m
index 7968730..39325b0 100644
--- a/inst/shaperead.m
+++ b/inst/shaperead.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014,2015 Philip Nienhuis
+## Copyright (C) 2014-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
@@ -47,7 +47,7 @@
## @itemx e
## Same as 1 but M and Z type and MultiPatch shape features are accepted. The
## resulting output struct is no more ML-compatible. If the shapefile contains
-## M and/or Z type shape features the mapstruct or gestruct has extra fields M
+## M and/or Z type shape features the mapstruct or geostruct has extra fields M
## and -optionally- Z. Note that MultiPatch shape features may not have
## M-values even if Z-values are present. For MultiPatch shapes another field
## Parts is added, a Px2 array with zero-based indices to the first vertex of
@@ -97,6 +97,13 @@
## characters are significant, case doesn't matter):
##
## @table @code
+## @item Attributes
+## Normally all attributes associated with the shape features will be read
+## and returned in the output struct(s). To limit this to just some
+## attributes, enter a value consisting of a cell array of attribute names to
+## be read. To have no attributes read at all, specify @{@}, an empty cell
+## array.
+##
## @item BoundingBox
## Select only those shape items (features) whose bounding box lies within, or
## intersets in at least one point with the limits of the BoundingBox value (a
@@ -105,18 +112,20 @@
## default!
##
## @item Clip
-## (only useful in conjuction with the BoundingBox property) If a value of 1 or
-## true is supplied, clip all shapes to the bounding box limits. This option
-## may take quite a bit of processing time. If a value of "0" or false is
-## given, do not perform clipping. The default value is 0.
+## (only useful in conjuction with the BoundingBox property) If a value of 1
+## or true is supplied, clip all shapes to the bounding box limits. This
+## option may take quite a bit of processing time. If a value of "0" or false
+## is given, do not perform clipping. The default value is 0.
## Clipping is merely meant to be performed in the XY plane. Clipping 3D
-## shapes may work to some extent but can lead to unpredictable results; this
-## is especially true for MultiPatch shape types.
-## For M and Z type polylines and polygons, the M and Z values are linearly
-## interpolated for segments crossing the bounding box. As no M and Z values
-## can be computed for "new" corner nodes, NaN values are inserted there.
-## For clipping polylines and polygons the Octave-Forge geometry and octclip
-## packages need to be installed and loaded.
+## shapes is supported but may lead to unexpected results.
+## For Z and M type polylines and polygons including MultiPatch and
+## Longitude/Latitude/Height types, Z (Height) and M values for each vertex in
+## the clipped shape feature are simply copied over from the nearest vertex in
+## the original shape feature. This implies that Z and M values of new
+## vertices created on the bounding box edges may be less optimal.
+##
+## For clipping polylines and polygons the Octave-Forge geometry package needs
+## to be installed and loaded.
##
## @item Debug
## If a value of 'true' or 1 is given, shaperead echoes the current record
@@ -134,55 +143,18 @@
## (Only applicable if a Matlab-style output struct is requested). If a value
## of 'true' (or 1) is supplied, return a geostruct rather than a mapstruct.
## If a value of 0 or false is given, return a mapstruct.
-## The mere difference is that in a geostruct the fields 'X' and 'Y' are
-## replaced by 'Long' and 'Lat'. The default value is 'false' (return a
-## mapstruct').
+## The mere difference is that in a geostruct the fields 'X' and 'Y' (and
+## optionally 'Z') are replaced by 'Long' and 'Lat' (and 'Hght'). The
+## default value is 'false' (return a mapstruct').
## @end table
##
+## Ref: http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf
+##
## @seealso{geoshow, mapshow, shapedraw, shapeinfo}
## @end deftypefn
## Author: Philip Nienhuis <prnienhuis@users.sf.net>
## Created: 2014-11-07
-## Updates: **** FIXME Please leave this until the next mapping package release ***
-## 2014-11-07/10 First draft
-## 2014-11-12 Rewrite into more logical struct array
-## 2014-11-13 Fix indexing in NaN row insertion for outopts=1
-## 2014-11-14 Added Matlab-compatible output mapstruct option
-## 2014-11-15 Removed redundant code leading to a 75 X speed increase
-## '' Fix ML-style X and Y coordinates
-## 2014-11-16 Add shape type to each struct element for ML-style output
-## '' Initial input options parsing
-## '' RecordNumbers, UseGeoCoords and BoundingBox implemented
-## 2014-11-17 Double output structs (ML-compatible)
-## 2014-11-18 Polygon clipping & test for geometry package
-## 2014-11-26 Polygon clipping debugged & working
-## 2014-11-28 Return empty cell arr if no shape features read
-## '' Return full mapstruct array for no output arg
-## 2014-11-29 Doubly run inpolygon to include features > and < BoundingBox
-## 2014-11-30 Implement Polygon & Point clipping
-## 2014-12-15 Last debug of Polyline clipping
-## 2014-12-17 Fix MultiPatch reading, esp. detection of missing M (measure)
-## 2014-12-29 Implement selecting cellstr attributes in .dbf file
-## 2014-12-30 Do not transpose npr values for MultiPatch
-## 2015-01-01 Catch null shapes and prevent reading their attributes
-## 2015-01-10 Use .shx (if present) to speed up RecordNumbers searches
-## 2015-01-12 For outopts=1, apply cell2mat to numeric & logical attributes
-## 2015-01-14 Swap outopts values for ml (now 0) and plt style (now 3). In
-## passing, fix wrong switch case id at ~L.651.
-## '' Reserve outopts = 1 for future use (ML-comp. mapstruct w. Z & M
-## atts & multipatch feature type
-## 2015-01-19 Do not include/process NULL shape types once record has been read
-## 2015-01-27 Stricter checks on ML compatibility for outopts=1; fix some
-## wrong references to outopts type, add ext/e for outopts=1;
-## update texinfo header; speed-up reading MultiPatch
-## 2015-04-19 Make warning setting local
-## '' Add filesep to basename
-## 2015-07-10 Separate checks for octclip and geometry package
-## '' Make rest of warning settings local
-## '' Avoid leading filesep in case of no full path name in basename
-## '' Improve struct type check and error message
-## **** FIXME Please leave this until the next mapping package release ***
function [ outs, oatt ] = shaperead (fname, varargin);
@@ -215,59 +187,75 @@ function [ outs, oatt ] = shaperead (fname, varargin);
outopts = 0;
elseif (nargin == 2)
## Assume filename + outopts was supplied
- outopts = varargin{1};
+ if (isempty (varargin{1}))
+ ## Assume ML-style output
+ outopts = 0;
+ else
+ outopts = varargin{1};
+ endif
varargin = {};
elseif (rem (nargin, 2) == 0)
## Even number of input args => Outopts & at least one pair of varargin
outopts = varargin{1};
varargin(1) = [];
else
- ## Only filename and varargin supplied
- outopts = 0;
- ## Just to be sure check, if output type was selected anyway; it would
- ## imply a missing opts value
- if (any (strncmp (varargin{1}, {"ml", "ext", "oct", "dat"}, 1)) || ...
- isnumeric (varargin{1}))
- outopts = varargin{1};
- varargin(1) = [];
+ ## Odd nr; maybe only filename and prop/val(s) supplied, outstyle skipped
+ if (ischar (varargin{1}))
+ ## Check arg#2, must be a property name then
+ if (! ismember (lower (varargin{1}(1:min(3, numel (varargin{1})))), ...
+ {"att", "bou", "cli", "deb", "rec", "sel", "use"}))
+ error ("shaperead: property name expected for arg. #2");
+ endif
+ else
+ ## no outstyle or property => wrong input
+ print_usage ();
endif
+ outopts = 0;
endif
## Check output type arg
if (isnumeric (outopts))
if (outopts < 0 || outopts > 3)
- error ("shaperead: value for arg. #2 out of range 0-3\n");
+ error ("shaperead: arg. #2 integer value out of range 0-3\n");
endif
elseif (ischar (outopts))
outopts = lower (outopts);
if (! any (strncmp (outopts, {"ml", "ext", "oct", "dat"}, 1)))
- error ("shaperead: value for arg. #2 should be one of 'ml', 'ext', 'oct' or 'dat'\n");
+ error ("shaperead: arg. #2 char value should be one of 'ml', 'ext', \
+'oct' or 'dat'\n");
endif
switch outopts
case {"ml", "m"}
outopts = 0;
case {"ext", "e"}
- outopts = 0;
+ outopts = 1;
case {"oct", "o"}
outopts = 2;
case {"dat", "d"}
outopts = 3;
otherwise
- error ("shaperead: illegal value for arg. #2: '%s' - expected 'ml', 'ext', 'oct' of 'dat'", ...
+ error ("shaperead: illegal value for arg. #2: '%s' - expected 'ml', \
+'ext', 'oct' of 'dat'", ...
outopts);
endswitch
else
error ("shaperead: numeric or character type expected for arg. #2\n");
endif
- ## ---------------------- 1. .shx ----------------------
-
+ ## Check .shp file existence
+ fidp = fopen (fname, "r");
+ ## Postpone file opening error until after other provisional input validation
+ if (fidp > 0)
+ ## Temporarily close to avoid file handle leaks during further input checks
+ fclose (fidp);
+ endif
+ ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
## Open .shx file to help speed up seeks to next records. We need this info
## for s_recs check below
have_shx = 0;
fidx = fopen ([bname ".shx"], "r");
if (fidx < 0)
- warning ("shaperead: index file %s not found\n", [fnm ".shx"]);
+ s_recs = [];
else
fseek (fidx, 24, "bof");
fxlng = fread (fidx, 1, "int32", 72, "ieee-be");
@@ -278,79 +266,86 @@ function [ outs, oatt ] = shaperead (fname, varargin);
fclose (fidx);
## Get indices & lengths in bytes
ridx *= 2;
+ have_shx = 1;
+ s_recs = [1 : size(ridx, 1)];
endif
## Parse options; first set defaults
clip = 0;
dbug = 0;
- s_atts = {};
+ s_atts = [];
s_bbox = [];
s_geo = 0;
- s_recs = [];
## Init collection of records that meet BB criteria
bb_union = [];
## Process input args
for ii = 1:2:length (varargin)
- switch (lower (varargin{ii})(1:3))
- case "att"
- ## Select records based on attribute values
- s_atts = varargin{ii+1};
- case "bou"
- ## Select whether record / shape feature points lie inside or outside limits
- try
- s_bbox = double (varargin{ii+1});
- if (numel (s_bbox) != 4)
- error ("shaperead: 2 X 2 numeric array expected for BoundingBox\n");
- endif
- catch
- error ("shaperead: numeric2 X 2 array expected for BoundingBox\n");
- end_try_catch
- ## Initialize supplementary polygon array here
- sbox = [s_bbox(1) s_bbox(2); s_bbox(1) s_bbox(4); s_bbox(3) s_bbox(4); ...
- s_bbox(3) s_bbox(2); s_bbox(1) s_bbox(2)];
- case "cli"
- ## Clip polygons to requested BoundingBox
- try
- clip = logical (varargin{ii+1});
- catch
- error ("numeric or logical value expected for 'Clip'\n");
- end_try_catch
- case "deb"
- ## Set verbose output (count records)
- try
- dbug = logical (varargin{ii+1});
- catch
- error ("numeric or logical value expected for 'Debug'\n");
- end_try_catch
- case "rec"
- ## Select record nrs directly. Check for proper type & clean up
- try
- s_recs = sort (unique (double (varargin{ii+1}(:))));
- have_shx = fidx > 0;
- if (have_shx && any (s_recs > nrec))
- printf ("shaperead: requ. record nos. > nr. of records (%d) ignored\n", ...
- nrec);
- s_recs (find (srecs > nrec)) = [];
+ if (! ischar (varargin{ii}))
+ error ("shaperead: property %d: property name expected but got a %s value", ...
+ (ii+1)/2, class (varargin{ii}));
+ elseif (numel (varargin{ii}) < 3)
+ warning ("shaperead: unknown option '%s' - ignored\n", varargin{ii});
+ else
+ switch (lower (varargin{ii})(1:3))
+ case "att"
+ ## Select records based on attribute values
+ s_atts = varargin{ii+1};
+ case "bou"
+ ## Select whether record/shape features partly lie inside or outside limits
+ try
+ s_bbox = double (varargin{ii+1});
+ if (numel (s_bbox) != 4)
+ error ("shaperead: 2 X 2 numeric array expected for BoundingBox\n");
+ endif
+ catch
+ error ("shaperead: numeric 2 X 2 array expected for BoundingBox\n");
+ end_try_catch
+ ## Initialize supplementary polygon array here
+ sbox = [s_bbox(1) s_bbox(3); s_bbox(2) s_bbox(3); s_bbox(2) s_bbox(4); ...
+ s_bbox(1) s_bbox(4); s_bbox(1) s_bbox(3)];
+ case "cli"
+ ## Clip polygons to requested BoundingBox
+ try
+ clip = logical (varargin{ii+1});
+ catch
+ error ("numeric or logical value expected for 'Clip'\n");
+ end_try_catch
+ case "deb"
+ ## Set verbose output (count records)
+ try
+ dbug = logical (varargin{ii+1});
+ catch
+ error ("numeric or logical value expected for 'Debug'\n");
+ end_try_catch
+ case "rec"
+ ## Select record nrs directly. Check for proper type & clean up
+ try
+ s_recs = sort (unique (double (varargin{ii+1}(:))));
+ if (have_shx && any (s_recs > nrec))
+ printf ("shaperead: requ. record nos. > nr. of records (%d) ignored\n", ...
+ nrec);
+ s_recs (find (srecs > nrec)) = [];
+ endif
+ catch
+ error ("shaperead: numeric value or array expected for RecordNumbers\n");
+ end_try_catch
+ case "sel"
+ ## A hard one, to be implemented later?
+ printf ("shaperead: 'Selector' option not implemented, option ignored\n");
+ case "use"
+ ## Return a geostruct or a mapstruct (default). Only for ML-structs
+ if (outopts != 0)
+ error ("shaperead: UseGeoCoords only valid for Matlab-style output\n");
endif
- catch
- error ("shaperead: numeric value or array expected for RecordNumbers\n");
- end_try_catch
- case "sel"
- ## A hard one, to be implemented later?
- printf ("shaperead: 'Selector' option not implemented, option ignored\n");
- case "use"
- ## Return a geostruct or a mapstruct (default). Only for ML-structs
- if (outopts != 0)
- error ("shaperead: UseGeoCoords only valid for Matlab-style output\n");
- endif
- try
- s_geo = logical (varargin{ii+1});
- catch
- error ("shaperead: logical value type expected for 'UseGeoCoords'\n");
- end_try_catch
- otherwise
- warning ("shaperead: unknown option '%s' - ignored\n", varargin{ii});
- endswitch
+ try
+ s_geo = logical (varargin{ii+1});
+ catch
+ error ("shaperead: logical value type expected for 'UseGeoCoords'\n");
+ end_try_catch
+ otherwise
+ warning ("shaperead: unknown option '%s' - ignored\n", varargin{ii});
+ endswitch
+ endif
endfor
## Post-processing
if (clip)
@@ -358,10 +353,10 @@ function [ outs, oatt ] = shaperead (fname, varargin);
warning ("shaperead: no BoundingBox supplied => Clip option ignored.\n");
clip = 0;
endif
- if (isempty (which ("oc_polybool")))
- ## No OF octclip package?
- printf ("shaperead: function 'oc_polybool' not found. Clip option ignored\n");
- warning (" (OF octclip package installed and loaded?)\n");
+ if (isempty (which ("clipPolygon_clipper")))
+ ## No OF geometry package?
+ printf ("shaperead: function 'clipPolygon' not found. Clip option ignored\n");
+ warning (" (OF geometry package installed and loaded?)\n");
clip = 0;
endif
if (isempty (which ("distancePoints")))
@@ -372,14 +367,21 @@ function [ outs, oatt ] = shaperead (fname, varargin);
endif
endif
+ if (fidp < 0)
+ ## Only now convey file open error message
+ error ("shaperead: can't open file %s\n", fname);
+ else
+ ## Open .shp file
+ fidp = fopen (fname, "r");
+ endif
+ if (fidx < 0)
+ warning ("shaperead: index file %s not found\n", [fnm ".shx"]);
+ endif
+
## ============= Preparations done, now we can start reading ============
## ---------------------- 2. Read .shp file proper ----------------------
## Start reading header
- fidp = fopen (fname, 'r');
- if (fidp < 0)
- error ("shaperead: couldn't open file %s\n", fname);
- endif
fseek (fidp, 0, "bof");
## Read & check file code
@@ -405,12 +407,13 @@ function [ outs, oatt ] = shaperead (fname, varargin);
## it. Initial tries showed no significant speed advantage yet over
## the incremental allocation scheme implemented in Ls. 611+ & 631+
## for oct/plt style output. For ml-style it is much harder as
- ## we'd need to preallocate a potentially very heterogeneous strtuct
+ ## we'd need to preallocate a potentially very heterogeneous struct
## array.
## Prepare for unsupported (in ML output) shape types
unsupp = 0;
- ign_mz = (outopts == 0);
+ ## Echo warning if dbug was set
+ ign_mz = (outopts == 0) && dbug;
## Buffer for record data
BUFSIZE = 10000;
## Read records, 1 by 1. Initialize final array
@@ -419,7 +422,7 @@ function [ outs, oatt ] = shaperead (fname, varargin);
nshp = 0;
## Temp pointer to keep track of array size and increase it w. BUFSIZE rows
ivals = 1;
- ## Assume file has M (measure) values
+ ## Provisionally assume file has M (measure) values
has_M = true;
## Record index number (equals struct element number)
@@ -469,40 +472,50 @@ function [ outs, oatt ] = shaperead (fname, varargin);
case 8 ## Multipoint
## Read bounding box
- tbbox = fread (fidp, 4, "double");
+ tbbox(1:4) = fread (fidp, 4, "double");
tnpt = fread (fidp, 1, "int32");
- val = reshape (fread (fidp, tnpt*2, "double"), 2, [])';
+ val(1:tnpt, 1:2) = reshape (fread (fidp, tnpt*2, "double"), 2, [])';
val(1:tnpt, 3:4) = NaN;
## Copy rec index & type down
- val(2:tnpt, 5:6) = val(1, 5:6);
+ val(2:tnpt, 5:6) = repmat (val(1, 5:6), tnpt-1, 1);
tnpr = 0;
case 18 ## MultipointZ
- tbbox = fread (fidp, 4, "double");
+ tbbox(1:4) = fread (fidp, 4, "double");
tnpt = fread (fidp, 1, "int32");
val(1:tnpt, 1:2) = reshape (fread (fidp, tnpt*2, "double"), 2, [])';
## Z min & max values
tbbox(5:6) = fread (fidp, 2, "double");
## Augment val array with Z values
- val(1:tnpt, 3) = [ rec(ir).points fread(fidp, tnpt, "double")' ];
+ val(1:tnpt, 3) = fread(fidp, tnpt, "double")';
## M min & max values
tbbox(7:8) = fread (fidp, 2, "double");
- ## Augment val array with M values
- val(1:tnpt, 4) = [ rec(ir).points fread(fidp, tnpt, "double")' ];
- ## Copy rec index & type down
- val(2:tnpt, 5:6) = repmat (val(1, 5:6), tnpt-1, 1);
- tnpr = 0;
+ if (val(1, 5) == 1)
+ has_M = checkM (fidp, val(1, 6), shpbox);
+ endif
+ if (has_M)
+ ## Augment val array with M values
+ val(1:tnpt, 4) = fread(fidp, tnpt, "double")';
+ ## Copy rec index & type down
+ val(2:tnpt, 5:6) = repmat (val(1, 5:6), tnpt-1, 1);
+ tnpr = 0;
+ endif
case 28 ## MultipointM
tbbox(1:4) = fread (fidp, 4, "double");
tnpt = fread (fidp, 1, "int32");
val(1:tnpt, 1:2) = reshape (fread (fidp, tnpt*2, "double"), 2, [])';
## Insert empty column for Z
- val(1:tnpt, 4) = NaN;
- ## M min & max values
- bbox(7:8) = fread (fidp, 2, "double");
- ## Augment val array with Z values
- val(1:tnpt, 4) = [ rec(ir).points fread(fidp, tnpt, "double")' ];
+ val(1:tnpt, 3:4) = NaN;
+ if (val(1, 5) == 1)
+ has_M = checkM (fidp, val(1, 6), shpbox);
+ endif
+ if (has_M)
+ ## M min & max values
+ tbbox(7:8) = fread (fidp, 2, "double");
+ ## Augment val array with M values
+ val(1:tnpt, 4) = fread(fidp, tnpt, "double")';
+ endif
## Copy rec index & type down
val(2:tnpt, 5:6) = repmat (val(1, 5:6), tnpt-1, 1);
tnpr = 0;
@@ -531,11 +544,16 @@ function [ outs, oatt ] = shaperead (fname, varargin);
## Z min & max values + data
tbbox(5:6) = fread (fidp, 2, "double");
val(1:tnpt, 3) = fread(fidp, tnpt, "double")';
- ## M min & max values + data
- tbbox(7:8) = fread (fidp, 2, "double");
- val(1:tnpt, 4) = fread(fidp, tnpt, "double")';
- ## Copy rec index and type down
- val(2:tnpt, 5:6) = repmat (val(1, 5:6), tnpt-1, 1);
+ if (val(1, 5) == 1)
+ has_M = checkM (fidp, val(1, 6), shpbox);
+ endif
+ if (has_M)
+ ## M min & max values + data
+ tbbox(7:8) = fread (fidp, 2, "double");
+ val(1:tnpt, 4) = fread(fidp, tnpt, "double")';
+ ## Copy rec index and type down
+ val(2:tnpt, 5:6) = repmat (val(1, 5:6), tnpt-1, 1);
+ endif
case {23, 25} ## Polyline/-gonM
tbbox(1:4) = fread (fidp, 4, "double");
@@ -547,11 +565,16 @@ function [ outs, oatt ] = shaperead (fname, varargin);
val(1:tnpt, 1:2) = reshape (fread (fidp, tnpt*2, "double"), 2, [])';
## No Z data
val(1:tnpt, 3) = NaN;
- ## M min & max values + data
- tbbox(7:8) = fread (fidp, 2, "double");
- val(1:tnpt, 4) = fread(fidp, tnpt, "double")';
- ## Copy rec index and type down
- val(2:tnpt, 5:6) = repmat (val(1, 5:6), tnpt-1, 1);
+ if (val(1, 5) == 1)
+ has_M = checkM (fidp, val(1, 6), shpbox);
+ endif
+ if (has_M)
+ ## M min & max values + data
+ tbbox(7:8) = fread (fidp, 2, "double");
+ val(1:tnpt, 4) = fread(fidp, tnpt, "double")';
+ ## Copy rec index and type down
+ val(2:tnpt, 5:6) = repmat (val(1, 5:6), tnpt-1, 1);
+ endif
case 31 ## Multipatch
tbbox(1:4) = fread (fidp, 4, "double");
@@ -562,16 +585,7 @@ function [ outs, oatt ] = shaperead (fname, varargin);
## Provisionally transpose, this is later on reset after NaN insertion
tnpr = reshape (fread (fidp, nparts*2, "int32")', [], 2)';
## Read XY point coordinates
-% val = NaN (tnpt + size (tnpr, 2) - 1, 6);
-% ipt = 1;
-% ttnpr = [tnpr(1, :) tnpt];
-% for ii=2:numel (ttnpr)
-% val(ipt:ipt+ttnpr(ii)-1, 1:2) = ...
-% reshape (fread (fidp, ttnpr(ii)*2, "double"), 2, [])';
-% ipt +=ttnpr(ii) + 1;
-% endfor
val(1:tnpt, 1:2) = reshape (fread (fidp, tnpt*2, "double"), 2, [])';
-
## Z min & max values + data. Watch out for incomplete .shp file
EOF = (ftell (fidp) > flngt-2);
if (! EOF)
@@ -581,19 +595,7 @@ function [ outs, oatt ] = shaperead (fname, varargin);
fptr = ftell (fidp);
EOF = (fptr > flngt-2);
if (! EOF && has_M)
- ## Check if the file really contains M values: try to read ("peek")
- ## three int32 record numbers matching the next record nr. to be
- ## read, the next record length and 31 (= MultiPatch type)
- tmp = fread (fidp, 2, "int32", 0, "ieee-be");
- tmp(3) = fread (fidp, 1, "int32");
- fseek (fidp, fptr, "bof");
- if (tmp(1) == (val(1, 5) + 1) && tmp(3) == 31)
- ## Undoubtedly a record index number.
- has_M = false;
- ## Empirically the min/maxM values in file header mean nothing, so
- shpbox.M(1) = 0;
- shpbox.M(2) = 0;
- endif
+ has_M = checkM (fidp, val(1, 6), shpbox);
endif
## M min & max values + data. Watch out for incomplete .shp file
if (! EOF && has_M)
@@ -615,9 +617,17 @@ function [ outs, oatt ] = shaperead (fname, varargin);
endswitch
+ ## Check if (X, Y) are valid coordinates
+ if (any (abs (val(:, 1:2)) > 1.797e308))
+ ## Probably +/- Inf
+ rincl = 0;
+ if (dbug)
+ printf ("shape# %d has no finite XY coordinates, skipped\n", ir);
+ endif
+
## Detect if shape lies (partly) within or completely out of BoundingBox.
## Null shapes are automatically skipped
- if (! isempty (s_bbox))
+ elseif (! isempty (s_bbox))
## Just check if any shape feature bounding box corner lies in s_bbox
tbox = [tbbox(1) tbbox(2); tbbox(1) tbbox(4); ...
tbbox(3) tbbox(4); tbbox(3) tbbox(2); tbbox(1) tbbox(2)];
@@ -649,14 +659,10 @@ function [ outs, oatt ] = shaperead (fname, varargin);
if (rincl && clip)
## What to do depends on shape type. Null and MultiPatch aren't clipped
switch val(1, 6)
- case {3, 13, 33} ## Polyline
- ## Temporarily silence Octave a bit, then call clipplg
+ case {3, 13, 23, 5, 15, 25, 31} ## Polyline/gon, Multipatch
+ ## Temporarily silence Octave a bit, then call clippln
warning ("off", "Octave:broadcast", "local");
[val, tnpt, tnpr] = clippln (val, tnpt, tnpr, sbox, val(1, 6));
- case {5, 15, 25, 31} ## Polygon, Multipatch
- ## Temporarily silence Octave a bit, then call clipplg
- warning ("off", "Octave:broadcast", "local");
- [val, tnpt, tnpr] = clipplg (val, tnpt, tnpr, sbox, val(1, 6));
otherwise
warning ("shaperead: unknown shape type found (%d) - ignored\n", ...
val(1, 6));
@@ -682,6 +688,10 @@ function [ outs, oatt ] = shaperead (fname, varargin);
## Keep track of nr of shape features read
++nshp;
+ ## M-values < -1e39 really mean absent values
+ im = find (val(:, 4) < -1e39);
+ val(im, 4) = NaN;
+
if (outopts < 3)
## Prepare an Octave or Matlab style struct optimized for fast
## plotting by inserting a NaN row after each polyline/-gon part
@@ -700,7 +710,7 @@ function [ outs, oatt ] = shaperead (fname, varargin);
## Shape either included by default or it lies in requested BoundingBox
switch outopts
- case {0, 1, "ml", "e"}
+ case {0, 1}
## Return a ML compatible mapstruct. Attributes will be added later
if (ign_mz && val(1, 6) >= 10)
printf ("shaperead: M and Z values ignored for ml-style output\n");
@@ -708,55 +718,60 @@ function [ outs, oatt ] = shaperead (fname, varargin);
endif
switch val(1, 6)
case {1, 11, 21} ## Point
- outs(end+1).Geometry = "Point";
+ outs(end+1, 1).Geometry = "Point";
case {8, 18, 28} ## Multipoint
- outs(end+1).Geometry = "MultiPoint";
+ outs(end+1, 1).Geometry = "MultiPoint";
case {3, 13, 23} ## Polyline
- outs(end+1).Geometry = "Line";
+ outs(end+1, 1).Geometry = "Line";
case {5, 15, 25} ## Polygon
- outs(end+1).Geometry = "Polygon";
+ outs(end+1, 1).Geometry = "Polygon";
otherwise
if (outopts == 1)
## "Extended" ML-style output struct
if (val(1, 6) == 31) ## MultiPatch
- outs(end+1).Geometry = "MultiPatch";
- outs(end).Parts = tnpr;
+ outs(end+1, 1).Geometry = "MultiPatch";
+ outs(end, 1).Parts = tnpr;
endif
else
if (! unsupp)
- warning ("shaperead: shapefile contains unsupported shape types\n");
- outs = [];
+ warning ("shaperead: shapefile contains unsupported shape \
+types\n");
+ outs = oatt = [];
return
endif
- outs(end+1).Geometry = val(1, 6);
+ outs(end+1, 1).Geometry = val(1, 6);
endif
endswitch
- outs(end).BoundingBox = reshape (tbbox(1:4), 2, [])';
+ ## Omit BoundingBox for Point
+ if (all ([1, 11, 21] - val(1, 6)))
+ outs(end).BoundingBox = reshape (tbbox(1:4), 2, [])';
+ endif
if (s_geo)
- outs(end).Lon = val(:, 1)';
- outs(end).Lat = val(:, 2)';
+ outs(end, 1).Lon = val(:, 1)';
+ outs(end, 1).Lat = val(:, 2)';
else
- outs(end).X = val(:, 1)';
- outs(end).Y = val(:, 2)';
+ outs(end, 1).X = val(:, 1)';
+ outs(end, 1).Y = val(:, 2)';
endif
## (ML-incompatible) add Z- and optional M-values, if any
if (outopts == 1 && any (isfinite (val(:, 4))))
- outs(end).M = val(:, 4)';
+ outs(end, 1).M = val(:, 4)';
## FIXME Decision needed if field Geometry should reflect the type
## outs{end}.Geometry = [ outs{end}.Geometry "M"];
endif
if (outopts == 1 && any (isfinite (val(:, 3))))
- outs(end).Z = val(:, 3)';
+ outs(end, 1).Z = val(:, 3)';
## FIXME Decision needed if field Geometry should reflect the type
## outs{end}.Geometry = [ outs{end}.Geometry "Z"];
endif
## (ML-incompatible) add Z- and optional M-values, if any
if (dbug)
## Add a field with shape feature identifier for boundingbox
- outs(end).___Shape_feature_nr___ = val(1, 5);
+ outs(end, 1).___Shape_feature_nr___ = val(1, 5);
endif
- case {2, "oct"}
+ case {2}
+ ## Return an Octave style struct.
## Append to vals array; keep track of appended nr. of rows
lvals = size (vals, 1);
lval = size (val, 1);
@@ -779,8 +794,8 @@ function [ outs, oatt ] = shaperead (fname, varargin);
endif
bbox = [bbox; tbbox];
- case {3, "dat"}
- ## Return an Octave style struct.
+ case {3}
+ ## Return a compressed Octave style struct.
## Simply append to vals array; keep track of appended nr. of rows
lvals = size (vals, 1);
lval = size (val, 1);
@@ -815,12 +830,13 @@ function [ outs, oatt ] = shaperead (fname, varargin);
until (ftell (fidp) > flngt - 3 ||
(have_shx && ir > numel (s_recs))); ## i.e., within last
- ## file bytes or
+ ## file bytes or all
+ ## req. records read
fclose (fidp);
## If no shape was read, or none fitted within BoundingBox, return empty
if (nshp < 1)
- outs = {};
+ outs = oatt = {};
return;
endif
@@ -875,11 +891,16 @@ function [ outs, oatt ] = shaperead (fname, varargin);
endif
## ---------------------- 3. .dbf ----------------------
-
+
+ if (iscell (s_atts) && isempty (s_atts))
+ ## {} indicates no attributes to be read. Morph into ""
+ return;
+ endif
## Check if dbfread is available
if (isempty (which ("dbfread")))
printf ("shaperead.m: dbfread function not found. No attributes will be added.\n");
printf (" (io package installed and loaded?)\n");
+ oatt = {};
else
## Try to read the .dbf file
@@ -887,8 +908,7 @@ function [ outs, oatt ] = shaperead (fname, varargin);
atts = dbfread ([ bname ".dbf" ], s_recs, s_atts);
if (outopts < 2)
## First check if any attributes match fieldnames; if so, pre-/append "_"
- tags = {"shpbox", "vals", "bbox", "npt", "npr", "idx", "Geometry", ...
- "BoundingBox", "X", "Y", "Lat", "Lon"};
+ tags = {"Geometry", "BoundingBox", "X", "Y", "Lat", "Lon"};
for ii=1:numel (tags)
idx = find (strcmp (tags{ii}, atts(1, :)));
if (! isempty (idx))
@@ -908,9 +928,13 @@ function [ outs, oatt ] = shaperead (fname, varargin);
for ii=1:size (atts, 2)
[oatt.(atts{1, ii})] = deal (atts(2:end, ii){:});
endfor
+ oatt = oatt';
endif
else
## Octave output struct. Add attributes columns as struct fields
+ ## Check if any attributes match fieldnames; if so, pre-/append "_"
+ tags = {"shpbox", "vals", "bbox", "npt", "npr", "idx", "Geometry", ...
+ "BoundingBox", "X", "Y", "Lat", "Lon"};
for ii=1:size (atts, 2)
outs.fields(end+1) = atts{1, ii};
if (islogical (atts{2, ii}) || isnumeric (atts{2, ii}))
@@ -928,3 +952,31 @@ function [ outs, oatt ] = shaperead (fname, varargin);
endif
endfunction
+
+
+function has_M = checkM (fidp, ft, shpbox)
+
+ has_M = true;
+ ## Check if we do have M-values. If so, the next 3 4-byte words
+ ## comprise the next record nr, rec length and shape type
+ fptr = ftell (fidp);
+ tmp = fread (fidp, 2, "int32", 0, "ieee-be");
+ tmp(3) = fread (fidp, 1, "int32");
+ if (tmp(1) == 2 && tmp(3) == ft)
+ ## Looks like next record header + next shape feature type => no M
+ has_M = false;
+ shpbox.M(1) = 0;
+ shpbox.M(2) = 0;
+ endif
+ fseek (fidp, fptr, "bof");
+
+endfunction
+
+
+## Only check input validation. I/O is tested in shapewrite.m
+%!error <arg. #2 char value should be one of> shaperead ('tst.shp', 'j');
+%!error <shaperead: arg. #2 integer value out of range> shaperead ('tst.shp', 7);
+%!error <illegal value for arg. #2:> shaperead ('tst.shp', 'deb')
+%!error < property name expected> shaperead ('tst.shp', "ml", []);
+%!error <numeric or logical value expected> shaperead ('tst.shp', 'deb', {});
+
diff --git a/inst/shapewrite.m b/inst/shapewrite.m
index 7e85e24..325c646 100644
--- a/inst/shapewrite.m
+++ b/inst/shapewrite.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014,2015 Philip Nienhuis
+## Copyright (C) 2014-2020 Philip Nienhuis
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
@@ -15,38 +15,56 @@
## -*- texinfo -*-
## @deftypefn {Function File} {@var{status} =} shapewrite (@var{shpstr}, @var{fname})
+## @deftypefnx {Function File} {@var{status} =} shapewrite (@var{shpstr}, @var{fname}, @var{atts})
## Write contents of map- or geostruct to a GIS shape file.
##
## @var{shpstr} must be a valid mapstruct or geostruct, a struct array with an
## entry for each shape feature, with fields Geometry, BoundingBox, and X and Y
## (mapstruct) or Lat and Lon (geostruct). For geostructs, Lat and Lon field
## data will be written as X and Y data. Field Geometry can have data values
-## of "Point", "MultiPoint", "Line", or "Polygon", all case-insensitive. For
-## each shape feature, field BoundingBox should contain the minimum and maximum
-## (X,Y) coordinates in a 2x2 array [minX, minY; maxX, maxY]. The X and Y
-## fields should contain X (or Latitude) and Y (or Longitude) coordinates for
-## each point or vertex as row vectors; for polylines and polygons vertices of
-## each subfeature (if present) should be separated by NaN entries.
+## of "Point", "MultiPoint", "Line" or "Polygon", all case-insensitive.
+## For each shape feature, field BoundingBox should contain the minimum and
+## maximum (X,Y) coordinates in a 2x2 array [minX, minY; maxX, maxY].
+## The X and Y fields should contain X (or Latitude) and Y (or Longitude)
+## coordinates for each point or vertex as row vectors; for
+## poly(lines) and polygons vertices of each subfeature (if present) should be
+## separated by NaN entries.
+##
+## <Geometry>M or <Geometry>Z types (e.g., PointM, PolygonZ) can also be
+## written; shapewrite.m just checks if fields "M" and/or "Z" are present in
+## input mapstruct.
##
## @var{fname} should be a valid shape file name, optionally with a '.shp'
## suffix.
##
-## shapewrite produces 2 or 3 files, i.e. a .shp file (the actual shape file),
-## a .shx file (index file), and if @var{shpstr} contained additional fields,
-## a .dbf file (dBase type 3) with the contents of those additional fields.
+## Optional third input argument @var{atts} is one attribute name or a cellstr
+## array containing a list of attribute names; only those attributes will be
+## written to the .dbf file. Alternatively a struct can be supplied with
+## attibute names contained in field "FieldName" (preferrably camelcased as
+## shown, but Octave treats this field's name as case-insensitive). If
+## argument @var{atts} is omitted all attributes will be written to the
+## shapefile.
+##
+## shapewrite produces 3 files, i.e. a .shp file (the actual shape file),
+## a .shx file (index file), and a .dbf file (dBase type 3) with the contents
+## of additional attribute fields other than Geometry, X/Y or Lat/Lon, M, Z,
+## and BoundingBox. If no additional attributes are present, a .dbf file is
+## created with the shape feature numbers as contents of field "ID".
+##
+## Return argument @var{status} is set to 1 if the complete shape file set was
+## written successfully, to 0 otherwise.
##
-## @var{status} is 1 if the shape file set was written successfully, 0
-## otherwise.
+## Ref: http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf
##
## @seealso{shapedraw, shapeinfo, shaperead}
## @end deftypefn
## Author: Philip Nienhuis <prnienhuis@users.sf.net>
## Created: 2014-12-30
-## 2015-06-26 Three fixes by Jan Heckman
-function [status] = shapewrite (shp, fname, atts=[])
+function [status] = shapewrite (shp, fname="", atts=[])
+ persistent n_dbfspec = 0;
status = 0;
## Input validation
@@ -56,7 +74,7 @@ function [status] = shapewrite (shp, fname, atts=[])
## Assess shape variable type (oct or ml/geo ml/map)
if (! isstruct (shp))
- error ("shapewrite: [map-, geo-] struct expected for argument #2");
+ error ("shapewrite: [map-, geo-] struct expected for argument #1");
else
## Yep. Find out what type
fldn = fieldnames (shp);
@@ -75,36 +93,97 @@ function [status] = shapewrite (shp, fname, atts=[])
## Not a supported struct type
error ("shapewrite: unsupported struct type.\n")
endif
- if (any (ismember ({"M", "Z"}, fldn)))
- otype = -otype;
- endif
+ endif
+
+ ## FIXME struct field type validation
+
+ if (strcmpi (shp(1).Geometry, "MultiPatch"))
+ error ("shapewrite.m: MultiPatch type not supported");
+ endif
+
+ ## Check for dbfwrite function
+ if (isempty (which ("dbfwrite")))
+ error ("shapewrite.m: dbfwrite function not found. (io package installed \
+and loaded?)");
+ return;
endif
## Check file name
- [pth, fnm, ext] = fileparts (fname);
- if (isempty (ext))
- bname = fname;
- fname = [fname ".shp"];
+ if (isempty (fname))
+ error ("shapewrite: filename expected for input argument #2");
else
- ## Later on bname.shx and bname.dbf will be read
- bname = [pth fnm];
+ [pth, fnm, ext] = fileparts (fname);
+ if (isempty (ext))
+ bname = fname;
+ fname = [fname ".shp"];
+ ## Later on bname.shx and bname.dbf will be created
+ elseif (isempty (pth))
+ bname = fnm;
+ else
+ bname = [pth filesep fnm];
+ endif
+ endif
+
+ ## Check optional 3rd argument
+ if (nargin > 2)
+ if (isstruct (atts))
+ if (! n_dbfspec)
+ warning ("shapewrite.m: DbfSpec not implemented; including requested \
+attributes\n");
+ n_dbfspec = 1;
+ endif
+ ## Get attribute names from field "FieldName"; allow lowercase and camelcase
+ ## Index of case-insensitive matches of "FieldName"
+ fnidx = find (ismember ("fieldname", lower (fieldnames (atts))));
+ if (! isempty (fnidx))
+ ## Get fieldnames out of first match
+ atts = {atts.(fieldnames (atts (fnidx)){1})};
+ else
+ warning ("shapewrite.m: no field 'fieldname' (case-insensitive) found \
+in struct\n=> input arg. #3 ignored");
+ endif
+ elseif (! iscellstr (atts) && ! ischar (atts))
+ error ("shapewrite.m: arg.#3: attribute name or cellstr array of attribute names expected");
+ endif
+ ## Check if requested attributes exist at all in shapestruct
+ atts = unique (atts);
+ mtch = ! ismember (atts, fieldnames (shp));
+ if (any (mtch))
+ warning ("shapewrite.m: requested attribute(s) '%s' not in shapestruct\n", ...
+ strjoin (atts(find (mtch)), "', '"));
+ atts(mtch) = [];
+ endif
endif
## Prepare a few things
numfeat = numel (shp);
if (abs (otype) >= 1)
- stype = find (strcmpi (shp(1).Geometry, ...
- {"Point", "MultiPoint", "Line", "Polygon"}));
- stype = [1, 8, 3, 5](stype);
+ stype = strmatch (lower (shp(1).Geometry), ...
+ {"point", "multipoint", "line", "polygon"});
+ if (isempty (stype))
+ ## Not a supported struct type
+ error ("shapewrite: unsupported struct type.\n")
+ else
+ stype = [1, 8, 3, 5](stype);
+ endif
+
+ ## Preprocess geostructs
if (abs (otype) == 2)
## Change Lat/Lon fields into X/Y
[shp.X] = deal (shp.Lon);
[shp.Y] = deal (shp.Lat);
-%% shp = rmfield (shp, {"Lat", "Lon"});
endif
## "Point" need not have a BoundingBox field => add a dummy if not found
if (stype == 1 && ! ismember ("BoundingBox", fldn))
- [shp.BoundingBox] = deal (repmat (zeros (0, 0), numel (shp)));
+ [shp.BoundingBox] = deal ([0, 0; 0, 0]);
+ endif
+ if (any (ismember ({"M", "Z"}, fldn)))
+ if (ismember ({"Z"}, fldn))
+ stype += 10;
+ else
+ stype += 20;
+ endif
+ otype = -otype;
endif
endif
@@ -116,14 +195,16 @@ function [status] = shapewrite (shp, fname, atts=[])
fseek (fids, 0, "bof");
fidx = fopen ([ bname ".shx" ], "w");
if (fidx < 0)
- error ("shapewrite: indexfile %s can't be opened for writing\n", fname);
+ error ("shapewrite: index file %s can't be opened for writing\n", fname);
endif
fseek (fidx, 0, "bof");
- ## Write headers in .shp & .shx (identical). First magic number 9994 + 5 zeros
+ ## Write headers in .shp & .shx (identical). First magic number 9994 + six
+ ## zeros, the last zero in .shp is a placeholder for the yet unknown .shp
+ ## file length.
fwrite (fids, [9994 0 0 0 0 0 0], "int32", 0, "ieee-be");
fwrite (fidx, [9994 0 0 0 0 0], "int32", 0, "ieee-be");
- ## In between here = filelength in 16-bit words (single). For .shx it's known
+ ## For .shx the file length in 16-bit words (single) is known:
fwrite (fidx, ((numfeat * 4) + 50), "int32", 0, "ieee-be");
## Next, shp file version
fwrite (fids, 1000, "int32");
@@ -135,8 +216,8 @@ function [status] = shapewrite (shp, fname, atts=[])
fwrite (fids, [0 0 0 0 0 0 0 0], "double");
fwrite (fidx, [0 0 0 0 0 0 0 0], "double");
## Prepare BoundingBox limits
- xMin = yMin = Inf;
- xMax = yMax = -Inf;
+ xMin = yMin = zMin = mMin = Inf;
+ xMax = yMax = zMax = mMax = -Inf;
## Skip to start of first record position
% fseek (fids, 100, "bof");
@@ -148,74 +229,226 @@ function [status] = shapewrite (shp, fname, atts=[])
## Write record start pos to .shx file
fwrite (fidx, ftell (fids) / 2, "int32", 0, "ieee-be");
+ ## Prepare multipart polygons/lines.
+ ## Find pointers to separators
+ idx = [ 0 find(isnan (shp(ishp).X)) ];
+ ## Eliminate trailing NaN rows
+ if (isnan (shp(ishp).X))
+ idx(end) = [];
+ endif
+ ## Augment idx for later on
+ idx = unique ([ idx (numel (shp(ishp).X)+1) ]);
+ ## Remove NaN separators
+ idn = find (! isfinite (shp(ishp).X));
+ shp(ishp).X(idn) = [];
+ shp(ishp).Y(idn) = [];
+ if (stype >= 10)
+ shp(ishp).Z(idn) = [];
+ endif
+ if (stype >= 10)
+ shp(ishp).M(idn) = [];
+ ## M as need not be present (we assume a NaN value then).
+ ## Set M to a value less than -1e39
+ idm = find (! isfinite (shp(ishp).M));
+ shp(ishp).M(idm) = -1.1e39;
+ endif
+ ## Nr. of vertices
+ npt = numel (shp(ishp).X);
+ ## Pointers to parts
+ nptr = idx(1:end-1) .- [0:numel(idx)-2];
+
## Write record contents
switch (stype)
case 1 ## Point
- ## Record index number
- fwrite (fids, ishp, "int32", 0, "ieee-be");
- ## Record length (fixed)
- reclen = 20;
- fwrite (fids, reclen, "int32", 0, "ieee-be");
+ reclen = 10;
+ ## Write record index number & content length (fixed)
+ fwrite (fids, [ishp reclen], "int32", 0, "ieee-be");
fwrite (fidx, reclen, "int32", 0, "ieee-be");
- ## Shape type
+ ## Write shape type
fwrite (fids, stype, "int32");
## Simply write XY cordinates
fwrite (fids, [shp(ishp).X shp(ishp).Y], "double");
## Update overall BoundingBox
- xMin = min (xMin, shp(ishp).BoundingBox(1, 1));
- xMax = max (xMax, shp(ishp).BoundingBox(1, 2));
- yMin = min (yMin, shp(ishp).BoundingBox(2, 1));
- yMax = max (yMax, shp(ishp).BoundingBox(2, 2));
-
+ xMin = min (xMin, shp(ishp).X);
+ xMax = max (xMax, shp(ishp).X);
+ yMin = min (yMin, shp(ishp).Y);
+ yMax = max (yMax, shp(ishp).Y);
+
+ case 11 ## PointZ
+ reclen = 18;
+ ## Write record index number & content length (fixed)
+ fwrite (fids, [ishp reclen], "int32", 0, "ieee-be");
+ fwrite (fidx, reclen, "int32", 0, "ieee-be");
+ ## Write shape type
+ fwrite (fids, stype, "int32");
+ ## Simply write XY coordinates & Z & M value
+ fwrite (fids, [shp(ishp).X shp(ishp).Y shp(ishp).Z shp(ishp).M], "double");
+ ## Update overall BoundingBox
+ xMin = min (xMin, shp(ishp).X);
+ xMax = max (xMax, shp(ishp).X);
+ yMin = min (yMin, shp(ishp).Y);
+ yMax = max (yMax, shp(ishp).Y);
+ zMin = min (zMin, shp(ishp).Z);
+ zMax = max (zMax, shp(ishp).Z);
+ mMin = min (mMin, shp(ishp).M);
+ mMax = max (mMax, shp(ishp).M);
+
+ case 21 ## PointM
+ reclen = 14;
+ ## Write record index number & content length (fixed)
+ fwrite (fids, [ishp reclen], "int32", 0, "ieee-be");
+ fwrite (fidx, reclen, "int32", 0, "ieee-be");
+ ## Write shape type
+ fwrite (fids, stype, "int32");
+ ## Simply write XY coordinates & M-value
+ fwrite (fids, [shp(ishp).X shp(ishp).Y shp(ishp).M], "double");
+ ## Update overall BoundingBox
+ xMin = min (xMin, shp(ishp).X);
+ xMax = max (xMax, shp(ishp).X);
+ yMin = min (yMin, shp(ishp).Y);
+ yMax = max (yMax, shp(ishp).Y);
+ mMin = min (mMin, shp(ishp).M);
+ mMax = max (mMax, shp(ishp).M);
+
case 8 ## MultiPoint
- ## Record index number
- fwrite (fids, ishp, "int32", 0, "ieee-be");
- ## Record length
- reclen = (48 + 16 * numel (shp(ishp).X)) / 2;
- fwrite (fids, reclen, "int32", 0, "ieee-be");
+ reclen = (40 + 16 * numel (shp(ishp).X)) / 2;
+ ## Write record index number & content length (fixed)
+ fwrite (fids, [ishp reclen], "int32", 0, "ieee-be");
fwrite (fidx, reclen, "int32", 0, "ieee-be");
- ## Shape type
+ ## Write shape type (+4)
fwrite (fids, stype, "int32");
- ## Bounding box
+ ## Write bounding box (+32 -> 36)
fwrite (fids, [shp(ishp).BoundingBox'(:)]', "double");
- xMin = min (xMin, shp(ishp).BoundingBox(1, 1));
- xMax = max (xMax, shp(ishp).BoundingBox(1, 2));
- yMin = min (yMin, shp(ishp).BoundingBox(2, 1));
- yMax = max (yMax, shp(ishp).BoundingBox(2, 2));
- ## Nr of points
+ ## Update overall BoundingBox
+ xMin = min (xMin, min (shp(ishp).X));
+ xMax = max (xMax, max (shp(ishp).X));
+ yMin = min (yMin, min (shp(ishp).Y));
+ yMax = max (yMax, max (shp(ishp).Y));
+ ## Write nr of points and XY data (+4 -> 40 + Nx16)
fwrite (fids, numel (shp(ishp).X), "int32");
- fwrite (fids, [shp(ishp).X shp(ishp).Y]', "double");
- reclen = (44 + 16 * numel (shp(ishp).X)) / 2;
-
+ fwrite (fids, [shp(ishp).X' shp(ishp).Y']', "double");
+
+ case 18 ## MultiPointZ
+ reclen = (72 + 32 * numel (shp(ishp).X)) / 2;
+ ## Write record index number & content length (fixed)
+ fwrite (fids, [ishp reclen], "int32", 0, "ieee-be");
+ fwrite (fidx, reclen, "int32", 0, "ieee-be");
+ ## Write shape type
+ fwrite (fids, stype, "int32");
+ ## Write bounding box
+ fwrite (fids, [shp(ishp).BoundingBox'(:)]', "double");
+ ## Update overall BoundingBox
+ xMin = min (xMin, min (shp(ishp).X));
+ xMax = max (xMax, max (shp(ishp).X));
+ yMin = min (yMin, min (shp(ishp).Y));
+ yMax = max (yMax, max (shp(ishp).Y));
+ zMin = min (zMin, min (shp(ishp).Z));
+ zMax = max (zMax, max (shp(ishp).Z));
+ mMin = min (mMin, min (shp(ishp).M));
+ mMax = max (mMax, max (shp(ishp).M));
+ ## Write Nr of points and XY data
+ fwrite (fids, numel (shp(ishp).X), "int32");
+ fwrite (fids, [shp(ishp).X' shp(ishp).Y']', "double");
+ ## Write Z/M range and -data in turn
+ fwrite (fids, [min(shp(ishp).Z) max(shp(ishp).Z) shp(ishp).Z], "double");
+ fwrite (fids, [min(shp(ishp).M) max(shp(ishp).M) shp(ishp).M], "double");
+
+ case 28 ## MultiPointM
+ reclen = (56 + 24 * numel (shp(ishp).X)) / 2;
+ ## Write record index number & content length (fixed)
+ fwrite (fids, [ishp reclen], "int32", 0, "ieee-be");
+ fwrite (fidx, reclen, "int32", 0, "ieee-be");
+ ## Write shape type
+ fwrite (fids, stype, "int32");
+ ## Write bounding box
+ fwrite (fids, [shp(ishp).BoundingBox'(:)]', "double");
+ ## Update overall BoundingBox
+ xMin = min (xMin, min (shp(ishp).X));
+ xMax = max (xMax, max (shp(ishp).X));
+ yMin = min (yMin, min (shp(ishp).Y));
+ yMax = max (yMax, max (shp(ishp).Y));
+ mMin = min (mMin, min (shp(ishp).M));
+ mMax = max (mMax, max (shp(ishp).M));
+ ## Write Nr of points and XY data
+ fwrite (fids, numel (shp(ishp).X), "int32");
+ fwrite (fids, [shp(ishp).X' shp(ishp).Y']', "double");
+ ## Write M range and M data
+ fwrite (fids, [min(shp(ishp).M) max(shp(ishp).M) shp(ishp).M], "double");
+
case {3, 5} ## Polyline/-gon
- ## Record index number
- fwrite (fids, ishp, "int32", 0, "ieee-be");
- ## Prepare multipart polygons
- idx = [ 0 find(isnan (shp(ishp).X)) ];
- npt = numel (shp(ishp).X) - numel (idx) + 1;
- ## Augment idx for later on, & this trick eliminates trailing NaN rows
- idx = unique ([ idx npt ]);
- ## Record length
+ ## Content length
reclen = (44 + (numel(idx)-1)*4 + 2*8*npt) / 2;
- fwrite (fids, reclen, "int32", 0, "ieee-be");
+ ## Write record index number & content length (fixed)
+ fwrite (fids, [ishp reclen], "int32", 0, "ieee-be");
+ fwrite (fidx, reclen, "int32", 0, "ieee-be");
+ ## Write shape type
+ fwrite (fids, stype, "int32");
+ ## Write bounding box
+ fwrite (fids, [shp(ishp).BoundingBox'(:)]', "double");
+ ## Update overall BoundingBox
+ xMin = min (xMin, min (shp(ishp).X));
+ xMax = max (xMax, max (shp(ishp).X));
+ yMin = min (yMin, min (shp(ishp).Y));
+ yMax = max (yMax, max (shp(ishp).Y));
+ ## Write number of parts, number of points, part pointers
+ fwrite (fids, [(numel(idx)-1) npt nptr ], "int32");
+ fwrite (fids, [shp(ishp).X' shp(ishp).Y']'(:), "double");
+
+ case {13, 15} ## Polyline/-gonZ
+ ## Content length
+ reclen = (76 + (numel(idx)-1)*4 + 4*8*npt) / 2;
+ ## Write record index number & content length (fixed)
+ fwrite (fids, [ishp reclen], "int32", 0, "ieee-be");
fwrite (fidx, reclen, "int32", 0, "ieee-be");
## Shape type
fwrite (fids, stype, "int32");
- ## Bounding box
+ ## Write bounding box
+ fwrite (fids, [shp(ishp).BoundingBox'(:)]', "double");
+ ## Update overall BoundingBox
+ xMin = min (xMin, min (shp(ishp).X));
+ xMax = max (xMax, max (shp(ishp).X));
+ yMin = min (yMin, min (shp(ishp).Y));
+ yMax = max (yMax, max (shp(ishp).Y));
+ zMin = min (zMin, min (shp(ishp).Z));
+ zMax = max (zMax, max (shp(ishp).Z));
+ mMin = min (mMin, min (shp(ishp).M));
+ mMax = max (mMax, max (shp(ishp).M));
+ ## Write number of parts, number of points, part pointers
+ fwrite (fids, [(numel(idx)-1) npt nptr ], "int32");
+ ## Write XY data
+ fwrite (fids, [shp(ishp).X' shp(ishp).Y']'(:), "double");
+ fwrite (fids, [min(shp(ishp).Z) max(shp(ishp).Z) ...
+ shp(ishp).Z], "double");
+ fwrite (fids, [min(shp(ishp).M) max(shp(ishp).M) ...
+ shp(ishp).M], "double");
+
+ case {23, 25} ## Polyline/-gonM
+ ## Content length
+ reclen = (60 + (numel(idx)-1)*4 + 3*8*npt) / 2;
+ ## Write record index number & content length (fixed)
+ fwrite (fids, [ishp reclen], "int32", 0, "ieee-be");
+ fwrite (fidx, reclen, "int32", 0, "ieee-be");
+ ## Write shape type
+ fwrite (fids, stype, "int32");
+ ## Write bounding box
fwrite (fids, [shp(ishp).BoundingBox'(:)]', "double");
- xMin = min (xMin, shp(ishp).BoundingBox(1, 1));
- xMax = max (xMax, shp(ishp).BoundingBox(1, 2));
- yMin = min (yMin, shp(ishp).BoundingBox(2, 1));
- yMax = max (yMax, shp(ishp).BoundingBox(2, 2));
- ## Number of parts, number of points, part pointers
- fwrite (fids, [(numel(idx)-1) npt idx(1:end-1) ], "int32");
- for ii=1:numel(idx)-1
- fwrite (fids, [shp(ishp).X(idx(ii)+1:idx(ii+1)) ...
- shp(ishp).Y(idx(ii)+1:idx(ii+1))](:)', "double");
- endfor
-
+ ## Update overall BoundingBox
+ xMin = min (xMin, min (shp(ishp).X));
+ xMax = max (xMax, max (shp(ishp).X));
+ yMin = min (yMin, min (shp(ishp).Y));
+ yMax = max (yMax, max (shp(ishp).Y));
+ mMin = min (mMin, min (shp(ishp).M));
+ mMax = max (mMax, max (shp(ishp).M));
+ ## Write number of parts, number of points, part pointers
+ fwrite (fids, [(numel(idx)-1) npt nptr ], "int32");
+ ## Write XY data
+ fwrite (fids, [shp(ishp).X' shp(ishp).Y']'(:), "double");
+ ## Write M range and M data
+ fwrite (fids, [min(shp(ishp).M) max(shp(ishp).M) ...
+ shp(ishp).M], "double");
+
otherwise
- ## Future shape types or types unsupported yet (M, Z, MultiPatch)
+ ## Future shape types or types unsupported yet (MultiPatch)
endswitch
endfor
@@ -227,44 +460,173 @@ function [status] = shapewrite (shp, fname, atts=[])
fwrite (fids, flen/2, "int32", 0, "ieee-be");
fseek (fids, 36, "bof");
fwrite (fids, [xMin yMin xMax yMax], "double");
+ ## Same for .shx header
+ xlen = ftell (fidx);
+ fseek (fidx, 24, "bof");
+ fwrite (fidx, xlen/2, "int32", 0, "ieee-be");
fseek (fidx, 36, "bof");
fwrite (fidx, [xMin yMin xMax yMax], "double");
+ if (stype > 10)
+ ## +-Inf & NaN not allowed in shapefiles
+ zm = [zMin zMax mMin mMax];
+ zm (! isfinite (zm)) = -1e-39;
+ fwrite (fids, zm, "double");
+ fwrite (fidx, zm, "double");
+ endif
## Close files
fclose (fids);
fclose (fidx);
- ## Check for dbfwrite function
- if (isempty (which ("dbfwrite")))
- printf ("shaperead.m: dbfwrite function not found. No attributes written.\n");
- printf (" (io package 2.2.6+ installed and loaded?)\n");
- return;
+ ## Write .dbf file.
+ ## Remove basic attributes
+ if (abs (otype) == 1)
+ ## Attributes + shp data in mapstruct
+ shp = rmfield (shp, {"Geometry", "BoundingBox", "X", "Y"});
+ elseif (abs (otype) == 2)
+ ## Attributes + shp data in geostruct
+ shp = rmfield (shp, {"Geometry", "BoundingBox", "Lat", "Lon", "X", "Y"});
+ endif
+ if (otype < 1)
+ shp = rmfield (shp, {"M"});
+ if (isfield (shp, "Z"))
+ shp = rmfield (shp, {"Z"});
+ endif
endif
## Write rest of attributes
if (nargin == 3)
- shp = atts;
- else
- if (otype == 1)
- ## Attributes + shp data in mapstruct
- shp = rmfield (shp, {"Geometry", "BoundingBox", "X", "Y"});
- elseif (otype == 2)
- ## Attributes + shp data in geostruct
- shp = rmfield (shp, {"Geometry", "BoundingBox", "Lat", "Lon", "X", "Y"});
- endif
+ ## Only write user-specified attribute selection
+ fldn = fieldnames (shp);
+ ## First remove regular attributes (with a value for each vertex)
+ fldn (ismember (fldn, {"Geometry", "BoundingBox", "Lat", "Lon", "X", "Y", "M", "Z"})) = [];
+ ## Next, only retain user-specified attributes
+ shp = rmfield (shp, setdiff (fldn, atts));
endif
attribs = cell (numfeat + 1, numel (fieldnames (shp)));
- attribs(1, :) = fieldnames (shp);
- attribs(2:end, :) = (squeeze (struct2cell (shp)))';
-
if (! isempty (attribs))
- try
- status = dbfwrite ([ bname ".dbf"], attribs);
- catch
- warning ("shapewrite: writing attributes to file %s failed\n", [bname ".dbf"]);
- end_try_catch
+ attribs(1, :) = fieldnames (shp);
+ attribs(2:end, :) = (squeeze (struct2cell (shp)))';
else
- status = 1;
+ ## Substitute ID attribute
+ attribs{1, 1} = "ID";
+ [attribs{2:end}] = deal (num2cell ([1:size(shp, 2)]){:});
endif
+ try
+ status = dbfwrite ([ bname ".dbf"], attribs);
+ status = 1;
+ catch
+ warning ("shapewrite: writing attributes to file %s failed\n", [bname ".dbf"]);
+ end_try_catch
endfunction
+
+
+## Test various geometries: (1) Point
+%!test
+%! shp.Geometry = "Point";
+%! shp.X = 10;
+%! shp.Y = 20;
+%! shp.Z = 30;
+%! shp.M = -1;
+%! shp.attr_1 = "Attribute1";
+%! shp.attr_Z = "AttributeA";
+%! shp(2).Geometry = "Point";
+%! shp(2).X = 11;
+%! shp(2).Y = 25;
+%! shp(2).Z = 32;
+%! shp(2).M = -2;
+%! shp(2).attr_1 = "Attribute2";
+%! shp(2).attr_Z = "AttributeB";
+%! fname = tempname ();
+%! sts = shapewrite (shp, fname);
+%! assert (sts, 1, eps);
+%! ## Check index file
+%%! fx = fopen ([fname ".shx"], "r");
+%%! fseek (fx, 100, "bof");
+%%! shxinfo = fread (fx, "*int32", 0, "ieee-be");
+%%! assert (shxinfo, int32 ([50; 36; 72; 36]));
+%%! fclose (fx);
+%! ## Check on filesizes, based on Esri shapewrite doc
+%! assert (stat ([fname ".shp"]).size, 188, 1e-10);
+%! assert (stat ([fname ".shx"]).size, 116, 1e-10);
+%! shp2 = shaperead ([fname ".shp"]);
+%! assert (size (shp2), [2 1]);
+%! flds = fieldnames (shp2);
+%! fields = {"Geometry", "X", "Y", "attr_1", "attr_Z"};
+%! ism = ismember (fields, flds);
+%! ## Do we have only those fields?
+%! assert (numel (ism), numel (fields));
+%! ## Do we have only those fields?
+%! assert (sum (ism), numel (ism));
+%! unlink ([fname ".shp"]);
+%! unlink ([fname ".shx"]);
+%! assert ([shp2.X shp2.Y], [10 11 20 25], 1e-10);
+%! assert ({shp2.Geometry}, {"Point", "Point"});
+%! assert ({shp2.attr_1}, {"Attribute1", "Attribute2"});
+
+## Test various geometries: (2) Line & Polygon
+%!test
+%! shp.Geometry = "Line";
+%! shp.BoundingBox = [9 110; 19 120];
+%! shp.X = [10 110 NaN 9 109];
+%! shp.Y = [20 120 NaN 19 119];
+%! shp.Z = [30 130 NaN 29 129];
+%! shp.M = [-1 1 NaN -11 11];
+%! shp.attr_1 = "Attribute1";
+%! shp.attr_Z = "AttributeA";
+%! shp(2).Geometry = "Line";
+%! shp(2).BoundingBox = [11 211; 24 225];
+%! shp(2).X = [11 111 211 NaN 11 110 NaN 12 200];
+%! shp(2).Y = [25 125 225 NaN 24 124 NaN 26 200];
+%! shp(2).Z = [32 132 232 NaN 31 131 NaN 33 200];
+%! shp(2).M = [-2 NaN -3 NaN -22 22 NaN -33 33];
+%! shp(2).attr_1 = "Attribute2";
+%! shp(2).attr_Z = "AttributeB";
+%! fname = tempname ();
+%! sts = shapewrite (shp, fname);
+%! assert (sts, 1, eps);
+%! ## Check index file
+%%! fx = fopen ([fname ".shx"], "r");
+%%! fseek (fx, 100, "bof");
+%%! shxinfo = fread (fx, "*int32", 0, "ieee-be");
+%%! assert (shxinfo, int32 ([50; 106; 160; 156]));
+%%! fclose (fx);
+%! ## Check on filesizes, based on Esri shapewrite doc
+%! assert (stat ([fname ".shp"]).size, 640, 1e-10);
+%! assert (stat ([fname ".shx"]).size, 116, 1e-10);
+%!
+%! ## Check Matlab-style reading
+%! shp2 = shaperead ([fname ".shp"]);
+%! assert (size (shp2), [2 1]);
+%! flds = fieldnames (shp2);
+%! fields = {"Geometry", "BoundingBox", "X", "Y", "attr_1", "attr_Z"};
+%! ism = ismember (fields, flds);
+%! ## Do we have only those fields?
+%! assert (numel (ism), numel (fields));
+%! ## Do we have only those fields?
+%! assert (sum (ism), numel (ism));
+%! assert ([shp2.BoundingBox], [9 110 11 211; 19 120 24 225], 1e-10);
+%! assert ({shp2.attr_Z}, {"AttributeA", "AttributeB"});
+%!
+%! ## Re-read using M & Z values, read only 2nd feature
+%! shp2 = shaperead ([fname ".shp"], "e", "rec", 2);
+%! assert (size (shp2), [1 1]);
+%! flds = fieldnames (shp2);
+%! fields = {"Geometry", "BoundingBox", "X", "Y", "Z", "M", "attr_1", "attr_Z"};
+%! ism = ismember (fields, flds);
+%! ## Do we have only those fields?
+%! assert (numel (ism), numel (fields));
+%! ## Do we have only those fields?
+%! assert (sum (ism), numel (ism));
+%! ## Check regular numerical values
+%! assert ([shp2.X; shp2.Y; shp2.Z], ...
+%! [11, 111, 211, NaN, 11, 110, NaN, 12, 200; ...
+%! 25, 125, 225, NaN, 24, 124, NaN, 26, 200; ...
+%! 32, 132, 232, NaN, 31, 131, NaN, 33, 200], 1e-10);
+%! ## Check missing M value
+%! assert (shp2.M, [-2, NaN, -3, NaN, -22, 22, NaN, -33, 33], 1e-10);
+%! assert ({shp2.attr_1, shp2.attr_Z}, {"Attribute2", "AttributeB"});
+%!
+%! unlink ([fname ".shp"]);
+%! unlink ([fname ".shx"]);
diff --git a/inst/sm2deg.m b/inst/sm2deg.m
index 878d0f8..829dc65 100644
--- a/inst/sm2deg.m
+++ b/inst/sm2deg.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Pooja Rao <poojarao12@gmail.com>
+## Copyright (C) 2014-2020 Pooja Rao <poojarao12@gmail.com>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
diff --git a/inst/sm2km.m b/inst/sm2km.m
index 42cbc18..6555536 100644
--- a/inst/sm2km.m
+++ b/inst/sm2km.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
+## Copyright (C) 2014-2020 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/sm2nm.m b/inst/sm2nm.m
index 69bf2e2..ed0db61 100644
--- a/inst/sm2nm.m
+++ b/inst/sm2nm.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
+## Copyright (C) 2014-2020 Eugenio Gianniti <eugenio.gianniti@mail.polimi.it>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/sm2rad.m b/inst/sm2rad.m
index 2c914c2..e30525e 100644
--- a/inst/sm2rad.m
+++ b/inst/sm2rad.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Pooja Rao <poojarao12@gmail.com>
+## Copyright (C) 2014-2020 Pooja Rao <poojarao12@gmail.com>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
diff --git a/inst/toDegrees.m b/inst/toDegrees.m
index e0afbfd..ecc9f06 100644
--- a/inst/toDegrees.m
+++ b/inst/toDegrees.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/toRadians.m b/inst/toRadians.m
index d00bc46..96590d1 100644
--- a/inst/toRadians.m
+++ b/inst/toRadians.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/unitsratio.m b/inst/unitsratio.m
index 34167e1..ac26500 100644
--- a/inst/unitsratio.m
+++ b/inst/unitsratio.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/utmzone.m b/inst/utmzone.m
new file mode 100644
index 0000000..11e22f5
--- /dev/null
+++ b/inst/utmzone.m
@@ -0,0 +1,206 @@
+## Copyright (C) 2019-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSEll. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{zone} =} utmzone (@var{lat} , @var{long})
+## @deftypefnx {Function File} {@var{latlon} =} utmzone (@var{zone})
+## @deftypefnx {Function File} {@var{lat}, @var{long} =} utmzone (@var{zone})
+## Returns the zone given a latitude and longitude, or the latitude and
+## longitude ranges given a zone.
+##
+## Examples:
+##
+## @example
+## utmzone (43, -79)
+## => ans =
+## 17T
+## @end example
+##
+## Can also handle the special zones of Norway
+##
+## @example
+## utmzone (60, 5)
+## => ans =
+## 32V
+## @end example
+##
+## For zones:
+##
+## @example
+## utmzone ("17T")
+## => ans =
+## 40 48
+## -84 -78
+## @end example
+##
+## @example
+## [lat, lon] = utmzone ("17T")
+## =>
+## lat =
+## 40 48
+## lon =
+## -84 -78
+## @end example
+##
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9756
+
+function [zone, z2] = utmzone (lat, long)
+
+ ## Since there is no I or O, the last 4 are special cases
+ alphabet = ("CDEFGHJKLMNPQRSTUVWXABYZ");
+
+ if (nargin < 2)
+ if (ischar (lat) && numel (lat) > 1)
+ num = sscanf (lat, "%f");
+ if (num < 1)
+ error ("utmzone.m: positive number expecte for zone");
+ endif
+ let = find (alphabet == upper (lat(end)));
+ if (isempty (let))
+ error ("utmzone.m: incorrect or no letter specified");
+ endif
+ switch upper (lat(end))
+ case "A"
+ lat = [-80 -90];
+ long = [-180 0];
+ zone = [lat; long];
+ case "B"
+ lat = [-80 -90];
+ long = [0 180];
+ zone = [lat; long];
+ case "Y"
+ lat = [84 90];
+ long = [-180 0];
+ zone = [lat; long];
+ case "Z"
+ lat = [84 90];
+ long = [0 180];
+ zone = [lat; long];
+ case "X"
+ lat = [72 84];
+ switch num
+ case 31
+ long = [0 9];
+ case 33
+ long = [9 21];
+ case 35
+ long = [21 33];
+ case 37
+ long = [33 42];
+ case {32, 34, 36}
+ error ("utmzone.m : zone %2iX does not exist", num);
+ otherwise
+ long = [(num - 1) * 6 - 180, (num * 6 - 180)];
+ endswitch
+ zone = [lat; long];
+ case "V"
+ lat = [56 64];
+ switch num
+ case 31
+ long = [0 3];
+ case 32
+ long = [3 12];
+ otherwise
+ long = [(num - 1) * 6 - 180, (num * 6 - 180)];
+ endswitch
+ zone = [lat; long];
+ otherwise
+ lat = [(let - 1) * 8 - 80, (let * 8) - 80 ];
+ long = [(num - 1) * 6 - 180, (num * 6 - 180)];
+ zone = [lat; long];
+ endswitch
+ endif
+ if (nargout ==2)
+ zone = lat;
+ z2 = long;
+ endif
+
+ elseif (isnumeric (lat) && isreal (lat) && isnumeric (long) && isreal (long))
+ lat = mean (lat);
+ long = mean (long);
+ if (lat <= -80)
+ if (long < 0)
+ zone = "A";
+ else
+ zone = "B";
+ endif
+ elseif (lat >= 84)
+ if long < 0
+ zone = "Y";
+ else
+ zone = "Z";
+ endif
+ elseif (lat >= 72 && lat < 84)
+ if (long >= 0 && long < 9)
+ zone = "31X";
+ elseif (long >= 9 && long < 21)
+ zone = "33X";
+ elseif (long >= 21 && long < 33)
+ zone = "35X";
+ elseif (long >= 33 && long < 42)
+ zone = "37X";
+ else
+ zone = zone (lat, long);
+ endif
+ elseif (lat >= 56 && lat < 64)
+ if (long >= 0 && long < 3)
+ zone = "31V";
+ elseif (long >= 3 && long < 12)
+ zone = "32V";
+ endif
+ else
+ zone = zone (lat, long);
+ endif
+
+ else
+ error ("utmzone.m: numeric input expected for LAT en LON");
+
+ endif
+
+endfunction
+
+
+function z = zone (lat, long)
+
+ alphabet = ("CDEFGHJKLMNPQRSTUVWX");
+
+ num = floor ((long + 180) / 6) + 1;
+ idx = -80;
+ ind = 0;
+ while (lat > idx)
+ idx = idx + 8;
+ ind = ind + 1;
+ endwhile
+ let = alphabet (ind);
+ z = strcat (num2str (num), let);
+
+endfunction
+
+
+%!test
+%! lat = 43; ## From Wiki
+%! long = -79;
+%! assert (utmzone (lat, long), "17T")
+%!assert (utmzone ("17T"), [40, 48;-84, -78])
+%!assert (utmzone (60, 5), "32V") ## For Bergen Norway
+%!assert (utmzone ("32V"), [56, 64;3, 12])
+
+%!error <zone> utmzone ("32X")
+%!error <incorrect> utmzone ("31I")
+%!error <incorrect> utmzone ("31O")
diff --git a/inst/validateLengthUnit.m b/inst/validateLengthUnit.m
index 754bed9..7d1c616 100644
--- a/inst/validateLengthUnit.m
+++ b/inst/validateLengthUnit.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2014 Carnë Draug <carandraug@octave.org>
+## Copyright (C) 2014-2020 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
diff --git a/inst/wgs84Ellipsoid.m b/inst/wgs84Ellipsoid.m
new file mode 100644
index 0000000..9235449
--- /dev/null
+++ b/inst/wgs84Ellipsoid.m
@@ -0,0 +1,79 @@
+## Copyright (C) 2018-2020 Philip Nienhuis
+##
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or (at
+## your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSEll. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING. If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} wgs84Ellipsoid (@var{unit})
+##
+## Returns the parameters of the wgs84 ellipsoid. Argument @var{unit} is
+## optional and if given, should be one of the units recognized by function
+## validateLengthUnit().
+##
+## Example:
+## @example
+## >> E = wgs84Ellipsoid
+## E =
+##
+## scalar structure containing the fields:
+##
+## Code = 7030
+## Name = World Geodetic System 1984
+## LengthUnit = meter
+## SemimajorAxis = 6378137
+## SemiminorAxis = 6.3568e+06
+## InverseFlattening = 298.26
+## Eccentricity = 0.081819
+## Flattening = 0.0033528
+## ThirdFlattening = 0.0016792
+## MeanRadius = 6.3710e+06
+## SurfaceArea = 5.1007e+14
+## Volume = 1.0832e+21
+## @end example
+##
+## A unit argument is also accepted:
+## @example
+## >> E = wgs84Ellipsoid ("km")
+## E =
+##
+## scalar structure containing the fields:
+##
+## Code = 7030
+## Name = World Geodetic System 1984
+## LengthUnit = km
+## SemimajorAxis = 6378.1
+## SemiminorAxis = 6356.8
+## InverseFlattening = 298.26
+## Eccentricity = 0.081819
+## Flattening = 0.0033528
+## ThirdFlattening = 0.0016792
+## MeanRadius = 6371.0
+## SurfaceArea = 5.1007e+08
+## Volume = 1.0832e+12
+## @end example
+## @seealso{referenceEllipsoid, validateLengthUnit}
+## @end deftypefn
+
+## Function supplied by anonymous contributor, see:
+## https://savannah.gnu.org/patch/index.php?9634
+
+function Ell = wgs84Ellipsoid (unit)
+
+ if ! nargin
+ unit = "meter";
+ end
+
+ Ell = referenceEllipsoid ("wgs84", unit);
+
+endfunction
diff --git a/inst/wrapTo180.m b/inst/wrapTo180.m
index c54ba95..7b2798e 100644
--- a/inst/wrapTo180.m
+++ b/inst/wrapTo180.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2015 Oscar Monerris Belda
+## Copyright (C) 2015-2020 Oscar Monerris Belda
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/wrapTo2Pi.m b/inst/wrapTo2Pi.m
index 96e5bb2..f6901be 100644
--- a/inst/wrapTo2Pi.m
+++ b/inst/wrapTo2Pi.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2015 Oscar Monerris Belda
+## Copyright (C) 2015-2020 Oscar Monerris Belda
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/inst/wrapTo360.m b/inst/wrapTo360.m
index 0f01e0e..41f2c72 100644
--- a/inst/wrapTo360.m
+++ b/inst/wrapTo360.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2015 Oscar Monerris Belda
+## Copyright (C) 2015-2020 Oscar Monerris Belda
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
@@ -33,7 +33,7 @@
## 10 360 360
## @end example
##
-## @seealso{wrapTo180, wrapToPi, wrapto2Pi}
+## @seealso{wrapTo180, wrapToPi, wrapTo2Pi}
## @end deftypefn
function xwrap = wrapTo360(x)
diff --git a/inst/wrapToPi.m b/inst/wrapToPi.m
index 703c727..c1886d8 100644
--- a/inst/wrapToPi.m
+++ b/inst/wrapToPi.m
@@ -1,4 +1,4 @@
-## Copyright (C) 2015 Oscar Monerris Belda
+## Copyright (C) 2015-2020 Oscar Monerris Belda
##
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
diff --git a/src/aclocal.m4 b/src/aclocal.m4
index 133d264..5722670 100644
--- a/src/aclocal.m4
+++ b/src/aclocal.m4
@@ -1,6 +1,6 @@
-# generated automatically by aclocal 1.14.1 -*- Autoconf -*-
+# generated automatically by aclocal 1.16.1 -*- Autoconf -*-
-# Copyright (C) 1996-2013 Free Software Foundation, Inc.
+# Copyright (C) 1996-2018 Free Software Foundation, Inc.
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
diff --git a/src/configure b/src/configure
index ee5d3c9..f79fb0a 100755
--- a/src/configure
+++ b/src/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for Octave-Forge mapping package 1.2.0+.
+# Generated by GNU Autoconf 2.69 for Octave-Forge mapping package 1.4.0.
#
#
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
@@ -576,8 +576,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='Octave-Forge mapping package'
PACKAGE_TARNAME='octave-forge-mapping-package'
-PACKAGE_VERSION='1.2.0+'
-PACKAGE_STRING='Octave-Forge mapping package 1.2.0+'
+PACKAGE_VERSION='1.4.0'
+PACKAGE_STRING='Octave-Forge mapping package 1.4.0'
PACKAGE_BUGREPORT=''
PACKAGE_URL=''
@@ -617,6 +617,7 @@ infodir
docdir
oldincludedir
includedir
+runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -695,6 +696,7 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -947,6 +949,15 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1084,7 +1095,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir
+ libdir localedir mandir runstatedir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -1197,7 +1208,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures Octave-Forge mapping package 1.2.0+ to adapt to many kinds of systems.
+\`configure' configures Octave-Forge mapping package 1.4.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1237,6 +1248,7 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -1259,7 +1271,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of Octave-Forge mapping package 1.2.0+:";;
+ short | recursive ) echo "Configuration of Octave-Forge mapping package 1.4.0:";;
esac
cat <<\_ACEOF
@@ -1349,7 +1361,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-Octave-Forge mapping package configure 1.2.0+
+Octave-Forge mapping package configure 1.4.0
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1450,7 +1462,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by Octave-Forge mapping package $as_me 1.2.0+, which was
+It was created by Octave-Forge mapping package $as_me 1.4.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -3144,7 +3156,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by Octave-Forge mapping package $as_me 1.2.0+, which was
+This file was extended by Octave-Forge mapping package $as_me 1.4.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -3206,7 +3218,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-Octave-Forge mapping package config.status 1.2.0+
+Octave-Forge mapping package config.status 1.4.0
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/src/configure.ac b/src/configure.ac
index 12b1537..8e1d1c4 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -1,5 +1,5 @@
AC_PREREQ([2.67])
-AC_INIT([Octave-Forge mapping package], [1.2.0+])
+AC_INIT([Octave-Forge mapping package], [1.4.0])
AC_CONFIG_HEADERS([config.h])
AC_ARG_VAR([MKOCTFILE], [Compiler for Octave dynamic-load modules (default=mkoctfile -Wall)])
diff --git a/src/deg2rad.m.in b/src/deg2rad.m.in
index 2ef0038..97dde71 100644
--- a/src/deg2rad.m.in
+++ b/src/deg2rad.m.in
@@ -1,4 +1,4 @@
-## Copyright (C) 2004 Andrew Collier <abcollier@users.sourceforge.net>
+## Copyright (C) 2004-2020 Andrew Collier <abcollier@users.sourceforge.net>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
diff --git a/src/gdalread.cc b/src/gdalread.cc
index cdc13f7..92e7fef 100644
--- a/src/gdalread.cc
+++ b/src/gdalread.cc
@@ -1,8 +1,8 @@
/*
The MIT License (MIT)
-Copyright (C) 2015 Shashank Khare, skhare at hotmail dot com
-Copyright (C) 2015 Philip Nienhuis <prnienhuis@users.sf.net>
+Copyright (C) 2015-2020 Shashank Khare, skhare at hotmail dot com
+Copyright (C) 2015-2020 Philip Nienhuis <prnienhuis@users.sf.net>
Large parts of the below code originate from http://www.gdal.org/gdal_tutorial.html
Acknowledgement: Snow and Avalanche Studies Establishment, DRDO, Chandigarh, India
diff --git a/src/misc.cpp b/src/misc.cpp
index f0902b1..fc40d4a 100644
--- a/src/misc.cpp
+++ b/src/misc.cpp
@@ -1,6 +1,6 @@
/*
The MIT License (MIT)
-Copyright (C) 2015 Shashank Khare
+Copyright (C) 2015-2020 Shashank Khare
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
diff --git a/src/misc.h b/src/misc.h
index a7c8845..fdd66c1 100644
--- a/src/misc.h
+++ b/src/misc.h
@@ -1,6 +1,6 @@
/*
The MIT License (MIT)
-Copyright (C) 2015 Shashank Khare
+Copyright (C) 2015-2020 Shashank Khare
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
diff --git a/src/rad2deg.m.in b/src/rad2deg.m.in
index da74577..495b89d 100644
--- a/src/rad2deg.m.in
+++ b/src/rad2deg.m.in
@@ -1,4 +1,4 @@
-## Copyright (C) 2004 Andrew Collier <abcollier@users.sourceforge.net>
+## Copyright (C) 2004-2020 Andrew Collier <abcollier@users.sourceforge.net>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software