diff options
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 @@ -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 @@ -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 @@ -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 |